[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Rust\n\non:\n  push:\n    branches: [ devel ]\n  pull_request:\n    branches: [ devel ]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - name: Install build deps\n      run: sudo apt-get install libasound2-dev\n    - name: Install latest nightly\n      uses: actions-rs/toolchain@v1\n      with:\n        toolchain: nightly\n        components: rustfmt, clippy\n    - name: Build with nightly\n      uses: actions-rs/cargo@v1.0.1\n      with:\n        command: build\n        toolchain: nightly\n        args: --all-targets\n    - name: Test with nightly\n      uses: actions-rs/cargo@v1.0.1\n      with:\n        command: test\n        toolchain: nightly\n        args: --workspace\n"
  },
  {
    "path": ".gitignore",
    "content": "Cargo.lock\nsite/public\ntarget\n*.bak\n*.bk\n*.pak\n*.pak.d\n.#*\n"
  },
  {
    "path": ".rustfmt.toml",
    "content": "unstable_features = true\n\nimports_granularity = \"Crate\"\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"richter\"\nversion = \"0.1.0\"\nauthors = [\"Cormac O'Brien <cormac@c-obrien.org>\"]\nedition = \"2018\"\n\n[dependencies]\narrayvec = \"0.7\"\nbitflags = \"1.0.1\"\nbumpalo = \"3.4\"\nbyteorder = \"1.3\"\ncgmath = \"0.17.0\"\nchrono = \"0.4.0\"\nenv_logger = \"0.5.3\"\nfailure = \"0.1.8\"\nfutures = \"0.3.5\"\nlazy_static = \"1.0.0\"\nlog = \"0.4.1\"\nnom = \"5.1\"\nnum = \"0.1.42\"\nnum-derive = \"0.1.42\"\npng = \"0.16\"\nrand = { version = \"0.7\", features = [\"small_rng\"] }\nregex = \"0.2.6\"\n# rodio = \"0.12\"\nrodio = { git = \"https://github.com/RustAudio/rodio\", rev = \"82b4952\" }\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\nshaderc = \"0.6.2\"\nslab = \"0.4\"\nstructopt = \"0.3.12\"\nstrum = \"0.18.0\"\nstrum_macros = \"0.18.0\"\nthiserror = \"1.0\"\nuluru = \"2\"\nwgpu = \"0.8\"\n\n# \"winit\" = \"0.22.2\"\n# necessary until winit/#1524 is merged\nwinit = { git = \"https://github.com/chemicstry/winit\", branch = \"optional_drag_and_drop\" }\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright © 2017 Cormac O'Brien\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Richter\n\n[![Build Status](https://travis-ci.org/cormac-obrien/richter.svg?branch=devel)](https://travis-ci.org/cormac-obrien/richter)\n\nA modern implementation of the Quake engine in Rust.\n\n![alt tag](https://i.imgur.com/25nOENn.png)\n\n## Status\n\nRichter is in pre-alpha development, so it's still under heavy construction.\nHowever, the client is nearly alpha-ready -- check out the Client section below to see progress.\n\n### Client\n\nThe client is capable of connecting to and playing on original Quake servers using `sv_protocol 15`.\nTo connect to a Quake server, run\n\n```\n$ cargo run --release --bin quake-client -- --connect <server_ip>:<server_port>\n```\n\nQuake servers run on port 26000 by default.\nI can guarantee compatibility with FitzQuake and its derived engines, as I use the QuakeSpasm server for development (just remember `sv_protocol 15`).\n\nThe client also supports demo playback using the `--demo` option:\n\n```\n$ cargo run --release --bin quake-client -- --demo <demo_file>\n```\n\nThis works for demos in the PAK archives (e.g. `demo1.dem`) or any demos you happen to have placed in the `id1` directory.\n\n#### Feature checklist\n\n- Networking\n  - [x] NetQuake network protocol implementation (`sv_protocol 15`)\n    - [x] Connection protocol implemented\n    - [x] All in-game server commands handled\n    - [x] Carryover between levels\n  - [ ] FitzQuake extended protocol support (`sv_protocol 666`)\n- Rendering\n  - [x] Deferred dynamic lighting\n  - [x] Particle effects\n  - Brush model (`.bsp`) rendering\n    - Textures\n      - [x] Static textures\n      - [x] Animated textures\n      - [x] Alternate animated textures\n      - [x] Liquid texture warping\n      - [ ] Sky texture scrolling (currently partial support)\n    - [x] Lightmaps\n    - [x] Occlusion culling\n  - Alias model (`.mdl`) rendering\n    - [x] Keyframe animation\n      - [x] Static keyframes\n      - [x] Animated keyframes\n    - [ ] Keyframe interpolation\n    - [ ] Ambient lighting\n    - [ ] Viewmodel rendering\n  - UI\n    - [x] Console\n    - [x] HUD\n    - [x] Level intermissions\n    - [ ] On-screen messages\n    - [ ] Menus\n- Sound\n  - [x] Loading and playback\n  - [x] Entity sound\n  - [ ] Ambient sound\n  - [x] Spatial attenuation\n  - [ ] Stereo spatialization\n  - [x] Music\n- Console\n  - [x] Line editing\n  - [x] History browsing\n  - [x] Cvar modification\n  - [x] Command execution\n  - [x] Quake script file execution\n- Demos\n  - [x] Demo playback\n  - [ ] Demo recording\n- File formats\n  - [x] BSP loader\n  - [x] MDL loader\n  - [x] SPR loader\n  - [x] PAK archive extraction\n  - [x] WAD archive extraction\n\n### Server\n\nThe Richter server is still in its early stages, so there's no checklist here yet.\nHowever, 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).\n\n## Building\n\nRichter makes use of feature gates and compiler plugins, which means you'll need a nightly build of\n`rustc`. The simplest way to do this is to download [rustup](https://www.rustup.rs/) and follow the\ndirections.\n\nBecause a Quake distribution contains multiple binaries, this software is packaged as a Cargo\nlibrary project. The source files for binaries are located in the `src/bin` directory and can be run\nwith\n\n    $ cargo run --bin <name>\n\nwhere `<name>` is the name of the source file without the `.rs` extension.\n\n## Legal\n\nThis software is released under the terms of the MIT License (see LICENSE.txt).\n\nThis project is in no way affiliated with id Software LLC, Bethesda Softworks LLC, or ZeniMax Media\nInc. Information regarding the Quake trademark can be found at Bethesda's [legal information\npage](https://bethesda.net/en/document/legal-information).\n\nDue to licensing restrictions, the data files necessary to run Quake cannot be distributed with this\npackage. `pak0.pak`, which contains the files for the first episode (\"shareware Quake\"), can be\nretrieved from id's FTP server at `ftp://ftp.idsoftware.com/idstuff/quake`. The full game can be\npurchased from a number of retailers including Steam and GOG.\n"
  },
  {
    "path": "shaders/alias.frag",
    "content": "#version 450\n\nlayout(location = 0) in vec3 f_normal;\nlayout(location = 1) in vec2 f_diffuse;\n\n// set 1: per-entity\nlayout(set = 1, binding = 1) uniform sampler u_diffuse_sampler;\n\n// set 2: per-texture chain\nlayout(set = 2, binding = 0) uniform texture2D u_diffuse_texture;\n\nlayout(location = 0) out vec4 diffuse_attachment;\nlayout(location = 1) out vec4 normal_attachment;\nlayout(location = 2) out vec4 light_attachment;\n\nvoid main() {\n  diffuse_attachment = texture(\n    sampler2D(u_diffuse_texture, u_diffuse_sampler),\n    f_diffuse\n  );\n\n  // TODO: get ambient light from uniform\n  light_attachment = vec4(0.25);\n\n  // rescale normal to [0, 1]\n  normal_attachment = vec4(f_normal / 2.0 + 0.5, 1.0);\n}\n"
  },
  {
    "path": "shaders/alias.vert",
    "content": "#version 450\n\nlayout(location = 0) in vec3 a_position1;\n// layout(location = 1) in vec3 a_position2;\nlayout(location = 2) in vec3 a_normal;\nlayout(location = 3) in vec2 a_diffuse;\n\nlayout(push_constant) uniform PushConstants {\n  mat4 transform;\n  mat4 model_view;\n} push_constants;\n\nlayout(location = 0) out vec3 f_normal;\nlayout(location = 1) out vec2 f_diffuse;\n\n// convert from Quake coordinates\nvec3 convert(vec3 from) {\n  return vec3(-from.y, from.z, -from.x);\n}\n\nvoid main() {\n  f_normal = mat3(transpose(inverse(push_constants.model_view))) * convert(a_normal);\n  f_diffuse = a_diffuse;\n  gl_Position = push_constants.transform * vec4(convert(a_position1), 1.0);\n}\n"
  },
  {
    "path": "shaders/blit.frag",
    "content": "#version 450\n\nlayout(location = 0) in vec2 f_texcoord;\n\nlayout(location = 0) out vec4 color_attachment;\n\nlayout(set = 0, binding = 0) uniform sampler u_sampler;\nlayout(set = 0, binding = 1) uniform texture2D u_color;\n\nvoid main() {\n  color_attachment = texture(sampler2D(u_color, u_sampler), f_texcoord);\n}\n"
  },
  {
    "path": "shaders/blit.vert",
    "content": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) out vec2 f_texcoord;\n\nvoid main() {\n  f_texcoord = a_texcoord;\n  gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0);\n}\n"
  },
  {
    "path": "shaders/brush.frag",
    "content": "#version 450\n#define LIGHTMAP_ANIM_END (255)\n\nconst uint TEXTURE_KIND_REGULAR = 0;\nconst uint TEXTURE_KIND_WARP = 1;\nconst uint TEXTURE_KIND_SKY = 2;\n\nconst float WARP_AMPLITUDE = 0.15;\nconst float WARP_FREQUENCY = 0.25;\nconst float WARP_SCALE = 1.0;\n\nlayout(location = 0) in vec3 f_normal;\nlayout(location = 1) in vec2 f_diffuse; // also used for fullbright\nlayout(location = 2) in vec2 f_lightmap;\nflat layout(location = 3) in uvec4 f_lightmap_anim;\n\nlayout(push_constant) uniform PushConstants {\n  layout(offset = 128) uint texture_kind;\n} push_constants;\n\n// set 0: per-frame\nlayout(set = 0, binding = 0) uniform FrameUniforms {\n    float light_anim_frames[64];\n    vec4 camera_pos;\n    float time;\n    bool r_lightmap;\n} frame_uniforms;\n\n// set 1: per-entity\nlayout(set = 1, binding = 1) uniform sampler u_diffuse_sampler; // also used for fullbright\nlayout(set = 1, binding = 2) uniform sampler u_lightmap_sampler;\n\n// set 2: per-texture\nlayout(set = 2, binding = 0) uniform texture2D u_diffuse_texture;\nlayout(set = 2, binding = 1) uniform texture2D u_fullbright_texture;\nlayout(set = 2, binding = 2) uniform TextureUniforms {\n    uint kind;\n} texture_uniforms;\n\n// set 3: per-face\nlayout(set = 3, binding = 0) uniform texture2D u_lightmap_texture[4];\n\nlayout(location = 0) out vec4 diffuse_attachment;\nlayout(location = 1) out vec4 normal_attachment;\nlayout(location = 2) out vec4 light_attachment;\n\nvec4 calc_light() {\n    vec4 light = vec4(0.0, 0.0, 0.0, 0.0);\n    for (int i = 0; i < 4 && f_lightmap_anim[i] != LIGHTMAP_ANIM_END; i++) {\n        float map = texture(\n            sampler2D(u_lightmap_texture[i], u_lightmap_sampler),\n            f_lightmap\n        ).r;\n\n        // range [0, 4]\n        float style = frame_uniforms.light_anim_frames[f_lightmap_anim[i]];\n        light[i] = map * style;\n    }\n\n    return light;\n}\n\nvoid main() {\n    switch (push_constants.texture_kind) {\n        case TEXTURE_KIND_REGULAR:\n            diffuse_attachment = texture(\n                sampler2D(u_diffuse_texture, u_diffuse_sampler),\n                f_diffuse\n            );\n\n            float fullbright = texture(\n                sampler2D(u_fullbright_texture, u_diffuse_sampler),\n                f_diffuse\n            ).r;\n\n            if (fullbright != 0.0) {\n                light_attachment = vec4(0.25);\n            } else {\n                light_attachment = calc_light();\n            }\n            break;\n\n        case TEXTURE_KIND_WARP:\n            // note the texcoord transpose here\n            vec2 wave1 = 3.14159265359\n                * (WARP_SCALE * f_diffuse.ts\n                    + WARP_FREQUENCY * frame_uniforms.time);\n\n            vec2 warp_texcoord = f_diffuse.st + WARP_AMPLITUDE\n                * vec2(sin(wave1.s), sin(wave1.t));\n\n            diffuse_attachment = texture(\n                sampler2D(u_diffuse_texture, u_diffuse_sampler),\n                warp_texcoord\n            );\n            light_attachment = vec4(0.25);\n            break;\n\n        case TEXTURE_KIND_SKY:\n            vec2 base = mod(f_diffuse + frame_uniforms.time, 1.0);\n            vec2 cloud_texcoord = vec2(base.s * 0.5, base.t);\n            vec2 sky_texcoord = vec2(base.s * 0.5 + 0.5, base.t);\n\n            vec4 sky_color = texture(\n                sampler2D(u_diffuse_texture, u_diffuse_sampler),\n                sky_texcoord\n            );\n            vec4 cloud_color = texture(\n                sampler2D(u_diffuse_texture, u_diffuse_sampler),\n                cloud_texcoord\n            );\n\n            // 0.0 if black, 1.0 otherwise\n            float cloud_factor;\n            if (cloud_color.r + cloud_color.g + cloud_color.b == 0.0) {\n                cloud_factor = 0.0;\n            } else {\n                cloud_factor = 1.0;\n            }\n            diffuse_attachment = mix(sky_color, cloud_color, cloud_factor);\n            light_attachment = vec4(0.25);\n            break;\n\n        // not possible\n        default:\n            break;\n    }\n\n    // rescale normal to [0, 1]\n    normal_attachment = vec4(f_normal / 2.0 + 0.5, 1.0);\n}\n"
  },
  {
    "path": "shaders/brush.vert",
    "content": "#version 450\n\nconst uint TEXTURE_KIND_NORMAL = 0;\nconst uint TEXTURE_KIND_WARP = 1;\nconst uint TEXTURE_KIND_SKY = 2;\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec3 a_normal;\nlayout(location = 2) in vec2 a_diffuse;\nlayout(location = 3) in vec2 a_lightmap;\nlayout(location = 4) in uvec4 a_lightmap_anim;\n\nlayout(push_constant) uniform PushConstants {\n  mat4 transform;\n  mat4 model_view;\n  uint texture_kind;\n} push_constants;\n\nlayout(location = 0) out vec3 f_normal;\nlayout(location = 1) out vec2 f_diffuse;\nlayout(location = 2) out vec2 f_lightmap;\nlayout(location = 3) out uvec4 f_lightmap_anim;\n\nlayout(set = 0, binding = 0) uniform FrameUniforms {\n    float light_anim_frames[64];\n    vec4 camera_pos;\n    float time;\n} frame_uniforms;\n\n// convert from Quake coordinates\nvec3 convert(vec3 from) {\n  return vec3(-from.y, from.z, -from.x);\n}\n\nvoid main() {\n    if (push_constants.texture_kind == TEXTURE_KIND_SKY) {\n        vec3 dir = a_position - frame_uniforms.camera_pos.xyz;\n        dir.z *= 3.0;\n\n        // the coefficients here are magic taken from the Quake source\n        float len = 6.0 * 63.0 / length(dir);\n        dir = vec3(dir.xy * len, dir.z);\n        f_diffuse = (mod(8.0 * frame_uniforms.time, 128.0) + dir.xy) / 128.0;\n    } else {\n        f_diffuse = a_diffuse;\n    }\n\n    f_normal = mat3(transpose(inverse(push_constants.model_view))) * convert(a_normal);\n    f_lightmap = a_lightmap;\n    f_lightmap_anim = a_lightmap_anim;\n    gl_Position = push_constants.transform * vec4(convert(a_position), 1.0);\n\n}\n"
  },
  {
    "path": "shaders/deferred.frag",
    "content": "#version 450\n\n// if this is changed, it must also be changed in client::entity\nconst uint MAX_LIGHTS = 32;\n\nlayout(location = 0) in vec2 a_texcoord;\n\nlayout(set = 0, binding = 0) uniform sampler u_sampler;\nlayout(set = 0, binding = 1) uniform texture2DMS u_diffuse;\nlayout(set = 0, binding = 2) uniform texture2DMS u_normal;\nlayout(set = 0, binding = 3) uniform texture2DMS u_light;\nlayout(set = 0, binding = 4) uniform texture2DMS u_depth;\nlayout(set = 0, binding = 5) uniform DeferredUniforms {\n  mat4 inv_projection;\n  uint light_count;\n  uint _pad1;\n  uvec2 _pad2;\n  vec4 lights[MAX_LIGHTS];\n} u_deferred;\n\nlayout(location = 0) out vec4 color_attachment;\n\nvec3 dlight_origin(vec4 dlight) {\n  return dlight.xyz;\n}\n\nfloat dlight_radius(vec4 dlight) {\n  return dlight.w;\n}\n\nvec3 reconstruct_position(float depth) {\n  float x = a_texcoord.s * 2.0 - 1.0;\n  float y = (1.0 - a_texcoord.t) * 2.0 - 1.0;\n  vec4 ndc = vec4(x, y, depth, 1.0);\n  vec4 view = u_deferred.inv_projection * ndc;\n  return view.xyz / view.w;\n}\n\nvoid main() {\n  ivec2 dims = textureSize(sampler2DMS(u_diffuse, u_sampler));\n  ivec2 texcoord = ivec2(vec2(dims) * a_texcoord);\n  vec4 in_color = texelFetch(sampler2DMS(u_diffuse, u_sampler), texcoord, gl_SampleID);\n\n  // scale from [0, 1] to [-1, 1]\n  vec3 in_normal = 2.0\n    * texelFetch(sampler2DMS(u_normal, u_sampler), texcoord, gl_SampleID).xyz\n    - 1.0;\n\n  // Double to restore overbright values.\n  vec4 in_light = 2.0 * texelFetch(sampler2DMS(u_light, u_sampler), texcoord, gl_SampleID);\n\n  float in_depth = texelFetch(sampler2DMS(u_depth, u_sampler), texcoord, gl_SampleID).x;\n  vec3 position = reconstruct_position(in_depth);\n\n  vec4 out_color = in_color;\n\n  float light = in_light.x + in_light.y + in_light.z + in_light.w;\n  for (uint i = 0; i < u_deferred.light_count && i < MAX_LIGHTS; i++) {\n    vec4 dlight = u_deferred.lights[i];\n    vec3 dir = normalize(position - dlight_origin(dlight));\n    float dist = abs(distance(dlight_origin(dlight), position));\n    float radius = dlight_radius(dlight);\n\n    if (dist < radius && dot(dir, in_normal) < 0.0) {\n      // linear attenuation\n      light += (radius - dist) / radius;\n    }\n  }\n\n  color_attachment = vec4(light * out_color.rgb, 1.0);\n}\n"
  },
  {
    "path": "shaders/deferred.vert",
    "content": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) out vec2 f_texcoord;\n\nvoid main() {\n  f_texcoord = a_texcoord;\n  gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0);\n}\n"
  },
  {
    "path": "shaders/glyph.frag",
    "content": "#version 450\n#extension GL_EXT_nonuniform_qualifier : require\n\nlayout(location = 0) in vec2 f_texcoord;\nlayout(location = 1) flat in uint f_layer;\n\nlayout(location = 0) out vec4 output_attachment;\n\nlayout(set = 0, binding = 0) uniform sampler u_sampler;\nlayout(set = 0, binding = 1) uniform texture2D u_texture[256];\n\nvoid main() {\n  vec4 color = texture(sampler2D(u_texture[f_layer], u_sampler), f_texcoord);\n  if (color.a == 0) {\n    discard;\n  } else {\n    output_attachment = color;\n  }\n}\n"
  },
  {
    "path": "shaders/glyph.vert",
    "content": "#version 450\n\n// vertex rate\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\n// instance rate\nlayout(location = 2) in vec2 a_instance_position;\nlayout(location = 3) in vec2 a_instance_scale;\nlayout(location = 4) in uint a_instance_layer;\n\nlayout(location = 0) out vec2 f_texcoord;\nlayout(location = 1) out uint f_layer;\n\nvoid main() {\n  f_texcoord = a_texcoord;\n  f_layer = a_instance_layer;\n  gl_Position = vec4(a_instance_scale * a_position + a_instance_position, 0.0, 1.0);\n}\n"
  },
  {
    "path": "shaders/particle.frag",
    "content": "#version 450\n\nlayout(location = 0) in vec2 f_texcoord;\n\nlayout(push_constant) uniform PushConstants {\n  layout(offset = 64) uint color;\n} push_constants;\n\nlayout(set = 0, binding = 0) uniform sampler u_sampler;\nlayout(set = 0, binding = 1) uniform texture2D u_texture[256];\n\nlayout(location = 0) out vec4 diffuse_attachment;\n// layout(location = 1) out vec4 normal_attachment;\nlayout(location = 2) out vec4 light_attachment;\n\nvoid main() {\n  vec4 tex_color = texture(\n    sampler2D(u_texture[push_constants.color], u_sampler),\n    f_texcoord\n  );\n\n  if (tex_color.a == 0.0) {\n    discard;\n  }\n\n  diffuse_attachment = tex_color;\n  light_attachment = vec4(0.25);\n}\n"
  },
  {
    "path": "shaders/particle.vert",
    "content": "#version 450\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(push_constant) uniform PushConstants {\n  mat4 transform;\n} push_constants;\n\nlayout(location = 0) out vec2 f_texcoord;\n\nvoid main() {\n  f_texcoord = a_texcoord;\n  gl_Position = push_constants.transform * vec4(a_position, 1.0);\n}\n"
  },
  {
    "path": "shaders/postprocess.frag",
    "content": "#version 450\n\nlayout(location = 0) in vec2 a_texcoord;\n\nlayout(location = 0) out vec4 color_attachment;\n\nlayout(set = 0, binding = 0) uniform sampler u_sampler;\nlayout(set = 0, binding = 1) uniform texture2DMS u_color;\nlayout(set = 0, binding = 2) uniform PostProcessUniforms {\n  vec4 color_shift;\n} postprocess_uniforms;\n\nvoid main() {\n  ivec2 dims = textureSize(sampler2DMS(u_color, u_sampler));\n  ivec2 texcoord = ivec2(vec2(dims) * a_texcoord);\n\n  vec4 in_color = texelFetch(sampler2DMS(u_color, u_sampler), texcoord, gl_SampleID);\n\n  float src_factor = postprocess_uniforms.color_shift.a;\n  float dst_factor = 1.0 - src_factor;\n  vec4 color_shifted = src_factor * postprocess_uniforms.color_shift\n    + dst_factor * in_color;\n\n  color_attachment = color_shifted;\n}\n"
  },
  {
    "path": "shaders/postprocess.vert",
    "content": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) out vec2 f_texcoord;\n\nvoid main() {\n  f_texcoord = a_texcoord;\n  gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0);\n}\n"
  },
  {
    "path": "shaders/quad.frag",
    "content": "#version 450\n\nlayout(location = 0) in vec2 f_texcoord;\n\nlayout(location = 0) out vec4 color_attachment;\n\nlayout(set = 0, binding = 0) uniform sampler quad_sampler;\nlayout(set = 1, binding = 0) uniform texture2D quad_texture;\n\nvoid main() {\n  vec4 color = texture(sampler2D(quad_texture, quad_sampler), f_texcoord);\n  if (color.a == 0) {\n    discard;\n  } else {\n    color_attachment = color;\n  }\n}\n"
  },
  {
    "path": "shaders/quad.vert",
    "content": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) out vec2 f_texcoord;\n\nlayout(set = 2, binding = 0) uniform QuadUniforms {\n  mat4 transform;\n} quad_uniforms;\n\nvoid main() {\n  f_texcoord = a_texcoord;\n  gl_Position = quad_uniforms.transform * vec4(a_position, 0.0, 1.0);\n}\n"
  },
  {
    "path": "shaders/sprite.frag",
    "content": "#version 450\n\nlayout(location = 0) in vec3 f_normal;\nlayout(location = 1) in vec2 f_diffuse;\n\n// set 1: per-entity\nlayout(set = 1, binding = 1) uniform sampler u_diffuse_sampler;\n\n// set 2: per-texture chain\nlayout(set = 2, binding = 0) uniform texture2D u_diffuse_texture;\n\nlayout(location = 0) out vec4 diffuse_attachment;\nlayout(location = 1) out vec4 normal_attachment;\nlayout(location = 2) out vec4 light_attachment;\n\nvoid main() {\n  diffuse_attachment = texture(sampler2D(u_diffuse_texture, u_diffuse_sampler), f_diffuse);\n\n  // rescale normal to [0, 1]\n  normal_attachment = vec4(f_normal / 2.0 + 0.5, 1.0);\n  light_attachment = vec4(1.0, 1.0, 1.0, 1.0);\n}\n"
  },
  {
    "path": "shaders/sprite.vert",
    "content": "#version 450\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec3 a_normal;\nlayout(location = 2) in vec2 a_diffuse;\n\nlayout(location = 0) out vec3 f_normal;\nlayout(location = 1) out vec2 f_diffuse;\n\nlayout(set = 0, binding = 0) uniform FrameUniforms {\n  float light_anim_frames[64];\n  vec4 camera_pos;\n  float time;\n} frame_uniforms;\n\nlayout(set = 1, binding = 0) uniform EntityUniforms {\n  mat4 u_transform;\n  mat4 u_model;\n} entity_uniforms;\n\n// convert from Quake coordinates\nvec3 convert(vec3 from) {\n  return vec3(-from.y, from.z, -from.x);\n}\n\nvoid main() {\n  f_normal = mat3(transpose(inverse(entity_uniforms.u_model))) * convert(a_normal);\n  f_diffuse = a_diffuse;\n  gl_Position = entity_uniforms.u_transform\n    * vec4(convert(a_position), 1.0);\n}\n"
  },
  {
    "path": "site/config.toml",
    "content": "# The URL the site will be built for\nbase_url = \"http://c-obrien.org/richter\"\n\n# Whether to automatically compile all Sass files in the sass directory\ncompile_sass = true\n\n# Whether to do syntax highlighting\n# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Gutenberg\nhighlight_code = true\n\n# Whether to build a search index to be used later on by a JavaScript library\nbuild_search_index = true\n\ntheme = \"richter\"\n\n[extra]\n# Put all your custom variables here\n"
  },
  {
    "path": "site/content/_index.md",
    "content": "+++\ntitle = \"Richter\"\ntemplate = \"index.html\"\ndescription = \"An open-source Quake engine written in Rust\"\ndate = 2018-04-22\n+++\n\n# RICHTER\n\n## A modern Quake engine\n\n<ul class=\"links\">\n<li><a href=\"https://github.com/cormac-obrien/richter\">Github</a></li>\n<li><a href=\"./blog\">Blog</a></li>\n</ul>\n\n</div>\n\n<div class=\"intro\">\n\nRichter is a brand-new Quake engine, built from the ground up in\n[Rust](https://rust-lang.org). Currently under active development, Richter aims\nto accurately reproduce the original Quake feel while removing some of the\ncruft that might prevent new players from enjoying a landmark experience.\n\n</div>\n\n"
  },
  {
    "path": "site/content/blog/2018-04-24.md",
    "content": "+++\ntitle = \"The New Site and the Way Forward\"\ntemplate = \"blog-post.html\"\ndate = 2018-04-24\n+++\n\nI've started rebuilding the site with [Gutenberg](https://www.getgutenberg.io/)\nnow that I actually have something to show for the past couple years (!) of\non-and-off work. I figure a dev blog will be good to have when I look back on\nthis project, even if I don't update it that often (the blog, not the project).\nIt'll probably take me a while to get the site in order since my HTML/CSS skills\nare rusty, but the old site was impossible to maintain so this ought to make\nthings easier.\n\nAs for the project itself, the client is coming along nicely -- I'm hoping to\nreach a playable state by the end of the year, even if there are still some\ngraphical bugs. Now that the infrastructure is there for networking, input,\nrendering, and sound, I can start work on the little things. The devil is in the\ndetails, etc.\n\nDefining the ultimate scope of the first alpha release is probably going to be\none of the biggest challenges. There are so many features I could add to the\nengine, and I suspect many of them are far more complicated than they seem on\nthe surface. Failed past projects have taught me to be wary of feature creep,\nso the alpha will most likely just be a working client and server -- no tools,\nno installers, no plugin support or anything like that. With any luck I'll be\nthere soon.\n\n"
  },
  {
    "path": "site/content/blog/2018-04-26/index.md",
    "content": "+++\ntitle = \"HUD Updates and Timing Bugs\"\ntemplate = \"blog-post.html\"\ndate = 2018-04-26\n+++\n\n![HUD Screenshot][1]\n\nThe HUD now renders armor, health and current ammo counts in addition to the\nper-ammo type display at the top. The latter uses [conchars][2], which, as the\nname suggests, are used for rendering text to the in-game console. Now that I\ncan load and display these I can start working on the console, which ought to\nmake debugging a great deal easier.\n\nUnfortunately, the client is still plagued by a bug with position lerping that\ncauses the geometry to jitter back and forth. This is most likely caused by bad\ntime delta calculations in `Client::update_time()` ([Github][3]), but I haven't\nbeen able to pinpoint the exact problem -- only that the lerp factor seems to go\nout of the expected range of `[0, 1)` once per server frame. I'll keep an eye on\nit.\n\n[1]: http://c-obrien.org/richter/blog/2018-04-26/hud-screenshot.png\n[2]: https://quakewiki.org/wiki/Quake_font\n[3]: https://github.com/cormac-obrien/richter/blob/12b1d9448cf9c3cfed013108fe0866cb78755902/src/client/mod.rs#L1499-L1552\n"
  },
  {
    "path": "site/content/blog/2018-05-12/index.md",
    "content": "+++\ntitle = \"Shared Ownership of Rendering Resources\"\ntemplate = \"blog-post.html\"\ndate = 2018-05-12\n+++\n\nAmong the most challenging design decisions in writing the rendering code has been the issue of\nownership. In order to avoid linking the rendering logic too closely with the data, most of the\nrendering is done by separate `Renderer` objects (i.e., to render an `AliasModel`, one must first\ncreate an `AliasRenderer`).\n\nThe process of converting on-disk model data to renderable format is fairly complex. Brush models\nare stored in a format designed for the Quake software renderer (which Michael Abrash explained\n[quite nicely][1]), while alias models have texture oddities that make it difficult to render them\nfrom a vertex buffer. In addition, all textures are composed of 8-bit indices into `gfx/palette.lmp`\nand must be converted to RGB in order to upload them to the GPU. Richter interleaves the position\nand texture coordinate data before upload.\n\nThe real challenge is in determining where to store the objects for resource creation (e.g.\n`gfx::Factory`) and the resource handles (e.g. `gfx::handle::ShaderResourceView`). Some of these\nobjects are model-specific -- a particular texture might belong to one model, and thus can be stored\nin that model's `Renderer` -- but others need to be more widely available.\n\nThe most obvious example of this is the vertex buffer used for rendering quads. This is conceptually\nstraightforward, but there are several layers of a renderer that might need this functionality.\nThe `ConsoleRenderer` needs it in order to render the console background, but also needs a\n`GlyphRenderer` to render console output -- and the `GlyphRenderer` needs to be able to render\ntextured quads. The `ConsoleRenderer` could own the `GlyphRenderer`, but the `HudRenderer` also\nneeds access to render ammo counts.\n\nThis leads to a rather complex network of `Rc`s, where many different objects own the basic building\nblocks that make up the rendering system. It isn't bad design *per se*, but it's a little difficult\nto follow, and I'm hoping that once I have the renderer fully completed I can refine the architecture\nto something more elegant.\n\n[1]: https://www.bluesnews.com/abrash/\n"
  },
  {
    "path": "site/content/blog/2018-07-20/index.md",
    "content": "+++\ntitle = \"Complications with Cross-Platform Input Handling\"\ntemplate = \"blog-post.html\"\ndate = 2018-07-20\n+++\n\nIt was bound to happen eventually, but the input handling module is the first\npart of the project to display different behavior across platforms. [winit][1]\nprovides a fairly solid basis for input handling, but Windows and Linux differ\nin terms of what sort of event is delivered to the program.\n\nInitially, I used `WindowEvent`s for everything. This works perfectly well for\nkeystrokes and mouse clicks, but mouse movement may still have acceleration\napplied, which is undesirable for camera control. `winit` also offers\n`DeviceEvent`s for this purpose. I tried just handling mouse movement with raw\ninput, keeping all other inputs in `WindowEvent`s, but it seems that handling\n`DeviceEvent`s on Linux causes the `WindowEvent`s to be eaten.\n\nThe next obvious solution is to simply handle everything with `DeviceEvent`s,\nbut this presents additional problems. First, Windows doesn't seem to even\ndeliver keyboard input as a `DeviceEvent` -- keyboard input still needs to be\npolled as a `WindowEvent`. It also means that window focus has to be handled\nmanually, since `DeviceEvent`s are delivered regardless of whether the window\nis focused or not.\n\nTo add to the complexity of this problem, apparently not all window managers\nare well-behaved when it comes to determining focus. I run [i3wm][2] on my\nLinux install, and it doesn't deliver `WindowEvent::Focused` events when\ntoggling focus or switching workspaces. This will have to remain an unsolved\nproblem for the time being.\n\n[1]: https://github.com/tomaka/winit/\n[2]: https://i3wm.org/\n"
  },
  {
    "path": "site/content/blog/_index.md",
    "content": "+++\ntitle = \"Blog\"\ntemplate = \"blog.html\"\ndescription = \"Richter project development log\"\nsort_by = \"date\"\n+++\n\n## Musings about the project and my experience with it.\n\n---\n"
  },
  {
    "path": "site/content/index.html",
    "content": "<!DOCTYPE html>\n<html>\n <head>\n  <meta charset=\"utf-8\">\n  <title>richter</title>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"hack.css\" />\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"solarized-dark.css\" />\n </head>\n <body class=\"hack solarized-dark\">\n  <div class=\"grid -center\">\n\n   <!-- spacer -->\n   <div class=\"cell -3of12\"></div>\n\n   <!-- sidebar -->\n   <div class=\"cell -2of12\" style=\"padding-right:4.15%;\">\n    <h1>richter</h1>\n    <div class=\"menu\">\n     <a class=\"menu-item active\">home</a>\n     <a class=\"menu-item\">richter docs</a>\n     <a class=\"menu-item\" href=\"/richter/quake\">quake docs</a>\n    </div>\n   </div>\n\n   <!-- main content -->\n   <div class=\"cell -4of12\">\n    <h1>richter - an open-source Quake engine</h1>\n    <h2>about</h2>\n    <p>Richter is an open-source reimplementation of the original Quake engine. The aims of the\n     project are as follows:</p>\n    <ul>\n     <li>produce a high-performance server which accurately implements the original game behavior and\n      the QuakeWorld network protocol</li>\n     <li>produce a client which recreates the original Quake experience</li>\n     <li>maintain a clean, legible code base to serve as an example for other Rust software,\n      particularly games</li>\n     <li>create comprehensive technical documentation for the original Quake engine, building upon\n      the work of <a href=\"http://fabiensanglard.net/quakeSource/\">Fabien Sanglard</a>, the\n      <a href=\"http://www.gamers.org/dEngine/quake/spec/\">Unofficial Quake Specs</a> team and\n      others</li>\n    </ul>\n    <h2>status</h2>\n    <p>Richter is currently in pre-alpha development. You can keep up with the project at its\n     <a href=\"https://github.com/cormac-obrien/richter\">github repository</a>.</p>\n   </div>\n\n   <!-- spacer -->\n   <div class=\"cell -3of12\"></div>\n\n  </div>\n </body>\n</html>\n"
  },
  {
    "path": "site/sass/_base.scss",
    "content": "@import \"reset\";\n\n@font-face {\n    font-family: \"Renner\";\n    src: local('Renner*'), local('Renner-Book'),\n    url(\"fonts/Renner-Book.woff2\") format('woff2'),\n    url(\"fonts/Renner-Book.woff\") format('woff');\n}\n\n@font-face {\n    font-family: \"Roboto\";\n    font-style: normal;\n    font-weight: 400;\n    src: local(\"Roboto\"), local(\"Roboto-Regular\"),\n    url(\"fonts/roboto-v18-latin-regular.woff2\") format(\"woff2\"),\n    url(\"fonts/roboto-v18-latin-regular.woff\") format(\"woff\");\n}\n\n@font-face {\n    font-family: \"DejaVu Sans Mono\";\n    font-style: normal;\n    src: local(\"DejaVu Sans Mono\"),\n    url(\"fonts/DejaVuSansMono.woff2\") format(\"woff2\"),\n    url(\"fonts/DejaVuSansMono.woff\") format(\"woff\");\n}\n\n$display-font-stack: Renner, Futura, Arial, sans-serif;\n$text-font-stack: Roboto, Arial, sans-serif;\n$code-font-stack: \"DejaVu Sans Mono\", \"Consolas\", monospace;\n\n$bg-color: #1F1F1F;\n$bg-hl-color: #0F0F0F;\n$text-color: #ABABAB;\n$link-color: #EFEFEF;\n\n$main-content-width: 40rem;\n\nhtml {\n    height: 100%;\n}\n\nbody {\n    font: 100% $text-font-stack;\n    color: $text-color;\n    background-color: $bg-color;\n\n    display: grid;\n    grid-template-areas:\n        \"header\"\n        \"main\"\n        \"footer\";\n    grid-template-rows: 0px 1fr auto;\n\n    margin: 100px auto;\n    max-width: $main-content-width;\n    justify-items: center;\n\n    min-height: 100%;\n}\n\np {\n    margin: 1rem auto;\n    line-height: 1.5;\n    text-align: justify;\n}\n\ncode {\n    font: 100% $code-font-stack;\n    border: 1px solid $text-color;\n    border-radius: 4px;\n    padding: 0 0.25rem 0;\n    margin: 0 0.25rem 0;\n}\n\na {\n    color: $link-color;\n    text-decoration: none;\n}\n\nhr {\n    margin: 1rem auto;\n    border: 0;\n    border-top: 1px solid $text-color;\n    border-bottom: 1px solid $bg-hl-color;\n}\n\nimg {\n    max-width: 100%;\n}\n\nfooter {\n    font: 0.75rem $text-font-stack;\n    text-align: center;\n\n    p {\n        text-align: center;\n    }\n}\n"
  },
  {
    "path": "site/sass/_reset.scss",
    "content": "/* http://meyerweb.com/eric/tools/css/reset/ \n   v2.0 | 20110126\n   License: none (public domain)\n*/\n\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed, \nfigure, figcaption, footer, header, hgroup, \nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tfont-size: 100%;\n\tfont: inherit;\n\tvertical-align: baseline;\n}\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure, \nfooter, header, hgroup, menu, nav, section {\n\tdisplay: block;\n}\nbody {\n\tline-height: 1;\n}\nol, ul {\n\tlist-style: none;\n}\nblockquote, q {\n\tquotes: none;\n}\nblockquote:before, blockquote:after,\nq:before, q:after {\n\tcontent: '';\n\tcontent: none;\n}\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n"
  },
  {
    "path": "site/sass/blog-post.scss",
    "content": "@import \"base\";\n\nbody {\n    margin: 0 auto;\n    display: grid;\n\n    max-width: $main-content-width;\n\n    .date {\n        font-family: $text-font-stack;\n    }\n\n    .title {\n        font-size: 2rem;\n    }\n}\n"
  },
  {
    "path": "site/sass/blog.scss",
    "content": "@import \"base\";\n\nbody {\n    // title of the page\n    h1 {\n        margin: 1rem auto;\n        text-align: center;\n        font: 3rem $display-font-stack;\n    }\n\n    // subtitle of the page\n    h2 {\n        margin: 1rem auto;\n        text-align: center;\n        font: 1.5rem $display-font-stack;\n    }\n\n    // post name\n    h3 {\n        font-weight: bold;\n        font-family: $text-font-stack;\n    }\n\n    // post date\n    h4 {\n        font-style: italic;\n        font-family: $text-font-stack;\n    }\n\n    .post {\n        margin-bottom: 2rem;\n    }\n}\n\n"
  },
  {
    "path": "site/sass/style.scss",
    "content": "@import \"base\";\n\n$body-margin-top: 150px;\n\nbody {\n    margin: $body-margin-top auto;\n\n    padding: $body-margin-top / 2 0;\n\n    background-image: url(richter-insignia.svg);\n    background-repeat: no-repeat;\n    background-position: 50% $body-margin-top;\n    background-size: 400px;\n\n    max-width: $main-content-width;\n    justify-items: center;\n\n    h1 {\n        $heading-font-size: 6rem;\n\n        margin: ($heading-font-size / 3) auto ($heading-font-size / 3);\n\n        font-family: $display-font-stack;\n        font-size: $heading-font-size;\n        text-align: center;\n        letter-spacing: 1rem;\n    }\n\n    h2 {\n        $subheading-font-size: 3rem;\n\n        margin: ($subheading-font-size / 3) auto ($subheading-font-size / 3);\n\n        font-family: $display-font-stack;\n        font-size: $subheading-font-size;\n        text-align: center;\n    }\n\n    .links {\n        margin: 40px 0;\n\n        font-family: $text-font-stack;\n        font-size: 1.5rem;\n        text-align: center;\n\n        li {\n            display: inline;\n            list-style: none;\n\n            // put dots between items\n            &:not(:first-child):before {\n                content: \" · \";\n            }\n        }\n    }\n\n    .intro {\n        margin-top: 40px;\n\n        font-family: $text-font-stack;\n        font-size: 1rem;\n        text-align: justify;\n    }\n}\n\n"
  },
  {
    "path": "site/templates/home.html",
    "content": "<html>\n  <head>\n    <title>{% block title %}{% endblock title %}</title>\n  </head>\n</html>\n"
  },
  {
    "path": "site/themes/richter/templates/base.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-us\">\n  <head>\n    {% block head %}\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"description\" content=\"{{ config.description }}\">\n    {% block style %}\n    <link rel=\"stylesheet\" href=\"{{ get_url(path='style.css', trailing_slash=false) }}\" />\n    {% endblock style %}\n    <title>{% block title %}{% endblock title %} – Richter</title>\n    {% endblock head %}\n  </head>\n  <body>\n    <header>{% block header %}{% endblock header %}</header>\n    <main>{% block main %}{% endblock main %}</main>\n\n    <footer>\n      {% block footer %}\n      <p>\n        Copyright © 2018 Cormac O'Brien.\n        <br />\n        This work is licensed under a\n        <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\">\n          Creative Commons Attribution-ShareAlike 4.0 International License\n        </a>.\n      </p>\n\n      {% endblock footer %}\n    </footer>\n  </body>\n</html>\n"
  },
  {
    "path": "site/themes/richter/templates/blog-post.html",
    "content": "{% extends \"base.html\" %}\n{% block style %}\n<link rel=\"stylesheet\" href=\"{{ get_url(path='blog-post.css', trailing_slash=false) }}\" />\n{% endblock style %}\n{% block title %}{{ page.title }}{% endblock title%}\n\n{% block main %}\n<h4>{{ page.date }}</h4>\n<h2>{{ page.title }}</h2>\n{{ page.content | safe }}\n{% endblock main %}\n"
  },
  {
    "path": "site/themes/richter/templates/blog.html",
    "content": "{% extends \"base.html\" %}\n\n{% block style %}\n<link rel=\"stylesheet\" href=\"{{ get_url(path='blog.css', trailing_slash=false) }}\" />\n{% endblock style %}\n{% block title %}{{ section.title }}{% endblock title %}\n{% block main %}\n<h1>{{ section.title }}</h1>\n{{ section.content | safe }}\n{% for page in section.pages %}\n<div class=\"post\">\n  <h3><a href=\"{{ page.permalink }}\">{{ page.title }}</a></h3>\n  <h4>{{ page.date }}</h4>\n  {{ page.content | safe }}\n</div>\n{% endfor %}\n{% endblock main %}\n"
  },
  {
    "path": "site/themes/richter/templates/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block title %}Home{% endblock title %}\n{% block style %}\n<link rel=\"stylesheet\" href=\"{{ get_url(path='style.css', trailing_slash=false) }}\" />\n{% endblock style %}\n{% block main %}{{ section.content | safe }}{% endblock main %}\n"
  },
  {
    "path": "site/themes/richter/theme.toml",
    "content": "name = \"richter\"\ndescription = \"theme for the Richter webpage\"\nlicense = \"MIT\"\nmin_version = \"0.2.2\"\n\n[author]\nname = \"Mac O'Brien\"\nhomepage = \"http://c-obrien.org\""
  },
  {
    "path": "specifications.md",
    "content": "# Specifications for the Original Quake (idTech 2) Engine\n\n### Coordinate Systems\n\nQuake's coordinate system specifies its axes as follows:\n\n- The x-axis specifies depth.\n- The y-axis specifies width.\n- The z-axis specifies height.\n\nThis contrasts with the OpenGL coordinate system, in which:\n\n- The x-axis specifies width.\n- The y-axis specifies height.\n- The z-axis specifies depth (inverted).\n\nThus, to convert between the coordinate systems:\n\n          x <-> -z\n    Quake y <->  x OpenGL\n          z <->  y\n\n           x <->  y\n    OpenGL y <->  z Quake\n           z <-> -x\n"
  },
  {
    "path": "src/bin/quake-client/capture.rs",
    "content": "use std::{\n    cell::RefCell,\n    fs::File,\n    io::BufWriter,\n    num::NonZeroU32,\n    path::{Path, PathBuf},\n    rc::Rc,\n};\n\nuse richter::client::render::Extent2d;\n\nuse chrono::Utc;\n\nconst BYTES_PER_PIXEL: u32 = 4;\n\n/// Implements the \"screenshot\" command.\n///\n/// This function returns a boxed closure which sets the `screenshot_path`\n/// argument to `Some` when called.\npub fn cmd_screenshot(\n    screenshot_path: Rc<RefCell<Option<PathBuf>>>,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |args| {\n        let path = match args.len() {\n            // TODO: make default path configurable\n            0 => PathBuf::from(format!(\"richter-{}.png\", Utc::now().format(\"%FT%H-%M-%S\"))),\n            1 => PathBuf::from(args[0]),\n            _ => {\n                log::error!(\"Usage: screenshot [PATH]\");\n                return \"Usage: screenshot [PATH]\".to_owned();\n            }\n        };\n\n        screenshot_path.replace(Some(path));\n        String::new()\n    })\n}\n\npub struct Capture {\n    // size of the capture image\n    capture_size: Extent2d,\n\n    // width of a row in the buffer, must be a multiple of 256 for mapped reads\n    row_width: u32,\n\n    // mappable buffer\n    buffer: wgpu::Buffer,\n}\n\nimpl Capture {\n    pub fn new(device: &wgpu::Device, capture_size: Extent2d) -> Capture {\n        // bytes_per_row must be a multiple of 256\n        // 4 bytes per pixel, so width must be multiple of 64\n        let row_width = (capture_size.width + 63) / 64 * 64;\n\n        let buffer = device.create_buffer(&wgpu::BufferDescriptor {\n            label: Some(\"capture buffer\"),\n            size: (row_width * capture_size.height * BYTES_PER_PIXEL) as u64,\n            usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ,\n            mapped_at_creation: false,\n        });\n\n        Capture {\n            capture_size,\n            row_width,\n            buffer,\n        }\n    }\n\n    pub fn copy_from_texture(\n        &self,\n        encoder: &mut wgpu::CommandEncoder,\n        texture: wgpu::ImageCopyTexture,\n    ) {\n        encoder.copy_texture_to_buffer(\n            texture,\n            wgpu::ImageCopyBuffer {\n                buffer: &self.buffer,\n                layout: wgpu::ImageDataLayout {\n                    offset: 0,\n                    bytes_per_row: Some(NonZeroU32::new(self.row_width * BYTES_PER_PIXEL).unwrap()),\n                    rows_per_image: None,\n                },\n            },\n            self.capture_size.into(),\n        );\n    }\n\n    pub fn write_to_file<P>(&self, device: &wgpu::Device, path: P)\n    where\n        P: AsRef<Path>,\n    {\n        let mut data = Vec::new();\n        {\n            // map the buffer\n            // TODO: maybe make this async so we don't force the whole program to block\n            let slice = self.buffer.slice(..);\n            let map_future = slice.map_async(wgpu::MapMode::Read);\n            device.poll(wgpu::Maintain::Wait);\n            futures::executor::block_on(map_future).unwrap();\n\n            // copy pixel data\n            let mapped = slice.get_mapped_range();\n            for row in mapped.chunks(self.row_width as usize * BYTES_PER_PIXEL as usize) {\n                // don't copy padding\n                for pixel in\n                    (&row[..self.capture_size.width as usize * BYTES_PER_PIXEL as usize]).chunks(4)\n                {\n                    // swap BGRA->RGBA\n                    data.extend_from_slice(&[pixel[2], pixel[1], pixel[0], pixel[3]]);\n                }\n            }\n        }\n        self.buffer.unmap();\n\n        let f = File::create(path).unwrap();\n        let mut png_encoder = png::Encoder::new(\n            BufWriter::new(f),\n            self.capture_size.width,\n            self.capture_size.height,\n        );\n        png_encoder.set_color(png::ColorType::RGBA);\n        png_encoder.set_depth(png::BitDepth::Eight);\n        let mut writer = png_encoder.write_header().unwrap();\n        writer.write_image_data(&data).unwrap();\n    }\n}\n"
  },
  {
    "path": "src/bin/quake-client/game.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::{cell::RefCell, path::PathBuf, rc::Rc};\n\nuse crate::{\n    capture::{cmd_screenshot, Capture},\n    trace::{cmd_trace_begin, cmd_trace_end},\n};\n\nuse richter::{\n    client::{\n        input::Input,\n        menu::Menu,\n        render::{\n            Extent2d, GraphicsState, RenderTarget as _, RenderTargetResolve as _, SwapChainTarget,\n        },\n        trace::TraceFrame,\n        Client, ClientError,\n    },\n    common::console::{CmdRegistry, Console, CvarRegistry},\n};\n\nuse chrono::Duration;\nuse failure::Error;\nuse log::info;\n\npub struct Game {\n    cvars: Rc<RefCell<CvarRegistry>>,\n    cmds: Rc<RefCell<CmdRegistry>>,\n    input: Rc<RefCell<Input>>,\n    pub client: Client,\n\n    // if Some(v), trace is in progress\n    trace: Rc<RefCell<Option<Vec<TraceFrame>>>>,\n\n    // if Some(path), take a screenshot and save it to path\n    screenshot_path: Rc<RefCell<Option<PathBuf>>>,\n}\n\nimpl Game {\n    pub fn new(\n        cvars: Rc<RefCell<CvarRegistry>>,\n        cmds: Rc<RefCell<CmdRegistry>>,\n        input: Rc<RefCell<Input>>,\n        client: Client,\n    ) -> Result<Game, Error> {\n        // set up input commands\n        input.borrow().register_cmds(&mut cmds.borrow_mut());\n\n        // set up screenshots\n        let screenshot_path = Rc::new(RefCell::new(None));\n        cmds.borrow_mut()\n            .insert(\"screenshot\", cmd_screenshot(screenshot_path.clone()))\n            .unwrap();\n\n        // set up frame tracing\n        let trace = Rc::new(RefCell::new(None));\n        cmds.borrow_mut()\n            .insert(\"trace_begin\", cmd_trace_begin(trace.clone()))\n            .unwrap();\n        cmds.borrow_mut()\n            .insert(\"trace_end\", cmd_trace_end(cvars.clone(), trace.clone()))\n            .unwrap();\n\n        Ok(Game {\n            cvars,\n            cmds,\n            input,\n            client,\n            trace,\n            screenshot_path,\n        })\n    }\n\n    // advance the simulation\n    pub fn frame(&mut self, gfx_state: &GraphicsState, frame_duration: Duration) {\n        use ClientError::*;\n\n        match self.client.frame(frame_duration, gfx_state) {\n            Ok(()) => (),\n            Err(e) => match e {\n                Cvar(_)\n                | UnrecognizedProtocol(_)\n                | NoSuchClient(_)\n                | NoSuchPlayer(_)\n                | NoSuchEntity(_)\n                | NullEntity\n                | EntityExists(_)\n                | InvalidViewEntity(_)\n                | TooManyStaticEntities\n                | NoSuchLightmapAnimation(_)\n                | Model(_)\n                | Network(_)\n                | Sound(_)\n                | Vfs(_) => {\n                    log::error!(\"{}\", e);\n                    self.client.disconnect();\n                }\n\n                _ => panic!(\"{}\", e),\n            },\n        };\n\n        if let Some(ref mut game_input) = self.input.borrow_mut().game_input_mut() {\n            self.client\n                .handle_input(game_input, frame_duration)\n                .unwrap();\n        }\n\n        // if there's an active trace, record this frame\n        if let Some(ref mut trace_frames) = *self.trace.borrow_mut() {\n            trace_frames.push(\n                self.client\n                    .trace(&[self.client.view_entity_id().unwrap()])\n                    .unwrap(),\n            );\n        }\n    }\n\n    pub fn render(\n        &mut self,\n        gfx_state: &GraphicsState,\n        color_attachment_view: &wgpu::TextureView,\n        width: u32,\n        height: u32,\n        console: &Console,\n        menu: &Menu,\n    ) {\n        info!(\"Beginning render pass\");\n        let mut encoder = gfx_state\n            .device()\n            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });\n\n        // render world, hud, console, menus\n        self.client\n            .render(\n                gfx_state,\n                &mut encoder,\n                width,\n                height,\n                menu,\n                self.input.borrow().focus(),\n            )\n            .unwrap();\n\n        // screenshot setup\n        let capture = self.screenshot_path.borrow().as_ref().map(|_| {\n            let cap = Capture::new(gfx_state.device(), Extent2d { width, height });\n            cap.copy_from_texture(\n                &mut encoder,\n                wgpu::ImageCopyTexture {\n                    texture: gfx_state.final_pass_target().resolve_attachment(),\n                    mip_level: 0,\n                    origin: wgpu::Origin3d::ZERO,\n                },\n            );\n            cap\n        });\n\n        // blit to swap chain\n        {\n            let swap_chain_target = SwapChainTarget::with_swap_chain_view(color_attachment_view);\n            let blit_pass_builder = swap_chain_target.render_pass_builder();\n            let mut blit_pass = encoder.begin_render_pass(&blit_pass_builder.descriptor());\n            gfx_state.blit_pipeline().blit(gfx_state, &mut blit_pass);\n        }\n\n        let command_buffer = encoder.finish();\n        {\n            gfx_state.queue().submit(vec![command_buffer]);\n            gfx_state.device().poll(wgpu::Maintain::Wait);\n        }\n\n        // write screenshot if requested and clear screenshot path\n        self.screenshot_path.replace(None).map(|path| {\n            capture\n                .as_ref()\n                .unwrap()\n                .write_to_file(gfx_state.device(), path)\n        });\n    }\n}\n\nimpl std::ops::Drop for Game {\n    fn drop(&mut self) {\n        let _ = self.cmds.borrow_mut().remove(\"trace_begin\");\n        let _ = self.cmds.borrow_mut().remove(\"trace_end\");\n    }\n}\n"
  },
  {
    "path": "src/bin/quake-client/main.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nmod capture;\nmod game;\nmod menu;\nmod trace;\n\nuse std::{\n    cell::{Ref, RefCell, RefMut},\n    fs::File,\n    io::{Cursor, Read, Write},\n    net::SocketAddr,\n    path::{Path, PathBuf},\n    process::exit,\n    rc::Rc,\n};\n\nuse game::Game;\n\nuse chrono::Duration;\nuse common::net::ServerCmd;\nuse richter::{\n    client::{\n        self,\n        demo::DemoServer,\n        input::{Input, InputFocus},\n        menu::Menu,\n        render::{self, Extent2d, GraphicsState, UiRenderer, DIFFUSE_ATTACHMENT_FORMAT},\n        Client,\n    },\n    common::{\n        self,\n        console::{CmdRegistry, Console, CvarRegistry},\n        host::{Host, Program},\n        vfs::Vfs,\n    },\n};\nuse structopt::StructOpt;\nuse winit::{\n    event::{Event, WindowEvent},\n    event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},\n    window::Window,\n};\n\nstruct ClientProgram {\n    vfs: Rc<Vfs>,\n    cvars: Rc<RefCell<CvarRegistry>>,\n    cmds: Rc<RefCell<CmdRegistry>>,\n    console: Rc<RefCell<Console>>,\n    menu: Rc<RefCell<Menu>>,\n\n    window: Window,\n    window_dimensions_changed: bool,\n\n    surface: wgpu::Surface,\n    swap_chain: RefCell<wgpu::SwapChain>,\n    gfx_state: RefCell<GraphicsState>,\n    ui_renderer: Rc<UiRenderer>,\n\n    game: Game,\n    input: Rc<RefCell<Input>>,\n}\n\nimpl ClientProgram {\n    pub async fn new(window: Window, base_dir: Option<PathBuf>, trace: bool) -> ClientProgram {\n        let vfs = Vfs::with_base_dir(base_dir.unwrap_or(common::default_base_dir()));\n\n        let con_names = Rc::new(RefCell::new(Vec::new()));\n\n        let cvars = Rc::new(RefCell::new(CvarRegistry::new(con_names.clone())));\n        client::register_cvars(&cvars.borrow()).unwrap();\n        render::register_cvars(&cvars.borrow());\n\n        let cmds = Rc::new(RefCell::new(CmdRegistry::new(con_names)));\n        // TODO: register commands as other subsystems come online\n\n        let console = Rc::new(RefCell::new(Console::new(cmds.clone(), cvars.clone())));\n        let menu = Rc::new(RefCell::new(menu::build_main_menu().unwrap()));\n\n        let input = Rc::new(RefCell::new(Input::new(\n            InputFocus::Console,\n            console.clone(),\n            menu.clone(),\n        )));\n        input.borrow_mut().bind_defaults();\n\n        let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);\n        let surface = unsafe { instance.create_surface(&window) };\n        let adapter = instance\n            .request_adapter(&wgpu::RequestAdapterOptions {\n                power_preference: wgpu::PowerPreference::HighPerformance,\n                compatible_surface: Some(&surface),\n            })\n            .await\n            .unwrap();\n        let (device, queue) = adapter\n            .request_device(\n                &wgpu::DeviceDescriptor {\n                    label: None,\n                    features: wgpu::Features::PUSH_CONSTANTS\n                        | wgpu::Features::SAMPLED_TEXTURE_BINDING_ARRAY\n                        | wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING\n                        | wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,\n                    limits: wgpu::Limits {\n                        max_sampled_textures_per_shader_stage: 256,\n                        max_uniform_buffer_binding_size: 65536,\n                        max_push_constant_size: 256,\n                        ..Default::default()\n                    },\n                },\n                if trace {\n                    Some(Path::new(\"./trace/\"))\n                } else {\n                    None\n                },\n            )\n            .await\n            .unwrap();\n        let size: Extent2d = window.inner_size().into();\n        let swap_chain = RefCell::new(device.create_swap_chain(\n            &surface,\n            &wgpu::SwapChainDescriptor {\n                usage: wgpu::TextureUsage::RENDER_ATTACHMENT,\n                format: DIFFUSE_ATTACHMENT_FORMAT,\n                width: size.width,\n                height: size.height,\n                present_mode: wgpu::PresentMode::Immediate,\n            },\n        ));\n\n        let vfs = Rc::new(vfs);\n\n        // TODO: warn user if r_msaa_samples is invalid\n        let mut sample_count = cvars.borrow().get_value(\"r_msaa_samples\").unwrap_or(2.0) as u32;\n        if !&[2, 4].contains(&sample_count) {\n            sample_count = 2;\n        }\n\n        let gfx_state = GraphicsState::new(device, queue, size, sample_count, vfs.clone()).unwrap();\n        let ui_renderer = Rc::new(UiRenderer::new(&gfx_state, &menu.borrow()));\n\n        // TODO: factor this out\n        // implements \"exec\" command\n        let exec_vfs = vfs.clone();\n        let exec_console = console.clone();\n        cmds.borrow_mut().insert_or_replace(\n            \"exec\",\n            Box::new(move |args| {\n                match args.len() {\n                    // exec (filename): execute a script file\n                    1 => {\n                        let mut script_file = match exec_vfs.open(args[0]) {\n                            Ok(s) => s,\n                            Err(e) => {\n                                return format!(\"Couldn't exec {}: {:?}\", args[0], e);\n                            }\n                        };\n\n                        let mut script = String::new();\n                        script_file.read_to_string(&mut script).unwrap();\n\n                        exec_console.borrow().stuff_text(script);\n                        String::new()\n                    }\n\n                    _ => format!(\"exec (filename): execute a script file\"),\n                }\n            }),\n        ).unwrap();\n\n        // this will also execute config.cfg and autoexec.cfg (assuming an unmodified quake.rc)\n        console.borrow().stuff_text(\"exec quake.rc\\n\");\n\n        let client = Client::new(\n            vfs.clone(),\n            cvars.clone(),\n            cmds.clone(),\n            console.clone(),\n            input.clone(),\n            &gfx_state,\n            &menu.borrow(),\n        );\n\n        let game = Game::new(cvars.clone(), cmds.clone(), input.clone(), client).unwrap();\n\n        ClientProgram {\n            vfs,\n            cvars,\n            cmds,\n            console,\n            menu,\n            window,\n            window_dimensions_changed: false,\n            surface,\n            swap_chain,\n            gfx_state: RefCell::new(gfx_state),\n            ui_renderer,\n            game,\n            input,\n        }\n    }\n\n    /// Builds a new swap chain with the specified present mode and the window's current dimensions.\n    fn recreate_swap_chain(&self, present_mode: wgpu::PresentMode) {\n        let winit::dpi::PhysicalSize { width, height } = self.window.inner_size();\n        let swap_chain = self.gfx_state.borrow().device().create_swap_chain(\n            &self.surface,\n            &wgpu::SwapChainDescriptor {\n                usage: wgpu::TextureUsage::RENDER_ATTACHMENT,\n                format: DIFFUSE_ATTACHMENT_FORMAT,\n                width,\n                height,\n                present_mode,\n            },\n        );\n        let _ = self.swap_chain.replace(swap_chain);\n    }\n\n    fn render(&mut self) {\n        let swap_chain_output = self.swap_chain.borrow_mut().get_current_frame().unwrap();\n        let winit::dpi::PhysicalSize { width, height } = self.window.inner_size();\n        self.game.render(\n            &self.gfx_state.borrow(),\n            &swap_chain_output.output.view,\n            width,\n            height,\n            &self.console.borrow(),\n            &self.menu.borrow(),\n        );\n    }\n}\n\nimpl Program for ClientProgram {\n    fn handle_event<T>(\n        &mut self,\n        event: Event<T>,\n        _target: &EventLoopWindowTarget<T>,\n        _control_flow: &mut ControlFlow,\n    ) {\n        match event {\n            Event::WindowEvent {\n                event: WindowEvent::Resized(_),\n                ..\n            } => {\n                self.window_dimensions_changed = true;\n            }\n\n            e => self.input.borrow_mut().handle_event(e).unwrap(),\n        }\n    }\n\n    fn frame(&mut self, frame_duration: Duration) {\n        // recreate swapchain if needed\n        if self.window_dimensions_changed {\n            self.window_dimensions_changed = false;\n            self.recreate_swap_chain(wgpu::PresentMode::Immediate);\n        }\n\n        let size: Extent2d = self.window.inner_size().into();\n\n        // TODO: warn user if r_msaa_samples is invalid\n        let mut sample_count = self\n            .cvars\n            .borrow()\n            .get_value(\"r_msaa_samples\")\n            .unwrap_or(2.0) as u32;\n        if !&[2, 4].contains(&sample_count) {\n            sample_count = 2;\n        }\n\n        // recreate attachments and rebuild pipelines if necessary\n        self.gfx_state.borrow_mut().update(size, sample_count);\n        self.game.frame(&self.gfx_state.borrow(), frame_duration);\n\n        match self.input.borrow().focus() {\n            InputFocus::Game => {\n                if let Err(e) = self.window.set_cursor_grab(true) {\n                    // This can happen if the window is running in another\n                    // workspace. It shouldn't be considered an error.\n                    log::debug!(\"Couldn't grab cursor: {}\", e);\n                }\n\n                self.window.set_cursor_visible(false);\n            }\n\n            _ => {\n                if let Err(e) = self.window.set_cursor_grab(false) {\n                    log::debug!(\"Couldn't release cursor: {}\", e);\n                };\n                self.window.set_cursor_visible(true);\n            }\n        }\n\n        // run console commands\n        self.console.borrow().execute();\n\n        self.render();\n    }\n\n    fn shutdown(&mut self) {\n        // TODO: do cleanup things here\n    }\n\n    fn cvars(&self) -> Ref<CvarRegistry> {\n        self.cvars.borrow()\n    }\n\n    fn cvars_mut(&self) -> RefMut<CvarRegistry> {\n        self.cvars.borrow_mut()\n    }\n}\n\n#[derive(StructOpt, Debug)]\nstruct Opt {\n    #[structopt(long)]\n    trace: bool,\n\n    #[structopt(long)]\n    connect: Option<SocketAddr>,\n\n    #[structopt(long)]\n    dump_demo: Option<String>,\n\n    #[structopt(long)]\n    demo: Option<String>,\n\n    #[structopt(long)]\n    base_dir: Option<PathBuf>,\n}\n\nfn main() {\n    env_logger::init();\n    let opt = Opt::from_args();\n\n    let event_loop = EventLoop::new();\n    let window = {\n        #[cfg(target_os = \"windows\")]\n        {\n            use winit::platform::windows::WindowBuilderExtWindows as _;\n            winit::window::WindowBuilder::new()\n                // disable file drag-and-drop so cpal and winit play nice\n                .with_drag_and_drop(false)\n                .with_title(\"Richter client\")\n                .with_inner_size(winit::dpi::PhysicalSize::<u32>::from((1366u32, 768)))\n                .build(&event_loop)\n                .unwrap()\n        }\n\n        #[cfg(not(target_os = \"windows\"))]\n        {\n            winit::window::WindowBuilder::new()\n                .with_title(\"Richter client\")\n                .with_inner_size(winit::dpi::PhysicalSize::<u32>::from((1366u32, 768)))\n                .build(&event_loop)\n                .unwrap()\n        }\n    };\n\n    let client_program =\n        futures::executor::block_on(ClientProgram::new(window, opt.base_dir, opt.trace));\n\n    // TODO: make dump_demo part of top-level binary and allow choosing file name\n    if let Some(ref demo) = opt.dump_demo {\n        let mut demfile = match client_program.vfs.open(demo) {\n            Ok(d) => d,\n            Err(e) => {\n                eprintln!(\"error opening demofile: {}\", e);\n                std::process::exit(1);\n            }\n        };\n\n        let mut demserv = match DemoServer::new(&mut demfile) {\n            Ok(d) => d,\n            Err(e) => {\n                eprintln!(\"error starting demo server: {}\", e);\n                std::process::exit(1);\n            }\n        };\n\n        let mut outfile = File::create(\"demodump.txt\").unwrap();\n        loop {\n            match demserv.next() {\n                Some(msg) => {\n                    let mut curs = Cursor::new(msg.message());\n                    loop {\n                        match ServerCmd::deserialize(&mut curs) {\n                            Ok(Some(cmd)) => write!(&mut outfile, \"{:#?}\\n\", cmd).unwrap(),\n                            Ok(None) => break,\n                            Err(e) => {\n                                eprintln!(\"error processing demo: {}\", e);\n                                std::process::exit(1);\n                            }\n                        }\n                    }\n                }\n                None => break,\n            }\n        }\n\n        std::process::exit(0);\n    }\n    if let Some(ref server) = opt.connect {\n        client_program\n            .console\n            .borrow_mut()\n            .stuff_text(format!(\"connect {}\", server));\n    } else if let Some(ref demo) = opt.demo {\n        client_program\n            .console\n            .borrow_mut()\n            .stuff_text(format!(\"playdemo {}\", demo));\n    }\n\n    let mut host = Host::new(client_program);\n\n    event_loop.run(move |event, _target, control_flow| {\n        host.handle_event(event, _target, control_flow);\n    });\n}\n"
  },
  {
    "path": "src/bin/quake-client/menu.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse richter::client::menu::{Menu, MenuBodyView, MenuBuilder, MenuView};\n\nuse failure::Error;\n\npub fn build_main_menu() -> Result<Menu, Error> {\n    Ok(MenuBuilder::new()\n        .add_submenu(\"Single Player\", build_menu_sp()?)\n        .add_submenu(\"Multiplayer\", build_menu_mp()?)\n        .add_submenu(\"Options\", build_menu_options()?)\n        .add_action(\"Help/Ordering\", Box::new(|| ()))\n        .add_action(\"Quit\", Box::new(|| ()))\n        .build(MenuView {\n            draw_plaque: true,\n            title_path: \"gfx/ttl_main.lmp\".to_string(),\n            body: MenuBodyView::Predefined {\n                path: \"gfx/mainmenu.lmp\".to_string(),\n            },\n        }))\n}\n\nfn build_menu_sp() -> Result<Menu, Error> {\n    Ok(MenuBuilder::new()\n        .add_action(\"New Game\", Box::new(|| ()))\n        // .add_submenu(\"Load\", unimplemented!())\n        // .add_submenu(\"Save\", unimplemented!())\n        .build(MenuView {\n            draw_plaque: true,\n            title_path: \"gfx/ttl_sgl.lmp\".to_string(),\n            body: MenuBodyView::Predefined {\n                path: \"gfx/sp_menu.lmp\".to_string(),\n            },\n        }))\n}\n\nfn build_menu_mp() -> Result<Menu, Error> {\n    Ok(MenuBuilder::new()\n        .add_submenu(\"Join a Game\", build_menu_mp_join()?)\n        // .add_submenu(\"New Game\", unimplemented!())\n        // .add_submenu(\"Setup\", unimplemented!())\n        .build(MenuView {\n            draw_plaque: true,\n            title_path: \"gfx/p_multi.lmp\".to_string(),\n            body: MenuBodyView::Predefined {\n                path: \"gfx/mp_menu.lmp\".to_string(),\n            },\n        }))\n}\n\nfn build_menu_mp_join() -> Result<Menu, Error> {\n    Ok(MenuBuilder::new()\n        .add_submenu(\"TCP\", build_menu_mp_join_tcp()?)\n        // .add_textbox // description\n        .build(MenuView {\n            draw_plaque: true,\n            title_path: \"gfx/p_multi.lmp\".to_string(),\n            body: MenuBodyView::Predefined {\n                path: \"gfx/mp_menu.lmp\".to_string(),\n            },\n        }))\n}\n\nfn build_menu_mp_join_tcp() -> Result<Menu, Error> {\n    // Join Game - TCP/IP          // title\n    //\n    //  Address: 127.0.0.1         // label\n    //\n    //  Port     [26000]           // text field\n    //\n    //  Search for local games...  // menu\n    //\n    //  Join game at:              // label\n    //  [                        ] // text field\n    Ok(MenuBuilder::new()\n        // .add\n        .add_toggle(\"placeholder\", false, Box::new(|_| ()))\n        .build(MenuView {\n            draw_plaque: true,\n            title_path: \"gfx/p_multi.lmp\".to_string(),\n            body: MenuBodyView::Dynamic,\n        }))\n}\n\nfn build_menu_options() -> Result<Menu, Error> {\n    Ok(MenuBuilder::new()\n        // .add_submenu(\"Customize controls\", unimplemented!())\n        .add_action(\"Go to console\", Box::new(|| ()))\n        .add_action(\"Reset to defaults\", Box::new(|| ()))\n        .add_slider(\"Render scale\", 0.25, 1.0, 2, 0, Box::new(|_| ()))?\n        .add_slider(\"Screen Size\", 0.0, 1.0, 10, 9, Box::new(|_| ()))?\n        .add_slider(\"Brightness\", 0.0, 1.0, 10, 9, Box::new(|_| ()))?\n        .add_slider(\"Mouse Speed\", 0.0, 1.0, 10, 9, Box::new(|_| ()))?\n        .add_slider(\"CD music volume\", 0.0, 1.0, 10, 9, Box::new(|_| ()))?\n        .add_slider(\"Sound volume\", 0.0, 1.0, 10, 9, Box::new(|_| ()))?\n        .add_toggle(\"Always run\", true, Box::new(|_| ()))\n        .add_toggle(\"Invert mouse\", false, Box::new(|_| ()))\n        .add_toggle(\"Lookspring\", false, Box::new(|_| ()))\n        .add_toggle(\"Lookstrafe\", false, Box::new(|_| ()))\n        // .add_submenu(\"Video options\", unimplemented!())\n        .build(MenuView {\n            draw_plaque: true,\n            title_path: \"gfx/p_option.lmp\".to_string(),\n            body: MenuBodyView::Dynamic,\n        }))\n}\n"
  },
  {
    "path": "src/bin/quake-client/trace.rs",
    "content": "use std::{cell::RefCell, io::BufWriter, rc::Rc, fs::File};\n\nuse richter::{client::trace::TraceFrame, common::console::CvarRegistry};\n\nconst DEFAULT_TRACE_PATH: &'static str = \"richter-trace.json\";\n\n/// Implements the `trace_begin` command.\npub fn cmd_trace_begin(trace: Rc<RefCell<Option<Vec<TraceFrame>>>>) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        if trace.borrow().is_some() {\n            log::error!(\"trace already in progress\");\n            \"trace already in progress\".to_owned()\n        } else {\n            // start a new trace\n            trace.replace(Some(Vec::new()));\n            String::new()\n        }\n    })\n}\n\n/// Implements the `trace_end` command.\npub fn cmd_trace_end(\n    cvars: Rc<RefCell<CvarRegistry>>,\n    trace: Rc<RefCell<Option<Vec<TraceFrame>>>>,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        if let Some(trace_frames) = trace.replace(None) {\n            let trace_path = cvars\n                .borrow()\n                .get(\"trace_path\")\n                .unwrap_or(DEFAULT_TRACE_PATH.to_string());\n            let trace_file = match File::create(&trace_path) {\n                Ok(f) => f,\n                Err(e) => {\n                    log::error!(\"Couldn't open trace file for write: {}\", e);\n                    return format!(\"Couldn't open trace file for write: {}\", e);\n                }\n            };\n\n            let mut writer = BufWriter::new(trace_file);\n\n            match serde_json::to_writer(&mut writer, &trace_frames) {\n                Ok(()) => (),\n                Err(e) => {\n                    log::error!(\"Couldn't serialize trace: {}\", e);\n                    return format!(\"Couldn't serialize trace: {}\", e);\n                }\n            };\n\n            log::debug!(\"wrote {} frames to {}\", trace_frames.len(), &trace_path);\n            format!(\"wrote {} frames to {}\", trace_frames.len(), &trace_path)\n        } else {\n            log::error!(\"no trace in progress\");\n            \"no trace in progress\".to_owned()\n        }\n    })\n}\n"
  },
  {
    "path": "src/bin/unpak.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nextern crate richter;\n\nuse std::{\n    fs,\n    fs::File,\n    io::{BufWriter, Write},\n    path::PathBuf,\n    process::exit,\n};\n\nuse richter::common::pak::Pak;\n\nuse structopt::StructOpt;\n\n#[derive(Debug, StructOpt)]\nstruct Opt {\n    #[structopt(short, long)]\n    verbose: bool,\n\n    #[structopt(long)]\n    version: bool,\n\n    #[structopt(name = \"INPUT_PAK\", parse(from_os_str))]\n    input_pak: PathBuf,\n\n    #[structopt(name = \"OUTPUT_DIR\", parse(from_os_str))]\n    output_dir: Option<PathBuf>,\n}\n\nconst VERSION: &'static str = \"\nunpak 0.1\nCopyright © 2020 Cormac O'Brien\nReleased under the terms of the MIT License\n\";\n\nfn main() {\n    let opt = Opt::from_args();\n\n    if opt.version {\n        println!(\"{}\", VERSION);\n        exit(0);\n    }\n\n    let pak = match Pak::new(&opt.input_pak) {\n        Ok(p) => p,\n        Err(why) => {\n            println!(\"Couldn't open {:#?}: {}\", &opt.input_pak, why);\n            exit(1);\n        }\n    };\n\n    for (k, v) in pak.iter() {\n        let mut path = PathBuf::new();\n\n        if let Some(ref d) = opt.output_dir {\n            path.push(d);\n        }\n\n        path.push(k);\n\n        if let Some(p) = path.parent() {\n            if !p.exists() {\n                if let Err(why) = fs::create_dir_all(p) {\n                    println!(\"Couldn't create parent directories: {}\", why);\n                    exit(1);\n                }\n            }\n        }\n\n        let file = match File::create(&path) {\n            Ok(f) => f,\n            Err(why) => {\n                println!(\"Couldn't open {}: {}\", path.to_str().unwrap(), why);\n                exit(1);\n            }\n        };\n\n        let mut writer = BufWriter::new(file);\n        match writer.write_all(v.as_ref()) {\n            Ok(_) => (),\n            Err(why) => {\n                println!(\"Couldn't write to {}: {}\", path.to_str().unwrap(), why);\n                exit(1);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/cvars.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse crate::common::console::{CvarRegistry, ConsoleError};\n\npub fn register_cvars(cvars: &CvarRegistry) -> Result<(), ConsoleError> {\n    cvars.register(\"cl_anglespeedkey\", \"1.5\")?;\n    cvars.register_archive(\"cl_backspeed\", \"200\")?;\n    cvars.register(\"cl_bob\", \"0.02\")?;\n    cvars.register(\"cl_bobcycle\", \"0.6\")?;\n    cvars.register(\"cl_bobup\", \"0.5\")?;\n    cvars.register_archive(\"_cl_color\", \"0\")?;\n    cvars.register(\"cl_crossx\", \"0\")?;\n    cvars.register(\"cl_crossy\", \"0\")?;\n    cvars.register_archive(\"cl_forwardspeed\", \"400\")?;\n    cvars.register(\"cl_movespeedkey\", \"2.0\")?;\n    cvars.register_archive(\"_cl_name\", \"player\")?;\n    cvars.register(\"cl_nolerp\", \"0\")?;\n    cvars.register(\"cl_pitchspeed\", \"150\")?;\n    cvars.register(\"cl_rollangle\", \"2.0\")?;\n    cvars.register(\"cl_rollspeed\", \"200\")?;\n    cvars.register(\"cl_shownet\", \"0\")?;\n    cvars.register(\"cl_sidespeed\", \"350\")?;\n    cvars.register(\"cl_upspeed\", \"200\")?;\n    cvars.register(\"cl_yawspeed\", \"140\")?;\n    cvars.register(\"fov\", \"90\")?;\n    cvars.register_archive(\"m_pitch\", \"0.022\")?;\n    cvars.register_archive(\"m_yaw\", \"0.022\")?;\n    cvars.register_archive(\"sensitivity\", \"3\")?;\n    cvars.register(\"v_idlescale\", \"0\")?;\n    cvars.register(\"v_ipitch_cycle\", \"1\")?;\n    cvars.register(\"v_ipitch_level\", \"0.3\")?;\n    cvars.register(\"v_iroll_cycle\", \"0.5\")?;\n    cvars.register(\"v_iroll_level\", \"0.1\")?;\n    cvars.register(\"v_iyaw_cycle\", \"2\")?;\n    cvars.register(\"v_iyaw_level\", \"0.3\")?;\n    cvars.register(\"v_kickpitch\", \"0.6\")?;\n    cvars.register(\"v_kickroll\", \"0.6\")?;\n    cvars.register(\"v_kicktime\", \"0.5\")?;\n\n    // some server cvars are needed by the client, but if the server is running\n    // in the same process they will have been set already, so we can ignore\n    // the duplicate cvar error\n    let _ = cvars.register(\"sv_gravity\", \"800\");\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/client/demo.rs",
    "content": "use std::{io, ops::Range};\n\nuse crate::common::{\n    net::{self, NetError},\n    util::read_f32_3,\n    vfs::VirtualFile,\n};\n\nuse arrayvec::ArrayVec;\nuse byteorder::{LittleEndian, ReadBytesExt};\nuse cgmath::{Deg, Vector3};\nuse io::BufReader;\nuse thiserror::Error;\n\n/// An error returned by a demo server.\n#[derive(Error, Debug)]\npub enum DemoServerError {\n    #[error(\"Invalid CD track number\")]\n    InvalidCdTrack,\n    #[error(\"No such CD track: {0}\")]\n    NoSuchCdTrack(i32),\n    #[error(\"Message size ({0}) exceeds maximum allowed size {}\", net::MAX_MESSAGE)]\n    MessageTooLong(u32),\n    #[error(\"I/O error: {0}\")]\n    Io(#[from] io::Error),\n    #[error(\"Network error: {0}\")]\n    Net(#[from] NetError),\n}\n\nstruct DemoMessage {\n    view_angles: Vector3<Deg<f32>>,\n    msg_range: Range<usize>,\n}\n\n/// A view of a server message from a demo.\npub struct DemoMessageView<'a> {\n    view_angles: Vector3<Deg<f32>>,\n    message: &'a [u8],\n}\n\nimpl<'a> DemoMessageView<'a> {\n    /// Returns the view angles recorded for this demo message.\n    pub fn view_angles(&self) -> Vector3<Deg<f32>> {\n        self.view_angles\n    }\n\n    /// Returns the server message for this demo message as a slice of bytes.\n    pub fn message(&self) -> &[u8] {\n        self.message\n    }\n}\n\n/// A server that yields commands from a demo file.\npub struct DemoServer {\n    track_override: Option<u32>,\n\n    // id of next message to \"send\"\n    message_id: usize,\n\n    messages: Vec<DemoMessage>,\n\n    // all message data\n    message_data: Vec<u8>,\n}\n\nimpl DemoServer {\n    /// Construct a new `DemoServer` from the specified demo file.\n    pub fn new(file: &mut VirtualFile) -> Result<DemoServer, DemoServerError> {\n        let mut dem_reader = BufReader::new(file);\n\n        let mut buf = ArrayVec::<u8, 3>::new();\n        // copy CD track number (terminated by newline) into buffer\n        for i in 0..buf.capacity() {\n            match dem_reader.read_u8()? {\n                b'\\n' => break,\n                // cannot panic because we won't exceed capacity with a loop this small\n                b => buf.push(b),\n            }\n\n            if i >= buf.capacity() - 1 {\n                // CD track would be more than 2 digits long, which is impossible\n                Err(DemoServerError::InvalidCdTrack)?;\n            }\n        }\n\n        let track_override = {\n            let track_str = match std::str::from_utf8(&buf) {\n                Ok(s) => s,\n                Err(_) => Err(DemoServerError::InvalidCdTrack)?,\n            };\n\n            match track_str {\n                // if track is empty, default to track 0\n                \"\" => Some(0),\n                s => match s.parse::<i32>() {\n                    Ok(track) => match track {\n                        // if track is -1, allow demo to specify tracks in messages\n                        -1 => None,\n                        t if t < -1 => Err(DemoServerError::InvalidCdTrack)?,\n                        _ => Some(track as u32),\n                    },\n                    Err(_) => Err(DemoServerError::InvalidCdTrack)?,\n                },\n            }\n        };\n\n        let mut message_data = Vec::new();\n        let mut messages = Vec::new();\n\n        // read all messages\n        while let Ok(msg_len) = dem_reader.read_u32::<LittleEndian>() {\n            // get view angles\n            let view_angles_f32 = read_f32_3(&mut dem_reader)?;\n            let view_angles = Vector3::new(\n                Deg(view_angles_f32[0]),\n                Deg(view_angles_f32[1]),\n                Deg(view_angles_f32[2]),\n            );\n\n            // read next message\n            let msg_start = message_data.len();\n            for _ in 0..msg_len {\n                message_data.push(dem_reader.read_u8()?);\n            }\n            let msg_end = message_data.len();\n\n            messages.push(DemoMessage {\n                view_angles,\n                msg_range: msg_start..msg_end,\n            });\n        }\n\n        Ok(DemoServer {\n            track_override,\n            message_id: 0,\n            messages,\n            message_data,\n        })\n    }\n\n    /// Retrieve the next server message from the currently playing demo.\n    ///\n    /// If this returns `None`, the demo is complete.\n    pub fn next(&mut self) -> Option<DemoMessageView> {\n        if self.message_id >= self.messages.len() {\n            return None;\n        }\n\n        let msg = &self.messages[self.message_id];\n        self.message_id += 1;\n\n        Some(DemoMessageView {\n            view_angles: msg.view_angles,\n            message: &self.message_data[msg.msg_range.clone()],\n        })\n    }\n\n    /// Returns the currently playing demo's music track override, if any.\n    ///\n    /// If this is `Some`, any `CdTrack` commands from the demo server should\n    /// cause the client to play this track instead of the one specified by the\n    /// command.\n    pub fn track_override(&self) -> Option<u32> {\n        self.track_override\n    }\n}\n"
  },
  {
    "path": "src/client/entity/mod.rs",
    "content": "// Copyright © 2020 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\npub mod particle;\n\nuse crate::common::{\n    alloc::LinkedSlab,\n    engine,\n    net::{EntityEffects, EntityState, EntityUpdate},\n};\n\nuse cgmath::{Deg, Vector3};\nuse chrono::Duration;\n\n// if this is changed, it must also be changed in deferred.frag\npub const MAX_LIGHTS: usize = 32;\npub const MAX_BEAMS: usize = 24;\npub const MAX_TEMP_ENTITIES: usize = 64;\npub const MAX_STATIC_ENTITIES: usize = 128;\n\n#[derive(Debug)]\npub struct ClientEntity {\n    pub force_link: bool,\n    pub baseline: EntityState,\n    pub msg_time: Duration,\n    pub msg_origins: [Vector3<f32>; 2],\n    pub origin: Vector3<f32>,\n    pub msg_angles: [Vector3<Deg<f32>>; 2],\n    pub angles: Vector3<Deg<f32>>,\n    pub model_id: usize,\n    model_changed: bool,\n    pub frame_id: usize,\n    pub skin_id: usize,\n    colormap: Option<u8>,\n    pub sync_base: Duration,\n    pub effects: EntityEffects,\n    pub light_id: Option<usize>,\n    // vis_frame: usize,\n}\n\nimpl ClientEntity {\n    pub fn from_baseline(baseline: EntityState) -> ClientEntity {\n        ClientEntity {\n            force_link: false,\n            baseline: baseline.clone(),\n            msg_time: Duration::zero(),\n            msg_origins: [Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0)],\n            origin: baseline.origin,\n            msg_angles: [\n                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),\n                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),\n            ],\n            angles: baseline.angles,\n            model_id: baseline.model_id,\n            model_changed: false,\n            frame_id: baseline.frame_id,\n            skin_id: baseline.skin_id,\n            colormap: None,\n            sync_base: Duration::zero(),\n            effects: baseline.effects,\n            light_id: None,\n        }\n    }\n\n    pub fn uninitialized() -> ClientEntity {\n        ClientEntity {\n            force_link: false,\n            baseline: EntityState::uninitialized(),\n            msg_time: Duration::zero(),\n            msg_origins: [Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0)],\n            origin: Vector3::new(0.0, 0.0, 0.0),\n            msg_angles: [\n                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),\n                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),\n            ],\n            angles: Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),\n            model_id: 0,\n            model_changed: false,\n            frame_id: 0,\n            skin_id: 0,\n            colormap: None,\n            sync_base: Duration::zero(),\n            effects: EntityEffects::empty(),\n            light_id: None,\n        }\n    }\n\n    /// Update the entity with values from the server.\n    ///\n    /// `msg_times` specifies the last two message times from the server, where\n    /// `msg_times[0]` is more recent.\n    pub fn update(&mut self, msg_times: [Duration; 2], update: EntityUpdate) {\n        // enable lerping\n        self.force_link = false;\n\n        if update.no_lerp || self.msg_time != msg_times[1] {\n            self.force_link = true;\n        }\n\n        self.msg_time = msg_times[0];\n\n        // fill in missing values from baseline\n        let new_state = update.to_entity_state(&self.baseline);\n\n        self.msg_origins[1] = self.msg_origins[0];\n        self.msg_origins[0] = new_state.origin;\n        self.msg_angles[1] = self.msg_angles[0];\n        self.msg_angles[0] = new_state.angles;\n\n        if self.model_id != new_state.model_id {\n            self.model_changed = true;\n            self.force_link = true;\n            self.model_id = new_state.model_id;\n        }\n\n        self.frame_id = new_state.frame_id;\n        self.skin_id = new_state.skin_id;\n        self.effects = new_state.effects;\n        self.colormap = update.colormap;\n\n        if self.force_link {\n            self.msg_origins[1] = self.msg_origins[0];\n            self.origin = self.msg_origins[0];\n            self.msg_angles[1] = self.msg_angles[0];\n            self.angles = self.msg_angles[0];\n        }\n    }\n\n    /// Sets the entity's most recent message angles to the specified value.\n    ///\n    /// This is primarily useful for allowing interpolated view angles in demos.\n    pub fn update_angles(&mut self, angles: Vector3<Deg<f32>>) {\n        self.msg_angles[0] = angles;\n    }\n\n    /// Sets the entity's angles to the specified value, overwriting the message\n    /// history.\n    ///\n    /// This causes the entity to \"snap\" to the correct angle rather than\n    /// interpolating to it.\n    pub fn set_angles(&mut self, angles: Vector3<Deg<f32>>) {\n        self.msg_angles[0] = angles;\n        self.msg_angles[1] = angles;\n        self.angles = angles;\n    }\n\n    /// Returns the timestamp of the last message that updated this entity.\n    pub fn msg_time(&self) -> Duration {\n        self.msg_time\n    }\n\n    /// Returns true if the last update to this entity changed its model.\n    pub fn model_changed(&self) -> bool {\n        self.model_changed\n    }\n\n    pub fn colormap(&self) -> Option<u8> {\n        self.colormap\n    }\n\n    pub fn get_origin(&self) -> Vector3<f32> {\n        self.origin\n    }\n\n    pub fn get_angles(&self) -> Vector3<Deg<f32>> {\n        self.angles\n    }\n\n    pub fn model_id(&self) -> usize {\n        self.model_id\n    }\n\n    pub fn frame_id(&self) -> usize {\n        self.frame_id\n    }\n\n    pub fn skin_id(&self) -> usize {\n        self.skin_id\n    }\n}\n\n/// A descriptor used to spawn dynamic lights.\n#[derive(Clone, Debug)]\npub struct LightDesc {\n    /// The origin of the light.\n    pub origin: Vector3<f32>,\n\n    /// The initial radius of the light.\n    pub init_radius: f32,\n\n    /// The rate of radius decay in units/second.\n    pub decay_rate: f32,\n\n    /// If the radius decays to this value, the light is ignored.\n    pub min_radius: Option<f32>,\n\n    /// Time-to-live of the light.\n    pub ttl: Duration,\n}\n\n/// A dynamic point light.\n#[derive(Clone, Debug)]\npub struct Light {\n    origin: Vector3<f32>,\n    init_radius: f32,\n    decay_rate: f32,\n    min_radius: Option<f32>,\n    spawned: Duration,\n    ttl: Duration,\n}\n\nimpl Light {\n    /// Create a light from a `LightDesc` at the specified time.\n    pub fn from_desc(time: Duration, desc: LightDesc) -> Light {\n        Light {\n            origin: desc.origin,\n            init_radius: desc.init_radius,\n            decay_rate: desc.decay_rate,\n            min_radius: desc.min_radius,\n            spawned: time,\n            ttl: desc.ttl,\n        }\n    }\n\n    /// Return the origin of the light.\n    pub fn origin(&self) -> Vector3<f32> {\n        self.origin\n    }\n\n    /// Return the radius of the light for the given time.\n    ///\n    /// If the radius would decay to a negative value, returns 0.\n    pub fn radius(&self, time: Duration) -> f32 {\n        let lived = time - self.spawned;\n        let decay = self.decay_rate * engine::duration_to_f32(lived);\n        let radius = (self.init_radius - decay).max(0.0);\n\n        if let Some(min) = self.min_radius {\n            if radius < min {\n                return 0.0;\n            }\n        }\n\n        radius\n    }\n\n    /// Returns `true` if the light should be retained at the specified time.\n    pub fn retain(&mut self, time: Duration) -> bool {\n        self.spawned + self.ttl > time\n    }\n}\n\n/// A set of active dynamic lights.\npub struct Lights {\n    slab: LinkedSlab<Light>,\n}\n\nimpl Lights {\n    /// Create an empty set of lights with the given capacity.\n    pub fn with_capacity(capacity: usize) -> Lights {\n        Lights {\n            slab: LinkedSlab::with_capacity(capacity),\n        }\n    }\n\n    /// Return a reference to the light with the given key, or `None` if no\n    /// such light exists.\n    pub fn get(&self, key: usize) -> Option<&Light> {\n        self.slab.get(key)\n    }\n\n    /// Return a mutable reference to the light with the given key, or `None`\n    /// if no such light exists.\n    pub fn get_mut(&mut self, key: usize) -> Option<&mut Light> {\n        self.slab.get_mut(key)\n    }\n\n    /// Insert a new light into the set of lights.\n    ///\n    /// Returns a key corresponding to the newly inserted light.\n    ///\n    /// If `key` is `Some` and there is an existing light with that key, then\n    /// the light will be overwritten with the new value.\n    pub fn insert(&mut self, time: Duration, desc: LightDesc, key: Option<usize>) -> usize {\n        if let Some(k) = key {\n            if let Some(key_light) = self.slab.get_mut(k) {\n                *key_light = Light::from_desc(time, desc);\n                return k;\n            }\n        }\n\n        self.slab.insert(Light::from_desc(time, desc))\n    }\n\n    /// Return an iterator over the active lights.\n    pub fn iter(&self) -> impl Iterator<Item = &Light> {\n        self.slab.iter()\n    }\n\n    /// Updates the set of dynamic lights for the specified time.\n    ///\n    /// This will deallocate any lights which have outlived their time-to-live.\n    pub fn update(&mut self, time: Duration) {\n        self.slab.retain(|_, light| light.retain(time));\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct Beam {\n    pub entity_id: usize,\n    pub model_id: usize,\n    pub expire: Duration,\n    pub start: Vector3<f32>,\n    pub end: Vector3<f32>,\n}\n"
  },
  {
    "path": "src/client/entity/particle.rs",
    "content": "// Copyright © 2020 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::ops::RangeInclusive;\n\nuse crate::{\n    client::ClientEntity,\n    common::{\n        alloc::LinkedSlab,\n        engine,\n        math::{self, VERTEX_NORMAL_COUNT},\n    },\n};\n\nuse cgmath::{InnerSpace as _, Vector3, Zero as _};\nuse chrono::Duration;\nuse rand::{\n    distributions::{Distribution as _, Uniform},\n    rngs::SmallRng,\n    SeedableRng,\n};\n\nlazy_static! {\n    static ref COLOR_RAMP_EXPLOSION_FAST: ColorRamp = ColorRamp {\n        ramp: vec![0x6F, 0x6D, 0x6B, 0x69, 0x67, 0x65, 0x63, 0x61],\n        fps: 10.0,\n    };\n    static ref COLOR_RAMP_EXPLOSION_SLOW: ColorRamp = ColorRamp {\n        ramp: vec![0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x68, 0x66],\n        fps: 5.0,\n    };\n    static ref COLOR_RAMP_FIRE: ColorRamp = ColorRamp {\n        ramp: vec![0x6D, 0x6B, 0x06, 0x05, 0x04, 0x03],\n        fps: 15.0,\n    };\n    static ref EXPLOSION_SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(-16.0, 16.0);\n    static ref EXPLOSION_VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(-256.0, 256.0);\n}\n\n// TODO: make max configurable\npub const MIN_PARTICLES: usize = 512;\n\n// should be possible to get the whole particle list in cache at once\npub const MAX_PARTICLES: usize = 16384;\n\n/// An animated color ramp.\n///\n/// Colors are specified using 8-bit indexed values, which should be translated\n/// using the palette.\n#[derive(Debug)]\npub struct ColorRamp {\n    // TODO: arrayvec, tinyvec, or array once const generics are stable\n    ramp: Vec<u8>,\n\n    // frames per second of the animation\n    fps: f32,\n}\n\nimpl ColorRamp {\n    /// Returns the frame corresponding to the given time.\n    ///\n    /// If the animation has already completed by `elapsed`, returns `None`.\n    pub fn color(&self, elapsed: Duration, frame_skip: usize) -> Option<u8> {\n        let frame = (engine::duration_to_f32(elapsed) * self.fps) as usize + frame_skip;\n        self.ramp.get(frame).map(|c| *c)\n    }\n}\n\n/// Dictates the behavior of a particular particle.\n///\n/// Particles which are animated with a color ramp are despawned automatically\n/// when the animation is complete.\n#[derive(Copy, Clone, Debug)]\npub enum ParticleKind {\n    /// Normal particle, unaffected by gravity.\n    Static,\n\n    /// Normal particle, affected by gravity.\n    Grav,\n\n    /// Fire and smoke particles. Animated using `COLOR_RAMP_FIRE`. Inversely\n    /// affected by gravity, rising instead of falling.\n    Fire {\n        /// Specifies the number of frames to skip.\n        frame_skip: usize,\n    },\n\n    /// Explosion particles. May have `COLOR_RAMP_EXPLOSION_FAST` or\n    /// `COLOR_RAMP_EXPLOSION_SLOW`. Affected by gravity.\n    Explosion {\n        /// Specifies the color ramp to use.\n        ramp: &'static ColorRamp,\n\n        /// Specifies the number of frames to skip.\n        frame_skip: usize,\n    },\n\n    /// Spawn (enemy) death explosion particle. Accelerates at\n    /// `v(t2) = v(t1) + 4 * (t2 - t1)`. May or may not have an intrinsic\n    /// z-velocity.\n    Blob {\n        /// If false, particle only moves in the XY plane and is unaffected by\n        /// gravity.\n        has_z_velocity: bool,\n    },\n}\n\n/// Factor at which particles are affected by gravity.\npub const PARTICLE_GRAVITY_FACTOR: f32 = 0.05;\n\n/// A live particle.\n#[derive(Copy, Clone, Debug)]\npub struct Particle {\n    kind: ParticleKind,\n    origin: Vector3<f32>,\n    velocity: Vector3<f32>,\n    color: u8,\n    spawned: Duration,\n    expire: Duration,\n}\n\nimpl Particle {\n    /// Particle update function.\n    ///\n    /// The return value indicates whether the particle should be retained after this\n    /// frame.\n    ///\n    /// For details on how individual particles behave, see the documentation for\n    /// [`ParticleKind`](ParticleKind).\n    pub fn update(&mut self, time: Duration, frame_time: Duration, sv_gravity: f32) -> bool {\n        use ParticleKind::*;\n\n        let velocity_factor = engine::duration_to_f32(frame_time);\n        let gravity = velocity_factor * sv_gravity * PARTICLE_GRAVITY_FACTOR;\n\n        // don't bother updating expired particles\n        if time >= self.expire {\n            return false;\n        }\n\n        match self.kind {\n            Static => true,\n\n            Grav => {\n                self.origin += self.velocity * velocity_factor;\n                self.velocity.z -= gravity;\n                true\n            }\n\n            Fire { frame_skip } => match COLOR_RAMP_FIRE.color(time - self.spawned, frame_skip) {\n                Some(c) => {\n                    self.origin += self.velocity * velocity_factor;\n                    // rises instead of falling\n                    self.velocity.z += gravity;\n                    self.color = c;\n                    true\n                }\n                None => false,\n            },\n\n            Explosion { ramp, frame_skip } => match ramp.color(time - self.spawned, frame_skip) {\n                Some(c) => {\n                    self.origin += self.velocity * velocity_factor;\n                    self.velocity.z -= gravity;\n                    self.color = c;\n                    true\n                }\n                None => false,\n            },\n\n            Blob { has_z_velocity } => {\n                if !has_z_velocity {\n                    let xy_velocity = Vector3::new(self.velocity.x, self.velocity.y, 0.0);\n                    self.origin += xy_velocity * velocity_factor;\n                } else {\n                    self.origin += self.velocity * velocity_factor;\n                    self.velocity.z -= gravity;\n                }\n\n                true\n            }\n        }\n    }\n\n    pub fn origin(&self) -> Vector3<f32> {\n        self.origin\n    }\n\n    pub fn color(&self) -> u8 {\n        self.color\n    }\n}\n\npub enum TrailKind {\n    Rocket = 0,\n    Smoke = 1,\n    Blood = 2,\n    TracerGreen = 3,\n    BloodSlight = 4,\n    TracerRed = 5,\n    Vore = 6,\n}\n\n/// A list of particles.\n///\n/// Space for new particles is allocated from an internal [`Slab`](slab::Slab) of fixed\n/// size.\npub struct Particles {\n    // allocation pool\n    slab: LinkedSlab<Particle>,\n\n    // random number generator\n    rng: SmallRng,\n\n    angle_velocities: [Vector3<f32>; VERTEX_NORMAL_COUNT],\n}\n\nimpl Particles {\n    /// Create a new particle list with the given capacity.\n    ///\n    /// This determines the capacity of both the underlying `Slab` and the set of\n    /// live particles.\n    pub fn with_capacity(capacity: usize) -> Particles {\n        lazy_static! {\n            // avelocities initialized with (rand() & 255) * 0.01;\n            static ref VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 2.56);\n        }\n\n        let slab = LinkedSlab::with_capacity(capacity.min(MAX_PARTICLES));\n        let rng = SmallRng::from_entropy();\n        let angle_velocities = [Vector3::zero(); VERTEX_NORMAL_COUNT];\n\n        let mut particles = Particles {\n            slab,\n            rng,\n            angle_velocities,\n        };\n\n        for i in 0..angle_velocities.len() {\n            particles.angle_velocities[i] = particles.random_vector3(&VELOCITY_DISTRIBUTION);\n        }\n\n        particles\n    }\n\n    /// Insert a particle into the live list.\n    // TODO: come up with a better eviction policy\n    // the original engine ignores new particles if at capacity, but it's not ideal\n    pub fn insert(&mut self, particle: Particle) -> bool {\n        // check capacity\n        if self.slab.len() == self.slab.capacity() {\n            return false;\n        }\n\n        // insert it\n        self.slab.insert(particle);\n        true\n    }\n\n    /// Clears all particles.\n    pub fn clear(&mut self) {\n        self.slab.clear();\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = &Particle> {\n        self.slab.iter()\n    }\n\n    /// Update all live particles, deleting any that are expired.\n    ///\n    /// Particles are updated with [Particle::update]. That\n    /// function's return value indicates whether the particle should be retained\n    /// or not.\n    pub fn update(&mut self, time: Duration, frame_time: Duration, sv_gravity: f32) {\n        self.slab\n            .retain(|_, particle| particle.update(time, frame_time, sv_gravity));\n    }\n\n    fn scatter(&mut self, origin: Vector3<f32>, scatter_distr: &Uniform<f32>) -> Vector3<f32> {\n        origin\n            + Vector3::new(\n                scatter_distr.sample(&mut self.rng),\n                scatter_distr.sample(&mut self.rng),\n                scatter_distr.sample(&mut self.rng),\n            )\n    }\n\n    fn random_vector3(&mut self, velocity_distr: &Uniform<f32>) -> Vector3<f32> {\n        Vector3::new(\n            velocity_distr.sample(&mut self.rng),\n            velocity_distr.sample(&mut self.rng),\n            velocity_distr.sample(&mut self.rng),\n        )\n    }\n\n    /// Creates a spherical cloud of particles around an entity.\n    pub fn create_entity_field(&mut self, time: Duration, entity: &ClientEntity) {\n        let beam_length = 16.0;\n        let dist = 64.0;\n\n        for i in 0..VERTEX_NORMAL_COUNT {\n            let float_time = engine::duration_to_f32(time);\n\n            let angles = float_time * self.angle_velocities[i];\n\n            let sin_yaw = angles[0].sin();\n            let cos_yaw = angles[0].cos();\n            let sin_pitch = angles[1].sin();\n            let cos_pitch = angles[1].cos();\n\n            let forward = Vector3::new(cos_pitch * cos_yaw, cos_pitch * sin_yaw, -sin_pitch);\n            let ttl = Duration::milliseconds(10);\n\n            let origin = entity.origin + dist * math::VERTEX_NORMALS[i] + beam_length * forward;\n\n            self.insert(Particle {\n                kind: ParticleKind::Explosion {\n                    ramp: &COLOR_RAMP_EXPLOSION_FAST,\n                    frame_skip: 0,\n                },\n                origin,\n                velocity: Vector3::zero(),\n                color: COLOR_RAMP_EXPLOSION_FAST.ramp[0],\n                spawned: time,\n                expire: time + ttl,\n            });\n        }\n    }\n\n    /// Spawns a cloud of particles at a point.\n    ///\n    /// Each particle's origin is offset by a vector with components sampled\n    /// from `scatter_distr`, and each particle's velocity is assigned a\n    /// vector with components sampled from `velocity_distr`.\n    ///\n    /// Each particle's color is taken from `colors`, which is an inclusive\n    /// range of palette indices. The spawned particles have evenly distributed\n    /// colors throughout the range.\n    pub fn create_random_cloud(\n        &mut self,\n        count: usize,\n        colors: RangeInclusive<u8>,\n        kind: ParticleKind,\n        time: Duration,\n        ttl: Duration,\n        origin: Vector3<f32>,\n        scatter_distr: &Uniform<f32>,\n        velocity_distr: &Uniform<f32>,\n    ) {\n        let color_start = *colors.start() as usize;\n        let color_end = *colors.end() as usize;\n        for i in 0..count {\n            let origin = self.scatter(origin, scatter_distr);\n            let velocity = self.random_vector3(velocity_distr);\n            let color = (color_start + i % (color_end - color_start + 1)) as u8;\n            if !self.insert(Particle {\n                kind,\n                origin,\n                velocity,\n                color,\n                spawned: time,\n                expire: time + ttl,\n            }) {\n                // can't fit any more particles\n                return;\n            };\n        }\n    }\n\n    /// Creates a rocket explosion.\n    pub fn create_explosion(&mut self, time: Duration, origin: Vector3<f32>) {\n        lazy_static! {\n            static ref FRAME_SKIP_DISTRIBUTION: Uniform<usize> = Uniform::new(0, 4);\n        }\n\n        // spawn 512 particles each for both color ramps\n        for ramp in [&*COLOR_RAMP_EXPLOSION_FAST, &*COLOR_RAMP_EXPLOSION_SLOW].iter() {\n            let frame_skip = FRAME_SKIP_DISTRIBUTION.sample(&mut self.rng);\n            self.create_random_cloud(\n                512,\n                ramp.ramp[frame_skip]..=ramp.ramp[frame_skip],\n                ParticleKind::Explosion { ramp, frame_skip },\n                time,\n                Duration::seconds(5),\n                origin,\n                &EXPLOSION_SCATTER_DISTRIBUTION,\n                &EXPLOSION_VELOCITY_DISTRIBUTION,\n            );\n        }\n    }\n\n    /// Creates an explosion using the given range of colors.\n    pub fn create_color_explosion(\n        &mut self,\n        time: Duration,\n        origin: Vector3<f32>,\n        colors: RangeInclusive<u8>,\n    ) {\n        self.create_random_cloud(\n            512,\n            colors,\n            ParticleKind::Blob {\n                has_z_velocity: true,\n            },\n            time,\n            Duration::milliseconds(300),\n            origin,\n            &EXPLOSION_SCATTER_DISTRIBUTION,\n            &EXPLOSION_VELOCITY_DISTRIBUTION,\n        );\n    }\n\n    /// Creates a death explosion for the Spawn.\n    pub fn create_spawn_explosion(&mut self, time: Duration, origin: Vector3<f32>) {\n        // R_BlobExplosion picks a random ttl with 1 + (rand() & 8) * 0.05\n        // which gives a value of either 1 or 1.4 seconds.\n        // (it's possible it was supposed to be 1 + (rand() & 7) * 0.05, which\n        // would yield between 1 and 1.35 seconds in increments of 50ms.)\n        let ttls = [Duration::seconds(1), Duration::milliseconds(1400)];\n\n        for ttl in ttls.iter().cloned() {\n            self.create_random_cloud(\n                256,\n                66..=71,\n                ParticleKind::Blob {\n                    has_z_velocity: true,\n                },\n                time,\n                ttl,\n                origin,\n                &EXPLOSION_SCATTER_DISTRIBUTION,\n                &EXPLOSION_VELOCITY_DISTRIBUTION,\n            );\n\n            self.create_random_cloud(\n                256,\n                150..=155,\n                ParticleKind::Blob {\n                    has_z_velocity: false,\n                },\n                time,\n                ttl,\n                origin,\n                &EXPLOSION_SCATTER_DISTRIBUTION,\n                &EXPLOSION_VELOCITY_DISTRIBUTION,\n            );\n        }\n    }\n\n    /// Creates a projectile impact.\n    pub fn create_projectile_impact(\n        &mut self,\n        time: Duration,\n        origin: Vector3<f32>,\n        direction: Vector3<f32>,\n        color: u8,\n        count: usize,\n    ) {\n        lazy_static! {\n            static ref SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(-8.0, 8.0);\n\n            // any color in block of 8 (see below)\n            static ref COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(0, 8);\n\n            // ttl between 0.1 and 0.5 seconds\n            static ref TTL_DISTRIBUTION: Uniform<i64> = Uniform::new(100, 500);\n        }\n\n        for _ in 0..count {\n            let scatter = self.random_vector3(&SCATTER_DISTRIBUTION);\n\n            // picks any color in the block of 8 the original color belongs to.\n            // e.g., if the color argument is 17, picks randomly in [16, 23]\n            let color = (color & !7) + COLOR_DISTRIBUTION.sample(&mut self.rng);\n\n            let ttl = Duration::milliseconds(TTL_DISTRIBUTION.sample(&mut self.rng));\n\n            self.insert(Particle {\n                kind: ParticleKind::Grav,\n                origin: origin + scatter,\n                velocity: 15.0 * direction,\n                color,\n                spawned: time,\n                expire: time + ttl,\n            });\n        }\n    }\n\n    /// Creates a lava splash effect.\n    pub fn create_lava_splash(&mut self, time: Duration, origin: Vector3<f32>) {\n        lazy_static! {\n            // ttl between 2 and 2.64 seconds\n            static ref TTL_DISTRIBUTION: Uniform<i64> = Uniform::new(2000, 2640);\n\n            // any color on row 14\n            static ref COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(224, 232);\n\n            static ref DIR_OFFSET_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 8.0);\n            static ref SCATTER_Z_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 64.0);\n            static ref VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(50.0, 114.0);\n        }\n\n        for i in -16..16 {\n            for j in -16..16 {\n                let direction = Vector3::new(\n                    8.0 * i as f32 + DIR_OFFSET_DISTRIBUTION.sample(&mut self.rng),\n                    8.0 * j as f32 + DIR_OFFSET_DISTRIBUTION.sample(&mut self.rng),\n                    256.0,\n                );\n\n                let scatter = Vector3::new(\n                    direction.x,\n                    direction.y,\n                    SCATTER_Z_DISTRIBUTION.sample(&mut self.rng),\n                );\n\n                let velocity = VELOCITY_DISTRIBUTION.sample(&mut self.rng);\n\n                let color = COLOR_DISTRIBUTION.sample(&mut self.rng);\n                let ttl = Duration::milliseconds(TTL_DISTRIBUTION.sample(&mut self.rng));\n\n                self.insert(Particle {\n                    kind: ParticleKind::Grav,\n                    origin: origin + scatter,\n                    velocity: direction.normalize() * velocity,\n                    color,\n                    spawned: time,\n                    expire: time + ttl,\n                });\n            }\n        }\n    }\n\n    /// Creates a teleporter warp effect.\n    pub fn create_teleporter_warp(&mut self, time: Duration, origin: Vector3<f32>) {\n        lazy_static! {\n            // ttl between 0.2 and 0.34 seconds\n            static ref TTL_DISTRIBUTION: Uniform<i64> = Uniform::new(200, 340);\n\n            // random grey particles\n            static ref COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(7, 14);\n\n            static ref SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 4.0);\n            static ref VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(50.0, 114.0);\n        }\n\n        for i in (-16..16).step_by(4) {\n            for j in (-16..16).step_by(4) {\n                for k in (-24..32).step_by(4) {\n                    let direction = Vector3::new(j as f32, i as f32, k as f32) * 8.0;\n                    let scatter = Vector3::new(i as f32, j as f32, k as f32)\n                        + self.random_vector3(&SCATTER_DISTRIBUTION);\n                    let velocity = VELOCITY_DISTRIBUTION.sample(&mut self.rng);\n                    let color = COLOR_DISTRIBUTION.sample(&mut self.rng);\n                    let ttl = Duration::milliseconds(TTL_DISTRIBUTION.sample(&mut self.rng));\n\n                    self.insert(Particle {\n                        kind: ParticleKind::Grav,\n                        origin: origin + scatter,\n                        velocity: direction.normalize() * velocity,\n                        color,\n                        spawned: time,\n                        expire: time + ttl,\n                    });\n                }\n            }\n        }\n    }\n\n    /// Create a particle trail between two points.\n    ///\n    /// Used for rocket fire/smoke trails, blood spatter, and projectile tracers.\n    /// If `sparse` is true, the interval between particles is increased by 3 units.\n    pub fn create_trail(\n        &mut self,\n        time: Duration,\n        start: Vector3<f32>,\n        end: Vector3<f32>,\n        kind: TrailKind,\n        sparse: bool,\n    ) {\n        use TrailKind::*;\n\n        lazy_static! {\n            static ref SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(-3.0, 3.0);\n            static ref FRAME_SKIP_DISTRIBUTION: Uniform<usize> = Uniform::new(0, 4);\n            static ref BLOOD_COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(67, 71);\n            static ref VORE_COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(152, 156);\n        }\n\n        let distance = (end - start).magnitude();\n        let direction = (end - start).normalize();\n\n        // particle interval in units\n        let interval = if sparse { 3.0 } else { 1.0 }\n            + match kind {\n                BloodSlight => 3.0,\n                _ => 0.0,\n            };\n\n        let ttl = Duration::seconds(2);\n\n        for step in 0..(distance / interval) as i32 {\n            let frame_skip = FRAME_SKIP_DISTRIBUTION.sample(&mut self.rng);\n            let particle_kind = match kind {\n                Rocket => ParticleKind::Fire { frame_skip },\n                Smoke => ParticleKind::Fire {\n                    frame_skip: frame_skip + 2,\n                },\n                Blood | BloodSlight => ParticleKind::Grav,\n                TracerGreen | TracerRed | Vore => ParticleKind::Static,\n            };\n\n            let scatter = self.random_vector3(&SCATTER_DISTRIBUTION);\n\n            let origin = start\n                + direction * interval\n                + match kind {\n                    // vore scatter is [-16, 15] in original\n                    // this gives range of ~[-16, 16]\n                    Vore => scatter * 5.33,\n                    _ => scatter,\n                };\n\n            let velocity = match kind {\n                TracerGreen | TracerRed => {\n                    30.0 * if step & 1 == 1 {\n                        Vector3::new(direction.y, -direction.x, 0.0)\n                    } else {\n                        Vector3::new(-direction.y, direction.x, 0.0)\n                    }\n                }\n\n                _ => Vector3::zero(),\n            };\n\n            let color = match kind {\n                Rocket => COLOR_RAMP_FIRE.ramp[frame_skip],\n                Smoke => COLOR_RAMP_FIRE.ramp[frame_skip + 2],\n                Blood | BloodSlight => BLOOD_COLOR_DISTRIBUTION.sample(&mut self.rng),\n                TracerGreen => 52 + 2 * (step & 4) as u8,\n                TracerRed => 230 + 2 * (step & 4) as u8,\n                Vore => VORE_COLOR_DISTRIBUTION.sample(&mut self.rng),\n            };\n\n            self.insert(Particle {\n                kind: particle_kind,\n                origin,\n                velocity,\n                color,\n                spawned: time,\n                expire: time + ttl,\n            });\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use cgmath::Zero;\n\n    fn particles_eq(p1: &Particle, p2: &Particle) -> bool {\n        p1.color == p2.color && p1.velocity == p2.velocity && p1.origin == p2.origin\n    }\n\n    #[test]\n    fn test_particle_list_update() {\n        let mut list = Particles::with_capacity(10);\n        let exp_times = vec![10, 5, 2, 7, 3];\n        for exp in exp_times.iter() {\n            list.insert(Particle {\n                kind: ParticleKind::Static,\n                origin: Vector3::zero(),\n                velocity: Vector3::zero(),\n                color: 0,\n                spawned: Duration::zero(),\n                expire: Duration::seconds(*exp),\n            });\n        }\n\n        let expected: Vec<_> = exp_times\n            .iter()\n            .filter(|t| **t > 5)\n            .map(|t| Particle {\n                kind: ParticleKind::Static,\n                origin: Vector3::zero(),\n                velocity: Vector3::zero(),\n                color: 0,\n                spawned: Duration::zero(),\n                expire: Duration::seconds(*t),\n            })\n            .collect();\n        let mut after_update: Vec<Particle> = Vec::new();\n        list.update(Duration::seconds(5), Duration::milliseconds(17), 10.0);\n        after_update\n            .iter()\n            .zip(expected.iter())\n            .for_each(|(p1, p2)| assert!(particles_eq(p1, p2)));\n    }\n}\n"
  },
  {
    "path": "src/client/input/console.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{cell::RefCell, rc::Rc};\n\nuse crate::common::console::Console;\n\nuse failure::Error;\nuse winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode as Key, WindowEvent};\n\npub struct ConsoleInput {\n    console: Rc<RefCell<Console>>,\n}\n\nimpl ConsoleInput {\n    pub fn new(console: Rc<RefCell<Console>>) -> ConsoleInput {\n        ConsoleInput { console }\n    }\n\n    pub fn handle_event<T>(&self, event: Event<T>) -> Result<(), Error> {\n        match event {\n            Event::WindowEvent { event, .. } => match event {\n                WindowEvent::ReceivedCharacter(c) => self.console.borrow_mut().send_char(c),\n\n                WindowEvent::KeyboardInput {\n                    input:\n                        KeyboardInput {\n                            virtual_keycode: Some(key),\n                            state: ElementState::Pressed,\n                            ..\n                        },\n                    ..\n                } => match key {\n                    Key::Up => self.console.borrow_mut().history_up(),\n                    Key::Down => self.console.borrow_mut().history_down(),\n                    Key::Left => self.console.borrow_mut().cursor_left(),\n                    Key::Right => self.console.borrow_mut().cursor_right(),\n                    Key::Grave => self.console.borrow_mut().stuff_text(\"toggleconsole\\n\"),\n                    _ => (),\n                },\n\n                _ => (),\n            },\n\n            _ => (),\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/client/input/game.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{\n    cell::{Cell, RefCell},\n    collections::HashMap,\n    rc::Rc,\n    str::FromStr,\n    string::ToString,\n};\n\nuse crate::common::{\n    console::{CmdRegistry, Console},\n    parse,\n};\n\nuse failure::Error;\nuse strum::IntoEnumIterator;\nuse strum_macros::EnumIter;\nuse winit::{\n    dpi::LogicalPosition,\n    event::{\n        DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta,\n        VirtualKeyCode as Key, WindowEvent,\n    },\n};\n\nconst ACTION_COUNT: usize = 19;\n\nstatic INPUT_NAMES: [&'static str; 79] = [\n    \",\",\n    \".\",\n    \"/\",\n    \"0\",\n    \"1\",\n    \"2\",\n    \"3\",\n    \"4\",\n    \"5\",\n    \"6\",\n    \"7\",\n    \"8\",\n    \"9\",\n    \"A\",\n    \"ALT\",\n    \"B\",\n    \"BACKSPACE\",\n    \"C\",\n    \"CTRL\",\n    \"D\",\n    \"DEL\",\n    \"DOWNARROW\",\n    \"E\",\n    \"END\",\n    \"ENTER\",\n    \"ESCAPE\",\n    \"F\",\n    \"F1\",\n    \"F10\",\n    \"F11\",\n    \"F12\",\n    \"F2\",\n    \"F3\",\n    \"F4\",\n    \"F5\",\n    \"F6\",\n    \"F7\",\n    \"F8\",\n    \"F9\",\n    \"G\",\n    \"H\",\n    \"HOME\",\n    \"I\",\n    \"INS\",\n    \"J\",\n    \"K\",\n    \"L\",\n    \"LEFTARROW\",\n    \"M\",\n    \"MOUSE1\",\n    \"MOUSE2\",\n    \"MOUSE3\",\n    \"MWHEELDOWN\",\n    \"MWHEELUP\",\n    \"N\",\n    \"O\",\n    \"P\",\n    \"PGDN\",\n    \"PGUP\",\n    \"Q\",\n    \"R\",\n    \"RIGHTARROW\",\n    \"S\",\n    \"SEMICOLON\",\n    \"SHIFT\",\n    \"SPACE\",\n    \"T\",\n    \"TAB\",\n    \"U\",\n    \"UPARROW\",\n    \"V\",\n    \"W\",\n    \"X\",\n    \"Y\",\n    \"Z\",\n    \"[\",\n    \"\\\\\",\n    \"]\",\n    \"`\",\n];\n\nstatic INPUT_VALUES: [BindInput; 79] = [\n    BindInput::Key(Key::Comma),\n    BindInput::Key(Key::Period),\n    BindInput::Key(Key::Slash),\n    BindInput::Key(Key::Key0),\n    BindInput::Key(Key::Key1),\n    BindInput::Key(Key::Key2),\n    BindInput::Key(Key::Key3),\n    BindInput::Key(Key::Key4),\n    BindInput::Key(Key::Key5),\n    BindInput::Key(Key::Key6),\n    BindInput::Key(Key::Key7),\n    BindInput::Key(Key::Key8),\n    BindInput::Key(Key::Key9),\n    BindInput::Key(Key::A),\n    BindInput::Key(Key::LAlt),\n    BindInput::Key(Key::B),\n    BindInput::Key(Key::Back),\n    BindInput::Key(Key::C),\n    BindInput::Key(Key::LControl),\n    BindInput::Key(Key::D),\n    BindInput::Key(Key::Delete),\n    BindInput::Key(Key::Down),\n    BindInput::Key(Key::E),\n    BindInput::Key(Key::End),\n    BindInput::Key(Key::Return),\n    BindInput::Key(Key::Escape),\n    BindInput::Key(Key::F),\n    BindInput::Key(Key::F1),\n    BindInput::Key(Key::F10),\n    BindInput::Key(Key::F11),\n    BindInput::Key(Key::F12),\n    BindInput::Key(Key::F2),\n    BindInput::Key(Key::F3),\n    BindInput::Key(Key::F4),\n    BindInput::Key(Key::F5),\n    BindInput::Key(Key::F6),\n    BindInput::Key(Key::F7),\n    BindInput::Key(Key::F8),\n    BindInput::Key(Key::F9),\n    BindInput::Key(Key::G),\n    BindInput::Key(Key::H),\n    BindInput::Key(Key::Home),\n    BindInput::Key(Key::I),\n    BindInput::Key(Key::Insert),\n    BindInput::Key(Key::J),\n    BindInput::Key(Key::K),\n    BindInput::Key(Key::L),\n    BindInput::Key(Key::Left),\n    BindInput::Key(Key::M),\n    BindInput::MouseButton(MouseButton::Left),\n    BindInput::MouseButton(MouseButton::Right),\n    BindInput::MouseButton(MouseButton::Middle),\n    BindInput::MouseWheel(MouseWheel::Down),\n    BindInput::MouseWheel(MouseWheel::Up),\n    BindInput::Key(Key::N),\n    BindInput::Key(Key::O),\n    BindInput::Key(Key::P),\n    BindInput::Key(Key::PageDown),\n    BindInput::Key(Key::PageUp),\n    BindInput::Key(Key::Q),\n    BindInput::Key(Key::R),\n    BindInput::Key(Key::Right),\n    BindInput::Key(Key::S),\n    BindInput::Key(Key::Semicolon),\n    BindInput::Key(Key::LShift),\n    BindInput::Key(Key::Space),\n    BindInput::Key(Key::T),\n    BindInput::Key(Key::Tab),\n    BindInput::Key(Key::U),\n    BindInput::Key(Key::Up),\n    BindInput::Key(Key::V),\n    BindInput::Key(Key::W),\n    BindInput::Key(Key::X),\n    BindInput::Key(Key::Y),\n    BindInput::Key(Key::Z),\n    BindInput::Key(Key::LBracket),\n    BindInput::Key(Key::Backslash),\n    BindInput::Key(Key::RBracket),\n    BindInput::Key(Key::Grave),\n];\n\n/// A unique identifier for an in-game action.\n#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter)]\npub enum Action {\n    /// Move forward.\n    Forward = 0,\n\n    /// Move backward.\n    Back = 1,\n\n    /// Strafe left.\n    MoveLeft = 2,\n\n    /// Strafe right.\n    MoveRight = 3,\n\n    /// Move up (when swimming).\n    MoveUp = 4,\n\n    /// Move down (when swimming).\n    MoveDown = 5,\n\n    /// Look up.\n    LookUp = 6,\n\n    /// Look down.\n    LookDown = 7,\n\n    /// Look left.\n    Left = 8,\n\n    /// Look right.\n    Right = 9,\n\n    /// Change move speed (walk/run).\n    Speed = 10,\n\n    /// Jump.\n    Jump = 11,\n\n    /// Interpret `Left`/`Right` like `MoveLeft`/`MoveRight`.\n    Strafe = 12,\n\n    /// Attack with the current weapon.\n    Attack = 13,\n\n    /// Interact with an object (not used).\n    Use = 14,\n\n    /// Interpret `Forward`/`Back` like `LookUp`/`LookDown`.\n    KLook = 15,\n\n    /// Interpret upward/downward vertical mouse movements like `LookUp`/`LookDown`.\n    MLook = 16,\n\n    /// If in single-player, show the current level stats. If in multiplayer, show the scoreboard.\n    ShowScores = 17,\n\n    /// Show the team scoreboard.\n    ShowTeamScores = 18,\n}\n\nimpl FromStr for Action {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let action = match s.to_lowercase().as_str() {\n            \"forward\" => Action::Forward,\n            \"back\" => Action::Back,\n            \"moveleft\" => Action::MoveLeft,\n            \"moveright\" => Action::MoveRight,\n            \"moveup\" => Action::MoveUp,\n            \"movedown\" => Action::MoveDown,\n            \"lookup\" => Action::LookUp,\n            \"lookdown\" => Action::LookDown,\n            \"left\" => Action::Left,\n            \"right\" => Action::Right,\n            \"speed\" => Action::Speed,\n            \"jump\" => Action::Jump,\n            \"strafe\" => Action::Strafe,\n            \"attack\" => Action::Attack,\n            \"use\" => Action::Use,\n            \"klook\" => Action::KLook,\n            \"mlook\" => Action::MLook,\n            \"showscores\" => Action::ShowScores,\n            \"showteamscores\" => Action::ShowTeamScores,\n            _ => bail!(\"Invalid action name: {}\", s),\n        };\n\n        Ok(action)\n    }\n}\n\nimpl ToString for Action {\n    fn to_string(&self) -> String {\n        String::from(match *self {\n            Action::Forward => \"forward\",\n            Action::Back => \"back\",\n            Action::MoveLeft => \"moveleft\",\n            Action::MoveRight => \"moveright\",\n            Action::MoveUp => \"moveup\",\n            Action::MoveDown => \"movedown\",\n            Action::LookUp => \"lookup\",\n            Action::LookDown => \"lookdown\",\n            Action::Left => \"left\",\n            Action::Right => \"right\",\n            Action::Speed => \"speed\",\n            Action::Jump => \"jump\",\n            Action::Strafe => \"strafe\",\n            Action::Attack => \"attack\",\n            Action::Use => \"use\",\n            Action::KLook => \"klook\",\n            Action::MLook => \"mlook\",\n            Action::ShowScores => \"showscores\",\n            Action::ShowTeamScores => \"showteamscores\",\n        })\n    }\n}\n\n// for game input, we only care about the direction the mouse wheel moved, not how far it went in\n// one event\n/// A movement of the mouse wheel up or down.\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub enum MouseWheel {\n    Up,\n    Down,\n}\n\n// TODO: this currently doesn't handle NaN and treats 0.0 as negative which is probably not optimal\nimpl ::std::convert::From<MouseScrollDelta> for MouseWheel {\n    fn from(src: MouseScrollDelta) -> MouseWheel {\n        match src {\n            MouseScrollDelta::LineDelta(_, y) => {\n                if y > 0.0 {\n                    MouseWheel::Up\n                } else {\n                    MouseWheel::Down\n                }\n            }\n\n            MouseScrollDelta::PixelDelta(LogicalPosition { y, .. }) => {\n                if y > 0.0 {\n                    MouseWheel::Up\n                } else {\n                    MouseWheel::Down\n                }\n            }\n        }\n    }\n}\n\n/// A physical input that can be bound to a command.\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub enum BindInput {\n    /// A key pressed on the keyboard.\n    Key(Key),\n\n    /// A button pressed on the mouse.\n    MouseButton(MouseButton),\n\n    /// A direction scrolled on the mouse wheel.\n    MouseWheel(MouseWheel),\n}\n\nimpl ::std::convert::From<Key> for BindInput {\n    fn from(src: Key) -> BindInput {\n        BindInput::Key(src)\n    }\n}\n\nimpl ::std::convert::From<MouseButton> for BindInput {\n    fn from(src: MouseButton) -> BindInput {\n        BindInput::MouseButton(src)\n    }\n}\n\nimpl ::std::convert::From<MouseWheel> for BindInput {\n    fn from(src: MouseWheel) -> BindInput {\n        BindInput::MouseWheel(src)\n    }\n}\n\nimpl ::std::convert::From<MouseScrollDelta> for BindInput {\n    fn from(src: MouseScrollDelta) -> BindInput {\n        BindInput::MouseWheel(MouseWheel::from(src))\n    }\n}\n\nimpl FromStr for BindInput {\n    type Err = Error;\n\n    fn from_str(src: &str) -> Result<BindInput, Error> {\n        let upper = src.to_uppercase();\n\n        for (i, name) in INPUT_NAMES.iter().enumerate() {\n            if upper == *name {\n                return Ok(INPUT_VALUES[i].clone());\n            }\n        }\n\n        bail!(\"\\\"{}\\\" isn't a valid key\", src);\n    }\n}\n\nimpl ToString for BindInput {\n    fn to_string(&self) -> String {\n        // this could be a binary search but it's unlikely to affect performance much\n        for (i, input) in INPUT_VALUES.iter().enumerate() {\n            if self == input {\n                return INPUT_NAMES[i].to_owned();\n            }\n        }\n\n        String::new()\n    }\n}\n\n/// An operation to perform when a `BindInput` is received.\n#[derive(Clone, Debug)]\npub enum BindTarget {\n    /// An action to set/unset.\n    Action {\n        // + is true, - is false\n        // so \"+forward\" maps to trigger: true, action: Action::Forward\n        trigger: ElementState,\n        action: Action,\n    },\n\n    /// Text to push to the console execution buffer.\n    ConsoleInput { text: String },\n}\n\nimpl FromStr for BindTarget {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match parse::action(s) {\n            // first, check if this is an action\n            Ok((_, (trigger, action_str))) => {\n                let action = match Action::from_str(&action_str) {\n                    Ok(a) => a,\n                    _ => return Ok(BindTarget::ConsoleInput { text: s.to_owned() }),\n                };\n\n                Ok(BindTarget::Action { trigger, action })\n            }\n\n            // if the parse fails, assume it's a cvar/cmd and return the text\n            _ => Ok(BindTarget::ConsoleInput { text: s.to_owned() }),\n        }\n    }\n}\n\nimpl ToString for BindTarget {\n    fn to_string(&self) -> String {\n        match *self {\n            BindTarget::Action { trigger, action } => {\n                String::new()\n                    + match trigger {\n                        ElementState::Pressed => \"+\",\n                        ElementState::Released => \"-\",\n                    }\n                    + &action.to_string()\n            }\n\n            BindTarget::ConsoleInput { ref text } => format!(\"\\\"{}\\\"\", text.to_owned()),\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct GameInput {\n    console: Rc<RefCell<Console>>,\n    bindings: Rc<RefCell<HashMap<BindInput, BindTarget>>>,\n    action_states: Rc<RefCell<[bool; ACTION_COUNT]>>,\n    mouse_delta: (f64, f64),\n    impulse: Rc<Cell<u8>>,\n}\n\nimpl GameInput {\n    pub fn new(console: Rc<RefCell<Console>>) -> GameInput {\n        GameInput {\n            console,\n            bindings: Rc::new(RefCell::new(HashMap::new())),\n            action_states: Rc::new(RefCell::new([false; ACTION_COUNT])),\n            mouse_delta: (0.0, 0.0),\n            impulse: Rc::new(Cell::new(0)),\n        }\n    }\n\n    pub fn mouse_delta(&self) -> (f64, f64) {\n        self.mouse_delta\n    }\n\n    pub fn impulse(&self) -> u8 {\n        self.impulse.get()\n    }\n\n    /// Bind the default controls.\n    pub fn bind_defaults(&mut self) {\n        self.bind(Key::W, BindTarget::from_str(\"+forward\").unwrap());\n        self.bind(Key::A, BindTarget::from_str(\"+moveleft\").unwrap());\n        self.bind(Key::S, BindTarget::from_str(\"+back\").unwrap());\n        self.bind(Key::D, BindTarget::from_str(\"+moveright\").unwrap());\n        self.bind(Key::Space, BindTarget::from_str(\"+jump\").unwrap());\n        self.bind(Key::Up, BindTarget::from_str(\"+lookup\").unwrap());\n        self.bind(Key::Left, BindTarget::from_str(\"+left\").unwrap());\n        self.bind(Key::Down, BindTarget::from_str(\"+lookdown\").unwrap());\n        self.bind(Key::Right, BindTarget::from_str(\"+right\").unwrap());\n        self.bind(Key::LControl, BindTarget::from_str(\"+attack\").unwrap());\n        self.bind(Key::E, BindTarget::from_str(\"+use\").unwrap());\n        self.bind(Key::Grave, BindTarget::from_str(\"toggleconsole\").unwrap());\n        self.bind(Key::Key1, BindTarget::from_str(\"impulse 1\").unwrap());\n        self.bind(Key::Key2, BindTarget::from_str(\"impulse 2\").unwrap());\n        self.bind(Key::Key3, BindTarget::from_str(\"impulse 3\").unwrap());\n        self.bind(Key::Key4, BindTarget::from_str(\"impulse 4\").unwrap());\n        self.bind(Key::Key5, BindTarget::from_str(\"impulse 5\").unwrap());\n        self.bind(Key::Key6, BindTarget::from_str(\"impulse 6\").unwrap());\n        self.bind(Key::Key7, BindTarget::from_str(\"impulse 7\").unwrap());\n        self.bind(Key::Key8, BindTarget::from_str(\"impulse 8\").unwrap());\n        self.bind(Key::Key9, BindTarget::from_str(\"impulse 9\").unwrap());\n    }\n\n    /// Bind a `BindInput` to a `BindTarget`.\n    pub fn bind<I, T>(&mut self, input: I, target: T) -> Option<BindTarget>\n    where\n        I: Into<BindInput>,\n        T: Into<BindTarget>,\n    {\n        self.bindings\n            .borrow_mut()\n            .insert(input.into(), target.into())\n    }\n\n    /// Return the `BindTarget` that `input` is bound to, or `None` if `input` is not present.\n    pub fn binding<I>(&self, input: I) -> Option<BindTarget>\n    where\n        I: Into<BindInput>,\n    {\n        self.bindings.borrow().get(&input.into()).map(|t| t.clone())\n    }\n\n    pub fn handle_event<T>(&mut self, outer_event: Event<T>) {\n        let (input, state): (BindInput, _) = match outer_event {\n            Event::WindowEvent { event, .. } => match event {\n                WindowEvent::KeyboardInput {\n                    input:\n                        KeyboardInput {\n                            state,\n                            virtual_keycode: Some(key),\n                            ..\n                        },\n                    ..\n                } => (key.into(), state),\n\n                WindowEvent::MouseInput { state, button, .. } => (button.into(), state),\n                WindowEvent::MouseWheel { delta, .. } => (delta.into(), ElementState::Pressed),\n                _ => return,\n            },\n\n            Event::DeviceEvent { event, .. } => match event {\n                DeviceEvent::MouseMotion { delta } => {\n                    self.mouse_delta.0 += delta.0;\n                    self.mouse_delta.1 += delta.1;\n                    return;\n                }\n\n                _ => return,\n            },\n\n            _ => return,\n        };\n\n        self.handle_input(input, state);\n    }\n\n    pub fn handle_input<I>(&mut self, input: I, state: ElementState)\n    where\n        I: Into<BindInput>,\n    {\n        let bind_input = input.into();\n\n        // debug!(\"handle input {:?}: {:?}\", &bind_input, state);\n        if let Some(target) = self.bindings.borrow().get(&bind_input) {\n            match *target {\n                BindTarget::Action { trigger, action } => {\n                    self.action_states.borrow_mut()[action as usize] = state == trigger;\n                    debug!(\n                        \"{}{}\",\n                        if state == trigger { '+' } else { '-' },\n                        action.to_string()\n                    );\n                }\n\n                BindTarget::ConsoleInput { ref text } => {\n                    if state == ElementState::Pressed {\n                        self.console.borrow_mut().stuff_text(text);\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn action_state(&self, action: Action) -> bool {\n        self.action_states.borrow()[action as usize]\n    }\n\n    // TODO: roll actions into a loop\n    pub fn register_cmds(&self, cmds: &mut CmdRegistry) {\n        let states = [(\"+\", true), (\"-\", false)];\n        for action in Action::iter() {\n            for (state_str, state_bool) in states.iter().cloned() {\n                let action_states = self.action_states.clone();\n                let cmd_name = format!(\"{}{}\", state_str, action.to_string());\n                cmds.insert_or_replace(\n                    &cmd_name,\n                    Box::new(move |_| {\n                        action_states.borrow_mut()[action as usize] = state_bool;\n                        String::new()\n                    }),\n                )\n                .unwrap();\n            }\n        }\n\n        // \"bind\"\n        let bindings = self.bindings.clone();\n        cmds.insert_or_replace(\n            \"bind\",\n            Box::new(move |args| {\n                match args.len() {\n                    // bind (key)\n                    // queries what (key) is bound to, if anything\n                    1 => match BindInput::from_str(args[0]) {\n                        Ok(i) => match bindings.borrow().get(&i) {\n                            Some(t) => format!(\"\\\"{}\\\" = \\\"{}\\\"\", i.to_string(), t.to_string()),\n                            None => format!(\"\\\"{}\\\" is not bound\", i.to_string()),\n                        },\n\n                        Err(_) => format!(\"\\\"{}\\\" isn't a valid key\", args[0]),\n                    },\n\n                    // bind (key) [command]\n                    2 => match BindInput::from_str(args[0]) {\n                        Ok(input) => match BindTarget::from_str(args[1]) {\n                            Ok(target) => {\n                                bindings.borrow_mut().insert(input, target);\n                                debug!(\"Bound {:?} to {:?}\", input, args[1]);\n                                String::new()\n                            }\n                            Err(_) => {\n                                format!(\"\\\"{}\\\" isn't a valid bind target\", args[1])\n                            }\n                        },\n\n                        Err(_) => format!(\"\\\"{}\\\" isn't a valid key\", args[0]),\n                    },\n\n                    _ => \"bind [key] (command): attach a command to a key\".to_owned(),\n                }\n            }),\n        )\n        .unwrap();\n\n        // \"unbindall\"\n        let bindings = self.bindings.clone();\n        cmds.insert_or_replace(\n            \"unbindall\",\n            Box::new(move |args| match args.len() {\n                0 => {\n                    let _ = bindings.replace(HashMap::new());\n                    String::new()\n                }\n                _ => \"unbindall: delete all keybindings\".to_owned(),\n            }),\n        )\n        .unwrap();\n\n        // \"impulse\"\n        let impulse = self.impulse.clone();\n        cmds.insert_or_replace(\n            \"impulse\",\n            Box::new(move |args| {\n                println!(\"args: {}\", args.len());\n                match args.len() {\n                    1 => match u8::from_str(args[0]) {\n                        Ok(i) => {\n                            impulse.set(i);\n                            String::new()\n                        }\n                        Err(_) => \"Impulse must be a number between 0 and 255\".to_owned(),\n                    },\n\n                    _ => \"usage: impulse [number]\".to_owned(),\n                }\n            }),\n        )\n        .unwrap();\n    }\n\n    // must be called every frame!\n    pub fn refresh(&mut self) {\n        self.clear_mouse();\n        self.clear_impulse();\n    }\n\n    fn clear_mouse(&mut self) {\n        self.handle_input(MouseWheel::Up, ElementState::Released);\n        self.handle_input(MouseWheel::Down, ElementState::Released);\n        self.mouse_delta = (0.0, 0.0);\n    }\n\n    fn clear_impulse(&mut self) {\n        self.impulse.set(0);\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_action_to_string() {\n        let act = Action::Forward;\n        assert_eq!(act.to_string(), \"forward\");\n    }\n\n    #[test]\n    fn test_bind_target_action_to_string() {\n        let target = BindTarget::Action {\n            trigger: ElementState::Pressed,\n            action: Action::Forward,\n        };\n\n        assert_eq!(target.to_string(), \"+forward\");\n    }\n}\n"
  },
  {
    "path": "src/client/input/menu.rs",
    "content": "// Copyright © 2019 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{cell::RefCell, rc::Rc};\n\nuse crate::{client::menu::Menu, common::console::Console};\n\nuse failure::Error;\nuse winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode as Key, WindowEvent};\n\npub struct MenuInput {\n    menu: Rc<RefCell<Menu>>,\n    console: Rc<RefCell<Console>>,\n}\n\nimpl MenuInput {\n    pub fn new(menu: Rc<RefCell<Menu>>, console: Rc<RefCell<Console>>) -> MenuInput {\n        MenuInput { menu, console }\n    }\n\n    pub fn handle_event<T>(&self, event: Event<T>) -> Result<(), Error> {\n        match event {\n            Event::WindowEvent { event, .. } => match event {\n                WindowEvent::ReceivedCharacter(_) => (),\n\n                WindowEvent::KeyboardInput {\n                    input:\n                        KeyboardInput {\n                            virtual_keycode: Some(key),\n                            state: ElementState::Pressed,\n                            ..\n                        },\n                    ..\n                } => match key {\n                    Key::Escape => {\n                        if self.menu.borrow().at_root() {\n                            self.console.borrow().stuff_text(\"togglemenu\\n\");\n                        } else {\n                            self.menu.borrow().back()?;\n                        }\n                    }\n\n                    Key::Up => self.menu.borrow().prev()?,\n                    Key::Down => self.menu.borrow().next()?,\n                    Key::Return => self.menu.borrow().activate()?,\n                    Key::Left => self.menu.borrow().left()?,\n                    Key::Right => self.menu.borrow().right()?,\n\n                    _ => (),\n                },\n\n                _ => (),\n            },\n\n            _ => (),\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/client/input/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\npub mod console;\npub mod game;\npub mod menu;\n\nuse std::{cell::RefCell, rc::Rc};\n\nuse crate::{\n    client::menu::Menu,\n    common::console::{CmdRegistry, Console},\n};\n\nuse failure::Error;\nuse winit::event::{Event, WindowEvent};\n\nuse self::{\n    console::ConsoleInput,\n    game::{BindInput, BindTarget, GameInput},\n    menu::MenuInput,\n};\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum InputFocus {\n    Game,\n    Console,\n    Menu,\n}\n\npub struct Input {\n    window_focused: bool,\n    focus: InputFocus,\n\n    game_input: GameInput,\n    console_input: ConsoleInput,\n    menu_input: MenuInput,\n}\n\nimpl Input {\n    pub fn new(\n        init_focus: InputFocus,\n        console: Rc<RefCell<Console>>,\n        menu: Rc<RefCell<Menu>>,\n    ) -> Input {\n        Input {\n            window_focused: true,\n            focus: init_focus,\n\n            game_input: GameInput::new(console.clone()),\n            console_input: ConsoleInput::new(console.clone()),\n            menu_input: MenuInput::new(menu.clone(), console.clone()),\n        }\n    }\n\n    pub fn handle_event<T>(&mut self, event: Event<T>) -> Result<(), Error> {\n        match event {\n            // we're polling for hardware events, so we have to check window focus ourselves\n            Event::WindowEvent {\n                event: WindowEvent::Focused(focused),\n                ..\n            } => self.window_focused = focused,\n\n            _ => {\n                if self.window_focused {\n                    match self.focus {\n                        InputFocus::Game => self.game_input.handle_event(event),\n                        InputFocus::Console => self.console_input.handle_event(event)?,\n                        InputFocus::Menu => self.menu_input.handle_event(event)?,\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn focus(&self) -> InputFocus {\n        self.focus\n    }\n\n    pub fn set_focus(&mut self, new_focus: InputFocus) {\n        self.focus = new_focus;\n    }\n\n    /// Bind a `BindInput` to a `BindTarget`.\n    pub fn bind<I, T>(&mut self, input: I, target: T) -> Option<BindTarget>\n    where\n        I: Into<BindInput>,\n        T: Into<BindTarget>,\n    {\n        self.game_input.bind(input, target)\n    }\n\n    pub fn bind_defaults(&mut self) {\n        self.game_input.bind_defaults();\n    }\n\n    pub fn game_input(&self) -> Option<&GameInput> {\n        if let InputFocus::Game = self.focus {\n            Some(&self.game_input)\n        } else {\n            None\n        }\n    }\n\n    pub fn game_input_mut(&mut self) -> Option<&mut GameInput> {\n        if let InputFocus::Game = self.focus {\n            Some(&mut self.game_input)\n        } else {\n            None\n        }\n    }\n\n    pub fn register_cmds(&self, cmds: &mut CmdRegistry) {\n        self.game_input.register_cmds(cmds);\n    }\n}\n"
  },
  {
    "path": "src/client/menu/item.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::cell::{Cell, RefCell};\n\nuse crate::client::menu::Menu;\n\nuse failure::Error;\n\npub enum Item {\n    Submenu(Menu),\n    Action(Box<dyn Fn()>),\n    Toggle(Toggle),\n    Enum(Enum),\n    Slider(Slider),\n    TextField(TextField),\n}\n\npub struct Toggle {\n    state: Cell<bool>,\n    on_toggle: Box<dyn Fn(bool)>,\n}\n\nimpl Toggle {\n    pub fn new(init: bool, on_toggle: Box<dyn Fn(bool)>) -> Toggle {\n        let t = Toggle {\n            state: Cell::new(init),\n            on_toggle,\n        };\n\n        // initialize with default\n        (t.on_toggle)(init);\n\n        t\n    }\n\n    pub fn set_false(&self) {\n        self.state.set(false);\n        (self.on_toggle)(self.state.get());\n    }\n\n    pub fn set_true(&self) {\n        self.state.set(true);\n        (self.on_toggle)(self.state.get());\n    }\n\n    pub fn toggle(&self) {\n        self.state.set(!self.state.get());\n        (self.on_toggle)(self.state.get());\n    }\n\n    pub fn get(&self) -> bool {\n        self.state.get()\n    }\n}\n\n// TODO: add wrapping configuration to enums\n// e.g. resolution enum wraps, texture filtering does not\npub struct Enum {\n    selected: Cell<usize>,\n    items: Vec<EnumItem>,\n}\n\nimpl Enum {\n    pub fn new(init: usize, items: Vec<EnumItem>) -> Result<Enum, Error> {\n        ensure!(items.len() > 0, \"Enum element must have at least one item\");\n        ensure!(init < items.len(), \"Invalid initial item ID\");\n\n        let e = Enum {\n            selected: Cell::new(init),\n            items,\n        };\n\n        // initialize with the default choice\n        (e.items[e.selected.get()].on_select)();\n\n        Ok(e)\n    }\n\n    pub fn selected_name(&self) -> &str {\n        self.items[self.selected.get()].name.as_str()\n    }\n\n    pub fn select_next(&self) {\n        let selected = match self.selected.get() + 1 {\n            s if s >= self.items.len() => 0,\n            s => s,\n        };\n\n        self.selected.set(selected);\n        (self.items[selected].on_select)();\n    }\n\n    pub fn select_prev(&self) {\n        let selected = match self.selected.get() {\n            0 => self.items.len() - 1,\n            s => s - 1,\n        };\n\n        self.selected.set(selected);\n        (self.items[selected].on_select)();\n    }\n}\n\npub struct EnumItem {\n    name: String,\n    on_select: Box<dyn Fn()>,\n}\n\nimpl EnumItem {\n    pub fn new<S>(name: S, on_select: Box<dyn Fn()>) -> Result<EnumItem, Error>\n    where\n        S: AsRef<str>,\n    {\n        Ok(EnumItem {\n            name: name.as_ref().to_string(),\n            on_select,\n        })\n    }\n}\n\npub struct Slider {\n    min: f32,\n    _max: f32,\n    increment: f32,\n    steps: usize,\n\n    selected: Cell<usize>,\n    on_select: Box<dyn Fn(f32)>,\n}\n\nimpl Slider {\n    pub fn new(\n        min: f32,\n        max: f32,\n        steps: usize,\n        init: usize,\n        on_select: Box<dyn Fn(f32)>,\n    ) -> Result<Slider, Error> {\n        ensure!(steps > 1, \"Slider must have at least 2 steps\");\n        ensure!(init < steps, \"Invalid initial setting\");\n        ensure!(\n            min < max,\n            \"Minimum setting must be less than maximum setting\"\n        );\n\n        Ok(Slider {\n            min,\n            _max: max,\n            increment: (max - min) / (steps - 1) as f32,\n            steps,\n            selected: Cell::new(init),\n            on_select,\n        })\n    }\n\n    pub fn increase(&self) {\n        let old = self.selected.get();\n\n        if old != self.steps - 1 {\n            self.selected.set(old + 1);\n        }\n\n        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);\n    }\n\n    pub fn decrease(&self) {\n        let old = self.selected.get();\n\n        if old != 0 {\n            self.selected.set(old - 1);\n        }\n\n        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);\n    }\n\n    pub fn position(&self) -> f32 {\n        self.selected.get() as f32 / self.steps as f32\n    }\n}\n\npub struct TextField {\n    chars: RefCell<Vec<char>>,\n    max_len: Option<usize>,\n    on_update: Box<dyn Fn(&str)>,\n    cursor: Cell<usize>,\n}\n\nimpl TextField {\n    pub fn new<S>(\n        default: Option<S>,\n        max_len: Option<usize>,\n        on_update: Box<dyn Fn(&str)>,\n    ) -> Result<TextField, Error>\n    where\n        S: AsRef<str>,\n    {\n        let chars = RefCell::new(match default {\n            Some(d) => d.as_ref().chars().collect(),\n            None => Vec::new(),\n        });\n\n        let cursor = Cell::new(chars.borrow().len());\n\n        Ok(TextField {\n            chars,\n            max_len,\n            on_update,\n            cursor,\n        })\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    pub fn text(&self) -> String {\n        self.chars.borrow().iter().collect()\n    }\n\n    pub fn len(&self) -> usize {\n        self.chars.borrow().len()\n    }\n\n    pub fn set_cursor(&self, cursor: usize) -> Result<(), Error> {\n        ensure!(cursor <= self.len(), \"Index out of range\");\n\n        self.cursor.set(cursor);\n\n        Ok(())\n    }\n\n    pub fn home(&self) {\n        self.cursor.set(0);\n    }\n\n    pub fn end(&self) {\n        self.cursor.set(self.len());\n    }\n\n    pub fn cursor_right(&self) {\n        let curs = self.cursor.get();\n        if curs < self.len() {\n            self.cursor.set(curs + 1);\n        }\n    }\n\n    pub fn cursor_left(&self) {\n        let curs = self.cursor.get();\n        if curs > 1 {\n            self.cursor.set(curs - 1);\n        }\n    }\n\n    pub fn insert(&self, c: char) {\n        if let Some(l) = self.max_len {\n            if self.len() == l {\n                return;\n            }\n        }\n\n        self.chars.borrow_mut().insert(self.cursor.get(), c);\n        (self.on_update)(&self.text());\n    }\n\n    pub fn backspace(&self) {\n        if self.cursor.get() > 1 {\n            self.chars.borrow_mut().remove(self.cursor.get() - 1);\n            (self.on_update)(&self.text());\n        }\n    }\n\n    pub fn delete(&self) {\n        if self.cursor.get() < self.len() {\n            self.chars.borrow_mut().remove(self.cursor.get());\n            (self.on_update)(&self.text());\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use std::{cell::RefCell, rc::Rc};\n\n    #[test]\n    fn test_toggle() {\n        let s = Rc::new(RefCell::new(\"false\".to_string()));\n\n        let s2 = s.clone();\n        let item = Toggle::new(\n            false,\n            Box::new(move |state| {\n                s2.replace(format!(\"{}\", state));\n            }),\n        );\n        item.toggle();\n\n        assert_eq!(*s.borrow(), \"true\");\n    }\n\n    #[test]\n    fn test_enum() {\n        let target = Rc::new(RefCell::new(\"null\".to_string()));\n\n        let enum_items = (0..3i32)\n            .into_iter()\n            .map(|i: i32| {\n                let target_handle = target.clone();\n                EnumItem::new(\n                    format!(\"option_{}\", i),\n                    Box::new(move || {\n                        target_handle.replace(format!(\"option_{}\", i));\n                    }),\n                )\n                .unwrap()\n            })\n            .collect();\n\n        let e = Enum::new(0, enum_items).unwrap();\n        assert_eq!(*target.borrow(), \"option_0\");\n\n        // wrap under\n        e.select_prev();\n        assert_eq!(*target.borrow(), \"option_2\");\n\n        e.select_next();\n        e.select_next();\n        e.select_next();\n        assert_eq!(*target.borrow(), \"option_2\");\n\n        // wrap over\n        e.select_next();\n        assert_eq!(*target.borrow(), \"option_0\");\n    }\n\n    #[test]\n    fn test_slider() {\n        let f = Rc::new(Cell::new(0.0f32));\n\n        let f2 = f.clone();\n        let item = Slider::new(\n            0.0,\n            10.0,\n            11,\n            0,\n            Box::new(move |f| {\n                f2.set(f);\n            }),\n        )\n        .unwrap();\n\n        // don't underflow\n        item.decrease();\n        assert_eq!(f.get(), 0.0);\n\n        for i in 0..10 {\n            item.increase();\n            assert_eq!(f.get(), i as f32 + 1.0);\n        }\n\n        // don't overflow\n        item.increase();\n        assert_eq!(f.get(), 10.0);\n    }\n\n    #[test]\n    fn test_textfield() {\n        let MAX_LEN = 10;\n        let s = Rc::new(RefCell::new(\"before\".to_owned()));\n        let s2 = s.clone();\n\n        let mut tf = TextField::new(\n            Some(\"default\"),\n            Some(MAX_LEN),\n            Box::new(move |x| {\n                s2.replace(x.to_string());\n            }),\n        )\n        .unwrap();\n\n        tf.cursor_left();\n        tf.backspace();\n        tf.backspace();\n        tf.home();\n        tf.delete();\n        tf.delete();\n        tf.delete();\n        tf.cursor_right();\n        tf.insert('f');\n        tf.end();\n        tf.insert('e');\n        tf.insert('r');\n\n        assert_eq!(tf.text(), *s.borrow());\n\n        for _ in 0..2 * MAX_LEN {\n            tf.insert('x');\n        }\n\n        assert_eq!(tf.len(), MAX_LEN);\n    }\n}\n"
  },
  {
    "path": "src/client/menu/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nmod item;\n\nuse std::cell::Cell;\n\nuse failure::Error;\n\npub use self::item::{Enum, EnumItem, Item, Slider, TextField, Toggle};\n\n#[derive(Clone, Copy, Debug)]\npub enum MenuState {\n    /// Menu is inactive.\n    Inactive,\n\n    /// Menu is active. `index` indicates the currently selected element.\n    Active { index: usize },\n\n    /// A submenu of this menu is active. `index` indicates the active submenu.\n    InSubMenu { index: usize },\n}\n\n/// Specifies how the menu body should be rendered.\npub enum MenuBodyView {\n    /// The menu body is rendered using a predefined bitmap.\n    Predefined {\n        /// The path to the bitmap.\n        path: String,\n    },\n    /// The menu body is rendered dynamically based on its contents.\n    Dynamic,\n}\n\npub struct MenuView {\n    pub draw_plaque: bool,\n    pub title_path: String,\n    pub body: MenuBodyView,\n}\n\nimpl MenuView {\n    /// Returns true if the Quake plaque should be drawn to the left of the menu.\n    pub fn draw_plaque(&self) -> bool {\n        self.draw_plaque\n    }\n\n    /// Returns the path to the menu title bitmap.\n    pub fn title_path(&self) -> &str {\n        &self.title_path\n    }\n\n    /// Returns a MenuBodyView which specifies how to render the menu body.\n    pub fn body(&self) -> &MenuBodyView {\n        &self.body\n    }\n}\n\npub struct Menu {\n    items: Vec<NamedMenuItem>,\n    state: Cell<MenuState>,\n    view: MenuView,\n}\n\nimpl Menu {\n    /// Returns a reference to the active submenu of this menu and its parent.\n    fn active_submenu_and_parent(&self) -> Result<(&Menu, Option<&Menu>), Error> {\n        let mut m = self;\n        let mut m_parent = None;\n\n        while let MenuState::InSubMenu { index } = m.state.get() {\n            match m.items[index].item {\n                Item::Submenu(ref s) => {\n                    m_parent = Some(m);\n                    m = s;\n                }\n                _ => bail!(\"Menu state points to invalid submenu\"),\n            }\n        }\n\n        Ok((m, m_parent))\n    }\n\n    /// Return a reference to the active submenu of this menu\n    pub fn active_submenu(&self) -> Result<&Menu, Error> {\n        let (m, _) = self.active_submenu_and_parent()?;\n        Ok(m)\n    }\n\n    /// Return a reference to the parent of the active submenu of this menu.\n    ///\n    /// If this is the root menu, returns None.\n    fn active_submenu_parent(&self) -> Result<Option<&Menu>, Error> {\n        let (_, m_parent) = self.active_submenu_and_parent()?;\n        Ok(m_parent)\n    }\n\n    /// Select the next element of this Menu.\n    pub fn next(&self) -> Result<(), Error> {\n        let m = self.active_submenu()?;\n\n        let s = m.state.get().clone();\n        if let MenuState::Active { index } = s {\n            m.state.replace(MenuState::Active {\n                index: (index + 1) % m.items.len(),\n            });\n        } else {\n            bail!(\"Selected menu is inactive (invariant violation)\");\n        }\n\n        Ok(())\n    }\n\n    /// Select the previous element of this Menu.\n    pub fn prev(&self) -> Result<(), Error> {\n        let m = self.active_submenu()?;\n\n        let s = m.state.get().clone();\n        if let MenuState::Active { index } = s {\n            m.state.replace(MenuState::Active {\n                index: (index - 1) % m.items.len(),\n            });\n        } else {\n            bail!(\"Selected menu is inactive (invariant violation)\");\n        }\n\n        Ok(())\n    }\n\n    /// Return a reference to the currently selected menu item.\n    pub fn selected(&self) -> Result<&Item, Error> {\n        let m = self.active_submenu()?;\n\n        if let MenuState::Active { index } = m.state.get() {\n            return Ok(&m.items[index].item);\n        } else {\n            bail!(\"Active menu in invalid state (invariant violation)\")\n        }\n    }\n\n    /// Activate the currently selected menu item.\n    ///\n    /// If this item is a `Menu`, sets the active (sub)menu's state to\n    /// `MenuState::InSubMenu` and the selected submenu's state to\n    /// `MenuState::Active`.\n    ///\n    /// If this item is an `Action`, executes the function contained in the\n    /// `Action`.\n    ///\n    /// Otherwise, this has no effect.\n    pub fn activate(&self) -> Result<(), Error> {\n        let m = self.active_submenu()?;\n\n        if let MenuState::Active { index } = m.state.get() {\n            match m.items[index].item {\n                Item::Submenu(ref submenu) => {\n                    m.state.replace(MenuState::InSubMenu { index });\n                    submenu.state.replace(MenuState::Active { index: 0 });\n                }\n\n                Item::Action(ref action) => (action)(),\n\n                _ => (),\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn left(&self) -> Result<(), Error> {\n        let m = self.active_submenu()?;\n\n        if let MenuState::Active { index } = m.state.get() {\n            match m.items[index].item {\n                Item::Enum(ref e) => e.select_prev(),\n                Item::Slider(ref slider) => slider.decrease(),\n                Item::TextField(ref text) => text.cursor_left(),\n                Item::Toggle(ref toggle) => toggle.set_false(),\n                _ => (),\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn right(&self) -> Result<(), Error> {\n        let m = self.active_submenu()?;\n\n        if let MenuState::Active { index } = m.state.get() {\n            match m.items[index].item {\n                Item::Enum(ref e) => e.select_next(),\n                Item::Slider(ref slider) => slider.increase(),\n                Item::TextField(ref text) => text.cursor_right(),\n                Item::Toggle(ref toggle) => toggle.set_true(),\n                _ => (),\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Return `true` if the root menu is active, `false` otherwise.\n    pub fn at_root(&self) -> bool {\n        match self.state.get() {\n            MenuState::Active { .. } => true,\n            _ => false,\n        }\n    }\n\n    /// Deactivate the active menu and activate its parent\n    pub fn back(&self) -> Result<(), Error> {\n        if self.at_root() {\n            bail!(\"Cannot back out of root menu!\");\n        }\n\n        let (m, m_parent) = self.active_submenu_and_parent()?;\n        m.state.replace(MenuState::Inactive);\n\n        match m_parent {\n            Some(mp) => {\n                let s = mp.state.get().clone();\n                match s {\n                    MenuState::InSubMenu { index } => mp.state.replace(MenuState::Active { index }),\n                    _ => unreachable!(),\n                };\n            }\n\n            None => unreachable!(),\n        }\n\n        Ok(())\n    }\n\n    pub fn items(&self) -> &[NamedMenuItem] {\n        &self.items\n    }\n\n    pub fn state(&self) -> MenuState {\n        self.state.get()\n    }\n\n    pub fn view(&self) -> &MenuView {\n        &self.view\n    }\n}\n\npub struct MenuBuilder {\n    gfx_name: Option<String>,\n    items: Vec<NamedMenuItem>,\n}\n\nimpl MenuBuilder {\n    pub fn new() -> MenuBuilder {\n        MenuBuilder {\n            gfx_name: None,\n            items: Vec::new(),\n        }\n    }\n\n    pub fn build(self, view: MenuView) -> Menu {\n        // deactivate all child menus\n        for item in self.items.iter() {\n            if let Item::Submenu(ref m) = item.item {\n                m.state.replace(MenuState::Inactive);\n            }\n        }\n\n        Menu {\n            items: self.items,\n            state: Cell::new(MenuState::Active { index: 0 }),\n            view,\n        }\n    }\n\n    pub fn add_submenu<S>(mut self, name: S, submenu: Menu) -> MenuBuilder\n    where\n        S: AsRef<str>,\n    {\n        self.items\n            .push(NamedMenuItem::new(name, Item::Submenu(submenu)));\n        self\n    }\n\n    pub fn add_action<S>(mut self, name: S, action: Box<dyn Fn()>) -> MenuBuilder\n    where\n        S: AsRef<str>,\n    {\n        self.items\n            .push(NamedMenuItem::new(name, Item::Action(action)));\n        self\n    }\n\n    pub fn add_toggle<S>(mut self, name: S, init: bool, on_toggle: Box<dyn Fn(bool)>) -> MenuBuilder\n    where\n        S: AsRef<str>,\n    {\n        self.items.push(NamedMenuItem::new(\n            name,\n            Item::Toggle(Toggle::new(init, on_toggle)),\n        ));\n        self\n    }\n\n    pub fn add_enum<S, E>(mut self, name: S, items: E, init: usize) -> Result<MenuBuilder, Error>\n    where\n        S: AsRef<str>,\n        E: Into<Vec<EnumItem>>,\n    {\n        self.items.push(NamedMenuItem::new(\n            name,\n            Item::Enum(Enum::new(init, items.into())?),\n        ));\n        Ok(self)\n    }\n\n    pub fn add_slider<S>(\n        mut self,\n        name: S,\n        min: f32,\n        max: f32,\n        steps: usize,\n        init: usize,\n        on_select: Box<dyn Fn(f32)>,\n    ) -> Result<MenuBuilder, Error>\n    where\n        S: AsRef<str>,\n    {\n        self.items.push(NamedMenuItem::new(\n            name,\n            Item::Slider(Slider::new(min, max, steps, init, on_select)?),\n        ));\n        Ok(self)\n    }\n\n    pub fn add_text_field<S>(\n        mut self,\n        name: S,\n        default: Option<S>,\n        max_len: Option<usize>,\n        on_update: Box<dyn Fn(&str)>,\n    ) -> Result<MenuBuilder, Error>\n    where\n        S: AsRef<str>,\n    {\n        self.items.push(NamedMenuItem::new(\n            name,\n            Item::TextField(TextField::new(default, max_len, on_update)?),\n        ));\n        Ok(self)\n    }\n}\n\npub struct NamedMenuItem {\n    name: String,\n    item: Item,\n}\n\nimpl NamedMenuItem {\n    fn new<S>(name: S, item: Item) -> NamedMenuItem\n    where\n        S: AsRef<str>,\n    {\n        NamedMenuItem {\n            name: name.as_ref().to_string(),\n            item,\n        }\n    }\n\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    pub fn item(&self) -> &Item {\n        &self.item\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use std::{cell::Cell, rc::Rc};\n\n    fn view() -> MenuView {\n        MenuView {\n            draw_plaque: false,\n            title_path: \"path\".to_string(),\n            body: MenuBodyView::Dynamic,\n        }\n    }\n\n    fn is_inactive(state: &MenuState) -> bool {\n        match state {\n            MenuState::Inactive => true,\n            _ => false,\n        }\n    }\n\n    fn is_active(state: &MenuState) -> bool {\n        match state {\n            MenuState::Active { .. } => true,\n            _ => false,\n        }\n    }\n\n    fn is_insubmenu(state: &MenuState) -> bool {\n        match state {\n            MenuState::InSubMenu { .. } => true,\n            _ => false,\n        }\n    }\n\n    #[test]\n    fn test_menu_builder() {\n        let action_target = Rc::new(Cell::new(false));\n        let action_target_handle = action_target.clone();\n\n        let _m = MenuBuilder::new()\n            .add_action(\"action\", Box::new(move || action_target_handle.set(true)))\n            .build(view());\n\n        // TODO\n    }\n\n    #[test]\n    fn test_menu_active_submenu() {\n        let menu = MenuBuilder::new()\n            .add_submenu(\n                \"menu_1\",\n                MenuBuilder::new()\n                    .add_action(\"action_1\", Box::new(|| ()))\n                    .build(view()),\n            )\n            .add_submenu(\n                \"menu_2\",\n                MenuBuilder::new()\n                    .add_action(\"action_2\", Box::new(|| ()))\n                    .build(view()),\n            )\n            .build(view());\n\n        let m = &menu;\n        let m1 = match m.items[0].item {\n            Item::Submenu(ref m1i) => m1i,\n            _ => unreachable!(),\n        };\n        let m2 = match m.items[1].item {\n            Item::Submenu(ref m2i) => m2i,\n            _ => unreachable!(),\n        };\n\n        assert!(is_active(&m.state.get()));\n        assert!(is_inactive(&m1.state.get()));\n        assert!(is_inactive(&m2.state.get()));\n\n        // enter m1\n        m.activate().unwrap();\n        assert!(is_insubmenu(&m.state.get()));\n        assert!(is_active(&m1.state.get()));\n        assert!(is_inactive(&m2.state.get()));\n\n        // exit m1\n        m.back().unwrap();\n        assert!(is_active(&m.state.get()));\n        assert!(is_inactive(&m1.state.get()));\n        assert!(is_inactive(&m2.state.get()));\n\n        // enter m2\n        m.next().unwrap();\n        m.activate().unwrap();\n        assert!(is_insubmenu(&m.state.get()));\n        assert!(is_inactive(&m1.state.get()));\n        assert!(is_active(&m2.state.get()));\n    }\n}\n"
  },
  {
    "path": "src/client/mod.rs",
    "content": "// Copyright © 2020 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nmod cvars;\npub mod demo;\npub mod entity;\npub mod input;\npub mod menu;\npub mod render;\npub mod sound;\npub mod state;\npub mod trace;\npub mod view;\n\npub use self::cvars::register_cvars;\n\nuse std::{\n    cell::RefCell,\n    collections::{HashMap, VecDeque},\n    io::BufReader,\n    net::ToSocketAddrs,\n    rc::Rc,\n};\n\nuse crate::{\n    client::{\n        demo::{DemoServer, DemoServerError},\n        entity::{ClientEntity, MAX_STATIC_ENTITIES},\n        input::{game::GameInput, Input},\n        sound::{MusicPlayer, StaticSound},\n        state::{ClientState, PlayerInfo},\n        trace::{TraceEntity, TraceFrame},\n        view::{IdleVars, KickVars, MouseVars, RollVars},\n    },\n    common::{\n        console::{CmdRegistry, Console, ConsoleError, CvarRegistry},\n        engine,\n        model::ModelError,\n        net::{\n            self,\n            connect::{ConnectSocket, Request, Response, CONNECT_PROTOCOL_VERSION},\n            BlockingMode, ClientCmd, ClientStat, ColorShift, EntityEffects, EntityState, GameType,\n            NetError, PlayerColor, QSocket, ServerCmd, SignOnStage,\n        },\n        vfs::{Vfs, VfsError},\n    },\n};\n\nuse cgmath::Deg;\nuse chrono::Duration;\nuse input::InputFocus;\nuse menu::Menu;\nuse render::{ClientRenderer, GraphicsState, WorldRenderer};\nuse rodio::{OutputStream, OutputStreamHandle};\nuse sound::SoundError;\nuse thiserror::Error;\nuse view::BobVars;\n\n// connections are tried 3 times, see\n// https://github.com/id-Software/Quake/blob/master/WinQuake/net_dgrm.c#L1248\nconst MAX_CONNECT_ATTEMPTS: usize = 3;\nconst MAX_STATS: usize = 32;\n\nconst DEFAULT_SOUND_PACKET_VOLUME: u8 = 255;\nconst DEFAULT_SOUND_PACKET_ATTENUATION: f32 = 1.0;\n\nconst CONSOLE_DIVIDER: &'static str = \"\\\n\\n\\n\\\n\\x1D\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\\n\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\\n\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\\n\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1F\\\n\\n\\n\";\n\n#[derive(Error, Debug)]\npub enum ClientError {\n    #[error(\"Connection rejected: {0}\")]\n    ConnectionRejected(String),\n    #[error(\"Couldn't read cvar value: {0}\")]\n    Cvar(ConsoleError),\n    #[error(\"Server sent an invalid port number ({0})\")]\n    InvalidConnectPort(i32),\n    #[error(\"Server sent an invalid connect response\")]\n    InvalidConnectResponse,\n    #[error(\"Invalid server address\")]\n    InvalidServerAddress,\n    #[error(\"No response from server\")]\n    NoResponse,\n    #[error(\"Unrecognized protocol: {0}\")]\n    UnrecognizedProtocol(i32),\n    #[error(\"Client is not connected\")]\n    NotConnected,\n    #[error(\"Client has already signed on\")]\n    AlreadySignedOn,\n    #[error(\"No client with ID {0}\")]\n    NoSuchClient(usize),\n    #[error(\"No player with ID {0}\")]\n    NoSuchPlayer(usize),\n    #[error(\"No entity with ID {0}\")]\n    NoSuchEntity(usize),\n    #[error(\"Null entity access\")]\n    NullEntity,\n    #[error(\"Entity already exists: {0}\")]\n    EntityExists(usize),\n    #[error(\"Invalid view entity: {0}\")]\n    InvalidViewEntity(usize),\n    #[error(\"Too many static entities\")]\n    TooManyStaticEntities,\n    #[error(\"No such lightmap animation: {0}\")]\n    NoSuchLightmapAnimation(usize),\n    // TODO: wrap PlayError\n    #[error(\"Failed to open audio output stream\")]\n    OutputStream,\n    #[error(\"Demo server error: {0}\")]\n    DemoServer(#[from] DemoServerError),\n    #[error(\"Model error: {0}\")]\n    Model(#[from] ModelError),\n    #[error(\"Network error: {0}\")]\n    Network(#[from] NetError),\n    #[error(\"Failed to load sound: {0}\")]\n    Sound(#[from] SoundError),\n    #[error(\"Virtual filesystem error: {0}\")]\n    Vfs(#[from] VfsError),\n}\n\npub struct MoveVars {\n    cl_anglespeedkey: f32,\n    cl_pitchspeed: f32,\n    cl_yawspeed: f32,\n    cl_sidespeed: f32,\n    cl_upspeed: f32,\n    cl_forwardspeed: f32,\n    cl_backspeed: f32,\n    cl_movespeedkey: f32,\n}\n\n#[derive(Debug, FromPrimitive)]\nenum ColorShiftCode {\n    Contents = 0,\n    Damage = 1,\n    Bonus = 2,\n    Powerup = 3,\n}\n\nstruct ServerInfo {\n    _max_clients: u8,\n    _game_type: GameType,\n}\n\n#[derive(Clone, Debug)]\npub enum IntermissionKind {\n    Intermission,\n    Finale { text: String },\n    Cutscene { text: String },\n}\n\n/// Indicates to the client what should be done with the current connection.\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\nenum ConnectionStatus {\n    /// Maintain the connection.\n    Maintain,\n\n    /// Disconnect from the server or demo server.\n    Disconnect,\n\n    /// Play the next demo in the demo queue.\n    NextDemo,\n}\n\n/// Indicates the state of an active connection.\nenum ConnectionState {\n    /// The client is in the sign-on process.\n    SignOn(SignOnStage),\n\n    /// The client is fully connected.\n    Connected(WorldRenderer),\n}\n\n/// Possible targets that a client can be connected to.\nenum ConnectionKind {\n    /// A regular Quake server.\n    Server {\n        /// The [`QSocket`](crate::net::QSocket) used to communicate with the server.\n        qsock: QSocket,\n\n        /// The client's packet composition buffer.\n        compose: Vec<u8>,\n    },\n\n    /// A demo server.\n    Demo(DemoServer),\n}\n\n/// A connection to a game server of some kind.\n///\n/// The exact nature of the connected server is specified by [`ConnectionKind`].\npub struct Connection {\n    state: ClientState,\n    conn_state: ConnectionState,\n    kind: ConnectionKind,\n}\n\nimpl Connection {\n    fn handle_signon(\n        &mut self,\n        new_stage: SignOnStage,\n        gfx_state: &GraphicsState,\n    ) -> Result<(), ClientError> {\n        use SignOnStage::*;\n\n        let new_conn_state = match self.conn_state {\n            // TODO: validate stage transition\n            ConnectionState::SignOn(ref mut _stage) => {\n                if let ConnectionKind::Server {\n                    ref mut compose, ..\n                } = self.kind\n                {\n                    match new_stage {\n                        Not => (), // TODO this is an error (invalid value)\n                        Prespawn => {\n                            ClientCmd::StringCmd {\n                                cmd: String::from(\"prespawn\"),\n                            }\n                            .serialize(compose)?;\n                        }\n                        ClientInfo => {\n                            // TODO: fill in client info here\n                            ClientCmd::StringCmd {\n                                cmd: format!(\"name \\\"{}\\\"\\n\", \"UNNAMED\"),\n                            }\n                            .serialize(compose)?;\n                            ClientCmd::StringCmd {\n                                cmd: format!(\"color {} {}\", 0, 0),\n                            }\n                            .serialize(compose)?;\n                            // TODO: need default spawn parameters?\n                            ClientCmd::StringCmd {\n                                cmd: format!(\"spawn {}\", \"\"),\n                            }\n                            .serialize(compose)?;\n                        }\n                        SignOnStage::Begin => {\n                            ClientCmd::StringCmd {\n                                cmd: String::from(\"begin\"),\n                            }\n                            .serialize(compose)?;\n                        }\n                        SignOnStage::Done => {\n                            debug!(\"SignOn complete\");\n                            // TODO: end load screen\n                            self.state.start_time = self.state.time;\n                        }\n                    }\n                }\n\n                match new_stage {\n                    // TODO proper error\n                    Not => panic!(\"SignOnStage::Not in handle_signon\"),\n                    // still signing on, advance to the new stage\n                    Prespawn | ClientInfo | Begin => ConnectionState::SignOn(new_stage),\n\n                    // finished signing on, build world renderer\n                    Done => ConnectionState::Connected(WorldRenderer::new(\n                        gfx_state,\n                        self.state.models(),\n                        1,\n                    )),\n                }\n            }\n\n            // ignore spurious sign-on messages\n            ConnectionState::Connected { .. } => return Ok(()),\n        };\n\n        self.conn_state = new_conn_state;\n\n        Ok(())\n    }\n\n    fn parse_server_msg(\n        &mut self,\n        vfs: &Vfs,\n        gfx_state: &GraphicsState,\n        cmds: &mut CmdRegistry,\n        console: &mut Console,\n        music_player: &mut MusicPlayer,\n        kick_vars: KickVars,\n    ) -> Result<ConnectionStatus, ClientError> {\n        use ConnectionStatus::*;\n\n        let (msg, demo_view_angles, track_override) = match self.kind {\n            ConnectionKind::Server { ref mut qsock, .. } => {\n                let msg = qsock.recv_msg(match self.conn_state {\n                    // if we're in the game, don't block waiting for messages\n                    ConnectionState::Connected(_) => BlockingMode::NonBlocking,\n\n                    // otherwise, give the server some time to respond\n                    // TODO: might make sense to make this a future or something\n                    ConnectionState::SignOn(_) => BlockingMode::Timeout(Duration::seconds(5)),\n                })?;\n\n                (msg, None, None)\n            }\n\n            ConnectionKind::Demo(ref mut demo_srv) => {\n                // only get the next update once we've made it all the way to\n                // the previous one\n                if self.state.time >= self.state.msg_times[0] {\n                    let msg_view = match demo_srv.next() {\n                        Some(v) => v,\n                        None => {\n                            // if there are no commands left in the demo, play\n                            // the next demo if there is one\n                            return Ok(NextDemo);\n                        }\n                    };\n\n                    let mut view_angles = msg_view.view_angles();\n                    // invert entity angles to get the camera direction right.\n                    // yaw is already inverted.\n                    view_angles.z = -view_angles.z;\n\n                    // TODO: we shouldn't have to copy the message here\n                    (\n                        msg_view.message().to_owned(),\n                        Some(view_angles),\n                        demo_srv.track_override(),\n                    )\n                } else {\n                    (Vec::new(), None, demo_srv.track_override())\n                }\n            }\n        };\n\n        // no data available at this time\n        if msg.is_empty() {\n            return Ok(Maintain);\n        }\n\n        let mut reader = BufReader::new(msg.as_slice());\n\n        while let Some(cmd) = ServerCmd::deserialize(&mut reader)? {\n            match cmd {\n                // TODO: have an error for this instead of panicking\n                // once all other commands have placeholder handlers, just error\n                // in the wildcard branch\n                ServerCmd::Bad => panic!(\"Invalid command from server\"),\n\n                ServerCmd::NoOp => (),\n\n                ServerCmd::CdTrack { track, .. } => {\n                    music_player.play_track(match track_override {\n                        Some(t) => t as usize,\n                        None => track as usize,\n                    })?;\n                }\n\n                ServerCmd::CenterPrint { text } => {\n                    // TODO: print to center of screen\n                    warn!(\"Center print not yet implemented!\");\n                    println!(\"{}\", text);\n                }\n\n                ServerCmd::PlayerData(player_data) => self.state.update_player(player_data),\n\n                ServerCmd::Cutscene { text } => {\n                    self.state.intermission = Some(IntermissionKind::Cutscene { text });\n                    self.state.completion_time = Some(self.state.time);\n                }\n\n                ServerCmd::Damage {\n                    armor,\n                    blood,\n                    source,\n                } => self.state.handle_damage(armor, blood, source, kick_vars),\n\n                ServerCmd::Disconnect => {\n                    return Ok(match self.kind {\n                        ConnectionKind::Demo(_) => NextDemo,\n                        ConnectionKind::Server { .. } => Disconnect,\n                    })\n                }\n\n                ServerCmd::FastUpdate(ent_update) => {\n                    // first update signals the last sign-on stage\n                    self.handle_signon(SignOnStage::Done, gfx_state)?;\n\n                    let ent_id = ent_update.ent_id as usize;\n                    self.state.update_entity(ent_id, ent_update)?;\n\n                    // patch view angles in demos\n                    if let Some(angles) = demo_view_angles {\n                        if ent_id == self.state.view_entity_id() {\n                            self.state.update_view_angles(angles);\n                        }\n                    }\n                }\n\n                ServerCmd::Finale { text } => {\n                    self.state.intermission = Some(IntermissionKind::Finale { text });\n                    self.state.completion_time = Some(self.state.time);\n                }\n\n                ServerCmd::FoundSecret => self.state.stats[ClientStat::FoundSecrets as usize] += 1,\n                ServerCmd::Intermission => {\n                    self.state.intermission = Some(IntermissionKind::Intermission);\n                    self.state.completion_time = Some(self.state.time);\n                }\n                ServerCmd::KilledMonster => {\n                    self.state.stats[ClientStat::KilledMonsters as usize] += 1\n                }\n\n                ServerCmd::LightStyle { id, value } => {\n                    trace!(\"Inserting light style {} with value {}\", id, &value);\n                    let _ = self.state.light_styles.insert(id, value);\n                }\n\n                ServerCmd::Particle {\n                    origin,\n                    direction,\n                    count,\n                    color,\n                } => {\n                    match count {\n                        // if count is 255, this is an explosion\n                        255 => self\n                            .state\n                            .particles\n                            .create_explosion(self.state.time, origin),\n\n                        // otherwise it's an impact\n                        _ => self.state.particles.create_projectile_impact(\n                            self.state.time,\n                            origin,\n                            direction,\n                            color,\n                            count as usize,\n                        ),\n                    }\n                }\n\n                ServerCmd::Print { text } => console.print_alert(&text),\n\n                ServerCmd::ServerInfo {\n                    protocol_version,\n                    max_clients,\n                    game_type,\n                    message,\n                    model_precache,\n                    sound_precache,\n                } => {\n                    // check protocol version\n                    if protocol_version != net::PROTOCOL_VERSION as i32 {\n                        Err(ClientError::UnrecognizedProtocol(protocol_version))?;\n                    }\n\n                    console.println(CONSOLE_DIVIDER);\n                    console.println(message);\n                    console.println(CONSOLE_DIVIDER);\n\n                    let _server_info = ServerInfo {\n                        _max_clients: max_clients,\n                        _game_type: game_type,\n                    };\n\n                    self.state = ClientState::from_server_info(\n                        vfs,\n                        self.state.mixer.stream(),\n                        max_clients,\n                        model_precache,\n                        sound_precache,\n                    )?;\n\n                    let bonus_cshift =\n                        self.state.color_shifts[ColorShiftCode::Bonus as usize].clone();\n                    cmds.insert_or_replace(\n                        \"bf\",\n                        Box::new(move |_| {\n                            bonus_cshift.replace(ColorShift {\n                                dest_color: [215, 186, 69],\n                                percent: 50,\n                            });\n                            String::new()\n                        }),\n                    )\n                    .unwrap();\n                }\n\n                ServerCmd::SetAngle { angles } => self.state.set_view_angles(angles),\n\n                ServerCmd::SetView { ent_id } => {\n                    if ent_id <= 0 {\n                        Err(ClientError::InvalidViewEntity(ent_id as usize))?;\n                    }\n\n                    self.state.set_view_entity(ent_id as usize)?;\n                }\n\n                ServerCmd::SignOnStage { stage } => self.handle_signon(stage, gfx_state)?,\n\n                ServerCmd::Sound {\n                    volume,\n                    attenuation,\n                    entity_id,\n                    channel,\n                    sound_id,\n                    position,\n                } => {\n                    trace!(\n                        \"starting sound with id {} on entity {} channel {}\",\n                        sound_id,\n                        entity_id,\n                        channel\n                    );\n\n                    if entity_id as usize >= self.state.entities.len() {\n                        warn!(\n                            \"server tried to start sound on nonexistent entity {}\",\n                            entity_id\n                        );\n                        break;\n                    }\n\n                    let volume = volume.unwrap_or(DEFAULT_SOUND_PACKET_VOLUME);\n                    let attenuation = attenuation.unwrap_or(DEFAULT_SOUND_PACKET_ATTENUATION);\n                    // TODO: apply volume, attenuation, spatialization\n                    self.state.mixer.start_sound(\n                        self.state.sounds[sound_id as usize].clone(),\n                        self.state.msg_times[0],\n                        Some(entity_id as usize),\n                        channel,\n                        volume as f32 / 255.0,\n                        attenuation,\n                        position,\n                        &self.state.listener,\n                    );\n                }\n\n                ServerCmd::SpawnBaseline {\n                    ent_id,\n                    model_id,\n                    frame_id,\n                    colormap,\n                    skin_id,\n                    origin,\n                    angles,\n                } => {\n                    self.state.spawn_entities(\n                        ent_id as usize,\n                        EntityState {\n                            model_id: model_id as usize,\n                            frame_id: frame_id as usize,\n                            colormap,\n                            skin_id: skin_id as usize,\n                            origin,\n                            angles,\n                            effects: EntityEffects::empty(),\n                        },\n                    )?;\n                }\n\n                ServerCmd::SpawnStatic {\n                    model_id,\n                    frame_id,\n                    colormap,\n                    skin_id,\n                    origin,\n                    angles,\n                } => {\n                    if self.state.static_entities.len() >= MAX_STATIC_ENTITIES {\n                        Err(ClientError::TooManyStaticEntities)?;\n                    }\n                    self.state\n                        .static_entities\n                        .push(ClientEntity::from_baseline(EntityState {\n                            origin,\n                            angles,\n                            model_id: model_id as usize,\n                            frame_id: frame_id as usize,\n                            colormap,\n                            skin_id: skin_id as usize,\n                            effects: EntityEffects::empty(),\n                        }));\n                }\n\n                ServerCmd::SpawnStaticSound {\n                    origin,\n                    sound_id,\n                    volume,\n                    attenuation,\n                } => {\n                    self.state.static_sounds.push(StaticSound::new(\n                        &self.state.mixer.stream(),\n                        origin,\n                        self.state.sounds[sound_id as usize].clone(),\n                        volume as f32 / 255.0,\n                        attenuation as f32 / 64.0,\n                        &self.state.listener,\n                    ));\n                }\n\n                ServerCmd::TempEntity { temp_entity } => self.state.spawn_temp_entity(&temp_entity),\n\n                ServerCmd::StuffText { text } => console.stuff_text(text),\n\n                ServerCmd::Time { time } => {\n                    self.state.msg_times[1] = self.state.msg_times[0];\n                    self.state.msg_times[0] = engine::duration_from_f32(time);\n                }\n\n                ServerCmd::UpdateColors {\n                    player_id,\n                    new_colors,\n                } => {\n                    let player_id = player_id as usize;\n                    self.state.check_player_id(player_id)?;\n\n                    match self.state.player_info[player_id] {\n                        Some(ref mut info) => {\n                            trace!(\n                                \"Player {} (ID {}) colors: {:?} -> {:?}\",\n                                info.name,\n                                player_id,\n                                info.colors,\n                                new_colors,\n                            );\n                            info.colors = new_colors;\n                        }\n\n                        None => {\n                            error!(\n                                \"Attempted to set colors on nonexistent player with ID {}\",\n                                player_id\n                            );\n                        }\n                    }\n                }\n\n                ServerCmd::UpdateFrags {\n                    player_id,\n                    new_frags,\n                } => {\n                    let player_id = player_id as usize;\n                    self.state.check_player_id(player_id)?;\n\n                    match self.state.player_info[player_id] {\n                        Some(ref mut info) => {\n                            trace!(\n                                \"Player {} (ID {}) frags: {} -> {}\",\n                                &info.name,\n                                player_id,\n                                info.frags,\n                                new_frags\n                            );\n                            info.frags = new_frags as i32;\n                        }\n                        None => {\n                            error!(\n                                \"Attempted to set frags on nonexistent player with ID {}\",\n                                player_id\n                            );\n                        }\n                    }\n                }\n\n                ServerCmd::UpdateName {\n                    player_id,\n                    new_name,\n                } => {\n                    let player_id = player_id as usize;\n                    self.state.check_player_id(player_id)?;\n\n                    if let Some(ref mut info) = self.state.player_info[player_id] {\n                        // if this player is already connected, it's a name change\n                        debug!(\"Player {} has changed name to {}\", &info.name, &new_name);\n                        info.name = new_name.to_owned();\n                    } else {\n                        // if this player is not connected, it's a join\n                        debug!(\"Player {} with ID {} has joined\", &new_name, player_id);\n                        self.state.player_info[player_id] = Some(PlayerInfo {\n                            name: new_name.to_owned(),\n                            colors: PlayerColor::new(0, 0),\n                            frags: 0,\n                        });\n                    }\n                }\n\n                ServerCmd::UpdateStat { stat, value } => {\n                    debug!(\n                        \"{:?}: {} -> {}\",\n                        stat, self.state.stats[stat as usize], value\n                    );\n                    self.state.stats[stat as usize] = value;\n                }\n\n                ServerCmd::Version { version } => {\n                    if version != net::PROTOCOL_VERSION as i32 {\n                        // TODO: handle with an error\n                        error!(\n                            \"Incompatible server version: server's is {}, client's is {}\",\n                            version,\n                            net::PROTOCOL_VERSION,\n                        );\n                        panic!(\"bad version number\");\n                    }\n                }\n\n                x => {\n                    debug!(\"{:?}\", x);\n                    unimplemented!();\n                }\n            }\n        }\n\n        Ok(Maintain)\n    }\n\n    fn frame(\n        &mut self,\n        frame_time: Duration,\n        vfs: &Vfs,\n        gfx_state: &GraphicsState,\n        cmds: &mut CmdRegistry,\n        console: &mut Console,\n        music_player: &mut MusicPlayer,\n        idle_vars: IdleVars,\n        kick_vars: KickVars,\n        roll_vars: RollVars,\n        bob_vars: BobVars,\n        cl_nolerp: f32,\n        sv_gravity: f32,\n    ) -> Result<ConnectionStatus, ClientError> {\n        debug!(\"frame time: {}ms\", frame_time.num_milliseconds());\n\n        // do this _before_ parsing server messages so that we know when to\n        // request the next message from the demo server.\n        self.state.advance_time(frame_time);\n        match self.parse_server_msg(vfs, gfx_state, cmds, console, music_player, kick_vars)? {\n            ConnectionStatus::Maintain => (),\n            // if Disconnect or NextDemo, delegate up the chain\n            s => return Ok(s),\n        };\n\n        self.state.update_interp_ratio(cl_nolerp);\n\n        // interpolate entity data and spawn particle effects, lights\n        self.state.update_entities()?;\n\n        // update temp entities (lightning, etc.)\n        self.state.update_temp_entities()?;\n\n        // remove expired lights\n        self.state.lights.update(self.state.time);\n\n        // apply particle physics and remove expired particles\n        self.state\n            .particles\n            .update(self.state.time, frame_time, sv_gravity);\n\n        if let ConnectionKind::Server {\n            ref mut qsock,\n            ref mut compose,\n        } = self.kind\n        {\n            // respond to the server\n            if qsock.can_send() && !compose.is_empty() {\n                qsock.begin_send_msg(&compose)?;\n                compose.clear();\n            }\n        }\n\n        // these all require the player entity to have spawned\n        if let ConnectionState::Connected(_) = self.conn_state {\n            // update view\n            self.state\n                .calc_final_view(idle_vars, kick_vars, roll_vars, bob_vars);\n\n            // update ear positions\n            self.state.update_listener();\n\n            // spatialize sounds for new ear positions\n            self.state.update_sound_spatialization();\n\n            // update camera color shifts for new position/effects\n            self.state.update_color_shifts(frame_time)?;\n        }\n\n        Ok(ConnectionStatus::Maintain)\n    }\n}\n\npub struct Client {\n    vfs: Rc<Vfs>,\n    cvars: Rc<RefCell<CvarRegistry>>,\n    cmds: Rc<RefCell<CmdRegistry>>,\n    console: Rc<RefCell<Console>>,\n    input: Rc<RefCell<Input>>,\n    _output_stream: OutputStream,\n    output_stream_handle: OutputStreamHandle,\n    music_player: Rc<RefCell<MusicPlayer>>,\n    conn: Rc<RefCell<Option<Connection>>>,\n    renderer: ClientRenderer,\n    demo_queue: Rc<RefCell<VecDeque<String>>>,\n}\n\nimpl Client {\n    pub fn new(\n        vfs: Rc<Vfs>,\n        cvars: Rc<RefCell<CvarRegistry>>,\n        cmds: Rc<RefCell<CmdRegistry>>,\n        console: Rc<RefCell<Console>>,\n        input: Rc<RefCell<Input>>,\n        gfx_state: &GraphicsState,\n        menu: &Menu,\n    ) -> Client {\n        let conn = Rc::new(RefCell::new(None));\n\n        let (stream, handle) = match OutputStream::try_default() {\n            Ok(o) => o,\n            // TODO: proceed without sound and allow configuration in menu\n            Err(_) => Err(ClientError::OutputStream).unwrap(),\n        };\n\n        // set up overlay/ui toggles\n        cmds.borrow_mut()\n            .insert_or_replace(\n                \"toggleconsole\",\n                cmd_toggleconsole(conn.clone(), input.clone()),\n            )\n            .unwrap();\n        cmds.borrow_mut()\n            .insert_or_replace(\"togglemenu\", cmd_togglemenu(conn.clone(), input.clone()))\n            .unwrap();\n\n        // set up connection console commands\n        cmds.borrow_mut()\n            .insert_or_replace(\n                \"connect\",\n                cmd_connect(conn.clone(), input.clone(), handle.clone()),\n            )\n            .unwrap();\n        cmds.borrow_mut()\n            .insert_or_replace(\"reconnect\", cmd_reconnect(conn.clone(), input.clone()))\n            .unwrap();\n        cmds.borrow_mut()\n            .insert_or_replace(\"disconnect\", cmd_disconnect(conn.clone(), input.clone()))\n            .unwrap();\n\n        // set up demo playback\n        cmds.borrow_mut()\n            .insert_or_replace(\n                \"playdemo\",\n                cmd_playdemo(conn.clone(), vfs.clone(), input.clone(), handle.clone()),\n            )\n            .unwrap();\n\n        let demo_queue = Rc::new(RefCell::new(VecDeque::new()));\n        cmds.borrow_mut()\n            .insert_or_replace(\n                \"startdemos\",\n                cmd_startdemos(\n                    conn.clone(),\n                    vfs.clone(),\n                    input.clone(),\n                    handle.clone(),\n                    demo_queue.clone(),\n                ),\n            )\n            .unwrap();\n\n        let music_player = Rc::new(RefCell::new(MusicPlayer::new(vfs.clone(), handle.clone())));\n        cmds.borrow_mut()\n            .insert_or_replace(\"music\", cmd_music(music_player.clone()))\n            .unwrap();\n        cmds.borrow_mut()\n            .insert_or_replace(\"music_stop\", cmd_music_stop(music_player.clone()))\n            .unwrap();\n        cmds.borrow_mut()\n            .insert_or_replace(\"music_pause\", cmd_music_pause(music_player.clone()))\n            .unwrap();\n        cmds.borrow_mut()\n            .insert_or_replace(\"music_resume\", cmd_music_resume(music_player.clone()))\n            .unwrap();\n\n        Client {\n            vfs,\n            cvars,\n            cmds,\n            console,\n            input,\n            _output_stream: stream,\n            output_stream_handle: handle,\n            music_player,\n            conn,\n            renderer: ClientRenderer::new(gfx_state, menu),\n            demo_queue,\n        }\n    }\n\n    pub fn disconnect(&mut self) {\n        self.conn.replace(None);\n        self.input.borrow_mut().set_focus(InputFocus::Console);\n    }\n\n    pub fn frame(\n        &mut self,\n        frame_time: Duration,\n        gfx_state: &GraphicsState,\n    ) -> Result<(), ClientError> {\n        let cl_nolerp = self.cvar_value(\"cl_nolerp\")?;\n        let sv_gravity = self.cvar_value(\"sv_gravity\")?;\n        let idle_vars = self.idle_vars()?;\n        let kick_vars = self.kick_vars()?;\n        let roll_vars = self.roll_vars()?;\n        let bob_vars = self.bob_vars()?;\n\n        let status = match *self.conn.borrow_mut() {\n            Some(ref mut conn) => conn.frame(\n                frame_time,\n                &self.vfs,\n                gfx_state,\n                &mut self.cmds.borrow_mut(),\n                &mut self.console.borrow_mut(),\n                &mut self.music_player.borrow_mut(),\n                idle_vars,\n                kick_vars,\n                roll_vars,\n                bob_vars,\n                cl_nolerp,\n                sv_gravity,\n            )?,\n            None => ConnectionStatus::Disconnect,\n        };\n\n        use ConnectionStatus::*;\n        match status {\n            Maintain => (),\n            _ => {\n                let conn = match status {\n                    // if client is already disconnected, this is a no-op\n                    Disconnect => None,\n\n                    // get the next demo from the queue\n                    NextDemo => match self.demo_queue.borrow_mut().pop_front() {\n                        Some(demo) => {\n                            let mut demo_file = match self.vfs.open(format!(\"{}.dem\", demo)) {\n                                Ok(f) => Some(f),\n                                Err(e) => {\n                                    // log the error, dump the demo queue and disconnect\n                                    self.console.borrow_mut().println(format!(\"{}\", e));\n                                    self.demo_queue.borrow_mut().clear();\n                                    None\n                                }\n                            };\n\n                            demo_file.as_mut().and_then(|df| match DemoServer::new(df) {\n                                Ok(d) => Some(Connection {\n                                    kind: ConnectionKind::Demo(d),\n                                    state: ClientState::new(self.output_stream_handle.clone()),\n                                    conn_state: ConnectionState::SignOn(SignOnStage::Prespawn),\n                                }),\n                                Err(e) => {\n                                    self.console.borrow_mut().println(format!(\"{}\", e));\n                                    self.demo_queue.borrow_mut().clear();\n                                    None\n                                }\n                            })\n                        }\n\n                        // if there are no more demos in the queue, disconnect\n                        None => None,\n                    },\n\n                    // covered in first match\n                    Maintain => unreachable!(),\n                };\n\n                match conn {\n                    Some(_) => self.input.borrow_mut().set_focus(InputFocus::Game),\n\n                    // don't allow game focus when disconnected\n                    None => self.input.borrow_mut().set_focus(InputFocus::Console),\n                }\n\n                self.conn.replace(conn);\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn render(\n        &mut self,\n        gfx_state: &GraphicsState,\n        encoder: &mut wgpu::CommandEncoder,\n        width: u32,\n        height: u32,\n        menu: &Menu,\n        focus: InputFocus,\n    ) -> Result<(), ClientError> {\n        let fov = Deg(self.cvar_value(\"fov\")?);\n        let cvars = self.cvars.borrow();\n        let console = self.console.borrow();\n\n        self.renderer.render(\n            gfx_state,\n            encoder,\n            self.conn.borrow().as_ref(),\n            width,\n            height,\n            fov,\n            &cvars,\n            &console,\n            menu,\n            focus,\n        );\n\n        Ok(())\n    }\n\n    pub fn cvar_value<S>(&self, name: S) -> Result<f32, ClientError>\n    where\n        S: AsRef<str>,\n    {\n        self.cvars\n            .borrow()\n            .get_value(name.as_ref())\n            .map_err(ClientError::Cvar)\n    }\n\n    pub fn handle_input(\n        &mut self,\n        game_input: &mut GameInput,\n        frame_time: Duration,\n    ) -> Result<(), ClientError> {\n        let move_vars = self.move_vars()?;\n        let mouse_vars = self.mouse_vars()?;\n\n        match *self.conn.borrow_mut() {\n            Some(Connection {\n                ref mut state,\n                kind: ConnectionKind::Server { ref mut qsock, .. },\n                ..\n            }) => {\n                let move_cmd = state.handle_input(game_input, frame_time, move_vars, mouse_vars);\n                // TODO: arrayvec here\n                let mut msg = Vec::new();\n                move_cmd.serialize(&mut msg)?;\n                qsock.send_msg_unreliable(&msg)?;\n\n                // clear mouse and impulse\n                game_input.refresh();\n            }\n\n            _ => (),\n        }\n\n        Ok(())\n    }\n\n    fn move_vars(&self) -> Result<MoveVars, ClientError> {\n        Ok(MoveVars {\n            cl_anglespeedkey: self.cvar_value(\"cl_anglespeedkey\")?,\n            cl_pitchspeed: self.cvar_value(\"cl_pitchspeed\")?,\n            cl_yawspeed: self.cvar_value(\"cl_yawspeed\")?,\n            cl_sidespeed: self.cvar_value(\"cl_sidespeed\")?,\n            cl_upspeed: self.cvar_value(\"cl_upspeed\")?,\n            cl_forwardspeed: self.cvar_value(\"cl_forwardspeed\")?,\n            cl_backspeed: self.cvar_value(\"cl_backspeed\")?,\n            cl_movespeedkey: self.cvar_value(\"cl_movespeedkey\")?,\n        })\n    }\n\n    fn idle_vars(&self) -> Result<IdleVars, ClientError> {\n        Ok(IdleVars {\n            v_idlescale: self.cvar_value(\"v_idlescale\")?,\n            v_ipitch_cycle: self.cvar_value(\"v_ipitch_cycle\")?,\n            v_ipitch_level: self.cvar_value(\"v_ipitch_level\")?,\n            v_iroll_cycle: self.cvar_value(\"v_iroll_cycle\")?,\n            v_iroll_level: self.cvar_value(\"v_iroll_level\")?,\n            v_iyaw_cycle: self.cvar_value(\"v_iyaw_cycle\")?,\n            v_iyaw_level: self.cvar_value(\"v_iyaw_level\")?,\n        })\n    }\n\n    fn kick_vars(&self) -> Result<KickVars, ClientError> {\n        Ok(KickVars {\n            v_kickpitch: self.cvar_value(\"v_kickpitch\")?,\n            v_kickroll: self.cvar_value(\"v_kickroll\")?,\n            v_kicktime: self.cvar_value(\"v_kicktime\")?,\n        })\n    }\n\n    fn mouse_vars(&self) -> Result<MouseVars, ClientError> {\n        Ok(MouseVars {\n            m_pitch: self.cvar_value(\"m_pitch\")?,\n            m_yaw: self.cvar_value(\"m_yaw\")?,\n            sensitivity: self.cvar_value(\"sensitivity\")?,\n        })\n    }\n\n    fn roll_vars(&self) -> Result<RollVars, ClientError> {\n        Ok(RollVars {\n            cl_rollangle: self.cvar_value(\"cl_rollangle\")?,\n            cl_rollspeed: self.cvar_value(\"cl_rollspeed\")?,\n        })\n    }\n\n    fn bob_vars(&self) -> Result<BobVars, ClientError> {\n        Ok(BobVars {\n            cl_bob: self.cvar_value(\"cl_bob\")?,\n            cl_bobcycle: self.cvar_value(\"cl_bobcycle\")?,\n            cl_bobup: self.cvar_value(\"cl_bobup\")?,\n        })\n    }\n\n    pub fn view_entity_id(&self) -> Option<usize> {\n        match *self.conn.borrow() {\n            Some(Connection { ref state, .. }) => Some(state.view_entity_id()),\n            None => None,\n        }\n    }\n\n    pub fn trace<'a, I>(&self, entity_ids: I) -> Result<TraceFrame, ClientError>\n    where\n        I: IntoIterator<Item = &'a usize>,\n    {\n        match *self.conn.borrow() {\n            Some(Connection { ref state, .. }) => {\n                let mut trace = TraceFrame {\n                    msg_times_ms: [\n                        state.msg_times[0].num_milliseconds(),\n                        state.msg_times[1].num_milliseconds(),\n                    ],\n                    time_ms: state.time.num_milliseconds(),\n                    lerp_factor: state.lerp_factor,\n                    entities: HashMap::new(),\n                };\n\n                for id in entity_ids.into_iter() {\n                    let ent = &state.entities[*id];\n\n                    let msg_origins = [ent.msg_origins[0].into(), ent.msg_origins[1].into()];\n                    let msg_angles_deg = [\n                        [\n                            ent.msg_angles[0][0].0,\n                            ent.msg_angles[0][1].0,\n                            ent.msg_angles[0][2].0,\n                        ],\n                        [\n                            ent.msg_angles[1][0].0,\n                            ent.msg_angles[1][1].0,\n                            ent.msg_angles[1][2].0,\n                        ],\n                    ];\n\n                    trace.entities.insert(\n                        *id as u32,\n                        TraceEntity {\n                            msg_origins,\n                            msg_angles_deg,\n                            origin: ent.origin.into(),\n                        },\n                    );\n                }\n\n                Ok(trace)\n            }\n\n            None => Err(ClientError::NotConnected),\n        }\n    }\n}\n\nimpl std::ops::Drop for Client {\n    fn drop(&mut self) {\n        // if this errors, it was already removed so we don't care\n        let _ = self.cmds.borrow_mut().remove(\"reconnect\");\n    }\n}\n\n// implements the \"toggleconsole\" command\nfn cmd_toggleconsole(\n    conn: Rc<RefCell<Option<Connection>>>,\n    input: Rc<RefCell<Input>>,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        let focus = input.borrow().focus();\n        match *conn.borrow() {\n            Some(_) => match focus {\n                InputFocus::Game => input.borrow_mut().set_focus(InputFocus::Console),\n                InputFocus::Console => input.borrow_mut().set_focus(InputFocus::Game),\n                InputFocus::Menu => input.borrow_mut().set_focus(InputFocus::Console),\n            },\n            None => match focus {\n                InputFocus::Console => input.borrow_mut().set_focus(InputFocus::Menu),\n                InputFocus::Game => unreachable!(),\n                InputFocus::Menu => input.borrow_mut().set_focus(InputFocus::Console),\n            },\n        }\n        String::new()\n    })\n}\n\n// implements the \"togglemenu\" command\nfn cmd_togglemenu(\n    conn: Rc<RefCell<Option<Connection>>>,\n    input: Rc<RefCell<Input>>,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        let focus = input.borrow().focus();\n        match *conn.borrow() {\n            Some(_) => match focus {\n                InputFocus::Game => input.borrow_mut().set_focus(InputFocus::Menu),\n                InputFocus::Console => input.borrow_mut().set_focus(InputFocus::Menu),\n                InputFocus::Menu => input.borrow_mut().set_focus(InputFocus::Game),\n            },\n            None => match focus {\n                InputFocus::Console => input.borrow_mut().set_focus(InputFocus::Menu),\n                InputFocus::Game => unreachable!(),\n                InputFocus::Menu => input.borrow_mut().set_focus(InputFocus::Console),\n            },\n        }\n        String::new()\n    })\n}\n\nfn connect<A>(server_addrs: A, stream: OutputStreamHandle) -> Result<Connection, ClientError>\nwhere\n    A: ToSocketAddrs,\n{\n    let mut con_sock = ConnectSocket::bind(\"0.0.0.0:0\")?;\n    let server_addr = match server_addrs.to_socket_addrs() {\n        Ok(ref mut a) => a.next().ok_or(ClientError::InvalidServerAddress),\n        Err(_) => Err(ClientError::InvalidServerAddress),\n    }?;\n\n    let mut response = None;\n\n    for attempt in 0..MAX_CONNECT_ATTEMPTS {\n        println!(\n            \"Connecting...(attempt {} of {})\",\n            attempt + 1,\n            MAX_CONNECT_ATTEMPTS\n        );\n        con_sock.send_request(\n            Request::connect(net::GAME_NAME, CONNECT_PROTOCOL_VERSION),\n            server_addr,\n        )?;\n\n        // TODO: get rid of magic constant (2.5 seconds wait time for response)\n        match con_sock.recv_response(Some(Duration::milliseconds(2500))) {\n            Err(err) => {\n                match err {\n                    // if the message is invalid, log it but don't quit\n                    // TODO: this should probably disconnect\n                    NetError::InvalidData(msg) => error!(\"{}\", msg),\n\n                    // other errors are fatal\n                    e => return Err(e.into()),\n                }\n            }\n\n            Ok(opt) => {\n                if let Some((resp, remote)) = opt {\n                    // if this response came from the right server, we're done\n                    if remote == server_addr {\n                        response = Some(resp);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    let port = match response.ok_or(ClientError::NoResponse)? {\n        Response::Accept(accept) => {\n            // validate port number\n            if accept.port < 0 || accept.port >= std::u16::MAX as i32 {\n                Err(ClientError::InvalidConnectPort(accept.port))?;\n            }\n\n            debug!(\"Connection accepted on port {}\", accept.port);\n            accept.port as u16\n        }\n\n        // our request was rejected.\n        Response::Reject(reject) => Err(ClientError::ConnectionRejected(reject.message))?,\n\n        // the server sent back a response that doesn't make sense here (i.e. something other\n        // than an Accept or Reject).\n        _ => Err(ClientError::InvalidConnectResponse)?,\n    };\n\n    let mut new_addr = server_addr;\n    new_addr.set_port(port);\n\n    // we're done with the connection socket, so turn it into a QSocket with the new address\n    let qsock = con_sock.into_qsocket(new_addr);\n\n    Ok(Connection {\n        state: ClientState::new(stream),\n        kind: ConnectionKind::Server {\n            qsock,\n            compose: Vec::new(),\n        },\n        conn_state: ConnectionState::SignOn(SignOnStage::Prespawn),\n    })\n}\n\n// TODO: when an audio device goes down, every command with an\n// OutputStreamHandle needs to be reconstructed so it doesn't pass out\n// references to a dead output stream\n\n// TODO: this will hang while connecting. ideally, input should be handled in a\n// separate thread so the OS doesn't think the client has gone unresponsive.\nfn cmd_connect(\n    conn: Rc<RefCell<Option<Connection>>>,\n    input: Rc<RefCell<Input>>,\n    stream: OutputStreamHandle,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |args| {\n        if args.len() < 1 {\n            // TODO: print to console\n            return \"usage: connect <server_ip>:<server_port>\".to_owned();\n        }\n\n        match connect(args[0], stream.clone()) {\n            Ok(new_conn) => {\n                conn.replace(Some(new_conn));\n                input.borrow_mut().set_focus(InputFocus::Game);\n                String::new()\n            }\n            Err(e) => format!(\"{}\", e),\n        }\n    })\n}\n\nfn cmd_reconnect(\n    conn: Rc<RefCell<Option<Connection>>>,\n    input: Rc<RefCell<Input>>,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        match *conn.borrow_mut() {\n            Some(ref mut conn) => {\n                // TODO: clear client state\n                conn.conn_state = ConnectionState::SignOn(SignOnStage::Prespawn);\n                input.borrow_mut().set_focus(InputFocus::Game);\n                String::new()\n            }\n            // TODO: log message, e.g. \"can't reconnect while disconnected\"\n            None => \"not connected\".to_string(),\n        }\n    })\n}\n\nfn cmd_disconnect(\n    conn: Rc<RefCell<Option<Connection>>>,\n    input: Rc<RefCell<Input>>,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        let connected = conn.borrow().is_some();\n        if connected {\n            conn.replace(None);\n            input.borrow_mut().set_focus(InputFocus::Console);\n            String::new()\n        } else {\n            \"not connected\".to_string()\n        }\n    })\n}\n\nfn cmd_playdemo(\n    conn: Rc<RefCell<Option<Connection>>>,\n    vfs: Rc<Vfs>,\n    input: Rc<RefCell<Input>>,\n    stream: OutputStreamHandle,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |args| {\n        if args.len() != 1 {\n            return \"usage: playdemo [DEMOFILE]\".to_owned();\n        }\n\n        let mut demo_file = match vfs.open(format!(\"{}.dem\", args[0])) {\n            Ok(f) => f,\n            Err(e) => return format!(\"{}\", e),\n        };\n\n        let demo_server = match DemoServer::new(&mut demo_file) {\n            Ok(d) => d,\n            Err(e) => return format!(\"{}\", e),\n        };\n\n        conn.replace(Some(Connection {\n            state: ClientState::new(stream.clone()),\n            kind: ConnectionKind::Demo(demo_server),\n            conn_state: ConnectionState::SignOn(SignOnStage::Prespawn),\n        }));\n\n        input.borrow_mut().set_focus(InputFocus::Game);\n        String::new()\n    })\n}\n\nfn cmd_startdemos(\n    conn: Rc<RefCell<Option<Connection>>>,\n    vfs: Rc<Vfs>,\n    input: Rc<RefCell<Input>>,\n    stream: OutputStreamHandle,\n    demo_queue: Rc<RefCell<VecDeque<String>>>,\n) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |args| {\n        if args.len() == 0 {\n            return \"usage: startdemos [DEMOS]\".to_owned();\n        }\n\n        for arg in args {\n            demo_queue.borrow_mut().push_back(arg.to_string());\n        }\n\n        let mut demo_file = match vfs.open(format!(\n            \"{}.dem\",\n            demo_queue.borrow_mut().pop_front().unwrap()\n        )) {\n            Ok(f) => f,\n            Err(e) => return format!(\"{}\", e),\n        };\n\n        let demo_server = match DemoServer::new(&mut demo_file) {\n            Ok(d) => d,\n            Err(e) => return format!(\"{}\", e),\n        };\n\n        conn.replace(Some(Connection {\n            state: ClientState::new(stream.clone()),\n            kind: ConnectionKind::Demo(demo_server),\n            conn_state: ConnectionState::SignOn(SignOnStage::Prespawn),\n        }));\n\n        input.borrow_mut().set_focus(InputFocus::Game);\n\n        String::new()\n    })\n}\n\nfn cmd_music(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |args| {\n        if args.len() != 1 {\n            return \"usage: music [TRACKNAME]\".to_owned();\n        }\n\n        let res = music_player.borrow_mut().play_named(args[0]);\n        match res {\n            Ok(()) => String::new(),\n            Err(e) => {\n                music_player.borrow_mut().stop();\n                format!(\"{}\", e)\n            }\n        }\n    })\n}\n\nfn cmd_music_stop(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        music_player.borrow_mut().stop();\n        String::new()\n    })\n}\n\nfn cmd_music_pause(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        music_player.borrow_mut().pause();\n        String::new()\n    })\n}\n\nfn cmd_music_resume(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn Fn(&[&str]) -> String> {\n    Box::new(move |_| {\n        music_player.borrow_mut().resume();\n        String::new()\n    })\n}\n"
  },
  {
    "path": "src/client/render/atlas.rs",
    "content": "use std::{cmp::Ordering, mem::size_of};\n\nuse crate::client::render::Palette;\n\nuse failure::Error;\n\nconst DEFAULT_ATLAS_DIM: u32 = 1024;\n\nstruct Rect {\n    x: u32,\n    y: u32,\n    width: u32,\n    height: u32,\n}\n\nfn area_order(t1: &TextureData, t2: &TextureData) -> Ordering {\n    (t1.width * t1.height).cmp(&(t2.width * t2.height))\n}\n\n#[derive(Clone, Debug)]\npub struct TextureData {\n    width: u32,\n    height: u32,\n    indexed: Vec<u8>,\n}\n\nimpl TextureData {\n    fn empty(width: u32, height: u32) -> TextureData {\n        let len = (width * height) as usize;\n        let mut indexed = Vec::with_capacity(len);\n        indexed.resize(len, 0);\n\n        TextureData {\n            width,\n            height,\n            indexed,\n        }\n    }\n\n    fn subtexture(&mut self, other: &TextureData, xy: [u32; 2]) -> Result<(), Error> {\n        let [x, y] = xy;\n        ensure!(x + other.width <= self.width);\n        ensure!(y + other.height <= self.height);\n\n        for r in 0..other.height {\n            for c in 0..other.width {\n                self.indexed[(self.width * (y + r) + x + c) as usize] =\n                    other.indexed[(other.width * r + c) as usize];\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub struct TextureAtlasBuilder {\n    textures: Vec<TextureData>,\n}\n\nimpl TextureAtlasBuilder {\n    pub fn new() -> TextureAtlasBuilder {\n        TextureAtlasBuilder {\n            textures: Vec::new(),\n        }\n    }\n\n    pub fn add(&mut self, texture: TextureData) -> Result<usize, Error> {\n        self.textures.push(texture);\n        Ok(self.textures.len() - 1)\n    }\n\n    /// Constructs a TextureAtlas by efficiently packing multiple textures together.\n    ///\n    /// - Enumerate and sort the textures by total area, returning the sorted\n    ///   list of textures and a corresponding list of each texture's original index\n    /// - Create a list of available rectangular spaces in the atlas, starting with\n    ///   the entire space.\n    /// - For each texture, find a large enough space. Remove the space from the list,\n    ///   splitting off any unnecessary space and returning that to the list. Add the\n    ///   coordinates to a list of texture locations.\n    /// - Sort the list of texture locations back into the original texture order.\n    pub fn build(\n        self,\n        label: Option<&str>,\n        device: &wgpu::Device,\n        queue: &wgpu::Queue,\n        palette: &Palette,\n    ) -> Result<TextureAtlas, Error> {\n        let TextureAtlasBuilder { textures } = self;\n        let mut enumerated_textures = textures\n            .into_iter()\n            .enumerate()\n            .collect::<Vec<(usize, TextureData)>>();\n        enumerated_textures.sort_unstable_by(|e1, e2| area_order(&e1.1, &e2.1));\n        let (indices, textures): (Vec<usize>, Vec<TextureData>) =\n            enumerated_textures.into_iter().unzip();\n\n        let mut atlas = TextureData::empty(DEFAULT_ATLAS_DIM, DEFAULT_ATLAS_DIM);\n        let mut spaces = vec![Rect {\n            x: 0,\n            y: 0,\n            width: atlas.width,\n            height: atlas.height,\n        }];\n\n        let mut subtextures: Vec<TextureAtlasSubtexture> = Vec::with_capacity(textures.len());\n\n        // iterate in reverse: largest textures first\n        for tex in textures.iter().rev() {\n            let mut coords: Option<(u32, u32)> = None;\n\n            // find a large enough space\n            for i in (0..spaces.len()).rev() {\n                use std::cmp::Ordering::*;\n\n                // - find a large enough space to fit the current texture\n                // - copy the texture into the space\n                // - remove the space from the list of candidates\n                // - split off any unused space and return it to the list\n                let subtex = match (\n                    spaces[i].width.cmp(&tex.width),\n                    spaces[i].height.cmp(&tex.height),\n                ) {\n                    // if either dimension is too small, keep looking\n                    (Less, _) | (_, Less) => continue,\n\n                    // perfect fit!\n                    (Equal, Equal) => {\n                        let Rect { x, y, .. } = spaces.remove(i);\n                        (x, y)\n                    }\n\n                    // split off the right side\n                    (Greater, Equal) => {\n                        let space = spaces.remove(i);\n                        spaces.push(Rect {\n                            x: space.x + tex.width,\n                            y: space.y,\n                            width: space.width - tex.width,\n                            height: space.height,\n                        });\n                        (space.x, space.y)\n                    }\n\n                    // split off the bottom\n                    (Equal, Greater) => {\n                        let space = spaces.remove(i);\n                        spaces.push(Rect {\n                            x: space.x,\n                            y: space.y + tex.height,\n                            width: space.width,\n                            height: space.height - tex.height,\n                        });\n                        (space.x, space.y)\n                    }\n\n                    // split off two spaces, maximizing the size of the large one\n                    (Greater, Greater) => {\n                        let space = spaces.remove(i);\n                        let w_diff = space.width - tex.width;\n                        let h_diff = space.height - tex.height;\n\n                        let (space_a, space_b) = if w_diff > h_diff {\n                            // =============\n                            // |     |     |\n                            // | tex |     |\n                            // |     |  A  |\n                            // |-----|     |\n                            // |  B  |     |\n                            // =============\n                            (\n                                Rect {\n                                    // A\n                                    x: space.x + tex.width,\n                                    y: space.y,\n                                    width: space.width - tex.width,\n                                    height: space.height,\n                                },\n                                Rect {\n                                    // B\n                                    x: space.x,\n                                    y: space.y + tex.height,\n                                    width: tex.width,\n                                    height: space.height - tex.height,\n                                },\n                            )\n                        } else {\n                            // =============\n                            // |  tex  | B |\n                            // |-----------|\n                            // |           |\n                            // |     A     |\n                            // |           |\n                            // =============\n                            (\n                                Rect {\n                                    // A\n                                    x: space.x,\n                                    y: space.y + tex.height,\n                                    width: space.width,\n                                    height: space.height - tex.height,\n                                },\n                                Rect {\n                                    // B\n                                    x: space.x + tex.width,\n                                    y: space.y,\n                                    width: space.width - tex.width,\n                                    height: tex.height,\n                                },\n                            )\n                        };\n\n                        // put the smaller space closer to the end\n                        spaces.push(space_a);\n                        spaces.push(space_b);\n\n                        (space.x, space.y)\n                    }\n                };\n\n                coords = Some(subtex);\n            }\n\n            match coords {\n                Some((x, y)) => {\n                    let base_s = x as f32 / atlas.width as f32;\n                    let base_t = y as f32 / atlas.height as f32;\n                    let subtex_w = tex.width as f32 / atlas.width as f32;\n                    let subtex_h = tex.height as f32 / atlas.height as f32;\n                    subtextures.push(TextureAtlasSubtexture {\n                        base_xy: [x, y],\n                        base_st: [base_s, base_t],\n                        width: subtex_w,\n                        height: subtex_h,\n                    });\n                }\n                None => bail!(\"Can't pack all textures in an atlas this size!\"),\n            }\n        }\n\n        // copy the textures into the atlas\n        for (subtex, tex) in subtextures.iter().rev().zip(textures.iter()) {\n            atlas.subtexture(tex, subtex.base_xy)?;\n        }\n\n        let mut enumerated_subtextures: Vec<(usize, TextureAtlasSubtexture)> = indices\n            .into_iter()\n            .zip(subtextures.into_iter().rev())\n            .collect();\n\n        // sort back into the original order\n        enumerated_subtextures.sort_unstable_by(|e1, e2| e1.0.cmp(&e2.0));\n        let (_, subtextures): (Vec<usize>, Vec<TextureAtlasSubtexture>) =\n            enumerated_subtextures.into_iter().unzip();\n\n        let (rgba, fullbright) = palette.translate(&atlas.indexed);\n\n        let diffuse_buffer = device.create_buffer_with_data(&rgba, wgpu::BufferUsage::COPY_SRC);\n        let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {\n            label: None,\n            size: wgpu::Extent3d {\n                width: atlas.width,\n                height: atlas.height,\n                depth_or_array_layers: 1,\n            },\n            array_layer_count: 1,\n            mip_level_count: 1,\n            sample_count: 1,\n            dimension: wgpu::TextureDimension::D2,\n            format: wgpu::TextureFormat::Rgba8UnormSrgb,\n            usage: wgpu::TextureUsage::NONE,\n        });\n        let mut encoder =\n            device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });\n        encoder.copy_buffer_to_texture(\n            wgpu::BufferCopyView {\n                buffer: &diffuse_buffer,\n                offset: 0,\n                bytes_per_row: atlas.width * atlas.height * size_of::<[u8; 4]> as u32,\n                rows_per_image: 1,\n            },\n            wgpu::ImageCopyTexture {\n                texture: &diffuse_texture,\n                mip_level: 1,\n                array_layer: 0,\n                origin: wgpu::Origin3d::ZERO,\n            },\n            wgpu::Extent3d {\n                width: atlas.width,\n                height: atlas.height,\n                depth_or_array_layers: 1,\n            },\n        );\n        let cmd_buffer = encoder.finish();\n        queue.submit(&[cmd_buffer]);\n\n        Ok(TextureAtlas {\n            atlas: diffuse_texture,\n            width: atlas.width,\n            height: atlas.height,\n            subtextures,\n        })\n    }\n}\n\nstruct TextureAtlasSubtexture {\n    // base subtexture coordinates in the atlas pixel space\n    base_xy: [u32; 2],\n\n    // base subtexture coordinates in the atlas texel space\n    base_st: [f32; 2],\n\n    // dimensions of the subtexture in atlas texel space\n    width: f32,\n    height: f32,\n}\n\nimpl TextureAtlasSubtexture {\n    fn convert_texcoords(&self, st: [f32; 2]) -> [f32; 2] {\n        [\n            self.base_st[0] + st[0] * self.width,\n            self.base_st[1] + st[1] * self.height,\n        ]\n    }\n}\n\npub struct TextureAtlas {\n    /// A handle to the atlas data on the GPU.\n    atlas: wgpu::Texture,\n    /// The width in texels of the atlas.\n    width: u32,\n    /// The height in texels of the atlas.\n    height: u32,\n    subtextures: Vec<TextureAtlasSubtexture>,\n}\n\nimpl TextureAtlas {\n    pub fn convert_texcoords(&self, id: usize, st: [f32; 2]) -> [f32; 2] {\n        self.subtextures[id].convert_texcoords(st)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_texture_data_subtexture() {\n        let src = TextureData {\n            width: 2,\n            height: 2,\n            #[rustfmt::skip]\n            indexed: vec![\n                1, 2,\n                3, 4,\n            ],\n        };\n\n        let dst = TextureData {\n            width: 4,\n            height: 4,\n            #[rustfmt::skip]\n            indexed: vec![\n                0, 0, 0, 0,\n                0, 0, 0, 0,\n                0, 0, 0, 0,\n                0, 0, 0, 0,\n            ],\n        };\n\n        let mut dst_copy = dst.clone();\n        dst_copy.subtexture(&src, [1, 1]).unwrap();\n\n        assert_eq!(\n            dst_copy.indexed,\n            vec![0, 0, 0, 0, 0, 1, 2, 0, 0, 3, 4, 0, 0, 0, 0, 0,]\n        );\n    }\n}\n"
  },
  {
    "path": "src/client/render/blit.rs",
    "content": "use crate::client::render::{pipeline::Pipeline, ui::quad::QuadPipeline, GraphicsState};\n\npub struct BlitPipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    bind_group: wgpu::BindGroup,\n    sampler: wgpu::Sampler,\n}\n\nimpl BlitPipeline {\n    pub fn create_bind_group(\n        device: &wgpu::Device,\n        layouts: &[wgpu::BindGroupLayout],\n        sampler: &wgpu::Sampler,\n        input: &wgpu::TextureView,\n    ) -> wgpu::BindGroup {\n        device.create_bind_group(&wgpu::BindGroupDescriptor {\n            label: Some(\"blit bind group\"),\n            layout: &layouts[0],\n            entries: &[\n                wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::Sampler(&sampler),\n                },\n                wgpu::BindGroupEntry {\n                    binding: 1,\n                    resource: wgpu::BindingResource::TextureView(input),\n                },\n            ],\n        })\n    }\n\n    pub fn new(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        input: &wgpu::TextureView,\n    ) -> BlitPipeline {\n        let (pipeline, bind_group_layouts) = BlitPipeline::create(device, compiler, &[], 1);\n\n        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {\n            label: None,\n            address_mode_u: wgpu::AddressMode::ClampToEdge,\n            address_mode_v: wgpu::AddressMode::ClampToEdge,\n            address_mode_w: wgpu::AddressMode::ClampToEdge,\n            mag_filter: wgpu::FilterMode::Nearest,\n            min_filter: wgpu::FilterMode::Nearest,\n            mipmap_filter: wgpu::FilterMode::Nearest,\n            lod_min_clamp: -1000.0,\n            lod_max_clamp: 1000.0,\n            compare: None,\n            anisotropy_clamp: None,\n            ..Default::default()\n        });\n\n        let bind_group = Self::create_bind_group(device, &bind_group_layouts, &sampler, input);\n\n        BlitPipeline {\n            pipeline,\n            bind_group_layouts,\n            bind_group,\n            sampler,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        input: &wgpu::TextureView,\n    ) {\n        let layout_refs: Vec<_> = self.bind_group_layouts.iter().collect();\n        let pipeline = BlitPipeline::recreate(device, compiler, &layout_refs, 1);\n        self.pipeline = pipeline;\n        self.bind_group =\n            Self::create_bind_group(device, self.bind_group_layouts(), &self.sampler, input);\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn blit<'a>(&'a self, state: &'a GraphicsState, pass: &mut wgpu::RenderPass<'a>) {\n        pass.set_pipeline(&self.pipeline());\n        pass.set_bind_group(0, &self.bind_group, &[]);\n        pass.set_vertex_buffer(0, state.quad_pipeline().vertex_buffer().slice(..));\n        pass.draw(0..6, 0..1);\n    }\n}\n\nimpl Pipeline for BlitPipeline {\n    type VertexPushConstants = ();\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"blit\"\n    }\n\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![wgpu::BindGroupLayoutDescriptor {\n            label: Some(\"blit bind group\"),\n            entries: &[\n                // sampler\n                wgpu::BindGroupLayoutEntry {\n                    binding: 0,\n                    visibility: wgpu::ShaderStage::FRAGMENT,\n                    ty: wgpu::BindingType::Sampler {\n                        filtering: true,\n                        comparison: false,\n                    },\n                    count: None,\n                },\n                // blit texture\n                wgpu::BindGroupLayoutEntry {\n                    binding: 1,\n                    visibility: wgpu::ShaderStage::FRAGMENT,\n                    ty: wgpu::BindingType::Texture {\n                        view_dimension: wgpu::TextureViewDimension::D2,\n                        sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                        multisampled: false,\n                    },\n                    count: None,\n                },\n            ],\n        }]\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/blit.vert\"))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/blit.frag\"))\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        QuadPipeline::primitive_state()\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        QuadPipeline::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        None\n    }\n\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        QuadPipeline::vertex_buffer_layouts()\n    }\n}\n"
  },
  {
    "path": "src/client/render/cvars.rs",
    "content": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse crate::common::console::CvarRegistry;\n\npub fn register_cvars(cvars: &CvarRegistry) {\n    cvars.register(\"r_lightmap\", \"0\").unwrap();\n    cvars.register(\"r_msaa_samples\", \"4\").unwrap();\n}\n"
  },
  {
    "path": "src/client/render/error.rs",
    "content": "use crate::common::{\n    vfs::VfsError,\n    wad::WadError,\n};\nuse failure::{Backtrace, Context, Fail};\nuse std::{\n    convert::From,\n    fmt::{self, Display},\n};\n\n#[derive(Debug)]\npub struct RenderError {\n    inner: Context<RenderErrorKind>,\n}\n\nimpl RenderError {\n    pub fn kind(&self) -> RenderErrorKind {\n        *self.inner.get_context()\n    }\n}\n\nimpl From<RenderErrorKind> for RenderError {\n    fn from(kind: RenderErrorKind) -> Self {\n        RenderError {\n            inner: Context::new(kind),\n        }\n    }\n}\n\nimpl From<VfsError> for RenderError {\n    fn from(vfs_error: VfsError) -> Self {\n        match vfs_error {\n            VfsError::NoSuchFile(_) => {\n                vfs_error.context(RenderErrorKind::ResourceNotLoaded).into()\n            }\n            _ => vfs_error.context(RenderErrorKind::Other).into(),\n        }\n    }\n}\n\nimpl From<WadError> for RenderError {\n    fn from(wad_error: WadError) -> Self {\n        wad_error.context(RenderErrorKind::ResourceNotLoaded).into()\n    }\n}\n\nimpl From<Context<RenderErrorKind>> for RenderError {\n    fn from(inner: Context<RenderErrorKind>) -> Self {\n        RenderError { inner }\n    }\n}\n\nimpl Fail for RenderError {\n    fn cause(&self) -> Option<&dyn Fail> {\n        self.inner.cause()\n    }\n\n    fn backtrace(&self) -> Option<&Backtrace> {\n        self.inner.backtrace()\n    }\n}\n\nimpl Display for RenderError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        Display::fmt(&self.inner, f)\n    }\n}\n\n#[derive(Clone, Copy, Eq, PartialEq, Debug, Fail)]\npub enum RenderErrorKind {\n    #[fail(display = \"Failed to load resource\")]\n    ResourceNotLoaded,\n    #[fail(display = \"Unspecified render error\")]\n    Other,\n}\n"
  },
  {
    "path": "src/client/render/mod.rs",
    "content": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/// Rendering functionality.\n///\n/// # Pipeline stages\n///\n/// The current rendering implementation consists of the following stages:\n/// - Initial geometry pass\n///   - Inputs:\n///     - `AliasPipeline`\n///     - `BrushPipeline`\n///     - `SpritePipeline`\n///   - Output: `InitialPassTarget`\n/// - Deferred lighting pass\n///   - Inputs:\n///     - `DeferredPipeline`\n///   - Output: `DeferredPassTarget`\n/// - Final pass\n///   - Inputs:\n///     - `PostProcessPipeline`\n///     - `QuadPipeline`\n///     - `GlyphPipeline`\n///   - Output: `FinalPassTarget`\n/// - Blit to swap chain\n///   - Inputs:\n///     - `BlitPipeline`\n///   - Output: `SwapChainTarget`\n// mod atlas;\nmod blit;\nmod cvars;\nmod error;\nmod palette;\nmod pipeline;\nmod target;\nmod ui;\nmod uniform;\nmod warp;\nmod world;\n\npub use cvars::register_cvars;\npub use error::{RenderError, RenderErrorKind};\npub use palette::Palette;\npub use pipeline::Pipeline;\npub use postprocess::PostProcessRenderer;\npub use target::{RenderTarget, RenderTargetResolve, SwapChainTarget};\npub use ui::{hud::HudState, UiOverlay, UiRenderer, UiState};\npub use world::{\n    deferred::{DeferredRenderer, DeferredUniforms, PointLight},\n    Camera, WorldRenderer,\n};\n\nuse std::{\n    borrow::Cow,\n    cell::{Cell, Ref, RefCell, RefMut},\n    mem::size_of,\n    num::{NonZeroU32, NonZeroU64, NonZeroU8},\n    rc::Rc,\n};\n\nuse crate::{\n    client::{\n        entity::MAX_LIGHTS,\n        input::InputFocus,\n        menu::Menu,\n        render::{\n            blit::BlitPipeline,\n            target::{DeferredPassTarget, FinalPassTarget, InitialPassTarget},\n            ui::{glyph::GlyphPipeline, quad::QuadPipeline},\n            uniform::DynamicUniformBuffer,\n            world::{\n                alias::AliasPipeline,\n                brush::BrushPipeline,\n                deferred::DeferredPipeline,\n                particle::ParticlePipeline,\n                postprocess::{self, PostProcessPipeline},\n                sprite::SpritePipeline,\n                EntityUniforms,\n            },\n        },\n        Connection, ConnectionKind,\n    },\n    common::{\n        console::{Console, CvarRegistry},\n        model::Model,\n        net::SignOnStage,\n        vfs::Vfs,\n        wad::Wad,\n    },\n};\n\nuse super::ConnectionState;\nuse bumpalo::Bump;\nuse cgmath::{Deg, InnerSpace, Vector3, Zero};\nuse chrono::{DateTime, Duration, Utc};\nuse failure::Error;\n\nconst DEPTH_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;\npub const DIFFUSE_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8Unorm;\nconst NORMAL_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;\nconst LIGHT_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;\n\nconst DIFFUSE_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;\nconst FULLBRIGHT_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;\nconst LIGHTMAP_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;\n\n/// Create a `wgpu::TextureDescriptor` appropriate for the provided texture data.\npub fn texture_descriptor<'a>(\n    label: Option<&'a str>,\n    width: u32,\n    height: u32,\n    format: wgpu::TextureFormat,\n) -> wgpu::TextureDescriptor {\n    wgpu::TextureDescriptor {\n        label,\n        size: wgpu::Extent3d {\n            width,\n            height,\n            depth_or_array_layers: 1,\n        },\n        mip_level_count: 1,\n        sample_count: 1,\n        dimension: wgpu::TextureDimension::D2,\n        format,\n        usage: wgpu::TextureUsage::COPY_DST | wgpu::TextureUsage::SAMPLED,\n    }\n}\n\npub fn create_texture<'a>(\n    device: &wgpu::Device,\n    queue: &wgpu::Queue,\n    label: Option<&'a str>,\n    width: u32,\n    height: u32,\n    data: &TextureData,\n) -> wgpu::Texture {\n    trace!(\n        \"Creating texture ({:?}: {}x{})\",\n        data.format(),\n        width,\n        height\n    );\n    let texture = device.create_texture(&texture_descriptor(label, width, height, data.format()));\n    queue.write_texture(\n        wgpu::ImageCopyTexture {\n            texture: &texture,\n            mip_level: 0,\n            origin: wgpu::Origin3d::ZERO,\n        },\n        data.data(),\n        wgpu::ImageDataLayout {\n            offset: 0,\n            bytes_per_row: NonZeroU32::new(width * data.stride()),\n            rows_per_image: None,\n        },\n        wgpu::Extent3d {\n            width,\n            height,\n            depth_or_array_layers: 1,\n        },\n    );\n\n    texture\n}\n\npub struct DiffuseData<'a> {\n    pub rgba: Cow<'a, [u8]>,\n}\n\npub struct FullbrightData<'a> {\n    pub fullbright: Cow<'a, [u8]>,\n}\n\npub struct LightmapData<'a> {\n    pub lightmap: Cow<'a, [u8]>,\n}\n\npub enum TextureData<'a> {\n    Diffuse(DiffuseData<'a>),\n    Fullbright(FullbrightData<'a>),\n    Lightmap(LightmapData<'a>),\n}\n\nimpl<'a> TextureData<'a> {\n    pub fn format(&self) -> wgpu::TextureFormat {\n        match self {\n            TextureData::Diffuse(_) => DIFFUSE_TEXTURE_FORMAT,\n            TextureData::Fullbright(_) => FULLBRIGHT_TEXTURE_FORMAT,\n            TextureData::Lightmap(_) => LIGHTMAP_TEXTURE_FORMAT,\n        }\n    }\n\n    pub fn data(&self) -> &[u8] {\n        match self {\n            TextureData::Diffuse(d) => &d.rgba,\n            TextureData::Fullbright(d) => &d.fullbright,\n            TextureData::Lightmap(d) => &d.lightmap,\n        }\n    }\n\n    pub fn stride(&self) -> u32 {\n        (match self {\n            TextureData::Diffuse(_) => size_of::<[u8; 4]>(),\n            TextureData::Fullbright(_) => size_of::<u8>(),\n            TextureData::Lightmap(_) => size_of::<u8>(),\n        }) as u32\n    }\n\n    pub fn size(&self) -> wgpu::BufferAddress {\n        self.data().len() as wgpu::BufferAddress\n    }\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub struct Extent2d {\n    pub width: u32,\n    pub height: u32,\n}\n\nimpl std::convert::Into<wgpu::Extent3d> for Extent2d {\n    fn into(self) -> wgpu::Extent3d {\n        wgpu::Extent3d {\n            width: self.width,\n            height: self.height,\n            depth_or_array_layers: 1,\n        }\n    }\n}\n\nimpl std::convert::From<winit::dpi::PhysicalSize<u32>> for Extent2d {\n    fn from(other: winit::dpi::PhysicalSize<u32>) -> Extent2d {\n        let winit::dpi::PhysicalSize { width, height } = other;\n        Extent2d { width, height }\n    }\n}\n\npub struct GraphicsState {\n    device: wgpu::Device,\n    queue: wgpu::Queue,\n\n    initial_pass_target: InitialPassTarget,\n    deferred_pass_target: DeferredPassTarget,\n    final_pass_target: FinalPassTarget,\n\n    world_bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    world_bind_groups: Vec<wgpu::BindGroup>,\n\n    frame_uniform_buffer: wgpu::Buffer,\n\n    entity_uniform_buffer: RefCell<DynamicUniformBuffer<EntityUniforms>>,\n    diffuse_sampler: wgpu::Sampler,\n    lightmap_sampler: wgpu::Sampler,\n\n    sample_count: Cell<u32>,\n\n    alias_pipeline: AliasPipeline,\n    brush_pipeline: BrushPipeline,\n    sprite_pipeline: SpritePipeline,\n    deferred_pipeline: DeferredPipeline,\n    particle_pipeline: ParticlePipeline,\n    postprocess_pipeline: PostProcessPipeline,\n    glyph_pipeline: GlyphPipeline,\n    quad_pipeline: QuadPipeline,\n    blit_pipeline: BlitPipeline,\n\n    default_lightmap: wgpu::Texture,\n    default_lightmap_view: wgpu::TextureView,\n\n    vfs: Rc<Vfs>,\n    palette: Palette,\n    gfx_wad: Wad,\n    compiler: RefCell<shaderc::Compiler>,\n}\n\nimpl GraphicsState {\n    pub fn new(\n        device: wgpu::Device,\n        queue: wgpu::Queue,\n        size: Extent2d,\n        sample_count: u32,\n        vfs: Rc<Vfs>,\n    ) -> Result<GraphicsState, Error> {\n        let palette = Palette::load(&vfs, \"gfx/palette.lmp\");\n        let gfx_wad = Wad::load(vfs.open(\"gfx.wad\")?).unwrap();\n        let mut compiler = shaderc::Compiler::new().unwrap();\n\n        let initial_pass_target = InitialPassTarget::new(&device, size, sample_count);\n        let deferred_pass_target = DeferredPassTarget::new(&device, size, sample_count);\n        let final_pass_target = FinalPassTarget::new(&device, size, sample_count);\n\n        let frame_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {\n            label: Some(\"frame uniform buffer\"),\n            size: size_of::<world::FrameUniforms>() as wgpu::BufferAddress,\n            usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,\n            mapped_at_creation: false,\n        });\n        let entity_uniform_buffer = RefCell::new(DynamicUniformBuffer::new(&device));\n\n        let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {\n            label: None,\n            address_mode_u: wgpu::AddressMode::Repeat,\n            address_mode_v: wgpu::AddressMode::Repeat,\n            address_mode_w: wgpu::AddressMode::Repeat,\n            mag_filter: wgpu::FilterMode::Nearest,\n            min_filter: wgpu::FilterMode::Linear,\n            mipmap_filter: wgpu::FilterMode::Nearest,\n            // TODO: these are the OpenGL defaults; see if there's a better choice for us\n            lod_min_clamp: -1000.0,\n            lod_max_clamp: 1000.0,\n            compare: None,\n            anisotropy_clamp: NonZeroU8::new(16),\n            ..Default::default()\n        });\n\n        let lightmap_sampler = device.create_sampler(&wgpu::SamplerDescriptor {\n            label: None,\n            address_mode_u: wgpu::AddressMode::ClampToEdge,\n            address_mode_v: wgpu::AddressMode::ClampToEdge,\n            address_mode_w: wgpu::AddressMode::ClampToEdge,\n            mag_filter: wgpu::FilterMode::Linear,\n            min_filter: wgpu::FilterMode::Linear,\n            mipmap_filter: wgpu::FilterMode::Nearest,\n            // TODO: these are the OpenGL defaults; see if there's a better choice for us\n            lod_min_clamp: -1000.0,\n            lod_max_clamp: 1000.0,\n            compare: None,\n            anisotropy_clamp: NonZeroU8::new(16),\n            ..Default::default()\n        });\n\n        let world_bind_group_layouts: Vec<wgpu::BindGroupLayout> =\n            world::BIND_GROUP_LAYOUT_DESCRIPTORS\n                .iter()\n                .map(|desc| device.create_bind_group_layout(desc))\n                .collect();\n        let world_bind_groups = vec![\n            device.create_bind_group(&wgpu::BindGroupDescriptor {\n                label: Some(\"per-frame bind group\"),\n                layout: &world_bind_group_layouts[world::BindGroupLayoutId::PerFrame as usize],\n                entries: &[wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {\n                        buffer: &frame_uniform_buffer,\n                        offset: 0,\n                        size: None,\n                    }),\n                }],\n            }),\n            device.create_bind_group(&wgpu::BindGroupDescriptor {\n                label: Some(\"brush per-entity bind group\"),\n                layout: &world_bind_group_layouts[world::BindGroupLayoutId::PerEntity as usize],\n                entries: &[\n                    wgpu::BindGroupEntry {\n                        binding: 0,\n                        resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {\n                            buffer: &entity_uniform_buffer.borrow().buffer(),\n                            offset: 0,\n                            size: Some(\n                                NonZeroU64::new(size_of::<EntityUniforms>() as u64).unwrap(),\n                            ),\n                        }),\n                    },\n                    wgpu::BindGroupEntry {\n                        binding: 1,\n                        resource: wgpu::BindingResource::Sampler(&diffuse_sampler),\n                    },\n                    wgpu::BindGroupEntry {\n                        binding: 2,\n                        resource: wgpu::BindingResource::Sampler(&lightmap_sampler),\n                    },\n                ],\n            }),\n        ];\n\n        let alias_pipeline = AliasPipeline::new(\n            &device,\n            &mut compiler,\n            &world_bind_group_layouts,\n            sample_count,\n        );\n        let brush_pipeline = BrushPipeline::new(\n            &device,\n            &queue,\n            &mut compiler,\n            &world_bind_group_layouts,\n            sample_count,\n        );\n        let sprite_pipeline = SpritePipeline::new(\n            &device,\n            &mut compiler,\n            &world_bind_group_layouts,\n            sample_count,\n        );\n        let deferred_pipeline = DeferredPipeline::new(&device, &mut compiler, sample_count);\n        let particle_pipeline =\n            ParticlePipeline::new(&device, &queue, &mut compiler, sample_count, &palette);\n        let postprocess_pipeline = PostProcessPipeline::new(&device, &mut compiler, sample_count);\n        let quad_pipeline = QuadPipeline::new(&device, &mut compiler, sample_count);\n        let glyph_pipeline = GlyphPipeline::new(&device, &mut compiler, sample_count);\n        let blit_pipeline =\n            BlitPipeline::new(&device, &mut compiler, final_pass_target.resolve_view());\n\n        let default_lightmap = create_texture(\n            &device,\n            &queue,\n            None,\n            1,\n            1,\n            &TextureData::Lightmap(LightmapData {\n                lightmap: (&[0xFF][..]).into(),\n            }),\n        );\n        let default_lightmap_view = default_lightmap.create_view(&Default::default());\n\n        Ok(GraphicsState {\n            device,\n            queue,\n            initial_pass_target,\n            deferred_pass_target,\n            final_pass_target,\n            frame_uniform_buffer,\n            entity_uniform_buffer,\n\n            world_bind_group_layouts,\n            world_bind_groups,\n\n            sample_count: Cell::new(sample_count),\n\n            alias_pipeline,\n            brush_pipeline,\n            sprite_pipeline,\n            deferred_pipeline,\n            particle_pipeline,\n            postprocess_pipeline,\n            glyph_pipeline,\n            quad_pipeline,\n            blit_pipeline,\n\n            diffuse_sampler,\n            lightmap_sampler,\n            default_lightmap,\n            default_lightmap_view,\n            vfs,\n            palette,\n            gfx_wad,\n            compiler: RefCell::new(compiler),\n        })\n    }\n\n    pub fn create_texture<'a>(\n        &self,\n        label: Option<&'a str>,\n        width: u32,\n        height: u32,\n        data: &TextureData,\n    ) -> wgpu::Texture {\n        create_texture(&self.device, &self.queue, label, width, height, data)\n    }\n\n    /// Update graphics state with the new framebuffer size and sample count.\n    ///\n    /// If the framebuffer size has changed, this recreates all render targets with the new size.\n    ///\n    /// If the framebuffer sample count has changed, this recreates all render targets with the\n    /// new sample count and rebuilds the render pipelines to output that number of samples.\n    pub fn update(&mut self, size: Extent2d, sample_count: u32) {\n        if self.sample_count.get() != sample_count {\n            self.sample_count.set(sample_count);\n            self.recreate_pipelines(sample_count);\n        }\n\n        if self.initial_pass_target.size() != size\n            || self.initial_pass_target.sample_count() != sample_count\n        {\n            self.initial_pass_target = InitialPassTarget::new(self.device(), size, sample_count);\n        }\n\n        if self.deferred_pass_target.size() != size\n            || self.deferred_pass_target.sample_count() != sample_count\n        {\n            self.deferred_pass_target = DeferredPassTarget::new(self.device(), size, sample_count);\n        }\n\n        if self.final_pass_target.size() != size\n            || self.final_pass_target.sample_count() != sample_count\n        {\n            self.final_pass_target = FinalPassTarget::new(self.device(), size, sample_count);\n            self.blit_pipeline.rebuild(\n                &self.device,\n                &mut *self.compiler.borrow_mut(),\n                self.final_pass_target.resolve_view(),\n            )\n        }\n    }\n\n    /// Rebuild all render pipelines using the new sample count.\n    ///\n    /// This must be called when the sample count of the render target(s) changes or the program\n    /// will panic.\n    fn recreate_pipelines(&mut self, sample_count: u32) {\n        self.alias_pipeline.rebuild(\n            &self.device,\n            &mut self.compiler.borrow_mut(),\n            &self.world_bind_group_layouts,\n            sample_count,\n        );\n        self.brush_pipeline.rebuild(\n            &self.device,\n            &mut self.compiler.borrow_mut(),\n            &self.world_bind_group_layouts,\n            sample_count,\n        );\n        self.sprite_pipeline.rebuild(\n            &self.device,\n            &mut self.compiler.borrow_mut(),\n            &self.world_bind_group_layouts,\n            sample_count,\n        );\n        self.deferred_pipeline\n            .rebuild(&self.device, &mut self.compiler.borrow_mut(), sample_count);\n        self.postprocess_pipeline.rebuild(\n            &self.device,\n            &mut self.compiler.borrow_mut(),\n            sample_count,\n        );\n        self.glyph_pipeline\n            .rebuild(&self.device, &mut self.compiler.borrow_mut(), sample_count);\n        self.quad_pipeline\n            .rebuild(&self.device, &mut self.compiler.borrow_mut(), sample_count);\n        self.blit_pipeline.rebuild(\n            &self.device,\n            &mut self.compiler.borrow_mut(),\n            self.final_pass_target.resolve_view(),\n        );\n    }\n\n    pub fn device(&self) -> &wgpu::Device {\n        &self.device\n    }\n\n    pub fn queue(&self) -> &wgpu::Queue {\n        &self.queue\n    }\n\n    pub fn initial_pass_target(&self) -> &InitialPassTarget {\n        &self.initial_pass_target\n    }\n\n    pub fn deferred_pass_target(&self) -> &DeferredPassTarget {\n        &self.deferred_pass_target\n    }\n\n    pub fn final_pass_target(&self) -> &FinalPassTarget {\n        &self.final_pass_target\n    }\n\n    pub fn frame_uniform_buffer(&self) -> &wgpu::Buffer {\n        &self.frame_uniform_buffer\n    }\n\n    pub fn entity_uniform_buffer(&self) -> Ref<DynamicUniformBuffer<EntityUniforms>> {\n        self.entity_uniform_buffer.borrow()\n    }\n\n    pub fn entity_uniform_buffer_mut(&self) -> RefMut<DynamicUniformBuffer<EntityUniforms>> {\n        self.entity_uniform_buffer.borrow_mut()\n    }\n\n    pub fn diffuse_sampler(&self) -> &wgpu::Sampler {\n        &self.diffuse_sampler\n    }\n\n    pub fn default_lightmap(&self) -> &wgpu::Texture {\n        &self.default_lightmap\n    }\n\n    pub fn default_lightmap_view(&self) -> &wgpu::TextureView {\n        &self.default_lightmap_view\n    }\n\n    pub fn lightmap_sampler(&self) -> &wgpu::Sampler {\n        &self.lightmap_sampler\n    }\n\n    pub fn world_bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.world_bind_group_layouts\n    }\n\n    pub fn world_bind_groups(&self) -> &[wgpu::BindGroup] {\n        &self.world_bind_groups\n    }\n\n    // pipelines\n\n    pub fn alias_pipeline(&self) -> &AliasPipeline {\n        &self.alias_pipeline\n    }\n\n    pub fn brush_pipeline(&self) -> &BrushPipeline {\n        &self.brush_pipeline\n    }\n\n    pub fn sprite_pipeline(&self) -> &SpritePipeline {\n        &self.sprite_pipeline\n    }\n\n    pub fn deferred_pipeline(&self) -> &DeferredPipeline {\n        &self.deferred_pipeline\n    }\n\n    pub fn particle_pipeline(&self) -> &ParticlePipeline {\n        &self.particle_pipeline\n    }\n\n    pub fn postprocess_pipeline(&self) -> &PostProcessPipeline {\n        &self.postprocess_pipeline\n    }\n\n    pub fn glyph_pipeline(&self) -> &GlyphPipeline {\n        &self.glyph_pipeline\n    }\n\n    pub fn quad_pipeline(&self) -> &QuadPipeline {\n        &self.quad_pipeline\n    }\n\n    pub fn blit_pipeline(&self) -> &BlitPipeline {\n        &self.blit_pipeline\n    }\n\n    pub fn vfs(&self) -> &Vfs {\n        &self.vfs\n    }\n\n    pub fn palette(&self) -> &Palette {\n        &self.palette\n    }\n\n    pub fn gfx_wad(&self) -> &Wad {\n        &self.gfx_wad\n    }\n}\n\npub struct ClientRenderer {\n    deferred_renderer: DeferredRenderer,\n    postprocess_renderer: PostProcessRenderer,\n    ui_renderer: UiRenderer,\n    bump: Bump,\n    start_time: DateTime<Utc>,\n}\n\nimpl ClientRenderer {\n    pub fn new(state: &GraphicsState, menu: &Menu) -> ClientRenderer {\n        ClientRenderer {\n            deferred_renderer: DeferredRenderer::new(\n                state,\n                state.initial_pass_target.diffuse_view(),\n                state.initial_pass_target.normal_view(),\n                state.initial_pass_target.light_view(),\n                state.initial_pass_target.depth_view(),\n            ),\n            postprocess_renderer: PostProcessRenderer::new(\n                state,\n                state.deferred_pass_target.color_view(),\n            ),\n            ui_renderer: UiRenderer::new(state, menu),\n            bump: Bump::new(),\n            start_time: Utc::now(),\n        }\n    }\n\n    pub fn render(\n        &mut self,\n        gfx_state: &GraphicsState,\n        encoder: &mut wgpu::CommandEncoder,\n        conn: Option<&Connection>,\n        width: u32,\n        height: u32,\n        fov: Deg<f32>,\n        cvars: &CvarRegistry,\n        console: &Console,\n        menu: &Menu,\n        focus: InputFocus,\n    ) {\n        self.bump.reset();\n\n        if let Some(Connection {\n            state: ref cl_state,\n            ref conn_state,\n            ref kind,\n        }) = conn\n        {\n            match conn_state {\n                ConnectionState::Connected(ref world) => {\n                    // if client is fully connected, draw world\n                    let camera = match kind {\n                        ConnectionKind::Demo(_) => {\n                            cl_state.demo_camera(width as f32 / height as f32, fov)\n                        }\n                        ConnectionKind::Server { .. } => {\n                            cl_state.camera(width as f32 / height as f32, fov)\n                        }\n                    };\n\n                    // initial render pass\n                    {\n                        let init_pass_builder =\n                            gfx_state.initial_pass_target().render_pass_builder();\n\n                        let mut init_pass =\n                            encoder.begin_render_pass(&init_pass_builder.descriptor());\n\n                        world.render_pass(\n                            gfx_state,\n                            &mut init_pass,\n                            &self.bump,\n                            &camera,\n                            cl_state.time(),\n                            cl_state.iter_visible_entities(),\n                            cl_state.iter_particles(),\n                            cl_state.lightstyle_values().unwrap().as_slice(),\n                            cl_state.viewmodel_id(),\n                            cvars,\n                        );\n                    }\n\n                    // deferred lighting pass\n                    {\n                        let deferred_pass_builder =\n                            gfx_state.deferred_pass_target().render_pass_builder();\n                        let mut deferred_pass =\n                            encoder.begin_render_pass(&deferred_pass_builder.descriptor());\n\n                        let mut lights = [PointLight {\n                            origin: Vector3::zero(),\n                            radius: 0.0,\n                        }; MAX_LIGHTS];\n\n                        let mut light_count = 0;\n                        for (light_id, light) in cl_state.iter_lights().enumerate() {\n                            light_count += 1;\n                            let light_origin = light.origin();\n                            let converted_origin =\n                                Vector3::new(-light_origin.y, light_origin.z, -light_origin.x);\n                            lights[light_id].origin =\n                                (camera.view() * converted_origin.extend(1.0)).truncate();\n                            lights[light_id].radius = light.radius(cl_state.time());\n                        }\n\n                        let uniforms = DeferredUniforms {\n                            inv_projection: camera.inverse_projection().into(),\n                            light_count,\n                            _pad: [0; 3],\n                            lights,\n                        };\n\n                        self.deferred_renderer.rebuild(\n                            gfx_state,\n                            gfx_state.initial_pass_target().diffuse_view(),\n                            gfx_state.initial_pass_target().normal_view(),\n                            gfx_state.initial_pass_target().light_view(),\n                            gfx_state.initial_pass_target().depth_view(),\n                        );\n\n                        self.deferred_renderer\n                            .record_draw(gfx_state, &mut deferred_pass, uniforms);\n                    }\n                }\n\n                // if client is still signing on, draw the loading screen\n                ConnectionState::SignOn(_) => {\n                    // TODO: loading screen\n                }\n            }\n        }\n\n        let ui_state = match conn {\n            Some(Connection {\n                state: ref cl_state,\n                ..\n            }) => UiState::InGame {\n                hud: match cl_state.intermission() {\n                    Some(kind) => HudState::Intermission {\n                        kind,\n                        completion_duration: cl_state.completion_time().unwrap()\n                            - cl_state.start_time(),\n                        stats: cl_state.stats(),\n                        console,\n                    },\n\n                    None => HudState::InGame {\n                        items: cl_state.items(),\n                        item_pickup_time: cl_state.item_pickup_times(),\n                        stats: cl_state.stats(),\n                        face_anim_time: cl_state.face_anim_time(),\n                        console,\n                    },\n                },\n\n                overlay: match focus {\n                    InputFocus::Game => None,\n                    InputFocus::Console => Some(UiOverlay::Console(console)),\n                    InputFocus::Menu => Some(UiOverlay::Menu(menu)),\n                },\n            },\n\n            None => UiState::Title {\n                overlay: match focus {\n                    InputFocus::Console => UiOverlay::Console(console),\n                    InputFocus::Menu => UiOverlay::Menu(menu),\n                    InputFocus::Game => unreachable!(),\n                },\n            },\n        };\n\n        // final render pass: postprocess the world and draw the UI\n        {\n            // quad_commands must outlive final pass\n            let mut quad_commands = Vec::new();\n            let mut glyph_commands = Vec::new();\n\n            let final_pass_builder = gfx_state.final_pass_target().render_pass_builder();\n            let mut final_pass = encoder.begin_render_pass(&final_pass_builder.descriptor());\n\n            if let Some(Connection {\n                state: ref cl_state,\n                ref conn_state,\n                ..\n            }) = conn\n            {\n                // only postprocess if client is in the game\n                if let ConnectionState::Connected(_) = conn_state {\n                    self.postprocess_renderer\n                        .rebuild(gfx_state, gfx_state.deferred_pass_target.color_view());\n                    self.postprocess_renderer.record_draw(\n                        gfx_state,\n                        &mut final_pass,\n                        cl_state.color_shift(),\n                    );\n                }\n            }\n\n            self.ui_renderer.render_pass(\n                &gfx_state,\n                &mut final_pass,\n                Extent2d { width, height },\n                // use client time when in game, renderer time otherwise\n                match conn {\n                    Some(Connection { ref state, .. }) => state.time,\n                    None => Utc::now().signed_duration_since(self.start_time),\n                },\n                &ui_state,\n                &mut quad_commands,\n                &mut glyph_commands,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/palette.rs",
    "content": "use std::{borrow::Cow, io::BufReader};\n\nuse crate::{\n    client::render::{DiffuseData, FullbrightData},\n    common::vfs::Vfs,\n};\n\nuse byteorder::ReadBytesExt;\n\npub struct Palette {\n    rgb: [[u8; 3]; 256],\n}\n\nimpl Palette {\n    pub fn new(data: &[u8]) -> Palette {\n        if data.len() != 768 {\n            panic!(\"Bad len for rgb data\");\n        }\n\n        let mut rgb = [[0; 3]; 256];\n        for color in 0..256 {\n            for component in 0..3 {\n                rgb[color][component] = data[color * 3 + component];\n            }\n        }\n\n        Palette { rgb }\n    }\n\n    pub fn load<S>(vfs: &Vfs, path: S) -> Palette\n    where\n        S: AsRef<str>,\n    {\n        let mut data = BufReader::new(vfs.open(path).unwrap());\n\n        let mut rgb = [[0u8; 3]; 256];\n\n        for color in 0..256 {\n            for component in 0..3 {\n                rgb[color][component] = data.read_u8().unwrap();\n            }\n        }\n\n        Palette { rgb }\n    }\n\n    // TODO: this will not render console characters correctly, as they use index 0 (black) to\n    // indicate transparency.\n    /// Translates a set of indices into a list of RGBA values and a list of fullbright values.\n    pub fn translate(&self, indices: &[u8]) -> (DiffuseData, FullbrightData) {\n        let mut rgba = Vec::with_capacity(indices.len() * 4);\n        let mut fullbright = Vec::with_capacity(indices.len());\n\n        for index in indices {\n            match *index {\n                0xFF => {\n                    for _ in 0..4 {\n                        rgba.push(0);\n                        fullbright.push(0);\n                    }\n                }\n\n                i => {\n                    for component in 0..3 {\n                        rgba.push(self.rgb[*index as usize][component]);\n                    }\n                    rgba.push(0xFF);\n                    fullbright.push(if i > 223 { 0xFF } else { 0 });\n                }\n            }\n        }\n\n        (\n            DiffuseData {\n                rgba: Cow::Owned(rgba),\n            },\n            FullbrightData {\n                fullbright: Cow::Owned(fullbright),\n            },\n        )\n    }\n}\n"
  },
  {
    "path": "src/client/render/pipeline.rs",
    "content": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::mem::size_of;\n\nuse crate::common::util::{any_as_bytes, Pod};\n\n/// The `Pipeline` trait, which allows render pipelines to be defined more-or-less declaratively.\n\nfn create_shader<S>(\n    device: &wgpu::Device,\n    compiler: &mut shaderc::Compiler,\n    name: S,\n    kind: shaderc::ShaderKind,\n    source: S,\n) -> wgpu::ShaderModule\nwhere\n    S: AsRef<str>,\n{\n    log::debug!(\"creating shader {}\", name.as_ref());\n    let spirv = compiler\n        .compile_into_spirv(source.as_ref(), kind, name.as_ref(), \"main\", None)\n        .unwrap();\n    device.create_shader_module(&wgpu::ShaderModuleDescriptor {\n        label: Some(name.as_ref()),\n        source: wgpu::ShaderSource::SpirV(spirv.as_binary().into()),\n        flags: wgpu::ShaderFlags::empty(),\n    })\n}\n\npub enum PushConstantUpdate<T> {\n    /// Update the push constant to a new value.\n    Update(T),\n    /// Retain the current value of the push constant.\n    Retain,\n    /// Clear the push constant to no value.\n    Clear,\n}\n\n/// A trait describing the behavior of a render pipeline.\n///\n/// This trait's methods are used to define the pipeline's behavior in a more-or-less declarative\n/// style, leaving the actual creation to the default implementation of `Pipeline::create()`.\npub trait Pipeline {\n    /// Push constants used for the vertex stage of the pipeline.\n    type VertexPushConstants: Pod;\n\n    /// Push constants shared between the vertex and fragment stages of the pipeline.\n    type SharedPushConstants: Pod;\n\n    /// Push constants used for the fragment stage of the pipeline.\n    type FragmentPushConstants: Pod;\n\n    /// The name of this pipeline.\n    fn name() -> &'static str;\n\n    /// The `BindGroupLayoutDescriptor`s describing the bindings used in the pipeline.\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>>;\n\n    /// The GLSL source of the pipeline's vertex shader.\n    fn vertex_shader() -> &'static str;\n\n    /// The GLSL source of the pipeline's fragment shader.\n    fn fragment_shader() -> &'static str;\n\n    /// The primitive state used for rasterization in this pipeline.\n    fn primitive_state() -> wgpu::PrimitiveState;\n\n    /// The color state used for the pipeline.\n    fn color_target_states() -> Vec<wgpu::ColorTargetState>;\n\n    /// The depth-stencil state used for the pipeline, if any.\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState>;\n\n    /// Descriptors for the vertex buffers used by the pipeline.\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>>;\n\n    fn vertex_push_constant_range() -> wgpu::PushConstantRange {\n        let range = wgpu::PushConstantRange {\n            stages: wgpu::ShaderStage::VERTEX,\n            range: 0..size_of::<Self::VertexPushConstants>() as u32\n                + size_of::<Self::SharedPushConstants>() as u32,\n        };\n        debug!(\"vertex push constant range: {:#?}\", &range);\n        range\n    }\n\n    fn fragment_push_constant_range() -> wgpu::PushConstantRange {\n        let range = wgpu::PushConstantRange {\n            stages: wgpu::ShaderStage::FRAGMENT,\n            range: size_of::<Self::VertexPushConstants>() as u32\n                ..size_of::<Self::VertexPushConstants>() as u32\n                    + size_of::<Self::SharedPushConstants>() as u32\n                    + size_of::<Self::FragmentPushConstants>() as u32,\n        };\n        debug!(\"fragment push constant range: {:#?}\", &range);\n        range\n    }\n\n    fn push_constant_ranges() -> Vec<wgpu::PushConstantRange> {\n        let vpc_size = size_of::<Self::VertexPushConstants>();\n        let spc_size = size_of::<Self::SharedPushConstants>();\n        let fpc_size = size_of::<Self::FragmentPushConstants>();\n\n        match (vpc_size, spc_size, fpc_size) {\n            (0, 0, 0) => Vec::new(),\n            (_, 0, 0) => vec![Self::vertex_push_constant_range()],\n            (0, 0, _) => vec![Self::fragment_push_constant_range()],\n            _ => vec![\n                Self::vertex_push_constant_range(),\n                Self::fragment_push_constant_range(),\n            ],\n        }\n    }\n\n    /// Ensures that the associated push constant types have the proper size and\n    /// alignment.\n    fn validate_push_constant_types(limits: wgpu::Limits) {\n        let pc_alignment = wgpu::PUSH_CONSTANT_ALIGNMENT as usize;\n        let max_pc_size = limits.max_push_constant_size as usize;\n        let vpc_size = size_of::<Self::VertexPushConstants>();\n        let spc_size = size_of::<Self::SharedPushConstants>();\n        let fpc_size = size_of::<Self::FragmentPushConstants>();\n        assert_eq!(\n            vpc_size % pc_alignment,\n            0,\n            \"Vertex push constant size must be a multiple of {} bytes\",\n            wgpu::PUSH_CONSTANT_ALIGNMENT,\n        );\n        assert_eq!(\n            spc_size % pc_alignment,\n            0,\n            \"Shared push constant size must be a multiple of {} bytes\",\n            wgpu::PUSH_CONSTANT_ALIGNMENT,\n        );\n        assert_eq!(\n            fpc_size % pc_alignment,\n            0,\n            \"Fragment push constant size must be a multiple of {} bytes\",\n            wgpu::PUSH_CONSTANT_ALIGNMENT,\n        );\n        assert!(\n            vpc_size + spc_size + fpc_size < max_pc_size,\n            \"Combined size of push constants must be less than push constant size limit of {}\",\n            max_pc_size\n        );\n    }\n\n    /// Constructs a `RenderPipeline` and a list of `BindGroupLayout`s from the associated methods.\n    ///\n    /// `bind_group_layout_prefix` specifies a list of `BindGroupLayout`s to be prefixed onto those\n    /// created from this pipeline's `bind_group_layout_descriptors()` method when creating the\n    /// `RenderPipeline`. This permits the reuse of `BindGroupLayout`s between pipelines.\n    fn create(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        bind_group_layout_prefix: &[wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) -> (wgpu::RenderPipeline, Vec<wgpu::BindGroupLayout>) {\n        Self::validate_push_constant_types(device.limits());\n\n        info!(\"Creating {} pipeline\", Self::name());\n        let bind_group_layouts = Self::bind_group_layout_descriptors()\n            .iter()\n            .map(|desc| device.create_bind_group_layout(desc))\n            .collect::<Vec<_>>();\n        info!(\n            \"{} layouts in prefix | {} specific to pipeline\",\n            bind_group_layout_prefix.len(),\n            bind_group_layouts.len(),\n        );\n\n        let pipeline_layout = {\n            // add bind group layout prefix\n            let layouts: Vec<&wgpu::BindGroupLayout> = bind_group_layout_prefix\n                .iter()\n                .chain(bind_group_layouts.iter())\n                .collect();\n            info!(\"{} layouts total\", layouts.len());\n            let ranges = Self::push_constant_ranges();\n            let label = format!(\"{} pipeline layout\", Self::name());\n            let desc = wgpu::PipelineLayoutDescriptor {\n                label: Some(&label),\n                bind_group_layouts: &layouts,\n                push_constant_ranges: &ranges,\n            };\n            device.create_pipeline_layout(&desc)\n        };\n\n        let vertex_shader = create_shader(\n            device,\n            compiler,\n            format!(\"{}.vert\", Self::name()).as_str(),\n            shaderc::ShaderKind::Vertex,\n            Self::vertex_shader(),\n        );\n        let fragment_shader = create_shader(\n            device,\n            compiler,\n            format!(\"{}.frag\", Self::name()).as_str(),\n            shaderc::ShaderKind::Fragment,\n            Self::fragment_shader(),\n        );\n\n        info!(\"create_render_pipeline\");\n        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {\n            label: Some(&format!(\"{} pipeline\", Self::name())),\n            layout: Some(&pipeline_layout),\n            vertex: wgpu::VertexState {\n                module: &vertex_shader,\n                entry_point: \"main\",\n                buffers: &Self::vertex_buffer_layouts(),\n            },\n            primitive: Self::primitive_state(),\n            fragment: Some(wgpu::FragmentState {\n                module: &fragment_shader,\n                entry_point: \"main\",\n                targets: &Self::color_target_states(),\n            }),\n            multisample: wgpu::MultisampleState {\n                count: sample_count,\n                mask: !0,\n                alpha_to_coverage_enabled: false,\n            },\n            depth_stencil: Self::depth_stencil_state(),\n        });\n\n        (pipeline, bind_group_layouts)\n    }\n\n    /// Reconstructs the pipeline using its original bind group layouts and a new sample count.\n    ///\n    /// Pipelines must be reconstructed when the MSAA sample count is changed.\n    fn recreate(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        bind_group_layouts: &[&wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) -> wgpu::RenderPipeline {\n        Self::validate_push_constant_types(device.limits());\n\n        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {\n            label: Some(&format!(\"{} pipeline layout\", Self::name())),\n            bind_group_layouts,\n            push_constant_ranges: &[\n                Self::vertex_push_constant_range(),\n                Self::fragment_push_constant_range(),\n            ],\n        });\n        let vertex_shader = create_shader(\n            device,\n            compiler,\n            format!(\"{}.vert\", Self::name()).as_str(),\n            shaderc::ShaderKind::Vertex,\n            Self::vertex_shader(),\n        );\n        let fragment_shader = create_shader(\n            device,\n            compiler,\n            format!(\"{}.frag\", Self::name()).as_str(),\n            shaderc::ShaderKind::Fragment,\n            Self::fragment_shader(),\n        );\n        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {\n            label: Some(&format!(\"{} pipeline\", Self::name())),\n            layout: Some(&pipeline_layout),\n            vertex: wgpu::VertexState {\n                module: &vertex_shader,\n                entry_point: \"main\",\n                buffers: &Self::vertex_buffer_layouts(),\n            },\n            primitive: Self::primitive_state(),\n            fragment: Some(wgpu::FragmentState {\n                module: &fragment_shader,\n                entry_point: \"main\",\n                targets: &Self::color_target_states(),\n            }),\n            multisample: wgpu::MultisampleState {\n                count: sample_count,\n                mask: !0,\n                alpha_to_coverage_enabled: false,\n            },\n            depth_stencil: Self::depth_stencil_state(),\n        });\n\n        pipeline\n    }\n\n    /// Set the push constant data for a render pass.\n    ///\n    /// For each argument, if the value is `Some`, then the corresponding push\n    /// constant range is updated. If the value is `None`, the corresponding push\n    /// constant range is cleared.\n    fn set_push_constants<'a>(\n        pass: &mut wgpu::RenderPass<'a>,\n        vpc: PushConstantUpdate<&'a Self::VertexPushConstants>,\n        spc: PushConstantUpdate<&'a Self::SharedPushConstants>,\n        fpc: PushConstantUpdate<&'a Self::FragmentPushConstants>,\n    ) {\n        use PushConstantUpdate::*;\n\n        let vpc_offset = 0;\n        let spc_offset = vpc_offset + size_of::<Self::VertexPushConstants>() as u32;\n        let fpc_offset = spc_offset + size_of::<Self::SharedPushConstants>() as u32;\n\n        // these push constant size checks are known statically and will be\n        // compiled out\n\n        if size_of::<Self::VertexPushConstants>() > 0 {\n            let data = match vpc {\n                Update(v) => Some(unsafe { any_as_bytes(v) }),\n                Retain => None,\n                Clear => Some(&[][..]),\n            };\n\n            if let Some(d) = data {\n                trace!(\n                    \"Update vertex push constants at offset {} with data {:?}\",\n                    vpc_offset,\n                    data\n                );\n\n                pass.set_push_constants(wgpu::ShaderStage::VERTEX, vpc_offset, d);\n            }\n        }\n\n        if size_of::<Self::SharedPushConstants>() > 0 {\n            let data = match spc {\n                Update(s) => Some(unsafe { any_as_bytes(s) }),\n                Retain => None,\n                Clear => Some(&[][..]),\n            };\n\n            if let Some(d) = data {\n                trace!(\n                    \"Update shared push constants at offset {} with data {:?}\",\n                    spc_offset,\n                    data\n                );\n\n                pass.set_push_constants(\n                    wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,\n                    spc_offset,\n                    d,\n                );\n            }\n        }\n\n        if size_of::<Self::FragmentPushConstants>() > 0 {\n            let data = match fpc {\n                Update(f) => Some(unsafe { any_as_bytes(f) }),\n                Retain => None,\n                Clear => Some(&[][..]),\n            };\n\n            if let Some(d) = data {\n                trace!(\n                    \"Update fragment push constants at offset {} with data {:?}\",\n                    fpc_offset,\n                    data\n                );\n\n                pass.set_push_constants(wgpu::ShaderStage::FRAGMENT, fpc_offset, d);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/target.rs",
    "content": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse crate::client::render::{\n    Extent2d, DEPTH_ATTACHMENT_FORMAT, DIFFUSE_ATTACHMENT_FORMAT, LIGHT_ATTACHMENT_FORMAT,\n    NORMAL_ATTACHMENT_FORMAT,\n};\n\n// TODO: collapse these into a single definition\n/// Create a texture suitable for use as a color attachment.\n///\n/// The resulting texture will have the RENDER_ATTACHMENT flag as well as\n/// any flags specified by `usage`.\npub fn create_color_attachment(\n    device: &wgpu::Device,\n    size: Extent2d,\n    sample_count: u32,\n    usage: wgpu::TextureUsage,\n) -> wgpu::Texture {\n    device.create_texture(&wgpu::TextureDescriptor {\n        label: Some(\"color attachment\"),\n        size: size.into(),\n        mip_level_count: 1,\n        sample_count,\n        dimension: wgpu::TextureDimension::D2,\n        format: DIFFUSE_ATTACHMENT_FORMAT,\n        usage: wgpu::TextureUsage::RENDER_ATTACHMENT | usage,\n    })\n}\n\n/// Create a texture suitable for use as a normal attachment.\n///\n/// The resulting texture will have the RENDER_ATTACHMENT flag as well as\n/// any flags specified by `usage`.\npub fn create_normal_attachment(\n    device: &wgpu::Device,\n    size: Extent2d,\n    sample_count: u32,\n    usage: wgpu::TextureUsage,\n) -> wgpu::Texture {\n    device.create_texture(&wgpu::TextureDescriptor {\n        label: Some(\"normal attachment\"),\n        size: size.into(),\n        mip_level_count: 1,\n        sample_count,\n        dimension: wgpu::TextureDimension::D2,\n        format: NORMAL_ATTACHMENT_FORMAT,\n        usage: wgpu::TextureUsage::RENDER_ATTACHMENT | usage,\n    })\n}\n\n/// Create a texture suitable for use as a light attachment.\n///\n/// The resulting texture will have the RENDER_ATTACHMENT flag as well as\n/// any flags specified by `usage`.\npub fn create_light_attachment(\n    device: &wgpu::Device,\n    size: Extent2d,\n    sample_count: u32,\n    usage: wgpu::TextureUsage,\n) -> wgpu::Texture {\n    device.create_texture(&wgpu::TextureDescriptor {\n        label: Some(\"light attachment\"),\n        size: size.into(),\n        mip_level_count: 1,\n        sample_count,\n        dimension: wgpu::TextureDimension::D2,\n        format: LIGHT_ATTACHMENT_FORMAT,\n        usage: wgpu::TextureUsage::RENDER_ATTACHMENT | usage,\n    })\n}\n\n/// Create a texture suitable for use as a depth attachment.\n///\n/// The underlying texture will have the RENDER_ATTACHMENT flag as well as\n/// any flags specified by `usage`.\npub fn create_depth_attachment(\n    device: &wgpu::Device,\n    size: Extent2d,\n    sample_count: u32,\n    usage: wgpu::TextureUsage,\n) -> wgpu::Texture {\n    device.create_texture(&wgpu::TextureDescriptor {\n        label: Some(\"depth attachment\"),\n        size: size.into(),\n        mip_level_count: 1,\n        sample_count,\n        dimension: wgpu::TextureDimension::D2,\n        format: DEPTH_ATTACHMENT_FORMAT,\n        usage: wgpu::TextureUsage::RENDER_ATTACHMENT | usage,\n    })\n}\n\n/// Intermediate object that can generate `RenderPassDescriptor`s.\npub struct RenderPassBuilder<'a> {\n    color_attachments: Vec<wgpu::RenderPassColorAttachment<'a>>,\n    depth_attachment: Option<wgpu::RenderPassDepthStencilAttachment<'a>>,\n}\n\nimpl<'a> RenderPassBuilder<'a> {\n    pub fn descriptor(&self) -> wgpu::RenderPassDescriptor {\n        wgpu::RenderPassDescriptor {\n            label: None,\n            color_attachments: &self.color_attachments,\n            depth_stencil_attachment: self.depth_attachment.clone(),\n        }\n    }\n}\n\n/// A trait describing a render target.\n///\n/// A render target consists of a series of color attachments and an optional depth-stencil\n/// attachment.\npub trait RenderTarget {\n    fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder<'a>;\n}\n\n/// A trait describing a render target with a built-in resolve attachment.\npub trait RenderTargetResolve: RenderTarget {\n    fn resolve_attachment(&self) -> &wgpu::Texture;\n    fn resolve_view(&self) -> &wgpu::TextureView;\n}\n\n// TODO: use ArrayVec<wgpu::TextureView> in concrete types so it can be passed\n// as Cow::Borrowed in RenderPassDescriptor\n\n/// Render target for the initial world pass.\npub struct InitialPassTarget {\n    size: Extent2d,\n    sample_count: u32,\n    diffuse_attachment: wgpu::Texture,\n    diffuse_view: wgpu::TextureView,\n    normal_attachment: wgpu::Texture,\n    normal_view: wgpu::TextureView,\n    light_attachment: wgpu::Texture,\n    light_view: wgpu::TextureView,\n    depth_attachment: wgpu::Texture,\n    depth_view: wgpu::TextureView,\n}\n\nimpl InitialPassTarget {\n    pub fn new(device: &wgpu::Device, size: Extent2d, sample_count: u32) -> InitialPassTarget {\n        let diffuse_attachment =\n            create_color_attachment(device, size, sample_count, wgpu::TextureUsage::SAMPLED);\n        let normal_attachment =\n            create_normal_attachment(device, size, sample_count, wgpu::TextureUsage::SAMPLED);\n        let light_attachment =\n            create_light_attachment(device, size, sample_count, wgpu::TextureUsage::SAMPLED);\n        let depth_attachment =\n            create_depth_attachment(device, size, sample_count, wgpu::TextureUsage::SAMPLED);\n\n        let diffuse_view = diffuse_attachment.create_view(&Default::default());\n        let normal_view = normal_attachment.create_view(&Default::default());\n        let light_view = light_attachment.create_view(&Default::default());\n        let depth_view = depth_attachment.create_view(&Default::default());\n\n        InitialPassTarget {\n            size,\n            sample_count,\n            diffuse_attachment,\n            diffuse_view,\n            normal_attachment,\n            normal_view,\n            light_attachment,\n            light_view,\n            depth_attachment,\n            depth_view,\n        }\n    }\n\n    pub fn size(&self) -> Extent2d {\n        self.size\n    }\n\n    pub fn sample_count(&self) -> u32 {\n        self.sample_count\n    }\n\n    pub fn diffuse_attachment(&self) -> &wgpu::Texture {\n        &self.diffuse_attachment\n    }\n\n    pub fn diffuse_view(&self) -> &wgpu::TextureView {\n        &self.diffuse_view\n    }\n\n    pub fn normal_attachment(&self) -> &wgpu::Texture {\n        &self.normal_attachment\n    }\n\n    pub fn normal_view(&self) -> &wgpu::TextureView {\n        &self.normal_view\n    }\n\n    pub fn light_attachment(&self) -> &wgpu::Texture {\n        &self.light_attachment\n    }\n\n    pub fn light_view(&self) -> &wgpu::TextureView {\n        &self.light_view\n    }\n\n    pub fn depth_attachment(&self) -> &wgpu::Texture {\n        &self.depth_attachment\n    }\n\n    pub fn depth_view(&self) -> &wgpu::TextureView {\n        &self.depth_view\n    }\n}\n\nimpl RenderTarget for InitialPassTarget {\n    fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder {\n        RenderPassBuilder {\n            color_attachments: vec![\n                wgpu::RenderPassColorAttachment {\n                    view: self.diffuse_view(),\n                    resolve_target: None,\n                    ops: wgpu::Operations {\n                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                        store: true,\n                    },\n                },\n                wgpu::RenderPassColorAttachment {\n                    view: self.normal_view(),\n                    resolve_target: None,\n                    ops: wgpu::Operations {\n                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                        store: true,\n                    },\n                },\n                wgpu::RenderPassColorAttachment {\n                    view: self.light_view(),\n                    resolve_target: None,\n                    ops: wgpu::Operations {\n                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                        store: true,\n                    },\n                },\n            ],\n            depth_attachment: Some(wgpu::RenderPassDepthStencilAttachment {\n                view: self.depth_view(),\n                depth_ops: Some(wgpu::Operations {\n                    load: wgpu::LoadOp::Clear(1.0),\n                    store: true,\n                }),\n                stencil_ops: None,\n            }),\n        }\n    }\n}\n\npub struct DeferredPassTarget {\n    size: Extent2d,\n    sample_count: u32,\n    color_attachment: wgpu::Texture,\n    color_view: wgpu::TextureView,\n}\n\nimpl DeferredPassTarget {\n    pub fn new(device: &wgpu::Device, size: Extent2d, sample_count: u32) -> DeferredPassTarget {\n        let color_attachment =\n            create_color_attachment(device, size, sample_count, wgpu::TextureUsage::SAMPLED);\n        let color_view = color_attachment.create_view(&Default::default());\n\n        DeferredPassTarget {\n            size,\n            sample_count,\n            color_attachment,\n            color_view,\n        }\n    }\n\n    pub fn size(&self) -> Extent2d {\n        self.size\n    }\n\n    pub fn sample_count(&self) -> u32 {\n        self.sample_count\n    }\n\n    pub fn color_attachment(&self) -> &wgpu::Texture {\n        &self.color_attachment\n    }\n\n    pub fn color_view(&self) -> &wgpu::TextureView {\n        &self.color_view\n    }\n}\n\nimpl RenderTarget for DeferredPassTarget {\n    fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder {\n        RenderPassBuilder {\n            color_attachments: vec![wgpu::RenderPassColorAttachment {\n                view: self.color_view(),\n                resolve_target: None,\n                ops: wgpu::Operations {\n                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                    store: true,\n                },\n            }],\n            depth_attachment: None,\n        }\n    }\n}\n\npub struct FinalPassTarget {\n    size: Extent2d,\n    sample_count: u32,\n    color_attachment: wgpu::Texture,\n    color_view: wgpu::TextureView,\n    resolve_attachment: wgpu::Texture,\n    resolve_view: wgpu::TextureView,\n}\n\nimpl FinalPassTarget {\n    pub fn new(device: &wgpu::Device, size: Extent2d, sample_count: u32) -> FinalPassTarget {\n        let color_attachment =\n            create_color_attachment(device, size, sample_count, wgpu::TextureUsage::empty());\n        let color_view = color_attachment.create_view(&Default::default());\n        // add COPY_SRC so we can copy to a buffer for capture and SAMPLED so we\n        // can blit to the swap chain\n        let resolve_attachment = create_color_attachment(\n            device,\n            size,\n            1,\n            wgpu::TextureUsage::COPY_SRC | wgpu::TextureUsage::SAMPLED,\n        );\n        let resolve_view = resolve_attachment.create_view(&Default::default());\n\n        FinalPassTarget {\n            size,\n            sample_count,\n            color_attachment,\n            color_view,\n            resolve_attachment,\n            resolve_view,\n        }\n    }\n\n    pub fn size(&self) -> Extent2d {\n        self.size\n    }\n\n    pub fn sample_count(&self) -> u32 {\n        self.sample_count\n    }\n}\n\nimpl RenderTarget for FinalPassTarget {\n    fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder {\n        RenderPassBuilder {\n            color_attachments: vec![wgpu::RenderPassColorAttachment {\n                view: &self.color_view,\n                resolve_target: Some(self.resolve_view()),\n                ops: wgpu::Operations {\n                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                    store: true,\n                },\n            }],\n            depth_attachment: None,\n        }\n    }\n}\n\nimpl RenderTargetResolve for FinalPassTarget {\n    fn resolve_attachment(&self) -> &wgpu::Texture {\n        &self.resolve_attachment\n    }\n\n    fn resolve_view(&self) -> &wgpu::TextureView {\n        &self.resolve_view\n    }\n}\n\npub struct SwapChainTarget<'a> {\n    swap_chain_view: &'a wgpu::TextureView,\n}\n\nimpl<'a> SwapChainTarget<'a> {\n    pub fn with_swap_chain_view(swap_chain_view: &'a wgpu::TextureView) -> SwapChainTarget<'a> {\n        SwapChainTarget { swap_chain_view }\n    }\n}\n\nimpl<'a> RenderTarget for SwapChainTarget<'a> {\n    fn render_pass_builder(&self) -> RenderPassBuilder {\n        RenderPassBuilder {\n            color_attachments: vec![wgpu::RenderPassColorAttachment {\n                view: self.swap_chain_view,\n                resolve_target: None,\n                ops: wgpu::Operations {\n                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                    store: true,\n                },\n            }],\n            depth_attachment: None,\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/ui/console.rs",
    "content": "use crate::{\n    client::render::{\n        ui::{\n            glyph::{GlyphRendererCommand, GLYPH_HEIGHT, GLYPH_WIDTH},\n            layout::{Anchor, AnchorCoord, Layout, ScreenPosition, Size},\n            quad::{QuadRendererCommand, QuadTexture},\n        },\n        GraphicsState,\n    },\n    common::{console::Console, engine, wad::QPic},\n};\n\nuse chrono::Duration;\n\nconst PAD_LEFT: i32 = GLYPH_WIDTH as i32;\n\npub struct ConsoleRenderer {\n    conback: QuadTexture,\n}\n\nimpl ConsoleRenderer {\n    pub fn new(state: &GraphicsState) -> ConsoleRenderer {\n        let conback = QuadTexture::from_qpic(\n            state,\n            &QPic::load(state.vfs().open(\"gfx/conback.lmp\").unwrap()).unwrap(),\n        );\n\n        ConsoleRenderer { conback }\n    }\n\n    pub fn generate_commands<'a>(\n        &'a self,\n        console: &Console,\n        time: Duration,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n        proportion: f32,\n    ) {\n        // TODO: take scale as cvar\n        let scale = 2.0;\n        let console_anchor = Anchor {\n            x: AnchorCoord::Zero,\n            y: AnchorCoord::Proportion(1.0 - proportion),\n        };\n\n        // draw console background\n        quad_cmds.push(QuadRendererCommand {\n            texture: &self.conback,\n            layout: Layout {\n                position: ScreenPosition::Absolute(console_anchor),\n                anchor: Anchor::BOTTOM_LEFT,\n                size: Size::DisplayScale { ratio: 1.0 },\n            },\n        });\n\n        // draw version string\n        let version_string = format!(\"{} {}\", env!(\"CARGO_PKG_NAME\"), env!(\"CARGO_PKG_VERSION\"));\n        glyph_cmds.push(GlyphRendererCommand::Text {\n            text: version_string,\n            position: ScreenPosition::Absolute(console_anchor),\n            anchor: Anchor::BOTTOM_RIGHT,\n            scale,\n        });\n\n        // draw input line\n        glyph_cmds.push(GlyphRendererCommand::Glyph {\n            glyph_id: ']' as u8,\n            position: ScreenPosition::Relative {\n                anchor: console_anchor,\n                x_ofs: PAD_LEFT,\n                y_ofs: 0,\n            },\n            anchor: Anchor::BOTTOM_LEFT,\n            scale,\n        });\n        let input_text = console.get_string();\n        glyph_cmds.push(GlyphRendererCommand::Text {\n            text: input_text,\n            position: ScreenPosition::Relative {\n                anchor: console_anchor,\n                x_ofs: PAD_LEFT + GLYPH_WIDTH as i32,\n                y_ofs: 0,\n            },\n            anchor: Anchor::BOTTOM_LEFT,\n            scale,\n        });\n        // blink cursor in half-second intervals\n        if engine::duration_to_f32(time).fract() > 0.5 {\n            glyph_cmds.push(GlyphRendererCommand::Glyph {\n                glyph_id: 11,\n                position: ScreenPosition::Relative {\n                    anchor: console_anchor,\n                    x_ofs: PAD_LEFT + (GLYPH_WIDTH * (console.cursor() + 1)) as i32,\n                    y_ofs: 0,\n                },\n                anchor: Anchor::BOTTOM_LEFT,\n                scale,\n            });\n        }\n\n        // draw previous output\n        for (line_id, line) in console.output().lines().enumerate() {\n            // TODO: implement scrolling\n            if line_id > 100 {\n                break;\n            }\n\n            for (chr_id, chr) in line.iter().enumerate() {\n                let position = ScreenPosition::Relative {\n                    anchor: console_anchor,\n                    x_ofs: PAD_LEFT + (1 + chr_id * GLYPH_WIDTH) as i32,\n                    y_ofs: ((line_id + 1) * GLYPH_HEIGHT) as i32,\n                };\n\n                let c = if *chr as u32 > std::u8::MAX as u32 {\n                    warn!(\n                        \"char \\\"{}\\\" (U+{:4}) cannot be displayed in the console\",\n                        *chr, *chr as u32\n                    );\n                    '?'\n                } else {\n                    *chr\n                };\n\n                glyph_cmds.push(GlyphRendererCommand::Glyph {\n                    glyph_id: c as u8,\n                    position,\n                    anchor: Anchor::BOTTOM_LEFT,\n                    scale,\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/ui/glyph.rs",
    "content": "use std::{mem::size_of, num::NonZeroU32};\n\nuse crate::{\n    client::render::{\n        ui::{\n            layout::{Anchor, ScreenPosition},\n            quad::{QuadPipeline, QuadVertex},\n            screen_space_vertex_scale, screen_space_vertex_translate,\n        },\n        Extent2d, GraphicsState, Pipeline, TextureData,\n    },\n    common::util::any_slice_as_bytes,\n};\n\nuse cgmath::Vector2;\n\npub const GLYPH_WIDTH: usize = 8;\npub const GLYPH_HEIGHT: usize = 8;\nconst GLYPH_COLS: usize = 16;\nconst GLYPH_ROWS: usize = 16;\nconst GLYPH_COUNT: usize = GLYPH_ROWS * GLYPH_COLS;\nconst GLYPH_TEXTURE_WIDTH: usize = GLYPH_WIDTH * GLYPH_COLS;\n\n/// The maximum number of glyphs that can be rendered at once.\npub const MAX_INSTANCES: usize = 65536;\n\nlazy_static! {\n    static ref VERTEX_BUFFER_ATTRIBUTES: [Vec<wgpu::VertexAttribute>; 2] = [\n        wgpu::vertex_attr_array![\n            0 => Float32x2, // a_position\n            1 => Float32x2 // a_texcoord\n        ].to_vec(),\n        wgpu::vertex_attr_array![\n            2 => Float32x2, // a_instance_position\n            3 => Float32x2, // a_instance_scale\n            4 => Uint32 // a_instance_layer\n        ].to_vec(),\n    ];\n}\n\npub struct GlyphPipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    instance_buffer: wgpu::Buffer,\n}\n\nimpl GlyphPipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) -> GlyphPipeline {\n        let (pipeline, bind_group_layouts) =\n            GlyphPipeline::create(device, compiler, &[], sample_count);\n\n        let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {\n            label: Some(\"quad instance buffer\"),\n            size: (MAX_INSTANCES * size_of::<GlyphInstance>()) as u64,\n            usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,\n            mapped_at_creation: false,\n        });\n\n        GlyphPipeline {\n            pipeline,\n            bind_group_layouts,\n            instance_buffer,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) {\n        let layout_refs = self.bind_group_layouts.iter().collect::<Vec<_>>();\n        self.pipeline = GlyphPipeline::recreate(device, compiler, &layout_refs, sample_count);\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn instance_buffer(&self) -> &wgpu::Buffer {\n        &self.instance_buffer\n    }\n}\n\nconst BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[\n    // sampler\n    wgpu::BindGroupLayoutEntry {\n        binding: 0,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Sampler {\n            filtering: true,\n            comparison: false,\n        },\n        count: None,\n    },\n    // glyph texture array\n    wgpu::BindGroupLayoutEntry {\n        binding: 1,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Texture {\n            view_dimension: wgpu::TextureViewDimension::D2,\n            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n            multisampled: false,\n        },\n        count: NonZeroU32::new(GLYPH_COUNT as u32),\n    },\n];\n\nimpl Pipeline for GlyphPipeline {\n    type VertexPushConstants = ();\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"glyph\"\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/glyph.vert\"))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/glyph.frag\"))\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        QuadPipeline::primitive_state()\n    }\n\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![wgpu::BindGroupLayoutDescriptor {\n            label: Some(\"glyph constant bind group\"),\n            entries: BIND_GROUP_LAYOUT_ENTRIES,\n        }]\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        QuadPipeline::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        QuadPipeline::depth_stencil_state()\n    }\n\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        vec![\n            wgpu::VertexBufferLayout {\n                array_stride: size_of::<QuadVertex>() as u64,\n                step_mode: wgpu::InputStepMode::Vertex,\n                attributes: &VERTEX_BUFFER_ATTRIBUTES[0],\n            },\n            wgpu::VertexBufferLayout {\n                array_stride: size_of::<GlyphInstance>() as u64,\n                step_mode: wgpu::InputStepMode::Instance,\n                attributes: &VERTEX_BUFFER_ATTRIBUTES[1],\n            },\n        ]\n    }\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug)]\npub struct GlyphInstance {\n    pub position: Vector2<f32>,\n    pub scale: Vector2<f32>,\n    pub layer: u32,\n}\n\npub enum GlyphRendererCommand {\n    Glyph {\n        glyph_id: u8,\n        position: ScreenPosition,\n        anchor: Anchor,\n        scale: f32,\n    },\n    Text {\n        text: String,\n        position: ScreenPosition,\n        anchor: Anchor,\n        scale: f32,\n    },\n}\n\npub struct GlyphRenderer {\n    #[allow(dead_code)]\n    textures: Vec<wgpu::Texture>,\n    #[allow(dead_code)]\n    texture_views: Vec<wgpu::TextureView>,\n    const_bind_group: wgpu::BindGroup,\n}\n\nimpl GlyphRenderer {\n    pub fn new(state: &GraphicsState) -> GlyphRenderer {\n        let conchars = state.gfx_wad().open_conchars().unwrap();\n\n        // TODO: validate conchars dimensions\n\n        let indices = conchars\n            .indices()\n            .iter()\n            .map(|i| if *i == 0 { 0xFF } else { *i })\n            .collect::<Vec<_>>();\n\n        // reorder indices from atlas order to array order\n        let mut array_order = Vec::new();\n        for glyph_id in 0..GLYPH_COUNT {\n            for glyph_r in 0..GLYPH_HEIGHT {\n                for glyph_c in 0..GLYPH_WIDTH {\n                    let atlas_r = GLYPH_HEIGHT * (glyph_id / GLYPH_COLS) + glyph_r;\n                    let atlas_c = GLYPH_WIDTH * (glyph_id % GLYPH_COLS) + glyph_c;\n                    array_order.push(indices[atlas_r * GLYPH_TEXTURE_WIDTH + atlas_c]);\n                }\n            }\n        }\n\n        let textures = array_order\n            .chunks_exact(GLYPH_WIDTH * GLYPH_HEIGHT)\n            .enumerate()\n            .map(|(id, indices)| {\n                let (diffuse_data, _) = state.palette().translate(&indices);\n                state.create_texture(\n                    Some(&format!(\"conchars[{}]\", id)),\n                    GLYPH_WIDTH as u32,\n                    GLYPH_HEIGHT as u32,\n                    &TextureData::Diffuse(diffuse_data),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        let texture_views = textures\n            .iter()\n            .map(|tex| tex.create_view(&Default::default()))\n            .collect::<Vec<_>>();\n        let texture_view_refs = texture_views.iter().collect::<Vec<_>>();\n\n        let const_bind_group = state\n            .device()\n            .create_bind_group(&wgpu::BindGroupDescriptor {\n                label: Some(\"glyph constant bind group\"),\n                layout: &state.glyph_pipeline().bind_group_layouts()[0],\n                entries: &[\n                    wgpu::BindGroupEntry {\n                        binding: 0,\n                        resource: wgpu::BindingResource::Sampler(state.diffuse_sampler()),\n                    },\n                    wgpu::BindGroupEntry {\n                        binding: 1,\n                        resource: wgpu::BindingResource::TextureViewArray(&texture_view_refs[..]),\n                    },\n                ],\n            });\n\n        GlyphRenderer {\n            textures,\n            texture_views,\n            const_bind_group,\n        }\n    }\n\n    pub fn generate_instances(\n        &self,\n        commands: &[GlyphRendererCommand],\n        target_size: Extent2d,\n    ) -> Vec<GlyphInstance> {\n        let mut instances = Vec::new();\n        let Extent2d {\n            width: display_width,\n            height: display_height,\n        } = target_size;\n        for cmd in commands {\n            match cmd {\n                GlyphRendererCommand::Glyph {\n                    glyph_id,\n                    position,\n                    anchor,\n                    scale,\n                } => {\n                    let (screen_x, screen_y) =\n                        position.to_xy(display_width, display_height, *scale);\n                    let (glyph_x, glyph_y) = anchor.to_xy(\n                        (GLYPH_WIDTH as f32 * scale) as u32,\n                        (GLYPH_HEIGHT as f32 * scale) as u32,\n                    );\n                    let x = screen_x - glyph_x;\n                    let y = screen_y - glyph_y;\n\n                    instances.push(GlyphInstance {\n                        position: screen_space_vertex_translate(\n                            display_width,\n                            display_height,\n                            x,\n                            y,\n                        ),\n                        scale: screen_space_vertex_scale(\n                            display_width,\n                            display_height,\n                            (GLYPH_WIDTH as f32 * scale) as u32,\n                            (GLYPH_HEIGHT as f32 * scale) as u32,\n                        ),\n                        layer: *glyph_id as u32,\n                    });\n                }\n                GlyphRendererCommand::Text {\n                    text,\n                    position,\n                    anchor,\n                    scale,\n                } => {\n                    let (screen_x, screen_y) =\n                        position.to_xy(display_width, display_height, *scale);\n                    let (glyph_x, glyph_y) = anchor.to_xy(\n                        ((text.chars().count() * GLYPH_WIDTH) as f32 * scale) as u32,\n                        (GLYPH_HEIGHT as f32 * scale) as u32,\n                    );\n                    let x = screen_x - glyph_x;\n                    let y = screen_y - glyph_y;\n\n                    for (chr_id, chr) in text.as_str().chars().enumerate() {\n                        let abs_x = x + ((GLYPH_WIDTH * chr_id) as f32 * scale) as i32;\n\n                        if abs_x >= display_width as i32 {\n                            // don't render past the edge of the screen\n                            break;\n                        }\n\n                        instances.push(GlyphInstance {\n                            position: screen_space_vertex_translate(\n                                display_width,\n                                display_height,\n                                abs_x,\n                                y,\n                            ),\n                            scale: screen_space_vertex_scale(\n                                display_width,\n                                display_height,\n                                (GLYPH_WIDTH as f32 * scale) as u32,\n                                (GLYPH_HEIGHT as f32 * scale) as u32,\n                            ),\n                            layer: chr as u32,\n                        });\n                    }\n                }\n            }\n        }\n\n        instances\n    }\n\n    pub fn record_draw<'a>(\n        &'a self,\n        state: &'a GraphicsState,\n        pass: &mut wgpu::RenderPass<'a>,\n        target_size: Extent2d,\n        commands: &[GlyphRendererCommand],\n    ) {\n        let instances = self.generate_instances(commands, target_size);\n        state\n            .queue()\n            .write_buffer(state.glyph_pipeline().instance_buffer(), 0, unsafe {\n                any_slice_as_bytes(&instances)\n            });\n        pass.set_pipeline(state.glyph_pipeline().pipeline());\n        pass.set_vertex_buffer(0, state.quad_pipeline().vertex_buffer().slice(..));\n        pass.set_vertex_buffer(1, state.glyph_pipeline().instance_buffer().slice(..));\n        pass.set_bind_group(0, &self.const_bind_group, &[]);\n        pass.draw(0..6, 0..commands.len() as u32);\n    }\n}\n"
  },
  {
    "path": "src/client/render/ui/hud.rs",
    "content": "use std::{collections::HashMap, iter::FromIterator};\n\nuse crate::{\n    client::{\n        render::{\n            ui::{\n                glyph::GlyphRendererCommand,\n                layout::{Anchor, Layout, ScreenPosition, Size},\n                quad::{QuadRendererCommand, QuadTexture},\n            },\n            GraphicsState,\n        },\n        IntermissionKind,\n    },\n    common::{\n        console::Console,\n        net::{ClientStat, ItemFlags},\n        wad::QPic,\n    },\n};\n\nuse arrayvec::ArrayVec;\nuse chrono::Duration;\nuse num::FromPrimitive as _;\nuse strum::IntoEnumIterator as _;\nuse strum_macros::EnumIter;\n\n// intermission overlay size\nconst OVERLAY_WIDTH: i32 = 320;\nconst OVERLAY_HEIGHT: i32 = 200;\n\nconst OVERLAY_X_OFS: i32 = -OVERLAY_WIDTH / 2;\nconst OVERLAY_Y_OFS: i32 = -OVERLAY_HEIGHT / 2;\n\nconst OVERLAY_ANCHOR: Anchor = Anchor::CENTER;\n\npub enum HudState<'a> {\n    InGame {\n        items: ItemFlags,\n        item_pickup_time: &'a [Duration],\n        stats: &'a [i32],\n        face_anim_time: Duration,\n        console: &'a Console,\n    },\n    Intermission {\n        kind: &'a IntermissionKind,\n        completion_duration: Duration,\n        stats: &'a [i32],\n        console: &'a Console,\n    },\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\nenum HudTextureId {\n    Digit { alt: bool, value: usize },\n    Minus { alt: bool },\n    Colon,\n    Slash,\n    Weapon { id: WeaponId, frame: WeaponFrame },\n    Ammo { id: AmmoId },\n    Armor { id: usize },\n    Item { id: ItemId },\n    Sigil { id: usize },\n    Face { id: FaceId },\n    StatusBar,\n    InvBar,\n    ScoreBar,\n\n    // these are not in gfx.wad\n    Complete,\n    Intermission,\n}\n\nimpl std::fmt::Display for HudTextureId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        use HudTextureId::*;\n        match *self {\n            Digit { alt, value } => write!(f, \"{}NUM_{}\", if alt { \"A\" } else { \"\" }, value),\n            Minus { alt } => write!(f, \"{}NUM_MINUS\", if alt { \"A\" } else { \"\" }),\n            Colon => write!(f, \"NUM_COLON\"),\n            Slash => write!(f, \"NUM_SLASH\"),\n            Weapon { id, frame } => write!(f, \"INV{}_{}\", frame, id),\n            Ammo { id } => write!(f, \"SB_{}\", id),\n            Armor { id } => write!(f, \"SB_ARMOR{}\", id + 1),\n            Item { id } => write!(f, \"SB_{}\", id),\n            Sigil { id } => write!(f, \"SB_SIGIL{}\", id + 1),\n            Face { id } => write!(f, \"{}\", id),\n            StatusBar => write!(f, \"SBAR\"),\n            InvBar => write!(f, \"IBAR\"),\n            ScoreBar => write!(f, \"SCOREBAR\"),\n\n            // these are not in gfx.wad\n            Complete => write!(f, \"gfx/complete.lmp\"),\n            Intermission => write!(f, \"gfx/inter.lmp\"),\n        }\n    }\n}\n\nconst WEAPON_ID_NAMES: [&'static str; 7] = [\n    \"SHOTGUN\", \"SSHOTGUN\", \"NAILGUN\", \"SNAILGUN\", \"RLAUNCH\", \"SRLAUNCH\", \"LIGHTNG\",\n];\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, FromPrimitive, EnumIter)]\nenum WeaponId {\n    Shotgun = 0,\n    SuperShotgun = 1,\n    Nailgun = 2,\n    SuperNailgun = 3,\n    RocketLauncher = 4,\n    GrenadeLauncher = 5,\n    LightningGun = 6,\n}\n\nimpl std::fmt::Display for WeaponId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", WEAPON_ID_NAMES[*self as usize])\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\nenum WeaponFrame {\n    Inactive,\n    Active,\n    Pickup { frame: usize },\n}\n\nimpl std::fmt::Display for WeaponFrame {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match *self {\n            WeaponFrame::Inactive => write!(f, \"\"),\n            WeaponFrame::Active => write!(f, \"2\"),\n            WeaponFrame::Pickup { frame } => write!(f, \"A{}\", frame + 1),\n        }\n    }\n}\n\nconst AMMO_ID_NAMES: [&'static str; 4] = [\"SHELLS\", \"NAILS\", \"ROCKET\", \"CELLS\"];\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, FromPrimitive, EnumIter)]\nenum AmmoId {\n    Shells = 0,\n    Nails = 1,\n    Rockets = 2,\n    Cells = 3,\n}\n\nimpl std::fmt::Display for AmmoId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", AMMO_ID_NAMES[*self as usize])\n    }\n}\n\nconst ITEM_ID_NAMES: [&'static str; 6] = [\"KEY1\", \"KEY2\", \"INVIS\", \"INVULN\", \"SUIT\", \"QUAD\"];\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, FromPrimitive, EnumIter)]\nenum ItemId {\n    Key1 = 0,\n    Key2 = 1,\n    Invisibility = 2,\n    Invulnerability = 3,\n    BioSuit = 4,\n    QuadDamage = 5,\n}\n\nimpl std::fmt::Display for ItemId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", ITEM_ID_NAMES[*self as usize])\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]\nenum FaceId {\n    Normal { pain: bool, frame: usize },\n    Invisible,\n    Invulnerable,\n    InvisibleInvulnerable,\n    QuadDamage,\n}\n\nimpl std::fmt::Display for FaceId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        use FaceId::*;\n        match *self {\n            Normal { pain, frame } => {\n                write!(f, \"FACE{}{}\", if pain { \"_P\" } else { \"\" }, frame + 1)\n            }\n            Invisible => write!(f, \"FACE_INVIS\"),\n            Invulnerable => write!(f, \"FACE_INVUL2\"),\n            InvisibleInvulnerable => write!(f, \"FACE_INV2\"),\n            QuadDamage => write!(f, \"FACE_QUAD\"),\n        }\n    }\n}\n\npub struct HudRenderer {\n    textures: HashMap<HudTextureId, QuadTexture>,\n}\n\nimpl HudRenderer {\n    /// Construct a new `HudRenderer`.\n    pub fn new(state: &GraphicsState) -> HudRenderer {\n        use HudTextureId::*;\n        let mut ids = Vec::new();\n\n        // digits and minus\n        ids.extend((&[false, true]).iter().flat_map(|b| {\n            (0..10)\n                .map(move |i| Digit { alt: *b, value: i })\n                .chain(std::iter::once(Minus { alt: *b }))\n        }));\n\n        // weapons\n        ids.extend(WeaponId::iter().flat_map(|id| {\n            (0..5)\n                .map(|frame| WeaponFrame::Pickup { frame })\n                .chain(std::iter::once(WeaponFrame::Inactive))\n                .chain(std::iter::once(WeaponFrame::Active))\n                .map(move |frame| Weapon { id, frame })\n        }));\n\n        // ammo\n        ids.extend(AmmoId::iter().map(|id| Ammo { id }));\n\n        // armor\n        ids.extend((0..3).map(|id| Armor { id }));\n\n        // items\n        ids.extend(ItemId::iter().map(|id| Item { id }));\n\n        // sigils\n        ids.extend((0..4).map(|id| Sigil { id }));\n\n        // faces\n        ids.extend(\n            (&[false, true])\n                .iter()\n                .flat_map(|b| (0..5).map(move |i| FaceId::Normal { pain: *b, frame: i }))\n                .chain(\n                    vec![\n                        FaceId::Invisible,\n                        FaceId::Invulnerable,\n                        FaceId::InvisibleInvulnerable,\n                        FaceId::QuadDamage,\n                    ]\n                    .into_iter(),\n                )\n                .map(move |id| Face { id }),\n        );\n\n        // unit variants\n        ids.extend(vec![Colon, Slash, StatusBar, InvBar, ScoreBar].into_iter());\n\n        let mut textures = HashMap::new();\n        for id in ids.into_iter() {\n            debug!(\"Opening {}\", id);\n            let qpic = state.gfx_wad().open_qpic(id.to_string()).unwrap();\n            let texture = QuadTexture::from_qpic(state, &qpic);\n            textures.insert(id, texture);\n        }\n\n        // new id list for textures not in gfx.wad\n        let ids = vec![Complete, Intermission];\n        for id in ids.into_iter() {\n            debug!(\"Opening {}\", id);\n            let qpic = QPic::load(state.vfs().open(&format!(\"{}\", id)).unwrap()).unwrap();\n            textures.insert(id, QuadTexture::from_qpic(state, &qpic));\n        }\n\n        HudRenderer { textures }\n    }\n\n    fn cmd_number<'a>(\n        &'a self,\n        number: i32,\n        alt_color: bool,\n        max_digits: usize,\n        screen_anchor: Anchor,\n        screen_x_ofs: i32,\n        screen_y_ofs: i32,\n        quad_anchor: Anchor,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) {\n        use HudTextureId::*;\n\n        let number_str = format!(\"{}\", number);\n        let number_chars = number_str.chars().collect::<Vec<_>>();\n\n        let mut skip = 0;\n        let mut place_ofs = 0;\n        if number_chars.len() > max_digits {\n            skip = number_chars.len() - max_digits;\n        } else if max_digits > number_chars.len() {\n            place_ofs = (max_digits - number_chars.len()) as i32 * 24;\n        }\n\n        for (chr_id, chr) in number_chars.into_iter().skip(skip).enumerate() {\n            let tex_id = match chr {\n                '-' => Minus { alt: alt_color },\n                '0'..='9' => Digit {\n                    alt: alt_color,\n                    value: chr as usize - '0' as usize,\n                },\n                _ => unreachable!(),\n            };\n\n            quad_cmds.push(QuadRendererCommand {\n                texture: self.textures.get(&tex_id).unwrap(),\n                layout: Layout {\n                    position: ScreenPosition::Relative {\n                        anchor: screen_anchor,\n                        x_ofs: screen_x_ofs + place_ofs + 24 * chr_id as i32,\n                        y_ofs: screen_y_ofs,\n                    },\n                    anchor: quad_anchor,\n                    size: Size::Scale { factor: scale },\n                },\n            });\n        }\n    }\n\n    // Draw a quad on the status bar.\n    //\n    // `x_ofs` and `y_ofs` are specified relative to the bottom-left corner of\n    // the status bar.\n    fn cmd_sbar_quad<'a>(\n        &'a self,\n        texture_id: HudTextureId,\n        x_ofs: i32,\n        y_ofs: i32,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) {\n        quad_cmds.push(QuadRendererCommand {\n            texture: self.textures.get(&texture_id).unwrap(),\n            layout: Layout {\n                position: ScreenPosition::Relative {\n                    anchor: Anchor::BOTTOM_CENTER,\n                    x_ofs: OVERLAY_X_OFS + x_ofs,\n                    y_ofs,\n                },\n                anchor: Anchor::BOTTOM_LEFT,\n                size: Size::Scale { factor: scale },\n            },\n        });\n    }\n\n    // Draw a quad on the status bar.\n    //\n    // `x_ofs` and `y_ofs` are specified relative to the bottom-left corner of\n    // the status bar.\n    fn cmd_sbar_number<'a>(\n        &'a self,\n        number: i32,\n        alt_color: bool,\n        max_digits: usize,\n        x_ofs: i32,\n        y_ofs: i32,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) {\n        self.cmd_number(\n            number,\n            alt_color,\n            max_digits,\n            Anchor::BOTTOM_CENTER,\n            OVERLAY_X_OFS + x_ofs,\n            y_ofs,\n            Anchor::BOTTOM_LEFT,\n            scale,\n            quad_cmds,\n        );\n    }\n\n    // Draw the status bar.\n    fn cmd_sbar<'a>(\n        &'a self,\n        time: Duration,\n        items: ItemFlags,\n        item_pickup_time: &'a [Duration],\n        stats: &'a [i32],\n        face_anim_time: Duration,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) {\n        use HudTextureId::*;\n\n        let sbar = self.textures.get(&StatusBar).unwrap();\n        let sbar_x_ofs = -(sbar.width() as i32) / 2;\n\n        // status bar background\n        self.cmd_sbar_quad(StatusBar, 0, 0, scale, quad_cmds);\n\n        // inventory bar background\n        self.cmd_sbar_quad(InvBar, 0, sbar.height() as i32, scale, quad_cmds);\n\n        // weapon slots\n        for i in 0..7 {\n            if items.contains(ItemFlags::from_bits(ItemFlags::SHOTGUN.bits() << i).unwrap()) {\n                let id = WeaponId::from_usize(i).unwrap();\n                let pickup_time = item_pickup_time[i];\n                let delta = time - pickup_time;\n                let frame = if delta >= Duration::milliseconds(100) {\n                    if stats[ClientStat::ActiveWeapon as usize] as u32\n                        == ItemFlags::SHOTGUN.bits() << i\n                    {\n                        WeaponFrame::Active\n                    } else {\n                        WeaponFrame::Inactive\n                    }\n                } else {\n                    WeaponFrame::Pickup {\n                        frame: (delta.num_milliseconds() * 100) as usize % 5,\n                    }\n                };\n\n                self.cmd_sbar_quad(\n                    Weapon { id, frame },\n                    24 * i as i32,\n                    sbar.height() as i32,\n                    scale,\n                    quad_cmds,\n                );\n            }\n        }\n\n        // ammo counters\n        for i in 0..4 {\n            let ammo_str = format!(\"{: >3}\", stats[ClientStat::Shells as usize + i]);\n            for (chr_id, chr) in ammo_str.chars().enumerate() {\n                if chr != ' ' {\n                    glyph_cmds.push(GlyphRendererCommand::Glyph {\n                        glyph_id: 18 + chr as u8 - '0' as u8,\n                        position: ScreenPosition::Relative {\n                            anchor: Anchor::BOTTOM_CENTER,\n                            x_ofs: sbar_x_ofs + 8 * (6 * i + chr_id) as i32 + 10,\n                            y_ofs: sbar.height() as i32 + 16,\n                        },\n                        anchor: Anchor::BOTTOM_LEFT,\n                        scale,\n                    });\n                }\n            }\n        }\n\n        // items (keys and powerups)\n        for i in 0..6 {\n            if items.contains(ItemFlags::from_bits(ItemFlags::KEY_1.bits() << i).unwrap()) {\n                quad_cmds.push(QuadRendererCommand {\n                    texture: self\n                        .textures\n                        .get(&Item {\n                            id: ItemId::from_usize(i).unwrap(),\n                        })\n                        .unwrap(),\n                    layout: Layout {\n                        position: ScreenPosition::Relative {\n                            anchor: Anchor::BOTTOM_CENTER,\n                            x_ofs: sbar_x_ofs + 16 * i as i32 + 192,\n                            y_ofs: sbar.height() as i32,\n                        },\n                        anchor: Anchor::BOTTOM_LEFT,\n                        size: Size::Scale { factor: scale },\n                    },\n                })\n            }\n        }\n\n        // sigils\n        for i in 0..4 {\n            if items.contains(ItemFlags::from_bits(ItemFlags::SIGIL_1.bits() << i).unwrap()) {\n                quad_cmds.push(QuadRendererCommand {\n                    texture: self.textures.get(&Sigil { id: i }).unwrap(),\n                    layout: Layout {\n                        position: ScreenPosition::Relative {\n                            anchor: Anchor::BOTTOM_CENTER,\n                            x_ofs: sbar_x_ofs + 8 * i as i32 + 288,\n                            y_ofs: sbar.height() as i32,\n                        },\n                        anchor: Anchor::BOTTOM_LEFT,\n                        size: Size::Scale { factor: scale },\n                    },\n                });\n            }\n        }\n\n        // armor\n        let armor_width = self.textures.get(&Armor { id: 0 }).unwrap().width() as i32;\n        if items.contains(ItemFlags::INVULNERABILITY) {\n            self.cmd_sbar_number(666, true, 3, armor_width, 0, scale, quad_cmds);\n        // TODO draw_disc\n        } else {\n            let armor = stats[ClientStat::Armor as usize];\n            self.cmd_sbar_number(armor, armor <= 25, 3, armor_width, 0, scale, quad_cmds);\n\n            let mut armor_id = None;\n            for i in (0..3).rev() {\n                if items.contains(ItemFlags::from_bits(ItemFlags::ARMOR_1.bits() << i).unwrap()) {\n                    armor_id = Some(Armor { id: i });\n                    break;\n                }\n            }\n\n            if let Some(a) = armor_id {\n                self.cmd_sbar_quad(a, 0, 0, scale, quad_cmds);\n            }\n        }\n\n        // health\n        let health = stats[ClientStat::Health as usize];\n        self.cmd_sbar_number(health, health <= 25, 3, 136, 0, scale, quad_cmds);\n\n        let ammo = stats[ClientStat::Ammo as usize];\n        self.cmd_sbar_number(ammo, ammo <= 10, 3, 248, 0, scale, quad_cmds);\n\n        let face = if items.contains(ItemFlags::INVISIBILITY | ItemFlags::INVULNERABILITY) {\n            FaceId::InvisibleInvulnerable\n        } else if items.contains(ItemFlags::QUAD) {\n            FaceId::QuadDamage\n        } else if items.contains(ItemFlags::INVISIBILITY) {\n            FaceId::Invisible\n        } else if items.contains(ItemFlags::INVULNERABILITY) {\n            FaceId::Invulnerable\n        } else {\n            let health = stats[ClientStat::Health as usize];\n            let frame = 4 - if health >= 100 {\n                4\n            } else {\n                health.max(0) as usize / 20\n            };\n\n            FaceId::Normal {\n                pain: face_anim_time > time,\n                frame,\n            }\n        };\n\n        self.cmd_sbar_quad(Face { id: face }, 112, 0, scale, quad_cmds);\n\n        // crosshair\n        glyph_cmds.push(GlyphRendererCommand::Glyph {\n            glyph_id: '+' as u8,\n            position: ScreenPosition::Absolute(Anchor::CENTER),\n            anchor: Anchor::TOP_LEFT,\n            scale,\n        });\n    }\n\n    // Draw a quad on the intermission overlay.\n    //\n    // `x_ofs` and `y_ofs` are specified relative to the top-left corner of the\n    // overlay.\n    fn cmd_intermission_quad<'a>(\n        &'a self,\n        texture_id: HudTextureId,\n        x_ofs: i32,\n        y_ofs: i32,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) {\n        quad_cmds.push(QuadRendererCommand {\n            texture: self.textures.get(&texture_id).unwrap(),\n            layout: Layout {\n                position: ScreenPosition::Relative {\n                    anchor: Anchor::CENTER,\n                    x_ofs: OVERLAY_X_OFS + x_ofs,\n                    y_ofs: OVERLAY_Y_OFS + y_ofs,\n                },\n                anchor: Anchor::TOP_LEFT,\n                size: Size::Scale { factor: scale },\n            },\n        });\n    }\n\n    // Draw a number on the intermission overlay.\n    //\n    // `x_ofs` and `y_ofs` are specified relative to the top-left corner of the\n    // overlay.\n    fn cmd_intermission_number<'a>(\n        &'a self,\n        number: i32,\n        max_digits: usize,\n        x_ofs: i32,\n        y_ofs: i32,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) {\n        self.cmd_number(\n            number,\n            false,\n            max_digits,\n            OVERLAY_ANCHOR,\n            OVERLAY_X_OFS + x_ofs,\n            OVERLAY_Y_OFS + y_ofs,\n            Anchor::TOP_LEFT,\n            scale,\n            quad_cmds,\n        );\n    }\n\n    // Draw the intermission overlay.\n    fn cmd_intermission_overlay<'a>(\n        &'a self,\n        _kind: &'a IntermissionKind,\n        completion_duration: Duration,\n        stats: &'a [i32],\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) {\n        use HudTextureId::*;\n\n        // TODO: check gametype\n\n        self.cmd_intermission_quad(Complete, 64, OVERLAY_HEIGHT - 24, scale, quad_cmds);\n        self.cmd_intermission_quad(Intermission, 0, OVERLAY_HEIGHT - 56, scale, quad_cmds);\n\n        // TODO: zero-pad number of seconds\n        let time_y_ofs = OVERLAY_HEIGHT - 64;\n        let minutes = completion_duration.num_minutes() as i32;\n        let seconds = completion_duration.num_seconds() as i32 - 60 * minutes;\n        self.cmd_intermission_number(minutes, 3, 160, time_y_ofs, scale, quad_cmds);\n        self.cmd_intermission_quad(Colon, 234, time_y_ofs, scale, quad_cmds);\n        self.cmd_intermission_number(seconds, 2, 246, time_y_ofs, scale, quad_cmds);\n\n        // secrets\n        let secrets_y_ofs = OVERLAY_HEIGHT - 104;\n        let secrets_found = stats[ClientStat::FoundSecrets as usize];\n        let secrets_total = stats[ClientStat::TotalSecrets as usize];\n        self.cmd_intermission_number(secrets_found, 3, 160, secrets_y_ofs, scale, quad_cmds);\n        self.cmd_intermission_quad(Slash, 232, secrets_y_ofs, scale, quad_cmds);\n        self.cmd_intermission_number(secrets_total, 3, 240, secrets_y_ofs, scale, quad_cmds);\n\n        // monsters\n        let monsters_y_ofs = OVERLAY_HEIGHT - 144;\n        let monsters_killed = stats[ClientStat::KilledMonsters as usize];\n        let monsters_total = stats[ClientStat::TotalMonsters as usize];\n        self.cmd_intermission_number(monsters_killed, 3, 160, monsters_y_ofs, scale, quad_cmds);\n        self.cmd_intermission_quad(Slash, 232, monsters_y_ofs, scale, quad_cmds);\n        self.cmd_intermission_number(monsters_total, 3, 240, monsters_y_ofs, scale, quad_cmds);\n    }\n\n    /// Generate render commands to draw the HUD in the specified state.\n    pub fn generate_commands<'state, 'a>(\n        &'a self,\n        hud_state: &HudState<'a>,\n        time: Duration,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) {\n        // TODO: get from cvar\n        let scale = 2.0;\n        let console_timeout = Duration::seconds(3);\n\n        match hud_state {\n            HudState::InGame {\n                items,\n                item_pickup_time,\n                stats,\n                face_anim_time,\n                console,\n            } => {\n                self.cmd_sbar(\n                    time,\n                    *items,\n                    item_pickup_time,\n                    stats,\n                    *face_anim_time,\n                    scale,\n                    quad_cmds,\n                    glyph_cmds,\n                );\n\n                let output = console.output();\n                for (id, line) in output.recent_lines(console_timeout, 100, 10).enumerate() {\n                    for (chr_id, chr) in line.into_iter().enumerate() {\n                        glyph_cmds.push(GlyphRendererCommand::Glyph {\n                            glyph_id: *chr as u8,\n                            position: ScreenPosition::Relative {\n                                anchor: Anchor::TOP_LEFT,\n                                x_ofs: 8 * chr_id as i32,\n                                y_ofs: -8 * id as i32,\n                            },\n                            anchor: Anchor::TOP_LEFT,\n                            scale,\n                        });\n                    }\n                }\n            }\n            HudState::Intermission {\n                kind,\n                completion_duration,\n                stats,\n                console,\n            } => {\n                self.cmd_intermission_overlay(kind, *completion_duration, stats, scale, quad_cmds);\n\n                // TODO: dedup this code\n                let output = console.output();\n                for (id, line) in output.recent_lines(console_timeout, 100, 10).enumerate() {\n                    for (chr_id, chr) in line.into_iter().enumerate() {\n                        glyph_cmds.push(GlyphRendererCommand::Glyph {\n                            glyph_id: *chr as u8,\n                            position: ScreenPosition::Relative {\n                                anchor: Anchor::TOP_LEFT,\n                                x_ofs: 8 * chr_id as i32,\n                                y_ofs: -8 * id as i32,\n                            },\n                            anchor: Anchor::TOP_LEFT,\n                            scale,\n                        });\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/ui/layout.rs",
    "content": "#[derive(Clone, Copy, Debug)]\npub struct Layout {\n    /// The position of the quad on the screen.\n    pub position: ScreenPosition,\n\n    /// Which part of the quad to position at `position`.\n    pub anchor: Anchor,\n\n    /// The size at which to render the quad.\n    pub size: Size,\n}\n\n/// An anchor coordinate.\n#[derive(Clone, Copy, Debug)]\npub enum AnchorCoord {\n    /// A value of zero in this dimension.\n    Zero,\n\n    /// The center of the quad in this dimension.\n    Center,\n\n    /// The maximum extent of the quad in this dimension.\n    Max,\n\n    /// An absolute anchor coordinate, in pixels.\n    Absolute(i32),\n\n    /// A proportion of the maximum extent of the quad in this dimension.\n    Proportion(f32),\n}\n\nimpl AnchorCoord {\n    pub fn to_value(&self, max: u32) -> i32 {\n        match *self {\n            AnchorCoord::Zero => 0,\n            AnchorCoord::Center => max as i32 / 2,\n            AnchorCoord::Max => max as i32,\n            AnchorCoord::Absolute(v) => v,\n            AnchorCoord::Proportion(p) => (p * max as f32) as i32,\n        }\n    }\n}\n\n/// An anchor position on a quad.\n///\n/// The anchor specifies which part of the quad should be considered the origin\n/// when positioning the quad, or when positioning quads relative to one another.\n#[derive(Clone, Copy, Debug)]\npub struct Anchor {\n    /// The x-coordinate of the anchor.\n    pub x: AnchorCoord,\n\n    /// The y-coordinate of the anchor.\n    pub y: AnchorCoord,\n}\n\nimpl Anchor {\n    pub const BOTTOM_LEFT: Anchor = Anchor {\n        x: AnchorCoord::Zero,\n        y: AnchorCoord::Zero,\n    };\n    pub const CENTER_LEFT: Anchor = Anchor {\n        x: AnchorCoord::Zero,\n        y: AnchorCoord::Center,\n    };\n    pub const TOP_LEFT: Anchor = Anchor {\n        x: AnchorCoord::Zero,\n        y: AnchorCoord::Max,\n    };\n    pub const BOTTOM_CENTER: Anchor = Anchor {\n        x: AnchorCoord::Center,\n        y: AnchorCoord::Zero,\n    };\n    pub const CENTER: Anchor = Anchor {\n        x: AnchorCoord::Center,\n        y: AnchorCoord::Center,\n    };\n    pub const TOP_CENTER: Anchor = Anchor {\n        x: AnchorCoord::Center,\n        y: AnchorCoord::Max,\n    };\n    pub const BOTTOM_RIGHT: Anchor = Anchor {\n        x: AnchorCoord::Max,\n        y: AnchorCoord::Zero,\n    };\n    pub const CENTER_RIGHT: Anchor = Anchor {\n        x: AnchorCoord::Max,\n        y: AnchorCoord::Center,\n    };\n    pub const TOP_RIGHT: Anchor = Anchor {\n        x: AnchorCoord::Max,\n        y: AnchorCoord::Max,\n    };\n\n    pub fn absolute_xy(x: i32, y: i32) -> Anchor {\n        Anchor {\n            x: AnchorCoord::Absolute(x),\n            y: AnchorCoord::Absolute(y),\n        }\n    }\n\n    pub fn to_xy(&self, width: u32, height: u32) -> (i32, i32) {\n        (self.x.to_value(width), self.y.to_value(height))\n    }\n}\n\n/// The position of a quad rendered on the screen.\n#[derive(Clone, Copy, Debug)]\npub enum ScreenPosition {\n    /// The quad is positioned at the exact coordinates provided.\n    Absolute(Anchor),\n\n    /// The quad is positioned relative to a reference point.\n    Relative {\n        anchor: Anchor,\n\n        /// The offset along the x-axis from `reference_x`.\n        x_ofs: i32,\n\n        /// The offset along the y-axis from `reference_y`.\n        y_ofs: i32,\n    },\n}\n\nimpl ScreenPosition {\n    pub fn to_xy(&self, display_width: u32, display_height: u32, scale: f32) -> (i32, i32) {\n        match *self {\n            ScreenPosition::Absolute(Anchor {\n                x: anchor_x,\n                y: anchor_y,\n            }) => (\n                anchor_x.to_value(display_width),\n                anchor_y.to_value(display_height),\n            ),\n            ScreenPosition::Relative {\n                anchor:\n                    Anchor {\n                        x: anchor_x,\n                        y: anchor_y,\n                    },\n                x_ofs,\n                y_ofs,\n            } => (\n                anchor_x.to_value(display_width) + (x_ofs as f32 * scale) as i32,\n                anchor_y.to_value(display_height) + (y_ofs as f32 * scale) as i32,\n            ),\n        }\n    }\n}\n\n/// Specifies what size a quad should be when rendered on the screen.\n#[derive(Clone, Copy, Debug)]\npub enum Size {\n    /// Render the quad at an exact size in pixels.\n    Absolute {\n        /// The width of the quad in pixels.\n        width: u32,\n\n        /// The height of the quad in pixels.\n        height: u32,\n    },\n\n    /// Render the quad at a size specified relative to the dimensions of its texture.\n    Scale {\n        /// The factor to multiply by the quad's texture dimensions to determine its size.\n        factor: f32,\n    },\n\n    /// Render the quad at a size specified relative to the size of the display.\n    DisplayScale {\n        /// The ratio of the display size at which to render the quad.\n        ratio: f32,\n    },\n}\n\nimpl Size {\n    pub fn to_wh(\n        &self,\n        texture_width: u32,\n        texture_height: u32,\n        display_width: u32,\n        display_height: u32,\n    ) -> (u32, u32) {\n        match *self {\n            Size::Absolute { width, height } => (width, height),\n            Size::Scale { factor } => (\n                (texture_width as f32 * factor) as u32,\n                (texture_height as f32 * factor) as u32,\n            ),\n            Size::DisplayScale { ratio } => (\n                (display_width as f32 * ratio) as u32,\n                (display_height as f32 * ratio) as u32,\n            ),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_anchor_to_xy() {\n        let width = 1366;\n        let height = 768;\n\n        assert_eq!(Anchor::BOTTOM_LEFT.to_xy(width, height), (0, 0));\n        assert_eq!(Anchor::CENTER_LEFT.to_xy(width, height), (0, 384));\n        assert_eq!(Anchor::TOP_LEFT.to_xy(width, height), (0, 768));\n        assert_eq!(Anchor::BOTTOM_CENTER.to_xy(width, height), (683, 0));\n        assert_eq!(Anchor::CENTER.to_xy(width, height), (683, 384));\n        assert_eq!(Anchor::TOP_CENTER.to_xy(width, height), (683, 768));\n        assert_eq!(Anchor::BOTTOM_RIGHT.to_xy(width, height), (1366, 0));\n        assert_eq!(Anchor::CENTER_RIGHT.to_xy(width, height), (1366, 384));\n        assert_eq!(Anchor::TOP_RIGHT.to_xy(width, height), (1366, 768));\n    }\n}\n"
  },
  {
    "path": "src/client/render/ui/menu.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::{\n    client::{\n        menu::{Item, Menu, MenuBodyView, MenuState, NamedMenuItem},\n        render::{\n            ui::{\n                glyph::{GlyphRendererCommand, GLYPH_HEIGHT, GLYPH_WIDTH},\n                layout::{Anchor, Layout, ScreenPosition, Size},\n                quad::{QuadRendererCommand, QuadTexture},\n            },\n            GraphicsState,\n        },\n    },\n    common::wad::QPic,\n};\n\nuse chrono::Duration;\n\n// original minimum Quake resolution\nconst MENU_WIDTH: i32 = 320;\nconst MENU_HEIGHT: i32 = 200;\n\nconst SLIDER_LEFT: u8 = 128;\nconst SLIDER_MIDDLE: u8 = 129;\nconst SLIDER_RIGHT: u8 = 130;\nconst SLIDER_HANDLE: u8 = 131;\nconst SLIDER_WIDTH: i32 = 10;\n\n#[derive(Clone, Copy, Debug)]\nenum Align {\n    Left,\n    Center,\n}\n\nimpl Align {\n    pub fn x_ofs(&self) -> i32 {\n        match *self {\n            Align::Left => -MENU_WIDTH / 2,\n            Align::Center => 0,\n        }\n    }\n\n    pub fn anchor(&self) -> Anchor {\n        match *self {\n            Align::Left => Anchor::TOP_LEFT,\n            Align::Center => Anchor::TOP_CENTER,\n        }\n    }\n}\n\npub struct MenuRenderer {\n    textures: HashMap<String, QuadTexture>,\n}\n\nimpl MenuRenderer {\n    pub fn new(state: &GraphicsState, menu: &Menu) -> MenuRenderer {\n        let mut tex_names = std::collections::HashSet::new();\n        tex_names.insert(\"gfx/qplaque.lmp\".to_string());\n        tex_names.extend((1..=6).into_iter().map(|i| format!(\"gfx/menudot{}.lmp\", i)));\n        let mut menus = vec![menu];\n\n        // walk menu and collect necessary textures\n        while let Some(m) = menus.pop() {\n            tex_names.insert(m.view().title_path().to_string());\n\n            if let MenuBodyView::Predefined { ref path, .. } = m.view().body() {\n                tex_names.insert(path.to_string());\n            }\n\n            for item in m.items() {\n                if let Item::Submenu(ref sub) = item.item() {\n                    menus.push(sub);\n                }\n            }\n        }\n\n        MenuRenderer {\n            textures: tex_names\n                .into_iter()\n                .map(|name| {\n                    (\n                        name.clone(),\n                        QuadTexture::from_qpic(\n                            state,\n                            &QPic::load(state.vfs().open(&name).unwrap()).unwrap(),\n                        ),\n                    )\n                })\n                .collect(),\n        }\n    }\n\n    fn texture<S>(&self, name: S) -> &QuadTexture\n    where\n        S: AsRef<str>,\n    {\n        debug!(\"Fetch texture {}\", name.as_ref());\n        self.textures.get(name.as_ref()).unwrap()\n    }\n\n    fn cmd_draw_quad<'state>(\n        &self,\n        texture: &'state QuadTexture,\n        align: Align,\n        x_ofs: i32,\n        y_ofs: i32,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'state>>,\n    ) {\n        quad_cmds.push(QuadRendererCommand {\n            texture,\n            layout: Layout {\n                position: ScreenPosition::Relative {\n                    anchor: Anchor::CENTER,\n                    x_ofs: align.x_ofs() + x_ofs,\n                    y_ofs: MENU_HEIGHT / 2 + y_ofs,\n                },\n                anchor: align.anchor(),\n                size: Size::Scale { factor: scale },\n            },\n        });\n    }\n\n    fn cmd_draw_glyph(\n        &self,\n        glyph_id: u8,\n        x_ofs: i32,\n        y_ofs: i32,\n        scale: f32,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) {\n        glyph_cmds.push(GlyphRendererCommand::Glyph {\n            glyph_id,\n            position: ScreenPosition::Relative {\n                anchor: Anchor::CENTER,\n                x_ofs: -MENU_WIDTH / 2 + x_ofs,\n                y_ofs: -MENU_HEIGHT / 2 + y_ofs,\n            },\n            anchor: Anchor::TOP_LEFT,\n            scale,\n        });\n    }\n\n    fn cmd_draw_plaque<'a>(&'a self, scale: f32, quad_cmds: &mut Vec<QuadRendererCommand<'a>>) {\n        let plaque = self.texture(\"gfx/qplaque.lmp\");\n        self.cmd_draw_quad(plaque, Align::Left, 16, 4, scale, quad_cmds);\n    }\n\n    fn cmd_draw_title<'a, S>(\n        &'a self,\n        name: S,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) where\n        S: AsRef<str>,\n    {\n        let title = self.texture(name.as_ref());\n        self.cmd_draw_quad(title, Align::Center, 0, 4, scale, quad_cmds);\n    }\n\n    fn cmd_draw_body_predef<'a, S>(\n        &'a self,\n        name: S,\n        cursor_pos: usize,\n        time: Duration,\n        scale: f32,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n    ) where\n        S: AsRef<str>,\n    {\n        let predef = self.texture(name.as_ref());\n        self.cmd_draw_quad(predef, Align::Left, 72, -32, scale, quad_cmds);\n        let curs_frame = (time.num_milliseconds() / 100) % 6;\n        let curs = self.texture(&format!(\"gfx/menudot{}.lmp\", curs_frame + 1));\n        self.cmd_draw_quad(\n            curs,\n            Align::Left,\n            72 - curs.width() as i32,\n            -32 - cursor_pos as i32 * 20,\n            scale,\n            quad_cmds,\n        );\n    }\n\n    fn cmd_draw_item_name<S>(\n        &self,\n        x: i32,\n        y: i32,\n        name: S,\n        scale: f32,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) where\n        S: AsRef<str>,\n    {\n        glyph_cmds.push(GlyphRendererCommand::Text {\n            text: name.as_ref().to_string(),\n            position: ScreenPosition::Relative {\n                anchor: Anchor::CENTER,\n                x_ofs: -MENU_WIDTH / 2 + x - GLYPH_WIDTH as i32,\n                y_ofs: -MENU_HEIGHT / 2 + y,\n            },\n            anchor: Anchor::TOP_RIGHT,\n            scale,\n        });\n    }\n\n    fn cmd_draw_item_text<S>(\n        &self,\n        x: i32,\n        y: i32,\n        text: S,\n        scale: f32,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) where\n        S: AsRef<str>,\n    {\n        glyph_cmds.push(GlyphRendererCommand::Text {\n            text: text.as_ref().to_string(),\n            position: ScreenPosition::Relative {\n                anchor: Anchor::CENTER,\n                x_ofs: -MENU_WIDTH / 2 + x + GLYPH_WIDTH as i32,\n                y_ofs: -MENU_HEIGHT / 2 + y,\n            },\n            anchor: Anchor::TOP_LEFT,\n            scale,\n        });\n    }\n\n    fn cmd_draw_slider(\n        &self,\n        x: i32,\n        y: i32,\n        pos: f32,\n        scale: f32,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) {\n        self.cmd_draw_glyph(SLIDER_LEFT, x, y, scale, glyph_cmds);\n        for i in 0..SLIDER_WIDTH {\n            self.cmd_draw_glyph(SLIDER_MIDDLE, x + 8 * (i + 1), y, scale, glyph_cmds);\n        }\n        self.cmd_draw_glyph(SLIDER_RIGHT, x + 8 * SLIDER_WIDTH, y, scale, glyph_cmds);\n        let handle_x = x + ((8 * (SLIDER_WIDTH - 1)) as f32 * pos) as i32;\n        self.cmd_draw_glyph(SLIDER_HANDLE, handle_x, y, scale, glyph_cmds);\n    }\n\n    fn cmd_draw_body_dynamic(\n        &self,\n        items: &[NamedMenuItem],\n        cursor_pos: usize,\n        time: Duration,\n        scale: f32,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) {\n        for (item_id, item) in items.iter().enumerate() {\n            let y = MENU_HEIGHT - 32 - (GLYPH_HEIGHT * item_id) as i32;\n            let x = 16 + 24 * GLYPH_WIDTH as i32;\n            self.cmd_draw_item_name(x, y, item.name(), scale, glyph_cmds);\n\n            match item.item() {\n                Item::Toggle(toggle) => self.cmd_draw_item_text(\n                    x,\n                    y,\n                    if toggle.get() { \"yes\" } else { \"no\" },\n                    scale,\n                    glyph_cmds,\n                ),\n                Item::Enum(e) => {\n                    self.cmd_draw_item_text(x, y, e.selected_name(), scale, glyph_cmds)\n                }\n                Item::Slider(slider) => {\n                    self.cmd_draw_slider(x, y, slider.position(), scale, glyph_cmds)\n                }\n                Item::TextField(_) => (),\n                _ => (),\n            }\n        }\n\n        if time.num_milliseconds() / 250 % 2 == 0 {\n            self.cmd_draw_glyph(\n                141,\n                200,\n                MENU_HEIGHT - 32 - 8 * cursor_pos as i32,\n                scale,\n                glyph_cmds,\n            );\n        }\n    }\n\n    pub fn generate_commands<'a>(\n        &'a self,\n        menu: &Menu,\n        time: Duration,\n        quad_cmds: &mut Vec<QuadRendererCommand<'a>>,\n        glyph_cmds: &mut Vec<GlyphRendererCommand>,\n    ) {\n        let active_menu = menu.active_submenu().unwrap();\n        let view = active_menu.view();\n\n        // TODO: use cvar\n        let scale = 2.0;\n\n        if view.draw_plaque() {\n            self.cmd_draw_plaque(scale, quad_cmds);\n        }\n\n        self.cmd_draw_title(view.title_path(), scale, quad_cmds);\n\n        let cursor_pos = match active_menu.state() {\n            MenuState::Active { index } => index,\n            _ => unreachable!(),\n        };\n\n        match *view.body() {\n            MenuBodyView::Predefined { ref path } => {\n                self.cmd_draw_body_predef(path, cursor_pos, time, scale, quad_cmds);\n            }\n            MenuBodyView::Dynamic => {\n                self.cmd_draw_body_dynamic(\n                    &active_menu.items(),\n                    cursor_pos,\n                    time,\n                    scale,\n                    glyph_cmds,\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/ui/mod.rs",
    "content": "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 crate::{\n    client::{\n        menu::Menu,\n        render::{\n            ui::{\n                console::ConsoleRenderer,\n                glyph::{GlyphRenderer, GlyphRendererCommand},\n                hud::{HudRenderer, HudState},\n                menu::MenuRenderer,\n                quad::{QuadRenderer, QuadRendererCommand, QuadUniforms},\n            },\n            uniform::{self, DynamicUniformBufferBlock},\n            Extent2d, GraphicsState,\n        },\n    },\n    common::{console::Console, util::any_slice_as_bytes},\n};\n\nuse cgmath::{Matrix4, Vector2};\nuse chrono::Duration;\n\npub fn screen_space_vertex_translate(\n    display_w: u32,\n    display_h: u32,\n    pos_x: i32,\n    pos_y: i32,\n) -> Vector2<f32> {\n    // rescale from [0, DISPLAY_*] to [-1, 1] (NDC)\n    Vector2::new(\n        (pos_x * 2 - display_w as i32) as f32 / display_w as f32,\n        (pos_y * 2 - display_h as i32) as f32 / display_h as f32,\n    )\n}\n\npub fn screen_space_vertex_scale(\n    display_w: u32,\n    display_h: u32,\n    quad_w: u32,\n    quad_h: u32,\n) -> Vector2<f32> {\n    Vector2::new(\n        (quad_w * 2) as f32 / display_w as f32,\n        (quad_h * 2) as f32 / display_h as f32,\n    )\n}\n\npub fn screen_space_vertex_transform(\n    display_w: u32,\n    display_h: u32,\n    quad_w: u32,\n    quad_h: u32,\n    pos_x: i32,\n    pos_y: i32,\n) -> Matrix4<f32> {\n    let Vector2 { x: ndc_x, y: ndc_y } =\n        screen_space_vertex_translate(display_w, display_h, pos_x, pos_y);\n\n    let Vector2 {\n        x: scale_x,\n        y: scale_y,\n    } = screen_space_vertex_scale(display_w, display_h, quad_w, quad_h);\n\n    Matrix4::from_translation([ndc_x, ndc_y, 0.0].into())\n        * Matrix4::from_nonuniform_scale(scale_x, scale_y, 1.0)\n}\n\npub enum UiOverlay<'a> {\n    Menu(&'a Menu),\n    Console(&'a Console),\n}\n\npub enum UiState<'a> {\n    Title {\n        overlay: UiOverlay<'a>,\n    },\n    InGame {\n        hud: HudState<'a>,\n        overlay: Option<UiOverlay<'a>>,\n    },\n}\n\npub struct UiRenderer {\n    console_renderer: ConsoleRenderer,\n    menu_renderer: MenuRenderer,\n    hud_renderer: HudRenderer,\n    glyph_renderer: GlyphRenderer,\n    quad_renderer: QuadRenderer,\n}\n\nimpl UiRenderer {\n    pub fn new(state: &GraphicsState, menu: &Menu) -> UiRenderer {\n        UiRenderer {\n            console_renderer: ConsoleRenderer::new(state),\n            menu_renderer: MenuRenderer::new(state, menu),\n            hud_renderer: HudRenderer::new(state),\n            glyph_renderer: GlyphRenderer::new(state),\n            quad_renderer: QuadRenderer::new(state),\n        }\n    }\n\n    pub fn render_pass<'pass>(\n        &'pass self,\n        state: &'pass GraphicsState,\n        pass: &mut wgpu::RenderPass<'pass>,\n        target_size: Extent2d,\n        time: Duration,\n        ui_state: &UiState<'pass>,\n        quad_commands: &'pass mut Vec<QuadRendererCommand<'pass>>,\n        glyph_commands: &'pass mut Vec<GlyphRendererCommand>,\n    ) {\n        let (hud_state, overlay) = match ui_state {\n            UiState::Title { overlay } => (None, Some(overlay)),\n            UiState::InGame { hud, overlay } => (Some(hud), overlay.as_ref()),\n        };\n\n        if let Some(hstate) = hud_state {\n            self.hud_renderer\n                .generate_commands(hstate, time, quad_commands, glyph_commands);\n        }\n\n        if let Some(o) = overlay {\n            match o {\n                UiOverlay::Menu(menu) => {\n                    self.menu_renderer\n                        .generate_commands(menu, time, quad_commands, glyph_commands);\n                }\n                UiOverlay::Console(console) => {\n                    // TODO: take in-game console proportion as cvar\n                    let proportion = match hud_state {\n                        Some(_) => 0.33,\n                        None => 1.0,\n                    };\n\n                    self.console_renderer.generate_commands(\n                        console,\n                        time,\n                        quad_commands,\n                        glyph_commands,\n                        proportion,\n                    );\n                }\n            }\n        }\n\n        self.quad_renderer\n            .record_draw(state, pass, target_size, quad_commands);\n        self.glyph_renderer\n            .record_draw(state, pass, target_size, glyph_commands);\n    }\n}\n"
  },
  {
    "path": "src/client/render/ui/quad.rs",
    "content": "use std::{\n    cell::{Ref, RefCell, RefMut},\n    mem::size_of,\n    num::NonZeroU64,\n};\n\nuse crate::{\n    client::render::{\n        ui::{\n            layout::{Layout, Size},\n            screen_space_vertex_transform,\n        },\n        uniform::{self, DynamicUniformBuffer, DynamicUniformBufferBlock},\n        Extent2d, GraphicsState, Pipeline, TextureData, DIFFUSE_ATTACHMENT_FORMAT,\n    },\n    common::{util::any_slice_as_bytes, wad::QPic},\n};\n\nuse cgmath::Matrix4;\n\npub const VERTICES: [QuadVertex; 6] = [\n    QuadVertex {\n        position: [0.0, 0.0],\n        texcoord: [0.0, 1.0],\n    },\n    QuadVertex {\n        position: [0.0, 1.0],\n        texcoord: [0.0, 0.0],\n    },\n    QuadVertex {\n        position: [1.0, 1.0],\n        texcoord: [1.0, 0.0],\n    },\n    QuadVertex {\n        position: [0.0, 0.0],\n        texcoord: [0.0, 1.0],\n    },\n    QuadVertex {\n        position: [1.0, 1.0],\n        texcoord: [1.0, 0.0],\n    },\n    QuadVertex {\n        position: [1.0, 0.0],\n        texcoord: [1.0, 1.0],\n    },\n];\n\n// these type aliases are here to aid readability of e.g. size_of::<Position>()\npub type Position = [f32; 2];\npub type Texcoord = [f32; 2];\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug)]\npub struct QuadVertex {\n    position: Position,\n    texcoord: Texcoord,\n}\n\nlazy_static! {\n    static ref VERTEX_BUFFER_ATTRIBUTES: Vec<wgpu::VertexAttribute> = vec![\n        // position\n        wgpu::VertexAttribute {\n            offset: 0,\n            format: wgpu::VertexFormat::Float32x2,\n            shader_location: 0,\n        },\n        // diffuse texcoord\n        wgpu::VertexAttribute {\n            offset: size_of::<Position>() as u64,\n            format: wgpu::VertexFormat::Float32x2,\n            shader_location: 1,\n        },\n    ];\n}\n\npub struct QuadPipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    vertex_buffer: wgpu::Buffer,\n    uniform_buffer: RefCell<DynamicUniformBuffer<QuadUniforms>>,\n    uniform_buffer_blocks: RefCell<Vec<DynamicUniformBufferBlock<QuadUniforms>>>,\n}\n\nimpl QuadPipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) -> QuadPipeline {\n        let (pipeline, bind_group_layouts) =\n            QuadPipeline::create(device, compiler, &[], sample_count);\n\n        use wgpu::util::DeviceExt as _;\n        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {\n            label: None,\n            contents: unsafe { any_slice_as_bytes(&VERTICES) },\n            usage: wgpu::BufferUsage::VERTEX,\n        });\n\n        let uniform_buffer = RefCell::new(DynamicUniformBuffer::new(device));\n        let uniform_buffer_blocks = RefCell::new(Vec::new());\n\n        QuadPipeline {\n            pipeline,\n            bind_group_layouts,\n            vertex_buffer,\n            uniform_buffer,\n            uniform_buffer_blocks,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) {\n        let layout_refs = self.bind_group_layouts.iter().collect::<Vec<_>>();\n        self.pipeline = QuadPipeline::recreate(device, compiler, &layout_refs, sample_count);\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn vertex_buffer(&self) -> &wgpu::Buffer {\n        &self.vertex_buffer\n    }\n\n    pub fn uniform_buffer(&self) -> Ref<DynamicUniformBuffer<QuadUniforms>> {\n        self.uniform_buffer.borrow()\n    }\n\n    pub fn uniform_buffer_mut(&self) -> RefMut<DynamicUniformBuffer<QuadUniforms>> {\n        self.uniform_buffer.borrow_mut()\n    }\n\n    pub fn uniform_buffer_blocks(&self) -> Ref<Vec<DynamicUniformBufferBlock<QuadUniforms>>> {\n        self.uniform_buffer_blocks.borrow()\n    }\n\n    pub fn uniform_buffer_blocks_mut(\n        &self,\n    ) -> RefMut<Vec<DynamicUniformBufferBlock<QuadUniforms>>> {\n        self.uniform_buffer_blocks.borrow_mut()\n    }\n}\n\nconst BIND_GROUP_LAYOUT_ENTRIES: &[&[wgpu::BindGroupLayoutEntry]] = &[\n    &[\n        // sampler\n        wgpu::BindGroupLayoutEntry {\n            binding: 0,\n            visibility: wgpu::ShaderStage::FRAGMENT,\n            ty: wgpu::BindingType::Sampler {\n                filtering: true,\n                comparison: false,\n            },\n            count: None,\n        },\n    ],\n    &[\n        // texture\n        wgpu::BindGroupLayoutEntry {\n            binding: 0,\n            visibility: wgpu::ShaderStage::FRAGMENT,\n            ty: wgpu::BindingType::Texture {\n                view_dimension: wgpu::TextureViewDimension::D2,\n                sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                multisampled: false,\n            },\n            count: None,\n        },\n    ],\n    &[\n        // transform matrix\n        // TODO: move to push constants once they're exposed in wgpu\n        wgpu::BindGroupLayoutEntry {\n            binding: 0,\n            visibility: wgpu::ShaderStage::all(),\n            ty: wgpu::BindingType::Buffer {\n                ty: wgpu::BufferBindingType::Uniform,\n                has_dynamic_offset: true,\n                min_binding_size: NonZeroU64::new(size_of::<QuadUniforms>() as u64),\n            },\n            count: None,\n        },\n    ],\n];\n\nimpl Pipeline for QuadPipeline {\n    type VertexPushConstants = ();\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"quad\"\n    }\n\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![\n            // group 0: per-frame\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"per-frame quad bind group\"),\n                entries: BIND_GROUP_LAYOUT_ENTRIES[0],\n            },\n            // group 1: per-texture\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"per-texture quad bind group\"),\n                entries: BIND_GROUP_LAYOUT_ENTRIES[1],\n            },\n            // group 2: per-quad\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"per-texture quad bind group\"),\n                entries: BIND_GROUP_LAYOUT_ENTRIES[2],\n            },\n        ]\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/quad.vert\"))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/quad.frag\"))\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        wgpu::PrimitiveState {\n            topology: wgpu::PrimitiveTopology::TriangleList,\n            strip_index_format: None,\n            front_face: wgpu::FrontFace::Cw,\n            cull_mode: Some(wgpu::Face::Back),\n            clamp_depth: false,\n            polygon_mode: wgpu::PolygonMode::Fill,\n            conservative: false,\n        }\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        vec![wgpu::ColorTargetState {\n            format: DIFFUSE_ATTACHMENT_FORMAT,\n            blend: Some(wgpu::BlendState::REPLACE),\n            write_mask: wgpu::ColorWrite::ALL,\n        }]\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        None\n    }\n\n    // NOTE: if the vertex format is changed, this descriptor must also be changed accordingly.\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        vec![wgpu::VertexBufferLayout {\n            array_stride: size_of::<QuadVertex>() as u64,\n            step_mode: wgpu::InputStepMode::Vertex,\n            attributes: &VERTEX_BUFFER_ATTRIBUTES[..],\n        }]\n    }\n}\n\n#[repr(C, align(256))]\n#[derive(Clone, Copy, Debug)]\npub struct QuadUniforms {\n    transform: Matrix4<f32>,\n}\n\npub struct QuadTexture {\n    #[allow(dead_code)]\n    texture: wgpu::Texture,\n    #[allow(dead_code)]\n    texture_view: wgpu::TextureView,\n    bind_group: wgpu::BindGroup,\n    width: u32,\n    height: u32,\n}\n\nimpl QuadTexture {\n    pub fn from_qpic(state: &GraphicsState, qpic: &QPic) -> QuadTexture {\n        let (diffuse_data, _) = state.palette().translate(qpic.indices());\n        let texture = state.create_texture(\n            None,\n            qpic.width(),\n            qpic.height(),\n            &TextureData::Diffuse(diffuse_data),\n        );\n        let texture_view = texture.create_view(&Default::default());\n        let bind_group = state\n            .device()\n            .create_bind_group(&wgpu::BindGroupDescriptor {\n                label: None,\n                layout: &state.quad_pipeline().bind_group_layouts()[1],\n                entries: &[wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::TextureView(&texture_view),\n                }],\n            });\n\n        QuadTexture {\n            texture,\n            texture_view,\n            bind_group,\n            width: qpic.width(),\n            height: qpic.height(),\n        }\n    }\n\n    pub fn width(&self) -> u32 {\n        self.width\n    }\n\n    pub fn height(&self) -> u32 {\n        self.height\n    }\n\n    pub fn scale_width(&self, scale: f32) -> u32 {\n        (self.width as f32 * scale) as u32\n    }\n\n    pub fn scale_height(&self, scale: f32) -> u32 {\n        (self.height as f32 * scale) as u32\n    }\n}\n\n/// A command which specifies how a quad should be rendered.\npub struct QuadRendererCommand<'a> {\n    /// The texture to be mapped to the quad.\n    pub texture: &'a QuadTexture,\n\n    /// The layout specifying the size and position of the quad on the screen.\n    pub layout: Layout,\n}\n\npub struct QuadRenderer {\n    sampler_bind_group: wgpu::BindGroup,\n    transform_bind_group: wgpu::BindGroup,\n}\n\nimpl QuadRenderer {\n    pub fn new(state: &GraphicsState) -> QuadRenderer {\n        let sampler_bind_group = state\n            .device()\n            .create_bind_group(&wgpu::BindGroupDescriptor {\n                label: Some(\"quad sampler bind group\"),\n                layout: &state.quad_pipeline().bind_group_layouts()[0],\n                entries: &[wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::Sampler(state.diffuse_sampler()),\n                }],\n            });\n        let transform_bind_group = state\n            .device()\n            .create_bind_group(&wgpu::BindGroupDescriptor {\n                label: Some(\"quad transform bind group\"),\n                layout: &state.quad_pipeline().bind_group_layouts()[2],\n                entries: &[wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {\n                        buffer: state.quad_pipeline().uniform_buffer().buffer(),\n                        offset: 0,\n                        size: Some(NonZeroU64::new(size_of::<QuadUniforms>() as u64).unwrap()),\n                    }),\n                }],\n            });\n\n        QuadRenderer {\n            sampler_bind_group,\n            transform_bind_group,\n        }\n    }\n\n    fn generate_uniforms<'cmds>(\n        &self,\n        commands: &[QuadRendererCommand<'cmds>],\n        target_size: Extent2d,\n    ) -> Vec<QuadUniforms> {\n        let mut uniforms = Vec::new();\n\n        for cmd in commands {\n            let QuadRendererCommand {\n                texture,\n                layout:\n                    Layout {\n                        position,\n                        anchor,\n                        size,\n                    },\n            } = *cmd;\n\n            let scale = match size {\n                Size::Scale { factor } => factor,\n                _ => 1.0,\n            };\n\n            let Extent2d {\n                width: display_width,\n                height: display_height,\n            } = target_size;\n\n            let (screen_x, screen_y) = position.to_xy(display_width, display_height, scale);\n            let (quad_x, quad_y) = anchor.to_xy(texture.width, texture.height);\n            let x = screen_x - (quad_x as f32 * scale) as i32;\n            let y = screen_y - (quad_y as f32 * scale) as i32;\n            let (quad_width, quad_height) =\n                size.to_wh(texture.width, texture.height, display_width, display_height);\n\n            uniforms.push(QuadUniforms {\n                transform: screen_space_vertex_transform(\n                    display_width,\n                    display_height,\n                    quad_width,\n                    quad_height,\n                    x,\n                    y,\n                ),\n            });\n        }\n\n        uniforms\n    }\n\n    pub fn record_draw<'pass, 'cmds>(\n        &'pass self,\n        state: &'pass GraphicsState,\n        pass: &mut wgpu::RenderPass<'pass>,\n        target_size: Extent2d,\n        commands: &'pass [QuadRendererCommand<'pass>],\n    ) {\n        // update uniform buffer\n        let uniforms = self.generate_uniforms(commands, target_size);\n        uniform::clear_and_rewrite(\n            state.queue(),\n            &mut state.quad_pipeline().uniform_buffer_mut(),\n            &mut state.quad_pipeline().uniform_buffer_blocks_mut(),\n            &uniforms,\n        );\n\n        pass.set_pipeline(state.quad_pipeline().pipeline());\n        pass.set_vertex_buffer(0, state.quad_pipeline().vertex_buffer().slice(..));\n        pass.set_bind_group(0, &self.sampler_bind_group, &[]);\n        for (cmd, block) in commands\n            .iter()\n            .zip(state.quad_pipeline().uniform_buffer_blocks().iter())\n        {\n            pass.set_bind_group(1, &cmd.texture.bind_group, &[]);\n            pass.set_bind_group(2, &self.transform_bind_group, &[block.offset()]);\n            pass.draw(0..6, 0..1);\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/uniform.rs",
    "content": "use std::{\n    cell::{Cell, RefCell},\n    marker::PhantomData,\n    mem::{align_of, size_of},\n    rc::Rc,\n};\n\nuse crate::common::util::{any_as_bytes, Pod};\n\nuse failure::Error;\n\n// minimum limit is 16384:\n// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#limits-maxUniformBufferRange\n// but https://vulkan.gpuinfo.org/displaydevicelimit.php?name=maxUniformBufferRange&platform=windows\n// indicates that a limit of 65536 or higher is more common\nconst DYNAMIC_UNIFORM_BUFFER_SIZE: wgpu::BufferAddress = 65536;\n\n// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#limits-minUniformBufferOffsetAlignment\npub const DYNAMIC_UNIFORM_BUFFER_ALIGNMENT: usize = 256;\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug)]\npub struct UniformBool {\n    value: u32,\n}\n\nimpl UniformBool {\n    pub fn new(value: bool) -> UniformBool {\n        UniformBool {\n            value: value as u32,\n        }\n    }\n}\n\n// uniform float array elements are aligned as if they were vec4s\n#[repr(C, align(16))]\n#[derive(Clone, Copy, Debug)]\npub struct UniformArrayFloat {\n    value: f32,\n}\n\nimpl UniformArrayFloat {\n    pub fn new(value: f32) -> UniformArrayFloat {\n        UniformArrayFloat { value }\n    }\n}\n\n/// A handle to a dynamic uniform buffer on the GPU.\n///\n/// Allows allocation and updating of individual blocks of memory.\npub struct DynamicUniformBuffer<T>\nwhere\n    T: Pod,\n{\n    // keeps track of how many blocks are allocated so we know whether we can\n    // clear the buffer or not\n    _rc: RefCell<Rc<()>>,\n\n    // represents the data in the buffer, which we don't actually own\n    _phantom: PhantomData<T>,\n\n    inner: wgpu::Buffer,\n    allocated: Cell<u64>,\n    update_buf: Vec<u8>,\n}\n\nimpl<T> DynamicUniformBuffer<T>\nwhere\n    T: Pod,\n{\n    pub fn new<'b>(device: &'b wgpu::Device) -> DynamicUniformBuffer<T> {\n        // TODO: is this something we can enforce at compile time?\n        assert!(align_of::<T>() % DYNAMIC_UNIFORM_BUFFER_ALIGNMENT == 0);\n\n        let inner = device.create_buffer(&wgpu::BufferDescriptor {\n            label: Some(\"dynamic uniform buffer\"),\n            size: DYNAMIC_UNIFORM_BUFFER_SIZE,\n            usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,\n            mapped_at_creation: false,\n        });\n\n        let mut update_buf = Vec::with_capacity(DYNAMIC_UNIFORM_BUFFER_SIZE as usize);\n        update_buf.resize(DYNAMIC_UNIFORM_BUFFER_SIZE as usize, 0);\n\n        DynamicUniformBuffer {\n            _rc: RefCell::new(Rc::new(())),\n            _phantom: PhantomData,\n            inner,\n            allocated: Cell::new(0),\n            update_buf,\n        }\n    }\n\n    pub fn block_size(&self) -> wgpu::BufferSize {\n        std::num::NonZeroU64::new(\n            ((DYNAMIC_UNIFORM_BUFFER_ALIGNMENT / 8).max(size_of::<T>())) as u64,\n        )\n        .unwrap()\n    }\n\n    /// Allocates a block of memory in this dynamic uniform buffer with the\n    /// specified initial value.\n    #[must_use]\n    pub fn allocate(&mut self, val: T) -> DynamicUniformBufferBlock<T> {\n        let allocated = self.allocated.get();\n        let size = self.block_size().get();\n        trace!(\n            \"Allocating dynamic uniform block (allocated: {})\",\n            allocated\n        );\n        if allocated + size > DYNAMIC_UNIFORM_BUFFER_SIZE {\n            panic!(\n                \"Not enough space to allocate {} bytes in dynamic uniform buffer\",\n                size\n            );\n        }\n\n        let addr = allocated;\n        self.allocated.set(allocated + size);\n\n        let block = DynamicUniformBufferBlock {\n            _rc: self._rc.borrow().clone(),\n            _phantom: PhantomData,\n            addr,\n        };\n\n        self.write_block(&block, val);\n        block\n    }\n\n    pub fn write_block(&mut self, block: &DynamicUniformBufferBlock<T>, val: T) {\n        let start = block.addr as usize;\n        let end = start + self.block_size().get() as usize;\n        let slice = &mut self.update_buf[start..end];\n        slice.copy_from_slice(unsafe { any_as_bytes(&val) });\n    }\n\n    /// Removes all allocations from the underlying buffer.\n    ///\n    /// Returns an error if the buffer is currently mapped or there are\n    /// outstanding allocated blocks.\n    pub fn clear(&self) -> Result<(), Error> {\n        let out = self._rc.replace(Rc::new(()));\n        match Rc::try_unwrap(out) {\n            // no outstanding blocks\n            Ok(()) => {\n                self.allocated.set(0);\n                Ok(())\n            }\n            Err(rc) => {\n                let _ = self._rc.replace(rc);\n                bail!(\"Can't clear uniform buffer: there are outstanding references to allocated blocks.\");\n            }\n        }\n    }\n\n    pub fn flush(&self, queue: &wgpu::Queue) {\n        queue.write_buffer(&self.inner, 0, &self.update_buf);\n    }\n\n    pub fn buffer(&self) -> &wgpu::Buffer {\n        &self.inner\n    }\n}\n\n/// An address into a dynamic uniform buffer.\n#[derive(Debug)]\npub struct DynamicUniformBufferBlock<T> {\n    _rc: Rc<()>,\n    _phantom: PhantomData<T>,\n\n    addr: wgpu::BufferAddress,\n}\n\nimpl<T> DynamicUniformBufferBlock<T> {\n    pub fn offset(&self) -> wgpu::DynamicOffset {\n        self.addr as wgpu::DynamicOffset\n    }\n}\n\npub fn clear_and_rewrite<T>(\n    queue: &wgpu::Queue,\n    buffer: &mut DynamicUniformBuffer<T>,\n    blocks: &mut Vec<DynamicUniformBufferBlock<T>>,\n    uniforms: &[T],\n) where\n    T: Pod,\n{\n    blocks.clear();\n    buffer.clear().unwrap();\n    for (uni_id, uni) in uniforms.iter().enumerate() {\n        if uni_id >= blocks.len() {\n            let block = buffer.allocate(*uni);\n            blocks.push(block);\n        } else {\n            buffer.write_block(&blocks[uni_id], *uni);\n        }\n    }\n    buffer.flush(queue);\n}\n"
  },
  {
    "path": "src/client/render/warp.rs",
    "content": "use std::cmp::Ordering;\n\nuse crate::common::math;\n\nuse cgmath::{InnerSpace, Vector2, Vector3};\n\n// TODO: make this a cvar\nconst SUBDIVIDE_SIZE: f32 = 32.0;\n\n/// Subdivide the given polygon on a grid.\n///\n/// The algorithm is described as follows:\n/// Given a polygon *P*,\n/// 1. Calculate the extents *P*min, *P*max and the midpoint *P*mid of *P*.\n/// 1. Calculate the distance vector *D*<sub>i</sub> for each *P*<sub>i</sub>.\n/// 1. For each axis *A* = [X, Y, Z]:\n///    1. If the distance between either *P*min<sub>A</sub> or\n///       *P*max<sub>A</sub> and *P*mid<sub>A</sub> is less than 8, continue to\n///       the next axis.\n///    1. For each vertex *v*...\n/// TODO...\npub fn subdivide(verts: Vec<Vector3<f32>>) -> Vec<Vector3<f32>> {\n    let mut out = Vec::new();\n    subdivide_impl(verts, &mut out);\n    out\n}\n\nfn subdivide_impl(mut verts: Vec<Vector3<f32>>, output: &mut Vec<Vector3<f32>>) {\n    let (min, max) = math::bounds(&verts);\n\n    let mut front = Vec::new();\n    let mut back = Vec::new();\n\n    // subdivide polygon along each axis in order\n    for ax in 0..3 {\n        // find the midpoint of the polygon bounds\n        let mid = {\n            let m = (min[ax] + max[ax]) / 2.0;\n            SUBDIVIDE_SIZE * (m / SUBDIVIDE_SIZE).round()\n        };\n\n        if max[ax] - mid < 8.0 || mid - min[ax] < 8.0 {\n            // this component doesn't need to be subdivided further.\n            // if no components need to be subdivided further, this breaks the loop.\n            continue;\n        }\n\n        // collect the distances of each vertex from the midpoint\n        let mut dist: Vec<f32> = verts.iter().map(|v| (*v)[ax] - mid).collect();\n        dist.push(dist[0]);\n\n        // duplicate first vertex\n        verts.push(verts[0]);\n        for (vi, v) in (&verts[..verts.len() - 1]).iter().enumerate() {\n            // sort vertices to front and back of axis\n            let cmp = dist[vi].partial_cmp(&0.0).unwrap();\n            match cmp {\n                Ordering::Less => {\n                    back.push(*v);\n                }\n                Ordering::Equal => {\n                    // if this vertex is on the axis, split it in two\n                    front.push(*v);\n                    back.push(*v);\n                    continue;\n                }\n                Ordering::Greater => {\n                    front.push(*v);\n                }\n            }\n\n            if dist[vi + 1] != 0.0 && cmp != dist[vi + 1].partial_cmp(&0.0).unwrap() {\n                // segment crosses the axis, add a vertex at the intercept\n                let ratio = dist[vi] / (dist[vi] - dist[vi + 1]);\n                let intercept = v + ratio * (verts[vi + 1] - v);\n                front.push(intercept);\n                back.push(intercept);\n            }\n        }\n\n        subdivide_impl(front, output);\n        subdivide_impl(back, output);\n        return;\n    }\n\n    // polygon is smaller than SUBDIVIDE_SIZE along all three axes\n    assert!(verts.len() >= 3);\n    let v1 = verts[0];\n    let mut v2 = verts[1];\n    for v3 in &verts[2..] {\n        output.push(v1);\n        output.push(v2);\n        output.push(*v3);\n        v2 = *v3;\n    }\n}\n"
  },
  {
    "path": "src/client/render/world/alias.rs",
    "content": "use std::{mem::size_of, ops::Range};\n\nuse crate::{\n    client::render::{\n        world::{BindGroupLayoutId, WorldPipelineBase},\n        GraphicsState, Pipeline, TextureData,\n    },\n    common::{\n        mdl::{self, AliasModel},\n        util::any_slice_as_bytes,\n    },\n};\n\nuse cgmath::{InnerSpace as _, Matrix4, Vector3, Zero as _};\nuse chrono::Duration;\nuse failure::Error;\n\npub struct AliasPipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n}\n\nimpl AliasPipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        world_bind_group_layouts: &[wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) -> AliasPipeline {\n        let (pipeline, bind_group_layouts) =\n            AliasPipeline::create(device, compiler, world_bind_group_layouts, sample_count);\n\n        AliasPipeline {\n            pipeline,\n            bind_group_layouts,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        world_bind_group_layouts: &[wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) {\n        let layout_refs: Vec<_> = world_bind_group_layouts\n            .iter()\n            .chain(self.bind_group_layouts.iter())\n            .collect();\n        self.pipeline = AliasPipeline::recreate(device, compiler, &layout_refs, sample_count);\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n}\n\n#[repr(C)]\n#[derive(Copy, Clone, Debug)]\npub struct VertexPushConstants {\n    pub transform: Matrix4<f32>,\n    pub model_view: Matrix4<f32>,\n}\n\nlazy_static! {\n    static ref VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 3] =\n        wgpu::vertex_attr_array![\n            // frame 0 position\n            0 => Float32x3,\n            // frame 1 position\n            // 1 => Float32x3,\n            // normal\n            2 => Float32x3,\n            // texcoord\n            3 => Float32x2,\n        ];\n}\n\nimpl Pipeline for AliasPipeline {\n    type VertexPushConstants = VertexPushConstants;\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"alias\"\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/alias.vert\"))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/alias.frag\"))\n    }\n\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![\n            // group 2: updated per-texture\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"brush per-texture chain bind group\"),\n                entries: &[\n                    // diffuse texture, updated once per face\n                    wgpu::BindGroupLayoutEntry {\n                        binding: 0,\n                        visibility: wgpu::ShaderStage::FRAGMENT,\n                        ty: wgpu::BindingType::Texture {\n                            view_dimension: wgpu::TextureViewDimension::D2,\n                            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                            multisampled: false,\n                        },\n                        count: None,\n                    },\n                ],\n            },\n        ]\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        WorldPipelineBase::primitive_state()\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        WorldPipelineBase::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        WorldPipelineBase::depth_stencil_state()\n    }\n\n    // NOTE: if the vertex format is changed, this descriptor must also be changed accordingly.\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        vec![wgpu::VertexBufferLayout {\n            array_stride: size_of::<AliasVertex>() as u64,\n            step_mode: wgpu::InputStepMode::Vertex,\n            attributes: &VERTEX_ATTRIBUTES[..],\n        }]\n    }\n}\n\n// these type aliases are here to aid readability of e.g. size_of::<Position>()\ntype Position = [f32; 3];\ntype Normal = [f32; 3];\ntype DiffuseTexcoord = [f32; 2];\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug)]\nstruct AliasVertex {\n    position: Position,\n    normal: Normal,\n    diffuse_texcoord: DiffuseTexcoord,\n}\n\nenum Keyframe {\n    Static {\n        vertex_range: Range<u32>,\n    },\n    Animated {\n        vertex_ranges: Vec<Range<u32>>,\n        total_duration: Duration,\n        durations: Vec<Duration>,\n    },\n}\n\nimpl Keyframe {\n    fn animate(&self, time: Duration) -> Range<u32> {\n        match self {\n            Keyframe::Static { vertex_range } => vertex_range.clone(),\n            Keyframe::Animated {\n                vertex_ranges,\n                total_duration,\n                durations,\n            } => {\n                let mut time_ms = time.num_milliseconds() % total_duration.num_milliseconds();\n\n                for (frame_id, frame_duration) in durations.iter().enumerate() {\n                    time_ms -= frame_duration.num_milliseconds();\n                    if time_ms <= 0 {\n                        return vertex_ranges[frame_id].clone();\n                    }\n                }\n\n                unreachable!()\n            }\n        }\n    }\n}\n\nenum Texture {\n    Static {\n        diffuse_texture: wgpu::Texture,\n        diffuse_view: wgpu::TextureView,\n        bind_group: wgpu::BindGroup,\n    },\n    Animated {\n        diffuse_textures: Vec<wgpu::Texture>,\n        diffuse_views: Vec<wgpu::TextureView>,\n        bind_groups: Vec<wgpu::BindGroup>,\n        total_duration: Duration,\n        durations: Vec<Duration>,\n    },\n}\n\nimpl Texture {\n    fn animate(&self, time: Duration) -> &wgpu::BindGroup {\n        match self {\n            Texture::Static { ref bind_group, .. } => bind_group,\n            Texture::Animated {\n                diffuse_textures,\n                diffuse_views,\n                bind_groups,\n                total_duration,\n                durations,\n            } => {\n                let mut time_ms = time.num_milliseconds() % total_duration.num_milliseconds();\n\n                for (frame_id, frame_duration) in durations.iter().enumerate() {\n                    time_ms -= frame_duration.num_milliseconds();\n                    if time_ms <= 0 {\n                        return &bind_groups[frame_id];\n                    }\n                }\n\n                unreachable!()\n            }\n        }\n    }\n}\n\npub struct AliasRenderer {\n    keyframes: Vec<Keyframe>,\n    textures: Vec<Texture>,\n    vertex_buffer: wgpu::Buffer,\n}\n\nimpl AliasRenderer {\n    pub fn new(state: &GraphicsState, alias_model: &AliasModel) -> Result<AliasRenderer, Error> {\n        let mut vertices = Vec::new();\n        let mut keyframes = Vec::new();\n\n        let w = alias_model.texture_width();\n        let h = alias_model.texture_height();\n        for keyframe in alias_model.keyframes() {\n            match *keyframe {\n                mdl::Keyframe::Static(ref static_keyframe) => {\n                    let vertex_start = vertices.len() as u32;\n                    for polygon in alias_model.polygons() {\n                        let mut tri = [Vector3::zero(); 3];\n                        let mut texcoords = [[0.0; 2]; 3];\n                        for (i, index) in polygon.indices().iter().enumerate() {\n                            tri[i] = static_keyframe.vertices()[*index as usize].into();\n\n                            let texcoord = &alias_model.texcoords()[*index as usize];\n                            let s = if !polygon.faces_front() && texcoord.is_on_seam() {\n                                (texcoord.s() + w / 2) as f32 + 0.5\n                            } else {\n                                texcoord.s() as f32 + 0.5\n                            } / w as f32;\n                            let t = (texcoord.t() as f32 + 0.5) / h as f32;\n                            texcoords[i] = [s, t];\n                        }\n\n                        let normal = (tri[0] - tri[1]).cross(tri[2] - tri[1]).normalize();\n\n                        for i in 0..3 {\n                            vertices.push(AliasVertex {\n                                position: tri[i].into(),\n                                normal: normal.into(),\n                                diffuse_texcoord: texcoords[i],\n                            });\n                        }\n                    }\n                    let vertex_end = vertices.len() as u32;\n\n                    keyframes.push(Keyframe::Static {\n                        vertex_range: vertex_start..vertex_end,\n                    });\n                }\n\n                mdl::Keyframe::Animated(ref kf) => {\n                    let mut durations = Vec::new();\n                    let mut vertex_ranges = Vec::new();\n\n                    for frame in kf.frames() {\n                        durations.push(frame.duration());\n\n                        let vertex_start = vertices.len() as u32;\n                        for polygon in alias_model.polygons() {\n                            let mut tri = [Vector3::zero(); 3];\n                            let mut texcoords = [[0.0; 2]; 3];\n                            for (i, index) in polygon.indices().iter().enumerate() {\n                                tri[i] = frame.vertices()[*index as usize].into();\n\n                                let texcoord = &alias_model.texcoords()[*index as usize];\n                                let s = if !polygon.faces_front() && texcoord.is_on_seam() {\n                                    (texcoord.s() + w / 2) as f32 + 0.5\n                                } else {\n                                    texcoord.s() as f32 + 0.5\n                                } / w as f32;\n                                let t = (texcoord.t() as f32 + 0.5) / h as f32;\n                                texcoords[i] = [s, t];\n                            }\n\n                            let normal = (tri[0] - tri[1]).cross(tri[2] - tri[1]).normalize();\n\n                            for i in 0..3 {\n                                vertices.push(AliasVertex {\n                                    position: tri[i].into(),\n                                    normal: normal.into(),\n                                    diffuse_texcoord: texcoords[i],\n                                });\n                            }\n                        }\n                        let vertex_end = vertices.len() as u32;\n                        vertex_ranges.push(vertex_start..vertex_end);\n                    }\n\n                    let total_duration = durations.iter().fold(Duration::zero(), |s, d| s + *d);\n                    keyframes.push(Keyframe::Animated {\n                        vertex_ranges,\n                        durations,\n                        total_duration,\n                    });\n                }\n            }\n        }\n\n        use wgpu::util::DeviceExt as _;\n        let vertex_buffer = state\n            .device()\n            .create_buffer_init(&wgpu::util::BufferInitDescriptor {\n                label: None,\n                contents: unsafe { any_slice_as_bytes(vertices.as_slice()) },\n                usage: wgpu::BufferUsage::VERTEX,\n            });\n\n        let mut textures = Vec::new();\n        for texture in alias_model.textures() {\n            match *texture {\n                mdl::Texture::Static(ref tex) => {\n                    let (diffuse_data, _fullbright_data) = state.palette.translate(tex.indices());\n                    let diffuse_texture =\n                        state.create_texture(None, w, h, &TextureData::Diffuse(diffuse_data));\n                    let diffuse_view = diffuse_texture.create_view(&Default::default());\n                    let bind_group = state\n                        .device()\n                        .create_bind_group(&wgpu::BindGroupDescriptor {\n                            label: None,\n                            // TODO: per-pipeline bind group layout ids\n                            layout: &state.alias_pipeline().bind_group_layouts()\n                                [BindGroupLayoutId::PerTexture as usize - 2],\n                            entries: &[wgpu::BindGroupEntry {\n                                binding: 0,\n                                resource: wgpu::BindingResource::TextureView(&diffuse_view),\n                            }],\n                        });\n                    textures.push(Texture::Static {\n                        diffuse_texture,\n                        diffuse_view,\n                        bind_group,\n                    });\n                }\n                mdl::Texture::Animated(ref tex) => {\n                    let mut total_duration = Duration::zero();\n                    let mut durations = Vec::new();\n                    let mut diffuse_textures = Vec::new();\n                    let mut diffuse_views = Vec::new();\n                    let mut bind_groups = Vec::new();\n\n                    for frame in tex.frames() {\n                        total_duration = total_duration + frame.duration();\n                        durations.push(frame.duration());\n\n                        let (diffuse_data, _fullbright_data) =\n                            state.palette.translate(frame.indices());\n                        let diffuse_texture =\n                            state.create_texture(None, w, h, &TextureData::Diffuse(diffuse_data));\n                        let diffuse_view = diffuse_texture.create_view(&Default::default());\n                        let bind_group =\n                            state\n                                .device()\n                                .create_bind_group(&wgpu::BindGroupDescriptor {\n                                    label: None,\n                                    layout: &state.alias_pipeline().bind_group_layouts()\n                                        [BindGroupLayoutId::PerTexture as usize - 2],\n                                    entries: &[wgpu::BindGroupEntry {\n                                        binding: 0,\n                                        resource: wgpu::BindingResource::TextureView(&diffuse_view),\n                                    }],\n                                });\n\n                        diffuse_textures.push(diffuse_texture);\n                        diffuse_views.push(diffuse_view);\n                        bind_groups.push(bind_group);\n                    }\n\n                    textures.push(Texture::Animated {\n                        diffuse_textures,\n                        diffuse_views,\n                        bind_groups,\n                        total_duration,\n                        durations,\n                    });\n                }\n            }\n        }\n\n        Ok(AliasRenderer {\n            keyframes,\n            textures,\n            vertex_buffer,\n        })\n    }\n\n    pub fn record_draw<'a>(\n        &'a self,\n        state: &'a GraphicsState,\n        pass: &mut wgpu::RenderPass<'a>,\n        time: Duration,\n        keyframe_id: usize,\n        texture_id: usize,\n    ) {\n        pass.set_pipeline(state.alias_pipeline().pipeline());\n        pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));\n\n        pass.set_bind_group(\n            BindGroupLayoutId::PerTexture as u32,\n            self.textures[texture_id].animate(time),\n            &[],\n        );\n        pass.draw(self.keyframes[keyframe_id].animate(time), 0..1)\n    }\n}\n"
  },
  {
    "path": "src/client/render/world/brush.rs",
    "content": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::{\n    borrow::Cow,\n    cell::{Cell, RefCell},\n    collections::HashMap,\n    mem::size_of,\n    num::NonZeroU32,\n    ops::Range,\n    rc::Rc,\n};\n\nuse crate::{\n    client::render::{\n        pipeline::PushConstantUpdate,\n        warp,\n        world::{BindGroupLayoutId, WorldPipelineBase},\n        Camera, GraphicsState, LightmapData, Pipeline, TextureData,\n    },\n    common::{\n        bsp::{\n            self, BspData, BspFace, BspLeaf, BspModel, BspTexInfo, BspTexture, BspTextureKind,\n            BspTextureMipmap,\n        },\n        math,\n        util::any_slice_as_bytes,\n    },\n};\n\nuse bumpalo::Bump;\nuse cgmath::{InnerSpace as _, Matrix4, Vector3};\nuse chrono::Duration;\nuse failure::Error;\n\npub struct BrushPipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n}\n\nimpl BrushPipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        queue: &wgpu::Queue,\n        compiler: &mut shaderc::Compiler,\n        world_bind_group_layouts: &[wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) -> BrushPipeline {\n        let (pipeline, bind_group_layouts) =\n            BrushPipeline::create(device, compiler, world_bind_group_layouts, sample_count);\n\n        BrushPipeline {\n            pipeline,\n            // TODO: pick a starting capacity\n            bind_group_layouts,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        world_bind_group_layouts: &[wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) {\n        let layout_refs: Vec<_> = world_bind_group_layouts\n            .iter()\n            .chain(self.bind_group_layouts.iter())\n            .collect();\n        self.pipeline = BrushPipeline::recreate(device, compiler, &layout_refs, sample_count);\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn bind_group_layout(&self, id: BindGroupLayoutId) -> &wgpu::BindGroupLayout {\n        assert!(id as usize >= BindGroupLayoutId::PerTexture as usize);\n        &self.bind_group_layouts[id as usize - BindGroupLayoutId::PerTexture as usize]\n    }\n}\n\n#[repr(C)]\n#[derive(Copy, Clone, Debug)]\npub struct VertexPushConstants {\n    pub transform: Matrix4<f32>,\n    pub model_view: Matrix4<f32>,\n}\n\n#[repr(C)]\n#[derive(Copy, Clone, Debug)]\npub struct SharedPushConstants {\n    pub texture_kind: u32,\n}\n\nconst BIND_GROUP_LAYOUT_ENTRIES: &[&[wgpu::BindGroupLayoutEntry]] = &[\n    &[\n        // diffuse texture, updated once per face\n        wgpu::BindGroupLayoutEntry {\n            binding: 0,\n            visibility: wgpu::ShaderStage::FRAGMENT,\n            ty: wgpu::BindingType::Texture {\n                view_dimension: wgpu::TextureViewDimension::D2,\n                sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                multisampled: false,\n            },\n            count: None,\n        },\n        // fullbright texture\n        wgpu::BindGroupLayoutEntry {\n            binding: 1,\n            visibility: wgpu::ShaderStage::FRAGMENT,\n            ty: wgpu::BindingType::Texture {\n                view_dimension: wgpu::TextureViewDimension::D2,\n                sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                multisampled: false,\n            },\n            count: None,\n        },\n    ],\n    &[\n        // lightmap texture array\n        wgpu::BindGroupLayoutEntry {\n            count: NonZeroU32::new(4),\n            binding: 0,\n            visibility: wgpu::ShaderStage::FRAGMENT,\n            ty: wgpu::BindingType::Texture {\n                view_dimension: wgpu::TextureViewDimension::D2,\n                sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                multisampled: false,\n            },\n        },\n    ],\n];\n\nlazy_static! {\n    static ref VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 5] =\n        wgpu::vertex_attr_array![\n            // position\n            0 => Float32x3,\n            // normal\n            1 => Float32x3,\n            // diffuse texcoord\n            2 => Float32x2,\n            // lightmap texcoord\n            3 => Float32x2,\n            // lightmap animation ids\n            4 => Uint8x4,\n        ];\n}\n\nimpl Pipeline for BrushPipeline {\n    type VertexPushConstants = VertexPushConstants;\n    type SharedPushConstants = SharedPushConstants;\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"brush\"\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/brush.vert\"))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/brush.frag\"))\n    }\n\n    // NOTE: if any of the binding indices are changed, they must also be changed in\n    // the corresponding shaders and the BindGroupLayout generation functions.\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![\n            // group 2: updated per-texture\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"brush per-texture bind group\"),\n                entries: BIND_GROUP_LAYOUT_ENTRIES[0],\n            },\n            // group 3: updated per-face\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"brush per-face bind group\"),\n                entries: BIND_GROUP_LAYOUT_ENTRIES[1],\n            },\n        ]\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        WorldPipelineBase::primitive_state()\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        WorldPipelineBase::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        WorldPipelineBase::depth_stencil_state()\n    }\n\n    // NOTE: if the vertex format is changed, this descriptor must also be changed accordingly.\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        vec![wgpu::VertexBufferLayout {\n            array_stride: size_of::<BrushVertex>() as u64,\n            step_mode: wgpu::InputStepMode::Vertex,\n            attributes: &VERTEX_ATTRIBUTES[..],\n        }]\n    }\n}\n\nfn calculate_lightmap_texcoords(\n    position: Vector3<f32>,\n    face: &BspFace,\n    texinfo: &BspTexInfo,\n) -> [f32; 2] {\n    let mut s = texinfo.s_vector.dot(position) + texinfo.s_offset;\n    s -= (face.texture_mins[0] as f32 / 16.0).floor() * 16.0;\n    s += 0.5;\n    s /= face.extents[0] as f32;\n\n    let mut t = texinfo.t_vector.dot(position) + texinfo.t_offset;\n    t -= (face.texture_mins[1] as f32 / 16.0).floor() * 16.0;\n    t += 0.5;\n    t /= face.extents[1] as f32;\n    [s, t]\n}\n\ntype Position = [f32; 3];\ntype Normal = [f32; 3];\ntype DiffuseTexcoord = [f32; 2];\ntype LightmapTexcoord = [f32; 2];\ntype LightmapAnim = [u8; 4];\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug)]\nstruct BrushVertex {\n    position: Position,\n    normal: Normal,\n    diffuse_texcoord: DiffuseTexcoord,\n    lightmap_texcoord: LightmapTexcoord,\n    lightmap_anim: LightmapAnim,\n}\n\n#[repr(u32)]\n#[derive(Clone, Copy, Debug)]\npub enum TextureKind {\n    Normal = 0,\n    Warp = 1,\n    Sky = 2,\n}\n\n/// A single frame of a brush texture.\npub struct BrushTextureFrame {\n    bind_group_id: usize,\n    diffuse: wgpu::Texture,\n    fullbright: wgpu::Texture,\n    diffuse_view: wgpu::TextureView,\n    fullbright_view: wgpu::TextureView,\n    kind: TextureKind,\n}\n\n/// A brush texture.\npub enum BrushTexture {\n    /// A brush texture with a single frame.\n    Static(BrushTextureFrame),\n\n    /// A brush texture with multiple frames.\n    ///\n    /// Animated brush textures advance one frame every 200 milliseconds, i.e.,\n    /// they have a framerate of 5 fps.\n    Animated {\n        primary: Vec<BrushTextureFrame>,\n        alternate: Option<Vec<BrushTextureFrame>>,\n    },\n}\n\nimpl BrushTexture {\n    fn kind(&self) -> TextureKind {\n        match self {\n            BrushTexture::Static(ref frame) => frame.kind,\n            BrushTexture::Animated { ref primary, .. } => primary[0].kind,\n        }\n    }\n}\n\n#[derive(Debug)]\nstruct BrushFace {\n    vertices: Range<u32>,\n    min: Vector3<f32>,\n    max: Vector3<f32>,\n\n    texture_id: usize,\n\n    lightmap_ids: Vec<usize>,\n    light_styles: [u8; 4],\n\n    /// Indicates whether the face should be drawn this frame.\n    ///\n    /// This is set to false by default, and will be set to true if the model is\n    /// a worldmodel and the containing leaf is in the PVS. If the model is not\n    /// a worldmodel, this flag is ignored.\n    draw_flag: Cell<bool>,\n}\n\nstruct BrushLeaf {\n    facelist_ids: Range<usize>,\n}\n\nimpl<B> std::convert::From<B> for BrushLeaf\nwhere\n    B: std::borrow::Borrow<BspLeaf>,\n{\n    fn from(bsp_leaf: B) -> Self {\n        let bsp_leaf = bsp_leaf.borrow();\n        BrushLeaf {\n            facelist_ids: bsp_leaf.facelist_id..bsp_leaf.facelist_id + bsp_leaf.facelist_count,\n        }\n    }\n}\n\npub struct BrushRendererBuilder {\n    bsp_data: Rc<BspData>,\n    face_range: Range<usize>,\n\n    leaves: Option<Vec<BrushLeaf>>,\n\n    per_texture_bind_groups: RefCell<Vec<wgpu::BindGroup>>,\n    per_face_bind_groups: Vec<wgpu::BindGroup>,\n\n    vertices: Vec<BrushVertex>,\n    faces: Vec<BrushFace>,\n    texture_chains: HashMap<usize, Vec<usize>>,\n    textures: Vec<BrushTexture>,\n    lightmaps: Vec<wgpu::Texture>,\n    //lightmap_views: Vec<wgpu::TextureView>,\n}\n\nimpl BrushRendererBuilder {\n    pub fn new(bsp_model: &BspModel, worldmodel: bool) -> BrushRendererBuilder {\n        BrushRendererBuilder {\n            bsp_data: bsp_model.bsp_data().clone(),\n            face_range: bsp_model.face_id..bsp_model.face_id + bsp_model.face_count,\n            leaves: if worldmodel {\n                Some(\n                    bsp_model\n                        .iter_leaves()\n                        .map(|leaf| BrushLeaf::from(leaf))\n                        .collect(),\n                )\n            } else {\n                None\n            },\n            per_texture_bind_groups: RefCell::new(Vec::new()),\n            per_face_bind_groups: Vec::new(),\n            vertices: Vec::new(),\n            faces: Vec::new(),\n            texture_chains: HashMap::new(),\n            textures: Vec::new(),\n            lightmaps: Vec::new(),\n            //lightmap_views: Vec::new(),\n        }\n    }\n\n    fn create_face(&mut self, state: &GraphicsState, face_id: usize) -> BrushFace {\n        let face = &self.bsp_data.faces()[face_id];\n        let face_vert_id = self.vertices.len();\n        let texinfo = &self.bsp_data.texinfo()[face.texinfo_id];\n        let tex = &self.bsp_data.textures()[texinfo.tex_id];\n\n        let mut min = Vector3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);\n        let mut max = Vector3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);\n\n        let no_collinear =\n            math::remove_collinear(self.bsp_data.face_iter_vertices(face_id).collect());\n\n        for vert in no_collinear.iter() {\n            for component in 0..3 {\n                min[component] = min[component].min(vert[component]);\n                max[component] = max[component].max(vert[component]);\n            }\n        }\n\n        if tex.name().starts_with(\"*\") {\n            // tessellate the surface so we can do texcoord warping\n            let verts = warp::subdivide(no_collinear);\n            let normal = (verts[0] - verts[1]).cross(verts[2] - verts[1]).normalize();\n            for vert in verts.into_iter() {\n                self.vertices.push(BrushVertex {\n                    position: vert.into(),\n                    normal: normal.into(),\n                    diffuse_texcoord: [\n                        ((vert.dot(texinfo.s_vector) + texinfo.s_offset) / tex.width() as f32),\n                        ((vert.dot(texinfo.t_vector) + texinfo.t_offset) / tex.height() as f32),\n                    ],\n                    lightmap_texcoord: calculate_lightmap_texcoords(vert.into(), face, texinfo),\n                    lightmap_anim: face.light_styles,\n                })\n            }\n        } else {\n            // expand the vertices into a triangle list.\n            // the vertices are guaranteed to be in valid triangle fan order (that's\n            // how GLQuake renders them) so we expand from triangle fan to triangle\n            // list order.\n            //\n            // v1 is the base vertex, so it remains constant.\n            // v2 takes the previous value of v3.\n            // v3 is the newest vertex.\n            let verts = no_collinear;\n            let normal = (verts[0] - verts[1]).cross(verts[2] - verts[1]).normalize();\n            let mut vert_iter = verts.into_iter();\n\n            let v1 = vert_iter.next().unwrap();\n            let mut v2 = vert_iter.next().unwrap();\n            for v3 in vert_iter {\n                let tri = &[v1, v2, v3];\n\n                // skip collinear points\n                for vert in tri.iter() {\n                    self.vertices.push(BrushVertex {\n                        position: (*vert).into(),\n                        normal: normal.into(),\n                        diffuse_texcoord: [\n                            ((vert.dot(texinfo.s_vector) + texinfo.s_offset) / tex.width() as f32),\n                            ((vert.dot(texinfo.t_vector) + texinfo.t_offset) / tex.height() as f32),\n                        ],\n                        lightmap_texcoord: calculate_lightmap_texcoords(\n                            (*vert).into(),\n                            face,\n                            texinfo,\n                        ),\n                        lightmap_anim: face.light_styles,\n                    });\n                }\n\n                v2 = v3;\n            }\n        }\n\n        // build the lightmaps\n        let lightmaps = if !texinfo.special {\n            self.bsp_data.face_lightmaps(face_id)\n        } else {\n            Vec::new()\n        };\n\n        let mut lightmap_ids = Vec::new();\n        for lightmap in lightmaps {\n            let lightmap_data = TextureData::Lightmap(LightmapData {\n                lightmap: Cow::Borrowed(lightmap.data()),\n            });\n\n            let texture =\n                state.create_texture(None, lightmap.width(), lightmap.height(), &lightmap_data);\n\n            let id = self.lightmaps.len();\n            self.lightmaps.push(texture);\n            //self.lightmap_views\n            //.push(self.lightmaps[id].create_view(&Default::default()));\n            lightmap_ids.push(id);\n        }\n\n        BrushFace {\n            vertices: face_vert_id as u32..self.vertices.len() as u32,\n            min,\n            max,\n            texture_id: texinfo.tex_id as usize,\n            lightmap_ids,\n            light_styles: face.light_styles,\n            draw_flag: Cell::new(true),\n        }\n    }\n\n    fn create_per_texture_bind_group(\n        &self,\n        state: &GraphicsState,\n        tex: &BrushTextureFrame,\n    ) -> wgpu::BindGroup {\n        let layout = &state\n            .brush_pipeline()\n            .bind_group_layout(BindGroupLayoutId::PerTexture);\n        let desc = wgpu::BindGroupDescriptor {\n            label: Some(\"per-texture bind group\"),\n            layout,\n            entries: &[\n                wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::TextureView(&tex.diffuse_view),\n                },\n                wgpu::BindGroupEntry {\n                    binding: 1,\n                    resource: wgpu::BindingResource::TextureView(&tex.fullbright_view),\n                },\n            ],\n        };\n        state.device().create_bind_group(&desc)\n    }\n\n    fn create_per_face_bind_group(&self, state: &GraphicsState, face_id: usize) -> wgpu::BindGroup {\n        let mut lightmap_views: Vec<_> = self.faces[face_id]\n            .lightmap_ids\n            .iter()\n            .map(|id| self.lightmaps[*id].create_view(&Default::default()))\n            .collect();\n        lightmap_views.resize_with(4, || {\n            state.default_lightmap().create_view(&Default::default())\n        });\n\n        let lightmap_view_refs = lightmap_views.iter().collect::<Vec<_>>();\n\n        let layout = &state\n            .brush_pipeline()\n            .bind_group_layout(BindGroupLayoutId::PerFace);\n        let desc = wgpu::BindGroupDescriptor {\n            label: Some(\"per-face bind group\"),\n            layout,\n            entries: &[wgpu::BindGroupEntry {\n                binding: 0,\n                resource: wgpu::BindingResource::TextureViewArray(&lightmap_view_refs[..]),\n            }],\n        };\n        state.device().create_bind_group(&desc)\n    }\n\n    fn create_brush_texture_frame<S>(\n        &self,\n        state: &GraphicsState,\n        mipmap: &[u8],\n        width: u32,\n        height: u32,\n        name: S,\n    ) -> BrushTextureFrame\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n\n        let (diffuse_data, fullbright_data) = state.palette().translate(mipmap);\n        let diffuse =\n            state.create_texture(None, width, height, &TextureData::Diffuse(diffuse_data));\n        let fullbright = state.create_texture(\n            None,\n            width,\n            height,\n            &TextureData::Fullbright(fullbright_data),\n        );\n\n        let diffuse_view = diffuse.create_view(&Default::default());\n        let fullbright_view = fullbright.create_view(&Default::default());\n\n        let kind = if name.starts_with(\"sky\") {\n            TextureKind::Sky\n        } else if name.starts_with(\"*\") {\n            TextureKind::Warp\n        } else {\n            TextureKind::Normal\n        };\n\n        let mut frame = BrushTextureFrame {\n            bind_group_id: 0,\n            diffuse,\n            fullbright,\n            diffuse_view,\n            fullbright_view,\n            kind,\n        };\n\n        // generate texture bind group\n        let per_texture_bind_group = self.create_per_texture_bind_group(state, &frame);\n        let bind_group_id = self.per_texture_bind_groups.borrow().len();\n        self.per_texture_bind_groups\n            .borrow_mut()\n            .push(per_texture_bind_group);\n\n        frame.bind_group_id = bind_group_id;\n        frame\n    }\n\n    pub fn create_brush_texture(&self, state: &GraphicsState, tex: &BspTexture) -> BrushTexture {\n        // TODO: upload mipmaps\n        let (width, height) = tex.dimensions();\n\n        match tex.kind() {\n            // sequence animated textures\n            BspTextureKind::Animated { primary, alternate } => {\n                let primary_frames: Vec<_> = primary\n                    .iter()\n                    .map(|f| {\n                        self.create_brush_texture_frame(\n                            state,\n                            f.mipmap(BspTextureMipmap::Full),\n                            width,\n                            height,\n                            tex.name(),\n                        )\n                    })\n                    .collect();\n\n                let alternate_frames: Option<Vec<_>> = alternate.as_ref().map(|a| {\n                    a.iter()\n                        .map(|f| {\n                            self.create_brush_texture_frame(\n                                state,\n                                f.mipmap(BspTextureMipmap::Full),\n                                width,\n                                height,\n                                tex.name(),\n                            )\n                        })\n                        .collect()\n                });\n\n                BrushTexture::Animated {\n                    primary: primary_frames,\n                    alternate: alternate_frames,\n                }\n            }\n\n            BspTextureKind::Static(bsp_tex) => {\n                BrushTexture::Static(self.create_brush_texture_frame(\n                    state,\n                    bsp_tex.mipmap(BspTextureMipmap::Full),\n                    tex.width(),\n                    tex.height(),\n                    tex.name(),\n                ))\n            }\n        }\n    }\n\n    pub fn build(mut self, state: &GraphicsState) -> Result<BrushRenderer, Error> {\n        // create the diffuse and fullbright textures\n        for tex in self.bsp_data.textures().iter() {\n            self.textures.push(self.create_brush_texture(state, tex));\n        }\n\n        // generate faces, vertices and lightmaps\n        // bsp_face_id is the id of the face in the bsp data\n        // face_id is the new id of the face in the renderer\n        for bsp_face_id in self.face_range.start..self.face_range.end {\n            let face_id = self.faces.len();\n            let face = self.create_face(state, bsp_face_id);\n            self.faces.push(face);\n\n            let face_tex_id = self.faces[face_id].texture_id;\n            // update the corresponding texture chain\n            self.texture_chains\n                .entry(face_tex_id)\n                .or_insert(Vec::new())\n                .push(face_id);\n\n            // generate face bind group\n            let per_face_bind_group = self.create_per_face_bind_group(state, face_id);\n            self.per_face_bind_groups.push(per_face_bind_group);\n        }\n\n        use wgpu::util::DeviceExt as _;\n        let vertex_buffer = state\n            .device()\n            .create_buffer_init(&wgpu::util::BufferInitDescriptor {\n                label: None,\n                contents: unsafe { any_slice_as_bytes(self.vertices.as_slice()) },\n                usage: wgpu::BufferUsage::VERTEX,\n            });\n\n        Ok(BrushRenderer {\n            bsp_data: self.bsp_data,\n            vertex_buffer,\n            leaves: self.leaves,\n            per_texture_bind_groups: self.per_texture_bind_groups.into_inner(),\n            per_face_bind_groups: self.per_face_bind_groups,\n            texture_chains: self.texture_chains,\n            faces: self.faces,\n            textures: self.textures,\n            lightmaps: self.lightmaps,\n            //lightmap_views: self.lightmap_views,\n        })\n    }\n}\n\npub struct BrushRenderer {\n    bsp_data: Rc<BspData>,\n\n    leaves: Option<Vec<BrushLeaf>>,\n\n    vertex_buffer: wgpu::Buffer,\n    per_texture_bind_groups: Vec<wgpu::BindGroup>,\n    per_face_bind_groups: Vec<wgpu::BindGroup>,\n\n    // faces are grouped by texture to reduce the number of texture rebinds\n    // texture_chains maps texture ids to face ids\n    texture_chains: HashMap<usize, Vec<usize>>,\n    faces: Vec<BrushFace>,\n    textures: Vec<BrushTexture>,\n    lightmaps: Vec<wgpu::Texture>,\n    //lightmap_views: Vec<wgpu::TextureView>,\n}\n\nimpl BrushRenderer {\n    /// Record the draw commands for this brush model to the given `wgpu::RenderPass`.\n    pub fn record_draw<'a>(\n        &'a self,\n        state: &'a GraphicsState,\n        pass: &mut wgpu::RenderPass<'a>,\n        bump: &'a Bump,\n        time: Duration,\n        camera: &Camera,\n        frame_id: usize,\n    ) {\n        pass.set_pipeline(state.brush_pipeline().pipeline());\n        pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));\n\n        // if this is a worldmodel, mark faces to be drawn\n        if let Some(ref leaves) = self.leaves {\n            let pvs = self\n                .bsp_data\n                .get_pvs(self.bsp_data.find_leaf(camera.origin), leaves.len());\n\n            // only draw faces in pvs\n            for leaf_id in pvs {\n                for facelist_id in leaves[leaf_id].facelist_ids.clone() {\n                    let face = &self.faces[self.bsp_data.facelist()[facelist_id]];\n\n                    // TODO: frustum culling\n                    face.draw_flag.set(true);\n                }\n            }\n        }\n\n        for (tex_id, face_ids) in self.texture_chains.iter() {\n            use PushConstantUpdate::*;\n            BrushPipeline::set_push_constants(\n                pass,\n                Retain,\n                Update(bump.alloc(SharedPushConstants {\n                    texture_kind: self.textures[*tex_id].kind() as u32,\n                })),\n                Retain,\n            );\n\n            let bind_group_id = match &self.textures[*tex_id] {\n                BrushTexture::Static(ref frame) => frame.bind_group_id,\n                BrushTexture::Animated { primary, alternate } => {\n                    // if frame is not zero and this texture has an alternate\n                    // animation, use it\n                    let anim = if frame_id == 0 {\n                        primary\n                    } else if let Some(a) = alternate {\n                        a\n                    } else {\n                        primary\n                    };\n\n                    let time_ms = time.num_milliseconds();\n                    let total_ms = (bsp::frame_duration() * anim.len() as i32).num_milliseconds();\n                    let anim_ms = if total_ms == 0 { 0 } else { time_ms % total_ms };\n                    anim[(anim_ms / bsp::frame_duration().num_milliseconds()) as usize]\n                        .bind_group_id\n                }\n            };\n\n            pass.set_bind_group(\n                BindGroupLayoutId::PerTexture as u32,\n                &self.per_texture_bind_groups[bind_group_id],\n                &[],\n            );\n\n            for face_id in face_ids.iter() {\n                let face = &self.faces[*face_id];\n\n                // only skip the face if we have visibility data but it's not marked\n                if self.leaves.is_some() && !face.draw_flag.replace(false) {\n                    continue;\n                }\n\n                pass.set_bind_group(\n                    BindGroupLayoutId::PerFace as u32,\n                    &self.per_face_bind_groups[*face_id],\n                    &[],\n                );\n\n                pass.draw(face.vertices.clone(), 0..1);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/render/world/deferred.rs",
    "content": "use std::{mem::size_of, num::NonZeroU64};\n\nuse cgmath::{Matrix4, SquareMatrix as _, Vector3, Zero as _};\n\nuse crate::{\n    client::{\n        entity::MAX_LIGHTS,\n        render::{pipeline::Pipeline, ui::quad::QuadPipeline, GraphicsState},\n    },\n    common::util::any_as_bytes,\n};\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug)]\npub struct PointLight {\n    pub origin: Vector3<f32>,\n    pub radius: f32,\n}\n\n#[repr(C, align(256))]\n#[derive(Clone, Copy, Debug)]\npub struct DeferredUniforms {\n    pub inv_projection: [[f32; 4]; 4],\n    pub light_count: u32,\n    pub _pad: [u32; 3],\n    pub lights: [PointLight; MAX_LIGHTS],\n}\n\npub struct DeferredPipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    uniform_buffer: wgpu::Buffer,\n}\n\nimpl DeferredPipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) -> DeferredPipeline {\n        let (pipeline, bind_group_layouts) =\n            DeferredPipeline::create(device, compiler, &[], sample_count);\n\n        use wgpu::util::DeviceExt as _;\n        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {\n            label: None,\n            contents: unsafe {\n                any_as_bytes(&DeferredUniforms {\n                    inv_projection: Matrix4::identity().into(),\n                    light_count: 0,\n                    _pad: [0; 3],\n                    lights: [PointLight {\n                        origin: Vector3::zero(),\n                        radius: 0.0,\n                    }; MAX_LIGHTS],\n                })\n            },\n            usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,\n        });\n\n        DeferredPipeline {\n            pipeline,\n            bind_group_layouts,\n            uniform_buffer,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) {\n        let layout_refs: Vec<_> = self.bind_group_layouts.iter().collect();\n        let pipeline = DeferredPipeline::recreate(device, compiler, &layout_refs, sample_count);\n        self.pipeline = pipeline;\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn uniform_buffer(&self) -> &wgpu::Buffer {\n        &self.uniform_buffer\n    }\n}\n\nconst BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[\n    // sampler\n    wgpu::BindGroupLayoutEntry {\n        binding: 0,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Sampler {\n            filtering: true,\n            comparison: false,\n        },\n        count: None,\n    },\n    // color buffer\n    wgpu::BindGroupLayoutEntry {\n        binding: 1,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Texture {\n            view_dimension: wgpu::TextureViewDimension::D2,\n            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n            multisampled: true,\n        },\n        count: None,\n    },\n    // normal buffer\n    wgpu::BindGroupLayoutEntry {\n        binding: 2,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Texture {\n            view_dimension: wgpu::TextureViewDimension::D2,\n            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n            multisampled: true,\n        },\n        count: None,\n    },\n    // light buffer\n    wgpu::BindGroupLayoutEntry {\n        binding: 3,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Texture {\n            view_dimension: wgpu::TextureViewDimension::D2,\n            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n            multisampled: true,\n        },\n        count: None,\n    },\n    // depth buffer\n    wgpu::BindGroupLayoutEntry {\n        binding: 4,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Texture {\n            view_dimension: wgpu::TextureViewDimension::D2,\n            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n            multisampled: true,\n        },\n        count: None,\n    },\n    // uniform buffer\n    wgpu::BindGroupLayoutEntry {\n        binding: 5,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Buffer {\n            ty: wgpu::BufferBindingType::Uniform,\n            has_dynamic_offset: false,\n            min_binding_size: NonZeroU64::new(size_of::<DeferredUniforms>() as u64),\n        },\n        count: None,\n    },\n];\n\nimpl Pipeline for DeferredPipeline {\n    type VertexPushConstants = ();\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"deferred\"\n    }\n\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![wgpu::BindGroupLayoutDescriptor {\n            label: Some(\"deferred bind group\"),\n            entries: BIND_GROUP_LAYOUT_ENTRIES,\n        }]\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(\n            env!(\"CARGO_MANIFEST_DIR\"),\n            \"/shaders/deferred.vert\"\n        ))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(\n            env!(\"CARGO_MANIFEST_DIR\"),\n            \"/shaders/deferred.frag\"\n        ))\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        QuadPipeline::primitive_state()\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        QuadPipeline::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        None\n    }\n\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        QuadPipeline::vertex_buffer_layouts()\n    }\n}\n\npub struct DeferredRenderer {\n    bind_group: wgpu::BindGroup,\n}\n\nimpl DeferredRenderer {\n    fn create_bind_group(\n        state: &GraphicsState,\n        diffuse_buffer: &wgpu::TextureView,\n        normal_buffer: &wgpu::TextureView,\n        light_buffer: &wgpu::TextureView,\n        depth_buffer: &wgpu::TextureView,\n    ) -> wgpu::BindGroup {\n        state\n            .device()\n            .create_bind_group(&wgpu::BindGroupDescriptor {\n                label: Some(\"deferred bind group\"),\n                layout: &state.deferred_pipeline().bind_group_layouts()[0],\n                entries: &[\n                    // sampler\n                    wgpu::BindGroupEntry {\n                        binding: 0,\n                        resource: wgpu::BindingResource::Sampler(state.diffuse_sampler()),\n                    },\n                    // diffuse buffer\n                    wgpu::BindGroupEntry {\n                        binding: 1,\n                        resource: wgpu::BindingResource::TextureView(diffuse_buffer),\n                    },\n                    // normal buffer\n                    wgpu::BindGroupEntry {\n                        binding: 2,\n                        resource: wgpu::BindingResource::TextureView(normal_buffer),\n                    },\n                    // light buffer\n                    wgpu::BindGroupEntry {\n                        binding: 3,\n                        resource: wgpu::BindingResource::TextureView(light_buffer),\n                    },\n                    // depth buffer\n                    wgpu::BindGroupEntry {\n                        binding: 4,\n                        resource: wgpu::BindingResource::TextureView(depth_buffer),\n                    },\n                    // uniform buffer\n                    wgpu::BindGroupEntry {\n                        binding: 5,\n                        resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {\n                            buffer: state.deferred_pipeline().uniform_buffer(),\n                            offset: 0,\n                            size: None,\n                        }),\n                    },\n                ],\n            })\n    }\n\n    pub fn new(\n        state: &GraphicsState,\n        diffuse_buffer: &wgpu::TextureView,\n        normal_buffer: &wgpu::TextureView,\n        light_buffer: &wgpu::TextureView,\n        depth_buffer: &wgpu::TextureView,\n    ) -> DeferredRenderer {\n        let bind_group = Self::create_bind_group(\n            state,\n            diffuse_buffer,\n            normal_buffer,\n            light_buffer,\n            depth_buffer,\n        );\n\n        DeferredRenderer { bind_group }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        state: &GraphicsState,\n        diffuse_buffer: &wgpu::TextureView,\n        normal_buffer: &wgpu::TextureView,\n        light_buffer: &wgpu::TextureView,\n        depth_buffer: &wgpu::TextureView,\n    ) {\n        self.bind_group = Self::create_bind_group(\n            state,\n            diffuse_buffer,\n            normal_buffer,\n            light_buffer,\n            depth_buffer,\n        );\n    }\n\n    pub fn update_uniform_buffers(&self, state: &GraphicsState, uniforms: DeferredUniforms) {\n        // update color shift\n        state\n            .queue()\n            .write_buffer(state.deferred_pipeline().uniform_buffer(), 0, unsafe {\n                any_as_bytes(&uniforms)\n            });\n    }\n\n    pub fn record_draw<'pass>(\n        &'pass self,\n        state: &'pass GraphicsState,\n        pass: &mut wgpu::RenderPass<'pass>,\n        uniforms: DeferredUniforms,\n    ) {\n        self.update_uniform_buffers(state, uniforms);\n        pass.set_pipeline(state.deferred_pipeline().pipeline());\n        pass.set_vertex_buffer(0, state.quad_pipeline().vertex_buffer().slice(..));\n        pass.set_bind_group(0, &self.bind_group, &[]);\n        pass.draw(0..6, 0..1);\n    }\n}\n"
  },
  {
    "path": "src/client/render/world/mod.rs",
    "content": "pub mod alias;\npub mod brush;\npub mod deferred;\npub mod particle;\npub mod postprocess;\npub mod sprite;\n\nuse std::{cell::RefCell, mem::size_of};\n\nuse crate::{\n    client::{\n        entity::particle::Particle,\n        render::{\n            pipeline::{Pipeline, PushConstantUpdate},\n            uniform::{DynamicUniformBufferBlock, UniformArrayFloat, UniformBool},\n            world::{\n                alias::{AliasPipeline, AliasRenderer},\n                brush::{BrushPipeline, BrushRenderer, BrushRendererBuilder},\n                sprite::{SpritePipeline, SpriteRenderer},\n            },\n            GraphicsState, DEPTH_ATTACHMENT_FORMAT, DIFFUSE_ATTACHMENT_FORMAT,\n            LIGHT_ATTACHMENT_FORMAT, NORMAL_ATTACHMENT_FORMAT,\n        },\n        ClientEntity,\n    },\n    common::{\n        console::CvarRegistry,\n        engine,\n        math::Angles,\n        model::{Model, ModelKind},\n        sprite::SpriteKind,\n        util::any_as_bytes,\n    },\n};\n\nuse bumpalo::Bump;\nuse cgmath::{Euler, InnerSpace, Matrix4, SquareMatrix as _, Vector3, Vector4};\nuse chrono::Duration;\n\nlazy_static! {\n    static ref BIND_GROUP_LAYOUT_DESCRIPTOR_BINDINGS: [Vec<wgpu::BindGroupLayoutEntry>; 2] = [\n        vec![\n            wgpu::BindGroupLayoutEntry {\n                binding:0,\n                visibility:wgpu::ShaderStage::all(),\n                ty:wgpu::BindingType::Buffer {\n                    ty: wgpu::BufferBindingType::Uniform,\n                    has_dynamic_offset: false,\n                    min_binding_size:\n                        std::num::NonZeroU64::new(size_of::<FrameUniforms>() as u64)\n                },\n                count:None,\n            },\n        ],\n        vec![\n            // transform matrix\n            // TODO: move this to push constants once they're exposed in wgpu\n            wgpu::BindGroupLayoutEntry {\n                binding:0,\n                visibility:wgpu::ShaderStage::VERTEX,\n                ty:wgpu::BindingType::Buffer {\n                    ty: wgpu::BufferBindingType::Uniform,\n                    has_dynamic_offset: true,\n                    min_binding_size:\n                        std::num::NonZeroU64::new(size_of::<EntityUniforms>() as u64)\n                },\n                count:None,\n            },\n            // diffuse and fullbright sampler\n            wgpu::BindGroupLayoutEntry {\n                binding:1,\n                visibility:wgpu::ShaderStage::FRAGMENT,\n                ty:wgpu::BindingType::Sampler { filtering: true, comparison: false },\n                count:None,\n            },\n            // lightmap sampler\n            wgpu::BindGroupLayoutEntry {\n                binding:2,\n                visibility:wgpu::ShaderStage::FRAGMENT,\n                ty:wgpu::BindingType::Sampler { filtering: true, comparison: false },\n                count:None,\n            },\n        ],\n    ];\n\n    pub static ref BIND_GROUP_LAYOUT_DESCRIPTORS: [wgpu::BindGroupLayoutDescriptor<'static>; 2] = [\n        // group 0: updated per-frame\n        wgpu::BindGroupLayoutDescriptor {\n            label: Some(\"per-frame bind group\"),\n            entries: &BIND_GROUP_LAYOUT_DESCRIPTOR_BINDINGS[0],\n        },\n        // group 1: updated per-entity\n        wgpu::BindGroupLayoutDescriptor {\n            label: Some(\"brush per-entity bind group\"),\n            entries: &BIND_GROUP_LAYOUT_DESCRIPTOR_BINDINGS[1],\n        },\n    ];\n}\n\nstruct WorldPipelineBase;\n\nimpl Pipeline for WorldPipelineBase {\n    type VertexPushConstants = ();\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"world\"\n    }\n\n    fn vertex_shader() -> &'static str {\n        \"\"\n    }\n\n    fn fragment_shader() -> &'static str {\n        \"\"\n    }\n\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        // TODO\n        vec![]\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        wgpu::PrimitiveState {\n            topology: wgpu::PrimitiveTopology::TriangleList,\n            strip_index_format: None,\n            front_face: wgpu::FrontFace::Cw,\n            cull_mode: None,\n            clamp_depth: false,\n            polygon_mode: wgpu::PolygonMode::Fill,\n            conservative: false,\n        }\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        vec![\n            // diffuse attachment\n            wgpu::ColorTargetState {\n                format: DIFFUSE_ATTACHMENT_FORMAT,\n                blend: Some(wgpu::BlendState::REPLACE),\n                write_mask: wgpu::ColorWrite::ALL,\n            },\n            // normal attachment\n            wgpu::ColorTargetState {\n                format: NORMAL_ATTACHMENT_FORMAT,\n                blend: Some(wgpu::BlendState::REPLACE),\n                write_mask: wgpu::ColorWrite::ALL,\n            },\n            // light attachment\n            wgpu::ColorTargetState {\n                format: LIGHT_ATTACHMENT_FORMAT,\n                blend: Some(wgpu::BlendState::REPLACE),\n                write_mask: wgpu::ColorWrite::ALL,\n            },\n        ]\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        Some(wgpu::DepthStencilState {\n            format: DEPTH_ATTACHMENT_FORMAT,\n            depth_write_enabled: true,\n            depth_compare: wgpu::CompareFunction::LessEqual,\n            stencil: wgpu::StencilState {\n                front: wgpu::StencilFaceState::IGNORE,\n                back: wgpu::StencilFaceState::IGNORE,\n                read_mask: 0,\n                write_mask: 0,\n            },\n            bias: wgpu::DepthBiasState {\n                constant: 0,\n                slope_scale: 0.0,\n                clamp: 0.0,\n            },\n        })\n    }\n\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        Vec::new()\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub enum BindGroupLayoutId {\n    PerFrame = 0,\n    PerEntity = 1,\n    PerTexture = 2,\n    PerFace = 3,\n}\n\npub struct Camera {\n    origin: Vector3<f32>,\n    angles: Angles,\n    view: Matrix4<f32>,\n    view_projection: Matrix4<f32>,\n    projection: Matrix4<f32>,\n    inverse_projection: Matrix4<f32>,\n    clipping_planes: [Vector4<f32>; 6],\n}\n\nimpl Camera {\n    pub fn new(origin: Vector3<f32>, angles: Angles, projection: Matrix4<f32>) -> Camera {\n        // convert coordinates\n        let converted_origin = Vector3::new(-origin.y, origin.z, -origin.x);\n\n        // translate the world by inverse of camera position\n        let translation = Matrix4::from_translation(-converted_origin);\n        let rotation = angles.mat4_wgpu();\n        let view = rotation * translation;\n        let view_projection = projection * view;\n\n        // see https://www.gamedevs.org/uploads/fast-extraction-viewing-frustum-planes-from-world-view-projection-matrix.pdf\n        let clipping_planes = [\n            // left\n            view_projection.w + view_projection.x,\n            // right\n            view_projection.w - view_projection.x,\n            // bottom\n            view_projection.w + view_projection.y,\n            // top\n            view_projection.w - view_projection.y,\n            // near\n            view_projection.w + view_projection.z,\n            // far\n            view_projection.w - view_projection.z,\n        ];\n\n        Camera {\n            origin,\n            angles,\n            view,\n            view_projection,\n            projection,\n            inverse_projection: projection.invert().unwrap(),\n            clipping_planes,\n        }\n    }\n\n    pub fn origin(&self) -> Vector3<f32> {\n        self.origin\n    }\n\n    pub fn angles(&self) -> Angles {\n        self.angles\n    }\n\n    pub fn view(&self) -> Matrix4<f32> {\n        self.view\n    }\n\n    pub fn view_projection(&self) -> Matrix4<f32> {\n        self.view_projection\n    }\n\n    pub fn projection(&self) -> Matrix4<f32> {\n        self.projection\n    }\n\n    pub fn inverse_projection(&self) -> Matrix4<f32> {\n        self.inverse_projection\n    }\n\n    // TODO: this seems to be too lenient\n    /// Determines whether a point falls outside the viewing frustum.\n    pub fn cull_point(&self, p: Vector3<f32>) -> bool {\n        for plane in self.clipping_planes.iter() {\n            if (self.view_projection() * p.extend(1.0)).dot(*plane) < 0.0 {\n                return true;\n            }\n        }\n\n        false\n    }\n}\n\n#[repr(C, align(256))]\n#[derive(Copy, Clone)]\n// TODO: derive Debug once const generics are stable\npub struct FrameUniforms {\n    // TODO: pack frame values into a [Vector4<f32>; 16],\n    lightmap_anim_frames: [UniformArrayFloat; 64],\n    camera_pos: Vector4<f32>,\n    time: f32,\n\n    // TODO: pack flags into a bit string\n    r_lightmap: UniformBool,\n}\n\n#[repr(C, align(256))]\n#[derive(Clone, Copy, Debug)]\npub struct EntityUniforms {\n    /// Model-view-projection transform matrix\n    transform: Matrix4<f32>,\n\n    /// Model-only transform matrix\n    model: Matrix4<f32>,\n}\n\nenum EntityRenderer {\n    Alias(AliasRenderer),\n    Brush(BrushRenderer),\n    Sprite(SpriteRenderer),\n    None,\n}\n\n/// Top-level renderer.\npub struct WorldRenderer {\n    worldmodel_renderer: BrushRenderer,\n    entity_renderers: Vec<EntityRenderer>,\n\n    world_uniform_block: DynamicUniformBufferBlock<EntityUniforms>,\n    entity_uniform_blocks: RefCell<Vec<DynamicUniformBufferBlock<EntityUniforms>>>,\n}\n\nimpl WorldRenderer {\n    pub fn new(state: &GraphicsState, models: &[Model], worldmodel_id: usize) -> WorldRenderer {\n        let mut worldmodel_renderer = None;\n        let mut entity_renderers = Vec::new();\n\n        let world_uniform_block = state.entity_uniform_buffer_mut().allocate(EntityUniforms {\n            transform: Matrix4::identity(),\n            model: Matrix4::identity(),\n        });\n\n        for (i, model) in models.iter().enumerate() {\n            if i == worldmodel_id {\n                match *model.kind() {\n                    ModelKind::Brush(ref bmodel) => {\n                        worldmodel_renderer = Some(\n                            BrushRendererBuilder::new(bmodel, true)\n                                .build(state)\n                                .unwrap(),\n                        );\n                    }\n                    _ => panic!(\"Invalid worldmodel\"),\n                }\n            } else {\n                match *model.kind() {\n                    ModelKind::Alias(ref amodel) => entity_renderers.push(EntityRenderer::Alias(\n                        AliasRenderer::new(state, amodel).unwrap(),\n                    )),\n\n                    ModelKind::Brush(ref bmodel) => {\n                        entity_renderers.push(EntityRenderer::Brush(\n                            BrushRendererBuilder::new(bmodel, false)\n                                .build(state)\n                                .unwrap(),\n                        ));\n                    }\n\n                    ModelKind::Sprite(ref smodel) => {\n                        entity_renderers\n                            .push(EntityRenderer::Sprite(SpriteRenderer::new(&state, smodel)));\n                    }\n\n                    _ => {\n                        warn!(\"Non-brush renderers not implemented!\");\n                        entity_renderers.push(EntityRenderer::None);\n                    }\n                }\n            }\n        }\n\n        WorldRenderer {\n            worldmodel_renderer: worldmodel_renderer.unwrap(),\n            entity_renderers,\n            world_uniform_block,\n            entity_uniform_blocks: RefCell::new(Vec::new()),\n        }\n    }\n\n    pub fn update_uniform_buffers<'a, I>(\n        &self,\n        state: &GraphicsState,\n        camera: &Camera,\n        time: Duration,\n        entities: I,\n        lightstyle_values: &[f32],\n        cvars: &CvarRegistry,\n    ) where\n        I: Iterator<Item = &'a ClientEntity>,\n    {\n        trace!(\"Updating frame uniform buffer\");\n        state\n            .queue()\n            .write_buffer(state.frame_uniform_buffer(), 0, unsafe {\n                any_as_bytes(&FrameUniforms {\n                    lightmap_anim_frames: {\n                        let mut frames = [UniformArrayFloat::new(0.0); 64];\n                        for i in 0..64 {\n                            frames[i] = UniformArrayFloat::new(lightstyle_values[i]);\n                        }\n                        frames\n                    },\n                    camera_pos: camera.origin.extend(1.0),\n                    time: engine::duration_to_f32(time),\n                    r_lightmap: UniformBool::new(cvars.get_value(\"r_lightmap\").unwrap() != 0.0),\n                })\n            });\n\n        trace!(\"Updating entity uniform buffer\");\n        let world_uniforms = EntityUniforms {\n            transform: camera.view_projection(),\n            model: Matrix4::identity(),\n        };\n        state\n            .entity_uniform_buffer_mut()\n            .write_block(&self.world_uniform_block, world_uniforms);\n\n        for (ent_pos, ent) in entities.into_iter().enumerate() {\n            let ent_uniforms = EntityUniforms {\n                transform: self.calculate_mvp_transform(camera, ent),\n                model: self.calculate_model_transform(camera, ent),\n            };\n\n            if ent_pos >= self.entity_uniform_blocks.borrow().len() {\n                // if we don't have enough blocks, get a new one\n                let block = state.entity_uniform_buffer_mut().allocate(ent_uniforms);\n                self.entity_uniform_blocks.borrow_mut().push(block);\n            } else {\n                state\n                    .entity_uniform_buffer_mut()\n                    .write_block(&self.entity_uniform_blocks.borrow()[ent_pos], ent_uniforms);\n            }\n        }\n\n        state.entity_uniform_buffer().flush(state.queue());\n    }\n\n    pub fn render_pass<'a, E, P>(\n        &'a self,\n        state: &'a GraphicsState,\n        pass: &mut wgpu::RenderPass<'a>,\n        bump: &'a Bump,\n        camera: &Camera,\n        time: Duration,\n        entities: E,\n        particles: P,\n        lightstyle_values: &[f32],\n        viewmodel_id: usize,\n        cvars: &CvarRegistry,\n    ) where\n        E: Iterator<Item = &'a ClientEntity> + Clone,\n        P: Iterator<Item = &'a Particle>,\n    {\n        use PushConstantUpdate::*;\n        info!(\"Updating uniform buffers\");\n        self.update_uniform_buffers(\n            state,\n            camera,\n            time,\n            entities.clone(),\n            lightstyle_values,\n            cvars,\n        );\n\n        pass.set_bind_group(\n            BindGroupLayoutId::PerFrame as u32,\n            &state.world_bind_groups()[BindGroupLayoutId::PerFrame as usize],\n            &[],\n        );\n\n        // draw world\n        info!(\"Drawing world\");\n        pass.set_pipeline(state.brush_pipeline().pipeline());\n        BrushPipeline::set_push_constants(\n            pass,\n            Update(bump.alloc(brush::VertexPushConstants {\n                transform: camera.view_projection(),\n                model_view: camera.view(),\n            })),\n            Clear,\n            Clear,\n        );\n        pass.set_bind_group(\n            BindGroupLayoutId::PerEntity as u32,\n            &state.world_bind_groups()[BindGroupLayoutId::PerEntity as usize],\n            &[self.world_uniform_block.offset()],\n        );\n        self.worldmodel_renderer\n            .record_draw(state, pass, &bump, time, camera, 0);\n\n        // draw entities\n        info!(\"Drawing entities\");\n        for (ent_pos, ent) in entities.enumerate() {\n            pass.set_bind_group(\n                BindGroupLayoutId::PerEntity as u32,\n                &state.world_bind_groups()[BindGroupLayoutId::PerEntity as usize],\n                &[self.entity_uniform_blocks.borrow()[ent_pos].offset()],\n            );\n\n            match self.renderer_for_entity(&ent) {\n                EntityRenderer::Brush(ref bmodel) => {\n                    pass.set_pipeline(state.brush_pipeline().pipeline());\n                    BrushPipeline::set_push_constants(\n                        pass,\n                        Update(bump.alloc(brush::VertexPushConstants {\n                            transform: self.calculate_mvp_transform(camera, ent),\n                            model_view: self.calculate_mv_transform(camera, ent),\n                        })),\n                        Clear,\n                        Clear,\n                    );\n                    bmodel.record_draw(state, pass, &bump, time, camera, ent.frame_id);\n                }\n                EntityRenderer::Alias(ref alias) => {\n                    pass.set_pipeline(state.alias_pipeline().pipeline());\n                    AliasPipeline::set_push_constants(\n                        pass,\n                        Update(bump.alloc(alias::VertexPushConstants {\n                            transform: self.calculate_mvp_transform(camera, ent),\n                            model_view: self.calculate_mv_transform(camera, ent),\n                        })),\n                        Clear,\n                        Clear,\n                    );\n                    alias.record_draw(state, pass, time, ent.frame_id(), ent.skin_id());\n                }\n                EntityRenderer::Sprite(ref sprite) => {\n                    pass.set_pipeline(state.sprite_pipeline().pipeline());\n                    SpritePipeline::set_push_constants(pass, Clear, Clear, Clear);\n                    sprite.record_draw(state, pass, ent.frame_id(), time);\n                }\n                _ => warn!(\"non-brush renderers not implemented!\"),\n                // _ => unimplemented!(),\n            }\n        }\n\n        let viewmodel_orig = camera.origin();\n        let cam_angles = camera.angles();\n        let viewmodel_mat = Matrix4::from_translation(Vector3::new(\n            -viewmodel_orig.y,\n            viewmodel_orig.z,\n            -viewmodel_orig.x,\n        )) * Matrix4::from_angle_y(cam_angles.yaw)\n            * Matrix4::from_angle_x(-cam_angles.pitch)\n            * Matrix4::from_angle_z(cam_angles.roll);\n        match self.entity_renderers[viewmodel_id] {\n            EntityRenderer::Alias(ref alias) => {\n                pass.set_pipeline(state.alias_pipeline().pipeline());\n                AliasPipeline::set_push_constants(\n                    pass,\n                    Update(bump.alloc(alias::VertexPushConstants {\n                        transform: camera.view_projection() * viewmodel_mat,\n                        model_view: camera.view() * viewmodel_mat,\n                    })),\n                    Clear,\n                    Clear,\n                );\n                alias.record_draw(state, pass, time, 0, 0);\n            }\n\n            _ => unreachable!(\"non-alias viewmodel\"),\n        }\n\n        log::debug!(\"Drawing particles\");\n        state\n            .particle_pipeline()\n            .record_draw(pass, &bump, camera, particles);\n    }\n\n    fn renderer_for_entity(&self, ent: &ClientEntity) -> &EntityRenderer {\n        // subtract 1 from index because world entity isn't counted\n        &self.entity_renderers[ent.model_id() - 1]\n    }\n\n    fn calculate_mvp_transform(&self, camera: &Camera, entity: &ClientEntity) -> Matrix4<f32> {\n        let model_transform = self.calculate_model_transform(camera, entity);\n\n        camera.view_projection() * model_transform\n    }\n\n    fn calculate_mv_transform(&self, camera: &Camera, entity: &ClientEntity) -> Matrix4<f32> {\n        let model_transform = self.calculate_model_transform(camera, entity);\n\n        camera.view() * model_transform\n    }\n\n    fn calculate_model_transform(&self, camera: &Camera, entity: &ClientEntity) -> Matrix4<f32> {\n        let origin = entity.get_origin();\n        let angles = entity.get_angles();\n        let rotation = match self.renderer_for_entity(entity) {\n            EntityRenderer::Sprite(ref sprite) => match sprite.kind() {\n                // used for decals\n                SpriteKind::Oriented => Matrix4::from(Euler::new(angles.z, -angles.x, angles.y)),\n\n                _ => {\n                    // keep sprite facing player, but preserve roll\n                    let cam_angles = camera.angles();\n\n                    Angles {\n                        pitch: -cam_angles.pitch,\n                        roll: angles.x,\n                        yaw: -cam_angles.yaw,\n                    }\n                    .mat4_quake()\n                }\n            },\n\n            _ => Matrix4::from(Euler::new(angles.x, angles.y, angles.z)),\n        };\n\n        Matrix4::from_translation(Vector3::new(-origin.y, origin.z, -origin.x)) * rotation\n    }\n}\n"
  },
  {
    "path": "src/client/render/world/particle.rs",
    "content": "use std::{\n    mem::size_of,\n    num::{NonZeroU32, NonZeroU8},\n};\n\nuse crate::{\n    client::{\n        entity::particle::Particle,\n        render::{\n            create_texture,\n            pipeline::{Pipeline, PushConstantUpdate},\n            world::{Camera, WorldPipelineBase},\n            Palette, TextureData,\n        },\n    },\n    common::{math::Angles, util::any_slice_as_bytes},\n};\n\nuse bumpalo::Bump;\nuse cgmath::Matrix4;\n\nlazy_static! {\n    static ref VERTEX_BUFFER_ATTRIBUTES: [Vec<wgpu::VertexAttribute>; 1] = [\n        wgpu::vertex_attr_array![\n            // position\n            0 => Float32x3,\n            // texcoord\n            1 => Float32x2,\n        ].to_vec(),\n    ];\n}\n\n#[rustfmt::skip]\nconst PARTICLE_TEXTURE_PIXELS: [u8; 64] = [\n    0, 0, 1, 1, 1, 1, 0, 0,\n    0, 1, 1, 1, 1, 1, 1, 0,\n    1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1,\n    0, 1, 1, 1, 1, 1, 1, 0,\n    0, 0, 1, 1, 1, 1, 0, 0,\n];\n\npub struct ParticlePipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    vertex_buffer: wgpu::Buffer,\n    sampler: wgpu::Sampler,\n    textures: Vec<wgpu::Texture>,\n    texture_views: Vec<wgpu::TextureView>,\n    bind_group: wgpu::BindGroup,\n}\n\nimpl ParticlePipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        queue: &wgpu::Queue,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n        palette: &Palette,\n    ) -> ParticlePipeline {\n        let (pipeline, bind_group_layouts) =\n            ParticlePipeline::create(device, compiler, &[], sample_count);\n\n        use wgpu::util::DeviceExt as _;\n        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {\n            label: None,\n            contents: unsafe { any_slice_as_bytes(&VERTICES) },\n            usage: wgpu::BufferUsage::VERTEX,\n        });\n\n        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {\n            label: Some(\"particle sampler\"),\n            address_mode_u: wgpu::AddressMode::ClampToEdge,\n            address_mode_v: wgpu::AddressMode::ClampToEdge,\n            address_mode_w: wgpu::AddressMode::ClampToEdge,\n            mag_filter: wgpu::FilterMode::Nearest,\n            min_filter: wgpu::FilterMode::Linear,\n            mipmap_filter: wgpu::FilterMode::Linear,\n            lod_min_clamp: -1000.0,\n            lod_max_clamp: 1000.0,\n            compare: None,\n            anisotropy_clamp: NonZeroU8::new(16),\n            border_color: None,\n        });\n\n        let textures: Vec<wgpu::Texture> = (0..256)\n            .map(|i| {\n                let mut pixels = PARTICLE_TEXTURE_PIXELS;\n\n                // set up palette translation\n                for pix in pixels.iter_mut() {\n                    if *pix == 0 {\n                        *pix = 0xFF;\n                    } else {\n                        *pix *= i as u8;\n                    }\n                }\n\n                let (diffuse_data, _) = palette.translate(&pixels);\n\n                create_texture(\n                    device,\n                    queue,\n                    Some(&format!(\"particle texture {}\", i)),\n                    8,\n                    8,\n                    &TextureData::Diffuse(diffuse_data),\n                )\n            })\n            .collect();\n        let texture_views: Vec<wgpu::TextureView> = textures\n            .iter()\n            .map(|t| t.create_view(&Default::default()))\n            .collect();\n        let texture_view_refs = texture_views.iter().collect::<Vec<_>>();\n\n        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {\n            label: Some(\"particle bind group\"),\n            layout: &bind_group_layouts[0],\n            entries: &[\n                wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::Sampler(&sampler),\n                },\n                wgpu::BindGroupEntry {\n                    binding: 1,\n                    resource: wgpu::BindingResource::TextureViewArray(&texture_view_refs[..]),\n                },\n            ],\n        });\n\n        ParticlePipeline {\n            pipeline,\n            bind_group_layouts,\n            sampler,\n            textures,\n            texture_views,\n            bind_group,\n            vertex_buffer,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) {\n        let layout_refs: Vec<_> = self.bind_group_layouts.iter().collect();\n        self.pipeline = ParticlePipeline::recreate(device, compiler, &layout_refs, sample_count);\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn vertex_buffer(&self) -> &wgpu::Buffer {\n        &self.vertex_buffer\n    }\n\n    pub fn record_draw<'a, 'b, P>(\n        &'a self,\n        pass: &mut wgpu::RenderPass<'a>,\n        bump: &'a Bump,\n        camera: &Camera,\n        particles: P,\n    ) where\n        P: Iterator<Item = &'b Particle>,\n    {\n        use PushConstantUpdate::*;\n\n        pass.set_pipeline(self.pipeline());\n        pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));\n        pass.set_bind_group(0, &self.bind_group, &[]);\n\n        // face toward camera\n        let Angles { pitch, yaw, roll } = camera.angles();\n        let rotation = Angles {\n            pitch: -pitch,\n            yaw: -yaw,\n            roll: -roll,\n        }\n        .mat4_wgpu();\n\n        for particle in particles {\n            let q_origin = particle.origin();\n            let translation =\n                Matrix4::from_translation([-q_origin.y, q_origin.z, -q_origin.x].into());\n            Self::set_push_constants(\n                pass,\n                Update(bump.alloc(VertexPushConstants {\n                    transform: camera.view_projection() * translation * rotation,\n                })),\n                Retain,\n                Update(bump.alloc(FragmentPushConstants {\n                    color: particle.color() as u32,\n                })),\n            );\n\n            pass.draw(0..6, 0..1);\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct VertexPushConstants {\n    pub transform: Matrix4<f32>,\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct FragmentPushConstants {\n    pub color: u32,\n}\n\nconst BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[\n    wgpu::BindGroupLayoutEntry {\n        binding: 0,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Sampler {\n            filtering: true,\n            comparison: false,\n        },\n        count: None,\n    },\n    // per-index texture array\n    wgpu::BindGroupLayoutEntry {\n        binding: 1,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Texture {\n            view_dimension: wgpu::TextureViewDimension::D2,\n            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n            multisampled: false,\n        },\n        count: NonZeroU32::new(256),\n    },\n];\n\nlazy_static! {\n    static ref VERTEX_ATTRIBUTES: [[wgpu::VertexAttribute; 2]; 2] = [\n        wgpu::vertex_attr_array![\n            // position\n            0 => Float32x3,\n            // texcoord\n            1 => Float32x2,\n        ],\n        wgpu::vertex_attr_array![\n            // instance position\n            2 => Float32x3,\n            // color index\n            3 => Uint32,\n        ]\n    ];\n}\n\nimpl Pipeline for ParticlePipeline {\n    type VertexPushConstants = VertexPushConstants;\n    type SharedPushConstants = ();\n    type FragmentPushConstants = FragmentPushConstants;\n\n    fn name() -> &'static str {\n        \"particle\"\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(\n            env!(\"CARGO_MANIFEST_DIR\"),\n            \"/shaders/particle.vert\"\n        ))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(\n            env!(\"CARGO_MANIFEST_DIR\"),\n            \"/shaders/particle.frag\"\n        ))\n    }\n\n    // NOTE: if any of the binding indices are changed, they must also be changed in\n    // the corresponding shaders and the BindGroupLayout generation functions.\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![\n            // group 0\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"particle bind group layout\"),\n                entries: BIND_GROUP_LAYOUT_ENTRIES,\n            },\n        ]\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        WorldPipelineBase::primitive_state()\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        WorldPipelineBase::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        let mut desc = WorldPipelineBase::depth_stencil_state().unwrap();\n        desc.depth_write_enabled = false;\n        Some(desc)\n    }\n\n    // NOTE: if the vertex format is changed, this descriptor must also be changed accordingly.\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        vec![wgpu::VertexBufferLayout {\n            array_stride: size_of::<ParticleVertex>() as u64,\n            step_mode: wgpu::InputStepMode::Vertex,\n            attributes: &VERTEX_ATTRIBUTES[0],\n        }]\n    }\n}\n\n#[repr(C)]\n#[derive(Copy, Clone, Debug)]\npub struct ParticleVertex {\n    position: [f32; 3],\n    texcoord: [f32; 2],\n}\n\npub const VERTICES: [ParticleVertex; 6] = [\n    ParticleVertex {\n        position: [-1.0, -1.0, 0.0],\n        texcoord: [0.0, 1.0],\n    },\n    ParticleVertex {\n        position: [-1.0, 1.0, 0.0],\n        texcoord: [0.0, 0.0],\n    },\n    ParticleVertex {\n        position: [1.0, 1.0, 0.0],\n        texcoord: [1.0, 0.0],\n    },\n    ParticleVertex {\n        position: [-1.0, -1.0, 0.0],\n        texcoord: [0.0, 1.0],\n    },\n    ParticleVertex {\n        position: [1.0, 1.0, 0.0],\n        texcoord: [1.0, 0.0],\n    },\n    ParticleVertex {\n        position: [1.0, -1.0, 0.0],\n        texcoord: [1.0, 1.0],\n    },\n];\n\n#[repr(C)]\npub struct ParticleInstance {\n    color: u32,\n}\n"
  },
  {
    "path": "src/client/render/world/postprocess.rs",
    "content": "use std::{mem::size_of, num::NonZeroU64};\n\nuse crate::{\n    client::render::{pipeline::Pipeline, ui::quad::QuadPipeline, GraphicsState},\n    common::util::any_as_bytes,\n};\n\n#[repr(C, align(256))]\n#[derive(Clone, Copy, Debug)]\npub struct PostProcessUniforms {\n    pub color_shift: [f32; 4],\n}\n\npub struct PostProcessPipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    uniform_buffer: wgpu::Buffer,\n}\n\nimpl PostProcessPipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) -> PostProcessPipeline {\n        let (pipeline, bind_group_layouts) =\n            PostProcessPipeline::create(device, compiler, &[], sample_count);\n        use wgpu::util::DeviceExt as _;\n        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {\n            label: None,\n            contents: unsafe {\n                any_as_bytes(&PostProcessUniforms {\n                    color_shift: [0.0; 4],\n                })\n            },\n            usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,\n        });\n\n        PostProcessPipeline {\n            pipeline,\n            bind_group_layouts,\n            uniform_buffer,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        sample_count: u32,\n    ) {\n        let layout_refs: Vec<_> = self.bind_group_layouts.iter().collect();\n        let pipeline = PostProcessPipeline::recreate(device, compiler, &layout_refs, sample_count);\n        self.pipeline = pipeline;\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn uniform_buffer(&self) -> &wgpu::Buffer {\n        &self.uniform_buffer\n    }\n}\n\nconst BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[\n    // sampler\n    wgpu::BindGroupLayoutEntry {\n        binding: 0,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Sampler {\n            filtering: true,\n            comparison: false,\n        },\n        count: None,\n    },\n    // color buffer\n    wgpu::BindGroupLayoutEntry {\n        binding: 1,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Texture {\n            view_dimension: wgpu::TextureViewDimension::D2,\n            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n            multisampled: true,\n        },\n        count: None,\n    },\n    // PostProcessUniforms\n    wgpu::BindGroupLayoutEntry {\n        binding: 2,\n        visibility: wgpu::ShaderStage::FRAGMENT,\n        ty: wgpu::BindingType::Buffer {\n            ty: wgpu::BufferBindingType::Uniform,\n            has_dynamic_offset: false,\n            min_binding_size: NonZeroU64::new(size_of::<PostProcessUniforms>() as u64),\n        },\n        count: None,\n    },\n];\n\nimpl Pipeline for PostProcessPipeline {\n    type VertexPushConstants = ();\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"postprocess\"\n    }\n\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![wgpu::BindGroupLayoutDescriptor {\n            label: Some(\"postprocess bind group\"),\n            entries: BIND_GROUP_LAYOUT_ENTRIES,\n        }]\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(\n            env!(\"CARGO_MANIFEST_DIR\"),\n            \"/shaders/postprocess.vert\"\n        ))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(\n            env!(\"CARGO_MANIFEST_DIR\"),\n            \"/shaders/postprocess.frag\"\n        ))\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        QuadPipeline::primitive_state()\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        QuadPipeline::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        None\n    }\n\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        QuadPipeline::vertex_buffer_layouts()\n    }\n}\n\npub struct PostProcessRenderer {\n    bind_group: wgpu::BindGroup,\n}\n\nimpl PostProcessRenderer {\n    pub fn create_bind_group(\n        state: &GraphicsState,\n        color_buffer: &wgpu::TextureView,\n    ) -> wgpu::BindGroup {\n        state\n            .device()\n            .create_bind_group(&wgpu::BindGroupDescriptor {\n                label: Some(\"postprocess bind group\"),\n                layout: &state.postprocess_pipeline().bind_group_layouts()[0],\n                entries: &[\n                    // sampler\n                    wgpu::BindGroupEntry {\n                        binding: 0,\n                        // TODO: might need a dedicated sampler if downsampling\n                        resource: wgpu::BindingResource::Sampler(state.diffuse_sampler()),\n                    },\n                    // color buffer\n                    wgpu::BindGroupEntry {\n                        binding: 1,\n                        resource: wgpu::BindingResource::TextureView(color_buffer),\n                    },\n                    // uniform buffer\n                    wgpu::BindGroupEntry {\n                        binding: 2,\n                        resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {\n                            buffer: state.postprocess_pipeline().uniform_buffer(),\n                            offset: 0,\n                            size: None,\n                        }),\n                    },\n                ],\n            })\n    }\n\n    pub fn new(state: &GraphicsState, color_buffer: &wgpu::TextureView) -> PostProcessRenderer {\n        let bind_group = Self::create_bind_group(state, color_buffer);\n\n        PostProcessRenderer { bind_group }\n    }\n\n    pub fn rebuild(&mut self, state: &GraphicsState, color_buffer: &wgpu::TextureView) {\n        self.bind_group = Self::create_bind_group(state, color_buffer);\n    }\n\n    pub fn update_uniform_buffers(&self, state: &GraphicsState, color_shift: [f32; 4]) {\n        // update color shift\n        state\n            .queue()\n            .write_buffer(state.postprocess_pipeline().uniform_buffer(), 0, unsafe {\n                any_as_bytes(&PostProcessUniforms { color_shift })\n            });\n    }\n\n    pub fn record_draw<'pass>(\n        &'pass self,\n        state: &'pass GraphicsState,\n        pass: &mut wgpu::RenderPass<'pass>,\n        color_shift: [f32; 4],\n    ) {\n        self.update_uniform_buffers(state, color_shift);\n        pass.set_pipeline(state.postprocess_pipeline().pipeline());\n        pass.set_vertex_buffer(0, state.quad_pipeline().vertex_buffer().slice(..));\n        pass.set_bind_group(0, &self.bind_group, &[]);\n        pass.draw(0..6, 0..1);\n    }\n}\n"
  },
  {
    "path": "src/client/render/world/sprite.rs",
    "content": "use std::mem::size_of;\n\nuse crate::{\n    client::render::{\n        world::{BindGroupLayoutId, WorldPipelineBase},\n        GraphicsState, Pipeline, TextureData,\n    },\n    common::{\n        sprite::{SpriteFrame, SpriteKind, SpriteModel, SpriteSubframe},\n        util::any_slice_as_bytes,\n    },\n};\n\nuse chrono::Duration;\n\npub struct SpritePipeline {\n    pipeline: wgpu::RenderPipeline,\n    bind_group_layouts: Vec<wgpu::BindGroupLayout>,\n    vertex_buffer: wgpu::Buffer,\n}\n\nimpl SpritePipeline {\n    pub fn new(\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        world_bind_group_layouts: &[wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) -> SpritePipeline {\n        let (pipeline, bind_group_layouts) =\n            SpritePipeline::create(device, compiler, world_bind_group_layouts, sample_count);\n\n        use wgpu::util::DeviceExt as _;\n        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {\n            label: None,\n            contents: unsafe { any_slice_as_bytes(&VERTICES) },\n            usage: wgpu::BufferUsage::VERTEX,\n        });\n\n        SpritePipeline {\n            pipeline,\n            bind_group_layouts,\n            vertex_buffer,\n        }\n    }\n\n    pub fn rebuild(\n        &mut self,\n        device: &wgpu::Device,\n        compiler: &mut shaderc::Compiler,\n        world_bind_group_layouts: &[wgpu::BindGroupLayout],\n        sample_count: u32,\n    ) {\n        let layout_refs: Vec<_> = world_bind_group_layouts\n            .iter()\n            .chain(self.bind_group_layouts.iter())\n            .collect();\n        self.pipeline = SpritePipeline::recreate(device, compiler, &layout_refs, sample_count);\n    }\n\n    pub fn pipeline(&self) -> &wgpu::RenderPipeline {\n        &self.pipeline\n    }\n\n    pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {\n        &self.bind_group_layouts\n    }\n\n    pub fn vertex_buffer(&self) -> &wgpu::Buffer {\n        &self.vertex_buffer\n    }\n}\n\nlazy_static! {\n    static ref VERTEX_BUFFER_ATTRIBUTES: [wgpu::VertexAttribute; 3] =\n        wgpu::vertex_attr_array![\n            // position\n            0 => Float32x3,\n            // normal\n            1 => Float32x3,\n            // texcoord\n            2 => Float32x2,\n        ];\n}\n\nimpl Pipeline for SpritePipeline {\n    type VertexPushConstants = ();\n    type SharedPushConstants = ();\n    type FragmentPushConstants = ();\n\n    fn name() -> &'static str {\n        \"sprite\"\n    }\n\n    fn vertex_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/sprite.vert\"))\n    }\n\n    fn fragment_shader() -> &'static str {\n        include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/sprite.frag\"))\n    }\n\n    // NOTE: if any of the binding indices are changed, they must also be changed in\n    // the corresponding shaders and the BindGroupLayout generation functions.\n    fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescriptor<'static>> {\n        vec![\n            // group 2: updated per-texture\n            wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"sprite per-texture chain bind group\"),\n                entries: &[\n                    // diffuse texture, updated once per face\n                    wgpu::BindGroupLayoutEntry {\n                        binding: 0,\n                        visibility: wgpu::ShaderStage::FRAGMENT,\n                        ty: wgpu::BindingType::Texture {\n                            view_dimension: wgpu::TextureViewDimension::D2,\n                            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                            multisampled: false,\n                        },\n                        count: None,\n                    },\n                ],\n            },\n        ]\n    }\n\n    fn primitive_state() -> wgpu::PrimitiveState {\n        WorldPipelineBase::primitive_state()\n    }\n\n    fn color_target_states() -> Vec<wgpu::ColorTargetState> {\n        WorldPipelineBase::color_target_states()\n    }\n\n    fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {\n        WorldPipelineBase::depth_stencil_state()\n    }\n\n    // NOTE: if the vertex format is changed, this descriptor must also be changed accordingly.\n    fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {\n        vec![wgpu::VertexBufferLayout {\n            array_stride: size_of::<SpriteVertex>() as u64,\n            step_mode: wgpu::InputStepMode::Vertex,\n            attributes: &VERTEX_BUFFER_ATTRIBUTES[..],\n        }]\n    }\n}\n\n// these type aliases are here to aid readability of e.g. size_of::<Position>()\ntype Position = [f32; 3];\ntype Normal = [f32; 3];\ntype DiffuseTexcoord = [f32; 2];\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug)]\npub struct SpriteVertex {\n    position: Position,\n    normal: Normal,\n    diffuse_texcoord: DiffuseTexcoord,\n}\n\npub const VERTICES: [SpriteVertex; 6] = [\n    SpriteVertex {\n        position: [0.0, 0.0, 0.0],\n        normal: [0.0, 0.0, 1.0],\n        diffuse_texcoord: [0.0, 1.0],\n    },\n    SpriteVertex {\n        position: [0.0, 1.0, 0.0],\n        normal: [0.0, 0.0, 1.0],\n        diffuse_texcoord: [0.0, 0.0],\n    },\n    SpriteVertex {\n        position: [1.0, 1.0, 0.0],\n        normal: [0.0, 0.0, 1.0],\n        diffuse_texcoord: [1.0, 0.0],\n    },\n    SpriteVertex {\n        position: [0.0, 0.0, 0.0],\n        normal: [0.0, 0.0, 1.0],\n        diffuse_texcoord: [0.0, 1.0],\n    },\n    SpriteVertex {\n        position: [1.0, 1.0, 0.0],\n        normal: [0.0, 0.0, 1.0],\n        diffuse_texcoord: [1.0, 0.0],\n    },\n    SpriteVertex {\n        position: [1.0, 0.0, 0.0],\n        normal: [0.0, 0.0, 1.0],\n        diffuse_texcoord: [1.0, 1.0],\n    },\n];\n\nenum Frame {\n    Static {\n        diffuse: wgpu::Texture,\n        diffuse_view: wgpu::TextureView,\n        bind_group: wgpu::BindGroup,\n    },\n    Animated {\n        diffuses: Vec<wgpu::Texture>,\n        diffuse_views: Vec<wgpu::TextureView>,\n        bind_groups: Vec<wgpu::BindGroup>,\n        total_duration: Duration,\n        durations: Vec<Duration>,\n    },\n}\n\nimpl Frame {\n    fn new(state: &GraphicsState, sframe: &SpriteFrame) -> Frame {\n        fn convert_subframe(\n            state: &GraphicsState,\n            subframe: &SpriteSubframe,\n        ) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup) {\n            let (diffuse_data, _fullbright_data) = state.palette.translate(subframe.indexed());\n            let diffuse = state.create_texture(\n                None,\n                subframe.width(),\n                subframe.height(),\n                &TextureData::Diffuse(diffuse_data),\n            );\n            let diffuse_view = diffuse.create_view(&Default::default());\n            let bind_group = state\n                .device()\n                .create_bind_group(&wgpu::BindGroupDescriptor {\n                    label: None,\n                    layout: &state.sprite_pipeline().bind_group_layouts()\n                        [BindGroupLayoutId::PerTexture as usize - 2],\n                    entries: &[wgpu::BindGroupEntry {\n                        binding: 0,\n                        resource: wgpu::BindingResource::TextureView(&diffuse_view),\n                    }],\n                });\n            (diffuse, diffuse_view, bind_group)\n        }\n\n        match sframe {\n            SpriteFrame::Static { frame } => {\n                let (diffuse, diffuse_view, bind_group) = convert_subframe(state, frame);\n\n                Frame::Static {\n                    diffuse,\n                    diffuse_view,\n                    bind_group,\n                }\n            }\n\n            SpriteFrame::Animated {\n                subframes,\n                durations,\n            } => {\n                let mut diffuses = Vec::new();\n                let mut diffuse_views = Vec::new();\n                let mut bind_groups = Vec::new();\n\n                let _ = subframes\n                    .iter()\n                    .map(|subframe| {\n                        let (diffuse, diffuse_view, bind_group) = convert_subframe(state, subframe);\n                        diffuses.push(diffuse);\n                        diffuse_views.push(diffuse_view);\n                        bind_groups.push(bind_group);\n                    })\n                    .count(); // count to consume the iterator\n\n                let total_duration = durations.iter().fold(Duration::zero(), |init, d| init + *d);\n\n                Frame::Animated {\n                    diffuses,\n                    diffuse_views,\n                    bind_groups,\n                    total_duration,\n                    durations: durations.clone(),\n                }\n            }\n        }\n    }\n\n    fn animate(&self, time: Duration) -> &wgpu::BindGroup {\n        match self {\n            Frame::Static { bind_group, .. } => &bind_group,\n            Frame::Animated {\n                bind_groups,\n                total_duration,\n                durations,\n                ..\n            } => {\n                let mut time_ms = time.num_milliseconds() % total_duration.num_milliseconds();\n                for (i, d) in durations.iter().enumerate() {\n                    time_ms -= d.num_milliseconds();\n                    if time_ms <= 0 {\n                        return &bind_groups[i];\n                    }\n                }\n\n                unreachable!()\n            }\n        }\n    }\n}\n\npub struct SpriteRenderer {\n    kind: SpriteKind,\n    frames: Vec<Frame>,\n}\n\nimpl SpriteRenderer {\n    pub fn new(state: &GraphicsState, sprite: &SpriteModel) -> SpriteRenderer {\n        let frames = sprite\n            .frames()\n            .iter()\n            .map(|f| Frame::new(state, f))\n            .collect();\n\n        SpriteRenderer {\n            kind: sprite.kind(),\n            frames,\n        }\n    }\n\n    pub fn record_draw<'a>(\n        &'a self,\n        state: &'a GraphicsState,\n        pass: &mut wgpu::RenderPass<'a>,\n        frame_id: usize,\n        time: Duration,\n    ) {\n        pass.set_pipeline(state.sprite_pipeline().pipeline());\n        pass.set_vertex_buffer(0, state.sprite_pipeline().vertex_buffer().slice(..));\n        pass.set_bind_group(\n            BindGroupLayoutId::PerTexture as u32,\n            self.frames[frame_id].animate(time),\n            &[],\n        );\n        pass.draw(0..VERTICES.len() as u32, 0..1);\n    }\n\n    pub fn kind(&self) -> SpriteKind {\n        self.kind\n    }\n}\n"
  },
  {
    "path": "src/client/sound/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nmod music;\npub use music::MusicPlayer;\n\nuse std::{\n    cell::{Cell, RefCell},\n    io::{self, BufReader, Cursor, Read},\n};\n\nuse crate::common::vfs::{Vfs, VfsError};\n\nuse cgmath::{InnerSpace, Vector3};\nuse rodio::{\n    source::{Buffered, SamplesConverter},\n    Decoder, OutputStreamHandle, Sink, Source,\n};\nuse thiserror::Error;\nuse chrono::Duration;\n\npub const DISTANCE_ATTENUATION_FACTOR: f32 = 0.001;\nconst MAX_ENTITY_CHANNELS: usize = 128;\n\n#[derive(Error, Debug)]\npub enum SoundError {\n    #[error(\"No such music track: {0}\")]\n    NoSuchTrack(String),\n    #[error(\"I/O error: {0}\")]\n    Io(#[from] io::Error),\n    #[error(\"Virtual filesystem error: {0}\")]\n    Vfs(#[from] VfsError),\n    #[error(\"WAV decoder error: {0}\")]\n    Decoder(#[from] rodio::decoder::DecoderError),\n}\n\n/// Data needed for sound spatialization.\n///\n/// This struct is updated every frame.\n#[derive(Debug)]\npub struct Listener {\n    origin: Cell<Vector3<f32>>,\n    left_ear: Cell<Vector3<f32>>,\n    right_ear: Cell<Vector3<f32>>,\n}\n\nimpl Listener {\n    pub fn new() -> Listener {\n        Listener {\n            origin: Cell::new(Vector3::new(0.0, 0.0, 0.0)),\n            left_ear: Cell::new(Vector3::new(0.0, 0.0, 0.0)),\n            right_ear: Cell::new(Vector3::new(0.0, 0.0, 0.0)),\n        }\n    }\n\n    pub fn origin(&self) -> Vector3<f32> {\n        self.origin.get()\n    }\n\n    pub fn left_ear(&self) -> Vector3<f32> {\n        self.left_ear.get()\n    }\n\n    pub fn right_ear(&self) -> Vector3<f32> {\n        self.right_ear.get()\n    }\n\n    pub fn set_origin(&self, new_origin: Vector3<f32>) {\n        self.origin.set(new_origin);\n    }\n\n    pub fn set_left_ear(&self, new_origin: Vector3<f32>) {\n        self.left_ear.set(new_origin);\n    }\n\n    pub fn set_right_ear(&self, new_origin: Vector3<f32>) {\n        self.right_ear.set(new_origin);\n    }\n\n    pub fn attenuate(\n        &self,\n        emitter_origin: Vector3<f32>,\n        base_volume: f32,\n        attenuation: f32,\n    ) -> f32 {\n        let decay = (emitter_origin - self.origin.get()).magnitude()\n            * attenuation\n            * DISTANCE_ATTENUATION_FACTOR;\n        let volume = ((1.0 - decay) * base_volume).max(0.0);\n        volume\n    }\n}\n\n#[derive(Clone)]\npub struct AudioSource(Buffered<SamplesConverter<Decoder<Cursor<Vec<u8>>>, f32>>);\n\nimpl AudioSource {\n    pub fn load<S>(vfs: &Vfs, name: S) -> Result<AudioSource, SoundError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n        let full_path = \"sound/\".to_owned() + name;\n        let mut file = vfs.open(&full_path)?;\n        let mut data = Vec::new();\n        file.read_to_end(&mut data)?;\n\n        let src = Decoder::new(Cursor::new(data))?\n            .convert_samples()\n            .buffered();\n\n        Ok(AudioSource(src))\n    }\n}\n\npub struct StaticSound {\n    origin: Vector3<f32>,\n    sink: RefCell<Sink>,\n    volume: f32,\n    attenuation: f32,\n}\n\nimpl StaticSound {\n    pub fn new(\n        stream: &OutputStreamHandle,\n        origin: Vector3<f32>,\n        src: AudioSource,\n        volume: f32,\n        attenuation: f32,\n        listener: &Listener,\n    ) -> StaticSound {\n        // TODO: handle PlayError once PR accepted\n        let sink = Sink::try_new(&stream).unwrap();\n        let infinite = src.0.clone().repeat_infinite();\n        sink.append(infinite);\n        sink.set_volume(listener.attenuate(origin, volume, attenuation));\n\n        StaticSound {\n            origin,\n            sink: RefCell::new(sink),\n            volume,\n            attenuation,\n        }\n    }\n\n    pub fn update(&self, listener: &Listener) {\n        let sink = self.sink.borrow_mut();\n\n        sink.set_volume(listener.attenuate(self.origin, self.volume, self.attenuation));\n    }\n}\n\n/// Represents a single audio channel, capable of playing one sound at a time.\npub struct Channel {\n    stream: OutputStreamHandle,\n    sink: RefCell<Option<Sink>>,\n    master_vol: Cell<f32>,\n    attenuation: Cell<f32>,\n}\n\nimpl Channel {\n    /// Create a new `Channel` backed by the given `Device`.\n    pub fn new(stream: OutputStreamHandle) -> Channel {\n        Channel {\n            stream,\n            sink: RefCell::new(None),\n            master_vol: Cell::new(0.0),\n            attenuation: Cell::new(0.0),\n        }\n    }\n\n    /// Play a new sound on this channel, cutting off any sound that was previously playing.\n    pub fn play(\n        &self,\n        src: AudioSource,\n        ent_pos: Vector3<f32>,\n        listener: &Listener,\n        volume: f32,\n        attenuation: f32,\n    ) {\n        self.master_vol.set(volume);\n        self.attenuation.set(attenuation);\n\n        // stop the old sound\n        self.sink.replace(None);\n\n        // start the new sound\n        let new_sink = Sink::try_new(&self.stream).unwrap();\n        new_sink.append(src.0);\n        new_sink.set_volume(listener.attenuate(\n            ent_pos,\n            self.master_vol.get(),\n            self.attenuation.get(),\n        ));\n\n        self.sink.replace(Some(new_sink));\n    }\n\n    pub fn update(&self, ent_pos: Vector3<f32>, listener: &Listener) {\n        if let Some(ref sink) = *self.sink.borrow_mut() {\n            // attenuate using quake coordinates since distance is the same either way\n            sink.set_volume(listener.attenuate(\n                ent_pos,\n                self.master_vol.get(),\n                self.attenuation.get(),\n            ));\n        };\n    }\n\n    /// Stop the sound currently playing on this channel, if there is one.\n    pub fn stop(&self) {\n        self.sink.replace(None);\n    }\n\n    /// Returns whether or not this `Channel` is currently in use.\n    pub fn in_use(&self) -> bool {\n        let replace_sink;\n        match *self.sink.borrow() {\n            Some(ref sink) => replace_sink = sink.empty(),\n            None => return false,\n        }\n\n        // if the sink isn't in use, free it\n        if replace_sink {\n            self.sink.replace(None);\n            false\n        } else {\n            true\n        }\n    }\n}\n\npub struct EntityChannel {\n    start_time: Duration,\n    // if None, sound is associated with a temp entity\n    ent_id: Option<usize>,\n    ent_channel: i8,\n    channel: Channel,\n}\n\nimpl EntityChannel {\n    pub fn channel(&self) -> &Channel {\n        &self.channel\n    }\n\n    pub fn entity_id(&self) -> Option<usize> {\n        self.ent_id\n    }\n}\n\npub struct EntityMixer {\n    stream: OutputStreamHandle,\n    // TODO: replace with an array once const type parameters are implemented\n    channels: Box<[Option<EntityChannel>]>,\n}\n\nimpl EntityMixer {\n    pub fn new(stream: OutputStreamHandle) -> EntityMixer {\n        let mut channel_vec = Vec::new();\n\n        for _ in 0..MAX_ENTITY_CHANNELS {\n            channel_vec.push(None);\n        }\n\n        EntityMixer {\n            stream,\n            channels: channel_vec.into_boxed_slice(),\n        }\n    }\n\n    fn find_free_channel(&self, ent_id: Option<usize>, ent_channel: i8) -> usize {\n        let mut oldest = 0;\n\n        for (i, channel) in self.channels.iter().enumerate() {\n            match *channel {\n                Some(ref chan) => {\n                    // if this channel is free, return it\n                    if !chan.channel.in_use() {\n                        return i;\n                    }\n\n                    // replace sounds on the same entity channel\n                    if ent_channel != 0\n                        && chan.ent_id == ent_id\n                        && (chan.ent_channel == ent_channel || ent_channel == -1)\n                    {\n                        return i;\n                    }\n\n                    // TODO: don't clobber player sounds with monster sounds\n\n                    // keep track of which sound started the earliest\n                    match self.channels[oldest] {\n                        Some(ref o) => {\n                            if chan.start_time < o.start_time {\n                                oldest = i;\n                            }\n                        }\n                        None => oldest = i,\n                    }\n                }\n\n                None => return i,\n            }\n        }\n\n        // if there are no good channels, just replace the one that's been running the longest\n        oldest\n    }\n\n    pub fn start_sound(\n        &mut self,\n        src: AudioSource,\n        time: Duration,\n        ent_id: Option<usize>,\n        ent_channel: i8,\n        volume: f32,\n        attenuation: f32,\n        origin: Vector3<f32>,\n        listener: &Listener,\n    ) {\n        let chan_id = self.find_free_channel(ent_id, ent_channel);\n        let new_channel = Channel::new(self.stream.clone());\n\n        new_channel.play(\n            src.clone(),\n            origin,\n            listener,\n            volume,\n            attenuation,\n        );\n        self.channels[chan_id] = Some(EntityChannel {\n            start_time: time,\n            ent_id,\n            ent_channel,\n            channel: new_channel,\n        })\n    }\n\n    pub fn iter_entity_channels(&self) -> impl Iterator<Item = &EntityChannel> {\n        self.channels.iter().filter_map(|e| e.as_ref())\n    }\n\n    pub fn stream(&self) -> OutputStreamHandle {\n        self.stream.clone()\n    }\n}\n"
  },
  {
    "path": "src/client/sound/music.rs",
    "content": "use std::{\n    io::{Cursor, Read},\n    rc::Rc,\n};\n\nuse crate::{client::sound::SoundError, common::vfs::Vfs};\n\nuse rodio::{Decoder, OutputStreamHandle, Sink, Source};\n\n/// Plays music tracks.\npub struct MusicPlayer {\n    vfs: Rc<Vfs>,\n    stream: OutputStreamHandle,\n    playing: Option<String>,\n    sink: Option<Sink>,\n}\n\nimpl MusicPlayer {\n    pub fn new(vfs: Rc<Vfs>, stream: OutputStreamHandle) -> MusicPlayer {\n        MusicPlayer {\n            vfs,\n            stream,\n            playing: None,\n            sink: None,\n        }\n    }\n\n    /// Start playing the track with the given name.\n    ///\n    /// Music tracks are expected to be in the \"music/\" directory of the virtual\n    /// filesystem, so they can be placed either in an actual directory\n    /// `\"id1/music/\"` or packaged in a PAK archive with a path beginning with\n    /// `\"music/\"`.\n    ///\n    /// If the specified track is already playing, this has no effect.\n    pub fn play_named<S>(&mut self, name: S) -> Result<(), SoundError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n\n        // don't replay the same track\n        if let Some(ref playing) = self.playing {\n            if playing == name {\n                return Ok(());\n            }\n        }\n\n        // TODO: there's probably a better way to do this extension check\n        let mut file = if !name.contains('.') {\n            // try all supported formats\n            self.vfs\n                .open(format!(\"music/{}.flac\", name))\n                .or_else(|_| self.vfs.open(format!(\"music/{}.wav\", name)))\n                .or_else(|_| self.vfs.open(format!(\"music/{}.mp3\", name)))\n                .or_else(|_| self.vfs.open(format!(\"music/{}.ogg\", name)))\n                .or(Err(SoundError::NoSuchTrack(name.to_owned())))?\n        } else {\n            self.vfs.open(name)?\n        };\n\n        let mut data = Vec::new();\n        file.read_to_end(&mut data)?;\n        let source = Decoder::new(Cursor::new(data))?\n            .convert_samples::<f32>()\n            .buffered()\n            .repeat_infinite();\n\n        // stop the old track before starting the new one so there's no overlap\n        self.sink = None;\n        // TODO handle PlayError\n        let new_sink = Sink::try_new(&self.stream).unwrap();\n        new_sink.append(source);\n        self.sink = Some(new_sink);\n\n        Ok(())\n    }\n\n    /// Start playing the track with the given number.\n    ///\n    /// Note that the first actual music track is track 2; track 1 on the\n    /// original Quake CD-ROM held the game data.\n    pub fn play_track(&mut self, track_id: usize) -> Result<(), SoundError> {\n        self.play_named(format!(\"track{:02}\", track_id))\n    }\n\n    /// Stop the current music track.\n    ///\n    /// This ceases playback entirely. To pause the track, allowing it to be\n    /// resumed later, use `MusicPlayer::pause()`.\n    ///\n    /// If no music track is currently playing, this has no effect.\n    pub fn stop(&mut self) {\n        self.sink = None;\n        self.playing = None;\n    }\n\n    /// Pause the current music track.\n    ///\n    /// If no music track is currently playing, or if the current track is\n    /// already paused, this has no effect.\n    pub fn pause(&mut self) {\n        if let Some(ref mut sink) = self.sink {\n            sink.pause();\n        }\n    }\n\n    /// Resume playback of the current music track.\n    ///\n    /// If no music track is currently playing, or if the current track is not\n    /// paused, this has no effect.\n    pub fn resume(&mut self) {\n        if let Some(ref mut sink) = self.sink {\n            sink.play();\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/state.rs",
    "content": "use std::{cell::RefCell, collections::HashMap, rc::Rc};\n\nuse super::view::BobVars;\nuse crate::{\n    client::{\n        entity::{\n            particle::{Particle, Particles, TrailKind, MAX_PARTICLES},\n            Beam, ClientEntity, Light, LightDesc, Lights, MAX_BEAMS, MAX_LIGHTS, MAX_TEMP_ENTITIES,\n        },\n        input::game::{Action, GameInput},\n        render::Camera,\n        sound::{AudioSource, EntityMixer, Listener, StaticSound},\n        view::{IdleVars, KickVars, MouseVars, RollVars, View},\n        ClientError, ColorShiftCode, IntermissionKind, MoveVars, MAX_STATS,\n    },\n    common::{\n        bsp, engine,\n        math::{self, Angles},\n        model::{Model, ModelFlags, ModelKind, SyncType},\n        net::{\n            self, BeamEntityKind, ButtonFlags, ColorShift, EntityEffects, ItemFlags, PlayerData,\n            PointEntityKind, TempEntity,\n        },\n        vfs::Vfs,\n    },\n};\nuse arrayvec::ArrayVec;\nuse cgmath::{Angle as _, Deg, InnerSpace as _, Matrix4, Vector3, Zero as _};\nuse chrono::Duration;\nuse net::{ClientCmd, ClientStat, EntityState, EntityUpdate, PlayerColor};\nuse rand::{\n    distributions::{Distribution as _, Uniform},\n    rngs::SmallRng,\n    SeedableRng,\n};\nuse rodio::OutputStreamHandle;\n\nconst CACHED_SOUND_NAMES: &[&'static str] = &[\n    \"hknight/hit.wav\",\n    \"weapons/r_exp3.wav\",\n    \"weapons/ric1.wav\",\n    \"weapons/ric2.wav\",\n    \"weapons/ric3.wav\",\n    \"weapons/tink1.wav\",\n    \"wizard/hit.wav\",\n];\n\npub struct PlayerInfo {\n    pub name: String,\n    pub frags: i32,\n    pub colors: PlayerColor,\n    // translations: [u8; VID_GRADES],\n}\n\n// client information regarding the current level\npub struct ClientState {\n    // local rng\n    rng: SmallRng,\n\n    // model precache\n    pub models: Vec<Model>,\n    // name-to-id map\n    pub model_names: HashMap<String, usize>,\n\n    // audio source precache\n    pub sounds: Vec<AudioSource>,\n\n    // sounds that are always needed even if not in precache\n    cached_sounds: HashMap<String, AudioSource>,\n\n    // ambient sounds (infinite looping, static position)\n    pub static_sounds: Vec<StaticSound>,\n\n    // entities and entity-like things\n    pub entities: Vec<ClientEntity>,\n    pub static_entities: Vec<ClientEntity>,\n    pub temp_entities: Vec<ClientEntity>,\n    // dynamic point lights\n    pub lights: Lights,\n    // lightning bolts and grappling hook cable\n    pub beams: [Option<Beam>; MAX_BEAMS],\n    // particle effects\n    pub particles: Particles,\n\n    // visible entities, rebuilt per-frame\n    pub visible_entity_ids: Vec<usize>,\n\n    pub light_styles: HashMap<u8, String>,\n\n    // various values relevant to the player and level (see common::net::ClientStat)\n    pub stats: [i32; MAX_STATS],\n\n    pub max_players: usize,\n    pub player_info: [Option<PlayerInfo>; net::MAX_CLIENTS],\n\n    // the last two timestamps sent by the server (for lerping)\n    pub msg_times: [Duration; 2],\n    pub time: Duration,\n    pub lerp_factor: f32,\n\n    pub items: ItemFlags,\n    pub item_get_time: [Duration; net::MAX_ITEMS],\n    pub face_anim_time: Duration,\n    pub color_shifts: [Rc<RefCell<ColorShift>>; 4],\n    pub view: View,\n\n    pub msg_velocity: [Vector3<f32>; 2],\n    pub velocity: Vector3<f32>,\n\n    // paused: bool,\n    pub on_ground: bool,\n    pub in_water: bool,\n    pub intermission: Option<IntermissionKind>,\n    pub start_time: Duration,\n    pub completion_time: Option<Duration>,\n\n    pub mixer: EntityMixer,\n    pub listener: Listener,\n}\n\nimpl ClientState {\n    // TODO: add parameter for number of player slots and reserve them in entity list\n    pub fn new(stream: OutputStreamHandle) -> ClientState {\n        ClientState {\n            rng: SmallRng::from_entropy(),\n            models: vec![Model::none()],\n            model_names: HashMap::new(),\n            sounds: Vec::new(),\n            cached_sounds: HashMap::new(),\n            static_sounds: Vec::new(),\n            entities: Vec::new(),\n            static_entities: Vec::new(),\n            temp_entities: Vec::new(),\n            lights: Lights::with_capacity(MAX_LIGHTS),\n            beams: [None; MAX_BEAMS],\n            particles: Particles::with_capacity(MAX_PARTICLES),\n            visible_entity_ids: Vec::new(),\n            light_styles: HashMap::new(),\n            stats: [0; MAX_STATS],\n            max_players: 0,\n            player_info: Default::default(),\n            msg_times: [Duration::zero(), Duration::zero()],\n            time: Duration::zero(),\n            lerp_factor: 0.0,\n            items: ItemFlags::empty(),\n            item_get_time: [Duration::zero(); net::MAX_ITEMS],\n            color_shifts: [\n                Rc::new(RefCell::new(ColorShift {\n                    dest_color: [0; 3],\n                    percent: 0,\n                })),\n                Rc::new(RefCell::new(ColorShift {\n                    dest_color: [0; 3],\n                    percent: 0,\n                })),\n                Rc::new(RefCell::new(ColorShift {\n                    dest_color: [0; 3],\n                    percent: 0,\n                })),\n                Rc::new(RefCell::new(ColorShift {\n                    dest_color: [0; 3],\n                    percent: 0,\n                })),\n            ],\n            view: View::new(),\n            face_anim_time: Duration::zero(),\n            msg_velocity: [Vector3::zero(), Vector3::zero()],\n            velocity: Vector3::zero(),\n            on_ground: false,\n            in_water: false,\n            intermission: None,\n            start_time: Duration::zero(),\n            completion_time: None,\n            mixer: EntityMixer::new(stream),\n            listener: Listener::new(),\n        }\n    }\n\n    pub fn from_server_info(\n        vfs: &Vfs,\n        stream: OutputStreamHandle,\n        max_clients: u8,\n        model_precache: Vec<String>,\n        sound_precache: Vec<String>,\n    ) -> Result<ClientState, ClientError> {\n        // TODO: validate submodel names\n        let mut models = Vec::with_capacity(model_precache.len());\n        models.push(Model::none());\n        let mut model_names = HashMap::new();\n        for mod_name in model_precache {\n            // BSPs can have more than one model\n            if mod_name.ends_with(\".bsp\") {\n                let bsp_data = vfs.open(&mod_name)?;\n                let (mut brush_models, _) = bsp::load(bsp_data).unwrap();\n                for bmodel in brush_models.drain(..) {\n                    let id = models.len();\n                    let name = bmodel.name().to_owned();\n                    models.push(bmodel);\n                    model_names.insert(name, id);\n                }\n            } else if !mod_name.starts_with(\"*\") {\n                // model names starting with * are loaded from the world BSP\n                debug!(\"Loading model {}\", mod_name);\n                let id = models.len();\n                models.push(Model::load(vfs, &mod_name)?);\n                model_names.insert(mod_name, id);\n            }\n\n            // TODO: send keepalive message?\n        }\n\n        let mut sounds = vec![AudioSource::load(&vfs, \"misc/null.wav\")?];\n        for ref snd_name in sound_precache {\n            debug!(\"Loading sound {}: {}\", sounds.len(), snd_name);\n            sounds.push(AudioSource::load(vfs, snd_name)?);\n            // TODO: send keepalive message?\n        }\n\n        let mut cached_sounds = HashMap::new();\n        for name in CACHED_SOUND_NAMES {\n            cached_sounds.insert(name.to_string(), AudioSource::load(vfs, name)?);\n        }\n\n        Ok(ClientState {\n            models,\n            model_names,\n            sounds,\n            cached_sounds,\n            max_players: max_clients as usize,\n            ..ClientState::new(stream)\n        })\n    }\n\n    /// Advance the simulation time by the specified amount.\n    ///\n    /// This method does not change the state of the world to match the new time value.\n    pub fn advance_time(&mut self, frame_time: Duration) {\n        self.time = self.time + frame_time;\n    }\n\n    /// Update the client state interpolation ratio.\n    ///\n    /// This calculates the ratio used to interpolate entities between the last\n    /// two updates from the server.\n    pub fn update_interp_ratio(&mut self, cl_nolerp: f32) {\n        if cl_nolerp != 0.0 {\n            self.time = self.msg_times[0];\n            self.lerp_factor = 1.0;\n            return;\n        }\n\n        let server_delta = engine::duration_to_f32(match self.msg_times[0] - self.msg_times[1] {\n            // if no time has passed between updates, don't lerp anything\n            d if d == Duration::zero() => {\n                self.time = self.msg_times[0];\n                self.lerp_factor = 1.0;\n                return;\n            }\n\n            d if d > Duration::milliseconds(100) => {\n                self.msg_times[1] = self.msg_times[0] - Duration::milliseconds(100);\n                Duration::milliseconds(100)\n            }\n\n            d if d < Duration::zero() => {\n                warn!(\n                    \"Negative time delta from server!: ({})s\",\n                    engine::duration_to_f32(d)\n                );\n                d\n            }\n\n            d => d,\n        });\n\n        let frame_delta = engine::duration_to_f32(self.time - self.msg_times[1]);\n\n        self.lerp_factor = match frame_delta / server_delta {\n            f if f < 0.0 => {\n                if f < -0.01 {\n                    self.time = self.msg_times[1];\n                }\n\n                0.0\n            }\n\n            f if f > 1.0 => {\n                if f > 1.01 {\n                    self.time = self.msg_times[0];\n                }\n\n                1.0\n            }\n\n            f => f,\n        }\n    }\n\n    /// Update all entities in the game world.\n    ///\n    /// This method is responsible for the following:\n    /// - Updating entity position\n    /// - Despawning entities which did not receive an update in the last server\n    ///   message\n    /// - Spawning particles on entities with particle effects\n    /// - Spawning dynamic lights on entities with lighting effects\n    pub fn update_entities(&mut self) -> Result<(), ClientError> {\n        lazy_static! {\n            static ref MFLASH_DIMLIGHT_DISTRIBUTION: Uniform<f32> = Uniform::new(200.0, 232.0);\n            static ref BRIGHTLIGHT_DISTRIBUTION: Uniform<f32> = Uniform::new(400.0, 432.0);\n        }\n\n        let lerp_factor = self.lerp_factor;\n\n        self.velocity =\n            self.msg_velocity[1] + lerp_factor * (self.msg_velocity[0] - self.msg_velocity[1]);\n\n        // TODO: if we're in demo playback, interpolate the view angles\n\n        let obj_rotate = Deg(100.0 * engine::duration_to_f32(self.time)).normalize();\n\n        // rebuild the list of visible entities\n        self.visible_entity_ids.clear();\n\n        // in the extremely unlikely event that there's only a world entity and nothing else, just\n        // return\n        if self.entities.len() <= 1 {\n            return Ok(());\n        }\n\n        // NOTE that we start at entity 1 since we don't need to link the world entity\n        for (ent_id, ent) in self.entities.iter_mut().enumerate().skip(1) {\n            if ent.model_id == 0 {\n                // nothing in this entity slot\n                continue;\n            }\n\n            // if we didn't get an update this frame, remove the entity\n            if ent.msg_time != self.msg_times[0] {\n                ent.model_id = 0;\n                continue;\n            }\n\n            let prev_origin = ent.origin;\n\n            if ent.force_link {\n                trace!(\"force link on entity {}\", ent_id);\n                ent.origin = ent.msg_origins[0];\n                ent.angles = ent.msg_angles[0];\n            } else {\n                let origin_delta = ent.msg_origins[0] - ent.msg_origins[1];\n                let ent_lerp_factor = if origin_delta.magnitude2() > 10_000.0 {\n                    // if the entity moved more than 100 units in one frame,\n                    // assume it was teleported and don't lerp anything\n                    1.0\n                } else {\n                    lerp_factor\n                };\n\n                ent.origin = ent.msg_origins[1] + ent_lerp_factor * origin_delta;\n\n                // assume that entities will not whip around 180+ degrees in one\n                // frame and adjust the delta accordingly. this avoids a bug\n                // where small turns between 0 <-> 359 cause the demo camera to\n                // face backwards for one frame.\n                for i in 0..3 {\n                    let mut angle_delta = ent.msg_angles[0][i] - ent.msg_angles[1][i];\n                    if angle_delta > Deg(180.0) {\n                        angle_delta = Deg(360.0) - angle_delta;\n                    } else if angle_delta < Deg(-180.0) {\n                        angle_delta = Deg(360.0) + angle_delta;\n                    }\n\n                    ent.angles[i] =\n                        (ent.msg_angles[1][i] + angle_delta * ent_lerp_factor).normalize();\n                }\n            }\n\n            let model = &self.models[ent.model_id];\n            if model.has_flag(ModelFlags::ROTATE) {\n                ent.angles[1] = obj_rotate;\n            }\n\n            if ent.effects.contains(EntityEffects::BRIGHT_FIELD) {\n                self.particles.create_entity_field(self.time, ent);\n            }\n\n            // TODO: factor out EntityEffects->LightDesc mapping\n            if ent.effects.contains(EntityEffects::MUZZLE_FLASH) {\n                // TODO: angle and move origin to muzzle\n                ent.light_id = Some(self.lights.insert(\n                    self.time,\n                    LightDesc {\n                        origin: ent.origin + Vector3::new(0.0, 0.0, 16.0),\n                        init_radius: MFLASH_DIMLIGHT_DISTRIBUTION.sample(&mut self.rng),\n                        decay_rate: 0.0,\n                        min_radius: Some(32.0),\n                        ttl: Duration::milliseconds(100),\n                    },\n                    ent.light_id,\n                ));\n            }\n\n            if ent.effects.contains(EntityEffects::BRIGHT_LIGHT) {\n                ent.light_id = Some(self.lights.insert(\n                    self.time,\n                    LightDesc {\n                        origin: ent.origin,\n                        init_radius: BRIGHTLIGHT_DISTRIBUTION.sample(&mut self.rng),\n                        decay_rate: 0.0,\n                        min_radius: None,\n                        ttl: Duration::milliseconds(1),\n                    },\n                    ent.light_id,\n                ));\n            }\n\n            if ent.effects.contains(EntityEffects::DIM_LIGHT) {\n                ent.light_id = Some(self.lights.insert(\n                    self.time,\n                    LightDesc {\n                        origin: ent.origin,\n                        init_radius: MFLASH_DIMLIGHT_DISTRIBUTION.sample(&mut self.rng),\n                        decay_rate: 0.0,\n                        min_radius: None,\n                        ttl: Duration::milliseconds(1),\n                    },\n                    ent.light_id,\n                ));\n            }\n\n            // check if this entity leaves a trail\n            let trail_kind = if model.has_flag(ModelFlags::GIB) {\n                Some(TrailKind::Blood)\n            } else if model.has_flag(ModelFlags::ZOMGIB) {\n                Some(TrailKind::BloodSlight)\n            } else if model.has_flag(ModelFlags::TRACER) {\n                Some(TrailKind::TracerGreen)\n            } else if model.has_flag(ModelFlags::TRACER2) {\n                Some(TrailKind::TracerRed)\n            } else if model.has_flag(ModelFlags::ROCKET) {\n                ent.light_id = Some(self.lights.insert(\n                    self.time,\n                    LightDesc {\n                        origin: ent.origin,\n                        init_radius: 200.0,\n                        decay_rate: 0.0,\n                        min_radius: None,\n                        ttl: Duration::milliseconds(10),\n                    },\n                    ent.light_id,\n                ));\n                Some(TrailKind::Rocket)\n            } else if model.has_flag(ModelFlags::GRENADE) {\n                Some(TrailKind::Smoke)\n            } else if model.has_flag(ModelFlags::TRACER3) {\n                Some(TrailKind::Vore)\n            } else {\n                None\n            };\n\n            // if the entity leaves a trail, generate it\n            if let Some(kind) = trail_kind {\n                self.particles\n                    .create_trail(self.time, prev_origin, ent.origin, kind, false);\n            }\n\n            // don't render the player model\n            if self.view.entity_id() != ent_id {\n                // mark entity for rendering\n                self.visible_entity_ids.push(ent_id);\n            }\n\n            // enable lerp for next frame\n            ent.force_link = false;\n        }\n\n        // apply effects to static entities as well\n        for ent in self.static_entities.iter_mut() {\n            if ent.effects.contains(EntityEffects::BRIGHT_LIGHT) {\n                debug!(\"spawn bright light on static entity\");\n                ent.light_id = Some(self.lights.insert(\n                    self.time,\n                    LightDesc {\n                        origin: ent.origin,\n                        init_radius: BRIGHTLIGHT_DISTRIBUTION.sample(&mut self.rng),\n                        decay_rate: 0.0,\n                        min_radius: None,\n                        ttl: Duration::milliseconds(1),\n                    },\n                    ent.light_id,\n                ));\n            }\n\n            if ent.effects.contains(EntityEffects::DIM_LIGHT) {\n                debug!(\"spawn dim light on static entity\");\n                ent.light_id = Some(self.lights.insert(\n                    self.time,\n                    LightDesc {\n                        origin: ent.origin,\n                        init_radius: MFLASH_DIMLIGHT_DISTRIBUTION.sample(&mut self.rng),\n                        decay_rate: 0.0,\n                        min_radius: None,\n                        ttl: Duration::milliseconds(1),\n                    },\n                    ent.light_id,\n                ));\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn update_temp_entities(&mut self) -> Result<(), ClientError> {\n        lazy_static! {\n            static ref ANGLE_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 360.0);\n        }\n\n        self.temp_entities.clear();\n        for id in 0..self.beams.len() {\n            // remove beam if expired\n            if self.beams[id].map_or(false, |b| b.expire < self.time) {\n                self.beams[id] = None;\n                continue;\n            }\n\n            let view_ent = self.view_entity_id();\n            if let Some(ref mut beam) = self.beams[id] {\n                // keep lightning gun bolts fixed to player\n                if beam.entity_id == view_ent {\n                    beam.start = self.entities[view_ent].origin;\n                }\n\n                let vec = beam.end - beam.start;\n                let yaw = Deg::from(cgmath::Rad(vec.y.atan2(vec.x))).normalize();\n                let forward = (vec.x.powf(2.0) + vec.y.powf(2.0)).sqrt();\n                let pitch = Deg::from(cgmath::Rad(vec.z.atan2(forward))).normalize();\n\n                let len = vec.magnitude();\n                let direction = vec.normalize();\n                for interval in 0..(len / 30.0) as i32 {\n                    let mut ent = ClientEntity::uninitialized();\n                    ent.origin = beam.start + 30.0 * interval as f32 * direction;\n                    ent.angles =\n                        Vector3::new(pitch, yaw, Deg(ANGLE_DISTRIBUTION.sample(&mut self.rng)));\n\n                    if self.temp_entities.len() < MAX_TEMP_ENTITIES {\n                        self.temp_entities.push(ent);\n                    } else {\n                        warn!(\"too many temp entities!\");\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn update_player(&mut self, update: PlayerData) {\n        self.view\n            .set_view_height(update.view_height.unwrap_or(net::DEFAULT_VIEWHEIGHT));\n        self.view\n            .set_ideal_pitch(update.ideal_pitch.unwrap_or(Deg(0.0)));\n        self.view.set_punch_angles(Angles {\n            pitch: update.punch_pitch.unwrap_or(Deg(0.0)),\n            roll: update.punch_roll.unwrap_or(Deg(0.0)),\n            yaw: update.punch_yaw.unwrap_or(Deg(0.0)),\n        });\n\n        // store old velocity\n        self.msg_velocity[1] = self.msg_velocity[0];\n        self.msg_velocity[0].x = update.velocity_x.unwrap_or(0.0);\n        self.msg_velocity[0].y = update.velocity_y.unwrap_or(0.0);\n        self.msg_velocity[0].z = update.velocity_z.unwrap_or(0.0);\n\n        let item_diff = update.items - self.items;\n        if !item_diff.is_empty() {\n            // item flags have changed, something got picked up\n            let bits = item_diff.bits();\n            for i in 0..net::MAX_ITEMS {\n                if bits & 1 << i != 0 {\n                    // item with flag value `i` was picked up\n                    self.item_get_time[i] = self.time;\n                }\n            }\n        }\n        self.items = update.items;\n\n        self.on_ground = update.on_ground;\n        self.in_water = update.in_water;\n\n        self.stats[ClientStat::WeaponFrame as usize] = update.weapon_frame.unwrap_or(0) as i32;\n        self.stats[ClientStat::Armor as usize] = update.armor.unwrap_or(0) as i32;\n        self.stats[ClientStat::Weapon as usize] = update.weapon.unwrap_or(0) as i32;\n        self.stats[ClientStat::Health as usize] = update.health as i32;\n        self.stats[ClientStat::Ammo as usize] = update.ammo as i32;\n        self.stats[ClientStat::Shells as usize] = update.ammo_shells as i32;\n        self.stats[ClientStat::Nails as usize] = update.ammo_nails as i32;\n        self.stats[ClientStat::Rockets as usize] = update.ammo_rockets as i32;\n        self.stats[ClientStat::Cells as usize] = update.ammo_cells as i32;\n\n        // TODO: this behavior assumes the `standard_quake` behavior and will likely\n        // break with the mission packs\n        self.stats[ClientStat::ActiveWeapon as usize] = update.active_weapon as i32;\n    }\n\n    pub fn handle_input(\n        &mut self,\n        game_input: &mut GameInput,\n        frame_time: Duration,\n        move_vars: MoveVars,\n        mouse_vars: MouseVars,\n    ) -> ClientCmd {\n        use Action::*;\n\n        let mlook = game_input.action_state(MLook);\n        self.view.handle_input(\n            frame_time,\n            game_input,\n            self.intermission.as_ref(),\n            mlook,\n            move_vars.cl_anglespeedkey,\n            move_vars.cl_pitchspeed,\n            move_vars.cl_yawspeed,\n            mouse_vars,\n        );\n\n        let mut move_left = game_input.action_state(MoveLeft);\n        let mut move_right = game_input.action_state(MoveRight);\n        if game_input.action_state(Strafe) {\n            move_left |= game_input.action_state(Left);\n            move_right |= game_input.action_state(Right);\n        }\n\n        let mut sidemove = move_vars.cl_sidespeed * (move_right as i32 - move_left as i32) as f32;\n\n        let mut upmove = move_vars.cl_upspeed\n            * (game_input.action_state(MoveUp) as i32 - game_input.action_state(MoveDown) as i32)\n                as f32;\n\n        let mut forwardmove = 0.0;\n        if !game_input.action_state(KLook) {\n            forwardmove +=\n                move_vars.cl_forwardspeed * game_input.action_state(Forward) as i32 as f32;\n            forwardmove -= move_vars.cl_backspeed * game_input.action_state(Back) as i32 as f32;\n        }\n\n        if game_input.action_state(Speed) {\n            sidemove *= move_vars.cl_movespeedkey;\n            upmove *= move_vars.cl_movespeedkey;\n            forwardmove *= move_vars.cl_movespeedkey;\n        }\n\n        let mut button_flags = ButtonFlags::empty();\n\n        if game_input.action_state(Attack) {\n            button_flags |= ButtonFlags::ATTACK;\n        }\n\n        if game_input.action_state(Jump) {\n            button_flags |= ButtonFlags::JUMP;\n        }\n\n        if !mlook {\n            // TODO: IN_Move (mouse / joystick / gamepad)\n        }\n\n        let send_time = self.msg_times[0];\n        // send \"raw\" angles without any pitch/roll from movement or damage\n        let angles = self.view.input_angles();\n\n        ClientCmd::Move {\n            send_time,\n            angles: Vector3::new(angles.pitch, angles.yaw, angles.roll),\n            fwd_move: forwardmove as i16,\n            side_move: sidemove as i16,\n            up_move: upmove as i16,\n            button_flags,\n            impulse: game_input.impulse(),\n        }\n    }\n\n    pub fn handle_damage(\n        &mut self,\n        armor: u8,\n        health: u8,\n        source: Vector3<f32>,\n        kick_vars: KickVars,\n    ) {\n        self.face_anim_time = self.time + Duration::milliseconds(200);\n\n        let dmg_factor = (armor + health).min(20) as f32 / 2.0;\n        let mut cshift = self.color_shifts[ColorShiftCode::Damage as usize].borrow_mut();\n        cshift.percent += 3 * dmg_factor as i32;\n        cshift.percent = cshift.percent.clamp(0, 150);\n\n        if armor > health {\n            cshift.dest_color = [200, 100, 100];\n        } else if armor > 0 {\n            cshift.dest_color = [220, 50, 50];\n        } else {\n            cshift.dest_color = [255, 0, 0];\n        }\n\n        let v_ent = &self.entities[self.view.entity_id()];\n\n        let v_angles = Angles {\n            pitch: v_ent.angles.x,\n            roll: v_ent.angles.z,\n            yaw: v_ent.angles.y,\n        };\n\n        self.view.handle_damage(\n            self.time,\n            armor as f32,\n            health as f32,\n            v_ent.origin,\n            v_angles,\n            source,\n            kick_vars,\n        );\n    }\n\n    pub fn calc_final_view(\n        &mut self,\n        idle_vars: IdleVars,\n        kick_vars: KickVars,\n        roll_vars: RollVars,\n        bob_vars: BobVars,\n    ) {\n        self.view.calc_final_angles(\n            self.time,\n            self.intermission.as_ref(),\n            self.velocity,\n            idle_vars,\n            kick_vars,\n            roll_vars,\n        );\n        self.view.calc_final_origin(\n            self.time,\n            self.entities[self.view.entity_id()].origin,\n            self.velocity,\n            bob_vars,\n        );\n    }\n\n    /// Spawn an entity with the given ID, also spawning any uninitialized\n    /// entities between the former last entity and the new one.\n    // TODO: skipping entities indicates that the entities have been freed by\n    // the server. it may make more sense to use a HashMap to store entities by\n    // ID since the lookup table is relatively sparse.\n    pub fn spawn_entities(&mut self, id: usize, baseline: EntityState) -> Result<(), ClientError> {\n        // don't clobber existing entities\n        if id < self.entities.len() {\n            Err(ClientError::EntityExists(id))?;\n        }\n\n        // spawn intermediate entities (uninitialized)\n        for i in self.entities.len()..id {\n            debug!(\"Spawning uninitialized entity with ID {}\", i);\n            self.entities.push(ClientEntity::uninitialized());\n        }\n\n        debug!(\n            \"Spawning entity with id {} from baseline {:?}\",\n            id, baseline\n        );\n        self.entities.push(ClientEntity::from_baseline(baseline));\n\n        Ok(())\n    }\n\n    pub fn update_entity(&mut self, id: usize, update: EntityUpdate) -> Result<(), ClientError> {\n        if id >= self.entities.len() {\n            let baseline = EntityState {\n                origin: Vector3::new(\n                    update.origin_x.unwrap_or(0.0),\n                    update.origin_y.unwrap_or(0.0),\n                    update.origin_z.unwrap_or(0.0),\n                ),\n                angles: Vector3::new(\n                    update.pitch.unwrap_or(Deg(0.0)),\n                    update.yaw.unwrap_or(Deg(0.0)),\n                    update.roll.unwrap_or(Deg(0.0)),\n                ),\n                model_id: update.model_id.unwrap_or(0) as usize,\n                frame_id: update.frame_id.unwrap_or(0) as usize,\n                colormap: update.colormap.unwrap_or(0),\n                skin_id: update.skin_id.unwrap_or(0) as usize,\n                effects: EntityEffects::empty(),\n            };\n\n            self.spawn_entities(id, baseline)?;\n        }\n\n        let entity = &mut self.entities[id];\n        entity.update(self.msg_times, update);\n        if entity.model_changed() {\n            match self.models[entity.model_id].kind() {\n                ModelKind::None => (),\n                _ => {\n                    entity.sync_base = match self.models[entity.model_id].sync_type() {\n                        SyncType::Sync => Duration::zero(),\n                        SyncType::Rand => unimplemented!(), // TODO\n                    }\n                }\n            }\n        }\n\n        if let Some(_c) = entity.colormap() {\n            // only players may have custom colormaps\n            if id > self.max_players {\n                warn!(\n                    \"Server attempted to set colormap on entity {}, which is not a player\",\n                    id\n                );\n            }\n            // TODO: set player custom colormaps\n        }\n\n        Ok(())\n    }\n\n    pub fn spawn_temp_entity(&mut self, temp_entity: &TempEntity) {\n        lazy_static! {\n            static ref ZERO_ONE_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 1.0);\n        }\n\n        let mut spike_sound = || match ZERO_ONE_DISTRIBUTION.sample(&mut self.rng) {\n            x if x < 0.2 => \"weapons/tink1.wav\",\n            x if x < 0.4667 => \"weapons/ric1.wav\",\n            x if x < 0.7333 => \"weapons/ric2.wav\",\n            _ => \"weapons/ric3.wav\",\n        };\n\n        match temp_entity {\n            TempEntity::Point { kind, origin } => {\n                use PointEntityKind::*;\n                match kind {\n                    // projectile impacts\n                    WizSpike | KnightSpike | Spike | SuperSpike | Gunshot => {\n                        let (color, count, sound) = match kind {\n                            // TODO: start wizard/hit.wav\n                            WizSpike => (20, 30, Some(\"wizard/hit.wav\")),\n\n                            KnightSpike => (226, 20, Some(\"hknight/hit.wav\")),\n\n                            // TODO: for Spike and SuperSpike, start one of:\n                            // - 26.67%: weapons/tink1.wav\n                            // - 20.0%: weapons/ric1.wav\n                            // - 20.0%: weapons/ric2.wav\n                            // - 20.0%: weapons/ric3.wav\n                            Spike => (0, 10, Some(spike_sound())),\n                            SuperSpike => (0, 20, Some(spike_sound())),\n\n                            // no impact sound\n                            Gunshot => (0, 20, None),\n                            _ => unreachable!(),\n                        };\n\n                        self.particles.create_projectile_impact(\n                            self.time,\n                            *origin,\n                            Vector3::zero(),\n                            color,\n                            count,\n                        );\n\n                        if let Some(snd) = sound {\n                            self.mixer.start_sound(\n                                self.cached_sounds.get(snd).unwrap().clone(),\n                                self.time,\n                                None,\n                                0,\n                                1.0,\n                                1.0,\n                                *origin,\n                                &self.listener,\n                            );\n                        }\n                    }\n\n                    Explosion => {\n                        self.particles.create_explosion(self.time, *origin);\n                        self.lights.insert(\n                            self.time,\n                            LightDesc {\n                                origin: *origin,\n                                init_radius: 350.0,\n                                decay_rate: 300.0,\n                                min_radius: None,\n                                ttl: Duration::milliseconds(500),\n                            },\n                            None,\n                        );\n\n                        self.mixer.start_sound(\n                            self.cached_sounds\n                                .get(\"weapons/r_exp3.wav\")\n                                .unwrap()\n                                .clone(),\n                            self.time,\n                            None,\n                            0,\n                            1.0,\n                            1.0,\n                            *origin,\n                            &self.listener,\n                        );\n                    }\n\n                    ColorExplosion {\n                        color_start,\n                        color_len,\n                    } => {\n                        self.particles.create_color_explosion(\n                            self.time,\n                            *origin,\n                            (*color_start)..=(*color_start + *color_len - 1),\n                        );\n                        self.lights.insert(\n                            self.time,\n                            LightDesc {\n                                origin: *origin,\n                                init_radius: 350.0,\n                                decay_rate: 300.0,\n                                min_radius: None,\n                                ttl: Duration::milliseconds(500),\n                            },\n                            None,\n                        );\n\n                        self.mixer.start_sound(\n                            self.cached_sounds\n                                .get(\"weapons/r_exp3.wav\")\n                                .unwrap()\n                                .clone(),\n                            self.time,\n                            None,\n                            0,\n                            1.0,\n                            1.0,\n                            *origin,\n                            &self.listener,\n                        );\n                    }\n\n                    TarExplosion => {\n                        self.particles.create_spawn_explosion(self.time, *origin);\n\n                        self.mixer.start_sound(\n                            self.cached_sounds\n                                .get(\"weapons/r_exp3.wav\")\n                                .unwrap()\n                                .clone(),\n                            self.time,\n                            None,\n                            0,\n                            1.0,\n                            1.0,\n                            *origin,\n                            &self.listener,\n                        );\n                    }\n\n                    LavaSplash => self.particles.create_lava_splash(self.time, *origin),\n                    Teleport => self.particles.create_teleporter_warp(self.time, *origin),\n                }\n            }\n\n            TempEntity::Beam {\n                kind,\n                entity_id,\n                start,\n                end,\n            } => {\n                use BeamEntityKind::*;\n                let model_name = match kind {\n                    Lightning { model_id } => format!(\n                        \"progs/bolt{}.mdl\",\n                        match model_id {\n                            1 => \"\",\n                            2 => \"2\",\n                            3 => \"3\",\n                            x => panic!(\"invalid lightning model id: {}\", x),\n                        }\n                    ),\n                    Grapple => \"progs/beam.mdl\".to_string(),\n                };\n\n                self.spawn_beam(\n                    self.time,\n                    *entity_id as usize,\n                    *self.model_names.get(&model_name).unwrap(),\n                    *start,\n                    *end,\n                );\n            }\n        }\n    }\n\n    pub fn spawn_beam(\n        &mut self,\n        time: Duration,\n        entity_id: usize,\n        model_id: usize,\n        start: Vector3<f32>,\n        end: Vector3<f32>,\n    ) {\n        // always override beam with same entity_id if it exists\n        // otherwise use the first free slot\n        let mut free = None;\n        for i in 0..self.beams.len() {\n            if let Some(ref mut beam) = self.beams[i] {\n                if beam.entity_id == entity_id {\n                    beam.model_id = model_id;\n                    beam.expire = time + Duration::milliseconds(200);\n                    beam.start = start;\n                    beam.end = end;\n                }\n            } else if free.is_none() {\n                free = Some(i);\n            }\n        }\n\n        if let Some(i) = free {\n            self.beams[i] = Some(Beam {\n                entity_id,\n                model_id,\n                expire: time + Duration::milliseconds(200),\n                start,\n                end,\n            });\n        } else {\n            warn!(\"No free beam slots!\");\n        }\n    }\n\n    pub fn update_listener(&self) {\n        // TODO: update to self.view_origin()\n        let view_origin = self.entities[self.view.entity_id()].origin;\n        let world_translate = Matrix4::from_translation(view_origin);\n\n        let left_base = Vector3::new(0.0, 4.0, self.view.view_height());\n        let right_base = Vector3::new(0.0, -4.0, self.view.view_height());\n\n        let rotate = self.view.input_angles().mat4_quake();\n\n        let left = (world_translate * rotate * left_base.extend(1.0)).truncate();\n        let right = (world_translate * rotate * right_base.extend(1.0)).truncate();\n\n        self.listener.set_origin(view_origin);\n        self.listener.set_left_ear(left);\n        self.listener.set_right_ear(right);\n    }\n\n    pub fn update_sound_spatialization(&self) {\n        self.update_listener();\n\n        // update entity sounds\n        for e_channel in self.mixer.iter_entity_channels() {\n            if let Some(ent_id) = e_channel.entity_id() {\n                if e_channel.channel().in_use() {\n                    e_channel\n                        .channel()\n                        .update(self.entities[ent_id].origin, &self.listener);\n                }\n            }\n        }\n\n        // update static sounds\n        for ss in self.static_sounds.iter() {\n            ss.update(&self.listener);\n        }\n    }\n\n    fn view_leaf_contents(&self) -> Result<bsp::BspLeafContents, ClientError> {\n        match self.models[1].kind() {\n            ModelKind::Brush(ref bmodel) => {\n                let bsp_data = bmodel.bsp_data();\n                let leaf_id = bsp_data.find_leaf(self.entities[self.view.entity_id()].origin);\n                let leaf = &bsp_data.leaves()[leaf_id];\n                Ok(leaf.contents)\n            }\n            _ => panic!(\"non-brush worldmodel\"),\n        }\n    }\n\n    pub fn update_color_shifts(&mut self, frame_time: Duration) -> Result<(), ClientError> {\n        let float_time = engine::duration_to_f32(frame_time);\n\n        // set color for leaf contents\n        self.color_shifts[ColorShiftCode::Contents as usize].replace(\n            match self.view_leaf_contents()? {\n                bsp::BspLeafContents::Empty => ColorShift {\n                    dest_color: [0, 0, 0],\n                    percent: 0,\n                },\n                bsp::BspLeafContents::Lava => ColorShift {\n                    dest_color: [255, 80, 0],\n                    percent: 150,\n                },\n                bsp::BspLeafContents::Slime => ColorShift {\n                    dest_color: [0, 25, 5],\n                    percent: 150,\n                },\n                _ => ColorShift {\n                    dest_color: [130, 80, 50],\n                    percent: 128,\n                },\n            },\n        );\n\n        // decay damage and item pickup shifts\n        // always decay at least 1 \"percent\" (actually 1/255)\n        // TODO: make percent an actual percent ([0.0, 1.0])\n        let mut dmg_shift = self.color_shifts[ColorShiftCode::Damage as usize].borrow_mut();\n        dmg_shift.percent -= ((float_time * 150.0) as i32).max(1);\n        dmg_shift.percent = dmg_shift.percent.max(0);\n\n        let mut bonus_shift = self.color_shifts[ColorShiftCode::Bonus as usize].borrow_mut();\n        bonus_shift.percent -= ((float_time * 100.0) as i32).max(1);\n        bonus_shift.percent = bonus_shift.percent.max(0);\n\n        // set power-up overlay\n        self.color_shifts[ColorShiftCode::Powerup as usize].replace(\n            if self.items.contains(ItemFlags::QUAD) {\n                ColorShift {\n                    dest_color: [0, 0, 255],\n                    percent: 30,\n                }\n            } else if self.items.contains(ItemFlags::SUIT) {\n                ColorShift {\n                    dest_color: [0, 255, 0],\n                    percent: 20,\n                }\n            } else if self.items.contains(ItemFlags::INVISIBILITY) {\n                ColorShift {\n                    dest_color: [100, 100, 100],\n                    percent: 100,\n                }\n            } else if self.items.contains(ItemFlags::INVULNERABILITY) {\n                ColorShift {\n                    dest_color: [255, 255, 0],\n                    percent: 30,\n                }\n            } else {\n                ColorShift {\n                    dest_color: [0, 0, 0],\n                    percent: 0,\n                }\n            },\n        );\n\n        Ok(())\n    }\n\n    /// Update the view angles to the specified value, disabling interpolation.\n    pub fn set_view_angles(&mut self, angles: Vector3<Deg<f32>>) {\n        self.view.update_input_angles(Angles {\n            pitch: angles.x,\n            roll: angles.z,\n            yaw: angles.y,\n        });\n        let final_angles = self.view.final_angles();\n        self.entities[self.view.entity_id()].set_angles(Vector3::new(\n            final_angles.pitch,\n            final_angles.yaw,\n            final_angles.roll,\n        ));\n    }\n\n    /// Update the view angles to the specified value, enabling interpolation.\n    pub fn update_view_angles(&mut self, angles: Vector3<Deg<f32>>) {\n        self.view.update_input_angles(Angles {\n            pitch: angles.x,\n            roll: angles.z,\n            yaw: angles.y,\n        });\n        let final_angles = self.view.final_angles();\n        self.entities[self.view.entity_id()].update_angles(Vector3::new(\n            final_angles.pitch,\n            final_angles.yaw,\n            final_angles.roll,\n        ));\n    }\n\n    pub fn set_view_entity(&mut self, entity_id: usize) -> Result<(), ClientError> {\n        // view entity may not have been spawned yet, so check\n        // against both max_players and the current number of\n        // entities\n        if entity_id > self.max_players && entity_id >= self.entities.len() {\n            Err(ClientError::InvalidViewEntity(entity_id))?;\n        }\n        self.view.set_entity_id(entity_id);\n        Ok(())\n    }\n\n    pub fn models(&self) -> &[Model] {\n        &self.models\n    }\n\n    pub fn viewmodel_id(&self) -> usize {\n        match self.stats[ClientStat::Weapon as usize] as usize {\n            0 => 0,\n            x => x - 1,\n        }\n    }\n\n    pub fn iter_visible_entities(&self) -> impl Iterator<Item = &ClientEntity> + Clone {\n        self.visible_entity_ids\n            .iter()\n            .map(move |i| &self.entities[*i])\n            .chain(self.temp_entities.iter())\n            .chain(self.static_entities.iter())\n    }\n\n    pub fn iter_particles(&self) -> impl Iterator<Item = &Particle> {\n        self.particles.iter()\n    }\n\n    pub fn iter_lights(&self) -> impl Iterator<Item = &Light> {\n        self.lights.iter()\n    }\n\n    pub fn time(&self) -> Duration {\n        self.time\n    }\n\n    pub fn view_entity_id(&self) -> usize {\n        self.view.entity_id()\n    }\n\n    pub fn camera(&self, aspect: f32, fov: Deg<f32>) -> Camera {\n        let fov_y = math::fov_x_to_fov_y(fov, aspect).unwrap();\n        Camera::new(\n            self.view.final_origin(),\n            self.view.final_angles(),\n            cgmath::perspective(fov_y, aspect, 4.0, 4096.0),\n        )\n    }\n\n    pub fn demo_camera(&self, aspect: f32, fov: Deg<f32>) -> Camera {\n        let fov_y = math::fov_x_to_fov_y(fov, aspect).unwrap();\n        let angles = self.entities[self.view.entity_id()].angles;\n        Camera::new(\n            self.view.final_origin(),\n            Angles {\n                pitch: angles.x,\n                roll: angles.z,\n                yaw: angles.y,\n            },\n            cgmath::perspective(fov_y, aspect, 4.0, 4096.0),\n        )\n    }\n\n    pub fn lightstyle_values(&self) -> Result<ArrayVec<f32, 64>, ClientError> {\n        let mut values = ArrayVec::new();\n\n        for lightstyle_id in 0..64 {\n            match self.light_styles.get(&lightstyle_id) {\n                Some(ls) => {\n                    let float_time = engine::duration_to_f32(self.time);\n                    let frame = if ls.len() == 0 {\n                        None\n                    } else {\n                        Some((float_time * 10.0) as usize % ls.len())\n                    };\n\n                    values.push(match frame {\n                        // 'z' - 'a' = 25, so divide by 12.5 to get range [0, 2]\n                        Some(f) => (ls.as_bytes()[f] - 'a' as u8) as f32 / 12.5,\n                        None => 1.0,\n                    })\n                }\n\n                None => Err(ClientError::NoSuchLightmapAnimation(lightstyle_id as usize))?,\n            }\n        }\n\n        Ok(values)\n    }\n\n    pub fn intermission(&self) -> Option<&IntermissionKind> {\n        self.intermission.as_ref()\n    }\n\n    pub fn start_time(&self) -> Duration {\n        self.start_time\n    }\n\n    pub fn completion_time(&self) -> Option<Duration> {\n        self.completion_time\n    }\n\n    pub fn stats(&self) -> &[i32] {\n        &self.stats\n    }\n\n    pub fn items(&self) -> ItemFlags {\n        self.items\n    }\n\n    pub fn item_pickup_times(&self) -> &[Duration] {\n        &self.item_get_time\n    }\n\n    pub fn face_anim_time(&self) -> Duration {\n        self.face_anim_time\n    }\n\n    pub fn color_shift(&self) -> [f32; 4] {\n        self.color_shifts.iter().fold([0.0; 4], |accum, elem| {\n            let elem_a = elem.borrow().percent as f32 / 255.0 / 2.0;\n            if elem_a == 0.0 {\n                return accum;\n            }\n            let in_a = accum[3];\n            let out_a = in_a + elem_a * (1.0 - in_a);\n            let color_factor = elem_a / out_a;\n\n            let mut out = [0.0; 4];\n            for i in 0..3 {\n                out[i] = accum[i] * (1.0 - color_factor)\n                    + elem.borrow().dest_color[i] as f32 / 255.0 * color_factor;\n            }\n            out[3] = out_a.min(1.0).max(0.0);\n            out\n        })\n    }\n\n    pub fn check_entity_id(&self, id: usize) -> Result<(), ClientError> {\n        match id {\n            0 => Err(ClientError::NullEntity),\n            e if e >= self.entities.len() => Err(ClientError::NoSuchEntity(id)),\n            _ => Ok(()),\n        }\n    }\n\n    pub fn check_player_id(&self, id: usize) -> Result<(), ClientError> {\n        if id >= net::MAX_CLIENTS {\n            Err(ClientError::NoSuchClient(id))\n        } else if id > self.max_players {\n            Err(ClientError::NoSuchPlayer(id))\n        } else {\n            Ok(())\n        }\n    }\n}\n"
  },
  {
    "path": "src/client/trace.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::collections::HashMap;\n\nuse serde::Serialize;\n\n/// Client-side debug tracing.\n\n#[derive(Serialize)]\npub struct TraceEntity {\n    pub msg_origins: [[f32; 3]; 2],\n    pub msg_angles_deg: [[f32; 3]; 2],\n    pub origin: [f32; 3],\n}\n\n#[derive(Serialize)]\npub struct TraceFrame {\n    pub msg_times_ms: [i64; 2],\n    pub time_ms: i64,\n    pub lerp_factor: f32,\n    pub entities: HashMap<u32, TraceEntity>,\n}\n"
  },
  {
    "path": "src/client/view.rs",
    "content": "use std::f32::consts::PI;\n\nuse crate::{\n    client::input::game::{Action, GameInput},\n    common::{\n        engine::{duration_from_f32, duration_to_f32},\n        math::{self, Angles},\n    },\n};\n\nuse super::IntermissionKind;\nuse cgmath::{Angle as _, Deg, InnerSpace as _, Vector3, Zero as _};\nuse chrono::Duration;\n\npub struct View {\n    // entity \"holding\" the camera\n    entity_id: usize,\n\n    // how high the entity is \"holding\" the camera\n    view_height: f32,\n\n    // TODO\n    ideal_pitch: Deg<f32>,\n\n    // view angles from the server\n    msg_angles: [Angles; 2],\n\n    // view angles from client input\n    input_angles: Angles,\n\n    // pitch and roll from damage\n    damage_angles: Angles,\n\n    // time at which damage punch decays to zero\n    damage_time: Duration,\n\n    // punch angles from server\n    punch_angles: Angles,\n\n    // final angles combining all sources\n    final_angles: Angles,\n\n    // final origin accounting for view bob\n    final_origin: Vector3<f32>,\n}\n\nimpl View {\n    pub fn new() -> View {\n        View {\n            entity_id: 0,\n            view_height: 0.0,\n            ideal_pitch: Deg(0.0),\n            msg_angles: [Angles::zero(); 2],\n            input_angles: Angles::zero(),\n            damage_angles: Angles::zero(),\n            damage_time: Duration::zero(),\n            punch_angles: Angles::zero(),\n            final_angles: Angles::zero(),\n            final_origin: Vector3::zero(),\n        }\n    }\n\n    pub fn entity_id(&self) -> usize {\n        self.entity_id\n    }\n\n    pub fn set_entity_id(&mut self, id: usize) {\n        self.entity_id = id;\n    }\n\n    pub fn view_height(&self) -> f32 {\n        self.view_height\n    }\n\n    pub fn set_view_height(&mut self, view_height: f32) {\n        self.view_height = view_height;\n    }\n\n    pub fn ideal_pitch(&self) -> Deg<f32> {\n        self.ideal_pitch\n    }\n\n    pub fn set_ideal_pitch(&mut self, ideal_pitch: Deg<f32>) {\n        self.ideal_pitch = ideal_pitch;\n    }\n\n    pub fn punch_angles(&self) -> Angles {\n        self.punch_angles\n    }\n\n    pub fn set_punch_angles(&mut self, punch_angles: Angles) {\n        self.punch_angles = punch_angles;\n    }\n\n    pub fn input_angles(&self) -> Angles {\n        self.input_angles\n    }\n\n    /// Update the current input angles with a new value.\n    pub fn update_input_angles(&mut self, input_angles: Angles) {\n        self.input_angles = input_angles;\n    }\n\n    pub fn handle_input(\n        &mut self,\n        frame_time: Duration,\n        game_input: &GameInput,\n        intermission: Option<&IntermissionKind>,\n        mlook: bool,\n        cl_anglespeedkey: f32,\n        cl_pitchspeed: f32,\n        cl_yawspeed: f32,\n        mouse_vars: MouseVars,\n    ) {\n        let frame_time_f32 = duration_to_f32(frame_time);\n        let speed = if game_input.action_state(Action::Speed) {\n            frame_time_f32 * cl_anglespeedkey\n        } else {\n            frame_time_f32\n        };\n\n        // ignore camera controls during intermission\n        if intermission.is_some() {\n            return;\n        }\n\n        if !game_input.action_state(Action::Strafe) {\n            let right_factor = game_input.action_state(Action::Right) as i32 as f32;\n            let left_factor = game_input.action_state(Action::Left) as i32 as f32;\n            self.input_angles.yaw += Deg(speed * cl_yawspeed * (left_factor - right_factor));\n            self.input_angles.yaw = self.input_angles.yaw.normalize();\n        }\n\n        let lookup_factor = game_input.action_state(Action::LookUp) as i32 as f32;\n        let lookdown_factor = game_input.action_state(Action::LookDown) as i32 as f32;\n        self.input_angles.pitch += Deg(speed * cl_pitchspeed * (lookdown_factor - lookup_factor));\n\n        if mlook {\n            let pitch_factor = mouse_vars.m_pitch * mouse_vars.sensitivity;\n            let yaw_factor = mouse_vars.m_yaw * mouse_vars.sensitivity;\n            self.input_angles.pitch += Deg(game_input.mouse_delta().1 as f32 * pitch_factor);\n            self.input_angles.yaw -= Deg(game_input.mouse_delta().0 as f32 * yaw_factor);\n        }\n\n        if lookup_factor != 0.0 || lookdown_factor != 0.0 {\n            // TODO: V_StopPitchDrift\n        }\n\n        // clamp pitch to [-70, 80] and roll to [-50, 50]\n        self.input_angles.pitch = math::clamp_deg(self.input_angles.pitch, Deg(-70.0), Deg(80.0));\n        self.input_angles.roll = math::clamp_deg(self.input_angles.roll, Deg(-50.0), Deg(50.0));\n    }\n\n    pub fn handle_damage(\n        &mut self,\n        time: Duration,\n        armor_dmg: f32,\n        health_dmg: f32,\n        view_ent_origin: Vector3<f32>,\n        view_ent_angles: Angles,\n        src_origin: Vector3<f32>,\n        vars: KickVars,\n    ) {\n        self.damage_time = time + duration_from_f32(vars.v_kicktime);\n\n        // dmg_factor is at most 10.0\n        let dmg_factor = (armor_dmg + health_dmg).min(20.0) / 2.0;\n        let dmg_vector = (view_ent_origin - src_origin).normalize();\n        let rot = view_ent_angles.mat3_quake();\n\n        let roll_factor = dmg_vector.dot(-rot.x);\n        self.damage_angles.roll = Deg(dmg_factor * roll_factor * vars.v_kickroll);\n\n        let pitch_factor = dmg_vector.dot(rot.y);\n        self.damage_angles.pitch = Deg(dmg_factor * pitch_factor * vars.v_kickpitch);\n    }\n\n    pub fn calc_final_angles(\n        &mut self,\n        time: Duration,\n        intermission: Option<&IntermissionKind>,\n        velocity: Vector3<f32>,\n        mut idle_vars: IdleVars,\n        kick_vars: KickVars,\n        roll_vars: RollVars,\n    ) {\n        let move_angles = Angles {\n            pitch: Deg(0.0),\n            roll: roll(self.input_angles, velocity, roll_vars),\n            yaw: Deg(0.0),\n        };\n\n        let kick_factor = duration_to_f32(self.damage_time - time).max(0.0) / kick_vars.v_kicktime;\n        let damage_angles = self.damage_angles * kick_factor;\n\n        // always idle during intermission\n        if intermission.is_some() {\n            idle_vars.v_idlescale = 1.0;\n        }\n        let idle_angles = idle(time, idle_vars);\n\n        self.final_angles =\n            self.input_angles + move_angles + damage_angles + self.punch_angles + idle_angles;\n    }\n\n    pub fn final_angles(&self) -> Angles {\n        self.final_angles\n    }\n\n    pub fn calc_final_origin(\n        &mut self,\n        time: Duration,\n        origin: Vector3<f32>,\n        velocity: Vector3<f32>,\n        bob_vars: BobVars,\n    ) {\n        // offset the view by 1/32 unit to keep it from intersecting liquid planes\n        let plane_offset = Vector3::new(1.0 / 32.0, 1.0 / 32.0, 1.0 / 32.0);\n        let height_offset = Vector3::new(0.0, 0.0, self.view_height);\n        let bob_offset = Vector3::new(0.0, 0.0, bob(time, velocity, bob_vars));\n        self.final_origin = origin + plane_offset + height_offset + bob_offset;\n    }\n\n    pub fn final_origin(&self) -> Vector3<f32> {\n        self.final_origin\n    }\n\n    pub fn viewmodel_angle(&self) -> Angles {\n        // TODO\n        self.final_angles()\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct MouseVars {\n    pub m_pitch: f32,\n    pub m_yaw: f32,\n    pub sensitivity: f32,\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct KickVars {\n    pub v_kickpitch: f32,\n    pub v_kickroll: f32,\n    pub v_kicktime: f32,\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct BobVars {\n    pub cl_bob: f32,\n    pub cl_bobcycle: f32,\n    pub cl_bobup: f32,\n}\n\npub fn bob(time: Duration, velocity: Vector3<f32>, vars: BobVars) -> f32 {\n    let time = duration_to_f32(time);\n    let ratio = (time % vars.cl_bobcycle) / vars.cl_bobcycle;\n    let cycle = if ratio < vars.cl_bobup {\n        PI * ratio / vars.cl_bobup\n    } else {\n        PI + PI * (ratio - vars.cl_bobup) / (1.0 - vars.cl_bobup)\n    };\n\n    // drop z coordinate\n    let vel_mag = velocity.truncate().magnitude();\n    let bob = vars.cl_bob * (vel_mag * 0.3 + vel_mag * 0.7 * cycle.sin());\n\n    bob.max(-7.0).min(4.0)\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct RollVars {\n    pub cl_rollangle: f32,\n    pub cl_rollspeed: f32,\n}\n\npub fn roll(angles: Angles, velocity: Vector3<f32>, vars: RollVars) -> Deg<f32> {\n    let rot = angles.mat3_quake();\n    let side = velocity.dot(rot.y);\n    let sign = side.signum();\n    let side_abs = side.abs();\n\n    let roll_abs = if side < vars.cl_rollspeed {\n        side_abs * vars.cl_rollangle / vars.cl_rollspeed\n    } else {\n        vars.cl_rollangle\n    };\n\n    Deg(roll_abs * sign)\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct IdleVars {\n    pub v_idlescale: f32,\n    pub v_ipitch_cycle: f32,\n    pub v_ipitch_level: f32,\n    pub v_iroll_cycle: f32,\n    pub v_iroll_level: f32,\n    pub v_iyaw_cycle: f32,\n    pub v_iyaw_level: f32,\n}\n\npub fn idle(time: Duration, vars: IdleVars) -> Angles {\n    let time = duration_to_f32(time);\n    let pitch = Deg(vars.v_idlescale * (time * vars.v_ipitch_cycle).sin() * vars.v_ipitch_level);\n    let roll = Deg(vars.v_idlescale * (time * vars.v_iroll_cycle).sin() * vars.v_iroll_level);\n    let yaw = Deg(vars.v_idlescale * (time * vars.v_iyaw_cycle).sin() * vars.v_iyaw_level);\n\n    Angles { pitch, roll, yaw }\n}\n"
  },
  {
    "path": "src/common/alloc.rs",
    "content": "use std::{collections::LinkedList, mem};\n\nuse slab::Slab;\n\n/// A slab allocator with a linked list of allocations.\n///\n/// This allocator trades O(1) random access by key, a property of\n/// [`Slab`](slab::Slab), for the ability to iterate only those entries that are\n/// actually allocated. This significantly reduces the cost of `retain()`: where\n/// `Slab::retain` is O(capacity) regardless of how many values are allocated,\n/// [`LinkedSlab::retain`](LinkedSlab::retain) is O(n) in the number of values.\npub struct LinkedSlab<T> {\n    slab: Slab<T>,\n    allocated: LinkedList<usize>,\n}\n\nimpl<T> LinkedSlab<T> {\n    /// Construct a new, empty `LinkedSlab` with the specified capacity.\n    ///\n    /// The returned allocator will be able to store exactly `capacity` without\n    /// reallocating. If `capacity` is 0, the slab will not allocate.\n    pub fn with_capacity(capacity: usize) -> LinkedSlab<T> {\n        LinkedSlab {\n            slab: Slab::with_capacity(capacity),\n            allocated: LinkedList::new(),\n        }\n    }\n\n    /// Return the number of values the allocator can store without reallocating.\n    pub fn capacity(&self) -> usize {\n        self.slab.capacity()\n    }\n\n    /// Clear the allocator of all values.\n    pub fn clear(&mut self) {\n        self.allocated.clear();\n        self.slab.clear();\n    }\n\n    /// Return the number of stored values.\n    pub fn len(&self) -> usize {\n        self.slab.len()\n    }\n\n    /// Return `true` if there are no values allocated.\n    pub fn is_empty(&self) -> bool {\n        self.slab.is_empty()\n    }\n\n    /// Return an iterator over the allocated values.\n    pub fn iter(&self) -> impl Iterator<Item = &T> {\n        self.allocated\n            .iter()\n            .map(move |key| self.slab.get(*key).unwrap())\n    }\n\n    /// Return a reference to the value associated with the given key.\n    ///\n    /// If the given key is not associated with a value, then None is returned.\n    pub fn get(&self, key: usize) -> Option<&T> {\n        self.slab.get(key)\n    }\n\n    /// Return a mutable reference to the value associated with the given key.\n    ///\n    /// If the given key is not associated with a value, then None is returned.\n    pub fn get_mut(&mut self, key: usize) -> Option<&mut T> {\n        self.slab.get_mut(key)\n    }\n\n    /// Allocate a value, returning the key assigned to the value.\n    ///\n    /// This operation is O(1).\n    pub fn insert(&mut self, val: T) -> usize {\n        let key = self.slab.insert(val);\n        self.allocated.push_front(key);\n        key\n    }\n\n    /// Remove and return the value associated with the given key.\n    ///\n    /// The key is then released and may be associated with future stored values.\n    ///\n    /// Note that this operation is O(n) in the number of allocated values.\n    pub fn remove(&mut self, key: usize) -> T {\n        self.allocated.drain_filter(|k| *k == key);\n        self.slab.remove(key)\n    }\n\n    /// Return `true` if a value is associated with the given key.\n    pub fn contains(&self, key: usize) -> bool {\n        self.slab.contains(key)\n    }\n\n    /// Retain only the elements specified by the predicate.\n    ///\n    /// The predicate is permitted to modify allocated values in-place.\n    ///\n    /// This operation is O(n) in the number of allocated values.\n    pub fn retain<F>(&mut self, mut f: F)\n    where\n        F: FnMut(usize, &mut T) -> bool,\n    {\n        // move contents out to avoid double mutable borrow of self.\n        // neither LinkedList::new() nor Slab::new() allocates any memory, so\n        // this is free.\n        let mut allocated = mem::replace(&mut self.allocated, LinkedList::new());\n        let mut slab = mem::replace(&mut self.slab, Slab::new());\n\n        allocated.drain_filter(|k| {\n            let retain = match slab.get_mut(*k) {\n                Some(ref mut v) => f(*k, v),\n                None => true,\n            };\n\n            if !retain {\n                slab.remove(*k);\n            }\n\n            !retain\n        });\n\n        // put them back\n        self.slab = slab;\n        self.allocated = allocated;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use std::{collections::HashSet, iter::FromIterator as _};\n\n    #[test]\n    fn test_iter() {\n        let values: Vec<i32> = vec![1, 3, 5, 7, 11, 13, 17, 19];\n\n        let mut linked_slab = LinkedSlab::with_capacity(values.len());\n        let mut expected = HashSet::new();\n\n        for value in values.iter() {\n            linked_slab.insert(*value);\n            expected.insert(*value);\n        }\n\n        let mut actual = HashSet::new();\n        for value in linked_slab.iter() {\n            actual.insert(*value);\n        }\n\n        assert_eq!(expected, actual);\n    }\n\n    #[test]\n    fn test_retain() {\n        let mut values: Vec<i32> = vec![0, 9, 1, 8, 2, 7, 3, 6, 4, 5];\n\n        let mut linked_slab = LinkedSlab::with_capacity(values.len());\n\n        for value in values.iter() {\n            linked_slab.insert(*value);\n        }\n\n        values.retain(|v| v % 2 == 0);\n        let mut expected: HashSet<i32> = HashSet::from_iter(values.into_iter());\n\n        linked_slab.retain(|_, v| *v % 2 == 0);\n\n        let mut actual = HashSet::from_iter(linked_slab.iter().map(|v| *v));\n\n        assert_eq!(expected, actual);\n    }\n}\n"
  },
  {
    "path": "src/common/bitset.rs",
    "content": "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() -> Self {\n        BitSet { blocks: [0; N_64] }\n    }\n\n    pub fn all_set() -> Self {\n        BitSet {\n            blocks: [u64::MAX; N_64],\n        }\n    }\n\n    #[inline]\n    fn bit_location(bit: u64) -> (u64, u64) {\n        (\n            bit >> 6,        // divide by 64\n            1 << (bit & 63), // modulo 64\n        )\n    }\n\n    #[inline]\n    pub fn count(&self) -> usize {\n        let mut count = 0;\n\n        for block in self.blocks {\n            count += block.count_ones() as usize;\n        }\n\n        count\n    }\n\n    #[inline]\n    pub fn contains(&self, bit: u64) -> bool {\n        let (index, mask) = Self::bit_location(bit);\n        self.blocks[index as usize] & mask != 0\n    }\n\n    #[inline]\n    pub fn set(&mut self, bit: u64) {\n        let (index, mask) = Self::bit_location(bit);\n        self.blocks[index as usize] |= mask;\n    }\n\n    #[inline]\n    pub fn clear(&mut self, bit: u64) {\n        let (index, mask) = Self::bit_location(bit);\n        self.blocks[index as usize] &= !mask;\n    }\n\n    #[inline]\n    pub fn toggle(&mut self, bit: u64) {\n        let (index, mask) = Self::bit_location(bit);\n        self.blocks[index as usize] ^= mask;\n    }\n\n    #[inline]\n    pub fn iter(&self) -> BitSetIter<'_, N_64> {\n        BitSetIter::new(&self.blocks)\n    }\n}\n\npub struct BitSetIter<'a, const N_64: usize> {\n    block_index: usize,\n    block_val: u64,\n    blocks: &'a [u64; N_64],\n}\n\nimpl<'a, const N_64: usize> BitSetIter<'a, N_64> {\n    fn new(blocks: &'a [u64; N_64]) -> BitSetIter<'_, N_64> {\n        BitSetIter {\n            block_index: 0,\n            block_val: blocks[0],\n            blocks,\n        }\n    }\n}\n\nimpl<'a, const N_64: usize> Iterator for BitSetIter<'a, N_64> {\n    type Item = u64;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        while self.block_index < N_64 {\n            println!(\n                \"block_index = {} | block_val = {:b}\",\n                self.block_index, self.block_val\n            );\n\n            if self.block_val != 0 {\n                // Locate the next set bit in the block.\n                let next_bit = self.block_val.trailing_zeros();\n\n                // Clear the bit.\n                self.block_val &= !(1 << next_bit);\n\n                // Return it.\n                return Some((u64::BITS * self.block_index as u32 + next_bit) as u64);\n            } else {\n                // No set bits, move to the next block.\n                self.block_index += 1;\n                self.block_val = *self.blocks.get(self.block_index)?;\n            }\n        }\n\n        None\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_set_bit() {\n        let mut bits: BitSet<2> = BitSet::new();\n\n        let cases = &[0, 1, 63, 64];\n\n        for case in cases.iter().copied() {\n            bits.set(case);\n            assert!(bits.contains(case));\n        }\n    }\n\n    #[test]\n    fn test_clear_bit() {\n        let mut bits: BitSet<2> = BitSet::all_set();\n\n        let cases = &[0, 1, 63, 64];\n\n        for case in cases.iter().copied() {\n            bits.clear(case);\n            assert!(!bits.contains(case));\n        }\n    }\n\n    #[test]\n    fn test_iter() {\n        let mut bits: BitSet<8> = BitSet::new();\n\n        let cases = &[1, 2, 3, 5, 8, 13, 21, 34, 55, 89];\n\n        for case in cases.iter().cloned() {\n            bits.set(case);\n        }\n\n        let back = bits.iter().collect::<Vec<_>>();\n\n        assert_eq!(&cases[..], &back);\n    }\n}\n"
  },
  {
    "path": "src/common/bsp/load.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{\n    collections::HashMap,\n    io::{BufRead, BufReader, Read, Seek, SeekFrom},\n    mem::size_of,\n    rc::Rc,\n};\n\nuse crate::common::{\n    bsp::{\n        BspCollisionHull, BspCollisionNode, BspCollisionNodeChild, BspData, BspEdge,\n        BspEdgeDirection, BspEdgeIndex, BspFace, BspFaceSide, BspLeaf, BspLeafContents, BspModel,\n        BspRenderNode, BspRenderNodeChild, BspTexInfo, BspTexture, MAX_HULLS, MAX_LIGHTSTYLES,\n        MIPLEVELS,\n    },\n    math::{Axis, Hyperplane},\n    model::Model,\n    util::read_f32_3,\n};\n\nuse super::{BspTextureFrame, BspTextureKind};\nuse byteorder::{LittleEndian, ReadBytesExt};\nuse cgmath::{InnerSpace, Vector3};\nuse chrono::Duration;\nuse failure::ResultExt as _;\nuse num::FromPrimitive;\nuse thiserror::Error;\n\nconst VERSION: i32 = 29;\n\npub const MAX_MODELS: usize = 256;\nconst MAX_LEAVES: usize = 32767;\n\nconst MAX_ENTSTRING: usize = 65536;\nconst MAX_PLANES: usize = 8192;\nconst MAX_RENDER_NODES: usize = 32767;\nconst MAX_COLLISION_NODES: usize = 32767;\nconst MAX_VERTICES: usize = 65535;\nconst MAX_FACES: usize = 65535;\nconst _MAX_MARKTEXINFO: usize = 65535;\nconst _MAX_TEXINFO: usize = 4096;\nconst MAX_EDGES: usize = 256000;\nconst MAX_EDGELIST: usize = 512000;\nconst MAX_TEXTURES: usize = 0x200000;\nconst _MAX_LIGHTMAP: usize = 0x100000;\nconst MAX_VISLIST: usize = 0x100000;\n\nconst TEX_NAME_MAX: usize = 16;\n\nconst NUM_AMBIENTS: usize = 4;\nconst MAX_TEXTURE_FRAMES: usize = 10;\nconst TEXTURE_FRAME_LEN_MS: i64 = 200;\n\nconst ASCII_0: usize = '0' as usize;\nconst ASCII_9: usize = '9' as usize;\nconst ASCII_CAPITAL_A: usize = 'A' as usize;\nconst ASCII_CAPITAL_J: usize = 'J' as usize;\nconst ASCII_SMALL_A: usize = 'a' as usize;\nconst ASCII_SMALL_J: usize = 'j' as usize;\n\n#[derive(Error, Debug)]\npub enum BspFileError {\n    #[error(\"I/O error\")]\n    Io(#[from] std::io::Error),\n    #[error(\"unsupported BSP format version (expected {}, found {0})\", VERSION)]\n    UnsupportedVersion(i32),\n    #[error(\"negative BSP file section offset: {0}\")]\n    NegativeSectionOffset(i32),\n    #[error(\"negative BSP file section size: {0}\")]\n    NegativeSectionSize(i32),\n    #[error(\n        \"invalid BSP file section size: section {section:?} size is {size}, must be multiple of {}\",\n        section.element_size(),\n    )]\n    InvalidSectionSize {\n        section: BspFileSectionId,\n        size: usize,\n    },\n    #[error(\"invalid BSP texture frame specifier: {0}\")]\n    InvalidTextureFrameSpecifier(String),\n    #[error(\"texture has primary animation with 0 frames: {0}\")]\n    EmptyPrimaryAnimation(String),\n}\n\n#[derive(Copy, Clone, Debug)]\nstruct BspFileSection {\n    offset: u64,\n    size: usize,\n}\n\nimpl BspFileSection {\n    fn read_from<R>(reader: &mut R) -> Result<BspFileSection, BspFileError>\n    where\n        R: ReadBytesExt,\n    {\n        let offset = match reader.read_i32::<LittleEndian>()? {\n            ofs if ofs < 0 => Err(BspFileError::NegativeSectionOffset(ofs)),\n            ofs => Ok(ofs as u64),\n        }?;\n\n        let size = match reader.read_i32::<LittleEndian>()? {\n            sz if sz < 0 => Err(BspFileError::NegativeSectionSize(sz)),\n            sz => Ok(sz as usize),\n        }?;\n\n        Ok(BspFileSection { offset, size })\n    }\n}\n\nconst SECTION_COUNT: usize = 15;\n#[derive(Debug, FromPrimitive)]\npub enum BspFileSectionId {\n    Entities = 0,\n    Planes = 1,\n    Textures = 2,\n    Vertices = 3,\n    Visibility = 4,\n    RenderNodes = 5,\n    TextureInfo = 6,\n    Faces = 7,\n    Lightmaps = 8,\n    CollisionNodes = 9,\n    Leaves = 10,\n    FaceList = 11,\n    Edges = 12,\n    EdgeList = 13,\n    Models = 14,\n}\n\nconst PLANE_SIZE: usize = 20;\nconst RENDER_NODE_SIZE: usize = 24;\nconst LEAF_SIZE: usize = 28;\nconst TEXTURE_INFO_SIZE: usize = 40;\nconst FACE_SIZE: usize = 20;\nconst COLLISION_NODE_SIZE: usize = 8;\nconst FACELIST_SIZE: usize = 2;\nconst EDGE_SIZE: usize = 4;\nconst EDGELIST_SIZE: usize = 4;\nconst MODEL_SIZE: usize = 64;\nconst VERTEX_SIZE: usize = 12;\n\nimpl BspFileSectionId {\n    // the size on disk of one element of a BSP file section.\n    fn element_size(&self) -> usize {\n        use BspFileSectionId::*;\n        match self {\n            Entities => size_of::<u8>(),\n            Planes => PLANE_SIZE,\n            Textures => size_of::<u8>(),\n            Vertices => VERTEX_SIZE,\n            Visibility => size_of::<u8>(),\n            RenderNodes => RENDER_NODE_SIZE,\n            TextureInfo => TEXTURE_INFO_SIZE,\n            Faces => FACE_SIZE,\n            Lightmaps => size_of::<u8>(),\n            CollisionNodes => COLLISION_NODE_SIZE,\n            Leaves => LEAF_SIZE,\n            FaceList => FACELIST_SIZE,\n            Edges => EDGE_SIZE,\n            EdgeList => EDGELIST_SIZE,\n            Models => MODEL_SIZE,\n        }\n    }\n}\n\nstruct BspFileTable {\n    sections: [BspFileSection; SECTION_COUNT],\n}\n\nimpl BspFileTable {\n    fn read_from<R>(reader: &mut R) -> Result<BspFileTable, BspFileError>\n    where\n        R: ReadBytesExt,\n    {\n        let mut sections = [BspFileSection { offset: 0, size: 0 }; SECTION_COUNT];\n\n        for (id, section) in sections.iter_mut().enumerate() {\n            *section = BspFileSection::read_from(reader)?;\n            let section_id = BspFileSectionId::from_usize(id).unwrap();\n            if section.size % section_id.element_size() != 0 {\n                Err(BspFileError::InvalidSectionSize {\n                    section: section_id,\n                    size: section.size,\n                })?\n            }\n        }\n\n        Ok(BspFileTable { sections })\n    }\n\n    fn section(&self, section_id: BspFileSectionId) -> BspFileSection {\n        self.sections[section_id as usize]\n    }\n\n    fn check_end_position<S>(\n        &self,\n        seeker: &mut S,\n        section_id: BspFileSectionId,\n    ) -> Result<(), failure::Error>\n    where\n        S: Seek,\n    {\n        let section = self.section(section_id);\n        ensure!(\n            seeker.seek(SeekFrom::Current(0))?\n                == seeker.seek(SeekFrom::Start(section.offset + section.size as u64))?,\n            \"BSP read misaligned\"\n        );\n\n        Ok(())\n    }\n}\n\nfn read_hyperplane<R>(reader: &mut R) -> Result<Hyperplane, failure::Error>\nwhere\n    R: ReadBytesExt,\n{\n    let normal: Vector3<f32> = read_f32_3(reader)?.into();\n    let dist = reader.read_f32::<LittleEndian>()?;\n    let plane = match Axis::from_i32(reader.read_i32::<LittleEndian>()?) {\n        Some(ax) => match ax {\n            Axis::X => Hyperplane::axis_x(dist),\n            Axis::Y => Hyperplane::axis_y(dist),\n            Axis::Z => Hyperplane::axis_z(dist),\n        },\n        None => Hyperplane::new(normal, dist),\n    };\n\n    Ok(plane)\n}\n\n#[derive(Debug)]\nstruct BspFileTexture {\n    name: String,\n    width: u32,\n    height: u32,\n    mipmaps: [Vec<u8>; MIPLEVELS],\n}\n\n// load a textures from the BSP file.\n//\n// converts the texture's name to all lowercase, including its frame specifier\n// if it has one.\nfn load_texture<R>(\n    mut reader: &mut R,\n    tex_section_ofs: u64,\n    tex_ofs: u64,\n) -> Result<BspFileTexture, failure::Error>\nwhere\n    R: ReadBytesExt + Seek,\n{\n    // convert texture name from NUL-terminated to str\n    let mut tex_name_bytes = [0u8; TEX_NAME_MAX];\n    reader.read(&mut tex_name_bytes)?;\n    let len = tex_name_bytes\n        .iter()\n        .enumerate()\n        .find(|&item| item.1 == &0)\n        .unwrap_or((TEX_NAME_MAX, &0))\n        .0;\n    let tex_name = String::from_utf8(tex_name_bytes[..len].to_vec())?.to_lowercase();\n\n    let width = reader.read_u32::<LittleEndian>()?;\n    let height = reader.read_u32::<LittleEndian>()?;\n\n    let mut mip_offsets = [0usize; MIPLEVELS];\n    for m in 0..MIPLEVELS {\n        mip_offsets[m] = reader.read_u32::<LittleEndian>()? as usize;\n    }\n\n    let mut mipmaps = [Vec::new(), Vec::new(), Vec::new(), Vec::new()];\n    for m in 0..MIPLEVELS {\n        let factor = 2usize.pow(m as u32);\n        let mipmap_size = (width as usize / factor) * (height as usize / factor);\n        let offset = tex_section_ofs + tex_ofs + mip_offsets[m] as u64;\n        reader.seek(SeekFrom::Start(offset))?;\n        (&mut reader)\n            .take(mipmap_size as u64)\n            .read_to_end(&mut mipmaps[m])?;\n    }\n\n    Ok(BspFileTexture {\n        name: tex_name,\n        width,\n        height,\n        mipmaps,\n    })\n}\n\nfn load_render_node<R>(reader: &mut R) -> Result<BspRenderNode, failure::Error>\nwhere\n    R: ReadBytesExt,\n{\n    let plane_id = reader.read_i32::<LittleEndian>()?;\n    if plane_id < 0 {\n        bail!(\"Invalid plane id\");\n    }\n\n    // If the child ID is positive, it points to another internal node. If it is negative, its\n    // bitwise negation points to a leaf node.\n\n    let front = match reader.read_i16::<LittleEndian>()? {\n        f if f < 0 => BspRenderNodeChild::Leaf((!f) as usize),\n        f => BspRenderNodeChild::Node(f as usize),\n    };\n\n    let back = match reader.read_i16::<LittleEndian>()? {\n        b if b < 0 => BspRenderNodeChild::Leaf((!b) as usize),\n        b => BspRenderNodeChild::Node(b as usize),\n    };\n\n    let min = read_i16_3(reader)?;\n    let max = read_i16_3(reader)?;\n\n    let face_id = reader.read_i16::<LittleEndian>()?;\n    if face_id < 0 {\n        bail!(\"Invalid face id\");\n    }\n\n    let face_count = reader.read_u16::<LittleEndian>()?;\n    if face_count as usize > MAX_FACES {\n        bail!(\"Invalid face count\");\n    }\n\n    Ok(BspRenderNode {\n        plane_id: plane_id as usize,\n        children: [front, back],\n        min,\n        max,\n        face_id: face_id as usize,\n        face_count: face_count as usize,\n    })\n}\n\nfn load_texinfo<R>(reader: &mut R, texture_count: usize) -> Result<BspTexInfo, failure::Error>\nwhere\n    R: ReadBytesExt,\n{\n    let s_vector = read_f32_3(reader)?.into();\n    let s_offset = reader.read_f32::<LittleEndian>()?;\n    let t_vector = read_f32_3(reader)?.into();\n    let t_offset = reader.read_f32::<LittleEndian>()?;\n\n    let tex_id = match reader.read_i32::<LittleEndian>()? {\n        t if t < 0 || t as usize > texture_count => bail!(\"Invalid texture ID\"),\n        t => t as usize,\n    };\n\n    let special = match reader.read_i32::<LittleEndian>()? {\n        0 => false,\n        1 => true,\n        _ => bail!(\"Invalid texture flags\"),\n    };\n\n    Ok(BspTexInfo {\n        s_vector,\n        s_offset,\n        t_vector,\n        t_offset,\n\n        tex_id,\n        special,\n    })\n}\n\n/// Load a BSP file, returning the models it contains and a `String` describing the entities\n/// it contains.\npub fn load<R>(data: R) -> Result<(Vec<Model>, String), failure::Error>\nwhere\n    R: Read + Seek,\n{\n    let mut reader = BufReader::new(data);\n\n    let _version = match reader.read_i32::<LittleEndian>()? {\n        VERSION => Ok(VERSION),\n        other => Err(BspFileError::UnsupportedVersion(other)),\n    }?;\n\n    let table = BspFileTable::read_from(&mut reader)?;\n\n    let ent_section = table.section(BspFileSectionId::Entities);\n    let plane_section = table.section(BspFileSectionId::Planes);\n    let tex_section = table.section(BspFileSectionId::Textures);\n    let vert_section = table.section(BspFileSectionId::Vertices);\n    let vis_section = table.section(BspFileSectionId::Visibility);\n    let texinfo_section = table.section(BspFileSectionId::TextureInfo);\n    let face_section = table.section(BspFileSectionId::Faces);\n    let lightmap_section = table.section(BspFileSectionId::Lightmaps);\n    let collision_node_section = table.section(BspFileSectionId::CollisionNodes);\n    let leaf_section = table.section(BspFileSectionId::Leaves);\n    let facelist_section = table.section(BspFileSectionId::FaceList);\n    let edge_section = table.section(BspFileSectionId::Edges);\n    let edgelist_section = table.section(BspFileSectionId::EdgeList);\n    let model_section = table.section(BspFileSectionId::Models);\n    let render_node_section = table.section(BspFileSectionId::RenderNodes);\n\n    let plane_count = plane_section.size / PLANE_SIZE;\n    let vert_count = vert_section.size / VERTEX_SIZE;\n    let render_node_count = render_node_section.size / RENDER_NODE_SIZE;\n    let texinfo_count = texinfo_section.size / TEXTURE_INFO_SIZE;\n    let face_count = face_section.size / FACE_SIZE;\n    let collision_node_count = collision_node_section.size / COLLISION_NODE_SIZE;\n    let leaf_count = leaf_section.size / LEAF_SIZE;\n    let facelist_count = facelist_section.size / FACELIST_SIZE;\n    let edge_count = edge_section.size / EDGE_SIZE;\n    let edgelist_count = edgelist_section.size / EDGELIST_SIZE;\n    let model_count = model_section.size / MODEL_SIZE;\n\n    // check limits\n    ensure!(plane_count <= MAX_PLANES, \"Plane count exceeds MAX_PLANES\");\n    ensure!(\n        vert_count <= MAX_VERTICES,\n        \"Vertex count exceeds MAX_VERTICES\"\n    );\n    ensure!(\n        vis_section.size <= MAX_VISLIST,\n        \"Visibility data size exceeds MAX_VISLIST\"\n    );\n    ensure!(\n        render_node_count <= MAX_RENDER_NODES,\n        \"Render node count exceeds MAX_RENDER_NODES\"\n    );\n    ensure!(\n        collision_node_count <= MAX_COLLISION_NODES,\n        \"Collision node count exceeds MAX_COLLISION_NODES\"\n    );\n    ensure!(leaf_count <= MAX_LEAVES, \"Leaf count exceeds MAX_LEAVES\");\n    ensure!(edge_count <= MAX_EDGES, \"Edge count exceeds MAX_EDGES\");\n    ensure!(\n        edgelist_count <= MAX_EDGELIST,\n        \"Edge list count exceeds MAX_EDGELIST\"\n    );\n    ensure!(\n        model_count > 0,\n        \"No brush models (need at least 1 for worldmodel)\"\n    );\n    ensure!(model_count <= MAX_MODELS, \"Model count exceeds MAX_MODELS\");\n\n    reader.seek(SeekFrom::Start(ent_section.offset))?;\n    let mut ent_data = Vec::with_capacity(MAX_ENTSTRING);\n    reader.read_until(0x00, &mut ent_data)?;\n    ensure!(\n        ent_data.len() <= MAX_ENTSTRING,\n        \"Entity data exceeds MAX_ENTSTRING\"\n    );\n    let ent_string =\n        String::from_utf8(ent_data).context(\"Failed to create string from entity data\")?;\n    table.check_end_position(&mut reader, BspFileSectionId::Entities)?;\n\n    // load planes\n    reader.seek(SeekFrom::Start(plane_section.offset))?;\n    let mut planes = Vec::with_capacity(plane_count);\n    for _ in 0..plane_count {\n        planes.push(read_hyperplane(&mut reader)?);\n    }\n    let planes_rc = Rc::new(planes.into_boxed_slice());\n\n    table.check_end_position(&mut reader, BspFileSectionId::Planes)?;\n\n    // load textures\n    reader.seek(SeekFrom::Start(tex_section.offset))?;\n    let tex_count = reader.read_i32::<LittleEndian>()?;\n    ensure!(\n        tex_count >= 0 && tex_count as usize <= MAX_TEXTURES,\n        \"Invalid texture count\"\n    );\n    let tex_count = tex_count as usize;\n\n    let mut tex_offsets = Vec::with_capacity(tex_count);\n    for _ in 0..tex_count {\n        let ofs = reader.read_i32::<LittleEndian>()?;\n\n        tex_offsets.push(match ofs {\n            o if o < -1 => bail!(\"negative texture offset ({})\", ofs),\n            -1 => None,\n            o => Some(o as usize),\n        });\n    }\n\n    let mut file_textures = Vec::with_capacity(tex_count);\n    for (id, tex_ofs) in tex_offsets.into_iter().enumerate() {\n        match tex_ofs {\n            Some(ofs) => {\n                reader.seek(SeekFrom::Start(tex_section.offset + ofs as u64))?;\n                let texture = load_texture(&mut reader, tex_section.offset as u64, ofs as u64)?;\n                debug!(\n                    \"Texture {id:>width$}: {name}\",\n                    id = id,\n                    width = (tex_count as f32).log(10.0) as usize,\n                    name = texture.name,\n                );\n\n                file_textures.push(texture);\n            }\n\n            None => {\n                file_textures.push(BspFileTexture {\n                    name: String::new(),\n                    width: 0,\n                    height: 0,\n                    mipmaps: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],\n                });\n            }\n        }\n    }\n    table.check_end_position(&mut reader, BspFileSectionId::Textures)?;\n\n    struct BspFileTextureAnimations {\n        primary: Vec<(usize, BspFileTexture)>,\n        alternate: Vec<(usize, BspFileTexture)>,\n    }\n\n    // maps animated texture names to primary and alternate animations\n    // e.g., for textures of the form +#slip, maps \"slip\" to the ids of\n    // [+0slip, +1slip, ...] and [+aslip, +bslip, ...]\n    let mut anim_file_textures: HashMap<String, BspFileTextureAnimations> = HashMap::new();\n\n    // final texture array\n    let mut textures = Vec::new();\n    // mapping from texture ids on disk to texture ids in memory\n    let mut texture_ids = Vec::new();\n\n    // map file texture ids to actual texture ids\n    let mut static_texture_ids = HashMap::new();\n    let mut animated_texture_ids = HashMap::new();\n\n    debug!(\"Sequencing textures\");\n    for (file_texture_id, file_texture) in file_textures.into_iter().enumerate() {\n        // recognize textures of the form +[frame][stem], where:\n        // - frame is in [0-9A-Za-z]\n        // - stem is the remainder of the string\n        match file_texture.name.strip_prefix(\"+\") {\n            Some(rest) => {\n                let (frame, stem) = rest.split_at(1);\n\n                debug!(\n                    \"Sequencing texture {}: {}\",\n                    file_texture_id, &file_texture.name\n                );\n\n                let anims =\n                    anim_file_textures\n                        .entry(stem.to_owned())\n                        .or_insert(BspFileTextureAnimations {\n                            primary: Vec::new(),\n                            alternate: Vec::new(),\n                        });\n\n                match frame.chars().nth(0).unwrap() {\n                    '0'..='9' => anims.primary.push((file_texture_id, file_texture)),\n                    // guaranteed to be lowercase by load_texture\n                    'a'..='j' => anims.alternate.push((file_texture_id, file_texture)),\n                    _ => Err(BspFileError::InvalidTextureFrameSpecifier(\n                        file_texture.name.clone(),\n                    ))?,\n                };\n            }\n\n            // if the string doesn't match, it's not animated, so add it as a static texture\n            None => {\n                let BspFileTexture {\n                    name,\n                    width,\n                    height,\n                    mipmaps,\n                } = file_texture;\n\n                let texture_id = textures.len();\n                static_texture_ids.insert(file_texture_id, texture_id);\n\n                textures.push(BspTexture {\n                    name,\n                    width,\n                    height,\n                    kind: BspTextureKind::Static(BspTextureFrame { mipmaps }),\n                });\n            }\n        };\n    }\n\n    // sequence animated textures with the same stem\n    for (\n        name,\n        BspFileTextureAnimations {\n            primary: mut pri,\n            alternate: mut alt,\n        },\n    ) in anim_file_textures.into_iter()\n    {\n        if pri.len() == 0 {\n            Err(BspFileError::EmptyPrimaryAnimation(name.to_owned()))?;\n        }\n\n        // TODO: ensure one-to-one frame specifiers\n        // sort names in ascending order to get the frames ordered correctly\n        pri.sort_unstable_by(|(_, tex), (_, other)| tex.name.cmp(&other.name));\n\n        // TODO: verify width and height?\n        let width = pri[0].1.width;\n        let height = pri[0].1.height;\n\n        // texture id of each frame in the file\n        let mut corresponding_file_ids = Vec::new();\n        let mut primary = Vec::new();\n        for (file_id, file_texture) in pri {\n            debug!(\n                \"primary frame: id = {}, name = {}\",\n                file_id, file_texture.name\n            );\n            corresponding_file_ids.push(file_id);\n            primary.push(BspTextureFrame {\n                mipmaps: file_texture.mipmaps,\n            });\n        }\n\n        let mut alt_corresp_file_ids = Vec::new();\n        let alternate = match alt.len() {\n            0 => None,\n            _ => {\n                alt.sort_unstable_by(|(_, tex), (_, other)| tex.name.cmp(&other.name));\n                let mut alternate = Vec::new();\n                for (file_id, file_texture) in alt {\n                    alt_corresp_file_ids.push(file_id);\n                    alternate.push(BspTextureFrame {\n                        mipmaps: file_texture.mipmaps,\n                    });\n                }\n                Some(alternate)\n            }\n        };\n\n        // actual id of the animated texture\n        let texture_id = textures.len();\n\n        // update map to point other data to the right texture\n        for id in corresponding_file_ids {\n            debug!(\"map disk texture id {} to texture id {}\", id, texture_id);\n            animated_texture_ids.insert(id, texture_id);\n        }\n\n        for id in alt_corresp_file_ids {\n            debug!(\"map disk texture id {} to texture id {}\", id, texture_id);\n            animated_texture_ids.insert(id, texture_id);\n        }\n\n        // push the sequenced texture\n        textures.push(BspTexture {\n            name: name.to_owned(),\n            width,\n            height,\n            kind: BspTextureKind::Animated { primary, alternate },\n        });\n    }\n\n    // build disk-to-memory texture id map\n    for file_texture_id in 0..tex_count {\n        texture_ids.push(if let Some(id) = static_texture_ids.get(&file_texture_id) {\n            *id\n        } else if let Some(id) = animated_texture_ids.get(&file_texture_id) {\n            *id\n        } else {\n            panic!(\n                \"Texture sequencing failed: texture with id {} unaccounted for\",\n                file_texture_id\n            );\n        });\n    }\n\n    reader.seek(SeekFrom::Start(vert_section.offset))?;\n    let mut vertices = Vec::with_capacity(vert_count);\n    for _ in 0..vert_count {\n        vertices.push(read_f32_3(&mut reader)?.into());\n    }\n    table.check_end_position(&mut reader, BspFileSectionId::Vertices)?;\n\n    reader.seek(SeekFrom::Start(vis_section.offset))?;\n\n    // visibility data\n    let mut vis_data = Vec::with_capacity(vis_section.size);\n    (&mut reader)\n        .take(vis_section.size as u64)\n        .read_to_end(&mut vis_data)?;\n    table.check_end_position(&mut reader, BspFileSectionId::Visibility)?;\n\n    // render nodes\n    reader.seek(SeekFrom::Start(render_node_section.offset))?;\n    debug!(\"Render node count = {}\", render_node_count);\n    let mut render_nodes = Vec::with_capacity(render_node_count);\n    for _ in 0..render_node_count {\n        render_nodes.push(load_render_node(&mut reader)?);\n    }\n    table.check_end_position(&mut reader, BspFileSectionId::RenderNodes)?;\n\n    // texinfo\n    reader.seek(SeekFrom::Start(texinfo_section.offset))?;\n    let mut texinfo = Vec::with_capacity(texinfo_count);\n    for _ in 0..texinfo_count {\n        let mut txi = load_texinfo(&mut reader, tex_count)?;\n        // !!! IMPORTANT !!!\n        // remap texture ids from the on-disk ids to our ids\n        txi.tex_id = texture_ids[txi.tex_id];\n        texinfo.push(txi);\n    }\n    table.check_end_position(&mut reader, BspFileSectionId::TextureInfo)?;\n\n    reader.seek(SeekFrom::Start(face_section.offset))?;\n    let mut faces = Vec::with_capacity(face_count);\n    for _ in 0..face_count {\n        let plane_id = reader.read_i16::<LittleEndian>()?;\n        if plane_id < 0 || plane_id as usize > plane_count {\n            bail!(\"Invalid plane count\");\n        }\n\n        let side = match reader.read_i16::<LittleEndian>()? {\n            0 => BspFaceSide::Front,\n            1 => BspFaceSide::Back,\n            _ => bail!(\"Invalid face side\"),\n        };\n\n        let edge_id = reader.read_i32::<LittleEndian>()?;\n        if edge_id < 0 {\n            bail!(\"Invalid edge ID\");\n        }\n\n        let edge_count = reader.read_i16::<LittleEndian>()?;\n        if edge_count < 3 {\n            bail!(\"Invalid edge count\");\n        }\n\n        let texinfo_id = reader.read_i16::<LittleEndian>()?;\n        if texinfo_id < 0 || texinfo_id as usize > texinfo_count {\n            bail!(\"Invalid texinfo ID\");\n        }\n\n        let mut light_styles = [0; MAX_LIGHTSTYLES];\n        for i in 0..light_styles.len() {\n            light_styles[i] = reader.read_u8()?;\n        }\n\n        let lightmap_id = match reader.read_i32::<LittleEndian>()? {\n            o if o < -1 => bail!(\"Invalid lightmap offset\"),\n            -1 => None,\n            o => Some(o as usize),\n        };\n\n        faces.push(BspFace {\n            plane_id: plane_id as usize,\n            side,\n            edge_id: edge_id as usize,\n            edge_count: edge_count as usize,\n            texinfo_id: texinfo_id as usize,\n            light_styles,\n            lightmap_id,\n            texture_mins: [0, 0],\n            extents: [0, 0],\n        });\n    }\n    table.check_end_position(&mut reader, BspFileSectionId::Faces)?;\n\n    reader.seek(SeekFrom::Start(lightmap_section.offset))?;\n    let mut lightmaps = Vec::with_capacity(lightmap_section.size);\n    (&mut reader)\n        .take(lightmap_section.size as u64)\n        .read_to_end(&mut lightmaps)?;\n    table.check_end_position(&mut reader, BspFileSectionId::Lightmaps)?;\n\n    reader.seek(SeekFrom::Start(collision_node_section.offset))?;\n\n    let mut collision_nodes = Vec::with_capacity(collision_node_count);\n    for _ in 0..collision_node_count {\n        let plane_id = match reader.read_i32::<LittleEndian>()? {\n            x if x < 0 => bail!(\"Invalid plane id\"),\n            x => x as usize,\n        };\n\n        let front = match reader.read_i16::<LittleEndian>()? {\n            x if x < 0 => match BspLeafContents::from_i16(-x) {\n                Some(c) => BspCollisionNodeChild::Contents(c),\n                None => bail!(\"Invalid leaf contents ({})\", -x),\n            },\n            x => BspCollisionNodeChild::Node(x as usize),\n        };\n\n        let back = match reader.read_i16::<LittleEndian>()? {\n            x if x < 0 => match BspLeafContents::from_i16(-x) {\n                Some(c) => BspCollisionNodeChild::Contents(c),\n                None => bail!(\"Invalid leaf contents ({})\", -x),\n            },\n            x => BspCollisionNodeChild::Node(x as usize),\n        };\n\n        collision_nodes.push(BspCollisionNode {\n            plane_id,\n            children: [front, back],\n        });\n    }\n\n    let collision_nodes_rc = Rc::new(collision_nodes.into_boxed_slice());\n\n    let hull_1 = BspCollisionHull {\n        planes: planes_rc.clone(),\n        nodes: collision_nodes_rc.clone(),\n        node_id: 0,\n        node_count: collision_node_count,\n        mins: Vector3::new(-16.0, -16.0, -24.0),\n        maxs: Vector3::new(16.0, 16.0, 32.0),\n    };\n\n    let hull_2 = BspCollisionHull {\n        planes: planes_rc.clone(),\n        nodes: collision_nodes_rc.clone(),\n        node_id: 0,\n        node_count: collision_node_count,\n        mins: Vector3::new(-32.0, -32.0, -24.0),\n        maxs: Vector3::new(32.0, 32.0, 64.0),\n    };\n\n    if reader.seek(SeekFrom::Current(0))?\n        != reader.seek(SeekFrom::Start(\n            collision_node_section.offset + collision_node_section.size as u64,\n        ))?\n    {\n        bail!(\"BSP read data misaligned\");\n    }\n\n    reader.seek(SeekFrom::Start(leaf_section.offset))?;\n\n    let mut leaves = Vec::with_capacity(leaf_count);\n    // leaves.push(BspLeaf {\n    // contents: BspLeafContents::Solid,\n    // vis_offset: None,\n    // min: [-32768, -32768, -32768],\n    // max: [32767, 32767, 32767],\n    // facelist_id: 0,\n    // facelist_count: 0,\n    // sounds: [0u8; NUM_AMBIENTS],\n    // });\n\n    for _ in 0..leaf_count {\n        // note the negation here (the constants are negative in the original engine to differentiate\n        // them from plane IDs)\n        let contents_id = -reader.read_i32::<LittleEndian>()?;\n\n        let contents = match BspLeafContents::from_i32(contents_id) {\n            Some(c) => c,\n            None => bail!(\"Invalid leaf contents ({})\", contents_id),\n        };\n\n        let vis_offset = match reader.read_i32::<LittleEndian>()? {\n            x if x < -1 => bail!(\"Invalid visibility data offset\"),\n            -1 => None,\n            x => Some(x as usize),\n        };\n\n        let min = read_i16_3(&mut reader)?;\n        let max = read_i16_3(&mut reader)?;\n\n        let facelist_id = reader.read_u16::<LittleEndian>()? as usize;\n        let facelist_count = reader.read_u16::<LittleEndian>()? as usize;\n        let mut sounds = [0u8; NUM_AMBIENTS];\n        reader.read(&mut sounds)?;\n        leaves.push(BspLeaf {\n            contents,\n            vis_offset,\n            min,\n            max,\n            facelist_id,\n            facelist_count,\n            sounds,\n        });\n    }\n    table.check_end_position(&mut reader, BspFileSectionId::Leaves)?;\n\n    reader.seek(SeekFrom::Start(facelist_section.offset))?;\n    let mut facelist = Vec::with_capacity(facelist_count);\n    for _ in 0..facelist_count {\n        facelist.push(reader.read_u16::<LittleEndian>()? as usize);\n    }\n    if reader.seek(SeekFrom::Current(0))?\n        != reader.seek(SeekFrom::Start(\n            facelist_section.offset + facelist_section.size as u64,\n        ))?\n    {\n        bail!(\"BSP read data misaligned\");\n    }\n\n    reader.seek(SeekFrom::Start(edge_section.offset))?;\n    let mut edges = Vec::with_capacity(edge_count);\n    for _ in 0..edge_count {\n        edges.push(BspEdge {\n            vertex_ids: [\n                reader.read_u16::<LittleEndian>()?,\n                reader.read_u16::<LittleEndian>()?,\n            ],\n        });\n    }\n    table.check_end_position(&mut reader, BspFileSectionId::Edges)?;\n\n    reader.seek(SeekFrom::Start(edgelist_section.offset))?;\n    let mut edgelist = Vec::with_capacity(edgelist_count);\n    for _ in 0..edgelist_count {\n        edgelist.push(match reader.read_i32::<LittleEndian>()? {\n            x if x >= 0 => BspEdgeIndex {\n                direction: BspEdgeDirection::Forward,\n                index: x as usize,\n            },\n\n            x if x < 0 => BspEdgeIndex {\n                direction: BspEdgeDirection::Backward,\n                index: -x as usize,\n            },\n\n            x => bail!(format!(\"Invalid edge index {}\", x)),\n        });\n    }\n    if reader.seek(SeekFrom::Current(0))?\n        != reader.seek(SeekFrom::Start(\n            edgelist_section.offset + edgelist_section.size as u64,\n        ))?\n    {\n        bail!(\"BSP read data misaligned\");\n    }\n\n    // see Calc_SurfaceExtents,\n    // https://github.com/id-Software/Quake/blob/master/WinQuake/gl_model.c#L705-L749\n\n    for (face_id, face) in faces.iter_mut().enumerate() {\n        let texinfo = &texinfo[face.texinfo_id];\n\n        let mut s_min = ::std::f32::INFINITY;\n        let mut t_min = ::std::f32::INFINITY;\n        let mut s_max = ::std::f32::NEG_INFINITY;\n        let mut t_max = ::std::f32::NEG_INFINITY;\n\n        for edge_idx in &edgelist[face.edge_id..face.edge_id + face.edge_count] {\n            let vertex_id = edges[edge_idx.index].vertex_ids[edge_idx.direction as usize] as usize;\n            let vertex = vertices[vertex_id];\n            let s = texinfo.s_vector.dot(vertex) + texinfo.s_offset;\n            let t = texinfo.t_vector.dot(vertex) + texinfo.t_offset;\n\n            s_min = s_min.min(s);\n            s_max = s_max.max(s);\n            t_min = t_min.min(t);\n            t_max = t_max.max(t);\n        }\n\n        let b_mins = [(s_min / 16.0).floor(), (t_min / 16.0).floor()];\n        let b_maxs = [(s_max / 16.0).ceil(), (t_max / 16.0).ceil()];\n\n        for i in 0..2 {\n            face.texture_mins[i] = b_mins[i] as i16 * 16;\n            face.extents[i] = (b_maxs[i] - b_mins[i]) as i16 * 16;\n\n            if !texinfo.special && face.extents[i] > 2000 {\n                bail!(\n                    \"Bad face extents: face {}, texture {}: {:?}\",\n                    face_id,\n                    textures[texinfo.tex_id].name,\n                    face.extents\n                );\n            }\n        }\n    }\n\n    // see Mod_MakeHull0,\n    // https://github.com/id-Software/Quake/blob/master/WinQuake/gl_model.c#L1001-L1031\n    //\n    // This essentially duplicates the render nodes into a tree of collision nodes.\n    let mut render_as_collision_nodes = Vec::with_capacity(render_nodes.len());\n    for i in 0..render_nodes.len() {\n        render_as_collision_nodes.push(BspCollisionNode {\n            plane_id: render_nodes[i].plane_id,\n            children: [\n                match render_nodes[i].children[0] {\n                    BspRenderNodeChild::Node(n) => BspCollisionNodeChild::Node(n),\n                    BspRenderNodeChild::Leaf(l) => {\n                        BspCollisionNodeChild::Contents(leaves[l].contents)\n                    }\n                },\n                match render_nodes[i].children[1] {\n                    BspRenderNodeChild::Node(n) => BspCollisionNodeChild::Node(n),\n                    BspRenderNodeChild::Leaf(l) => {\n                        BspCollisionNodeChild::Contents(leaves[l].contents)\n                    }\n                },\n            ],\n        })\n    }\n    let render_as_collision_nodes_rc = Rc::new(render_as_collision_nodes.into_boxed_slice());\n\n    let hull_0 = BspCollisionHull {\n        planes: planes_rc.clone(),\n        nodes: render_as_collision_nodes_rc.clone(),\n        node_id: 0,\n        node_count: render_as_collision_nodes_rc.len(),\n        mins: Vector3::new(0.0, 0.0, 0.0),\n        maxs: Vector3::new(0.0, 0.0, 0.0),\n    };\n\n    let bsp_data = Rc::new(BspData {\n        planes: planes_rc.clone(),\n        textures: textures.into_boxed_slice(),\n        vertices: vertices.into_boxed_slice(),\n        visibility: vis_data.into_boxed_slice(),\n        render_nodes: render_nodes.into_boxed_slice(),\n        texinfo: texinfo.into_boxed_slice(),\n        faces: faces.into_boxed_slice(),\n        lightmaps: lightmaps.into_boxed_slice(),\n        hulls: [hull_0, hull_1, hull_2],\n        leaves: leaves.into_boxed_slice(),\n        facelist: facelist.into_boxed_slice(),\n        edges: edges.into_boxed_slice(),\n        edgelist: edgelist.into_boxed_slice(),\n    });\n\n    reader.seek(SeekFrom::Start(model_section.offset))?;\n\n    let mut total_leaf_count = 0;\n    let mut brush_models = Vec::with_capacity(model_count);\n    for i in 0..model_count {\n        // pad the bounding box by one unit in all directions\n        let min = Vector3::from(read_f32_3(&mut reader)?) - Vector3::new(1.0, 1.0, 1.0);\n        let max = Vector3::from(read_f32_3(&mut reader)?) + Vector3::new(1.0, 1.0, 1.0);\n        let origin = read_f32_3(&mut reader)?.into();\n\n        debug!(\"model[{}].min = {:?}\", i, min);\n        debug!(\"model[{}].max = {:?}\", i, max);\n        debug!(\"model[{}].origin = {:?}\", i, max);\n\n        let mut collision_node_ids = [0; MAX_HULLS];\n        for i in 0..collision_node_ids.len() {\n            collision_node_ids[i] = match reader.read_i32::<LittleEndian>()? {\n                r if r < 0 => bail!(\"Invalid collision tree root node\"),\n                r => r as usize,\n            };\n        }\n\n        // throw away the last collision node ID -- BSP files make room for 4 collision hulls but\n        // only 3 are ever used.\n        reader.read_i32::<LittleEndian>()?;\n\n        debug!(\"model[{}].headnodes = {:?}\", i, collision_node_ids);\n\n        let leaf_id = total_leaf_count;\n        debug!(\"model[{}].leaf_id = {:?}\", i, leaf_id);\n\n        let leaf_count = match reader.read_i32::<LittleEndian>()? {\n            x if x < 0 => bail!(\"Invalid leaf count\"),\n            x => x as usize,\n        };\n\n        total_leaf_count += leaf_count;\n\n        debug!(\"model[{}].leaf_count = {:?}\", i, leaf_count);\n\n        let face_id = match reader.read_i32::<LittleEndian>()? {\n            x if x < 0 => bail!(\"Invalid face id\"),\n            x => x as usize,\n        };\n\n        let face_count = match reader.read_i32::<LittleEndian>()? {\n            x if x < 0 => bail!(\"Invalid face count\"),\n            x => x as usize,\n        };\n\n        let mut collision_node_counts = [0; MAX_HULLS];\n        for i in 0..collision_node_counts.len() {\n            collision_node_counts[i] = collision_node_count - collision_node_ids[i];\n        }\n\n        brush_models.push(BspModel {\n            bsp_data: bsp_data.clone(),\n            min,\n            max,\n            origin,\n            collision_node_ids,\n            collision_node_counts,\n            leaf_id,\n            leaf_count,\n            face_id,\n            face_count,\n        });\n    }\n\n    table.check_end_position(&mut reader, BspFileSectionId::Models)?;\n\n    let models = brush_models\n        .into_iter()\n        .enumerate()\n        .map(|(i, bmodel)| Model::from_brush_model(format!(\"*{}\", i), bmodel))\n        .collect();\n\n    Ok((models, ent_string))\n}\n\nfn read_i16_3<R>(reader: &mut R) -> Result<[i16; 3], std::io::Error>\nwhere\n    R: ReadBytesExt,\n{\n    let mut ar = [0i16; 3];\n    reader.read_i16_into::<LittleEndian>(&mut ar)?;\n    Ok(ar)\n}\n"
  },
  {
    "path": "src/common/bsp/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// TODO:\n// - Replace index fields with direct references where possible\n\n//! Quake BSP file and data structure handling.\n//!\n//! # Data Structure\n//!\n//! The binary space partitioning tree, or BSP, is the central data structure used by the Quake\n//! engine for collision detection and rendering level geometry. At its core, the BSP tree is a\n//! binary search tree with each node representing a subspace of the map. The tree is navigated\n//! using the planes stored in each node; each child represents one side of the plane.\n//!\n//! # File Format\n//!\n//! The BSP file header consists only of the file format version number, stored as an `i32`.\n//!\n//! This is followed by a series of \"lumps\" (as they are called in the Quake source code),\n//! which act as a directory into the BSP file data. There are 15 of these lumps, each\n//! consisting of a 32-bit offset (into the file data) and a 32-bit size (in bytes).\n//!\n//! ## Entities\n//! Lump 0 points to the level entity data, which is stored in a JSON-like dictionary\n//! format. Entities are anonymous; they do not have names, only attributes. They are stored\n//! as follows:\n//!\n//! ```text\n//! {\n//! \"attribute0\" \"value0\"\n//! \"attribute1\" \"value1\"\n//! \"attribute2\" \"value2\"\n//! }\n//! {\n//! \"attribute0\" \"value0\"\n//! \"attribute1\" \"value1\"\n//! \"attribute2\" \"value2\"\n//! }\n//! ```\n//!\n//! The newline character is `0x0A` (line feed). The entity data is stored as a null-terminated\n//! string (it ends when byte `0x00` is reached).\n//!\n//! ## Planes\n//!\n//! Lump 1 points to the planes used to partition the map, stored in point-normal form as 4 IEEE 754\n//! single-precision floats. The first 3 floats form the normal vector for the plane, and the last\n//! float specifies the distance from the map origin along the line defined by the normal vector.\n//!\n//! ## Textures\n//!\n//! The textures are preceded by a 32-bit integer count and a list of 32-bit integer offsets. The\n//! offsets are given in bytes from the beginning of the texture section (the offset given by the\n//! texture lump at the start of the file).\n//!\n//! The textures themselves consist of a 16-byte name field, a 32-bit integer width, a 32-bit\n//! integer height, and 4 32-bit mipmap offsets. These offsets are given in bytes from the beginning\n//! of the texture. Each mipmap has its dimensions halved (i.e. its area quartered) from the\n//! previous mipmap: the first is full size, the second 1/4, the third 1/16, and the last 1/64. Each\n//! byte represents one pixel and contains an index into `gfx/palette.lmp`.\n//!\n//! ### Texture sequencing\n//!\n//! Animated textures are stored as individual frames with no guarantee of being in the correct\n//! order. This means that animated textures must be sequenced when the map is loaded. Frames of\n//! animated textures have names beginning with `U+002B PLUS SIGN` (`+`).\n//!\n//! Each texture can have two animations of up to MAX_TEXTURE_FRAMES frames each. The character\n//! following the plus sign determines whether the frame belongs to the first or second animation.\n//!\n//! If it is between `U+0030 DIGIT ZERO` (`0`) and `U+0039 DIGIT NINE` (`9`), then the character\n//! represents that texture's frame index in the first animation sequence.\n//!\n//! If it is between `U+0041 LATIN CAPITAL LETTER A` (`A`) and `U+004A LATIN CAPITAL LETTER J`, or\n//! between `U+0061 LATIN SMALL LETTER A` (`a`) and `U+006A LATIN SMALL LETTER J`, then the\n//! character represents that texture's frame index in the second animation sequence as that\n//! letter's position in the English alphabet (that is, `A`/`a` correspond to 0 and `J`/`j`\n//! correspond to 9).\n//!\n//! ## Vertex positions\n//!\n//! The vertex positions are stored as 3-component vectors of `float`. The Quake coordinate system\n//! defines X as the longitudinal axis, Y as the lateral axis, and Z as the vertical axis.\n//!\n//! # Visibility lists\n//!\n//! The visibility lists are simply stored as a series of run-length encoded bit strings. The total\n//! size of the visibility data is given by the lump size.\n//!\n//! ## Nodes\n//!\n//! Nodes are stored with a 32-bit integer plane ID denoting which plane splits the node. This is\n//! followed by two 16-bit integers which point to the children in front and back of the plane. If\n//! the high bit is set, the ID points to a leaf node; if not, it points to another internal node.\n//!\n//! After the node IDs are a 16-bit integer face ID, which denotes the index of the first face in\n//! the face list that belongs to this node, and a 16-bit integer face count, which denotes the\n//! number of faces to draw starting with the face ID.\n//!\n//! ## Edges\n//!\n//! The edges are stored as a pair of 16-bit integer vertex IDs.\n\nmod load;\n\nuse std::{collections::HashSet, error::Error, fmt, iter::Iterator, rc::Rc};\n\nuse crate::common::math::{Hyperplane, HyperplaneSide, LinePlaneIntersect};\n\n// TODO: Either Trace should be moved into common or the functions requiring it should be moved into server\nuse crate::server::world::{Trace, TraceEnd, TraceStart};\n\nuse cgmath::Vector3;\nuse chrono::Duration;\n\npub use self::load::{load, BspFileError};\n\n// this is 4 in the original source, but the 4th hull is never used.\nconst MAX_HULLS: usize = 3;\n\npub const MAX_LIGHTMAPS: usize = 64;\npub const MAX_LIGHTSTYLES: usize = 4;\npub const MAX_SOUNDS: usize = 4;\npub const MIPLEVELS: usize = 4;\nconst DIST_EPSILON: f32 = 0.03125;\n\npub fn frame_duration() -> Duration {\n    Duration::milliseconds(200)\n}\n\n#[derive(Debug)]\npub enum BspError {\n    Io(::std::io::Error),\n    Other(String),\n}\n\nimpl BspError {\n    fn with_msg<S>(msg: S) -> Self\n    where\n        S: AsRef<str>,\n    {\n        BspError::Other(msg.as_ref().to_owned())\n    }\n}\n\nimpl fmt::Display for BspError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match *self {\n            BspError::Io(ref err) => err.fmt(f),\n            BspError::Other(ref msg) => write!(f, \"{}\", msg),\n        }\n    }\n}\n\nimpl Error for BspError {\n    fn description(&self) -> &str {\n        match *self {\n            BspError::Io(ref err) => err.description(),\n            BspError::Other(ref msg) => &msg,\n        }\n    }\n}\n\nimpl From<::std::io::Error> for BspError {\n    fn from(error: ::std::io::Error) -> Self {\n        BspError::Io(error)\n    }\n}\n\n#[derive(Copy, Clone, Debug, FromPrimitive)]\npub enum BspTextureMipmap {\n    Full = 0,\n    Half = 1,\n    Quarter = 2,\n    Eighth = 3,\n}\n\n#[derive(Debug)]\npub struct BspTextureFrame {\n    mipmaps: [Vec<u8>; MIPLEVELS],\n}\n\nimpl BspTextureFrame {\n    pub fn mipmap(&self, level: BspTextureMipmap) -> &[u8] {\n        &self.mipmaps[level as usize]\n    }\n}\n\n#[derive(Debug)]\npub enum BspTextureKind {\n    Static(BspTextureFrame),\n    Animated {\n        primary: Vec<BspTextureFrame>,\n        alternate: Option<Vec<BspTextureFrame>>,\n    },\n}\n\n#[derive(Debug)]\npub struct BspTexture {\n    name: String,\n    width: u32,\n    height: u32,\n    kind: BspTextureKind,\n}\n\nimpl BspTexture {\n    /// Returns the name of the texture.\n    pub fn name(&self) -> &str {\n        self.name.as_ref()\n    }\n\n    pub fn width(&self) -> u32 {\n        self.width\n    }\n\n    pub fn height(&self) -> u32 {\n        self.height\n    }\n\n    /// Returns a tuple containing the width and height of the texture.\n    pub fn dimensions(&self) -> (u32, u32) {\n        (self.width, self.height)\n    }\n\n    /// Returns this texture's animation data, if any.\n    pub fn kind(&self) -> &BspTextureKind {\n        &self.kind\n    }\n}\n\n#[derive(Debug)]\npub enum BspRenderNodeChild {\n    Node(usize),\n    Leaf(usize),\n}\n\n#[derive(Debug)]\npub struct BspRenderNode {\n    pub plane_id: usize,\n    pub children: [BspRenderNodeChild; 2],\n    pub min: [i16; 3],\n    pub max: [i16; 3],\n    pub face_id: usize,\n    pub face_count: usize,\n}\n\n#[derive(Debug)]\npub struct BspTexInfo {\n    pub s_vector: Vector3<f32>,\n    pub s_offset: f32,\n    pub t_vector: Vector3<f32>,\n    pub t_offset: f32,\n    pub tex_id: usize,\n    pub special: bool,\n}\n\n#[derive(Copy, Clone, Debug)]\npub enum BspFaceSide {\n    Front,\n    Back,\n}\n\n#[derive(Debug)]\npub struct BspFace {\n    pub plane_id: usize,\n    pub side: BspFaceSide,\n    pub edge_id: usize,\n    pub edge_count: usize,\n    pub texinfo_id: usize,\n    pub light_styles: [u8; MAX_LIGHTSTYLES],\n    pub lightmap_id: Option<usize>,\n\n    pub texture_mins: [i16; 2],\n    pub extents: [i16; 2],\n}\n\n/// The contents of a leaf in the BSP tree, specifying how it should look and behave.\n#[derive(Copy, Clone, Debug, Eq, FromPrimitive, PartialEq)]\npub enum BspLeafContents {\n    /// The leaf has nothing in it. Vision is unobstructed and movement is unimpeded.\n    Empty = 1,\n\n    /// The leaf is solid. Physics objects will collide with its surface and may not move inside it.\n    Solid = 2,\n\n    /// The leaf is full of water. Vision is warped to simulate refraction and movement is done by\n    /// swimming instead of walking.\n    Water = 3,\n\n    /// The leaf is full of acidic slime. Vision is tinted green, movement is done by swimming and\n    /// entities take periodic minor damage.\n    Slime = 4,\n\n    /// The leaf is full of lava. Vision is tinted red, movement is done by swimming and entities\n    /// take periodic severe damage.\n    Lava = 5,\n\n    // This doesn't appear to ever be used\n    // Sky = 6,\n\n    // This is removed during map compilation\n    // Origin = 7,\n\n    // This is converted to `BspLeafContents::Solid`\n    // Collide = 8,\n    /// Same as `BspLeafContents::Water`, but the player is constantly pushed in the positive\n    /// x-direction (east).\n    Current0 = 9,\n\n    /// Same as `BspLeafContents::Water`, but the player is constantly pushed in the positive\n    /// y-direction (north).\n    Current90 = 10,\n\n    /// Same as `BspLeafContents::Water`, but the player is constantly pushed in the negative\n    /// x-direction (west).\n    Current180 = 11,\n\n    /// Same as `BspLeafContents::Water`, but the player is constantly pushed in the negative\n    /// y-direction (south).\n    Current270 = 12,\n\n    /// Same as `BspLeafContents::Water`, but the player is constantly pushed in the positive\n    /// z-direction (up).\n    CurrentUp = 13,\n\n    /// Same as `BspLeafContents::Water`, but the player is constantly pushed in the negative\n    /// z-direction (down).\n    CurrentDown = 14,\n}\n\n#[derive(Debug)]\npub enum BspCollisionNodeChild {\n    Node(usize),\n    Contents(BspLeafContents),\n}\n\n#[derive(Debug)]\npub struct BspCollisionNode {\n    plane_id: usize,\n    children: [BspCollisionNodeChild; 2],\n}\n\n#[derive(Debug)]\npub struct BspCollisionHull {\n    planes: Rc<Box<[Hyperplane]>>,\n    nodes: Rc<Box<[BspCollisionNode]>>,\n    node_id: usize,\n    node_count: usize,\n    mins: Vector3<f32>,\n    maxs: Vector3<f32>,\n}\n\nimpl BspCollisionHull {\n    // TODO: see if we can't make this a little less baffling\n    /// Constructs a collision hull with the given minimum and maximum bounds.\n    ///\n    /// This generates six planes which intersect to form a rectangular prism. The interior of the\n    /// prism is `BspLeafContents::Solid`; the exterior is `BspLeafContents::Empty`.\n    pub fn for_bounds(\n        mins: Vector3<f32>,\n        maxs: Vector3<f32>,\n    ) -> Result<BspCollisionHull, BspError> {\n        debug!(\n            \"Generating collision hull for min = {:?} max = {:?}\",\n            mins, maxs\n        );\n\n        if mins.x >= maxs.x || mins.y >= maxs.y || mins.z >= maxs.z {\n            return Err(BspError::with_msg(\"min bound exceeds max bound\"));\n        }\n\n        let mut nodes = Vec::new();\n        let mut planes = Vec::new();\n\n        // front plane (positive x)\n        planes.push(Hyperplane::axis_x(maxs.x));\n        nodes.push(BspCollisionNode {\n            plane_id: 0,\n            children: [\n                BspCollisionNodeChild::Contents(BspLeafContents::Empty),\n                BspCollisionNodeChild::Node(1),\n            ],\n        });\n\n        // back plane (negative x)\n        planes.push(Hyperplane::axis_x(mins.x));\n        nodes.push(BspCollisionNode {\n            plane_id: 1,\n            children: [\n                BspCollisionNodeChild::Node(2),\n                BspCollisionNodeChild::Contents(BspLeafContents::Empty),\n            ],\n        });\n\n        // left plane (positive y)\n        planes.push(Hyperplane::axis_y(maxs.y));\n        nodes.push(BspCollisionNode {\n            plane_id: 2,\n            children: [\n                BspCollisionNodeChild::Contents(BspLeafContents::Empty),\n                BspCollisionNodeChild::Node(3),\n            ],\n        });\n\n        // right plane (negative y)\n        planes.push(Hyperplane::axis_y(mins.y));\n        nodes.push(BspCollisionNode {\n            plane_id: 3,\n            children: [\n                BspCollisionNodeChild::Node(4),\n                BspCollisionNodeChild::Contents(BspLeafContents::Empty),\n            ],\n        });\n\n        // top plane (positive z)\n        planes.push(Hyperplane::axis_z(maxs.z));\n        nodes.push(BspCollisionNode {\n            plane_id: 4,\n            children: [\n                BspCollisionNodeChild::Contents(BspLeafContents::Empty),\n                BspCollisionNodeChild::Node(5),\n            ],\n        });\n\n        // bottom plane (negative z)\n        planes.push(Hyperplane::axis_z(mins.z));\n        nodes.push(BspCollisionNode {\n            plane_id: 5,\n            children: [\n                BspCollisionNodeChild::Contents(BspLeafContents::Solid),\n                BspCollisionNodeChild::Contents(BspLeafContents::Empty),\n            ],\n        });\n\n        Ok(BspCollisionHull {\n            planes: Rc::new(planes.into_boxed_slice()),\n            nodes: Rc::new(nodes.into_boxed_slice()),\n            node_id: 0,\n            node_count: 6,\n            mins,\n            maxs,\n        })\n    }\n\n    pub fn min(&self) -> Vector3<f32> {\n        self.mins\n    }\n\n    pub fn max(&self) -> Vector3<f32> {\n        self.maxs\n    }\n\n    /// Returns the leaf contents at the given point in this hull.\n    pub fn contents_at_point(&self, point: Vector3<f32>) -> Result<BspLeafContents, BspError> {\n        self.contents_at_point_node(self.node_id, point)\n    }\n\n    fn contents_at_point_node(\n        &self,\n        node: usize,\n        point: Vector3<f32>,\n    ) -> Result<BspLeafContents, BspError> {\n        let mut current_node = &self.nodes[node];\n\n        loop {\n            let ref plane = self.planes[current_node.plane_id];\n\n            match current_node.children[plane.point_side(point) as usize] {\n                BspCollisionNodeChild::Contents(c) => return Ok(c),\n                BspCollisionNodeChild::Node(n) => current_node = &self.nodes[n],\n            }\n        }\n    }\n\n    pub fn trace(&self, start: Vector3<f32>, end: Vector3<f32>) -> Result<Trace, BspError> {\n        self.recursive_trace(self.node_id, start, end)\n    }\n\n    fn recursive_trace(\n        &self,\n        node: usize,\n        start: Vector3<f32>,\n        end: Vector3<f32>,\n    ) -> Result<Trace, BspError> {\n        debug!(\"start={:?} end={:?}\", start, end);\n        let ref node = self.nodes[node];\n        let ref plane = self.planes[node.plane_id];\n\n        match plane.line_segment_intersection(start, end) {\n            // start -> end falls entirely on one side of the plane\n            LinePlaneIntersect::NoIntersection(side) => {\n                debug!(\"No intersection\");\n                match node.children[side as usize] {\n                    // this is an internal node, keep searching for a leaf\n                    BspCollisionNodeChild::Node(n) => {\n                        debug!(\"Descending to {:?} node with ID {}\", side, n);\n                        self.recursive_trace(n, start, end)\n                    }\n\n                    // start -> end falls entirely inside a leaf\n                    BspCollisionNodeChild::Contents(c) => {\n                        debug!(\"Found leaf with contents {:?}\", c);\n                        Ok(Trace::new(\n                            TraceStart::new(start, 0.0),\n                            TraceEnd::terminal(end),\n                            c,\n                        ))\n                    }\n                }\n            }\n\n            // start -> end crosses the plane at one point\n            LinePlaneIntersect::PointIntersection(point_intersect) => {\n                let near_side = plane.point_side(start);\n                let far_side = plane.point_side(end);\n                let mid = point_intersect.point();\n                let ratio = point_intersect.ratio();\n                debug!(\"Intersection at {:?} (ratio={})\", mid, ratio);\n\n                // calculate the near subtrace\n                let near = match node.children[near_side as usize] {\n                    BspCollisionNodeChild::Node(near_n) => {\n                        debug!(\n                            \"Descending to near ({:?}) node with ID {}\",\n                            near_side, near_n\n                        );\n                        self.recursive_trace(near_n, start, mid)?\n                    }\n                    BspCollisionNodeChild::Contents(near_c) => {\n                        debug!(\"Found near leaf with contents {:?}\", near_c);\n                        Trace::new(\n                            TraceStart::new(start, 0.0),\n                            TraceEnd::boundary(\n                                mid,\n                                ratio,\n                                match near_side {\n                                    HyperplaneSide::Positive => plane.to_owned(),\n                                    HyperplaneSide::Negative => -plane.to_owned(),\n                                },\n                            ),\n                            near_c,\n                        )\n                    }\n                };\n\n                // check for an early collision\n                if near.is_terminal() || near.end_point() != point_intersect.point() {\n                    return Ok(near);\n                }\n\n                // if we haven't collided yet, calculate the far subtrace\n                let far = match node.children[far_side as usize] {\n                    BspCollisionNodeChild::Node(far_n) => {\n                        debug!(\"Descending to far ({:?}) node with ID {}\", far_side, far_n);\n                        self.recursive_trace(far_n, mid, end)?\n                    }\n                    BspCollisionNodeChild::Contents(far_c) => {\n                        debug!(\"Found far leaf with contents {:?}\", far_c);\n                        Trace::new(TraceStart::new(mid, ratio), TraceEnd::terminal(end), far_c)\n                    }\n                };\n\n                // check for collision and join traces accordingly\n                Ok(near.join(far))\n            }\n        }\n    }\n\n    pub fn gen_dot_graph(&self) -> String {\n        let mut dot = String::new();\n        dot += \"digraph hull {\\n\";\n        dot += \"    rankdir=LR\\n\";\n\n        let mut rank_lists = Vec::new();\n        let mut leaf_names = Vec::new();\n\n        dot += &self.gen_dot_graph_recursive(0, &mut rank_lists, &mut leaf_names, self.node_id);\n\n        for rank in rank_lists {\n            dot += \"    {rank=same;\";\n            for node_id in rank {\n                dot += &format!(\"n{},\", node_id);\n            }\n            // discard trailing comma\n            dot.pop().unwrap();\n            dot += \"}\\n\"\n        }\n\n        dot += \"    {rank=same;\";\n        for leaf in leaf_names {\n            dot += &format!(\"{},\", leaf);\n        }\n        // discard trailing comma\n        dot.pop().unwrap();\n        dot.pop().unwrap();\n        dot += \"}\\n\";\n\n        dot += \"}\";\n\n        dot\n    }\n\n    fn gen_dot_graph_recursive(\n        &self,\n        rank: usize,\n        rank_lists: &mut Vec<HashSet<usize>>,\n        leaf_names: &mut Vec<String>,\n        node_id: usize,\n    ) -> String {\n        let mut result = String::new();\n\n        if rank >= rank_lists.len() {\n            rank_lists.push(HashSet::new());\n        }\n\n        rank_lists[rank].insert(node_id);\n\n        for child in self.nodes[node_id].children.iter() {\n            match child {\n                &BspCollisionNodeChild::Node(n) => {\n                    result += &format!(\"    n{} -> n{}\\n\", node_id, n);\n                    result += &self.gen_dot_graph_recursive(rank + 1, rank_lists, leaf_names, n);\n                }\n                &BspCollisionNodeChild::Contents(_) => {\n                    let leaf_count = leaf_names.len();\n                    let leaf_name = format!(\"l{}\", leaf_count);\n                    result += &format!(\"    n{} -> {}\\n\", node_id, leaf_name);\n                    leaf_names.push(leaf_name);\n                }\n            }\n        }\n\n        result\n    }\n}\n\n#[derive(Debug)]\npub struct BspLeaf {\n    pub contents: BspLeafContents,\n    pub vis_offset: Option<usize>,\n    pub min: [i16; 3],\n    pub max: [i16; 3],\n    pub facelist_id: usize,\n    pub facelist_count: usize,\n    pub sounds: [u8; MAX_SOUNDS],\n}\n\n#[derive(Debug)]\npub struct BspEdge {\n    pub vertex_ids: [u16; 2],\n}\n\n#[derive(Copy, Clone, Debug)]\npub enum BspEdgeDirection {\n    Forward = 0,\n    Backward = 1,\n}\n\n#[derive(Debug)]\npub struct BspEdgeIndex {\n    pub direction: BspEdgeDirection,\n    pub index: usize,\n}\n\n#[derive(Debug)]\npub struct BspLightmap<'a> {\n    width: u32,\n    height: u32,\n    data: &'a [u8],\n}\n\nimpl<'a> BspLightmap<'a> {\n    pub fn width(&self) -> u32 {\n        self.width\n    }\n\n    pub fn height(&self) -> u32 {\n        self.height\n    }\n\n    pub fn data(&self) -> &[u8] {\n        self.data\n    }\n}\n\n#[derive(Debug)]\npub struct BspData {\n    pub(crate) planes: Rc<Box<[Hyperplane]>>,\n    pub(crate) textures: Box<[BspTexture]>,\n    pub(crate) vertices: Box<[Vector3<f32>]>,\n    pub(crate) visibility: Box<[u8]>,\n    pub(crate) render_nodes: Box<[BspRenderNode]>,\n    pub(crate) texinfo: Box<[BspTexInfo]>,\n    pub(crate) faces: Box<[BspFace]>,\n    pub(crate) lightmaps: Box<[u8]>,\n    pub(crate) leaves: Box<[BspLeaf]>,\n    pub(crate) facelist: Box<[usize]>,\n    pub(crate) edges: Box<[BspEdge]>,\n    pub(crate) edgelist: Box<[BspEdgeIndex]>,\n    pub(crate) hulls: [BspCollisionHull; MAX_HULLS],\n}\n\nimpl BspData {\n    pub fn planes(&self) -> &[Hyperplane] {\n        &self.planes\n    }\n\n    pub fn textures(&self) -> &[BspTexture] {\n        &self.textures\n    }\n\n    pub fn vertices(&self) -> &[Vector3<f32>] {\n        &self.vertices\n    }\n\n    pub fn render_nodes(&self) -> &[BspRenderNode] {\n        &self.render_nodes\n    }\n\n    pub fn texinfo(&self) -> &[BspTexInfo] {\n        &self.texinfo\n    }\n\n    pub fn face(&self, face_id: usize) -> &BspFace {\n        &self.faces[face_id]\n    }\n\n    pub fn face_iter_vertices(&self, face_id: usize) -> impl Iterator<Item = Vector3<f32>> + '_ {\n        let face = &self.faces[face_id];\n        self.edgelist[face.edge_id..face.edge_id + face.edge_count]\n            .iter()\n            .map(move |id| {\n                self.vertices[self.edges[id.index].vertex_ids[id.direction as usize] as usize]\n            })\n    }\n\n    pub fn face_texinfo(&self, face_id: usize) -> &BspTexInfo {\n        &self.texinfo[self.faces[face_id].texinfo_id]\n    }\n\n    pub fn face_lightmaps(&self, face_id: usize) -> Vec<BspLightmap> {\n        let face = &self.faces[face_id];\n        match face.lightmap_id {\n            Some(lightmap_id) => {\n                let lightmap_w = face.extents[0] as u32 / 16 + 1;\n                let lightmap_h = face.extents[1] as u32 / 16 + 1;\n                let lightmap_size = (lightmap_w * lightmap_h) as usize;\n\n                face.light_styles\n                    .iter()\n                    .take_while(|style| **style != 255)\n                    .enumerate()\n                    .map(|(i, _)| {\n                        let start = lightmap_id + lightmap_size * i as usize;\n                        let end = start + lightmap_size;\n                        BspLightmap {\n                            width: lightmap_w,\n                            height: lightmap_h,\n                            data: &self.lightmaps[start..end],\n                        }\n                    })\n                    .collect()\n            }\n            None => Vec::new(),\n        }\n    }\n\n    pub fn faces(&self) -> &[BspFace] {\n        &self.faces\n    }\n\n    pub fn lightmaps(&self) -> &[u8] {\n        &self.lightmaps\n    }\n\n    pub fn leaves(&self) -> &[BspLeaf] {\n        &self.leaves\n    }\n\n    pub fn facelist(&self) -> &[usize] {\n        &self.facelist\n    }\n\n    pub fn edges(&self) -> &[BspEdge] {\n        &self.edges\n    }\n\n    pub fn edgelist(&self) -> &[BspEdgeIndex] {\n        &self.edgelist\n    }\n\n    pub fn hulls(&self) -> &[BspCollisionHull] {\n        &self.hulls\n    }\n\n    /// Locates the leaf containing the given position vector and returns its index.\n    pub fn find_leaf<V>(&self, pos: V) -> usize\n    where\n        V: Into<Vector3<f32>>,\n    {\n        let pos_vec = pos.into();\n\n        let mut node = &self.render_nodes[0];\n        loop {\n            let plane = &self.planes[node.plane_id];\n\n            match node.children[plane.point_side(pos_vec) as usize] {\n                BspRenderNodeChild::Node(node_id) => {\n                    node = &self.render_nodes[node_id];\n                }\n                BspRenderNodeChild::Leaf(leaf_id) => return leaf_id,\n            }\n        }\n    }\n\n    pub fn get_pvs(&self, leaf_id: usize, leaf_count: usize) -> Vec<usize> {\n        // leaf 0 is outside the map, everything is visible\n        if leaf_id == 0 {\n            return Vec::new();\n        }\n\n        match self.leaves[leaf_id].vis_offset {\n            Some(o) => {\n                let mut visleaf = 1;\n                let mut visleaf_list = Vec::new();\n                let mut it = (&self.visibility[o..]).iter();\n\n                while visleaf < leaf_count {\n                    let byte = it.next().unwrap();\n                    match *byte {\n                        // a zero byte signals the start of an RLE sequence\n                        0 => visleaf += 8 * *it.next().unwrap() as usize,\n\n                        bits => {\n                            for shift in 0..8 {\n                                if bits & 1 << shift != 0 {\n                                    visleaf_list.push(visleaf);\n                                }\n\n                                visleaf += 1;\n                            }\n                        }\n                    }\n                }\n\n                visleaf_list\n            }\n\n            None => Vec::new(),\n        }\n    }\n\n    pub fn gen_dot_graph(&self) -> String {\n        let mut dot = String::new();\n        dot += \"digraph render {\\n\";\n        dot += \"    rankdir=LR\\n\";\n\n        let mut rank_lists = Vec::new();\n        let mut leaf_names = Vec::new();\n\n        dot += &self.gen_dot_graph_recursive(0, &mut rank_lists, &mut leaf_names, 0);\n\n        for rank in rank_lists {\n            dot += \"    {rank=same;\";\n            for node_id in rank {\n                dot += &format!(\"n{},\", node_id);\n            }\n            // discard trailing comma\n            dot.pop().unwrap();\n            dot += \"}\\n\"\n        }\n\n        dot += \"    {rank=same;\";\n        for leaf_id in 1..self.leaves().len() {\n            dot += &format!(\"l{},\", leaf_id);\n        }\n        // discard trailing comma\n        dot.pop().unwrap();\n        dot += \"}\\n\";\n\n        dot += \"}\";\n\n        dot\n    }\n\n    fn gen_dot_graph_recursive(\n        &self,\n        rank: usize,\n        rank_lists: &mut Vec<HashSet<usize>>,\n        leaf_names: &mut Vec<String>,\n        node_id: usize,\n    ) -> String {\n        let mut result = String::new();\n\n        if rank >= rank_lists.len() {\n            rank_lists.push(HashSet::new());\n        }\n\n        rank_lists[rank].insert(node_id);\n\n        for child in self.render_nodes[node_id].children.iter() {\n            match *child {\n                BspRenderNodeChild::Node(n) => {\n                    result += &format!(\"    n{} -> n{}\\n\", node_id, n);\n                    result += &self.gen_dot_graph_recursive(rank + 1, rank_lists, leaf_names, n);\n                }\n                BspRenderNodeChild::Leaf(leaf_id) => match leaf_id {\n                    0 => {\n                        result += &format!(\n                            \"    l0_{0} [shape=point label=\\\"\\\"]\\n    n{0} -> l0_{0}\\n\",\n                            node_id\n                        );\n                    }\n                    _ => result += &format!(\"    n{} -> l{}\\n\", node_id, leaf_id),\n                },\n            }\n        }\n\n        result\n    }\n}\n\n#[derive(Debug)]\npub struct BspModel {\n    pub bsp_data: Rc<BspData>,\n    pub min: Vector3<f32>,\n    pub max: Vector3<f32>,\n    pub origin: Vector3<f32>,\n    pub collision_node_ids: [usize; MAX_HULLS],\n    pub collision_node_counts: [usize; MAX_HULLS],\n    pub leaf_id: usize,\n    pub leaf_count: usize,\n    pub face_id: usize,\n    pub face_count: usize,\n}\n\nimpl BspModel {\n    pub fn bsp_data(&self) -> Rc<BspData> {\n        self.bsp_data.clone()\n    }\n\n    /// Returns the minimum extent of this BSP model.\n    pub fn min(&self) -> Vector3<f32> {\n        self.min\n    }\n\n    /// Returns the maximum extent of this BSP model.\n    pub fn max(&self) -> Vector3<f32> {\n        self.max\n    }\n\n    /// Returns the size of this BSP model.\n    pub fn size(&self) -> Vector3<f32> {\n        self.max - self.min\n    }\n\n    /// Returns the origin of this BSP model.\n    pub fn origin(&self) -> Vector3<f32> {\n        self.origin\n    }\n\n    pub fn iter_leaves(&self) -> impl Iterator<Item = &BspLeaf> {\n        // add 1 to leaf_count because...??? TODO: figure out if this is documented anywhere\n        self.bsp_data.leaves[self.leaf_id..self.leaf_id + self.leaf_count + 1].iter()\n    }\n\n    pub fn iter_faces(&self) -> impl Iterator<Item = &BspFace> {\n        self.bsp_data.facelist[self.face_id..self.face_id + self.face_count]\n            .iter()\n            .map(move |face_id| &self.bsp_data.faces[*face_id])\n    }\n\n    pub fn face_list(&self) -> &[usize] {\n        &self.bsp_data.facelist[self.face_id..self.face_id + self.face_count]\n    }\n\n    pub fn hull(&self, index: usize) -> Result<BspCollisionHull, BspError> {\n        if index > MAX_HULLS {\n            return Err(BspError::with_msg(format!(\n                \"Invalid hull index ({})\",\n                index\n            )));\n        }\n\n        let main_hull = &self.bsp_data.hulls[index];\n        Ok(BspCollisionHull {\n            planes: main_hull.planes.clone(),\n            nodes: main_hull.nodes.clone(),\n            node_id: self.collision_node_ids[index],\n            node_count: self.collision_node_counts[index],\n            mins: main_hull.mins,\n            maxs: main_hull.maxs,\n        })\n    }\n}\n\nimpl BspData {}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use cgmath::Zero;\n\n    #[test]\n    fn test_hull_for_bounds() {\n        let hull =\n            BspCollisionHull::for_bounds(Vector3::zero(), Vector3::new(1.0, 1.0, 1.0)).unwrap();\n\n        let empty_points = vec![\n            // points strictly less than hull min should be empty\n            Vector3::new(-1.0, -1.0, -1.0),\n            // points strictly greater than hull max should be empty\n            Vector3::new(2.0, 2.0, 2.0),\n            // points in front of hull should be empty\n            Vector3::new(2.0, 0.5, 0.5),\n            // points behind hull should be empty\n            Vector3::new(-1.0, 0.5, 0.5),\n            // points left of hull should be empty\n            Vector3::new(0.5, 2.0, 0.5),\n            // points right of hull should be empty\n            Vector3::new(0.5, -1.0, 0.5),\n            // points above hull should be empty\n            Vector3::new(0.5, 0.5, 2.0),\n            // points below hull should be empty\n            Vector3::new(0.5, 0.5, -1.0),\n        ];\n\n        for point in empty_points {\n            assert_eq!(\n                hull.contents_at_point(point).unwrap(),\n                BspLeafContents::Empty\n            );\n        }\n\n        let solid_points = vec![\n            // center of the hull should be solid\n            Vector3::new(0.5, 0.5, 0.5),\n            // various interior corners should be solid\n            Vector3::new(0.01, 0.01, 0.01),\n            Vector3::new(0.99, 0.01, 0.01),\n            Vector3::new(0.01, 0.99, 0.01),\n            Vector3::new(0.01, 0.01, 0.99),\n            Vector3::new(0.99, 0.99, 0.01),\n            Vector3::new(0.99, 0.01, 0.99),\n            Vector3::new(0.01, 0.99, 0.99),\n            Vector3::new(0.99, 0.99, 0.99),\n        ];\n\n        for point in solid_points {\n            assert_eq!(\n                hull.contents_at_point(point).unwrap(),\n                BspLeafContents::Solid\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/common/console/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::{\n    cell::{Ref, RefCell},\n    collections::{HashMap, VecDeque},\n    fmt::Write,\n    iter::FromIterator,\n    rc::Rc,\n};\n\nuse crate::common::parse;\n\nuse chrono::{Duration, Utc};\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\npub enum ConsoleError {\n    #[error(\"{0}\")]\n    CmdError(String),\n    #[error(\"Could not parse cvar as a number: {name} = \\\"{value}\\\"\")]\n    CvarParseFailed { name: String, value: String },\n    #[error(\"A command named \\\"{0}\\\" already exists\")]\n    DuplicateCommand(String),\n    #[error(\"A cvar named \\\"{0}\\\" already exists\")]\n    DuplicateCvar(String),\n    #[error(\"No such command: {0}\")]\n    NoSuchCommand(String),\n    #[error(\"No such cvar: {0}\")]\n    NoSuchCvar(String),\n}\n\ntype Cmd = Box<dyn Fn(&[&str]) -> String>;\n\nfn insert_name<S>(names: &mut Vec<String>, name: S) -> Result<usize, usize>\nwhere\n    S: AsRef<str>,\n{\n    let name = name.as_ref();\n    match names.binary_search_by(|item| item.as_str().cmp(name)) {\n        Ok(i) => Err(i),\n        Err(i) => {\n            names.insert(i, name.to_owned());\n            Ok(i)\n        }\n    }\n}\n\n/// Stores console commands.\npub struct CmdRegistry {\n    cmds: HashMap<String, Cmd>,\n    names: Rc<RefCell<Vec<String>>>,\n}\n\nimpl CmdRegistry {\n    pub fn new(names: Rc<RefCell<Vec<String>>>) -> CmdRegistry {\n        CmdRegistry {\n            cmds: HashMap::new(),\n            names,\n        }\n    }\n\n    /// Registers a new command with the given name.\n    ///\n    /// Returns an error if a command with the specified name already exists.\n    pub fn insert<S>(&mut self, name: S, cmd: Cmd) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n\n        match self.cmds.get(name) {\n            Some(_) => Err(ConsoleError::DuplicateCommand(name.to_owned()))?,\n            None => {\n                if insert_name(&mut self.names.borrow_mut(), name).is_err() {\n                    return Err(ConsoleError::DuplicateCvar(name.into()));\n                }\n\n                self.cmds.insert(name.to_owned(), cmd);\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Registers a new command with the given name, or replaces one if the name is in use.\n    pub fn insert_or_replace<S>(&mut self, name: S, cmd: Cmd) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n\n        // If the name isn't registered as a command and it exists in the name\n        // table, it's a cvar.\n        if !self.cmds.contains_key(name) && insert_name(&mut self.names.borrow_mut(), name).is_err()\n        {\n            return Err(ConsoleError::DuplicateCvar(name.into()));\n        }\n\n        self.cmds.insert(name.into(), cmd);\n\n        Ok(())\n    }\n\n    /// Removes the command with the given name.\n    ///\n    /// Returns an error if there was no command with that name.\n    pub fn remove<S>(&mut self, name: S) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        if self.cmds.remove(name.as_ref()).is_none() {\n            return Err(ConsoleError::NoSuchCommand(name.as_ref().to_string()))?;\n        }\n\n        let mut names = self.names.borrow_mut();\n        match names.binary_search_by(|item| item.as_str().cmp(name.as_ref())) {\n            Ok(i) => drop(names.remove(i)),\n            Err(_) => unreachable!(\"name in map but not in list: {}\", name.as_ref()),\n        }\n\n        Ok(())\n    }\n\n    /// Executes a command.\n    ///\n    /// Returns an error if no command with the specified name exists.\n    pub fn exec<S>(&mut self, name: S, args: &[&str]) -> Result<String, ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        let cmd = self\n            .cmds\n            .get(name.as_ref())\n            .ok_or(ConsoleError::NoSuchCommand(name.as_ref().to_string()))?;\n\n        Ok(cmd(args))\n    }\n\n    pub fn contains<S>(&self, name: S) -> bool\n    where\n        S: AsRef<str>,\n    {\n        self.cmds.contains_key(name.as_ref())\n    }\n\n    pub fn names(&self) -> Rc<RefCell<Vec<String>>> {\n        self.names.clone()\n    }\n}\n\n/// A configuration variable.\n///\n/// Cvars are the primary method of configuring the game.\n#[derive(Debug)]\nstruct Cvar {\n    // Value of this variable\n    val: String,\n\n    // If true, this variable should be archived in vars.rc\n    archive: bool,\n\n    // If true:\n    // - If a server cvar, broadcast updates to clients\n    // - If a client cvar, update userinfo\n    notify: bool,\n\n    // The default value of this variable\n    default: String,\n}\n\n#[derive(Debug)]\npub struct CvarRegistry {\n    cvars: RefCell<HashMap<String, Cvar>>,\n    names: Rc<RefCell<Vec<String>>>,\n}\n\nimpl CvarRegistry {\n    /// Construct a new empty `CvarRegistry`.\n    pub fn new(names: Rc<RefCell<Vec<String>>>) -> CvarRegistry {\n        CvarRegistry {\n            cvars: RefCell::new(HashMap::new()),\n            names,\n        }\n    }\n\n    fn register_impl<S>(\n        &self,\n        name: S,\n        default: S,\n        archive: bool,\n        notify: bool,\n    ) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n        let default = default.as_ref();\n\n        let mut cvars = self.cvars.borrow_mut();\n        match cvars.get(name) {\n            Some(_) => Err(ConsoleError::DuplicateCvar(name.into()))?,\n            None => {\n                if insert_name(&mut self.names.borrow_mut(), name).is_err() {\n                    return Err(ConsoleError::DuplicateCommand(name.into()));\n                }\n\n                cvars.insert(\n                    name.to_owned(),\n                    Cvar {\n                        val: default.to_owned(),\n                        archive,\n                        notify,\n                        default: default.to_owned(),\n                    },\n                );\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Register a new `Cvar` with the given name.\n    pub fn register<S>(&self, name: S, default: S) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        self.register_impl(name, default, false, false)\n    }\n\n    /// Register a new archived `Cvar` with the given name.\n    ///\n    /// The value of this `Cvar` should be written to `vars.rc` whenever the game is closed or\n    /// `host_writeconfig` is issued.\n    pub fn register_archive<S>(&self, name: S, default: S) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        self.register_impl(name, default, true, false)\n    }\n\n    /// Register a new notify `Cvar` with the given name.\n    ///\n    /// When this `Cvar` is set:\n    /// - If the host is a server, broadcast that the variable has been changed to all clients.\n    /// - If the host is a client, update the clientinfo string.\n    pub fn register_notify<S>(&self, name: S, default: S) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        self.register_impl(name, default, false, true)\n    }\n\n    /// Register a new notify + archived `Cvar` with the given name.\n    ///\n    /// The value of this `Cvar` should be written to `vars.rc` whenever the game is closed or\n    /// `host_writeconfig` is issued.\n    ///\n    /// Additionally, when this `Cvar` is set:\n    /// - If the host is a server, broadcast that the variable has been changed to all clients.\n    /// - If the host is a client, update the clientinfo string.\n    pub fn register_archive_notify<S>(&mut self, name: S, default: S) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        self.register_impl(name, default, true, true)\n    }\n\n    pub fn get<S>(&self, name: S) -> Result<String, ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        Ok(self\n            .cvars\n            .borrow()\n            .get(name.as_ref())\n            .ok_or(ConsoleError::NoSuchCvar(name.as_ref().to_owned()))?\n            .val\n            .clone())\n    }\n\n    pub fn get_value<S>(&self, name: S) -> Result<f32, ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n        let mut cvars = self.cvars.borrow_mut();\n        let cvar = cvars\n            .get_mut(name)\n            .ok_or(ConsoleError::NoSuchCvar(name.to_owned()))?;\n\n        // try parsing as f32\n        let val_string = cvar.val.clone();\n        let val = match val_string.parse::<f32>() {\n            Ok(v) => Ok(v),\n            // if parse fails, reset to default value and try again\n            Err(_) => {\n                cvar.val = cvar.default.clone();\n                cvar.val.parse::<f32>()\n            }\n        }\n        .or(Err(ConsoleError::CvarParseFailed {\n            name: name.to_owned(),\n            value: val_string.clone(),\n        }))?;\n\n        Ok(val)\n    }\n\n    pub fn set<S>(&self, name: S, value: S) -> Result<(), ConsoleError>\n    where\n        S: AsRef<str>,\n    {\n        trace!(\"cvar assignment: {} {}\", name.as_ref(), value.as_ref());\n        let mut cvars = self.cvars.borrow_mut();\n        let mut cvar = cvars\n            .get_mut(name.as_ref())\n            .ok_or(ConsoleError::NoSuchCvar(name.as_ref().to_owned()))?;\n        cvar.val = value.as_ref().to_owned();\n        if cvar.notify {\n            // TODO: update userinfo/serverinfo\n            unimplemented!();\n        }\n\n        Ok(())\n    }\n\n    pub fn contains<S>(&self, name: S) -> bool\n    where\n        S: AsRef<str>,\n    {\n        self.cvars.borrow().contains_key(name.as_ref())\n    }\n}\n\n/// The line of text currently being edited in the console.\npub struct ConsoleInput {\n    text: Vec<char>,\n    curs: usize,\n}\n\nimpl ConsoleInput {\n    /// Constructs a new `ConsoleInput`.\n    ///\n    /// Initializes the text content to be empty and places the cursor at position 0.\n    pub fn new() -> ConsoleInput {\n        ConsoleInput {\n            text: Vec::new(),\n            curs: 0,\n        }\n    }\n\n    /// Returns the current content of the `ConsoleInput`.\n    pub fn get_text(&self) -> Vec<char> {\n        self.text.to_owned()\n    }\n\n    /// Sets the content of the `ConsoleInput` to `Text`.\n    ///\n    /// This also moves the cursor to the end of the line.\n    pub fn set_text(&mut self, text: &Vec<char>) {\n        self.text = text.clone();\n        self.curs = self.text.len();\n    }\n\n    /// Inserts the specified character at the position of the cursor.\n    ///\n    /// The cursor is moved one character to the right.\n    pub fn insert(&mut self, c: char) {\n        self.text.insert(self.curs, c);\n        self.cursor_right();\n    }\n\n    /// Moves the cursor to the right.\n    ///\n    /// If the cursor is at the end of the current text, no change is made.\n    pub fn cursor_right(&mut self) {\n        if self.curs < self.text.len() {\n            self.curs += 1;\n        }\n    }\n\n    /// Moves the cursor to the left.\n    ///\n    /// If the cursor is at the beginning of the current text, no change is made.\n    pub fn cursor_left(&mut self) {\n        if self.curs > 0 {\n            self.curs -= 1;\n        }\n    }\n\n    /// Deletes the character to the right of the cursor.\n    ///\n    /// If the cursor is at the end of the current text, no character is deleted.\n    pub fn delete(&mut self) {\n        if self.curs < self.text.len() {\n            self.text.remove(self.curs);\n        }\n    }\n\n    /// Deletes the character to the left of the cursor.\n    ///\n    /// If the cursor is at the beginning of the current text, no character is deleted.\n    pub fn backspace(&mut self) {\n        if self.curs > 0 {\n            self.text.remove(self.curs - 1);\n            self.curs -= 1;\n        }\n    }\n\n    /// Clears the contents of the `ConsoleInput`.\n    ///\n    /// Also moves the cursor to position 0.\n    pub fn clear(&mut self) {\n        self.text.clear();\n        self.curs = 0;\n    }\n}\n\npub struct History {\n    lines: VecDeque<Vec<char>>,\n    curs: usize,\n}\n\nimpl History {\n    pub fn new() -> History {\n        History {\n            lines: VecDeque::new(),\n            curs: 0,\n        }\n    }\n\n    pub fn add_line(&mut self, line: Vec<char>) {\n        self.lines.push_front(line);\n        self.curs = 0;\n    }\n\n    // TODO: handle case where history is empty\n    pub fn line_up(&mut self) -> Option<Vec<char>> {\n        if self.lines.len() == 0 || self.curs >= self.lines.len() {\n            None\n        } else {\n            self.curs += 1;\n            Some(self.lines[self.curs - 1].clone())\n        }\n    }\n\n    pub fn line_down(&mut self) -> Option<Vec<char>> {\n        if self.curs > 0 {\n            self.curs -= 1;\n        }\n\n        if self.curs > 0 {\n            Some(self.lines[self.curs - 1].clone())\n        } else {\n            Some(Vec::new())\n        }\n    }\n}\n\npub struct ConsoleOutput {\n    // A ring buffer of lines of text. Each line has an optional timestamp used\n    // to determine whether it should be displayed on screen. If the timestamp\n    // is `None`, the message will not be displayed.\n    //\n    // The timestamp is specified in seconds since the Unix epoch (so it is\n    // decoupled from client/server time).\n    lines: VecDeque<(Vec<char>, Option<i64>)>,\n}\n\nimpl ConsoleOutput {\n    pub fn new() -> ConsoleOutput {\n        ConsoleOutput {\n            lines: VecDeque::new(),\n        }\n    }\n\n    fn push<C>(&mut self, chars: C, timestamp: Option<i64>)\n    where\n        C: IntoIterator<Item = char>,\n    {\n        self.lines\n            .push_front((chars.into_iter().collect(), timestamp))\n        // TODO: set maximum capacity and pop_back when we reach it\n    }\n\n    pub fn lines(&self) -> impl Iterator<Item = &[char]> {\n        self.lines.iter().map(|(v, _)| v.as_slice())\n    }\n\n    /// Return an iterator over lines that have been printed in the last\n    /// `interval` of time.\n    ///\n    /// The iterator yields the oldest results first.\n    ///\n    /// `max_candidates` specifies the maximum number of lines to consider,\n    /// while `max_results` specifies the maximum number of lines that should\n    /// be returned.\n    pub fn recent_lines(\n        &self,\n        interval: Duration,\n        max_candidates: usize,\n        max_results: usize,\n    ) -> impl Iterator<Item = &[char]> {\n        let timestamp = (Utc::now() - interval).timestamp();\n        self.lines\n            .iter()\n            // search only the most recent `max_candidates` lines\n            .take(max_candidates)\n            // yield oldest to newest\n            .rev()\n            // eliminate non-timestamped lines and lines older than `timestamp`\n            .filter_map(move |(l, t)| if (*t)? > timestamp { Some(l) } else { None })\n            // return at most `max_results` lines\n            .take(max_results)\n            .map(Vec::as_slice)\n    }\n}\n\npub struct Console {\n    cmds: Rc<RefCell<CmdRegistry>>,\n    cvars: Rc<RefCell<CvarRegistry>>,\n    aliases: Rc<RefCell<HashMap<String, String>>>,\n\n    input: ConsoleInput,\n    hist: History,\n    buffer: RefCell<String>,\n\n    out_buffer: RefCell<Vec<char>>,\n    output: RefCell<ConsoleOutput>,\n}\n\nimpl Console {\n    pub fn new(cmds: Rc<RefCell<CmdRegistry>>, cvars: Rc<RefCell<CvarRegistry>>) -> Console {\n        let output = RefCell::new(ConsoleOutput::new());\n        cmds.borrow_mut()\n            .insert(\n                \"echo\",\n                Box::new(move |args| {\n                    let msg = match args.len() {\n                        0 => \"\",\n                        _ => args[0],\n                    };\n\n                    msg.to_owned()\n                }),\n            )\n            .unwrap();\n\n        let aliases: Rc<RefCell<HashMap<String, String>>> = Rc::new(RefCell::new(HashMap::new()));\n        let cmd_aliases = aliases.clone();\n        cmds.borrow_mut()\n            .insert(\n                \"alias\",\n                Box::new(move |args| {\n                    match args.len() {\n                        0 => {\n                            for (name, script) in cmd_aliases.borrow().iter() {\n                                println!(\"    {}: {}\", name, script);\n                            }\n                            println!(\"{} alias command(s)\", cmd_aliases.borrow().len());\n                        }\n\n                        2 => {\n                            let name = args[0].to_string();\n                            let script = args[1].to_string();\n                            let _ = cmd_aliases.borrow_mut().insert(name, script);\n                        }\n\n                        _ => (),\n                    }\n                    String::new()\n                }),\n            )\n            .unwrap();\n\n        let find_names = cmds.borrow().names();\n        cmds.borrow_mut()\n            .insert(\n                \"find\",\n                Box::new(move |args| match args.len() {\n                    1 => {\n                        let names = find_names.borrow_mut();\n\n                        // Find the index of the first item >= the target.\n                        let start = match names.binary_search_by(|item| item.as_str().cmp(&args[0]))\n                        {\n                            Ok(i) => i,\n                            Err(i) => i,\n                        };\n\n                        // Take every item starting with the target.\n                        let it = (&names[start..])\n                            .iter()\n                            .take_while(move |item| item.starts_with(&args[0]))\n                            .map(|s| s.as_str());\n\n                        let mut output = String::new();\n                        for name in it {\n                            write!(&mut output, \"{}\\n\", name).unwrap();\n                        }\n\n                        output\n                    }\n\n                    _ => \"usage: find <cvar or command>\".into(),\n                }),\n            )\n            .unwrap();\n\n        Console {\n            cmds,\n            cvars,\n            aliases: aliases.clone(),\n            input: ConsoleInput::new(),\n            hist: History::new(),\n            buffer: RefCell::new(String::new()),\n            out_buffer: RefCell::new(Vec::new()),\n            output,\n        }\n    }\n\n    // The timestamp is applied to any line flushed during this call.\n    fn print_impl<S>(&self, s: S, timestamp: Option<i64>)\n    where\n        S: AsRef<str>,\n    {\n        let mut buf = self.out_buffer.borrow_mut();\n        let mut it = s.as_ref().chars();\n\n        while let Some(c) = it.next() {\n            if c == '\\n' {\n                // Flush and clear the line buffer.\n                self.output\n                    .borrow_mut()\n                    .push(buf.iter().copied(), timestamp);\n                buf.clear();\n            } else {\n                buf.push(c);\n            }\n        }\n    }\n\n    pub fn print<S>(&self, s: S)\n    where\n        S: AsRef<str>,\n    {\n        self.print_impl(s, None);\n    }\n\n    pub fn print_alert<S>(&self, s: S)\n    where\n        S: AsRef<str>,\n    {\n        self.print_impl(s, Some(Utc::now().timestamp()));\n    }\n\n    pub fn println<S>(&self, s: S)\n    where\n        S: AsRef<str>,\n    {\n        self.print_impl(s, None);\n        self.print_impl(\"\\n\", None);\n    }\n\n    pub fn println_alert<S>(&self, s: S)\n    where\n        S: AsRef<str>,\n    {\n        let ts = Some(Utc::now().timestamp());\n        self.print_impl(s, ts);\n        self.print_impl(\"\\n\", ts);\n    }\n\n    pub fn send_char(&mut self, c: char) {\n        match c {\n            // ignore grave and escape keys\n            '`' | '\\x1b' => (),\n\n            '\\r' => {\n                // cap with a newline and push to the execution buffer\n                let mut entered = self.get_string();\n                entered.push('\\n');\n                self.buffer.borrow_mut().push_str(&entered);\n\n                // add the current input to the history\n                self.hist.add_line(self.input.get_text());\n\n                // echo the input to console output\n                let mut input_echo: Vec<char> = vec![']'];\n                input_echo.append(&mut self.input.get_text());\n                self.output.borrow_mut().push(input_echo, None);\n\n                // clear the input line\n                self.input.clear();\n            }\n\n            '\\x08' => self.input.backspace(),\n            '\\x7f' => self.input.delete(),\n\n            '\\t' => warn!(\"Tab completion not implemented\"), // TODO: tab completion\n\n            // TODO: we should probably restrict what characters are allowed\n            c => self.input.insert(c),\n        }\n    }\n\n    pub fn cursor(&self) -> usize {\n        self.input.curs\n    }\n\n    pub fn cursor_right(&mut self) {\n        self.input.cursor_right()\n    }\n\n    pub fn cursor_left(&mut self) {\n        self.input.cursor_left()\n    }\n\n    pub fn history_up(&mut self) {\n        if let Some(line) = self.hist.line_up() {\n            self.input.set_text(&line);\n        }\n    }\n\n    pub fn history_down(&mut self) {\n        if let Some(line) = self.hist.line_down() {\n            self.input.set_text(&line);\n        }\n    }\n\n    /// Interprets the contents of the execution buffer.\n    pub fn execute(&self) {\n        let text = self.buffer.replace(String::new());\n\n        let (_remaining, commands) = parse::commands(text.as_str()).unwrap();\n\n        for command in commands.iter() {\n            debug!(\"{:?}\", command);\n        }\n\n        for args in commands {\n            if let Some(arg_0) = args.get(0) {\n                let maybe_alias = self.aliases.borrow().get(*arg_0).map(|a| a.to_owned());\n                match maybe_alias {\n                    Some(a) => {\n                        self.stuff_text(a);\n                        self.execute();\n                    }\n\n                    None => {\n                        let tail_args: Vec<&str> =\n                            args.iter().map(|s| s.as_ref()).skip(1).collect();\n\n                        if self.cmds.borrow().contains(arg_0) {\n                            match self.cmds.borrow_mut().exec(arg_0, &tail_args) {\n                                Ok(o) => {\n                                    if !o.is_empty() {\n                                        self.println(o)\n                                    }\n                                }\n                                Err(e) => self.println(format!(\"{}\", e)),\n                            }\n                        } else if self.cvars.borrow().contains(arg_0) {\n                            // TODO error handling on cvar set\n                            match args.get(1) {\n                                Some(arg_1) => self.cvars.borrow_mut().set(arg_0, arg_1).unwrap(),\n                                None => {\n                                    let msg = format!(\n                                        \"\\\"{}\\\" is \\\"{}\\\"\",\n                                        arg_0,\n                                        self.cvars.borrow().get(arg_0).unwrap()\n                                    );\n                                    self.println(msg);\n                                }\n                            }\n                        } else {\n                            // TODO: try sending to server first\n                            self.println(format!(\"Unrecognized command \\\"{}\\\"\", arg_0));\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn get_string(&self) -> String {\n        String::from_iter(self.input.text.clone().into_iter())\n    }\n\n    pub fn stuff_text<S>(&self, text: S)\n    where\n        S: AsRef<str>,\n    {\n        debug!(\"stuff_text:\\n{:?}\", text.as_ref());\n        self.buffer.borrow_mut().push_str(text.as_ref());\n\n        // in case the last line doesn't end with a newline\n        self.buffer.borrow_mut().push_str(\"\\n\");\n    }\n\n    pub fn output(&self) -> Ref<ConsoleOutput> {\n        self.output.borrow()\n    }\n}\n"
  },
  {
    "path": "src/common/engine.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{fs::File, io::Read};\n\nuse cgmath::{Deg, Vector3};\nuse chrono::Duration;\n\n// TODO: the palette should be host-specific and loaded alongside pak0.pak (or the latest PAK with a\n// palette.lmp)\nlazy_static! {\n    static ref PALETTE: [u8; 768] = {\n        let mut _palette = [0; 768];\n        let mut f = File::open(\"pak0.pak.d/gfx/palette.lmp\").unwrap();\n        match f.read(&mut _palette) {\n            Err(why) => panic!(\"{}\", why),\n            Ok(768) => _palette,\n            _ => panic!(\"Bad read on pak0/gfx/palette.lmp\"),\n        }\n    };\n}\n\npub fn indexed_to_rgba(indices: &[u8]) -> Vec<u8> {\n    let mut rgba = Vec::with_capacity(4 * indices.len());\n    for i in 0..indices.len() {\n        if indices[i] != 0xFF {\n            for c in 0..3 {\n                rgba.push(PALETTE[(3 * (indices[i] as usize) + c) as usize]);\n            }\n            rgba.push(0xFF);\n        } else {\n            for _ in 0..4 {\n                rgba.push(0x00);\n            }\n        }\n    }\n    rgba\n}\n\n// TODO: handle this unwrap? i64 can handle ~200,000 years in microseconds\n#[inline]\npub fn duration_to_f32(d: Duration) -> f32 {\n    d.num_microseconds().unwrap() as f32 / 1_000_000.0\n}\n\n#[inline]\npub fn duration_from_f32(f: f32) -> Duration {\n    Duration::microseconds((f * 1_000_000.0) as i64)\n}\n\n#[inline]\npub fn deg_vector_to_f32_vector(av: Vector3<Deg<f32>>) -> Vector3<f32> {\n    Vector3::new(av[0].0, av[1].0, av[2].0)\n}\n\n#[inline]\npub fn deg_vector_from_f32_vector(v: Vector3<f32>) -> Vector3<Deg<f32>> {\n    Vector3::new(Deg(v[0]), Deg(v[1]), Deg(v[2]))\n}\n"
  },
  {
    "path": "src/common/host.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::cell::{Ref, RefMut};\n\nuse crate::common::{console::CvarRegistry, engine};\n\nuse chrono::{DateTime, Duration, Utc};\nuse winit::{\n    event::{Event, WindowEvent},\n    event_loop::{ControlFlow, EventLoopWindowTarget},\n};\n\npub trait Program: Sized {\n    fn handle_event<T>(\n        &mut self,\n        event: Event<T>,\n        _target: &EventLoopWindowTarget<T>,\n        control_flow: &mut ControlFlow,\n    );\n\n    fn frame(&mut self, frame_duration: Duration);\n    fn shutdown(&mut self);\n    fn cvars(&self) -> Ref<CvarRegistry>;\n    fn cvars_mut(&self) -> RefMut<CvarRegistry>;\n}\n\npub struct Host<P>\nwhere\n    P: Program,\n{\n    program: P,\n\n    init_time: DateTime<Utc>,\n    prev_frame_time: DateTime<Utc>,\n    prev_frame_duration: Duration,\n}\n\nimpl<P> Host<P>\nwhere\n    P: Program,\n{\n    pub fn new(program: P) -> Host<P> {\n        let init_time = Utc::now();\n        program\n            .cvars_mut()\n            .register_archive(\"host_maxfps\", \"72\")\n            .unwrap();\n\n        Host {\n            program,\n            init_time,\n            prev_frame_time: init_time,\n            prev_frame_duration: Duration::zero(),\n        }\n    }\n\n    pub fn handle_event<T>(\n        &mut self,\n        event: Event<T>,\n        _target: &EventLoopWindowTarget<T>,\n        control_flow: &mut ControlFlow,\n    ) {\n        match event {\n            Event::WindowEvent {\n                event: WindowEvent::CloseRequested,\n                ..\n            } => {\n                self.program.shutdown();\n                *control_flow = ControlFlow::Exit;\n            }\n\n            Event::MainEventsCleared => self.frame(),\n            Event::Suspended | Event::Resumed => unimplemented!(),\n            Event::LoopDestroyed => {\n                // TODO:\n                // - host_writeconfig\n                // - others...\n            }\n\n            e => self.program.handle_event(e, _target, control_flow),\n        }\n    }\n\n    pub fn frame(&mut self) {\n        // TODO: make sure this doesn't cause weirdness with e.g. leap seconds\n        let new_frame_time = Utc::now();\n        self.prev_frame_duration = new_frame_time.signed_duration_since(self.prev_frame_time);\n\n        // if the time elapsed since the last frame is too low, don't run this one yet\n        let prev_frame_duration = self.prev_frame_duration;\n        if !self.check_frame_duration(prev_frame_duration) {\n            // avoid busy waiting if we're running at a really high framerate\n            std::thread::sleep(std::time::Duration::from_millis(1));\n            return;\n        }\n\n        // we're running this frame, so update the frame time\n        self.prev_frame_time = new_frame_time;\n\n        self.program.frame(self.prev_frame_duration);\n    }\n\n    // Returns whether enough time has elapsed to run the next frame.\n    fn check_frame_duration(&mut self, frame_duration: Duration) -> bool {\n        let host_maxfps = self\n            .program\n            .cvars()\n            .get_value(\"host_maxfps\")\n            .unwrap_or(72.0);\n        let min_frame_duration = engine::duration_from_f32(1.0 / host_maxfps);\n        frame_duration >= min_frame_duration\n    }\n\n    pub fn uptime(&self) -> Duration {\n        self.prev_frame_time.signed_duration_since(self.init_time)\n    }\n}\n"
  },
  {
    "path": "src/common/math.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::{cmp::Ordering, convert::Into, ops::Neg};\n\nuse cgmath::{Angle, Deg, InnerSpace, Matrix3, Matrix4, Vector2, Vector3, Zero};\n\ntrait CoordSys {}\n\nstruct Quake;\nimpl CoordSys for Quake {}\n\nstruct Wgpu;\nimpl CoordSys for Wgpu {}\n\npub const VERTEX_NORMAL_COUNT: usize = 162;\nlazy_static! {\n    /// Precomputed vertex normals used for alias models and particle effects\n    pub static ref VERTEX_NORMALS: [Vector3<f32>; VERTEX_NORMAL_COUNT] = [\n        [-0.525731, 0.000000, 0.850651].into(),\n        [-0.442863, 0.238856, 0.864188].into(),\n        [-0.295242, 0.000000, 0.955423].into(),\n        [-0.309017, 0.500000, 0.809017].into(),\n        [-0.162460, 0.262866, 0.951056].into(),\n        [0.000000, 0.000000, 1.000000].into(),\n        [0.000000, 0.850651, 0.525731].into(),\n        [-0.147621, 0.716567, 0.681718].into(),\n        [0.147621, 0.716567, 0.681718].into(),\n        [0.000000, 0.525731, 0.850651].into(),\n        [0.309017, 0.500000, 0.809017].into(),\n        [0.525731, 0.000000, 0.850651].into(),\n        [0.295242, 0.000000, 0.955423].into(),\n        [0.442863, 0.238856, 0.864188].into(),\n        [0.162460, 0.262866, 0.951056].into(),\n        [-0.681718, 0.147621, 0.716567].into(),\n        [-0.809017, 0.309017, 0.500000].into(),\n        [-0.587785, 0.425325, 0.688191].into(),\n        [-0.850651, 0.525731, 0.000000].into(),\n        [-0.864188, 0.442863, 0.238856].into(),\n        [-0.716567, 0.681718, 0.147621].into(),\n        [-0.688191, 0.587785, 0.425325].into(),\n        [-0.500000, 0.809017, 0.309017].into(),\n        [-0.238856, 0.864188, 0.442863].into(),\n        [-0.425325, 0.688191, 0.587785].into(),\n        [-0.716567, 0.681718, -0.147621].into(),\n        [-0.500000, 0.809017, -0.309017].into(),\n        [-0.525731, 0.850651, 0.000000].into(),\n        [0.000000, 0.850651, -0.525731].into(),\n        [-0.238856, 0.864188, -0.442863].into(),\n        [0.000000, 0.955423, -0.295242].into(),\n        [-0.262866, 0.951056, -0.162460].into(),\n        [0.000000, 1.000000, 0.000000].into(),\n        [0.000000, 0.955423, 0.295242].into(),\n        [-0.262866, 0.951056, 0.162460].into(),\n        [0.238856, 0.864188, 0.442863].into(),\n        [0.262866, 0.951056, 0.162460].into(),\n        [0.500000, 0.809017, 0.309017].into(),\n        [0.238856, 0.864188, -0.442863].into(),\n        [0.262866, 0.951056, -0.162460].into(),\n        [0.500000, 0.809017, -0.309017].into(),\n        [0.850651, 0.525731, 0.000000].into(),\n        [0.716567, 0.681718, 0.147621].into(),\n        [0.716567, 0.681718, -0.147621].into(),\n        [0.525731, 0.850651, 0.000000].into(),\n        [0.425325, 0.688191, 0.587785].into(),\n        [0.864188, 0.442863, 0.238856].into(),\n        [0.688191, 0.587785, 0.425325].into(),\n        [0.809017, 0.309017, 0.500000].into(),\n        [0.681718, 0.147621, 0.716567].into(),\n        [0.587785, 0.425325, 0.688191].into(),\n        [0.955423, 0.295242, 0.000000].into(),\n        [1.000000, 0.000000, 0.000000].into(),\n        [0.951056, 0.162460, 0.262866].into(),\n        [0.850651, -0.525731, 0.000000].into(),\n        [0.955423, -0.295242, 0.000000].into(),\n        [0.864188, -0.442863, 0.238856].into(),\n        [0.951056, -0.162460, 0.262866].into(),\n        [0.809017, -0.309017, 0.500000].into(),\n        [0.681718, -0.147621, 0.716567].into(),\n        [0.850651, 0.000000, 0.525731].into(),\n        [0.864188, 0.442863, -0.238856].into(),\n        [0.809017, 0.309017, -0.500000].into(),\n        [0.951056, 0.162460, -0.262866].into(),\n        [0.525731, 0.000000, -0.850651].into(),\n        [0.681718, 0.147621, -0.716567].into(),\n        [0.681718, -0.147621, -0.716567].into(),\n        [0.850651, 0.000000, -0.525731].into(),\n        [0.809017, -0.309017, -0.500000].into(),\n        [0.864188, -0.442863, -0.238856].into(),\n        [0.951056, -0.162460, -0.262866].into(),\n        [0.147621, 0.716567, -0.681718].into(),\n        [0.309017, 0.500000, -0.809017].into(),\n        [0.425325, 0.688191, -0.587785].into(),\n        [0.442863, 0.238856, -0.864188].into(),\n        [0.587785, 0.425325, -0.688191].into(),\n        [0.688191, 0.587785, -0.425325].into(),\n        [-0.147621, 0.716567, -0.681718].into(),\n        [-0.309017, 0.500000, -0.809017].into(),\n        [0.000000, 0.525731, -0.850651].into(),\n        [-0.525731, 0.000000, -0.850651].into(),\n        [-0.442863, 0.238856, -0.864188].into(),\n        [-0.295242, 0.000000, -0.955423].into(),\n        [-0.162460, 0.262866, -0.951056].into(),\n        [0.000000, 0.000000, -1.000000].into(),\n        [0.295242, 0.000000, -0.955423].into(),\n        [0.162460, 0.262866, -0.951056].into(),\n        [-0.442863, -0.238856, -0.864188].into(),\n        [-0.309017, -0.500000, -0.809017].into(),\n        [-0.162460, -0.262866, -0.951056].into(),\n        [0.000000, -0.850651, -0.525731].into(),\n        [-0.147621, -0.716567, -0.681718].into(),\n        [0.147621, -0.716567, -0.681718].into(),\n        [0.000000, -0.525731, -0.850651].into(),\n        [0.309017, -0.500000, -0.809017].into(),\n        [0.442863, -0.238856, -0.864188].into(),\n        [0.162460, -0.262866, -0.951056].into(),\n        [0.238856, -0.864188, -0.442863].into(),\n        [0.500000, -0.809017, -0.309017].into(),\n        [0.425325, -0.688191, -0.587785].into(),\n        [0.716567, -0.681718, -0.147621].into(),\n        [0.688191, -0.587785, -0.425325].into(),\n        [0.587785, -0.425325, -0.688191].into(),\n        [0.000000, -0.955423, -0.295242].into(),\n        [0.000000, -1.000000, 0.000000].into(),\n        [0.262866, -0.951056, -0.162460].into(),\n        [0.000000, -0.850651, 0.525731].into(),\n        [0.000000, -0.955423, 0.295242].into(),\n        [0.238856, -0.864188, 0.442863].into(),\n        [0.262866, -0.951056, 0.162460].into(),\n        [0.500000, -0.809017, 0.309017].into(),\n        [0.716567, -0.681718, 0.147621].into(),\n        [0.525731, -0.850651, 0.000000].into(),\n        [-0.238856, -0.864188, -0.442863].into(),\n        [-0.500000, -0.809017, -0.309017].into(),\n        [-0.262866, -0.951056, -0.162460].into(),\n        [-0.850651, -0.525731, 0.000000].into(),\n        [-0.716567, -0.681718, -0.147621].into(),\n        [-0.716567, -0.681718, 0.147621].into(),\n        [-0.525731, -0.850651, 0.000000].into(),\n        [-0.500000, -0.809017, 0.309017].into(),\n        [-0.238856, -0.864188, 0.442863].into(),\n        [-0.262866, -0.951056, 0.162460].into(),\n        [-0.864188, -0.442863, 0.238856].into(),\n        [-0.809017, -0.309017, 0.500000].into(),\n        [-0.688191, -0.587785, 0.425325].into(),\n        [-0.681718, -0.147621, 0.716567].into(),\n        [-0.442863, -0.238856, 0.864188].into(),\n        [-0.587785, -0.425325, 0.688191].into(),\n        [-0.309017, -0.500000, 0.809017].into(),\n        [-0.147621, -0.716567, 0.681718].into(),\n        [-0.425325, -0.688191, 0.587785].into(),\n        [-0.162460, -0.262866, 0.951056].into(),\n        [0.442863, -0.238856, 0.864188].into(),\n        [0.162460, -0.262866, 0.951056].into(),\n        [0.309017, -0.500000, 0.809017].into(),\n        [0.147621, -0.716567, 0.681718].into(),\n        [0.000000, -0.525731, 0.850651].into(),\n        [0.425325, -0.688191, 0.587785].into(),\n        [0.587785, -0.425325, 0.688191].into(),\n        [0.688191, -0.587785, 0.425325].into(),\n        [-0.955423, 0.295242, 0.000000].into(),\n        [-0.951056, 0.162460, 0.262866].into(),\n        [-1.000000, 0.000000, 0.000000].into(),\n        [-0.850651, 0.000000, 0.525731].into(),\n        [-0.955423, -0.295242, 0.000000].into(),\n        [-0.951056, -0.162460, 0.262866].into(),\n        [-0.864188, 0.442863, -0.238856].into(),\n        [-0.951056, 0.162460, -0.262866].into(),\n        [-0.809017, 0.309017, -0.500000].into(),\n        [-0.864188, -0.442863, -0.238856].into(),\n        [-0.951056, -0.162460, -0.262866].into(),\n        [-0.809017, -0.309017, -0.500000].into(),\n        [-0.681718, 0.147621, -0.716567].into(),\n        [-0.681718, -0.147621, -0.716567].into(),\n        [-0.850651, 0.000000, -0.525731].into(),\n        [-0.688191, 0.587785, -0.425325].into(),\n        [-0.587785, 0.425325, -0.688191].into(),\n        [-0.425325, 0.688191, -0.587785].into(),\n        [-0.425325, -0.688191, -0.587785].into(),\n        [-0.587785, -0.425325, -0.688191].into(),\n        [-0.688191, -0.587785, -0.425325].into(),\n    ];\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct Angles {\n    pub pitch: Deg<f32>,\n    pub roll: Deg<f32>,\n    pub yaw: Deg<f32>,\n}\n\nimpl Angles {\n    pub fn zero() -> Angles {\n        Angles {\n            pitch: Deg(0.0),\n            roll: Deg(0.0),\n            yaw: Deg(0.0),\n        }\n    }\n\n    pub fn mat3_quake(&self) -> Matrix3<f32> {\n        Matrix3::from_angle_x(-self.roll)\n            * Matrix3::from_angle_y(-self.pitch)\n            * Matrix3::from_angle_z(self.yaw)\n    }\n\n    pub fn mat4_quake(&self) -> Matrix4<f32> {\n        Matrix4::from_angle_x(-self.roll)\n            * Matrix4::from_angle_y(-self.pitch)\n            * Matrix4::from_angle_z(self.yaw)\n    }\n\n    pub fn mat3_wgpu(&self) -> Matrix3<f32> {\n        Matrix3::from_angle_z(-self.roll)\n            * Matrix3::from_angle_x(self.pitch)\n            * Matrix3::from_angle_y(-self.yaw)\n    }\n\n    pub fn mat4_wgpu(&self) -> Matrix4<f32> {\n        Matrix4::from_angle_z(-self.roll)\n            * Matrix4::from_angle_x(self.pitch)\n            * Matrix4::from_angle_y(-self.yaw)\n    }\n}\n\nimpl std::ops::Add for Angles {\n    type Output = Self;\n\n    fn add(self, other: Self) -> Self {\n        Self {\n            pitch: self.pitch + other.pitch,\n            roll: self.roll + other.roll,\n            yaw: self.yaw + other.yaw,\n        }\n    }\n}\n\nimpl std::ops::Mul<f32> for Angles {\n    type Output = Self;\n\n    fn mul(self, other: f32) -> Self {\n        Self {\n            pitch: self.pitch * other,\n            roll: self.roll * other,\n            yaw: self.yaw * other,\n        }\n    }\n}\n\npub fn clamp_deg(val: Deg<f32>, min: Deg<f32>, max: Deg<f32>) -> Deg<f32> {\n    assert!(min <= max);\n\n    return if val < min {\n        min\n    } else if val > max {\n        max\n    } else {\n        val\n    };\n}\n\n#[derive(Copy, Clone, Debug, PartialEq)]\npub enum HyperplaneSide {\n    Positive = 0,\n    Negative = 1,\n}\n\nimpl Neg for HyperplaneSide {\n    type Output = HyperplaneSide;\n\n    fn neg(self) -> Self::Output {\n        match self {\n            HyperplaneSide::Positive => HyperplaneSide::Negative,\n            HyperplaneSide::Negative => HyperplaneSide::Positive,\n        }\n    }\n}\n\nimpl HyperplaneSide {\n    // TODO: check this against the original game logic.\n    pub fn from_dist(dist: f32) -> HyperplaneSide {\n        if dist >= 0.0 {\n            HyperplaneSide::Positive\n        } else {\n            HyperplaneSide::Negative\n        }\n    }\n}\n\n#[derive(Debug)]\n/// The intersection of a line or segment and a plane at a point.\npub struct PointIntersection {\n    // percentage of distance between start and end where crossover occurred\n    ratio: f32,\n\n    // crossover point\n    point: Vector3<f32>,\n\n    // plane crossed over\n    plane: Hyperplane,\n}\n\nimpl PointIntersection {\n    pub fn ratio(&self) -> f32 {\n        self.ratio\n    }\n\n    pub fn point(&self) -> Vector3<f32> {\n        self.point\n    }\n\n    pub fn plane(&self) -> &Hyperplane {\n        &self.plane\n    }\n}\n\n#[derive(Debug)]\n/// The intersection of a line or line segment with a plane.\n///\n/// A true mathematical representation would account for lines or segments contained entirely within\n/// the plane, but here a distance of 0.0 is considered Positive. Thus, lines or segments contained\n/// by the plane are considered to be `NoIntersection(Positive)`.\npub enum LinePlaneIntersect {\n    /// The line or line segment never intersects with the plane.\n    NoIntersection(HyperplaneSide),\n\n    /// The line or line segment intersects with the plane at precisely one point.\n    PointIntersection(PointIntersection),\n}\n\n#[derive(Copy, Clone, Debug, FromPrimitive)]\npub enum Axis {\n    X = 0,\n    Y = 1,\n    Z = 2,\n}\n\n#[derive(Clone, Debug)]\nenum Alignment {\n    Axis(Axis),\n    Normal(Vector3<f32>),\n}\n\n#[derive(Clone, Debug)]\npub struct Hyperplane {\n    alignment: Alignment,\n    dist: f32,\n}\n\nimpl Neg for Hyperplane {\n    type Output = Self;\n\n    fn neg(self) -> Self::Output {\n        let normal = match self.alignment {\n            Alignment::Axis(a) => {\n                let mut n = Vector3::zero();\n                n[a as usize] = -1.0;\n                n\n            }\n            Alignment::Normal(n) => -n,\n        };\n\n        Hyperplane::new(normal, -self.dist)\n    }\n}\n\nimpl Hyperplane {\n    /// Creates a new hyperplane aligned along the given normal, `dist` units away from the origin.\n    ///\n    /// If the given normal is equivalent to one of the axis normals, the hyperplane will be optimized\n    /// to only consider that axis when performing point comparisons.\n    pub fn new(normal: Vector3<f32>, dist: f32) -> Hyperplane {\n        match normal {\n            n if n == Vector3::unit_x() => Self::axis_x(dist),\n            n if n == Vector3::unit_y() => Self::axis_y(dist),\n            n if n == Vector3::unit_z() => Self::axis_z(dist),\n            _ => Self::from_normal(normal.normalize(), dist),\n        }\n    }\n\n    /// Creates a new hyperplane aligned along the x-axis, `dist` units away from the origin.\n    ///\n    /// This hyperplane will only consider the x-axis when performing point comparisons.\n    pub fn axis_x(dist: f32) -> Hyperplane {\n        Hyperplane {\n            alignment: Alignment::Axis(Axis::X),\n            dist,\n        }\n    }\n\n    /// Creates a new hyperplane aligned along the y-axis, `dist` units away from the origin.\n    ///\n    /// This hyperplane will only consider the y-axis when performing point comparisons.\n    pub fn axis_y(dist: f32) -> Hyperplane {\n        Hyperplane {\n            alignment: Alignment::Axis(Axis::Y),\n            dist,\n        }\n    }\n\n    /// Creates a new hyperplane aligned along the z-axis, `dist` units away from the origin.\n    ///\n    /// This hyperplane will only consider the z-axis when performing point comparisons.\n    pub fn axis_z(dist: f32) -> Hyperplane {\n        Hyperplane {\n            alignment: Alignment::Axis(Axis::Z),\n            dist,\n        }\n    }\n\n    /// Creates a new hyperplane aligned along the given normal, `dist` units away from the origin.\n    ///\n    /// This function will force the hyperplane alignment to be represented as a normal even if it\n    /// is aligned along an axis.\n    pub fn from_normal(normal: Vector3<f32>, dist: f32) -> Hyperplane {\n        Hyperplane {\n            alignment: Alignment::Normal(normal.normalize()),\n            dist,\n        }\n    }\n\n    /// Returns the surface normal of this plane.\n    pub fn normal(&self) -> Vector3<f32> {\n        match self.alignment {\n            Alignment::Axis(ax) => match ax {\n                Axis::X => Vector3::unit_x(),\n                Axis::Y => Vector3::unit_y(),\n                Axis::Z => Vector3::unit_z(),\n            },\n            Alignment::Normal(normal) => normal,\n        }\n    }\n\n    /// Calculates the shortest distance between this hyperplane and the given point.\n    pub fn point_dist(&self, point: Vector3<f32>) -> f32 {\n        match self.alignment {\n            Alignment::Axis(a) => point[a as usize] - self.dist,\n            Alignment::Normal(n) => point.dot(n) - self.dist,\n        }\n    }\n\n    /// Calculates which side of this hyperplane the given point belongs to.\n    ///\n    /// Points with a distance of 0.0 are considered to be on the positive side.\n    pub fn point_side(&self, point: Vector3<f32>) -> HyperplaneSide {\n        let point_dist_greater = match self.alignment {\n            Alignment::Axis(a) => point[a as usize] >= self.dist,\n            Alignment::Normal(n) => point.dot(n) - self.dist >= 0.0,\n        };\n\n        match point_dist_greater {\n            true => HyperplaneSide::Positive,\n            false => HyperplaneSide::Negative,\n        }\n    }\n\n    /// Calculates the intersection of a line segment with this hyperplane.\n    pub fn line_segment_intersection(\n        &self,\n        start: Vector3<f32>,\n        end: Vector3<f32>,\n    ) -> LinePlaneIntersect {\n        let start_dist = self.point_dist(start);\n        let end_dist = self.point_dist(end);\n\n        debug!(\n            \"line_segment_intersection: alignment={:?} plane_dist={} start_dist={} end_dist={}\",\n            self.alignment, self.dist, start_dist, end_dist\n        );\n\n        let start_side = HyperplaneSide::from_dist(start_dist);\n        let end_side = HyperplaneSide::from_dist(end_dist);\n\n        // if both points fall on the same side of the hyperplane, there is no intersection\n        if start_side == end_side {\n            return LinePlaneIntersect::NoIntersection(start_side);\n        }\n\n        // calculate how far along the segment the intersection occurred\n        let ratio = start_dist / (start_dist - end_dist);\n\n        let point = start + ratio * (end - start);\n\n        let plane = match start_side {\n            HyperplaneSide::Positive => self.to_owned(),\n            HyperplaneSide::Negative => -self.to_owned(),\n        };\n\n        LinePlaneIntersect::PointIntersection(PointIntersection {\n            ratio,\n            point,\n            plane,\n        })\n    }\n}\n\npub fn fov_x_to_fov_y(fov_x: Deg<f32>, aspect: f32) -> Option<Deg<f32>> {\n    // aspect = tan(fov_x / 2) / tan(fov_y / 2)\n    // tan(fov_y / 2) = tan(fov_x / 2) / aspect\n    // fov_y / 2 = atan(tan(fov_x / 2) / aspect)\n    // fov_y = 2 * atan(tan(fov_x / 2) / aspect)\n    match fov_x {\n        // TODO: genericize over cgmath::Angle\n        f if f < Deg(0.0) => None,\n        f if f > Deg(360.0) => None,\n        f => Some(Deg::atan((f / 2.0).tan() / aspect) * 2.0),\n    }\n}\n\n// see https://github.com/id-Software/Quake/blob/master/WinQuake/gl_rsurf.c#L1544\nconst COLLINEAR_EPSILON: f32 = 0.001;\n\n/// Determines if the given points are collinear.\n///\n/// A set of points V is considered collinear if\n/// norm(V<sub>1</sub> &minus; V<sub>0</sub>) &equals;\n/// norm(V<sub>2</sub> &minus; V<sub>1</sub>) &equals;\n/// .&nbsp;.&nbsp;. &equals;\n/// norm(V<sub>k &minus; 1</sub> &minus; V<sub>k</sub>).\n///\n/// Special cases:\n/// - If `vs.len() < 2`, always returns `false`.\n/// - If `vs.len() == 2`, always returns `true`.\npub fn collinear(vs: &[Vector3<f32>]) -> bool {\n    match vs.len() {\n        l if l < 2 => false,\n        2 => true,\n        _ => {\n            let init = (vs[1] - vs[0]).normalize();\n            for i in 2..vs.len() {\n                let norm = (vs[i] - vs[i - 1]).normalize();\n                if (norm[0] - init[0]).abs() > COLLINEAR_EPSILON\n                    || (norm[1] - init[1]).abs() > COLLINEAR_EPSILON\n                    || (norm[2] - init[2]).abs() > COLLINEAR_EPSILON\n                {\n                    return false;\n                }\n            }\n\n            true\n        }\n    }\n}\n\npub fn remove_collinear(vs: Vec<Vector3<f32>>) -> Vec<Vector3<f32>> {\n    assert!(vs.len() >= 3);\n\n    let mut out = Vec::new();\n\n    let mut vs_iter = vs.into_iter().cycle();\n    let v_init = vs_iter.next().unwrap();\n    let mut v1 = v_init;\n    let mut v2 = vs_iter.next().unwrap();\n    out.push(v1);\n    for v3 in vs_iter {\n        let tri = &[v1, v2, v3];\n\n        if !collinear(tri) {\n            out.push(v2);\n        }\n\n        if v3 == v_init {\n            break;\n        }\n\n        v1 = v2;\n        v2 = v3;\n    }\n\n    out\n}\n\npub fn bounds<'a, I>(points: I) -> (Vector3<f32>, Vector3<f32>)\nwhere\n    I: IntoIterator<Item = &'a Vector3<f32>>,\n{\n    let mut min = Vector3::new(32767.0, 32767.0, 32767.0);\n    let mut max = Vector3::new(-32768.0, -32768.0, -32768.0);\n    for p in points.into_iter() {\n        for c in 0..3 {\n            min[c] = p[c].min(min[c]);\n            max[c] = p[c].max(max[c]);\n        }\n    }\n    (min, max)\n}\n\npub fn vec2_extend_n(v: Vector2<f32>, n: usize, val: f32) -> Vector3<f32> {\n    let mut ar = [0.0; 3];\n    for i in 0..3 {\n        match i.cmp(&n) {\n            Ordering::Less => ar[i] = v[i],\n            Ordering::Equal => ar[i] = val,\n            Ordering::Greater => ar[i] = v[i - 1],\n        }\n    }\n\n    ar.into()\n}\n\npub fn vec3_truncate_n(v: Vector3<f32>, n: usize) -> Vector2<f32> {\n    let mut ar = [0.0; 2];\n    for i in 0..3 {\n        match i.cmp(&n) {\n            Ordering::Less => ar[i] = v[i],\n            Ordering::Equal => (),\n            Ordering::Greater => ar[i - 1] = v[i],\n        }\n    }\n    ar.into()\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_hyperplane_side_x() {\n        let plane = Hyperplane::axis_x(1.0);\n        assert_eq!(\n            plane.point_side(Vector3::unit_x() * 2.0),\n            HyperplaneSide::Positive\n        );\n        assert_eq!(\n            plane.point_side(Vector3::unit_x() * -2.0),\n            HyperplaneSide::Negative\n        );\n    }\n\n    #[test]\n    fn test_hyperplane_side_y() {\n        let plane = Hyperplane::axis_y(1.0);\n        assert_eq!(\n            plane.point_side(Vector3::unit_y() * 2.0),\n            HyperplaneSide::Positive\n        );\n        assert_eq!(\n            plane.point_side(Vector3::unit_y() * -2.0),\n            HyperplaneSide::Negative\n        );\n    }\n\n    #[test]\n    fn test_hyperplane_side_z() {\n        let plane = Hyperplane::axis_z(1.0);\n        assert_eq!(\n            plane.point_side(Vector3::unit_z() * 2.0),\n            HyperplaneSide::Positive\n        );\n        assert_eq!(\n            plane.point_side(Vector3::unit_z() * -2.0),\n            HyperplaneSide::Negative\n        );\n    }\n\n    #[test]\n    fn test_hyperplane_side_arbitrary() {\n        // test 16 hyperplanes around the origin\n        for x_comp in [1.0, -1.0].into_iter() {\n            for y_comp in [1.0, -1.0].into_iter() {\n                for z_comp in [1.0, -1.0].into_iter() {\n                    for dist in [1, -1].into_iter() {\n                        let base_vector = Vector3::new(*x_comp, *y_comp, *z_comp);\n                        let plane = Hyperplane::new(base_vector, *dist as f32);\n                        assert_eq!(\n                            plane.point_side(Vector3::zero()),\n                            match *dist {\n                                1 => HyperplaneSide::Negative,\n                                -1 => HyperplaneSide::Positive,\n                                _ => unreachable!(),\n                            }\n                        );\n                        assert_eq!(\n                            plane.point_side(base_vector * 2.0 * *dist as f32),\n                            match *dist {\n                                1 => HyperplaneSide::Positive,\n                                -1 => HyperplaneSide::Negative,\n                                _ => unreachable!(),\n                            }\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_hyperplane_point_dist_x() {\n        let plane = Hyperplane::axis_x(1.0);\n        assert_eq!(plane.point_dist(Vector3::unit_x() * 2.0), 1.0);\n        assert_eq!(plane.point_dist(Vector3::zero()), -1.0);\n    }\n\n    #[test]\n    fn test_hyperplane_point_dist_y() {\n        let plane = Hyperplane::axis_y(1.0);\n        assert_eq!(plane.point_dist(Vector3::unit_y() * 2.0), 1.0);\n        assert_eq!(plane.point_dist(Vector3::zero()), -1.0);\n    }\n\n    #[test]\n    fn test_hyperplane_point_dist_z() {\n        let plane = Hyperplane::axis_z(1.0);\n        assert_eq!(plane.point_dist(Vector3::unit_z() * 2.0), 1.0);\n        assert_eq!(plane.point_dist(Vector3::zero()), -1.0);\n    }\n\n    #[test]\n    fn test_hyperplane_point_dist_x_no_axis() {\n        let plane = Hyperplane::from_normal(Vector3::unit_x(), 1.0);\n        assert_eq!(plane.point_dist(Vector3::unit_x() * 2.0), 1.0);\n        assert_eq!(plane.point_dist(Vector3::zero()), -1.0);\n    }\n\n    #[test]\n    fn test_hyperplane_point_dist_y_no_axis() {\n        let plane = Hyperplane::from_normal(Vector3::unit_y(), 1.0);\n        assert_eq!(plane.point_dist(Vector3::unit_y() * 2.0), 1.0);\n        assert_eq!(plane.point_dist(Vector3::zero()), -1.0);\n    }\n\n    #[test]\n    fn test_hyperplane_point_dist_z_no_axis() {\n        let plane = Hyperplane::from_normal(Vector3::unit_z(), 1.0);\n        assert_eq!(plane.point_dist(Vector3::unit_z() * 2.0), 1.0);\n        assert_eq!(plane.point_dist(Vector3::zero()), -1.0);\n    }\n\n    #[test]\n    fn test_hyperplane_line_segment_intersection_x() {\n        let plane = Hyperplane::axis_x(1.0);\n        let start = Vector3::new(0.0, 0.5, 0.5);\n        let end = Vector3::new(2.0, 0.5, 0.5);\n\n        match plane.line_segment_intersection(start, end) {\n            LinePlaneIntersect::PointIntersection(p_i) => {\n                assert_eq!(p_i.ratio(), 0.5);\n                assert_eq!(p_i.point(), Vector3::new(1.0, 0.5, 0.5));\n            }\n            _ => panic!(),\n        }\n    }\n\n    #[test]\n    fn test_hyperplane_line_segment_intersection_y() {\n        let plane = Hyperplane::axis_y(1.0);\n        let start = Vector3::new(0.5, 0.0, 0.5);\n        let end = Vector3::new(0.5, 2.0, 0.5);\n\n        match plane.line_segment_intersection(start, end) {\n            LinePlaneIntersect::PointIntersection(p_i) => {\n                assert_eq!(p_i.ratio(), 0.5);\n                assert_eq!(p_i.point(), Vector3::new(0.5, 1.0, 0.5));\n            }\n            _ => panic!(),\n        }\n    }\n\n    #[test]\n    fn test_hyperplane_line_segment_intersection_z() {\n        let plane = Hyperplane::axis_z(1.0);\n        let start = Vector3::new(0.5, 0.5, 0.0);\n        let end = Vector3::new(0.5, 0.5, 2.0);\n\n        match plane.line_segment_intersection(start, end) {\n            LinePlaneIntersect::PointIntersection(p_i) => {\n                assert_eq!(p_i.ratio(), 0.5);\n                assert_eq!(p_i.point(), Vector3::new(0.5, 0.5, 1.0));\n            }\n            _ => panic!(),\n        }\n    }\n\n    #[test]\n    fn test_collinear() {\n        let cases = vec![\n            (\n                vec![Vector3::unit_x(), Vector3::unit_y(), Vector3::unit_z()],\n                false,\n            ),\n            (\n                vec![\n                    Vector3::unit_x(),\n                    Vector3::unit_x() * 2.0,\n                    Vector3::unit_x() * 3.0,\n                ],\n                true,\n            ),\n            (\n                vec![\n                    [1400.0, 848.0, -456.0].into(),\n                    [1352.0, 848.0, -456.0].into(),\n                    [1272.0, 848.0, -456.0].into(),\n                    [1256.0, 848.0, -456.0].into(),\n                    [1208.0, 848.0, -456.0].into(),\n                    [1192.0, 848.0, -456.0].into(),\n                    [1176.0, 848.0, -456.0].into(),\n                ],\n                true,\n            ),\n        ];\n\n        for (input, result) in cases.into_iter() {\n            assert_eq!(collinear(&input), result);\n        }\n    }\n\n    #[test]\n    fn test_remove_collinear() {\n        let cases = vec![\n            (\n                vec![\n                    [1176.0, 992.0, -456.0].into(),\n                    [1176.0, 928.0, -456.0].into(),\n                    [1176.0, 880.0, -456.0].into(),\n                    [1176.0, 864.0, -456.0].into(),\n                    [1176.0, 848.0, -456.0].into(),\n                    [1120.0, 848.0, -456.0].into(),\n                    [1120.0, 992.0, -456.0].into(),\n                ],\n                vec![\n                    [1176.0, 992.0, -456.0].into(),\n                    [1176.0, 848.0, -456.0].into(),\n                    [1120.0, 848.0, -456.0].into(),\n                    [1120.0, 992.0, -456.0].into(),\n                ],\n            ),\n            (\n                vec![\n                    [1400.0, 768.0, -456.0].into(),\n                    [1400.0, 848.0, -456.0].into(),\n                    [1352.0, 848.0, -456.0].into(),\n                    [1272.0, 848.0, -456.0].into(),\n                    [1256.0, 848.0, -456.0].into(),\n                    [1208.0, 848.0, -456.0].into(),\n                    [1192.0, 848.0, -456.0].into(),\n                    [1176.0, 848.0, -456.0].into(),\n                    [1120.0, 848.0, -456.0].into(),\n                    [1200.0, 768.0, -456.0].into(),\n                ],\n                vec![\n                    [1400.0, 768.0, -456.0].into(),\n                    [1400.0, 848.0, -456.0].into(),\n                    [1120.0, 848.0, -456.0].into(),\n                    [1200.0, 768.0, -456.0].into(),\n                ],\n            ),\n        ];\n\n        for (input, output) in cases.into_iter() {\n            assert_eq!(remove_collinear(input), output);\n        }\n    }\n}\n"
  },
  {
    "path": "src/common/mdl.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::io::{self, BufReader, Read, Seek, SeekFrom};\n\nuse crate::common::{\n    engine,\n    model::{ModelFlags, SyncType},\n    util::read_f32_3,\n};\n\nuse byteorder::{LittleEndian, ReadBytesExt};\nuse cgmath::{ElementWise as _, Vector3};\nuse chrono::Duration;\nuse num::FromPrimitive;\nuse thiserror::Error;\n\npub const MAGIC: i32 =\n    ('I' as i32) << 0 | ('D' as i32) << 8 | ('P' as i32) << 16 | ('O' as i32) << 24;\npub const VERSION: i32 = 6;\n\nconst HEADER_SIZE: u64 = 84;\n\n#[derive(Error, Debug)]\npub enum MdlFileError {\n    #[error(\"I/O error: {0}\")]\n    Io(#[from] io::Error),\n    #[error(\"Invalid magic number: found {0}, expected {}\", MAGIC)]\n    InvalidMagicNumber(i32),\n    #[error(\"Unrecognized version: {0}\")]\n    UnrecognizedVersion(i32),\n    #[error(\"Invalid texture width: {0}\")]\n    InvalidTextureWidth(i32),\n    #[error(\"Invalid texture height: {0}\")]\n    InvalidTextureHeight(i32),\n    #[error(\"Invalid vertex count: {0}\")]\n    InvalidVertexCount(i32),\n    #[error(\"Invalid polygon count: {0}\")]\n    InvalidPolygonCount(i32),\n    #[error(\"Invalid keyframe count: {0}\")]\n    InvalidKeyframeCount(i32),\n    #[error(\"Invalid model flags: {0:X?}\")]\n    InvalidFlags(i32),\n    #[error(\"Invalid texture kind: {0}\")]\n    InvalidTextureKind(i32),\n    #[error(\"Invalid seam flag: {0}\")]\n    InvalidSeamFlag(i32),\n    #[error(\"Invalid texture coordinates: {0:?}\")]\n    InvalidTexcoord([i32; 2]),\n    #[error(\"Invalid front-facing flag: {0}\")]\n    InvalidFrontFacing(i32),\n    #[error(\"Keyframe name too long: {0:?}\")]\n    KeyframeNameTooLong([u8; 16]),\n    #[error(\"Non-UTF-8 keyframe name: {0}\")]\n    NonUtf8KeyframeName(#[from] std::string::FromUtf8Error),\n}\n\n#[derive(Clone, Debug)]\npub struct StaticTexture {\n    indices: Box<[u8]>,\n}\n\nimpl StaticTexture {\n    /// Returns the indexed colors of this texture.\n    pub fn indices(&self) -> &[u8] {\n        &self.indices\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct AnimatedTextureFrame {\n    duration: Duration,\n    indices: Box<[u8]>,\n}\n\nimpl AnimatedTextureFrame {\n    /// Returns the duration of this frame.\n    pub fn duration(&self) -> Duration {\n        self.duration\n    }\n\n    /// Returns the indexed colors of this texture.\n    pub fn indices(&self) -> &[u8] {\n        &self.indices\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct AnimatedTexture {\n    frames: Box<[AnimatedTextureFrame]>,\n}\n\nimpl AnimatedTexture {\n    pub fn frames(&self) -> &[AnimatedTextureFrame] {\n        &self.frames\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum Texture {\n    Static(StaticTexture),\n    Animated(AnimatedTexture),\n}\n\n#[derive(Clone, Debug)]\npub struct Texcoord {\n    is_on_seam: bool,\n    s: u32,\n    t: u32,\n}\n\nimpl Texcoord {\n    pub fn is_on_seam(&self) -> bool {\n        self.is_on_seam\n    }\n\n    pub fn s(&self) -> u32 {\n        self.s\n    }\n\n    pub fn t(&self) -> u32 {\n        self.t\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct IndexedPolygon {\n    faces_front: bool,\n    indices: [u32; 3],\n}\n\nimpl IndexedPolygon {\n    pub fn faces_front(&self) -> bool {\n        self.faces_front\n    }\n\n    pub fn indices(&self) -> &[u32; 3] {\n        &self.indices\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct StaticKeyframe {\n    name: String,\n    min: Vector3<f32>,\n    max: Vector3<f32>,\n    vertices: Box<[Vector3<f32>]>,\n}\n\nimpl StaticKeyframe {\n    /// Returns the name of this keyframe.\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    /// Returns the minimum extent of this keyframe relative to the model origin.\n    pub fn min(&self) -> Vector3<f32> {\n        self.min\n    }\n\n    /// Returns the minimum extent of this keyframe relative to the model origin.\n    pub fn max(&self) -> Vector3<f32> {\n        self.max\n    }\n\n    /// Returns the vertices defining this keyframe.\n    pub fn vertices(&self) -> &[Vector3<f32>] {\n        &self.vertices\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct AnimatedKeyframeFrame {\n    name: String,\n    min: Vector3<f32>,\n    max: Vector3<f32>,\n    duration: Duration,\n    vertices: Box<[Vector3<f32>]>,\n}\n\nimpl AnimatedKeyframeFrame {\n    /// Returns the name of this subframe.\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    /// Returns the minimum extent of this keyframe relative to the model origin.\n    pub fn min(&self) -> Vector3<f32> {\n        self.min\n    }\n\n    /// Returns the minimum extent of this keyframe relative to the model origin.\n    pub fn max(&self) -> Vector3<f32> {\n        self.max\n    }\n\n    /// Returns the duration of this subframe.\n    pub fn duration(&self) -> Duration {\n        self.duration\n    }\n\n    /// Returns the vertices defining this subframe.\n    pub fn vertices(&self) -> &[Vector3<f32>] {\n        &self.vertices\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct AnimatedKeyframe {\n    min: Vector3<f32>,\n    max: Vector3<f32>,\n    frames: Box<[AnimatedKeyframeFrame]>,\n}\n\nimpl AnimatedKeyframe {\n    /// Returns the minimum extent of all subframes in this keyframe relative to the model origin.\n    pub fn min(&self) -> Vector3<f32> {\n        self.min\n    }\n\n    /// Returns the maximum extent of all subframes in this keyframe relative to the model origin.\n    pub fn max(&self) -> Vector3<f32> {\n        self.max\n    }\n\n    /// Returns the subframes of this keyframe.\n    pub fn frames(&self) -> &[AnimatedKeyframeFrame] {\n        &self.frames\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum Keyframe {\n    Static(StaticKeyframe),\n    Animated(AnimatedKeyframe),\n}\n\n#[derive(Debug)]\npub struct AliasModel {\n    origin: Vector3<f32>,\n    radius: f32,\n    texture_width: u32,\n    texture_height: u32,\n    textures: Box<[Texture]>,\n    texcoords: Box<[Texcoord]>,\n    polygons: Box<[IndexedPolygon]>,\n    keyframes: Box<[Keyframe]>,\n    flags: ModelFlags,\n}\n\nimpl AliasModel {\n    pub fn origin(&self) -> Vector3<f32> {\n        self.origin\n    }\n\n    pub fn radius(&self) -> f32 {\n        self.radius\n    }\n\n    pub fn texture_width(&self) -> u32 {\n        self.texture_width\n    }\n\n    pub fn texture_height(&self) -> u32 {\n        self.texture_height\n    }\n\n    pub fn textures(&self) -> &[Texture] {\n        &self.textures\n    }\n\n    pub fn texcoords(&self) -> &[Texcoord] {\n        &self.texcoords\n    }\n\n    pub fn polygons(&self) -> &[IndexedPolygon] {\n        &self.polygons\n    }\n\n    pub fn keyframes(&self) -> &[Keyframe] {\n        &self.keyframes\n    }\n\n    pub fn flags(&self) -> ModelFlags {\n        self.flags\n    }\n}\n\npub fn load<R>(data: R) -> Result<AliasModel, MdlFileError>\nwhere\n    R: Read + Seek,\n{\n    let mut reader = BufReader::new(data);\n\n    // struct MdlHeader {\n    //     magic: i32\n    //     version: i32\n    //     scale: [f32; 3]\n    //     origin: [f32; 3]\n    //     radius: f32\n    //     eye_position: [f32; 3]\n    //     texture_count: i32,\n    //     texture_width: i32,\n    //     texture_height: i32,\n    //     vertex_count: i32,\n    //     poly_count: i32,\n    //     keyframe_count: i32,\n    //     sync_type: i32,\n    //     flags_bits: i32,\n    // }\n\n    let magic = reader.read_i32::<LittleEndian>()?;\n    if magic != MAGIC {\n        Err(MdlFileError::InvalidMagicNumber(magic))?;\n    }\n\n    let version = reader.read_i32::<LittleEndian>()?;\n    if version != VERSION {\n        Err(MdlFileError::UnrecognizedVersion(version))?;\n    }\n\n    let scale: Vector3<f32> = read_f32_3(&mut reader)?.into();\n    let origin: Vector3<f32> = read_f32_3(&mut reader)?.into();\n    let radius = reader.read_f32::<LittleEndian>()?;\n    let _eye_position: Vector3<f32> = read_f32_3(&mut reader)?.into();\n    let texture_count = reader.read_i32::<LittleEndian>()?;\n    let texture_width = reader.read_i32::<LittleEndian>()?;\n    if texture_width <= 0 {\n        Err(MdlFileError::InvalidTextureWidth(texture_width))?;\n    }\n    let texture_height = reader.read_i32::<LittleEndian>()?;\n    if texture_height <= 0 {\n        Err(MdlFileError::InvalidTextureHeight(texture_height))?;\n    }\n    let vertex_count = reader.read_i32::<LittleEndian>()?;\n    if vertex_count <= 0 {\n        Err(MdlFileError::InvalidVertexCount(vertex_count))?;\n    }\n    let poly_count = reader.read_i32::<LittleEndian>()?;\n    if poly_count <= 0 {\n        Err(MdlFileError::InvalidPolygonCount(poly_count))?;\n    }\n    let keyframe_count = reader.read_i32::<LittleEndian>()?;\n    if keyframe_count <= 0 {\n        Err(MdlFileError::InvalidKeyframeCount(keyframe_count))?;\n    }\n\n    let _sync_type = SyncType::from_i32(reader.read_i32::<LittleEndian>()?);\n\n    let flags_bits = reader.read_i32::<LittleEndian>()?;\n    if flags_bits < 0 || flags_bits > u8::MAX as i32 {\n        Err(MdlFileError::InvalidFlags(flags_bits))?;\n    }\n    let flags =\n        ModelFlags::from_bits(flags_bits as u8).ok_or(MdlFileError::InvalidFlags(flags_bits))?;\n\n    // unused\n    let _size = reader.read_i32::<LittleEndian>()?;\n\n    assert_eq!(\n        reader.seek(SeekFrom::Current(0))?,\n        reader.seek(SeekFrom::Start(HEADER_SIZE))?,\n        \"Misaligned read on MDL header\"\n    );\n\n    let mut textures: Vec<Texture> = Vec::with_capacity(texture_count as usize);\n\n    for _ in 0..texture_count {\n        // TODO: add a TextureKind type\n        let texture = match reader.read_i32::<LittleEndian>()? {\n            // Static\n            0 => {\n                let mut indices: Vec<u8> =\n                    Vec::with_capacity((texture_width * texture_height) as usize);\n                (&mut reader)\n                    .take((texture_width * texture_height) as u64)\n                    .read_to_end(&mut indices)?;\n                Texture::Static(StaticTexture {\n                    indices: indices.into_boxed_slice(),\n                })\n            }\n\n            // Animated\n            1 => {\n                // TODO: sanity check this value\n                let texture_frame_count = reader.read_i32::<LittleEndian>()? as usize;\n\n                let mut durations = Vec::with_capacity(texture_frame_count);\n                for _ in 0..texture_frame_count {\n                    durations.push(engine::duration_from_f32(\n                        reader.read_f32::<LittleEndian>()?,\n                    ));\n                }\n\n                let mut frames = Vec::with_capacity(texture_frame_count);\n                for frame_id in 0..texture_frame_count {\n                    let mut indices: Vec<u8> =\n                        Vec::with_capacity((texture_width * texture_height) as usize);\n                    (&mut reader)\n                        .take((texture_width * texture_height) as u64)\n                        .read_to_end(&mut indices)?;\n                    frames.push(AnimatedTextureFrame {\n                        duration: durations[frame_id as usize],\n                        indices: indices.into_boxed_slice(),\n                    });\n                }\n\n                Texture::Animated(AnimatedTexture {\n                    frames: frames.into_boxed_slice(),\n                })\n            }\n\n            k => Err(MdlFileError::InvalidTextureKind(k))?,\n        };\n\n        textures.push(texture);\n    }\n\n    let mut texcoords = Vec::with_capacity(vertex_count as usize);\n    for _ in 0..vertex_count {\n        let is_on_seam = match reader.read_i32::<LittleEndian>()? {\n            0 => false,\n            0x20 => true,\n            x => Err(MdlFileError::InvalidSeamFlag(x))?,\n        };\n\n        let s = reader.read_i32::<LittleEndian>()?;\n        let t = reader.read_i32::<LittleEndian>()?;\n        if s < 0 || t < 0 {\n            Err(MdlFileError::InvalidTexcoord([s, t]))?;\n        }\n\n        texcoords.push(Texcoord {\n            is_on_seam,\n            s: s as u32,\n            t: t as u32,\n        });\n    }\n\n    let mut polygons = Vec::with_capacity(poly_count as usize);\n    for _ in 0..poly_count {\n        let faces_front = match reader.read_i32::<LittleEndian>()? {\n            0 => false,\n            1 => true,\n            x => Err(MdlFileError::InvalidFrontFacing(x))?,\n        };\n\n        let mut indices = [0; 3];\n        for i in 0..3 {\n            indices[i] = reader.read_i32::<LittleEndian>()? as u32;\n        }\n\n        polygons.push(IndexedPolygon {\n            faces_front,\n            indices,\n        });\n    }\n\n    let mut keyframes: Vec<Keyframe> = Vec::with_capacity(keyframe_count as usize);\n    for _ in 0..keyframe_count {\n        keyframes.push(match reader.read_i32::<LittleEndian>()? {\n            0 => {\n                let min = read_vertex(&mut reader, scale, origin)?;\n                reader.read_u8()?; // discard vertex normal\n                let max = read_vertex(&mut reader, scale, origin)?;\n                reader.read_u8()?; // discard vertex normal\n\n                let name = {\n                    let mut bytes: [u8; 16] = [0; 16];\n                    reader.read(&mut bytes)?;\n                    let len = bytes\n                        .iter()\n                        .position(|b| *b == 0)\n                        .ok_or(MdlFileError::KeyframeNameTooLong(bytes))?;\n                    String::from_utf8(bytes[0..(len + 1)].to_vec())?\n                };\n\n                debug!(\"Keyframe name: {}\", name);\n\n                let mut vertices: Vec<Vector3<f32>> = Vec::with_capacity(vertex_count as usize);\n                for _ in 0..vertex_count {\n                    vertices.push(read_vertex(&mut reader, scale, origin)?);\n                    reader.read_u8()?; // discard vertex normal\n                }\n\n                Keyframe::Static(StaticKeyframe {\n                    name,\n                    min,\n                    max,\n                    vertices: vertices.into_boxed_slice(),\n                })\n            }\n\n            1 => {\n                let subframe_count = match reader.read_i32::<LittleEndian>()? {\n                    s if s <= 0 => panic!(\"Invalid subframe count: {}\", s),\n                    s => s,\n                };\n\n                let abs_min = read_vertex(&mut reader, scale, origin)?;\n                reader.read_u8()?; // discard vertex normal\n                let abs_max = read_vertex(&mut reader, scale, origin)?;\n                reader.read_u8()?; // discard vertex normal\n\n                let mut durations = Vec::new();\n                for _ in 0..subframe_count {\n                    durations.push(engine::duration_from_f32(\n                        reader.read_f32::<LittleEndian>()?,\n                    ));\n                }\n\n                let mut subframes = Vec::new();\n                for subframe_id in 0..subframe_count {\n                    let min = read_vertex(&mut reader, scale, origin)?;\n                    reader.read_u8()?; // discard vertex normal\n                    let max = read_vertex(&mut reader, scale, origin)?;\n                    reader.read_u8()?; // discard vertex normal\n\n                    let name = {\n                        let mut bytes: [u8; 16] = [0; 16];\n                        reader.read(&mut bytes)?;\n                        let len = bytes\n                            .iter()\n                            .position(|b| *b == 0)\n                            .ok_or(MdlFileError::KeyframeNameTooLong(bytes))?;\n                        String::from_utf8(bytes[0..(len + 1)].to_vec())?\n                    };\n\n                    debug!(\"Frame name: {}\", name);\n\n                    let mut vertices: Vec<Vector3<f32>> = Vec::with_capacity(vertex_count as usize);\n                    for _ in 0..vertex_count {\n                        vertices.push(read_vertex(&mut reader, scale, origin)?);\n                        reader.read_u8()?; // discard vertex normal\n                    }\n\n                    subframes.push(AnimatedKeyframeFrame {\n                        min,\n                        max,\n                        name,\n                        duration: durations[subframe_id as usize],\n                        vertices: vertices.into_boxed_slice(),\n                    })\n                }\n\n                Keyframe::Animated(AnimatedKeyframe {\n                    min: abs_min,\n                    max: abs_max,\n                    frames: subframes.into_boxed_slice(),\n                })\n            }\n\n            x => panic!(\"Bad frame kind value: {}\", x),\n        });\n    }\n\n    if reader.seek(SeekFrom::Current(0))? != reader.seek(SeekFrom::End(0))? {\n        panic!(\"Misaligned read on MDL file\");\n    }\n\n    Ok(AliasModel {\n        origin,\n        radius,\n        texture_width: texture_width as u32,\n        texture_height: texture_height as u32,\n        textures: textures.into_boxed_slice(),\n        texcoords: texcoords.into_boxed_slice(),\n        polygons: polygons.into_boxed_slice(),\n        keyframes: keyframes.into_boxed_slice(),\n        flags,\n    })\n}\n\nfn read_vertex<R>(\n    reader: &mut R,\n    scale: Vector3<f32>,\n    translate: Vector3<f32>,\n) -> Result<Vector3<f32>, io::Error>\nwhere\n    R: ReadBytesExt,\n{\n    Ok(Vector3::new(\n        reader.read_u8()? as f32,\n        reader.read_u8()? as f32,\n        reader.read_u8()? as f32,\n    )\n    .mul_element_wise(scale)\n        + translate)\n}\n"
  },
  {
    "path": "src/common/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\npub mod alloc;\npub mod bitset;\npub mod bsp;\npub mod console;\npub mod engine;\npub mod host;\npub mod math;\npub mod mdl;\npub mod model;\npub mod net;\npub mod pak;\npub mod parse;\npub mod sprite;\npub mod util;\npub mod vfs;\npub mod wad;\n\nuse std::path::PathBuf;\n\npub fn default_base_dir() -> std::path::PathBuf {\n    match std::env::current_dir() {\n        Ok(cwd) => cwd,\n        Err(e) => {\n            log::error!(\"cannot access current directory: {}\", e);\n            std::process::exit(1);\n        }\n    }\n}\n\npub const MAX_LIGHTSTYLES: usize = 64;\n\n/// The maximum number of `.pak` files that should be loaded at runtime.\n///\n/// The original engine does not make this restriction, and this limit can be increased if need be.\npub const MAX_PAKFILES: usize = 32;\n"
  },
  {
    "path": "src/common/model.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse crate::common::{\n    bsp::{BspFileError, BspModel},\n    mdl::{self, AliasModel, MdlFileError},\n    sprite::{self, SpriteModel},\n    vfs::{Vfs, VfsError},\n};\n\nuse cgmath::Vector3;\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\npub enum ModelError {\n    #[error(\"BSP file error: {0}\")]\n    BspFile(#[from] BspFileError),\n    #[error(\"MDL file error: {0}\")]\n    MdlFile(#[from] MdlFileError),\n    #[error(\"SPR file error\")]\n    SprFile,\n    #[error(\"Virtual filesystem error: {0}\")]\n    Vfs(#[from] VfsError),\n}\n\n#[derive(Debug, FromPrimitive)]\npub enum SyncType {\n    Sync = 0,\n    Rand = 1,\n}\n\nbitflags! {\n    pub struct ModelFlags: u8 {\n        const ROCKET  = 0b00000001;\n        const GRENADE = 0b00000010;\n        const GIB     = 0b00000100;\n        const ROTATE  = 0b00001000;\n        const TRACER  = 0b00010000;\n        const ZOMGIB  = 0b00100000;\n        const TRACER2 = 0b01000000;\n        const TRACER3 = 0b10000000;\n    }\n}\n\n#[derive(Debug)]\npub struct Model {\n    pub name: String,\n    pub kind: ModelKind,\n    pub flags: ModelFlags,\n}\n\n#[derive(Debug)]\npub enum ModelKind {\n    // TODO: find a more elegant way to express the null model\n    None,\n    Brush(BspModel),\n    Alias(AliasModel),\n    Sprite(SpriteModel),\n}\n\nimpl Model {\n    pub fn none() -> Model {\n        Model {\n            name: String::new(),\n            kind: ModelKind::None,\n            flags: ModelFlags::empty(),\n        }\n    }\n\n    pub fn kind(&self) -> &ModelKind {\n        &self.kind\n    }\n\n    pub fn load<S>(vfs: &Vfs, name: S) -> Result<Model, ModelError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n        // TODO: original engine uses the magic numbers of each format instead of the extension.\n        if name.ends_with(\".bsp\") {\n            panic!(\"BSP files may contain multiple models, use bsp::load for this\");\n        } else if name.ends_with(\".mdl\") {\n            Ok(Model::from_alias_model(\n                name.to_owned(),\n                mdl::load(vfs.open(name)?)?,\n            ))\n        } else if name.ends_with(\".spr\") {\n            Ok(Model::from_sprite_model(\n                name.to_owned(),\n                sprite::load(vfs.open(name)?),\n            ))\n        } else {\n            panic!(\"Unrecognized model type: {}\", name);\n        }\n    }\n\n    /// Construct a new generic model from a brush model.\n    pub fn from_brush_model<S>(name: S, brush_model: BspModel) -> Model\n    where\n        S: AsRef<str>,\n    {\n        Model {\n            name: name.as_ref().to_owned(),\n            kind: ModelKind::Brush(brush_model),\n            flags: ModelFlags::empty(),\n        }\n    }\n\n    /// Construct a new generic model from an alias model.\n    pub fn from_alias_model<S>(name: S, alias_model: AliasModel) -> Model\n    where\n        S: AsRef<str>,\n    {\n        let flags = alias_model.flags();\n\n        Model {\n            name: name.as_ref().to_owned(),\n            kind: ModelKind::Alias(alias_model),\n            flags,\n        }\n    }\n\n    /// Construct a new generic model from a sprite model.\n    pub fn from_sprite_model<S>(name: S, sprite_model: SpriteModel) -> Model\n    where\n        S: AsRef<str>,\n    {\n        Model {\n            name: name.as_ref().to_owned(),\n            kind: ModelKind::Sprite(sprite_model),\n            flags: ModelFlags::empty(),\n        }\n    }\n\n    /// Return the name of this model.\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    /// Return the minimum extent of this model.\n    pub fn min(&self) -> Vector3<f32> {\n        debug!(\"Retrieving min of model {}\", self.name);\n        match self.kind {\n            ModelKind::None => panic!(\"attempted to take min() of NULL model\"),\n            ModelKind::Brush(ref bmodel) => bmodel.min(),\n            ModelKind::Sprite(ref smodel) => smodel.min(),\n\n            // TODO: maybe change this?\n            // https://github.com/id-Software/Quake/blob/master/WinQuake/gl_model.c#L1625\n            ModelKind::Alias(_) => Vector3::new(-16.0, -16.0, -16.0),\n        }\n    }\n\n    /// Return the maximum extent of this model.\n    pub fn max(&self) -> Vector3<f32> {\n        debug!(\"Retrieving max of model {}\", self.name);\n        match self.kind {\n            ModelKind::None => panic!(\"attempted to take max() of NULL model\"),\n            ModelKind::Brush(ref bmodel) => bmodel.max(),\n            ModelKind::Sprite(ref smodel) => smodel.max(),\n\n            // TODO: maybe change this?\n            // https://github.com/id-Software/Quake/blob/master/WinQuake/gl_model.c#L1625\n            ModelKind::Alias(_) => Vector3::new(16.0, 16.0, 16.0),\n        }\n    }\n\n    pub fn sync_type(&self) -> SyncType {\n        match self.kind {\n            ModelKind::None => panic!(\"Attempted to take sync_type() of NULL model\"),\n            ModelKind::Brush(_) => SyncType::Sync,\n            // TODO: expose sync_type in Sprite and reflect it here\n            ModelKind::Sprite(ref _smodel) => SyncType::Sync,\n            // TODO: expose sync_type in Mdl and reflect it here\n            ModelKind::Alias(ref _amodel) => SyncType::Sync,\n        }\n    }\n\n    pub fn flags(&self) -> ModelFlags {\n        self.flags\n    }\n\n    pub fn has_flag(&self, flag: ModelFlags) -> bool {\n        self.flags.contains(flag)\n    }\n}\n"
  },
  {
    "path": "src/common/net/connect.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::{\n    io::{BufReader, Cursor, ErrorKind},\n    mem::size_of,\n    net::{SocketAddr, ToSocketAddrs, UdpSocket},\n};\n\nuse crate::common::{\n    net::{NetError, QSocket, MAX_MESSAGE},\n    util,\n};\n\nuse byteorder::{LittleEndian, NetworkEndian, ReadBytesExt, WriteBytesExt};\nuse chrono::Duration;\nuse num::FromPrimitive;\n\npub const CONNECT_PROTOCOL_VERSION: u8 = 3;\nconst CONNECT_CONTROL: i32 = 1 << 31;\nconst CONNECT_LENGTH_MASK: i32 = 0x0000FFFF;\n\npub trait ConnectPacket {\n    /// Returns the numeric value of this packet's code.\n    fn code(&self) -> u8;\n\n    /// Returns the length in bytes of this packet's content.\n    fn content_len(&self) -> usize;\n\n    /// Writes this packet's content to the given sink.\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt;\n\n    /// Returns the total length in bytes of this packet, including the header.\n    fn packet_len(&self) -> i32 {\n        let mut len = 0;\n\n        // control header\n        len += size_of::<i32>();\n\n        // request/reply code\n        len += size_of::<u8>();\n\n        len += self.content_len();\n\n        len as i32\n    }\n\n    /// Generates the control header for this packet.\n    fn control_header(&self) -> i32 {\n        CONNECT_CONTROL | (self.packet_len() & CONNECT_LENGTH_MASK)\n    }\n\n    /// Generates the byte representation of this packet for transmission.\n    fn to_bytes(&self) -> Result<Vec<u8>, NetError> {\n        let mut writer = Cursor::new(Vec::new());\n        writer.write_i32::<NetworkEndian>(self.control_header())?;\n        writer.write_u8(self.code())?;\n        self.write_content(&mut writer)?;\n        let packet = writer.into_inner();\n        Ok(packet)\n    }\n}\n\n#[derive(Debug, FromPrimitive)]\npub enum RequestCode {\n    Connect = 1,\n    ServerInfo = 2,\n    PlayerInfo = 3,\n    RuleInfo = 4,\n}\n\n#[derive(Debug)]\npub struct RequestConnect {\n    pub game_name: String,\n    pub proto_ver: u8,\n}\n\nimpl ConnectPacket for RequestConnect {\n    fn code(&self) -> u8 {\n        RequestCode::Connect as u8\n    }\n\n    fn content_len(&self) -> usize {\n        let mut len = 0;\n\n        // game name and terminating zero byte\n        len += self.game_name.len() + size_of::<u8>();\n\n        // protocol version\n        len += size_of::<u8>();\n\n        len\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write(self.game_name.as_bytes())?;\n        writer.write_u8(0)?;\n        writer.write_u8(self.proto_ver)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct RequestServerInfo {\n    pub game_name: String,\n}\n\nimpl ConnectPacket for RequestServerInfo {\n    fn code(&self) -> u8 {\n        RequestCode::ServerInfo as u8\n    }\n\n    fn content_len(&self) -> usize {\n        // game name and terminating zero byte\n        self.game_name.len() + size_of::<u8>()\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write(self.game_name.as_bytes())?;\n        writer.write_u8(0)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct RequestPlayerInfo {\n    pub player_id: u8,\n}\n\nimpl ConnectPacket for RequestPlayerInfo {\n    fn code(&self) -> u8 {\n        RequestCode::PlayerInfo as u8\n    }\n\n    fn content_len(&self) -> usize {\n        // player id\n        size_of::<u8>()\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write_u8(self.player_id)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct RequestRuleInfo {\n    pub prev_cvar: String,\n}\n\nimpl ConnectPacket for RequestRuleInfo {\n    fn code(&self) -> u8 {\n        RequestCode::RuleInfo as u8\n    }\n\n    fn content_len(&self) -> usize {\n        // previous cvar in rule chain and terminating zero byte\n        self.prev_cvar.len() + size_of::<u8>()\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write(self.prev_cvar.as_bytes())?;\n        writer.write_u8(0)?;\n        Ok(())\n    }\n}\n\n/// A request from a client to retrieve information from or connect to the server.\n#[derive(Debug)]\npub enum Request {\n    Connect(RequestConnect),\n    ServerInfo(RequestServerInfo),\n    PlayerInfo(RequestPlayerInfo),\n    RuleInfo(RequestRuleInfo),\n}\n\nimpl Request {\n    pub fn connect<S>(game_name: S, proto_ver: u8) -> Request\n    where\n        S: AsRef<str>,\n    {\n        Request::Connect(RequestConnect {\n            game_name: game_name.as_ref().to_owned(),\n            proto_ver,\n        })\n    }\n\n    pub fn server_info<S>(game_name: S) -> Request\n    where\n        S: AsRef<str>,\n    {\n        Request::ServerInfo(RequestServerInfo {\n            game_name: game_name.as_ref().to_owned(),\n        })\n    }\n\n    pub fn player_info(player_id: u8) -> Request {\n        Request::PlayerInfo(RequestPlayerInfo { player_id })\n    }\n\n    pub fn rule_info<S>(prev_cvar: S) -> Request\n    where\n        S: AsRef<str>,\n    {\n        Request::RuleInfo(RequestRuleInfo {\n            prev_cvar: prev_cvar.as_ref().to_string(),\n        })\n    }\n}\n\nimpl ConnectPacket for Request {\n    fn code(&self) -> u8 {\n        use self::Request::*;\n        match *self {\n            Connect(ref c) => c.code(),\n            ServerInfo(ref s) => s.code(),\n            PlayerInfo(ref p) => p.code(),\n            RuleInfo(ref r) => r.code(),\n        }\n    }\n\n    fn content_len(&self) -> usize {\n        use self::Request::*;\n        match *self {\n            Connect(ref c) => c.content_len(),\n            ServerInfo(ref s) => s.content_len(),\n            PlayerInfo(ref p) => p.content_len(),\n            RuleInfo(ref r) => r.content_len(),\n        }\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        use self::Request::*;\n        match *self {\n            Connect(ref c) => c.write_content(writer),\n            ServerInfo(ref s) => s.write_content(writer),\n            PlayerInfo(ref p) => p.write_content(writer),\n            RuleInfo(ref r) => r.write_content(writer),\n        }\n    }\n}\n\n#[derive(Debug, FromPrimitive)]\npub enum ResponseCode {\n    Accept = 0x81,\n    Reject = 0x82,\n    ServerInfo = 0x83,\n    PlayerInfo = 0x84,\n    RuleInfo = 0x85,\n}\n\n#[derive(Debug)]\npub struct ResponseAccept {\n    pub port: i32,\n}\n\nimpl ConnectPacket for ResponseAccept {\n    fn code(&self) -> u8 {\n        ResponseCode::Accept as u8\n    }\n\n    fn content_len(&self) -> usize {\n        // port number\n        size_of::<i32>()\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write_i32::<LittleEndian>(self.port)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct ResponseReject {\n    pub message: String,\n}\n\nimpl ConnectPacket for ResponseReject {\n    fn code(&self) -> u8 {\n        ResponseCode::Reject as u8\n    }\n\n    fn content_len(&self) -> usize {\n        // message plus terminating zero byte\n        self.message.len() + size_of::<u8>()\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write(self.message.as_bytes())?;\n        writer.write_u8(0)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct ResponseServerInfo {\n    pub address: String,\n    pub hostname: String,\n    pub levelname: String,\n    pub client_count: u8,\n    pub client_max: u8,\n    pub protocol_version: u8,\n}\n\nimpl ConnectPacket for ResponseServerInfo {\n    fn code(&self) -> u8 {\n        ResponseCode::ServerInfo as u8\n    }\n\n    fn content_len(&self) -> usize {\n        let mut len = 0;\n\n        // address string and terminating zero byte\n        len += self.address.len() + size_of::<u8>();\n\n        // hostname string and terminating zero byte\n        len += self.hostname.len() + size_of::<u8>();\n\n        // levelname string and terminating zero byte\n        len += self.levelname.len() + size_of::<u8>();\n\n        // current client count\n        len += size_of::<u8>();\n\n        // maximum client count\n        len += size_of::<u8>();\n\n        // protocol version\n        len += size_of::<u8>();\n\n        len\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write(self.address.as_bytes())?;\n        writer.write_u8(0)?;\n        writer.write(self.hostname.as_bytes())?;\n        writer.write_u8(0)?;\n        writer.write(self.levelname.as_bytes())?;\n        writer.write_u8(0)?;\n        writer.write_u8(self.client_count)?;\n        writer.write_u8(self.client_max)?;\n        writer.write_u8(self.protocol_version)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct ResponsePlayerInfo {\n    pub player_id: u8,\n    pub player_name: String,\n    pub colors: i32,\n    pub frags: i32,\n    pub connect_duration: i32,\n    pub address: String,\n}\n\nimpl ConnectPacket for ResponsePlayerInfo {\n    fn code(&self) -> u8 {\n        ResponseCode::PlayerInfo as u8\n    }\n\n    fn content_len(&self) -> usize {\n        let mut len = 0;\n\n        // player id\n        len += size_of::<u8>();\n\n        // player name and terminating zero byte\n        len += self.player_name.len() + size_of::<u8>();\n\n        // colors\n        len += size_of::<i32>();\n\n        // frags\n        len += size_of::<i32>();\n\n        // connection duration\n        len += size_of::<i32>();\n\n        // address and terminating zero byte\n        len += self.address.len() + size_of::<u8>();\n\n        len\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write_u8(self.player_id)?;\n        writer.write(self.player_name.as_bytes())?;\n        writer.write_u8(0)?; // NUL-terminate\n        writer.write_i32::<LittleEndian>(self.colors)?;\n        writer.write_i32::<LittleEndian>(self.frags)?;\n        writer.write_i32::<LittleEndian>(self.connect_duration)?;\n        writer.write(self.address.as_bytes())?;\n        writer.write_u8(0)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct ResponseRuleInfo {\n    pub cvar_name: String,\n    pub cvar_val: String,\n}\n\nimpl ConnectPacket for ResponseRuleInfo {\n    fn code(&self) -> u8 {\n        ResponseCode::RuleInfo as u8\n    }\n\n    fn content_len(&self) -> usize {\n        let mut len = 0;\n\n        // cvar name and terminating zero byte\n        len += self.cvar_name.len() + size_of::<u8>();\n\n        // cvar val and terminating zero byte\n        len += self.cvar_val.len() + size_of::<u8>();\n\n        len\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write(self.cvar_name.as_bytes())?;\n        writer.write_u8(0)?;\n        writer.write(self.cvar_val.as_bytes())?;\n        writer.write_u8(0)?;\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub enum Response {\n    Accept(ResponseAccept),\n    Reject(ResponseReject),\n    ServerInfo(ResponseServerInfo),\n    PlayerInfo(ResponsePlayerInfo),\n    RuleInfo(ResponseRuleInfo),\n}\n\nimpl ConnectPacket for Response {\n    fn code(&self) -> u8 {\n        use self::Response::*;\n        match *self {\n            Accept(ref a) => a.code(),\n            Reject(ref r) => r.code(),\n            ServerInfo(ref s) => s.code(),\n            PlayerInfo(ref p) => p.code(),\n            RuleInfo(ref r) => r.code(),\n        }\n    }\n\n    fn content_len(&self) -> usize {\n        use self::Response::*;\n        match *self {\n            Accept(ref a) => a.content_len(),\n            Reject(ref r) => r.content_len(),\n            ServerInfo(ref s) => s.content_len(),\n            PlayerInfo(ref p) => p.content_len(),\n            RuleInfo(ref r) => r.content_len(),\n        }\n    }\n\n    fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        use self::Response::*;\n        match *self {\n            Accept(ref a) => a.write_content(writer),\n            Reject(ref r) => r.write_content(writer),\n            ServerInfo(ref s) => s.write_content(writer),\n            PlayerInfo(ref p) => p.write_content(writer),\n            RuleInfo(ref r) => r.write_content(writer),\n        }\n    }\n}\n\n/// A socket that listens for new connections or queries.\npub struct ConnectListener {\n    socket: UdpSocket,\n}\n\nimpl ConnectListener {\n    /// Creates a `ConnectListener` from the given address.\n    pub fn bind<A>(addr: A) -> Result<ConnectListener, NetError>\n    where\n        A: ToSocketAddrs,\n    {\n        let socket = UdpSocket::bind(addr)?;\n\n        Ok(ConnectListener { socket })\n    }\n\n    /// Receives a request and returns it along with its remote address.\n    pub fn recv_request(&self) -> Result<(Request, SocketAddr), NetError> {\n        // Original engine receives connection requests in `net_message`,\n        // allocated at https://github.com/id-Software/Quake/blob/master/WinQuake/net_main.c#L851\n        let mut recv_buf = [0u8; MAX_MESSAGE];\n        let (len, remote) = self.socket.recv_from(&mut recv_buf)?;\n        let mut reader = BufReader::new(&recv_buf[..len]);\n\n        let control = reader.read_i32::<NetworkEndian>()?;\n\n        // TODO: figure out what a control value of -1 means\n        if control == -1 {\n            return Err(NetError::with_msg(\"Control value is -1\"));\n        }\n\n        // high 4 bits must be 0x8000 (CONNECT_CONTROL)\n        if control & !CONNECT_LENGTH_MASK != CONNECT_CONTROL {\n            return Err(NetError::InvalidData(format!(\n                \"control value {:X}\",\n                control & !CONNECT_LENGTH_MASK\n            )));\n        }\n\n        // low 4 bits must be total length of packet\n        let control_len = (control & CONNECT_LENGTH_MASK) as usize;\n        if control_len != len {\n            return Err(NetError::InvalidData(format!(\n                \"Actual packet length ({}) differs from header value ({})\",\n                len, control_len,\n            )));\n        }\n\n        // validate request code\n        let request_byte = reader.read_u8()?;\n        let request_code = match RequestCode::from_u8(request_byte) {\n            Some(r) => r,\n            None => {\n                return Err(NetError::InvalidData(format!(\n                    \"request code {}\",\n                    request_byte\n                )))\n            }\n        };\n\n        let request = match request_code {\n            RequestCode::Connect => {\n                let game_name = util::read_cstring(&mut reader).unwrap();\n                let proto_ver = reader.read_u8()?;\n                Request::Connect(RequestConnect {\n                    game_name,\n                    proto_ver,\n                })\n            }\n\n            RequestCode::ServerInfo => {\n                let game_name = util::read_cstring(&mut reader).unwrap();\n                Request::ServerInfo(RequestServerInfo { game_name })\n            }\n\n            RequestCode::PlayerInfo => {\n                let player_id = reader.read_u8()?;\n                Request::PlayerInfo(RequestPlayerInfo { player_id })\n            }\n\n            RequestCode::RuleInfo => {\n                let prev_cvar = util::read_cstring(&mut reader).unwrap();\n                Request::RuleInfo(RequestRuleInfo { prev_cvar })\n            }\n        };\n\n        Ok((request, remote))\n    }\n\n    pub fn send_response(&self, response: Response, remote: SocketAddr) -> Result<(), NetError> {\n        self.socket.send_to(&response.to_bytes()?, remote)?;\n        Ok(())\n    }\n}\n\npub struct ConnectSocket {\n    socket: UdpSocket,\n}\n\nimpl ConnectSocket {\n    pub fn bind<A>(local: A) -> Result<ConnectSocket, NetError>\n    where\n        A: ToSocketAddrs,\n    {\n        let socket = UdpSocket::bind(local)?;\n\n        Ok(ConnectSocket { socket })\n    }\n\n    pub fn into_qsocket(self, remote: SocketAddr) -> QSocket {\n        QSocket::new(self.socket, remote)\n    }\n\n    /// Send a `Request` to the server at the specified address.\n    pub fn send_request(&mut self, request: Request, remote: SocketAddr) -> Result<(), NetError> {\n        self.socket.send_to(&request.to_bytes()?, remote)?;\n        Ok(())\n    }\n\n    /// Receive a `Response` from the server.\n    ///\n    /// If `timeout` is not `None`, the operation times out after the specified duration and the\n    /// function returns `None`.\n    pub fn recv_response(\n        &mut self,\n        timeout: Option<Duration>,\n    ) -> Result<Option<(Response, SocketAddr)>, NetError> {\n        let mut recv_buf = [0u8; MAX_MESSAGE];\n\n        // if a timeout was specified, apply it for this recv\n        self.socket\n            .set_read_timeout(timeout.map(|d| d.to_std().unwrap()))?;\n        let (len, remote) = match self.socket.recv_from(&mut recv_buf) {\n            Err(e) => match e.kind() {\n                ErrorKind::WouldBlock | ErrorKind::TimedOut => return Ok(None),\n                _ => return Err(NetError::from(e)),\n            },\n            Ok(ret) => ret,\n        };\n        self.socket.set_read_timeout(None)?;\n\n        let mut reader = BufReader::new(&recv_buf[..len]);\n\n        let control = reader.read_i32::<NetworkEndian>()?;\n\n        // TODO: figure out what a control value of -1 means\n        if control == -1 {\n            return Err(NetError::with_msg(\"Control value is -1\"));\n        }\n\n        // high 4 bits must be 0x8000 (CONNECT_CONTROL)\n        if control & !CONNECT_LENGTH_MASK != CONNECT_CONTROL {\n            return Err(NetError::InvalidData(format!(\n                \"control value {:X}\",\n                control & !CONNECT_LENGTH_MASK\n            )));\n        }\n\n        // low 4 bits must be total length of packet\n        let control_len = (control & CONNECT_LENGTH_MASK) as usize;\n        if control_len != len {\n            return Err(NetError::with_msg(format!(\n                \"Actual packet length ({}) differs from header value ({})\",\n                len, control_len,\n            )));\n        }\n\n        let response_byte = reader.read_u8()?;\n        let response_code = match ResponseCode::from_u8(response_byte) {\n            Some(r) => r,\n            None => {\n                return Err(NetError::InvalidData(format!(\n                    \"response code {}\",\n                    response_byte\n                )))\n            }\n        };\n\n        let response = match response_code {\n            ResponseCode::Accept => {\n                let port = reader.read_i32::<LittleEndian>()?;\n                Response::Accept(ResponseAccept { port })\n            }\n\n            ResponseCode::Reject => {\n                let message = util::read_cstring(&mut reader).unwrap();\n                Response::Reject(ResponseReject { message })\n            }\n\n            ResponseCode::ServerInfo => {\n                let address = util::read_cstring(&mut reader).unwrap();\n                let hostname = util::read_cstring(&mut reader).unwrap();\n                let levelname = util::read_cstring(&mut reader).unwrap();\n                let client_count = reader.read_u8()?;\n                let client_max = reader.read_u8()?;\n                let protocol_version = reader.read_u8()?;\n\n                Response::ServerInfo(ResponseServerInfo {\n                    address,\n                    hostname,\n                    levelname,\n                    client_count,\n                    client_max,\n                    protocol_version,\n                })\n            }\n\n            ResponseCode::PlayerInfo => unimplemented!(),\n            ResponseCode::RuleInfo => unimplemented!(),\n        };\n\n        Ok(Some((response, remote)))\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    // test_request_*_packet_len\n    //\n    // These tests ensure that ConnectPacket::packet_len() returns an accurate value by comparing it\n    // with the number of bytes returned by ConnectPacket::to_bytes().\n    #[test]\n    fn test_request_connect_packet_len() {\n        let request_connect = RequestConnect {\n            game_name: String::from(\"QUAKE\"),\n            proto_ver: CONNECT_PROTOCOL_VERSION,\n        };\n\n        let packet_len = request_connect.packet_len() as usize;\n        let packet = request_connect.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_request_server_info_packet_len() {\n        let request_server_info = RequestServerInfo {\n            game_name: String::from(\"QUAKE\"),\n        };\n        let packet_len = request_server_info.packet_len() as usize;\n        let packet = request_server_info.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_request_player_info_packet_len() {\n        let request_player_info = RequestPlayerInfo { player_id: 0 };\n        let packet_len = request_player_info.packet_len() as usize;\n        let packet = request_player_info.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_request_rule_info_packet_len() {\n        let request_rule_info = RequestRuleInfo {\n            prev_cvar: String::from(\"sv_gravity\"),\n        };\n        let packet_len = request_rule_info.packet_len() as usize;\n        let packet = request_rule_info.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_response_accept_packet_len() {\n        let response_accept = ResponseAccept { port: 26000 };\n        let packet_len = response_accept.packet_len() as usize;\n        let packet = response_accept.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_response_reject_packet_len() {\n        let response_reject = ResponseReject {\n            message: String::from(\"error\"),\n        };\n        let packet_len = response_reject.packet_len() as usize;\n        let packet = response_reject.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_response_server_info_packet_len() {\n        let response_server_info = ResponseServerInfo {\n            address: String::from(\"127.0.0.1\"),\n            hostname: String::from(\"localhost\"),\n            levelname: String::from(\"e1m1\"),\n            client_count: 1,\n            client_max: 16,\n            protocol_version: 15,\n        };\n        let packet_len = response_server_info.packet_len() as usize;\n        let packet = response_server_info.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_response_player_info_packet_len() {\n        let response_player_info = ResponsePlayerInfo {\n            player_id: 0,\n            player_name: String::from(\"player\"),\n            colors: 0,\n            frags: 0,\n            connect_duration: 120,\n            address: String::from(\"127.0.0.1\"),\n        };\n        let packet_len = response_player_info.packet_len() as usize;\n        let packet = response_player_info.to_bytes().unwrap();\n        assert_eq!(packet_len, packet.len());\n    }\n\n    #[test]\n    fn test_connect_listener_bind() {\n        let _listener = ConnectListener::bind(\"127.0.0.1:26000\").unwrap();\n    }\n}\n"
  },
  {
    "path": "src/common/net/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// TODO: need to figure out an equivalence relation for read_/write_coord and read_/write_angle\n\npub mod connect;\n\nuse std::{\n    collections::VecDeque,\n    error::Error,\n    fmt,\n    io::{BufRead, BufReader, Cursor, Read, Write},\n    net::{SocketAddr, UdpSocket},\n};\n\nuse crate::common::{engine, util};\n\nuse byteorder::{LittleEndian, NetworkEndian, ReadBytesExt, WriteBytesExt};\nuse cgmath::{Deg, Vector3, Zero};\nuse chrono::Duration;\nuse num::FromPrimitive;\n\npub const MAX_MESSAGE: usize = 8192;\nconst MAX_DATAGRAM: usize = 1024;\nconst HEADER_SIZE: usize = 8;\nconst MAX_PACKET: usize = HEADER_SIZE + MAX_DATAGRAM;\n\npub const PROTOCOL_VERSION: u8 = 15;\n\nconst NAME_LEN: usize = 64;\n\nconst FAST_UPDATE_FLAG: u8 = 0x80;\n\nconst VELOCITY_READ_FACTOR: f32 = 16.0;\nconst VELOCITY_WRITE_FACTOR: f32 = 1.0 / VELOCITY_READ_FACTOR;\n\nconst PARTICLE_DIRECTION_READ_FACTOR: f32 = 1.0 / 16.0;\nconst PARTICLE_DIRECTION_WRITE_FACTOR: f32 = 1.0 / PARTICLE_DIRECTION_READ_FACTOR;\n\nconst SOUND_ATTENUATION_WRITE_FACTOR: u8 = 64;\nconst SOUND_ATTENUATION_READ_FACTOR: f32 = 1.0 / SOUND_ATTENUATION_WRITE_FACTOR as f32;\n\npub static GAME_NAME: &'static str = \"QUAKE\";\npub const MAX_CLIENTS: usize = 16;\npub const MAX_ITEMS: usize = 32;\n\npub const DEFAULT_VIEWHEIGHT: f32 = 22.0;\n\n#[derive(Debug)]\npub enum NetError {\n    Io(::std::io::Error),\n    InvalidData(String),\n    Other(String),\n}\n\nimpl NetError {\n    pub fn with_msg<S>(msg: S) -> Self\n    where\n        S: AsRef<str>,\n    {\n        NetError::Other(msg.as_ref().to_owned())\n    }\n}\n\nimpl fmt::Display for NetError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match *self {\n            NetError::Io(ref err) => {\n                write!(f, \"I/O error: \")?;\n                err.fmt(f)\n            }\n            NetError::InvalidData(ref msg) => write!(f, \"Invalid data: {}\", msg),\n            NetError::Other(ref msg) => write!(f, \"{}\", msg),\n        }\n    }\n}\n\nimpl Error for NetError {\n    fn description(&self) -> &str {\n        match *self {\n            NetError::Io(ref err) => err.description(),\n            NetError::InvalidData(_) => \"Invalid data\",\n            NetError::Other(ref msg) => &msg,\n        }\n    }\n}\n\nimpl From<::std::io::Error> for NetError {\n    fn from(error: ::std::io::Error) -> Self {\n        NetError::Io(error)\n    }\n}\n\n// the original engine treats these as bitflags, but all of them are mutually exclusive except for\n// NETFLAG_DATA (reliable message) and NETFLAG_EOM (end of reliable message).\n#[derive(Debug, Eq, FromPrimitive, PartialEq)]\npub enum MsgKind {\n    Reliable = 0x0001,\n    Ack = 0x0002,\n    ReliableEom = 0x0009,\n    Unreliable = 0x0010,\n    Ctl = 0x8000,\n}\n\nbitflags! {\n    pub struct UpdateFlags: u16 {\n        const MORE_BITS = 1 << 0;\n        const ORIGIN_X = 1 << 1;\n        const ORIGIN_Y = 1 << 2;\n        const ORIGIN_Z = 1 << 3;\n        const YAW = 1 << 4;\n        const NO_LERP = 1 << 5;\n        const FRAME = 1 << 6;\n        const SIGNAL = 1 << 7;\n        const PITCH = 1 << 8;\n        const ROLL = 1 << 9;\n        const MODEL = 1 << 10;\n        const COLORMAP = 1 << 11;\n        const SKIN = 1 << 12;\n        const EFFECTS = 1 << 13;\n        const LONG_ENTITY = 1 << 14;\n    }\n}\n\nbitflags! {\n    pub struct ClientUpdateFlags: u16 {\n        const VIEW_HEIGHT = 1 << 0;\n        const IDEAL_PITCH = 1 << 1;\n        const PUNCH_PITCH = 1 << 2;\n        const PUNCH_YAW = 1 << 3;\n        const PUNCH_ROLL = 1 << 4;\n        const VELOCITY_X = 1 << 5;\n        const VELOCITY_Y = 1 << 6;\n        const VELOCITY_Z = 1 << 7;\n        // const AIM_ENT = 1 << 8; // unused\n        const ITEMS = 1 << 9;\n        const ON_GROUND = 1 << 10;\n        const IN_WATER = 1 << 11;\n        const WEAPON_FRAME = 1 << 12;\n        const ARMOR = 1 << 13;\n        const WEAPON = 1 << 14;\n    }\n}\n\nbitflags! {\n    pub struct SoundFlags: u8 {\n        const VOLUME = 1 << 0;\n        const ATTENUATION = 1 << 1;\n        const LOOPING = 1 << 2;\n    }\n}\n\nbitflags! {\n    pub struct ItemFlags: u32 {\n        const SHOTGUN          = 0x00000001;\n        const SUPER_SHOTGUN    = 0x00000002;\n        const NAILGUN          = 0x00000004;\n        const SUPER_NAILGUN    = 0x00000008;\n        const GRENADE_LAUNCHER = 0x00000010;\n        const ROCKET_LAUNCHER  = 0x00000020;\n        const LIGHTNING        = 0x00000040;\n        const SUPER_LIGHTNING  = 0x00000080;\n        const SHELLS           = 0x00000100;\n        const NAILS            = 0x00000200;\n        const ROCKETS          = 0x00000400;\n        const CELLS            = 0x00000800;\n        const AXE              = 0x00001000;\n        const ARMOR_1          = 0x00002000;\n        const ARMOR_2          = 0x00004000;\n        const ARMOR_3          = 0x00008000;\n        const SUPER_HEALTH     = 0x00010000;\n        const KEY_1            = 0x00020000;\n        const KEY_2            = 0x00040000;\n        const INVISIBILITY     = 0x00080000;\n        const INVULNERABILITY  = 0x00100000;\n        const SUIT             = 0x00200000;\n        const QUAD             = 0x00400000;\n        const SIGIL_1          = 0x10000000;\n        const SIGIL_2          = 0x20000000;\n        const SIGIL_3          = 0x40000000;\n        const SIGIL_4          = 0x80000000;\n    }\n}\n\nbitflags! {\n    pub struct ButtonFlags: u8 {\n        const ATTACK = 0x01;\n        const JUMP = 0x02;\n    }\n}\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub struct PlayerColor {\n    top: u8,\n    bottom: u8,\n}\n\nimpl PlayerColor {\n    pub fn new(top: u8, bottom: u8) -> PlayerColor {\n        if top > 15 {\n            warn!(\"Top color index ({}) will be truncated\", top);\n        }\n\n        if bottom > 15 {\n            warn!(\"Bottom color index ({}) will be truncated\", bottom);\n        }\n\n        PlayerColor { top, bottom }\n    }\n\n    pub fn from_bits(bits: u8) -> PlayerColor {\n        let top = bits >> 4;\n        let bottom = bits & 0x0F;\n\n        PlayerColor { top, bottom }\n    }\n\n    pub fn bits(&self) -> u8 {\n        self.top << 4 | (self.bottom & 0x0F)\n    }\n}\n\nimpl ::std::convert::From<u8> for PlayerColor {\n    fn from(src: u8) -> PlayerColor {\n        PlayerColor {\n            top: src << 4,\n            bottom: src & 0x0F,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct ColorShift {\n    pub dest_color: [u8; 3],\n    pub percent: i32,\n}\n\n#[derive(Copy, Clone, Debug, Eq, FromPrimitive, PartialEq)]\npub enum ClientStat {\n    Health = 0,\n    Frags = 1,\n    Weapon = 2,\n    Ammo = 3,\n    Armor = 4,\n    WeaponFrame = 5,\n    Shells = 6,\n    Nails = 7,\n    Rockets = 8,\n    Cells = 9,\n    ActiveWeapon = 10,\n    TotalSecrets = 11,\n    TotalMonsters = 12,\n    FoundSecrets = 13,\n    KilledMonsters = 14,\n}\n\n/// Numeric codes used to identify the type of a temporary entity.\n#[derive(Debug, Eq, FromPrimitive, PartialEq)]\npub enum TempEntityCode {\n    Spike = 0,\n    SuperSpike = 1,\n    Gunshot = 2,\n    Explosion = 3,\n    TarExplosion = 4,\n    Lightning1 = 5,\n    Lightning2 = 6,\n    WizSpike = 7,\n    KnightSpike = 8,\n    Lightning3 = 9,\n    LavaSplash = 10,\n    Teleport = 11,\n    ColorExplosion = 12,\n    Grapple = 13,\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum PointEntityKind {\n    Spike,\n    SuperSpike,\n    Gunshot,\n    Explosion,\n    ColorExplosion { color_start: u8, color_len: u8 },\n    TarExplosion,\n    WizSpike,\n    KnightSpike,\n    LavaSplash,\n    Teleport,\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum BeamEntityKind {\n    /// Lightning bolt\n    Lightning {\n        /// id of the lightning model to use. must be 1, 2, or 3.\n        model_id: u8,\n    },\n    /// Grappling hook cable\n    Grapple,\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub enum TempEntity {\n    Point {\n        kind: PointEntityKind,\n        origin: Vector3<f32>,\n    },\n    Beam {\n        kind: BeamEntityKind,\n        entity_id: i16,\n        start: Vector3<f32>,\n        end: Vector3<f32>,\n    },\n}\n\nimpl TempEntity {\n    pub fn read_temp_entity<R>(reader: &mut R) -> Result<TempEntity, NetError>\n    where\n        R: BufRead + ReadBytesExt,\n    {\n        let code_byte = reader.read_u8()?;\n        let code = match TempEntityCode::from_u8(code_byte) {\n            Some(c) => c,\n            None => {\n                return Err(NetError::InvalidData(format!(\n                    \"Temp entity code {}\",\n                    code_byte\n                )))\n            }\n        };\n\n        use TempEntity::*;\n        use TempEntityCode as Code;\n\n        Ok(match code {\n            Code::Spike\n            | Code::SuperSpike\n            | Code::Gunshot\n            | Code::Explosion\n            | Code::TarExplosion\n            | Code::WizSpike\n            | Code::KnightSpike\n            | Code::LavaSplash\n            | Code::Teleport => Point {\n                kind: match code {\n                    Code::Spike => PointEntityKind::Spike,\n                    Code::SuperSpike => PointEntityKind::SuperSpike,\n                    Code::Gunshot => PointEntityKind::Gunshot,\n                    Code::Explosion => PointEntityKind::Explosion,\n                    Code::TarExplosion => PointEntityKind::TarExplosion,\n                    Code::WizSpike => PointEntityKind::WizSpike,\n                    Code::KnightSpike => PointEntityKind::KnightSpike,\n                    Code::LavaSplash => PointEntityKind::LavaSplash,\n                    Code::Teleport => PointEntityKind::Teleport,\n                    _ => unreachable!(),\n                },\n                origin: read_coord_vector3(reader)?,\n            },\n            Code::ColorExplosion => {\n                let origin = read_coord_vector3(reader)?;\n                let color_start = reader.read_u8()?;\n                let color_len = reader.read_u8()?;\n\n                Point {\n                    origin,\n                    kind: PointEntityKind::ColorExplosion {\n                        color_start,\n                        color_len,\n                    },\n                }\n            }\n            Code::Lightning1 | Code::Lightning2 | Code::Lightning3 => Beam {\n                kind: BeamEntityKind::Lightning {\n                    model_id: match code {\n                        Code::Lightning1 => 1,\n                        Code::Lightning2 => 2,\n                        Code::Lightning3 => 3,\n                        _ => unreachable!(),\n                    },\n                },\n                entity_id: reader.read_i16::<LittleEndian>()?,\n                start: read_coord_vector3(reader)?,\n                end: read_coord_vector3(reader)?,\n            },\n            Code::Grapple => Beam {\n                kind: BeamEntityKind::Grapple,\n                entity_id: reader.read_i16::<LittleEndian>()?,\n                start: read_coord_vector3(reader)?,\n                end: read_coord_vector3(reader)?,\n            },\n        })\n    }\n\n    pub fn write_temp_entity<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        use TempEntityCode as Code;\n\n        match *self {\n            TempEntity::Point { kind, origin } => {\n                use PointEntityKind as Pk;\n                match kind {\n                    Pk::Spike\n                    | Pk::SuperSpike\n                    | Pk::Gunshot\n                    | Pk::Explosion\n                    | Pk::TarExplosion\n                    | Pk::WizSpike\n                    | Pk::KnightSpike\n                    | Pk::LavaSplash\n                    | Pk::Teleport => {\n                        let code = match kind {\n                            Pk::Spike => Code::Spike,\n                            Pk::SuperSpike => Code::SuperSpike,\n                            Pk::Gunshot => Code::Gunshot,\n                            Pk::Explosion => Code::Explosion,\n                            Pk::TarExplosion => Code::TarExplosion,\n                            Pk::WizSpike => Code::WizSpike,\n                            Pk::KnightSpike => Code::KnightSpike,\n                            Pk::LavaSplash => Code::LavaSplash,\n                            Pk::Teleport => Code::Teleport,\n                            _ => unreachable!(),\n                        };\n\n                        // write code\n                        writer.write_u8(code as u8)?;\n                    }\n                    PointEntityKind::ColorExplosion {\n                        color_start,\n                        color_len,\n                    } => {\n                        // write code and colors\n                        writer.write_u8(Code::ColorExplosion as u8)?;\n                        writer.write_u8(color_start)?;\n                        writer.write_u8(color_len)?;\n                    }\n                };\n\n                write_coord_vector3(writer, origin)?;\n            }\n\n            TempEntity::Beam {\n                kind,\n                entity_id,\n                start,\n                end,\n            } => {\n                let code = match kind {\n                    BeamEntityKind::Lightning { model_id } => match model_id {\n                        1 => Code::Lightning1,\n                        2 => Code::Lightning2,\n                        3 => Code::Lightning3,\n                        // TODO: error\n                        _ => panic!(\"invalid lightning model id: {}\", model_id),\n                    },\n                    BeamEntityKind::Grapple => Code::Grapple,\n                };\n                writer.write_i16::<LittleEndian>(entity_id)?;\n                writer.write_u8(code as u8)?;\n                write_coord_vector3(writer, start)?;\n                write_coord_vector3(writer, end)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Copy, Clone, Ord, Debug, Eq, FromPrimitive, PartialOrd, PartialEq)]\npub enum SignOnStage {\n    Not = 0,\n    Prespawn = 1,\n    ClientInfo = 2,\n    Begin = 3,\n    Done = 4,\n}\n\nbitflags! {\n    pub struct EntityEffects: u8 {\n        const BRIGHT_FIELD = 0b0001;\n        const MUZZLE_FLASH = 0b0010;\n        const BRIGHT_LIGHT = 0b0100;\n        const DIM_LIGHT    = 0b1000;\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct EntityState {\n    pub origin: Vector3<f32>,\n    pub angles: Vector3<Deg<f32>>,\n    pub model_id: usize,\n    pub frame_id: usize,\n\n    // TODO: more specific types for these\n    pub colormap: u8,\n    pub skin_id: usize,\n    pub effects: EntityEffects,\n}\n\nimpl EntityState {\n    pub fn uninitialized() -> EntityState {\n        EntityState {\n            origin: Vector3::new(0.0, 0.0, 0.0),\n            angles: Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),\n            model_id: 0,\n            frame_id: 0,\n            colormap: 0,\n            skin_id: 0,\n            effects: EntityEffects::empty(),\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub struct EntityUpdate {\n    pub ent_id: u16,\n    pub model_id: Option<u8>,\n    pub frame_id: Option<u8>,\n    pub colormap: Option<u8>,\n    pub skin_id: Option<u8>,\n    pub effects: Option<EntityEffects>,\n    pub origin_x: Option<f32>,\n    pub pitch: Option<Deg<f32>>,\n    pub origin_y: Option<f32>,\n    pub yaw: Option<Deg<f32>>,\n    pub origin_z: Option<f32>,\n    pub roll: Option<Deg<f32>>,\n    pub no_lerp: bool,\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub struct PlayerData {\n    pub view_height: Option<f32>,\n    pub ideal_pitch: Option<Deg<f32>>,\n    pub punch_pitch: Option<Deg<f32>>,\n    pub velocity_x: Option<f32>,\n    pub punch_yaw: Option<Deg<f32>>,\n    pub velocity_y: Option<f32>,\n    pub punch_roll: Option<Deg<f32>>,\n    pub velocity_z: Option<f32>,\n    pub items: ItemFlags,\n    pub on_ground: bool,\n    pub in_water: bool,\n    pub weapon_frame: Option<u8>,\n    pub armor: Option<u8>,\n    pub weapon: Option<u8>,\n    pub health: i16,\n    pub ammo: u8,\n    pub ammo_shells: u8,\n    pub ammo_nails: u8,\n    pub ammo_rockets: u8,\n    pub ammo_cells: u8,\n    pub active_weapon: u8,\n}\n\nimpl EntityUpdate {\n    /// Create an `EntityState` from this update, filling in any `None` values\n    /// from the specified baseline state.\n    pub fn to_entity_state(&self, baseline: &EntityState) -> EntityState {\n        EntityState {\n            origin: Vector3::new(\n                self.origin_x.unwrap_or(baseline.origin.x),\n                self.origin_y.unwrap_or(baseline.origin.y),\n                self.origin_z.unwrap_or(baseline.origin.z),\n            ),\n            angles: Vector3::new(\n                self.pitch.unwrap_or(baseline.angles[0]),\n                self.yaw.unwrap_or(baseline.angles[1]),\n                self.roll.unwrap_or(baseline.angles[2]),\n            ),\n            model_id: self.model_id.map_or(baseline.model_id, |m| m as usize),\n            frame_id: self.frame_id.map_or(baseline.frame_id, |f| f as usize),\n            skin_id: self.skin_id.map_or(baseline.skin_id, |s| s as usize),\n            effects: self.effects.unwrap_or(baseline.effects),\n            colormap: self.colormap.unwrap_or(baseline.colormap),\n        }\n    }\n}\n\n/// A trait for in-game server and client network commands.\npub trait Cmd: Sized {\n    /// Returns the numeric value of this command's code.\n    fn code(&self) -> u8;\n\n    /// Reads data from the given source and constructs a command object.\n    fn deserialize<R>(reader: &mut R) -> Result<Self, NetError>\n    where\n        R: BufRead + ReadBytesExt;\n\n    /// Writes this command's content to the given sink.\n    fn serialize<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt;\n}\n\n// TODO: use feature(arbitrary_enum_discriminant)\n#[derive(Debug, FromPrimitive)]\npub enum ServerCmdCode {\n    Bad = 0,\n    NoOp = 1,\n    Disconnect = 2,\n    UpdateStat = 3,\n    Version = 4,\n    SetView = 5,\n    Sound = 6,\n    Time = 7,\n    Print = 8,\n    StuffText = 9,\n    SetAngle = 10,\n    ServerInfo = 11,\n    LightStyle = 12,\n    UpdateName = 13,\n    UpdateFrags = 14,\n    PlayerData = 15,\n    StopSound = 16,\n    UpdateColors = 17,\n    Particle = 18,\n    Damage = 19,\n    SpawnStatic = 20,\n    // SpawnBinary = 21, // unused\n    SpawnBaseline = 22,\n    TempEntity = 23,\n    SetPause = 24,\n    SignOnStage = 25,\n    CenterPrint = 26,\n    KilledMonster = 27,\n    FoundSecret = 28,\n    SpawnStaticSound = 29,\n    Intermission = 30,\n    Finale = 31,\n    CdTrack = 32,\n    SellScreen = 33,\n    Cutscene = 34,\n}\n\n#[derive(Copy, Clone, Debug, Eq, FromPrimitive, PartialEq)]\npub enum GameType {\n    CoOp = 0,\n    Deathmatch = 1,\n}\n\n#[derive(Debug, PartialEq)]\npub enum ServerCmd {\n    Bad,\n    NoOp,\n    Disconnect,\n    UpdateStat {\n        stat: ClientStat,\n        value: i32,\n    },\n    Version {\n        version: i32,\n    },\n    SetView {\n        ent_id: i16,\n    },\n    Sound {\n        volume: Option<u8>,\n        attenuation: Option<f32>,\n        entity_id: u16,\n        channel: i8,\n        sound_id: u8,\n        position: Vector3<f32>,\n    },\n    Time {\n        time: f32,\n    },\n    Print {\n        text: String,\n    },\n    StuffText {\n        text: String,\n    },\n    SetAngle {\n        angles: Vector3<Deg<f32>>,\n    },\n    ServerInfo {\n        protocol_version: i32,\n        max_clients: u8,\n        game_type: GameType,\n        message: String,\n        model_precache: Vec<String>,\n        sound_precache: Vec<String>,\n    },\n    LightStyle {\n        id: u8,\n        value: String,\n    },\n    UpdateName {\n        player_id: u8,\n        new_name: String,\n    },\n    UpdateFrags {\n        player_id: u8,\n        new_frags: i16,\n    },\n    PlayerData(PlayerData),\n    StopSound {\n        entity_id: u16,\n        channel: u8,\n    },\n    UpdateColors {\n        player_id: u8,\n        new_colors: PlayerColor,\n    },\n    Particle {\n        origin: Vector3<f32>,\n        direction: Vector3<f32>,\n        count: u8,\n        color: u8,\n    },\n    Damage {\n        armor: u8,\n        blood: u8,\n        source: Vector3<f32>,\n    },\n    SpawnStatic {\n        model_id: u8,\n        frame_id: u8,\n        colormap: u8,\n        skin_id: u8,\n        origin: Vector3<f32>,\n        angles: Vector3<Deg<f32>>,\n    },\n    // SpawnBinary, // unused\n    SpawnBaseline {\n        ent_id: u16,\n        model_id: u8,\n        frame_id: u8,\n        colormap: u8,\n        skin_id: u8,\n        origin: Vector3<f32>,\n        angles: Vector3<Deg<f32>>,\n    },\n    TempEntity {\n        temp_entity: TempEntity,\n    },\n    SetPause {\n        paused: bool,\n    },\n    SignOnStage {\n        stage: SignOnStage,\n    },\n    CenterPrint {\n        text: String,\n    },\n    KilledMonster,\n    FoundSecret,\n    SpawnStaticSound {\n        origin: Vector3<f32>,\n        sound_id: u8,\n        volume: u8,\n        attenuation: u8,\n    },\n    Intermission,\n    Finale {\n        text: String,\n    },\n    CdTrack {\n        track: u8,\n        loop_: u8,\n    },\n    SellScreen,\n    Cutscene {\n        text: String,\n    },\n    FastUpdate(EntityUpdate),\n}\n\nimpl ServerCmd {\n    pub fn code(&self) -> u8 {\n        let code = match *self {\n            ServerCmd::Bad => ServerCmdCode::Bad,\n            ServerCmd::NoOp => ServerCmdCode::NoOp,\n            ServerCmd::Disconnect => ServerCmdCode::Disconnect,\n            ServerCmd::UpdateStat { .. } => ServerCmdCode::UpdateStat,\n            ServerCmd::Version { .. } => ServerCmdCode::Version,\n            ServerCmd::SetView { .. } => ServerCmdCode::SetView,\n            ServerCmd::Sound { .. } => ServerCmdCode::Sound,\n            ServerCmd::Time { .. } => ServerCmdCode::Time,\n            ServerCmd::Print { .. } => ServerCmdCode::Print,\n            ServerCmd::StuffText { .. } => ServerCmdCode::StuffText,\n            ServerCmd::SetAngle { .. } => ServerCmdCode::SetAngle,\n            ServerCmd::ServerInfo { .. } => ServerCmdCode::ServerInfo,\n            ServerCmd::LightStyle { .. } => ServerCmdCode::LightStyle,\n            ServerCmd::UpdateName { .. } => ServerCmdCode::UpdateName,\n            ServerCmd::UpdateFrags { .. } => ServerCmdCode::UpdateFrags,\n            ServerCmd::PlayerData(_) => ServerCmdCode::PlayerData,\n            ServerCmd::StopSound { .. } => ServerCmdCode::StopSound,\n            ServerCmd::UpdateColors { .. } => ServerCmdCode::UpdateColors,\n            ServerCmd::Particle { .. } => ServerCmdCode::Particle,\n            ServerCmd::Damage { .. } => ServerCmdCode::Damage,\n            ServerCmd::SpawnStatic { .. } => ServerCmdCode::SpawnStatic,\n            ServerCmd::SpawnBaseline { .. } => ServerCmdCode::SpawnBaseline,\n            ServerCmd::TempEntity { .. } => ServerCmdCode::TempEntity,\n            ServerCmd::SetPause { .. } => ServerCmdCode::SetPause,\n            ServerCmd::SignOnStage { .. } => ServerCmdCode::SignOnStage,\n            ServerCmd::CenterPrint { .. } => ServerCmdCode::CenterPrint,\n            ServerCmd::KilledMonster => ServerCmdCode::KilledMonster,\n            ServerCmd::FoundSecret => ServerCmdCode::FoundSecret,\n            ServerCmd::SpawnStaticSound { .. } => ServerCmdCode::SpawnStaticSound,\n            ServerCmd::Intermission => ServerCmdCode::Intermission,\n            ServerCmd::Finale { .. } => ServerCmdCode::Finale,\n            ServerCmd::CdTrack { .. } => ServerCmdCode::CdTrack,\n            ServerCmd::SellScreen => ServerCmdCode::SellScreen,\n            ServerCmd::Cutscene { .. } => ServerCmdCode::Cutscene,\n            // TODO: figure out a more elegant way of doing this\n            ServerCmd::FastUpdate(_) => panic!(\"FastUpdate has no code\"),\n        };\n\n        code as u8\n    }\n\n    pub fn deserialize<R>(reader: &mut R) -> Result<Option<ServerCmd>, NetError>\n    where\n        R: BufRead + ReadBytesExt,\n    {\n        let code_num = match reader.read_u8() {\n            Ok(c) => c,\n            Err(ref e) if e.kind() == ::std::io::ErrorKind::UnexpectedEof => return Ok(None),\n            Err(e) => return Err(NetError::from(e)),\n        };\n\n        if code_num & FAST_UPDATE_FLAG != 0 {\n            let all_bits;\n            let low_bits = code_num & !FAST_UPDATE_FLAG;\n            if low_bits & UpdateFlags::MORE_BITS.bits() as u8 != 0 {\n                let high_bits = reader.read_u8()?;\n                all_bits = (high_bits as u16) << 8 | low_bits as u16;\n            } else {\n                all_bits = low_bits as u16;\n            }\n\n            let update_flags = match UpdateFlags::from_bits(all_bits) {\n                Some(u) => u,\n                None => {\n                    return Err(NetError::InvalidData(format!(\n                        \"UpdateFlags: {:b}\",\n                        all_bits\n                    )))\n                }\n            };\n\n            let ent_id;\n            if update_flags.contains(UpdateFlags::LONG_ENTITY) {\n                ent_id = reader.read_u16::<LittleEndian>()?;\n            } else {\n                ent_id = reader.read_u8()? as u16;\n            }\n\n            let model_id;\n            if update_flags.contains(UpdateFlags::MODEL) {\n                model_id = Some(reader.read_u8()?);\n            } else {\n                model_id = None;\n            }\n\n            let frame_id;\n            if update_flags.contains(UpdateFlags::FRAME) {\n                frame_id = Some(reader.read_u8()?);\n            } else {\n                frame_id = None;\n            }\n\n            let colormap;\n            if update_flags.contains(UpdateFlags::COLORMAP) {\n                colormap = Some(reader.read_u8()?);\n            } else {\n                colormap = None;\n            }\n\n            let skin_id;\n            if update_flags.contains(UpdateFlags::SKIN) {\n                skin_id = Some(reader.read_u8()?);\n            } else {\n                skin_id = None;\n            }\n\n            let effects;\n            if update_flags.contains(UpdateFlags::EFFECTS) {\n                let effects_bits = reader.read_u8()?;\n                effects = match EntityEffects::from_bits(effects_bits) {\n                    Some(e) => Some(e),\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"EntityEffects: {:b}\",\n                            effects_bits\n                        )))\n                    }\n                };\n            } else {\n                effects = None;\n            }\n\n            let origin_x;\n            if update_flags.contains(UpdateFlags::ORIGIN_X) {\n                origin_x = Some(read_coord(reader)?);\n            } else {\n                origin_x = None;\n            }\n\n            let pitch;\n            if update_flags.contains(UpdateFlags::PITCH) {\n                pitch = Some(read_angle(reader)?);\n            } else {\n                pitch = None;\n            }\n\n            let origin_y;\n            if update_flags.contains(UpdateFlags::ORIGIN_Y) {\n                origin_y = Some(read_coord(reader)?);\n            } else {\n                origin_y = None;\n            }\n\n            let yaw;\n            if update_flags.contains(UpdateFlags::YAW) {\n                yaw = Some(read_angle(reader)?);\n            } else {\n                yaw = None;\n            }\n\n            let origin_z;\n            if update_flags.contains(UpdateFlags::ORIGIN_Z) {\n                origin_z = Some(read_coord(reader)?);\n            } else {\n                origin_z = None;\n            }\n\n            let roll;\n            if update_flags.contains(UpdateFlags::ROLL) {\n                roll = Some(read_angle(reader)?);\n            } else {\n                roll = None;\n            }\n\n            let no_lerp = update_flags.contains(UpdateFlags::NO_LERP);\n\n            return Ok(Some(ServerCmd::FastUpdate(EntityUpdate {\n                ent_id,\n                model_id,\n                frame_id,\n                colormap,\n                skin_id,\n                effects,\n                origin_x,\n                pitch,\n                origin_y,\n                yaw,\n                origin_z,\n                roll,\n                no_lerp,\n            })));\n        }\n\n        let code = match ServerCmdCode::from_u8(code_num) {\n            Some(c) => c,\n            None => {\n                return Err(NetError::InvalidData(format!(\n                    \"Invalid server command code: {}\",\n                    code_num\n                )))\n            }\n        };\n\n        let cmd = match code {\n            ServerCmdCode::Bad => ServerCmd::Bad,\n            ServerCmdCode::NoOp => ServerCmd::NoOp,\n            ServerCmdCode::Disconnect => ServerCmd::Disconnect,\n\n            ServerCmdCode::UpdateStat => {\n                let stat_id = reader.read_u8()?;\n                let stat = match ClientStat::from_u8(stat_id) {\n                    Some(c) => c,\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"value for ClientStat: {}\",\n                            stat_id,\n                        )))\n                    }\n                };\n                let value = reader.read_i32::<LittleEndian>()?;\n\n                ServerCmd::UpdateStat { stat, value }\n            }\n\n            ServerCmdCode::Version => {\n                let version = reader.read_i32::<LittleEndian>()?;\n                ServerCmd::Version { version }\n            }\n\n            ServerCmdCode::SetView => {\n                let ent_id = reader.read_i16::<LittleEndian>()?;\n                ServerCmd::SetView { ent_id }\n            }\n\n            ServerCmdCode::Sound => {\n                let flags_bits = reader.read_u8()?;\n                let flags = match SoundFlags::from_bits(flags_bits) {\n                    Some(f) => f,\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"SoundFlags: {:b}\",\n                            flags_bits\n                        )))\n                    }\n                };\n\n                let volume = match flags.contains(SoundFlags::VOLUME) {\n                    true => Some(reader.read_u8()?),\n                    false => None,\n                };\n\n                let attenuation = match flags.contains(SoundFlags::ATTENUATION) {\n                    true => Some(reader.read_u8()? as f32 * SOUND_ATTENUATION_READ_FACTOR),\n                    false => None,\n                };\n\n                let entity_channel = reader.read_i16::<LittleEndian>()?;\n                let entity_id = (entity_channel >> 3) as u16;\n                let channel = (entity_channel & 0b111) as i8;\n                let sound_id = reader.read_u8()?;\n                let position = Vector3::new(\n                    read_coord(reader)?,\n                    read_coord(reader)?,\n                    read_coord(reader)?,\n                );\n\n                ServerCmd::Sound {\n                    volume,\n                    attenuation,\n                    entity_id,\n                    channel,\n                    sound_id,\n                    position,\n                }\n            }\n\n            ServerCmdCode::Time => {\n                let time = reader.read_f32::<LittleEndian>()?;\n                ServerCmd::Time { time }\n            }\n\n            ServerCmdCode::Print => {\n                let text = match util::read_cstring(reader) {\n                    Ok(t) => t,\n                    Err(e) => return Err(NetError::with_msg(format!(\"{}\", e))),\n                };\n\n                ServerCmd::Print { text }\n            }\n\n            ServerCmdCode::StuffText => {\n                let text = match util::read_cstring(reader) {\n                    Ok(t) => t,\n                    Err(e) => return Err(NetError::with_msg(format!(\"{}\", e))),\n                };\n\n                ServerCmd::StuffText { text }\n            }\n\n            ServerCmdCode::SetAngle => {\n                let angles = Vector3::new(\n                    read_angle(reader)?,\n                    read_angle(reader)?,\n                    read_angle(reader)?,\n                );\n\n                ServerCmd::SetAngle { angles }\n            }\n\n            ServerCmdCode::ServerInfo => {\n                let protocol_version = reader.read_i32::<LittleEndian>()?;\n                let max_clients = reader.read_u8()?;\n                let game_type_code = reader.read_u8()?;\n                let game_type = match GameType::from_u8(game_type_code) {\n                    Some(g) => g,\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"Invalid game type ({})\",\n                            game_type_code\n                        )))\n                    }\n                };\n\n                let message = util::read_cstring(reader).unwrap();\n\n                let mut model_precache = Vec::new();\n                loop {\n                    let model_name = util::read_cstring(reader).unwrap();\n                    if model_name.is_empty() {\n                        break;\n                    }\n                    model_precache.push(model_name);\n                }\n\n                let mut sound_precache = Vec::new();\n                loop {\n                    let sound_name = util::read_cstring(reader).unwrap();\n                    if sound_name.is_empty() {\n                        break;\n                    }\n                    sound_precache.push(sound_name);\n                }\n\n                ServerCmd::ServerInfo {\n                    protocol_version,\n                    max_clients,\n                    game_type,\n                    message,\n                    model_precache,\n                    sound_precache,\n                }\n            }\n\n            ServerCmdCode::LightStyle => {\n                let id = reader.read_u8()?;\n                let value = util::read_cstring(reader).unwrap();\n                ServerCmd::LightStyle { id, value }\n            }\n\n            ServerCmdCode::UpdateName => {\n                let player_id = reader.read_u8()?;\n                let new_name = util::read_cstring(reader).unwrap();\n                ServerCmd::UpdateName {\n                    player_id,\n                    new_name,\n                }\n            }\n\n            ServerCmdCode::UpdateFrags => {\n                let player_id = reader.read_u8()?;\n                let new_frags = reader.read_i16::<LittleEndian>()?;\n\n                ServerCmd::UpdateFrags {\n                    player_id,\n                    new_frags,\n                }\n            }\n\n            ServerCmdCode::PlayerData => {\n                let flags_bits = reader.read_u16::<LittleEndian>()?;\n                let flags = match ClientUpdateFlags::from_bits(flags_bits) {\n                    Some(f) => f,\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"client update flags: {:b}\",\n                            flags_bits\n                        )))\n                    }\n                };\n\n                let view_height = match flags.contains(ClientUpdateFlags::VIEW_HEIGHT) {\n                    true => Some(reader.read_i8()? as f32),\n                    false => None,\n                };\n\n                let ideal_pitch = match flags.contains(ClientUpdateFlags::IDEAL_PITCH) {\n                    true => Some(Deg(reader.read_i8()? as f32)),\n                    false => None,\n                };\n\n                let punch_pitch = match flags.contains(ClientUpdateFlags::PUNCH_PITCH) {\n                    true => Some(Deg(reader.read_i8()? as f32)),\n                    false => None,\n                };\n\n                let velocity_x = match flags.contains(ClientUpdateFlags::VELOCITY_X) {\n                    true => Some(reader.read_i8()? as f32 * VELOCITY_READ_FACTOR),\n                    false => None,\n                };\n\n                let punch_yaw = match flags.contains(ClientUpdateFlags::PUNCH_YAW) {\n                    true => Some(Deg(reader.read_i8()? as f32)),\n                    false => None,\n                };\n\n                let velocity_y = match flags.contains(ClientUpdateFlags::VELOCITY_Y) {\n                    true => Some(reader.read_i8()? as f32 * VELOCITY_READ_FACTOR),\n                    false => None,\n                };\n\n                let punch_roll = match flags.contains(ClientUpdateFlags::PUNCH_ROLL) {\n                    true => Some(Deg(reader.read_i8()? as f32)),\n                    false => None,\n                };\n\n                let velocity_z = match flags.contains(ClientUpdateFlags::VELOCITY_Z) {\n                    true => Some(reader.read_i8()? as f32 * VELOCITY_READ_FACTOR),\n                    false => None,\n                };\n\n                let items_bits = reader.read_u32::<LittleEndian>()?;\n                let items = match ItemFlags::from_bits(items_bits) {\n                    Some(i) => i,\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"ItemFlags: {:b}\",\n                            items_bits\n                        )))\n                    }\n                };\n\n                let on_ground = flags.contains(ClientUpdateFlags::ON_GROUND);\n                let in_water = flags.contains(ClientUpdateFlags::IN_WATER);\n\n                let weapon_frame = match flags.contains(ClientUpdateFlags::WEAPON_FRAME) {\n                    true => Some(reader.read_u8()?),\n                    false => None,\n                };\n\n                let armor = match flags.contains(ClientUpdateFlags::ARMOR) {\n                    true => Some(reader.read_u8()?),\n                    false => None,\n                };\n\n                let weapon = match flags.contains(ClientUpdateFlags::WEAPON) {\n                    true => Some(reader.read_u8()?),\n                    false => None,\n                };\n\n                let health = reader.read_i16::<LittleEndian>()?;\n                let ammo = reader.read_u8()?;\n                let ammo_shells = reader.read_u8()?;\n                let ammo_nails = reader.read_u8()?;\n                let ammo_rockets = reader.read_u8()?;\n                let ammo_cells = reader.read_u8()?;\n                let active_weapon = reader.read_u8()?;\n\n                ServerCmd::PlayerData(PlayerData {\n                    view_height,\n                    ideal_pitch,\n                    punch_pitch,\n                    velocity_x,\n                    punch_yaw,\n                    velocity_y,\n                    punch_roll,\n                    velocity_z,\n                    items,\n                    on_ground,\n                    in_water,\n                    weapon_frame,\n                    armor,\n                    weapon,\n                    health,\n                    ammo,\n                    ammo_shells,\n                    ammo_nails,\n                    ammo_rockets,\n                    ammo_cells,\n                    active_weapon,\n                })\n            }\n\n            ServerCmdCode::StopSound => {\n                let entity_channel = reader.read_u16::<LittleEndian>()?;\n                let entity_id = entity_channel >> 3;\n                let channel = (entity_channel & 0b111) as u8;\n\n                ServerCmd::StopSound { entity_id, channel }\n            }\n\n            ServerCmdCode::UpdateColors => {\n                let player_id = reader.read_u8()?;\n                let new_colors_bits = reader.read_u8()?;\n                let new_colors = PlayerColor::from_bits(new_colors_bits);\n\n                ServerCmd::UpdateColors {\n                    player_id,\n                    new_colors,\n                }\n            }\n\n            ServerCmdCode::Particle => {\n                let origin = read_coord_vector3(reader)?;\n\n                let mut direction = Vector3::zero();\n                for i in 0..3 {\n                    direction[i] = reader.read_i8()? as f32 * PARTICLE_DIRECTION_READ_FACTOR;\n                }\n\n                let count = reader.read_u8()?;\n                let color = reader.read_u8()?;\n\n                ServerCmd::Particle {\n                    origin,\n                    direction,\n                    count,\n                    color,\n                }\n            }\n\n            ServerCmdCode::Damage => {\n                let armor = reader.read_u8()?;\n                let blood = reader.read_u8()?;\n                let source = read_coord_vector3(reader)?;\n\n                ServerCmd::Damage {\n                    armor,\n                    blood,\n                    source,\n                }\n            }\n\n            ServerCmdCode::SpawnStatic => {\n                let model_id = reader.read_u8()?;\n                let frame_id = reader.read_u8()?;\n                let colormap = reader.read_u8()?;\n                let skin_id = reader.read_u8()?;\n\n                let mut origin = Vector3::zero();\n                let mut angles = Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0));\n                for i in 0..3 {\n                    origin[i] = read_coord(reader)?;\n                    angles[i] = read_angle(reader)?;\n                }\n\n                ServerCmd::SpawnStatic {\n                    model_id,\n                    frame_id,\n                    colormap,\n                    skin_id,\n                    origin,\n                    angles,\n                }\n            }\n\n            ServerCmdCode::SpawnBaseline => {\n                let ent_id = reader.read_u16::<LittleEndian>()?;\n                let model_id = reader.read_u8()?;\n                let frame_id = reader.read_u8()?;\n                let colormap = reader.read_u8()?;\n                let skin_id = reader.read_u8()?;\n\n                let mut origin = Vector3::zero();\n                let mut angles = Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0));\n                for i in 0..3 {\n                    origin[i] = read_coord(reader)?;\n                    angles[i] = read_angle(reader)?;\n                }\n\n                ServerCmd::SpawnBaseline {\n                    ent_id,\n                    model_id,\n                    frame_id,\n                    colormap,\n                    skin_id,\n                    origin,\n                    angles,\n                }\n            }\n\n            ServerCmdCode::TempEntity => {\n                let temp_entity = TempEntity::read_temp_entity(reader)?;\n\n                ServerCmd::TempEntity { temp_entity }\n            }\n\n            ServerCmdCode::SetPause => {\n                let paused = match reader.read_u8()? {\n                    0 => false,\n                    1 => true,\n                    x => return Err(NetError::InvalidData(format!(\"setpause: {}\", x))),\n                };\n\n                ServerCmd::SetPause { paused }\n            }\n\n            ServerCmdCode::SignOnStage => {\n                let stage_num = reader.read_u8()?;\n                let stage = match SignOnStage::from_u8(stage_num) {\n                    Some(s) => s,\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"Invalid value for sign-on stage: {}\",\n                            stage_num\n                        )))\n                    }\n                };\n\n                ServerCmd::SignOnStage { stage }\n            }\n\n            ServerCmdCode::CenterPrint => {\n                let text = match util::read_cstring(reader) {\n                    Ok(t) => t,\n                    Err(e) => return Err(NetError::with_msg(format!(\"{}\", e))),\n                };\n\n                ServerCmd::CenterPrint { text }\n            }\n\n            ServerCmdCode::KilledMonster => ServerCmd::KilledMonster,\n            ServerCmdCode::FoundSecret => ServerCmd::FoundSecret,\n\n            ServerCmdCode::SpawnStaticSound => {\n                let origin = read_coord_vector3(reader)?;\n                let sound_id = reader.read_u8()?;\n                let volume = reader.read_u8()?;\n                let attenuation = reader.read_u8()?;\n\n                ServerCmd::SpawnStaticSound {\n                    origin,\n                    sound_id,\n                    volume,\n                    attenuation,\n                }\n            }\n\n            ServerCmdCode::Intermission => ServerCmd::Intermission,\n\n            ServerCmdCode::Finale => {\n                let text = match util::read_cstring(reader) {\n                    Ok(t) => t,\n                    Err(e) => return Err(NetError::with_msg(format!(\"{}\", e))),\n                };\n\n                ServerCmd::Finale { text }\n            }\n\n            ServerCmdCode::CdTrack => {\n                let track = reader.read_u8()?;\n                let loop_ = reader.read_u8()?;\n                ServerCmd::CdTrack { track, loop_ }\n            }\n\n            ServerCmdCode::SellScreen => ServerCmd::SellScreen,\n\n            ServerCmdCode::Cutscene => {\n                let text = match util::read_cstring(reader) {\n                    Ok(t) => t,\n                    Err(e) => return Err(NetError::with_msg(format!(\"{}\", e))),\n                };\n\n                ServerCmd::Cutscene { text }\n            }\n        };\n\n        Ok(Some(cmd))\n    }\n\n    pub fn serialize<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write_u8(self.code())?;\n\n        match *self {\n            ServerCmd::Bad | ServerCmd::NoOp | ServerCmd::Disconnect => (),\n\n            ServerCmd::UpdateStat { stat, value } => {\n                writer.write_u8(stat as u8)?;\n                writer.write_i32::<LittleEndian>(value)?;\n            }\n\n            ServerCmd::Version { version } => {\n                writer.write_i32::<LittleEndian>(version)?;\n            }\n\n            ServerCmd::SetView { ent_id } => {\n                writer.write_i16::<LittleEndian>(ent_id)?;\n            }\n\n            ServerCmd::Sound {\n                volume,\n                attenuation,\n                entity_id,\n                channel,\n                sound_id,\n                position,\n            } => {\n                let mut sound_flags = SoundFlags::empty();\n\n                if volume.is_some() {\n                    sound_flags |= SoundFlags::VOLUME;\n                }\n\n                if attenuation.is_some() {\n                    sound_flags |= SoundFlags::ATTENUATION;\n                }\n\n                writer.write_u8(sound_flags.bits())?;\n\n                if let Some(v) = volume {\n                    writer.write_u8(v)?;\n                }\n\n                if let Some(a) = attenuation {\n                    writer.write_u8(a as u8 * SOUND_ATTENUATION_WRITE_FACTOR)?;\n                }\n\n                // TODO: document this better. The entity and channel fields are combined in Sound commands.\n                let ent_channel = (entity_id as i16) << 3 | channel as i16 & 0b111;\n                writer.write_i16::<LittleEndian>(ent_channel)?;\n\n                writer.write_u8(sound_id)?;\n\n                for component in 0..3 {\n                    write_coord(writer, position[component])?;\n                }\n            }\n\n            ServerCmd::Time { time } => writer.write_f32::<LittleEndian>(time)?,\n\n            ServerCmd::Print { ref text } => {\n                writer.write(text.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n\n            ServerCmd::StuffText { ref text } => {\n                writer.write(text.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n\n            ServerCmd::SetAngle { angles } => write_angle_vector3(writer, angles)?,\n\n            ServerCmd::ServerInfo {\n                protocol_version,\n                max_clients,\n                game_type,\n                ref message,\n                ref model_precache,\n                ref sound_precache,\n            } => {\n                writer.write_i32::<LittleEndian>(protocol_version)?;\n                writer.write_u8(max_clients)?;\n                writer.write_u8(game_type as u8)?;\n\n                writer.write(message.as_bytes())?;\n                writer.write_u8(0)?;\n\n                for model_name in model_precache.iter() {\n                    writer.write(model_name.as_bytes())?;\n                    writer.write_u8(0)?;\n                }\n                writer.write_u8(0)?;\n\n                for sound_name in sound_precache.iter() {\n                    writer.write(sound_name.as_bytes())?;\n                    writer.write_u8(0)?;\n                }\n                writer.write_u8(0)?;\n            }\n\n            ServerCmd::LightStyle { id, ref value } => {\n                writer.write_u8(id)?;\n                writer.write(value.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n\n            ServerCmd::UpdateName {\n                player_id,\n                ref new_name,\n            } => {\n                writer.write_u8(player_id)?;\n                writer.write(new_name.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n\n            ServerCmd::UpdateFrags {\n                player_id,\n                new_frags,\n            } => {\n                writer.write_u8(player_id)?;\n                writer.write_i16::<LittleEndian>(new_frags)?;\n            }\n\n            ServerCmd::PlayerData(PlayerData {\n                view_height,\n                ideal_pitch,\n                punch_pitch,\n                velocity_x,\n                punch_yaw,\n                velocity_y,\n                punch_roll,\n                velocity_z,\n                items,\n                on_ground,\n                in_water,\n                weapon_frame,\n                armor,\n                weapon,\n                health,\n                ammo,\n                ammo_shells,\n                ammo_nails,\n                ammo_rockets,\n                ammo_cells,\n                active_weapon,\n            }) => {\n                let mut flags = ClientUpdateFlags::empty();\n                if view_height.is_some() {\n                    flags |= ClientUpdateFlags::VIEW_HEIGHT;\n                }\n                if ideal_pitch.is_some() {\n                    flags |= ClientUpdateFlags::IDEAL_PITCH;\n                }\n                if punch_pitch.is_some() {\n                    flags |= ClientUpdateFlags::PUNCH_PITCH;\n                }\n                if velocity_x.is_some() {\n                    flags |= ClientUpdateFlags::VELOCITY_X;\n                }\n                if punch_yaw.is_some() {\n                    flags |= ClientUpdateFlags::PUNCH_YAW;\n                }\n                if velocity_y.is_some() {\n                    flags |= ClientUpdateFlags::VELOCITY_Y;\n                }\n                if punch_roll.is_some() {\n                    flags |= ClientUpdateFlags::PUNCH_ROLL;\n                }\n                if velocity_z.is_some() {\n                    flags |= ClientUpdateFlags::VELOCITY_Z;\n                }\n\n                // items are always sent\n                flags |= ClientUpdateFlags::ITEMS;\n\n                if on_ground {\n                    flags |= ClientUpdateFlags::ON_GROUND;\n                }\n                if in_water {\n                    flags |= ClientUpdateFlags::IN_WATER;\n                }\n                if weapon_frame.is_some() {\n                    flags |= ClientUpdateFlags::WEAPON_FRAME;\n                }\n                if armor.is_some() {\n                    flags |= ClientUpdateFlags::ARMOR;\n                }\n                if weapon.is_some() {\n                    flags |= ClientUpdateFlags::WEAPON;\n                }\n\n                // write flags\n                writer.write_u16::<LittleEndian>(flags.bits())?;\n\n                if let Some(vh) = view_height {\n                    writer.write_u8(vh as i32 as u8)?;\n                }\n                if let Some(ip) = ideal_pitch {\n                    writer.write_u8(ip.0 as i32 as u8)?;\n                }\n                if let Some(pp) = punch_pitch {\n                    writer.write_u8(pp.0 as i32 as u8)?;\n                }\n                if let Some(vx) = velocity_x {\n                    writer.write_u8((vx * VELOCITY_WRITE_FACTOR) as i32 as u8)?;\n                }\n                if let Some(py) = punch_yaw {\n                    writer.write_u8(py.0 as i32 as u8)?;\n                }\n                if let Some(vy) = velocity_y {\n                    writer.write_u8((vy * VELOCITY_WRITE_FACTOR) as i32 as u8)?;\n                }\n                if let Some(pr) = punch_roll {\n                    writer.write_u8(pr.0 as i32 as u8)?;\n                }\n                if let Some(vz) = velocity_z {\n                    writer.write_u8((vz * VELOCITY_WRITE_FACTOR) as i32 as u8)?;\n                }\n                writer.write_u32::<LittleEndian>(items.bits())?;\n                if let Some(wf) = weapon_frame {\n                    writer.write_u8(wf)?;\n                }\n                if let Some(a) = armor {\n                    writer.write_u8(a)?;\n                }\n                if let Some(w) = weapon {\n                    writer.write_u8(w)?;\n                }\n                writer.write_i16::<LittleEndian>(health)?;\n                writer.write_u8(ammo)?;\n                writer.write_u8(ammo_shells)?;\n                writer.write_u8(ammo_nails)?;\n                writer.write_u8(ammo_rockets)?;\n                writer.write_u8(ammo_cells)?;\n                writer.write_u8(active_weapon)?;\n            }\n\n            ServerCmd::StopSound { entity_id, channel } => {\n                let entity_channel = entity_id << 3 | channel as u16 & 0b111;\n                writer.write_u16::<LittleEndian>(entity_channel)?;\n            }\n\n            ServerCmd::UpdateColors {\n                player_id,\n                new_colors,\n            } => {\n                writer.write_u8(player_id)?;\n                writer.write_u8(new_colors.bits())?;\n            }\n\n            ServerCmd::Particle {\n                origin,\n                direction,\n                count,\n                color,\n            } => {\n                write_coord_vector3(writer, origin)?;\n\n                for i in 0..3 {\n                    writer.write_i8(match direction[i] * PARTICLE_DIRECTION_WRITE_FACTOR {\n                        d if d > ::std::i8::MAX as f32 => ::std::i8::MAX,\n                        d if d < ::std::i8::MIN as f32 => ::std::i8::MIN,\n                        d => d as i8,\n                    })?;\n                }\n\n                writer.write_u8(count)?;\n                writer.write_u8(color)?;\n            }\n\n            ServerCmd::Damage {\n                armor,\n                blood,\n                source,\n            } => {\n                writer.write_u8(armor)?;\n                writer.write_u8(blood)?;\n                write_coord_vector3(writer, source)?;\n            }\n\n            ServerCmd::SpawnStatic {\n                model_id,\n                frame_id,\n                colormap,\n                skin_id,\n                origin,\n                angles,\n            } => {\n                writer.write_u8(model_id)?;\n                writer.write_u8(frame_id)?;\n                writer.write_u8(colormap)?;\n                writer.write_u8(skin_id)?;\n\n                for i in 0..3 {\n                    write_coord(writer, origin[i])?;\n                    write_angle(writer, angles[i])?;\n                }\n            }\n\n            ServerCmd::SpawnBaseline {\n                ent_id,\n                model_id,\n                frame_id,\n                colormap,\n                skin_id,\n                origin,\n                angles,\n            } => {\n                writer.write_u16::<LittleEndian>(ent_id)?;\n                writer.write_u8(model_id)?;\n                writer.write_u8(frame_id)?;\n                writer.write_u8(colormap)?;\n                writer.write_u8(skin_id)?;\n\n                for i in 0..3 {\n                    write_coord(writer, origin[i])?;\n                    write_angle(writer, angles[i])?;\n                }\n            }\n\n            ServerCmd::TempEntity { ref temp_entity } => {\n                temp_entity.write_temp_entity(writer)?;\n            }\n\n            ServerCmd::SetPause { paused } => {\n                writer.write_u8(match paused {\n                    false => 0,\n                    true => 1,\n                })?;\n            }\n\n            ServerCmd::SignOnStage { stage } => {\n                writer.write_u8(stage as u8)?;\n            }\n\n            ServerCmd::CenterPrint { ref text } => {\n                writer.write(text.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n\n            ServerCmd::KilledMonster | ServerCmd::FoundSecret => (),\n\n            ServerCmd::SpawnStaticSound {\n                origin,\n                sound_id,\n                volume,\n                attenuation,\n            } => {\n                write_coord_vector3(writer, origin)?;\n                writer.write_u8(sound_id)?;\n                writer.write_u8(volume)?;\n                writer.write_u8(attenuation)?;\n            }\n\n            ServerCmd::Intermission => (),\n\n            ServerCmd::Finale { ref text } => {\n                writer.write(text.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n\n            ServerCmd::CdTrack { track, loop_ } => {\n                writer.write_u8(track)?;\n                writer.write_u8(loop_)?;\n            }\n\n            ServerCmd::SellScreen => (),\n\n            ServerCmd::Cutscene { ref text } => {\n                writer.write(text.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n\n            // TODO\n            ServerCmd::FastUpdate(_) => unimplemented!(),\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(FromPrimitive)]\npub enum ClientCmdCode {\n    Bad = 0,\n    NoOp = 1,\n    Disconnect = 2,\n    Move = 3,\n    StringCmd = 4,\n}\n\n#[derive(Debug, PartialEq)]\npub enum ClientCmd {\n    Bad,\n    NoOp,\n    Disconnect,\n    Move {\n        send_time: Duration,\n        angles: Vector3<Deg<f32>>,\n        fwd_move: i16,\n        side_move: i16,\n        up_move: i16,\n        button_flags: ButtonFlags,\n        impulse: u8,\n    },\n    StringCmd {\n        cmd: String,\n    },\n}\n\nimpl ClientCmd {\n    pub fn code(&self) -> u8 {\n        match *self {\n            ClientCmd::Bad => ClientCmdCode::Bad as u8,\n            ClientCmd::NoOp => ClientCmdCode::NoOp as u8,\n            ClientCmd::Disconnect => ClientCmdCode::Disconnect as u8,\n            ClientCmd::Move { .. } => ClientCmdCode::Move as u8,\n            ClientCmd::StringCmd { .. } => ClientCmdCode::StringCmd as u8,\n        }\n    }\n\n    pub fn deserialize<R>(reader: &mut R) -> Result<ClientCmd, NetError>\n    where\n        R: ReadBytesExt + BufRead,\n    {\n        let code_val = reader.read_u8()?;\n        let code = match ClientCmdCode::from_u8(code_val) {\n            Some(c) => c,\n            None => {\n                return Err(NetError::InvalidData(format!(\n                    \"Invalid client command code: {}\",\n                    code_val\n                )))\n            }\n        };\n\n        let cmd = match code {\n            ClientCmdCode::Bad => ClientCmd::Bad,\n            ClientCmdCode::NoOp => ClientCmd::NoOp,\n            ClientCmdCode::Disconnect => ClientCmd::Disconnect,\n            ClientCmdCode::Move => {\n                let send_time = engine::duration_from_f32(reader.read_f32::<LittleEndian>()?);\n                let angles = Vector3::new(\n                    read_angle(reader)?,\n                    read_angle(reader)?,\n                    read_angle(reader)?,\n                );\n                let fwd_move = reader.read_i16::<LittleEndian>()?;\n                let side_move = reader.read_i16::<LittleEndian>()?;\n                let up_move = reader.read_i16::<LittleEndian>()?;\n                let button_flags_val = reader.read_u8()?;\n                let button_flags = match ButtonFlags::from_bits(button_flags_val) {\n                    Some(bf) => bf,\n                    None => {\n                        return Err(NetError::InvalidData(format!(\n                            \"Invalid value for button flags: {}\",\n                            button_flags_val\n                        )))\n                    }\n                };\n                let impulse = reader.read_u8()?;\n                ClientCmd::Move {\n                    send_time,\n                    angles,\n                    fwd_move,\n                    side_move,\n                    up_move,\n                    button_flags,\n                    impulse,\n                }\n            }\n            ClientCmdCode::StringCmd => {\n                let cmd = util::read_cstring(reader).unwrap();\n                ClientCmd::StringCmd { cmd }\n            }\n        };\n\n        Ok(cmd)\n    }\n\n    pub fn serialize<W>(&self, writer: &mut W) -> Result<(), NetError>\n    where\n        W: WriteBytesExt,\n    {\n        writer.write_u8(self.code())?;\n\n        match *self {\n            ClientCmd::Bad => (),\n            ClientCmd::NoOp => (),\n            ClientCmd::Disconnect => (),\n            ClientCmd::Move {\n                send_time,\n                angles,\n                fwd_move,\n                side_move,\n                up_move,\n                button_flags,\n                impulse,\n            } => {\n                writer.write_f32::<LittleEndian>(engine::duration_to_f32(send_time))?;\n                write_angle_vector3(writer, angles)?;\n                writer.write_i16::<LittleEndian>(fwd_move)?;\n                writer.write_i16::<LittleEndian>(side_move)?;\n                writer.write_i16::<LittleEndian>(up_move)?;\n                writer.write_u8(button_flags.bits())?;\n                writer.write_u8(impulse)?;\n            }\n            ClientCmd::StringCmd { ref cmd } => {\n                writer.write(cmd.as_bytes())?;\n                writer.write_u8(0)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(PartialEq)]\npub enum BlockingMode {\n    Blocking,\n    NonBlocking,\n    Timeout(Duration),\n}\n\npub struct QSocket {\n    socket: UdpSocket,\n    remote: SocketAddr,\n\n    unreliable_send_sequence: u32,\n    unreliable_recv_sequence: u32,\n\n    ack_sequence: u32,\n\n    send_sequence: u32,\n    send_queue: VecDeque<Box<[u8]>>,\n    send_cache: Box<[u8]>,\n    send_next: bool,\n    send_count: usize,\n    resend_count: usize,\n\n    recv_sequence: u32,\n    recv_buf: [u8; MAX_MESSAGE],\n}\n\nimpl QSocket {\n    pub fn new(socket: UdpSocket, remote: SocketAddr) -> QSocket {\n        QSocket {\n            socket,\n            remote,\n\n            unreliable_send_sequence: 0,\n            unreliable_recv_sequence: 0,\n\n            ack_sequence: 0,\n\n            send_sequence: 0,\n            send_queue: VecDeque::new(),\n            send_cache: Box::new([]),\n            send_count: 0,\n            send_next: false,\n            resend_count: 0,\n\n            recv_sequence: 0,\n            recv_buf: [0; MAX_MESSAGE],\n        }\n    }\n\n    pub fn can_send(&self) -> bool {\n        self.send_queue.is_empty() && self.send_cache.is_empty()\n    }\n\n    /// Begin sending a reliable message over this socket.\n    pub fn begin_send_msg(&mut self, msg: &[u8]) -> Result<(), NetError> {\n        // make sure all reliable messages have been ACKed in their entirety\n        if !self.send_queue.is_empty() {\n            return Err(NetError::with_msg(\n                \"begin_send_msg: previous message unacknowledged\",\n            ));\n        }\n\n        // empty messages are an error\n        if msg.len() == 0 {\n            return Err(NetError::with_msg(\n                \"begin_send_msg: Input data has zero length\",\n            ));\n        }\n\n        // check upper message length bound\n        if msg.len() > MAX_MESSAGE {\n            return Err(NetError::with_msg(\n                \"begin_send_msg: Input data exceeds MAX_MESSAGE\",\n            ));\n        }\n\n        // split the message into chunks and enqueue them\n        for chunk in msg.chunks(MAX_DATAGRAM) {\n            self.send_queue\n                .push_back(chunk.to_owned().into_boxed_slice());\n        }\n\n        // send the first chunk\n        self.send_msg_next()?;\n\n        Ok(())\n    }\n\n    /// Resend the last reliable message packet.\n    pub fn resend_msg(&mut self) -> Result<(), NetError> {\n        if self.send_cache.is_empty() {\n            Err(NetError::with_msg(\"Attempted resend with empty send cache\"))\n        } else {\n            self.socket.send_to(&self.send_cache, self.remote)?;\n            self.resend_count += 1;\n\n            Ok(())\n        }\n    }\n\n    /// Send the next segment of a reliable message.\n    pub fn send_msg_next(&mut self) -> Result<(), NetError> {\n        // grab the first chunk in the queue\n        let content = self\n            .send_queue\n            .pop_front()\n            .expect(\"Send queue is empty (this is a bug)\");\n\n        // if this was the last chunk, set the EOM flag\n        let msg_kind = match self.send_queue.is_empty() {\n            true => MsgKind::ReliableEom,\n            false => MsgKind::Reliable,\n        };\n\n        // compose the packet\n        let mut compose = Vec::with_capacity(MAX_PACKET);\n        compose.write_u16::<NetworkEndian>(msg_kind as u16)?;\n        compose.write_u16::<NetworkEndian>((HEADER_SIZE + content.len()) as u16)?;\n        compose.write_u32::<NetworkEndian>(self.send_sequence)?;\n        compose.write_all(&content)?;\n\n        // store packet to send cache\n        self.send_cache = compose.into_boxed_slice();\n\n        // increment send sequence\n        self.send_sequence += 1;\n\n        // send the composed packet\n        self.socket.send_to(&self.send_cache, self.remote)?;\n\n        // TODO: update send time\n        // bump send count\n        self.send_count += 1;\n\n        // don't send the next chunk until this one gets ACKed\n        self.send_next = false;\n\n        Ok(())\n    }\n\n    pub fn send_msg_unreliable(&mut self, content: &[u8]) -> Result<(), NetError> {\n        if content.len() == 0 {\n            return Err(NetError::with_msg(\"Unreliable message has zero length\"));\n        }\n\n        if content.len() > MAX_DATAGRAM {\n            return Err(NetError::with_msg(\n                \"Unreliable message length exceeds MAX_DATAGRAM\",\n            ));\n        }\n\n        let packet_len = HEADER_SIZE + content.len();\n\n        // compose the packet\n        let mut packet = Vec::with_capacity(MAX_PACKET);\n        packet.write_u16::<NetworkEndian>(MsgKind::Unreliable as u16)?;\n        packet.write_u16::<NetworkEndian>(packet_len as u16)?;\n        packet.write_u32::<NetworkEndian>(self.unreliable_send_sequence)?;\n        packet.write_all(content)?;\n\n        // increment unreliable send sequence\n        self.unreliable_send_sequence += 1;\n\n        // send the message\n        self.socket.send_to(&packet, self.remote)?;\n\n        // bump send count\n        self.send_count += 1;\n\n        Ok(())\n    }\n\n    /// Receive a message on this socket.\n    // TODO: the flow control in this function is completely baffling, make it a little less awful\n    pub fn recv_msg(&mut self, block: BlockingMode) -> Result<Vec<u8>, NetError> {\n        let mut msg = Vec::new();\n\n        match block {\n            BlockingMode::Blocking => {\n                self.socket.set_nonblocking(false)?;\n                self.socket.set_read_timeout(None)?;\n            }\n\n            BlockingMode::NonBlocking => {\n                self.socket.set_nonblocking(true)?;\n                self.socket.set_read_timeout(None)?;\n            }\n\n            BlockingMode::Timeout(d) => {\n                self.socket.set_nonblocking(false)?;\n                self.socket.set_read_timeout(Some(d.to_std().unwrap()))?;\n            }\n        }\n\n        loop {\n            let (packet_len, src_addr) = match self.socket.recv_from(&mut self.recv_buf) {\n                Ok(x) => x,\n                Err(e) => {\n                    use std::io::ErrorKind;\n                    match e.kind() {\n                        // these errors are expected in nonblocking mode\n                        ErrorKind::WouldBlock | ErrorKind::TimedOut => return Ok(Vec::new()),\n                        _ => return Err(NetError::from(e)),\n                    }\n                }\n            };\n\n            if src_addr != self.remote {\n                // this packet didn't come from remote, drop it\n                debug!(\n                    \"forged packet (src_addr was {}, should be {})\",\n                    src_addr, self.remote\n                );\n                continue;\n            }\n\n            let mut reader = BufReader::new(Cursor::new(&self.recv_buf[..packet_len]));\n\n            let msg_kind_code = reader.read_u16::<NetworkEndian>()?;\n            let msg_kind = match MsgKind::from_u16(msg_kind_code) {\n                Some(f) => f,\n                None => {\n                    return Err(NetError::InvalidData(format!(\n                        \"Invalid message kind: {}\",\n                        msg_kind_code\n                    )))\n                }\n            };\n\n            if packet_len < HEADER_SIZE {\n                // TODO: increment short packet count\n                debug!(\"short packet\");\n                continue;\n            }\n\n            let field_len = reader.read_u16::<NetworkEndian>()?;\n            if field_len as usize != packet_len {\n                return Err(NetError::InvalidData(format!(\n                    \"Length field and actual length differ ({} != {})\",\n                    field_len, packet_len\n                )));\n            }\n\n            let sequence;\n            if msg_kind != MsgKind::Ctl {\n                sequence = reader.read_u32::<NetworkEndian>()?;\n            } else {\n                sequence = 0;\n            }\n\n            match msg_kind {\n                // ignore control messages\n                MsgKind::Ctl => (),\n\n                MsgKind::Unreliable => {\n                    // we've received a newer datagram, ignore\n                    if sequence < self.unreliable_recv_sequence {\n                        println!(\"Stale datagram with sequence # {}\", sequence);\n                        break;\n                    }\n\n                    // we've skipped some datagrams, count them as dropped\n                    if sequence > self.unreliable_recv_sequence {\n                        let drop_count = sequence - self.unreliable_recv_sequence;\n                        println!(\n                            \"Dropped {} packet(s) ({} -> {})\",\n                            drop_count, sequence, self.unreliable_recv_sequence\n                        );\n                    }\n\n                    self.unreliable_recv_sequence = sequence + 1;\n\n                    // copy the rest of the packet into the message buffer and return\n                    reader.read_to_end(&mut msg)?;\n                    return Ok(msg);\n                }\n\n                MsgKind::Ack => {\n                    if sequence != self.send_sequence - 1 {\n                        println!(\"Stale ACK received\");\n                    } else if sequence != self.ack_sequence {\n                        println!(\"Duplicate ACK received\");\n                    } else {\n                        self.ack_sequence += 1;\n                        if self.ack_sequence != self.send_sequence {\n                            return Err(NetError::with_msg(\"ACK sequencing error\"));\n                        }\n\n                        // our last reliable message has been acked\n                        if self.send_queue.is_empty() {\n                            // the whole message is through, clear the send cache\n                            self.send_cache = Box::new([]);\n                        } else {\n                            // send the next chunk before returning\n                            self.send_next = true;\n                        }\n                    }\n                }\n\n                // TODO: once we start reading a reliable message, don't allow other packets until\n                // we have the whole thing\n                MsgKind::Reliable | MsgKind::ReliableEom => {\n                    // send ack message and increment self.recv_sequence\n                    let mut ack_buf: [u8; HEADER_SIZE] = [0; HEADER_SIZE];\n                    let mut ack_curs = Cursor::new(&mut ack_buf[..]);\n                    ack_curs.write_u16::<NetworkEndian>(MsgKind::Ack as u16)?;\n                    ack_curs.write_u16::<NetworkEndian>(HEADER_SIZE as u16)?;\n                    ack_curs.write_u32::<NetworkEndian>(sequence)?;\n                    self.socket.send_to(ack_curs.into_inner(), self.remote)?;\n\n                    // if this was a duplicate, drop it\n                    if sequence != self.recv_sequence {\n                        println!(\"Duplicate message received\");\n                        continue;\n                    }\n\n                    self.recv_sequence += 1;\n                    reader.read_to_end(&mut msg)?;\n\n                    // if this is the last chunk of a reliable message, break out and return\n                    if msg_kind == MsgKind::ReliableEom {\n                        break;\n                    }\n                }\n            }\n        }\n\n        if self.send_next {\n            self.send_msg_next()?;\n        }\n\n        Ok(msg)\n    }\n}\n\nfn read_coord<R>(reader: &mut R) -> Result<f32, NetError>\nwhere\n    R: BufRead + ReadBytesExt,\n{\n    Ok(reader.read_i16::<LittleEndian>()? as f32 / 8.0)\n}\n\nfn read_coord_vector3<R>(reader: &mut R) -> Result<Vector3<f32>, NetError>\nwhere\n    R: BufRead + ReadBytesExt,\n{\n    Ok(Vector3::new(\n        read_coord(reader)?,\n        read_coord(reader)?,\n        read_coord(reader)?,\n    ))\n}\n\nfn write_coord<W>(writer: &mut W, coord: f32) -> Result<(), NetError>\nwhere\n    W: WriteBytesExt,\n{\n    writer.write_i16::<LittleEndian>((coord * 8.0) as i16)?;\n    Ok(())\n}\n\nfn write_coord_vector3<W>(writer: &mut W, coords: Vector3<f32>) -> Result<(), NetError>\nwhere\n    W: WriteBytesExt,\n{\n    for coord in &coords[..] {\n        write_coord(writer, *coord)?;\n    }\n\n    Ok(())\n}\n\nfn read_angle<R>(reader: &mut R) -> Result<Deg<f32>, NetError>\nwhere\n    R: BufRead + ReadBytesExt,\n{\n    Ok(Deg(reader.read_i8()? as f32 * (360.0 / 256.0)))\n}\n\nfn read_angle_vector3<R>(reader: &mut R) -> Result<Vector3<Deg<f32>>, NetError>\nwhere\n    R: BufRead + ReadBytesExt,\n{\n    Ok(Vector3::new(\n        read_angle(reader)?,\n        read_angle(reader)?,\n        read_angle(reader)?,\n    ))\n}\n\nfn write_angle<W>(writer: &mut W, angle: Deg<f32>) -> Result<(), NetError>\nwhere\n    W: WriteBytesExt,\n{\n    writer.write_u8(((angle.0 as i32 * 256 / 360) & 0xFF) as u8)?;\n    Ok(())\n}\n\nfn write_angle_vector3<W>(writer: &mut W, angles: Vector3<Deg<f32>>) -> Result<(), NetError>\nwhere\n    W: WriteBytesExt,\n{\n    for angle in &angles[..] {\n        write_angle(writer, *angle)?;\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    use std::io::BufReader;\n\n    #[test]\n    fn test_server_cmd_update_stat_read_write_eq() {\n        let src = ServerCmd::UpdateStat {\n            stat: ClientStat::Nails,\n            value: 64,\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_version_read_write_eq() {\n        let src = ServerCmd::Version { version: 42 };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_set_view_read_write_eq() {\n        let src = ServerCmd::SetView { ent_id: 17 };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_time_read_write_eq() {\n        let src = ServerCmd::Time { time: 23.07 };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_print_read_write_eq() {\n        let src = ServerCmd::Print {\n            text: String::from(\"print test\"),\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_stuff_text_read_write_eq() {\n        let src = ServerCmd::StuffText {\n            text: String::from(\"stufftext test\"),\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_server_info_read_write_eq() {\n        let src = ServerCmd::ServerInfo {\n            protocol_version: 42,\n            max_clients: 16,\n            game_type: GameType::Deathmatch,\n            message: String::from(\"Test message\"),\n            model_precache: vec![String::from(\"test1.bsp\"), String::from(\"test2.bsp\")],\n            sound_precache: vec![String::from(\"test1.wav\"), String::from(\"test2.wav\")],\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_light_style_read_write_eq() {\n        let src = ServerCmd::LightStyle {\n            id: 11,\n            value: String::from(\"aaaaabcddeefgghjjjkaaaazzzzyxwaaaba\"),\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_update_name_read_write_eq() {\n        let src = ServerCmd::UpdateName {\n            player_id: 7,\n            new_name: String::from(\"newname\"),\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_update_frags_read_write_eq() {\n        let src = ServerCmd::UpdateFrags {\n            player_id: 7,\n            new_frags: 11,\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_stop_sound_read_write_eq() {\n        let src = ServerCmd::StopSound {\n            entity_id: 17,\n            channel: 3,\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_update_colors_read_write_eq() {\n        let src = ServerCmd::UpdateColors {\n            player_id: 11,\n            new_colors: PlayerColor::new(4, 13),\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_set_pause_read_write_eq() {\n        let src = ServerCmd::SetPause { paused: true };\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_sign_on_stage_read_write_eq() {\n        let src = ServerCmd::SignOnStage {\n            stage: SignOnStage::Begin,\n        };\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_center_print_read_write_eq() {\n        let src = ServerCmd::CenterPrint {\n            text: String::from(\"Center print test\"),\n        };\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_finale_read_write_eq() {\n        let src = ServerCmd::Finale {\n            text: String::from(\"Finale test\"),\n        };\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_cd_track_read_write_eq() {\n        let src = ServerCmd::CdTrack { track: 5, loop_: 1 };\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_server_cmd_cutscene_read_write_eq() {\n        let src = ServerCmd::Cutscene {\n            text: String::from(\"Cutscene test\"),\n        };\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ServerCmd::deserialize(&mut reader).unwrap().unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_client_cmd_string_cmd_read_write_eq() {\n        let src = ClientCmd::StringCmd {\n            cmd: String::from(\"StringCmd test\"),\n        };\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ClientCmd::deserialize(&mut reader).unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    #[test]\n    fn test_client_cmd_move_read_write_eq() {\n        let src = ClientCmd::Move {\n            send_time: Duration::milliseconds(1234),\n            // have to use angles that won't lose precision from write_angle\n            angles: Vector3::new(Deg(90.0), Deg(-90.0), Deg(0.0)),\n            fwd_move: 27,\n            side_move: 85,\n            up_move: 76,\n            button_flags: ButtonFlags::empty(),\n            impulse: 121,\n        };\n\n        let mut packet = Vec::new();\n        src.serialize(&mut packet).unwrap();\n        let mut reader = BufReader::new(packet.as_slice());\n        let dst = ClientCmd::deserialize(&mut reader).unwrap();\n\n        assert_eq!(src, dst);\n    }\n\n    fn gen_qsocket_pair() -> (QSocket, QSocket) {\n        let src_udp = UdpSocket::bind(\"localhost:0\").unwrap();\n        let src_addr = src_udp.local_addr().unwrap();\n\n        let dst_udp = UdpSocket::bind(\"localhost:0\").unwrap();\n        let dst_addr = dst_udp.local_addr().unwrap();\n\n        (\n            QSocket::new(src_udp, dst_addr),\n            QSocket::new(dst_udp, src_addr),\n        )\n    }\n\n    #[test]\n    fn test_qsocket_send_msg_short() {\n        let (mut src, mut dst) = gen_qsocket_pair();\n\n        let message = String::from(\"test message\").into_bytes();\n        src.begin_send_msg(&message).unwrap();\n        let received = dst.recv_msg(BlockingMode::Blocking).unwrap();\n        assert_eq!(message, received);\n\n        // TODO: assert can_send == true, send_next == false, etc\n    }\n\n    #[test]\n    fn test_qsocket_send_msg_unreliable_recv_msg_eq() {\n        let (mut src, mut dst) = gen_qsocket_pair();\n\n        let message = String::from(\"test message\").into_bytes();\n        src.send_msg_unreliable(&message).unwrap();\n        let received = dst.recv_msg(BlockingMode::Blocking).unwrap();\n        assert_eq!(message, received);\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_qsocket_send_msg_unreliable_zero_length_fails() {\n        let (mut src, _) = gen_qsocket_pair();\n\n        let message = [];\n        src.send_msg_unreliable(&message).unwrap();\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_qsocket_send_msg_unreliable_exceeds_max_length_fails() {\n        let (mut src, _) = gen_qsocket_pair();\n\n        let message = [0; MAX_DATAGRAM + 1];\n        src.send_msg_unreliable(&message).unwrap();\n    }\n}\n"
  },
  {
    "path": "src/common/pak.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n//! Quake PAK archive manipulation.\n\nuse std::{\n    collections::{hash_map::Iter, HashMap},\n    fs,\n    io::{self, Read, Seek, SeekFrom},\n    path::Path,\n};\n\nuse byteorder::{LittleEndian, ReadBytesExt};\nuse thiserror::Error;\n\nconst PAK_MAGIC: [u8; 4] = [b'P', b'A', b'C', b'K'];\nconst PAK_ENTRY_SIZE: usize = 64;\n\n#[derive(Error, Debug)]\npub enum PakError {\n    #[error(\"I/O error: {0}\")]\n    Io(#[from] io::Error),\n    #[error(\"Invalid magic number: {0:?}\")]\n    InvalidMagicNumber([u8; 4]),\n    #[error(\"Invalid file table offset: {0}\")]\n    InvalidTableOffset(i32),\n    #[error(\"Invalid file table size: {0}\")]\n    InvalidTableSize(i32),\n    #[error(\"Invalid file offset: {0}\")]\n    InvalidFileOffset(i32),\n    #[error(\"Invalid file size: {0}\")]\n    InvalidFileSize(i32),\n    #[error(\"File name too long: {0}\")]\n    FileNameTooLong(String),\n    #[error(\"Non-UTF-8 file name: {0}\")]\n    NonUtf8FileName(#[from] std::string::FromUtf8Error),\n    #[error(\"No such file in PAK archive: {0}\")]\n    NoSuchFile(String),\n}\n\n/// An open Pak archive.\n#[derive(Debug)]\npub struct Pak(HashMap<String, Box<[u8]>>);\n\nimpl Pak {\n    // TODO: rename to from_path or similar\n    pub fn new<P>(path: P) -> Result<Pak, PakError>\n    where\n        P: AsRef<Path>,\n    {\n        debug!(\"Opening {}\", path.as_ref().to_str().unwrap());\n\n        let mut infile = fs::File::open(path)?;\n        let mut magic = [0u8; 4];\n        infile.read(&mut magic)?;\n\n        if magic != PAK_MAGIC {\n            Err(PakError::InvalidMagicNumber(magic))?;\n        }\n\n        // Locate the file table\n        let table_offset = match infile.read_i32::<LittleEndian>()? {\n            o if o <= 0 => Err(PakError::InvalidTableOffset(o))?,\n            o => o as u32,\n        };\n\n        let table_size = match infile.read_i32::<LittleEndian>()? {\n            s if s <= 0 || s as usize % PAK_ENTRY_SIZE != 0 => Err(PakError::InvalidTableSize(s))?,\n            s => s as u32,\n        };\n\n        let mut map = HashMap::new();\n\n        for i in 0..(table_size as usize / PAK_ENTRY_SIZE) {\n            let entry_offset = table_offset as u64 + (i * PAK_ENTRY_SIZE) as u64;\n            infile.seek(SeekFrom::Start(entry_offset))?;\n\n            let mut path_bytes = [0u8; 56];\n            infile.read(&mut path_bytes)?;\n\n            let file_offset = match infile.read_i32::<LittleEndian>()? {\n                o if o <= 0 => Err(PakError::InvalidFileOffset(o))?,\n                o => o as u32,\n            };\n\n            let file_size = match infile.read_i32::<LittleEndian>()? {\n                s if s <= 0 => Err(PakError::InvalidFileSize(s))?,\n                s => s as u32,\n            };\n\n            let last = path_bytes\n                .iter()\n                .position(|b| *b == 0)\n                .ok_or(PakError::FileNameTooLong(\n                    String::from_utf8_lossy(&path_bytes).into_owned(),\n                ))?;\n            let path = String::from_utf8(path_bytes[0..last].to_vec())?;\n            infile.seek(SeekFrom::Start(file_offset as u64))?;\n\n            let mut data: Vec<u8> = Vec::with_capacity(file_size as usize);\n            (&mut infile)\n                .take(file_size as u64)\n                .read_to_end(&mut data)?;\n\n            map.insert(path, data.into_boxed_slice());\n        }\n\n        Ok(Pak(map))\n    }\n\n    /// Opens a file in the file tree for reading.\n    ///\n    /// # Examples\n    /// ```no_run\n    /// # extern crate richter;\n    /// use richter::common::pak::Pak;\n    ///\n    /// # fn main() {\n    /// let mut pak = Pak::new(\"pak0.pak\").unwrap();\n    /// let progs_dat = pak.open(\"progs.dat\").unwrap();\n    /// # }\n    /// ```\n    pub fn open<S>(&self, path: S) -> Result<&[u8], PakError>\n    where\n        S: AsRef<str>,\n    {\n        let path = path.as_ref();\n        self.0\n            .get(path)\n            .map(|s| s.as_ref())\n            .ok_or(PakError::NoSuchFile(path.to_owned()))\n    }\n\n    pub fn iter<'a>(&self) -> Iter<String, impl AsRef<[u8]>> {\n        self.0.iter()\n    }\n}\n"
  },
  {
    "path": "src/common/parse/console.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse crate::common::parse::quoted;\n\nuse nom::{\n    branch::alt,\n    bytes::complete::tag,\n    character::complete::{line_ending, not_line_ending, one_of, space0},\n    combinator::{opt, recognize},\n    multi::{many0, many1},\n    sequence::{delimited, preceded, terminated, tuple},\n};\n\n/// Match a line comment.\n///\n/// A line comment is considered to be composed of:\n/// - Two forward slashes (`\"//\"`)\n/// - Zero or more characters, excluding line endings (`\"\\n\"` or `\"\\r\\n\"`)\npub fn line_comment(input: &str) -> nom::IResult<&str, &str> {\n    recognize(preceded(tag(\"//\"), not_line_ending))(input)\n}\n\n/// Match an empty line.\n///\n/// An empty line is considered to be composed of:\n/// - Zero or more spaces or tabs\n/// - An optional line comment\n/// - A line ending (`\"\\n\"` or `\"\\r\\n\"`)\npub fn empty_line(input: &str) -> nom::IResult<&str, &str> {\n    recognize(tuple((space0, opt(line_comment), line_ending)))(input)\n}\n\n/// Match a basic argument terminator.\n///\n/// Basic (unquoted) arguments can be terminated by any of:\n/// - A non-newline whitespace character (`\" \"` or `\"\\t\"`)\n/// - The beginning of a line comment (`\"//\"`)\n/// - A line ending (`\"\\r\\n\"` or `\"\\n\"`)\n/// - A semicolon (`\";\"`)\npub fn basic_arg_terminator(input: &str) -> nom::IResult<&str, &str> {\n    alt((recognize(one_of(\" \\t;\")), line_ending, tag(\"//\")))(input)\n}\n\n/// Match a sequence of any non-whitespace, non-line-ending ASCII characters,\n/// ending with whitespace, a line comment or a line terminator.\npub fn basic_arg(input: &str) -> nom::IResult<&str, &str> {\n    // break on comment, semicolon, quote, or whitespace\n    let patterns = [\"//\", \";\", \"\\\"\", \" \", \"\\t\", \"\\r\\n\", \"\\n\"];\n\n    // length in bytes of matched sequence\n    let mut match_len = 0;\n\n    // consume characters not matching any of the patterns\n    loop {\n        let remaining = input.split_at(match_len).1;\n        let terminator = patterns.iter().fold(false, |found_match: bool, p| {\n            found_match || remaining.starts_with(*p)\n        });\n\n        let chr = match remaining.chars().nth(0) {\n            Some(c) => c,\n            None => break,\n        };\n\n        if terminator || !chr.is_ascii() || chr.is_ascii_control() {\n            break;\n        }\n\n        match_len += chr.len_utf8();\n    }\n\n    match match_len {\n        // TODO: more descriptive error?\n        0 => Err(nom::Err::Error((input, nom::error::ErrorKind::Many1))),\n        len => {\n            let (matched, rest) = input.split_at(len);\n            Ok((rest, matched))\n        }\n    }\n}\n\n/// Match a basic argument or a quoted string.\npub fn arg(input: &str) -> nom::IResult<&str, &str> {\n    alt((quoted, basic_arg))(input)\n}\n\n/// Match a command terminator.\n///\n/// Commands can be terminated by either:\n/// - A semicolon (`\";\"`), or\n/// - An empty line (see `empty_line`)\npub fn command_terminator(input: &str) -> nom::IResult<&str, &str> {\n    alt((empty_line, tag(\";\")))(input)\n}\n\n/// Match a single command.\n///\n/// A command is considered to be composed of:\n/// - Zero or more leading non-newline whitespace characters\n/// - One or more arguments, separated by non-newline whitespace characters\n/// - A command terminator (see `command_terminator`)\npub fn command(input: &str) -> nom::IResult<&str, Vec<&str>> {\n    terminated(many1(preceded(space0, arg)), command_terminator)(input)\n}\n\npub fn commands(input: &str) -> nom::IResult<&str, Vec<Vec<&str>>> {\n    delimited(\n        many0(empty_line),\n        many0(terminated(command, many0(empty_line))),\n        many0(empty_line),\n    )(input)\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_line_comment() {\n        let result = line_comment(\"// a comment\\nnext line\");\n        assert_eq!(result, Ok((\"\\nnext line\", \"// a comment\")));\n    }\n\n    #[test]\n    fn test_empty_line() {\n        let result = empty_line(\"  \\t \\t // a comment\\nnext line\");\n        assert_eq!(result, Ok((\"next line\", \"  \\t \\t // a comment\\n\")));\n    }\n\n    #[test]\n    fn test_basic_arg_space_terminated() {\n        let result = basic_arg(\"space_terminated \");\n        assert_eq!(result, Ok((\" \", \"space_terminated\")));\n    }\n\n    #[test]\n    fn test_basic_arg_newline_terminated() {\n        let result = basic_arg(\"newline_terminated\\n\");\n        assert_eq!(result, Ok((\"\\n\", \"newline_terminated\")));\n    }\n\n    #[test]\n    fn test_basic_arg_semicolon_terminated() {\n        let result = basic_arg(\"semicolon_terminated;\");\n        assert_eq!(result, Ok((\";\", \"semicolon_terminated\")));\n    }\n\n    #[test]\n    fn test_arg_basic() {\n        let result = arg(\"basic_arg \\t;\");\n        assert_eq!(result, Ok((\" \\t;\", \"basic_arg\")));\n    }\n\n    #[test]\n    fn test_quoted_arg() {\n        let result = arg(\"\\\"quoted argument\\\";\\n\");\n        assert_eq!(result, Ok((\";\\n\", \"quoted argument\")));\n    }\n\n    #[test]\n    fn test_command_basic() {\n        let result = command(\"arg_0 arg_1;\\n\");\n        assert_eq!(result, Ok((\"\\n\", vec![\"arg_0\", \"arg_1\"])));\n    }\n\n    #[test]\n    fn test_command_quoted() {\n        let result = command(\"bind \\\"space\\\" \\\"+jump\\\";\\n\");\n        assert_eq!(result, Ok((\"\\n\", vec![\"bind\", \"space\", \"+jump\"])));\n    }\n\n    #[test]\n    fn test_command_comment() {\n        let result = command(\"bind \\\"space\\\" \\\"+jump\\\" // bind space to jump\\n\\n\");\n        assert_eq!(result, Ok((\"\\n\", vec![\"bind\", \"space\", \"+jump\"])));\n    }\n\n    #[test]\n    fn test_commands_quake_rc() {\n        let script = \"\n// load the base configuration\nexec default.cfg\n\n// load the last saved configuration\nexec config.cfg\n\n// run a user script file if present\nexec autoexec.cfg\n\n//\n// stuff command line statements\n//\nstuffcmds\n\n// start demos if not already running a server\nstartdemos demo1 demo2 demo3\n\";\n        let expected = vec![\n            vec![\"exec\", \"default.cfg\"],\n            vec![\"exec\", \"config.cfg\"],\n            vec![\"exec\", \"autoexec.cfg\"],\n            vec![\"stuffcmds\"],\n            vec![\"startdemos\", \"demo1\", \"demo2\", \"demo3\"],\n        ];\n\n        let result = commands(script);\n        assert_eq!(result, Ok((\"\", expected)));\n    }\n}\n"
  },
  {
    "path": "src/common/parse/map.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::collections::HashMap;\n\nuse crate::common::parse::quoted;\n\nuse nom::{\n    bytes::complete::tag,\n    character::complete::newline,\n    combinator::{all_consuming, map},\n    multi::many0,\n    sequence::{delimited, separated_pair, terminated},\n};\n\n// \"name\" \"value\"\\n\npub fn entity_attribute(input: &str) -> nom::IResult<&str, (&str, &str)> {\n    terminated(separated_pair(quoted, tag(\" \"), quoted), newline)(input)\n}\n\n// {\n// \"name1\" \"value1\"\n// \"name2\" \"value2\"\n// \"name3\" \"value3\"\n// }\npub fn entity(input: &str) -> nom::IResult<&str, HashMap<&str, &str>> {\n    delimited(\n        terminated(tag(\"{\"), newline),\n        map(many0(entity_attribute), |attrs| attrs.into_iter().collect()),\n        terminated(tag(\"}\"), newline),\n    )(input)\n}\n\npub fn entities(input: &str) -> Result<Vec<HashMap<&str, &str>>, failure::Error> {\n    let input = input.strip_suffix('\\0').unwrap_or(input);\n    match all_consuming(many0(entity))(input) {\n        Ok((\"\", entities)) => Ok(entities),\n        Ok(_) => unreachable!(),\n        Err(e) => bail!(\"parse failed: {}\", e),\n    }\n}\n"
  },
  {
    "path": "src/common/parse/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\npub mod console;\npub mod map;\n\nuse cgmath::Vector3;\nuse nom::{\n    branch::alt,\n    bytes::complete::{tag, take_while1},\n    character::complete::{alphanumeric1, one_of, space1},\n    combinator::map,\n    sequence::{delimited, tuple},\n};\nuse winit::event::ElementState;\n\npub use self::{console::commands, map::entities};\n\npub fn non_newline_spaces(input: &str) -> nom::IResult<&str, &str> {\n    space1(input)\n}\n\nfn string_contents(input: &str) -> nom::IResult<&str, &str> {\n    take_while1(|c: char| !\"\\\"\".contains(c) && c.is_ascii() && !c.is_ascii_control())(input)\n}\n\npub fn quoted(input: &str) -> nom::IResult<&str, &str> {\n    delimited(tag(\"\\\"\"), string_contents, tag(\"\\\"\"))(input)\n}\n\npub fn action(input: &str) -> nom::IResult<&str, (ElementState, &str)> {\n    tuple((\n        map(one_of(\"+-\"), |c| match c {\n            '+' => ElementState::Pressed,\n            '-' => ElementState::Released,\n            _ => unreachable!(),\n        }),\n        alphanumeric1,\n    ))(input)\n}\n\npub fn newline(input: &str) -> nom::IResult<&str, &str> {\n    nom::character::complete::line_ending(input)\n}\n\n// TODO: rename to line_terminator and move to console module\npub fn line_ending(input: &str) -> nom::IResult<&str, &str> {\n    alt((tag(\";\"), nom::character::complete::line_ending))(input)\n}\n\npub fn vector3_components<S>(src: S) -> Option<[f32; 3]>\nwhere\n    S: AsRef<str>,\n{\n    let src = src.as_ref();\n\n    let components: Vec<_> = src.split(\" \").collect();\n    if components.len() != 3 {\n        return None;\n    }\n\n    let x: f32 = match components[0].parse().ok() {\n        Some(p) => p,\n        None => return None,\n    };\n\n    let y: f32 = match components[1].parse().ok() {\n        Some(p) => p,\n        None => return None,\n    };\n\n    let z: f32 = match components[2].parse().ok() {\n        Some(p) => p,\n        None => return None,\n    };\n\n    Some([x, y, z])\n}\n\npub fn vector3<S>(src: S) -> Option<Vector3<f32>>\nwhere\n    S: AsRef<str>,\n{\n    let src = src.as_ref();\n\n    let components: Vec<_> = src.split(\" \").collect();\n    if components.len() != 3 {\n        return None;\n    }\n\n    let x: f32 = match components[0].parse().ok() {\n        Some(p) => p,\n        None => return None,\n    };\n\n    let y: f32 = match components[1].parse().ok() {\n        Some(p) => p,\n        None => return None,\n    };\n\n    let z: f32 = match components[2].parse().ok() {\n        Some(p) => p,\n        None => return None,\n    };\n\n    Some(Vector3::new(x, y, z))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_quoted() {\n        let s = \"\\\"hello\\\"\";\n        assert_eq!(quoted(s), Ok((\"\", \"hello\")))\n    }\n\n    #[test]\n    fn test_action() {\n        let s = \"+up\";\n        assert_eq!(action(s), Ok((\"\", (ElementState::Pressed, \"up\"))))\n    }\n}\n"
  },
  {
    "path": "src/common/sprite.rs",
    "content": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::io::{BufReader, Read, Seek};\n\nuse crate::common::{engine, model::SyncType};\n\nuse byteorder::{LittleEndian, ReadBytesExt};\nuse cgmath::Vector3;\nuse chrono::Duration;\nuse num::FromPrimitive;\n\nconst MAGIC: u32 = ('I' as u32) << 0 | ('D' as u32) << 8 | ('S' as u32) << 16 | ('P' as u32) << 24;\nconst VERSION: u32 = 1;\n\n#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]\npub enum SpriteKind {\n    ViewPlaneParallelUpright = 0,\n    Upright = 1,\n    ViewPlaneParallel = 2,\n    Oriented = 3,\n    ViewPlaneParallelOriented = 4,\n}\n\n#[derive(Debug)]\npub struct SpriteModel {\n    kind: SpriteKind,\n    max_width: usize,\n    max_height: usize,\n    radius: f32,\n    frames: Vec<SpriteFrame>,\n}\n\nimpl SpriteModel {\n    pub fn min(&self) -> Vector3<f32> {\n        Vector3::new(\n            -(self.max_width as f32) / 2.0,\n            -(self.max_width as f32) / 2.0,\n            -(self.max_height as f32) / 2.0,\n        )\n    }\n\n    pub fn max(&self) -> Vector3<f32> {\n        Vector3::new(\n            self.max_width as f32 / 2.0,\n            self.max_width as f32 / 2.0,\n            self.max_height as f32 / 2.0,\n        )\n    }\n\n    pub fn radius(&self) -> f32 {\n        self.radius\n    }\n\n    pub fn kind(&self) -> SpriteKind {\n        self.kind\n    }\n\n    pub fn frames(&self) -> &[SpriteFrame] {\n        &self.frames\n    }\n}\n\n#[derive(Debug)]\npub enum SpriteFrame {\n    Static {\n        frame: SpriteSubframe,\n    },\n    Animated {\n        subframes: Vec<SpriteSubframe>,\n        durations: Vec<Duration>,\n    },\n}\n\n#[derive(Debug)]\npub struct SpriteSubframe {\n    width: u32,\n    height: u32,\n    up: f32,\n    down: f32,\n    left: f32,\n    right: f32,\n    indexed: Vec<u8>,\n}\n\nimpl SpriteSubframe {\n    pub fn width(&self) -> u32 {\n        self.width\n    }\n\n    pub fn height(&self) -> u32 {\n        self.height\n    }\n\n    pub fn indexed(&self) -> &[u8] {\n        &self.indexed\n    }\n}\n\npub fn load<R>(data: R) -> SpriteModel\nwhere\n    R: Read + Seek,\n{\n    let mut reader = BufReader::new(data);\n\n    let magic = reader.read_u32::<LittleEndian>().unwrap();\n    if magic != MAGIC {\n        panic!(\n            \"Bad magic number for sprite model (got {}, should be {})\",\n            magic, MAGIC\n        );\n    }\n\n    let version = reader.read_u32::<LittleEndian>().unwrap();\n    if version != VERSION {\n        panic!(\n            \"Bad version number for sprite model (got {}, should be {})\",\n            version, VERSION\n        );\n    }\n\n    // TODO: use an enum for this\n    let kind = SpriteKind::from_i32(reader.read_i32::<LittleEndian>().unwrap()).unwrap();\n\n    let radius = reader.read_f32::<LittleEndian>().unwrap();\n\n    let max_width = match reader.read_i32::<LittleEndian>().unwrap() {\n        w if w < 0 => panic!(\"Negative max width ({})\", w),\n        w => w as usize,\n    };\n\n    let max_height = match reader.read_i32::<LittleEndian>().unwrap() {\n        h if h < 0 => panic!(\"Negative max height ({})\", h),\n        h => h as usize,\n    };\n\n    let frame_count = match reader.read_i32::<LittleEndian>().unwrap() {\n        c if c < 1 => panic!(\"Invalid frame count ({}), must be at least 1\", c),\n        c => c as usize,\n    };\n\n    let _beam_len = match reader.read_i32::<LittleEndian>().unwrap() {\n        l if l < 0 => panic!(\"Negative beam length ({})\", l),\n        l => l as usize,\n    };\n\n    debug!(\n        \"max_width = {} max_height = {} frame_count = {}\",\n        max_width, max_height, frame_count\n    );\n\n    let _sync_type = SyncType::from_i32(reader.read_i32::<LittleEndian>().unwrap()).unwrap();\n\n    let mut frames = Vec::with_capacity(frame_count);\n\n    for i in 0..frame_count {\n        let frame_kind_int = reader.read_i32::<LittleEndian>().unwrap();\n\n        // TODO: substitute out this magic number\n        if frame_kind_int == 0 {\n            let origin_x = reader.read_i32::<LittleEndian>().unwrap();\n            let origin_z = reader.read_i32::<LittleEndian>().unwrap();\n\n            let width = match reader.read_i32::<LittleEndian>().unwrap() {\n                w if w < 0 => panic!(\"Negative frame width ({})\", w),\n                w => w,\n            };\n\n            let height = match reader.read_i32::<LittleEndian>().unwrap() {\n                h if h < 0 => panic!(\"Negative frame height ({})\", h),\n                h => h,\n            };\n\n            debug!(\"Frame {}: width = {} height = {}\", i, width, height);\n\n            let index_count = (width * height) as usize;\n            let mut indices = Vec::with_capacity(index_count);\n            for _ in 0..index_count as usize {\n                indices.push(reader.read_u8().unwrap());\n            }\n\n            frames.push(SpriteFrame::Static {\n                frame: SpriteSubframe {\n                    width: width as u32,\n                    height: height as u32,\n                    up: origin_z as f32,\n                    down: (origin_z - height) as f32,\n                    left: origin_x as f32,\n                    right: (width + origin_x) as f32,\n                    indexed: indices,\n                },\n            });\n        } else {\n            let subframe_count = match reader.read_i32::<LittleEndian>().unwrap() {\n                c if c < 0 => panic!(\"Negative subframe count ({}) in frame {}\", c, i),\n                c => c as usize,\n            };\n\n            let mut durations = Vec::with_capacity(subframe_count);\n            for _ in 0..subframe_count {\n                durations.push(engine::duration_from_f32(\n                    reader.read_f32::<LittleEndian>().unwrap(),\n                ));\n            }\n\n            let mut subframes = Vec::with_capacity(subframe_count);\n            for _ in 0..subframe_count {\n                let origin_x = reader.read_i32::<LittleEndian>().unwrap();\n                let origin_z = reader.read_i32::<LittleEndian>().unwrap();\n\n                let width = match reader.read_i32::<LittleEndian>().unwrap() {\n                    w if w < 0 => panic!(\"Negative subframe width ({}) in frame {}\", w, i),\n                    w => w,\n                };\n\n                let height = match reader.read_i32::<LittleEndian>().unwrap() {\n                    h if h < 0 => panic!(\"Negative subframe height ({}) in frame {}\", h, i),\n                    h => h,\n                };\n\n                let index_count = (width * height) as usize;\n                let mut indices = Vec::with_capacity(index_count);\n                for _ in 0..index_count as usize {\n                    indices.push(reader.read_u8().unwrap());\n                }\n\n                subframes.push(SpriteSubframe {\n                    width: width as u32,\n                    height: height as u32,\n                    up: origin_z as f32,\n                    down: (origin_z - height) as f32,\n                    left: origin_x as f32,\n                    right: (width + origin_x) as f32,\n                    indexed: indices,\n                });\n            }\n            frames.push(SpriteFrame::Animated {\n                durations,\n                subframes,\n            });\n        }\n    }\n\n    SpriteModel {\n        kind,\n        max_width,\n        max_height,\n        radius,\n        frames,\n    }\n}\n"
  },
  {
    "path": "src/common/util.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::mem::size_of;\n\nuse byteorder::{LittleEndian, ReadBytesExt};\n\n/// A plain-old-data type.\npub trait Pod: 'static + Copy + Sized + Send + Sync {}\nimpl<T: 'static + Copy + Sized + Send + Sync> Pod for T {}\n\n/// Read a `[f32; 3]` in little-endian byte order.\npub fn read_f32_3<R>(reader: &mut R) -> Result<[f32; 3], std::io::Error>\nwhere\n    R: ReadBytesExt,\n{\n    let mut ar = [0.0f32; 3];\n    reader.read_f32_into::<LittleEndian>(&mut ar)?;\n    Ok(ar)\n}\n\n/// Read a null-terminated sequence of bytes and convert it into a `String`.\n///\n/// The zero byte is consumed.\n///\n/// ## Panics\n/// - If the end of the input is reached before a zero byte is found.\npub fn read_cstring<R>(src: &mut R) -> Result<String, std::string::FromUtf8Error>\nwhere\n    R: std::io::BufRead,\n{\n    let mut bytes: Vec<u8> = Vec::new();\n    src.read_until(0, &mut bytes).unwrap();\n    bytes.pop();\n    String::from_utf8(bytes)\n}\n\npub unsafe fn any_as_bytes<T>(t: &T) -> &[u8]\nwhere\n    T: Pod,\n{\n    std::slice::from_raw_parts((t as *const T) as *const u8, size_of::<T>())\n}\n\npub unsafe fn any_slice_as_bytes<T>(t: &[T]) -> &[u8]\nwhere\n    T: Pod,\n{\n    std::slice::from_raw_parts(t.as_ptr() as *const u8, size_of::<T>() * t.len())\n}\n\npub unsafe fn bytes_as_any<T>(bytes: &[u8]) -> T\nwhere\n    T: Pod,\n{\n    assert_eq!(bytes.len(), size_of::<T>());\n    std::ptr::read_unaligned(bytes.as_ptr() as *const T)\n}\n\npub unsafe fn any_as_u32_slice<T>(t: &T) -> &[u32]\nwhere\n    T: Pod,\n{\n    assert!(size_of::<T>() % size_of::<u32>() == 0);\n    std::slice::from_raw_parts(\n        (t as *const T) as *const u32,\n        size_of::<T>() / size_of::<u32>(),\n    )\n}\n"
  },
  {
    "path": "src/common/vfs.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{\n    fs::File,\n    io::{self, BufReader, Cursor, Read, Seek, SeekFrom},\n    path::{Path, PathBuf},\n};\n\nuse crate::common::pak::{Pak, PakError};\n\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\npub enum VfsError {\n    #[error(\"Couldn't load pakfile: {0}\")]\n    Pak(#[from] PakError),\n    #[error(\"File does not exist: {0}\")]\n    NoSuchFile(String),\n}\n\n#[derive(Debug)]\nenum VfsComponent {\n    Pak(Pak),\n    Directory(PathBuf),\n}\n\n#[derive(Debug)]\npub struct Vfs {\n    components: Vec<VfsComponent>,\n}\n\nimpl Vfs {\n    pub fn new() -> Vfs {\n        Vfs {\n            components: Vec::new(),\n        }\n    }\n\n    /// Initializes the virtual filesystem using a base directory.\n    pub fn with_base_dir(base_dir: PathBuf) -> Vfs {\n        let mut vfs = Vfs::new();\n\n        let mut game_dir = base_dir;\n        game_dir.push(\"id1\");\n\n        if !game_dir.is_dir() {\n            log::error!(concat!(\n                \"`id1/` directory does not exist! Use the `--base-dir` option with the name of the\",\n                \" directory which contains `id1/`.\"\n            ));\n\n            std::process::exit(1);\n        }\n\n        vfs.add_directory(&game_dir).unwrap();\n\n        // ...then add PAK archives.\n        let mut num_paks = 0;\n        let mut pak_path = game_dir;\n        for vfs_id in 0..crate::common::MAX_PAKFILES {\n            // Add the file name.\n            pak_path.push(format!(\"pak{}.pak\", vfs_id));\n\n            // Keep adding PAKs until we don't find one or we hit MAX_PAKFILES.\n            if !pak_path.exists() {\n                // If the lowercase path doesn't exist, try again with uppercase.\n                pak_path.pop();\n                pak_path.push(format!(\"PAK{}.PAK\", vfs_id));\n                if !pak_path.exists() {\n                    break;\n                }\n            }\n\n            vfs.add_pakfile(&pak_path).unwrap();\n            num_paks += 1;\n\n            // Remove the file name, leaving the game directory.\n            pak_path.pop();\n        }\n\n        if num_paks == 0 {\n            log::warn!(\"No PAK files found.\");\n        }\n\n        vfs\n    }\n\n    pub fn add_pakfile<P>(&mut self, path: P) -> Result<(), VfsError>\n    where\n        P: AsRef<Path>,\n    {\n        let path = path.as_ref();\n        self.components.push(VfsComponent::Pak(Pak::new(path)?));\n        Ok(())\n    }\n\n    pub fn add_directory<P>(&mut self, path: P) -> Result<(), VfsError>\n    where\n        P: AsRef<Path>,\n    {\n        self.components\n            .push(VfsComponent::Directory(path.as_ref().to_path_buf()));\n        Ok(())\n    }\n\n    pub fn open<S>(&self, virtual_path: S) -> Result<VirtualFile, VfsError>\n    where\n        S: AsRef<str>,\n    {\n        let vp = virtual_path.as_ref();\n\n        // iterate in reverse so later PAKs overwrite earlier ones\n        for c in self.components.iter().rev() {\n            match c {\n                VfsComponent::Pak(pak) => {\n                    if let Ok(f) = pak.open(vp) {\n                        return Ok(VirtualFile::PakBacked(Cursor::new(f)));\n                    }\n                }\n\n                VfsComponent::Directory(path) => {\n                    let mut full_path = path.to_owned();\n                    full_path.push(vp);\n\n                    if let Ok(f) = File::open(full_path) {\n                        return Ok(VirtualFile::FileBacked(BufReader::new(f)));\n                    }\n                }\n            }\n        }\n\n        Err(VfsError::NoSuchFile(vp.to_owned()))\n    }\n}\n\npub enum VirtualFile<'a> {\n    PakBacked(Cursor<&'a [u8]>),\n    FileBacked(BufReader<File>),\n}\n\nimpl<'a> Read for VirtualFile<'a> {\n    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n        match self {\n            VirtualFile::PakBacked(curs) => curs.read(buf),\n            VirtualFile::FileBacked(file) => file.read(buf),\n        }\n    }\n}\n\nimpl<'a> Seek for VirtualFile<'a> {\n    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {\n        match self {\n            VirtualFile::PakBacked(curs) => curs.seek(pos),\n            VirtualFile::FileBacked(file) => file.seek(pos),\n        }\n    }\n}\n"
  },
  {
    "path": "src/common/wad.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nuse std::{\n    collections::HashMap,\n    convert::From,\n    fmt::{self, Display},\n    io::{self, BufReader, Cursor, Read, Seek, SeekFrom},\n};\n\nuse crate::common::util;\n\nuse byteorder::{LittleEndian, ReadBytesExt};\nuse failure::{Backtrace, Context, Error, Fail};\n\n// see definition of lumpinfo_t:\n// https://github.com/id-Software/Quake/blob/master/WinQuake/wad.h#L54-L63\nconst LUMPINFO_SIZE: usize = 32;\nconst MAGIC: u32 = 'W' as u32 | ('A' as u32) << 8 | ('D' as u32) << 16 | ('2' as u32) << 24;\n\n#[derive(Debug)]\npub struct WadError {\n    inner: Context<WadErrorKind>,\n}\n\nimpl WadError {\n    pub fn kind(&self) -> WadErrorKind {\n        *self.inner.get_context()\n    }\n}\n\nimpl From<WadErrorKind> for WadError {\n    fn from(kind: WadErrorKind) -> Self {\n        WadError {\n            inner: Context::new(kind),\n        }\n    }\n}\n\nimpl From<Context<WadErrorKind>> for WadError {\n    fn from(inner: Context<WadErrorKind>) -> Self {\n        WadError { inner }\n    }\n}\n\nimpl From<io::Error> for WadError {\n    fn from(io_error: io::Error) -> Self {\n        let kind = io_error.kind();\n        match kind {\n            io::ErrorKind::UnexpectedEof => io_error.context(WadErrorKind::UnexpectedEof).into(),\n            _ => io_error.context(WadErrorKind::Io).into(),\n        }\n    }\n}\n\nimpl Fail for WadError {\n    fn cause(&self) -> Option<&dyn Fail> {\n        self.inner.cause()\n    }\n\n    fn backtrace(&self) -> Option<&Backtrace> {\n        self.inner.backtrace()\n    }\n}\n\nimpl Display for WadError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        Display::fmt(&self.inner, f)\n    }\n}\n\n#[derive(Clone, Copy, Eq, PartialEq, Debug, Fail)]\npub enum WadErrorKind {\n    #[fail(display = \"CONCHARS must be loaded with the dedicated function\")]\n    ConcharsUseDedicatedFunction,\n    #[fail(display = \"Invalid magic number\")]\n    InvalidMagicNumber,\n    #[fail(display = \"I/O error\")]\n    Io,\n    #[fail(display = \"No such file in WAD\")]\n    NoSuchFile,\n    #[fail(display = \"Failed to load QPic\")]\n    QPicNotLoaded,\n    #[fail(display = \"Unexpected end of data\")]\n    UnexpectedEof,\n}\n\npub struct QPic {\n    width: u32,\n    height: u32,\n    indices: Box<[u8]>,\n}\n\nimpl QPic {\n    pub fn load<R>(data: R) -> Result<QPic, WadError>\n    where\n        R: Read + Seek,\n    {\n        let mut reader = BufReader::new(data);\n\n        let width = reader.read_u32::<LittleEndian>()?;\n        let height = reader.read_u32::<LittleEndian>()?;\n\n        let mut indices = Vec::new();\n        (&mut reader)\n            .take((width * height) as u64)\n            .read_to_end(&mut indices)?;\n\n        Ok(QPic {\n            width,\n            height,\n            indices: indices.into_boxed_slice(),\n        })\n    }\n\n    pub fn width(&self) -> u32 {\n        self.width\n    }\n\n    pub fn height(&self) -> u32 {\n        self.height\n    }\n\n    pub fn indices(&self) -> &[u8] {\n        &self.indices\n    }\n}\n\nstruct LumpInfo {\n    offset: u32,\n    size: u32,\n    name: String,\n}\n\npub struct Wad {\n    files: HashMap<String, Box<[u8]>>,\n}\n\nimpl Wad {\n    pub fn load<R>(data: R) -> Result<Wad, Error>\n    where\n        R: Read + Seek,\n    {\n        let mut reader = BufReader::new(data);\n\n        let magic = reader.read_u32::<LittleEndian>()?;\n        if magic != MAGIC {\n            return Err(WadErrorKind::InvalidMagicNumber.into());\n        }\n\n        let lump_count = reader.read_u32::<LittleEndian>()?;\n        let lumpinfo_ofs = reader.read_u32::<LittleEndian>()?;\n\n        reader.seek(SeekFrom::Start(lumpinfo_ofs as u64))?;\n\n        let mut lump_infos = Vec::new();\n\n        for _ in 0..lump_count {\n            // TODO sanity check these values\n            let offset = reader.read_u32::<LittleEndian>()?;\n            let _size_on_disk = reader.read_u32::<LittleEndian>()?;\n            let size = reader.read_u32::<LittleEndian>()?;\n            let _type = reader.read_u8()?;\n            let _compression = reader.read_u8()?;\n            let _pad = reader.read_u16::<LittleEndian>()?;\n            let mut name_bytes = [0u8; 16];\n            reader.read_exact(&mut name_bytes)?;\n            let name_lossy = String::from_utf8_lossy(&name_bytes);\n            debug!(\"name: {}\", name_lossy);\n            let name = util::read_cstring(&mut BufReader::new(Cursor::new(name_bytes)))?;\n\n            lump_infos.push(LumpInfo { offset, size, name });\n        }\n\n        let mut files = HashMap::new();\n\n        for lump_info in lump_infos {\n            let mut data = Vec::with_capacity(lump_info.size as usize);\n            reader.seek(SeekFrom::Start(lump_info.offset as u64))?;\n            (&mut reader)\n                .take(lump_info.size as u64)\n                .read_to_end(&mut data)?;\n            files.insert(lump_info.name.to_owned(), data.into_boxed_slice());\n        }\n\n        Ok(Wad { files })\n    }\n\n    pub fn open_conchars(&self) -> Result<QPic, Error> {\n        match self.files.get(\"CONCHARS\") {\n            Some(ref data) => {\n                let width = 128;\n                let height = 128;\n                let indices = Vec::from(&data[..(width * height) as usize]);\n\n                Ok(QPic {\n                    width,\n                    height,\n                    indices: indices.into_boxed_slice(),\n                })\n            }\n\n            None => bail!(\"conchars not found in WAD\"),\n        }\n    }\n\n    pub fn open_qpic<S>(&self, name: S) -> Result<QPic, WadError>\n    where\n        S: AsRef<str>,\n    {\n        if name.as_ref() == \"CONCHARS\" {\n            Err(WadErrorKind::ConcharsUseDedicatedFunction)?\n        }\n\n        match self.files.get(name.as_ref()) {\n            Some(ref data) => QPic::load(Cursor::new(data)),\n            None => Err(WadErrorKind::NoSuchFile.into()),\n        }\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#![deny(unused_must_use)]\n#![feature(drain_filter)]\n\n#[macro_use]\nextern crate bitflags;\nextern crate byteorder;\nextern crate cgmath;\nextern crate chrono;\nextern crate env_logger;\n#[macro_use]\nextern crate failure;\n#[macro_use]\nextern crate lazy_static;\n#[macro_use]\nextern crate log;\nextern crate num;\n#[macro_use]\nextern crate num_derive;\nextern crate rand;\nextern crate regex;\nextern crate rodio;\nextern crate winit;\n\npub mod client;\npub mod common;\npub mod server;\n"
  },
  {
    "path": "src/server/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\npub mod precache;\npub mod progs;\npub mod world;\n\nuse std::{\n    cell::{Ref, RefCell},\n    collections::HashMap,\n    rc::Rc,\n};\n\nuse crate::{\n    common::{\n        console::CvarRegistry,\n        engine::{duration_from_f32, duration_to_f32},\n        math::Hyperplane,\n        model::Model,\n        parse,\n        vfs::Vfs,\n    },\n    server::{\n        progs::{functions::FunctionKind, GlobalAddrFunction},\n        world::{FieldAddrEntityId, FieldAddrVector, MoveKind},\n    },\n};\n\nuse self::{\n    precache::Precache,\n    progs::{\n        globals::{\n            GLOBAL_ADDR_ARG_0, GLOBAL_ADDR_ARG_1, GLOBAL_ADDR_ARG_2, GLOBAL_ADDR_ARG_3,\n            GLOBAL_ADDR_RETURN,\n        },\n        EntityFieldAddr, EntityId, ExecutionContext, FunctionId, GlobalAddrEntity, GlobalAddrFloat,\n        Globals, LoadProgs, Opcode, ProgsError, StringId, StringTable,\n    },\n    world::{\n        phys::{self, CollideKind, CollisionFlags, Trace, TraceEndKind},\n        EntityFlags, EntitySolid, FieldAddrFloat, FieldAddrFunctionId, FieldAddrStringId, World,\n    },\n};\n\nuse arrayvec::ArrayVec;\nuse cgmath::{InnerSpace, Vector3, Zero};\nuse chrono::Duration;\n\nconst MAX_DATAGRAM: usize = 1024;\nconst MAX_LIGHTSTYLES: usize = 64;\n\n/// The state of a client's connection to the server.\npub enum ClientState {\n    /// The client is still connecting.\n    Connecting,\n\n    /// The client is active.\n    Active(ClientActive),\n}\n\npub struct ClientActive {\n    /// If true, client may execute any command.\n    privileged: bool,\n\n    /// ID of the entity controlled by this client.\n    entity_id: EntityId,\n}\n\nbitflags! {\n    pub struct SessionFlags: i32 {\n        const EPISODE_1 =      0x0001;\n        const EPISODE_2 =      0x0002;\n        const EPISODE_3 =      0x0004;\n        const EPISODE_4 =      0x0008;\n        const NEW_UNIT =       0x0010;\n        const NEW_EPISODE =    0x0020;\n        const CROSS_TRIGGERS = 0xFF00;\n    }\n}\n\n/// A fixed-size pool of client connections.\npub struct ClientSlots {\n    /// Occupied slots are `Some`.\n    slots: Vec<Option<ClientState>>,\n}\n\nimpl ClientSlots {\n    /// Creates a new pool which supports at most `limit` clients.\n    pub fn new(limit: usize) -> ClientSlots {\n        let mut slots = Vec::with_capacity(limit);\n        slots.resize_with(limit, || None);\n\n        ClientSlots { slots }\n    }\n\n    /// Returns a reference to the client in a slot.\n    ///\n    /// If the slot is unoccupied, or if `id` is greater than `self.limit()`,\n    /// returns `None`.\n    pub fn get(&self, id: usize) -> Option<&ClientState> {\n        self.slots.get(id)?.as_ref()\n    }\n\n    /// Returns the maximum number of simultaneous clients.\n    pub fn limit(&self) -> usize {\n        self.slots.len()\n    }\n\n    /// Finds an available connection slot for a new client.\n    pub fn find_available(&mut self) -> Option<&mut ClientState> {\n        let slot = self.slots.iter_mut().find(|s| s.is_none())?;\n        Some(slot.insert(ClientState::Connecting))\n    }\n}\n\n/// Server state that persists between levels.\npub struct SessionPersistent {\n    client_slots: ClientSlots,\n    flags: SessionFlags,\n}\n\nimpl SessionPersistent {\n    pub fn new(max_clients: usize) -> SessionPersistent {\n        SessionPersistent {\n            client_slots: ClientSlots::new(max_clients),\n            flags: SessionFlags::empty(),\n        }\n    }\n\n    pub fn client(&self, slot: usize) -> Option<&ClientState> {\n        self.client_slots.get(slot)\n    }\n}\n\n/// The state of a server.\npub enum SessionState {\n    /// The server is loading.\n    ///\n    /// Certain operations, such as precaching, are only permitted while the\n    /// server is loading a level.\n    Loading(SessionLoading),\n\n    /// The server is active (in-game).\n    Active(SessionActive),\n}\n\n/// Contains the state of the server during level load.\npub struct SessionLoading {\n    level: LevelState,\n}\n\nimpl SessionLoading {\n    pub fn new(\n        vfs: Rc<Vfs>,\n        cvars: Rc<RefCell<CvarRegistry>>,\n        progs: LoadProgs,\n        models: Vec<Model>,\n        entmap: String,\n    ) -> SessionLoading {\n        SessionLoading {\n            level: LevelState::new(vfs, cvars, progs, models, entmap),\n        }\n    }\n\n    /// Adds a name to the sound precache.\n    ///\n    /// If the sound already exists in the precache, this has no effect.\n    #[inline]\n    pub fn precache_sound(&mut self, name_id: StringId) {\n        self.level.precache_sound(name_id)\n    }\n\n    /// Adds a name to the model precache.\n    ///\n    /// If the model already exists in the precache, this has no effect.\n    #[inline]\n    pub fn precache_model(&mut self, name_id: StringId) {\n        self.level.precache_model(name_id)\n    }\n\n    /// Completes the loading process.\n    ///\n    /// This consumes the `ServerLoading` and returns a `ServerActive`.\n    pub fn finish(self) -> SessionActive {\n        SessionActive { level: self.level }\n    }\n}\n\n/// State specific to an active (in-game) server.\npub struct SessionActive {\n    level: LevelState,\n}\n\n/// A server instance.\npub struct Session {\n    persist: SessionPersistent,\n    state: SessionState,\n}\n\nimpl Session {\n    pub fn new(\n        max_clients: usize,\n        vfs: Rc<Vfs>,\n        cvars: Rc<RefCell<CvarRegistry>>,\n        progs: LoadProgs,\n        models: Vec<Model>,\n        entmap: String,\n    ) -> Session {\n        Session {\n            persist: SessionPersistent::new(max_clients),\n            state: SessionState::Loading(SessionLoading {\n                level: LevelState::new(vfs, cvars, progs, models, entmap),\n            }),\n        }\n    }\n\n    /// Returns the maximum number of clients allowed on the server.\n    pub fn max_clients(&self) -> usize {\n        self.persist.client_slots.limit()\n    }\n\n    #[inline]\n    pub fn client(&self, slot: usize) -> Option<&ClientState> {\n        self.persist.client(slot)\n    }\n\n    pub fn precache_sound(&mut self, name_id: StringId) {\n        if let SessionState::Loading(ref mut loading) = self.state {\n            loading.precache_sound(name_id);\n        } else {\n            panic!(\"Sounds cannot be precached after loading\");\n        }\n    }\n\n    pub fn precache_model(&mut self, name_id: StringId) {\n        if let SessionState::Loading(ref mut loading) = self.state {\n            loading.precache_model(name_id);\n        } else {\n            panic!(\"Models cannot be precached after loading\");\n        }\n    }\n\n    #[inline]\n    fn level(&self) -> &LevelState {\n        match self.state {\n            SessionState::Loading(ref loading) => &loading.level,\n            SessionState::Active(ref active) => &active.level,\n        }\n    }\n\n    #[inline]\n    fn level_mut(&mut self) -> &mut LevelState {\n        match self.state {\n            SessionState::Loading(ref mut loading) => &mut loading.level,\n            SessionState::Active(ref mut active) => &mut active.level,\n        }\n    }\n\n    #[inline]\n    pub fn sound_id(&self, name_id: StringId) -> Option<usize> {\n        self.level().sound_id(name_id)\n    }\n\n    #[inline]\n    pub fn model_id(&self, name_id: StringId) -> Option<usize> {\n        self.level().model_id(name_id)\n    }\n\n    #[inline]\n    pub fn set_lightstyle(&mut self, index: usize, val: StringId) {\n        self.level_mut().set_lightstyle(index, val);\n    }\n\n    /// Returns the amount of time the current level has been active.\n    #[inline]\n    pub fn time(&self) -> Option<Duration> {\n        match self.state {\n            SessionState::Loading(_) => None,\n            SessionState::Active(ref active) => Some(active.level.time),\n        }\n    }\n}\n\n/// Server-side level state.\n#[derive(Debug)]\npub struct LevelState {\n    vfs: Rc<Vfs>,\n    cvars: Rc<RefCell<CvarRegistry>>,\n\n    string_table: Rc<RefCell<StringTable>>,\n    sound_precache: Precache,\n    model_precache: Precache,\n    lightstyles: [StringId; MAX_LIGHTSTYLES],\n\n    /// Amount of time the current level has been active.\n    time: Duration,\n\n    /// QuakeC bytecode execution context.\n    ///\n    /// This includes the program counter, call stack, and local variables.\n    cx: ExecutionContext,\n\n    /// Global values for QuakeC bytecode.\n    globals: Globals,\n\n    /// The state of the game world.\n    ///\n    /// This contains the entities and world geometry.\n    world: World,\n\n    datagram: ArrayVec<u8, MAX_DATAGRAM>,\n}\n\nimpl LevelState {\n    pub fn new(\n        vfs: Rc<Vfs>,\n        cvars: Rc<RefCell<CvarRegistry>>,\n        progs: LoadProgs,\n        models: Vec<Model>,\n        entmap: String,\n    ) -> LevelState {\n        let LoadProgs {\n            cx,\n            globals,\n            entity_def,\n            string_table,\n        } = progs;\n\n        let mut sound_precache = Precache::new();\n        sound_precache.precache(\"\");\n\n        let mut model_precache = Precache::new();\n        model_precache.precache(\"\");\n\n        for model in models.iter() {\n            let model_name = (*string_table).borrow_mut().find_or_insert(model.name());\n            model_precache.precache(string_table.borrow().get(model_name).unwrap());\n        }\n\n        let world = World::create(models, entity_def.clone(), string_table.clone()).unwrap();\n        let entity_list = parse::entities(&entmap).unwrap();\n\n        let mut level = LevelState {\n            vfs,\n            cvars,\n            string_table,\n            sound_precache,\n            model_precache,\n            lightstyles: [StringId(0); MAX_LIGHTSTYLES],\n            time: Duration::zero(),\n\n            cx,\n            globals,\n            world,\n\n            datagram: ArrayVec::new(),\n        };\n\n        for entity in entity_list {\n            level.spawn_entity_from_map(entity).unwrap();\n        }\n\n        level\n    }\n\n    #[inline]\n    pub fn precache_sound(&mut self, name_id: StringId) {\n        let name = Ref::map(self.string_table.borrow(), |this| {\n            this.get(name_id).unwrap()\n        });\n        self.sound_precache.precache(&*name);\n    }\n\n    #[inline]\n    pub fn precache_model(&mut self, name_id: StringId) {\n        let name = Ref::map(self.string_table.borrow(), |this| {\n            this.get(name_id).unwrap()\n        });\n        self.model_precache.precache(&*name)\n    }\n\n    #[inline]\n    pub fn sound_id(&self, name_id: StringId) -> Option<usize> {\n        let name = Ref::map(self.string_table.borrow(), |this| {\n            this.get(name_id).unwrap()\n        });\n        self.sound_precache.find(&*name)\n    }\n\n    #[inline]\n    pub fn model_id(&self, name_id: StringId) -> Option<usize> {\n        let name = Ref::map(self.string_table.borrow(), |this| {\n            this.get(name_id).unwrap()\n        });\n        self.model_precache.find(&*name)\n    }\n\n    #[inline]\n    pub fn set_lightstyle(&mut self, index: usize, val: StringId) {\n        self.lightstyles[index] = val;\n    }\n\n    /// Execute a QuakeC function in the VM.\n    pub fn execute_program(&mut self, f: FunctionId) -> Result<(), ProgsError> {\n        let mut runaway = 100000;\n\n        let exit_depth = self.cx.call_stack_depth();\n\n        self.cx.enter_function(&mut self.globals, f)?;\n\n        while self.cx.call_stack_depth() != exit_depth {\n            runaway -= 1;\n\n            if runaway == 0 {\n                panic!(\"runaway program\");\n            }\n\n            let statement = self.cx.load_statement();\n            let op = statement.opcode;\n            let a = statement.arg1;\n            let b = statement.arg2;\n            let c = statement.arg3;\n\n            debug!(\n                \"              {:<9} {:>5} {:>5} {:>5}\",\n                format!(\"{:?}\", op),\n                a,\n                b,\n                c\n            );\n\n            use Opcode::*;\n\n            // Y'all like jump tables?\n            match op {\n                // Control flow ================================================\n                If => {\n                    let cond = self.globals.get_float(a)? != 0.0;\n                    log::debug!(\"If: cond == {}\", cond);\n\n                    if cond {\n                        self.cx.jump_relative(b);\n                        continue;\n                    }\n                }\n\n                IfNot => {\n                    let cond = self.globals.get_float(a)? != 0.0;\n                    log::debug!(\"IfNot: cond == {}\", cond);\n\n                    if !cond {\n                        self.cx.jump_relative(b);\n                        continue;\n                    }\n                }\n\n                Goto => {\n                    self.cx.jump_relative(a);\n                    continue;\n                }\n\n                Call0 | Call1 | Call2 | Call3 | Call4 | Call5 | Call6 | Call7 | Call8 => {\n                    // TODO: pass to equivalent of PF_VarString\n                    let _arg_count = op as usize - Opcode::Call0 as usize;\n\n                    let f_to_call = self.globals.function_id(a)?;\n                    if f_to_call.0 == 0 {\n                        panic!(\"NULL function\");\n                    }\n\n                    let name_id = self.cx.function_def(f_to_call)?.name_id;\n                    let name = self.string_table.borrow().get(name_id).unwrap().to_owned();\n\n                    if let FunctionKind::BuiltIn(b) = self.cx.function_def(f_to_call)?.kind {\n                        debug!(\"Calling built-in function {}\", name);\n                        use progs::functions::BuiltinFunctionId::*;\n                        match b {\n                            MakeVectors => self.globals.make_vectors()?,\n                            SetOrigin => self.builtin_set_origin()?,\n                            SetModel => self.builtin_set_model()?,\n                            SetSize => self.builtin_set_size()?,\n                            Break => unimplemented!(),\n                            Random => self.globals.builtin_random()?,\n                            Sound => unimplemented!(),\n                            Normalize => unimplemented!(),\n                            Error => unimplemented!(),\n                            ObjError => unimplemented!(),\n                            VLen => self.globals.builtin_v_len()?,\n                            VecToYaw => self.globals.builtin_vec_to_yaw()?,\n                            Spawn => self.builtin_spawn()?,\n                            Remove => self.builtin_remove()?,\n                            TraceLine => unimplemented!(),\n                            CheckClient => unimplemented!(),\n                            Find => unimplemented!(),\n                            PrecacheSound => self.builtin_precache_sound()?,\n                            PrecacheModel => self.builtin_precache_model()?,\n                            StuffCmd => unimplemented!(),\n                            FindRadius => unimplemented!(),\n                            BPrint => unimplemented!(),\n                            SPrint => unimplemented!(),\n                            DPrint => self.builtin_dprint()?,\n                            FToS => unimplemented!(),\n                            VToS => unimplemented!(),\n                            CoreDump => unimplemented!(),\n                            TraceOn => unimplemented!(),\n                            TraceOff => unimplemented!(),\n                            EPrint => unimplemented!(),\n                            WalkMove => unimplemented!(),\n\n                            DropToFloor => self.builtin_drop_to_floor()?,\n                            LightStyle => self.builtin_light_style()?,\n                            RInt => self.globals.builtin_r_int()?,\n                            Floor => self.globals.builtin_floor()?,\n                            Ceil => self.globals.builtin_ceil()?,\n                            CheckBottom => unimplemented!(),\n                            PointContents => unimplemented!(),\n                            FAbs => self.globals.builtin_f_abs()?,\n                            Aim => unimplemented!(),\n                            Cvar => self.builtin_cvar()?,\n                            LocalCmd => unimplemented!(),\n                            NextEnt => unimplemented!(),\n                            Particle => unimplemented!(),\n                            ChangeYaw => unimplemented!(),\n                            VecToAngles => unimplemented!(),\n                            WriteByte => unimplemented!(),\n                            WriteChar => unimplemented!(),\n                            WriteShort => unimplemented!(),\n                            WriteLong => unimplemented!(),\n                            WriteCoord => unimplemented!(),\n                            WriteAngle => unimplemented!(),\n                            WriteString => unimplemented!(),\n                            WriteEntity => unimplemented!(),\n                            MoveToGoal => unimplemented!(),\n                            PrecacheFile => unimplemented!(),\n                            MakeStatic => unimplemented!(),\n                            ChangeLevel => unimplemented!(),\n                            CvarSet => self.builtin_cvar_set()?,\n                            CenterPrint => unimplemented!(),\n                            AmbientSound => self.builtin_ambient_sound()?,\n                            PrecacheModel2 => unimplemented!(),\n                            PrecacheSound2 => unimplemented!(),\n                            PrecacheFile2 => unimplemented!(),\n                            SetSpawnArgs => unimplemented!(),\n                        }\n                        debug!(\"Returning from built-in function {}\", name);\n                    } else {\n                        self.cx.enter_function(&mut self.globals, f_to_call)?;\n                        continue;\n                    }\n                }\n\n                Done | Return => self.op_return(a, b, c)?,\n\n                MulF => self.globals.op_mul_f(a, b, c)?,\n                MulV => self.globals.op_mul_v(a, b, c)?,\n                MulFV => self.globals.op_mul_fv(a, b, c)?,\n                MulVF => self.globals.op_mul_vf(a, b, c)?,\n                Div => self.globals.op_div(a, b, c)?,\n                AddF => self.globals.op_add_f(a, b, c)?,\n                AddV => self.globals.op_add_v(a, b, c)?,\n                SubF => self.globals.op_sub_f(a, b, c)?,\n                SubV => self.globals.op_sub_v(a, b, c)?,\n                EqF => self.globals.op_eq_f(a, b, c)?,\n                EqV => self.globals.op_eq_v(a, b, c)?,\n                EqS => self.globals.op_eq_s(a, b, c)?,\n                EqEnt => self.globals.op_eq_ent(a, b, c)?,\n                EqFnc => self.globals.op_eq_fnc(a, b, c)?,\n                NeF => self.globals.op_ne_f(a, b, c)?,\n                NeV => self.globals.op_ne_v(a, b, c)?,\n                NeS => self.globals.op_ne_s(a, b, c)?,\n                NeEnt => self.globals.op_ne_ent(a, b, c)?,\n                NeFnc => self.globals.op_ne_fnc(a, b, c)?,\n                Le => self.globals.op_le(a, b, c)?,\n                Ge => self.globals.op_ge(a, b, c)?,\n                Lt => self.globals.op_lt(a, b, c)?,\n                Gt => self.globals.op_gt(a, b, c)?,\n                LoadF => self.op_load_f(a, b, c)?,\n                LoadV => self.op_load_v(a, b, c)?,\n                LoadS => self.op_load_s(a, b, c)?,\n                LoadEnt => self.op_load_ent(a, b, c)?,\n                LoadFld => panic!(\"load_fld not implemented\"),\n                LoadFnc => self.op_load_fnc(a, b, c)?,\n                Address => self.op_address(a, b, c)?,\n                StoreF => self.globals.op_store_f(a, b, c)?,\n                StoreV => self.globals.op_store_v(a, b, c)?,\n                StoreS => self.globals.op_store_s(a, b, c)?,\n                StoreEnt => self.globals.op_store_ent(a, b, c)?,\n                StoreFld => self.globals.op_store_fld(a, b, c)?,\n                StoreFnc => self.globals.op_store_fnc(a, b, c)?,\n                StorePF => self.op_storep_f(a, b, c)?,\n                StorePV => self.op_storep_v(a, b, c)?,\n                StorePS => self.op_storep_s(a, b, c)?,\n                StorePEnt => self.op_storep_ent(a, b, c)?,\n                StorePFld => panic!(\"storep_fld not implemented\"),\n                StorePFnc => self.op_storep_fnc(a, b, c)?,\n                NotF => self.globals.op_not_f(a, b, c)?,\n                NotV => self.globals.op_not_v(a, b, c)?,\n                NotS => self.globals.op_not_s(a, b, c)?,\n                NotEnt => self.globals.op_not_ent(a, b, c)?,\n                NotFnc => self.globals.op_not_fnc(a, b, c)?,\n                And => self.globals.op_and(a, b, c)?,\n                Or => self.globals.op_or(a, b, c)?,\n                BitAnd => self.globals.op_bit_and(a, b, c)?,\n                BitOr => self.globals.op_bit_or(a, b, c)?,\n\n                State => self.op_state(a, b, c)?,\n            }\n\n            // Increment program counter.\n            self.cx.jump_relative(1);\n        }\n\n        Ok(())\n    }\n\n    pub fn execute_program_by_name<S>(&mut self, name: S) -> Result<(), ProgsError>\n    where\n        S: AsRef<str>,\n    {\n        let func_id = self.cx.find_function_by_name(name)?;\n        self.execute_program(func_id)?;\n        Ok(())\n    }\n\n    /// Link an entity into the `World`.\n    ///\n    /// If `touch_triggers` is `true`, this will invoke the touch function of\n    /// any trigger the entity is touching.\n    pub fn link_entity(\n        &mut self,\n        ent_id: EntityId,\n        touch_triggers: bool,\n    ) -> Result<(), ProgsError> {\n        self.world.link_entity(ent_id)?;\n\n        if touch_triggers {\n            self.touch_triggers(ent_id)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn spawn_entity(&mut self) -> Result<EntityId, ProgsError> {\n        let ent_id = self.world.alloc_uninitialized()?;\n\n        self.link_entity(ent_id, false)?;\n\n        Ok(ent_id)\n    }\n\n    pub fn spawn_entity_from_map(\n        &mut self,\n        map: HashMap<&str, &str>,\n    ) -> Result<EntityId, ProgsError> {\n        let classname = match map.get(\"classname\") {\n            Some(c) => c.to_owned(),\n            None => return Err(ProgsError::with_msg(\"No classname for entity\")),\n        };\n\n        let ent_id = self.world.alloc_from_map(map)?;\n\n        // TODO: set origin, mins and maxs here if needed\n\n        // set `self` before calling spawn function\n        self.globals\n            .put_entity_id(ent_id, GlobalAddrEntity::Self_ as i16)?;\n\n        self.execute_program_by_name(classname)?;\n\n        self.link_entity(ent_id, true)?;\n\n        Ok(ent_id)\n    }\n\n    pub fn set_entity_origin(\n        &mut self,\n        ent_id: EntityId,\n        origin: Vector3<f32>,\n    ) -> Result<(), ProgsError> {\n        self.world\n            .entity_mut(ent_id)?\n            .store(FieldAddrVector::Origin, origin.into())?;\n        self.link_entity(ent_id, false)?;\n\n        Ok(())\n    }\n\n    pub fn set_entity_model(\n        &mut self,\n        ent_id: EntityId,\n        model_name_id: StringId,\n    ) -> Result<(), ProgsError> {\n        let model_id = {\n            let ent = self.world.entity_mut(ent_id)?;\n\n            ent.put_string_id(model_name_id, FieldAddrStringId::ModelName as i16)?;\n\n            let model_id = match self.string_table.borrow().get(model_name_id) {\n                Some(name) => match self.model_precache.find(name) {\n                    Some(i) => i,\n                    None => return Err(ProgsError::with_msg(\"model not precached\")),\n                },\n                None => return Err(ProgsError::with_msg(\"invalid StringId\")),\n            };\n\n            ent.put_float(model_id as f32, FieldAddrFloat::ModelIndex as i16)?;\n\n            model_id\n        };\n\n        self.world.set_entity_model(ent_id, model_id)?;\n\n        Ok(())\n    }\n\n    pub fn think(&mut self, ent_id: EntityId, frame_time: Duration) -> Result<(), ProgsError> {\n        let ent = self.world.entity_mut(ent_id)?;\n        let think_time = duration_from_f32(ent.load(FieldAddrFloat::NextThink)?);\n\n        if think_time <= Duration::zero() || think_time > self.time + frame_time {\n            // Think either already happened or isn't due yet.\n            return Ok(());\n        }\n\n        // Deschedule next think.\n        ent.store(FieldAddrFloat::NextThink, 0.0)?;\n\n        // Call entity's think function.\n        let think = ent.load(FieldAddrFunctionId::Think)?;\n        self.globals\n            .store(GlobalAddrFloat::Time, duration_to_f32(think_time))?;\n        self.globals.store(GlobalAddrEntity::Self_, ent_id)?;\n        self.globals.store(GlobalAddrEntity::Other, EntityId(0))?;\n        self.execute_program(think)?;\n\n        Ok(())\n    }\n\n    pub fn physics(\n        &mut self,\n        clients: &ClientSlots,\n        frame_time: Duration,\n    ) -> Result<(), ProgsError> {\n        self.globals.store(GlobalAddrEntity::Self_, EntityId(0))?;\n        self.globals.store(GlobalAddrEntity::Other, EntityId(0))?;\n        self.globals\n            .store(GlobalAddrFloat::Time, duration_to_f32(self.time))?;\n\n        let start_frame = self\n            .globals\n            .function_id(GlobalAddrFunction::StartFrame as i16)?;\n        self.execute_program(start_frame)?;\n\n        // TODO: don't alloc\n        let mut ent_ids = Vec::new();\n\n        self.world.list_entities(&mut ent_ids);\n\n        for ent_id in ent_ids {\n            if self.globals.load(GlobalAddrFloat::ForceRetouch)? != 0.0 {\n                // Force all entities to touch triggers, even if they didn't\n                // move. This is required when e.g. creating new triggers, as\n                // stationary entities typically do not get relinked, and so\n                // will ignore new triggers even when touching them.\n                //\n                // TODO: this may have a subtle ordering bug. If entity 2 has\n                // physics run, sets ForceRetouch and spawns entity 1, then\n                // entity 1 will not have a chance to touch triggers this frame.\n                // Quake solves this by using a linked list and always spawning\n                // at the end so that newly spawned entities always have physics\n                // run this frame.\n                self.link_entity(ent_id, true)?;\n            }\n\n            let max_clients = clients.limit();\n            if ent_id.0 != 0 && ent_id.0 < max_clients {\n                self.physics_player(clients, ent_id)?;\n            } else {\n                match self.world.entity(ent_id).move_kind()? {\n                    MoveKind::Walk => {\n                        todo!(\"MoveKind::Walk\");\n                    }\n\n                    MoveKind::Push => self.physics_push(ent_id, frame_time)?,\n                    // No actual physics for this entity, but still let it think.\n                    MoveKind::None => self.think(ent_id, frame_time)?,\n                    MoveKind::NoClip => self.physics_noclip(ent_id, frame_time)?,\n                    MoveKind::Step => self.physics_step(ent_id, frame_time)?,\n\n                    // all airborne entities have the same physics\n                    _ => unimplemented!(),\n                }\n            }\n\n            match self.globals.load(GlobalAddrFloat::ForceRetouch)? {\n                f if f > 0.0 => self.globals.store(GlobalAddrFloat::ForceRetouch, f - 1.0)?,\n                _ => (),\n            }\n        }\n\n        // TODO: increase sv.time by host_frametime\n        unimplemented!();\n    }\n\n    // TODO: rename arguments when implementing\n    pub fn physics_player(\n        &mut self,\n        clients: &ClientSlots,\n        ent_id: EntityId,\n    ) -> Result<(), ProgsError> {\n        let client_id = ent_id.0.checked_sub(1).ok_or_else(|| {\n            ProgsError::with_msg(format!(\"Invalid client entity ID: {:?}\", ent_id))\n        })?;\n\n        if clients.get(client_id).is_none() {\n            // No client in this slot.\n            return Ok(());\n        }\n\n        let ent = self.world.entity_mut(ent_id)?;\n        ent.limit_velocity(self.cvars.borrow().get_value(\"sv_maxvelocity\").unwrap())?;\n        unimplemented!();\n    }\n\n    pub fn physics_push(\n        &mut self,\n        ent_id: EntityId,\n        frame_time: Duration,\n    ) -> Result<(), ProgsError> {\n        let ent = self.world.entity_mut(ent_id)?;\n\n        let local_time = duration_from_f32(ent.load(FieldAddrFloat::LocalTime)?);\n        let next_think = duration_from_f32(ent.load(FieldAddrFloat::NextThink)?);\n\n        let move_time = if local_time + frame_time > next_think {\n            (next_think - local_time).max(Duration::zero())\n        } else {\n            frame_time\n        };\n\n        drop(ent);\n        if !move_time.is_zero() {\n            self.move_push(ent_id, frame_time, move_time)?;\n        }\n\n        let ent = self.world.entity_mut(ent_id)?;\n\n        let old_local_time = local_time;\n        let new_local_time = duration_from_f32(ent.load(FieldAddrFloat::LocalTime)?);\n\n        // Let the entity think if it needs to.\n        if old_local_time < next_think && next_think <= new_local_time {\n            // Deschedule thinking.\n            ent.store(FieldAddrFloat::NextThink, 0.0)?;\n\n            self.globals\n                .put_float(duration_to_f32(self.time), GlobalAddrFloat::Time as i16)?;\n            self.globals\n                .put_entity_id(ent_id, GlobalAddrEntity::Self_ as i16)?;\n            self.globals\n                .put_entity_id(EntityId(0), GlobalAddrEntity::Other as i16)?;\n\n            let think = ent.function_id(FieldAddrFunctionId::Think as i16)?;\n            self.execute_program(think)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn physics_noclip(\n        &mut self,\n        ent_id: EntityId,\n        frame_time: Duration,\n    ) -> Result<(), ProgsError> {\n        // Let entity think, then move if it didn't remove itself.\n        self.think(ent_id, frame_time)?;\n\n        if let Ok(ent) = self.world.entity_mut(ent_id) {\n            let frame_time_f = duration_to_f32(frame_time);\n\n            let angles: Vector3<f32> = ent.load(FieldAddrVector::Angles)?.into();\n            let angle_vel: Vector3<f32> = ent.load(FieldAddrVector::AngularVelocity)?.into();\n            let new_angles = angles + frame_time_f * angle_vel;\n            ent.store(FieldAddrVector::Angles, new_angles.into())?;\n\n            let orig: Vector3<f32> = ent.load(FieldAddrVector::Origin)?.into();\n            let vel: Vector3<f32> = ent.load(FieldAddrVector::Velocity)?.into();\n            let new_orig = orig + frame_time_f * vel;\n            ent.store(FieldAddrVector::Origin, new_orig.into())?;\n        }\n\n        Ok(())\n    }\n\n    pub fn physics_step(\n        &mut self,\n        ent_id: EntityId,\n        frame_time: Duration,\n    ) -> Result<(), ProgsError> {\n        let in_freefall = !self\n            .world\n            .entity(ent_id)\n            .flags()?\n            .intersects(EntityFlags::ON_GROUND | EntityFlags::FLY | EntityFlags::IN_WATER);\n\n        if in_freefall {\n            let sv_gravity = self.cvars.borrow().get_value(\"sv_gravity\").unwrap();\n            let vel: Vector3<f32> = self\n                .world\n                .entity(ent_id)\n                .load(FieldAddrVector::Velocity)?\n                .into();\n\n            // If true, play an impact sound when the entity hits the ground.\n            let hit_sound = vel.z < -0.1 * sv_gravity;\n\n            self.world\n                .entity_mut(ent_id)?\n                .apply_gravity(sv_gravity, frame_time)?;\n\n            let sv_maxvelocity = self.cvars.borrow().get_value(\"sv_maxvelocity\").unwrap();\n            self.world\n                .entity_mut(ent_id)?\n                .limit_velocity(sv_maxvelocity)?;\n\n            // Move the entity and relink it.\n            self.move_ballistic(frame_time, ent_id)?;\n            self.link_entity(ent_id, true)?;\n\n            let ent = self.world.entity_mut(ent_id)?;\n\n            if ent.flags()?.contains(EntityFlags::ON_GROUND) && hit_sound {\n                // Entity hit the ground this frame.\n                todo!(\"SV_StartSound(demon/dland2.wav)\");\n            }\n        }\n\n        self.think(ent_id, frame_time)?;\n\n        todo!(\"SV_CheckWaterTransition\");\n\n        Ok(())\n    }\n\n    pub fn move_push(\n        &mut self,\n        ent_id: EntityId,\n        frame_time: Duration,\n        move_time: Duration,\n    ) -> Result<(), ProgsError> {\n        let ent = self.world.entity_mut(ent_id)?;\n\n        let vel: Vector3<f32> = ent.load(FieldAddrVector::Velocity)?.into();\n        if vel.is_zero() {\n            // Entity doesn't need to move.\n            let local_time = ent.load(FieldAddrFloat::LocalTime)?;\n            let new_local_time = local_time + duration_to_f32(move_time);\n            ent.store(FieldAddrFloat::LocalTime, new_local_time)?;\n            return Ok(());\n        }\n\n        let move_time_f = duration_to_f32(move_time);\n        let move_vector = vel * move_time_f;\n        // TODO let mins =\n        todo!()\n    }\n\n    const MAX_BALLISTIC_COLLISIONS: usize = 4;\n\n    /// Movement function for freefalling entities.\n    pub fn move_ballistic(\n        &mut self,\n        sim_time: Duration,\n        ent_id: EntityId,\n    ) -> Result<(CollisionFlags, Option<Trace>), ProgsError> {\n        let mut sim_time_f = duration_to_f32(sim_time);\n\n        let mut out_trace = None;\n        let mut flags = CollisionFlags::empty();\n        let mut touching_planes: ArrayVec<Hyperplane, 5> = ArrayVec::new();\n\n        let init_velocity = self.world.entity(ent_id).velocity()?;\n        let mut trace_velocity = init_velocity;\n\n        // Even when the entity collides with something along its path, it may\n        // continue moving. This may occur when bouncing or sliding off a solid\n        // object, or when moving between media (e.g. from air to water).\n        for _ in 0..Self::MAX_BALLISTIC_COLLISIONS {\n            let velocity = self.world.entity(ent_id).velocity()?;\n\n            if velocity.is_zero() {\n                // Not moving.\n                break;\n            }\n\n            let orig = self.world.entity(ent_id).origin()?;\n            let end = orig + sim_time_f * velocity;\n            let min = self.world.entity(ent_id).min()?;\n            let max = self.world.entity(ent_id).max()?;\n\n            let (trace, hit_entity) =\n                self.world\n                    .move_entity(ent_id, orig, min, max, end, CollideKind::Normal)?;\n\n            if trace.all_solid() {\n                // Entity is stuck in a wall.\n                self.world\n                    .entity_mut(ent_id)?\n                    .store(FieldAddrVector::Velocity, Vector3::zero().into())?;\n\n                return Ok((CollisionFlags::HORIZONTAL | CollisionFlags::VERTICAL, None));\n            }\n\n            if trace.ratio() > 0.0 {\n                // If the entity moved at all, update its position.\n                self.world\n                    .entity_mut(ent_id)?\n                    .store(FieldAddrVector::Origin, trace.end_point().into())?;\n                touching_planes.clear();\n\n                trace_velocity = self.world.entity(ent_id).velocity()?;\n            }\n\n            // Find the plane the entity hit, if any.\n            let boundary = match trace.end().kind() {\n                // Entity didn't hit anything.\n                TraceEndKind::Terminal => break,\n\n                TraceEndKind::Boundary(b) => b,\n            };\n\n            // Sanity check to make sure the trace actually hit something.\n            let hit_entity = match hit_entity {\n                Some(h) => h,\n                None => panic!(\"trace collided with nothing\"),\n            };\n\n            // TODO: magic constant\n            if boundary.plane.normal().z > 0.7 {\n                flags |= CollisionFlags::HORIZONTAL;\n                if self.world.entity(hit_entity).solid()? == EntitySolid::Bsp {\n                    self.world\n                        .entity_mut(ent_id)?\n                        .add_flags(EntityFlags::ON_GROUND)?;\n                    self.world\n                        .entity_mut(ent_id)?\n                        .store(FieldAddrEntityId::Ground, hit_entity)?;\n                }\n            } else if boundary.plane.normal().z == 0.0 {\n                flags |= CollisionFlags::VERTICAL;\n                out_trace = Some(trace.clone());\n            }\n\n            self.impact_entities(ent_id, hit_entity)?;\n            if !self.world.entity_exists(ent_id) {\n                // Entity removed by touch function.\n                break;\n            }\n\n            sim_time_f -= trace.ratio() * sim_time_f;\n\n            if touching_planes.try_push(boundary.plane.clone()).is_err() {\n                // Touching too many planes to make much sense of, so stop.\n                self.world\n                    .entity_mut(ent_id)?\n                    .store(FieldAddrVector::Velocity, Vector3::zero().into())?;\n                return Ok((CollisionFlags::HORIZONTAL | CollisionFlags::VERTICAL, None));\n            }\n\n            let end_velocity =\n                match phys::velocity_after_multi_collision(trace_velocity, &touching_planes, 1.0) {\n                    Some(v) => v,\n                    None => {\n                        // Entity is wedged in a corner, so it simply stops.\n                        self.world\n                            .entity_mut(ent_id)?\n                            .store(FieldAddrVector::Velocity, Vector3::zero().into())?;\n\n                        return Ok((\n                            CollisionFlags::HORIZONTAL\n                                | CollisionFlags::VERTICAL\n                                | CollisionFlags::STOPPED,\n                            None,\n                        ));\n                    }\n                };\n\n            if init_velocity.dot(end_velocity) <= 0.0 {\n                // Avoid bouncing the entity at a sharp angle.\n                self.world\n                    .entity_mut(ent_id)?\n                    .store(FieldAddrVector::Velocity, Vector3::zero().into())?;\n                return Ok((flags, out_trace));\n            }\n\n            self.world\n                .entity_mut(ent_id)?\n                .store(FieldAddrVector::Velocity, end_velocity.into())?;\n        }\n\n        Ok((flags, out_trace))\n    }\n\n    const DROP_TO_FLOOR_DIST: f32 = 256.0;\n\n    /// Moves an entity straight down until it collides with a solid surface.\n    ///\n    /// Returns `true` if the entity hit the floor, `false` otherwise.\n    ///\n    /// ## Notes\n    /// - The drop distance is limited to 256, so entities which are more than 256 units above a\n    ///   solid surface will not actually hit the ground.\n    pub fn drop_entity_to_floor(&mut self, ent_id: EntityId) -> Result<bool, ProgsError> {\n        debug!(\"Finding floor for entity with ID {}\", ent_id.0);\n        let origin = self.world.entity(ent_id).origin()?;\n\n        let end = Vector3::new(origin.x, origin.y, origin.z - Self::DROP_TO_FLOOR_DIST);\n        let min = self.world.entity(ent_id).min()?;\n        let max = self.world.entity(ent_id).max()?;\n\n        let (trace, collide_entity) =\n            self.world\n                .move_entity(ent_id, origin, min, max, end, CollideKind::Normal)?;\n        debug!(\"End position after drop: {:?}\", trace.end_point());\n\n        let drop_dist = 256.0;\n        let actual_dist = (trace.end_point() - origin).magnitude();\n\n        if collide_entity.is_none() || actual_dist == drop_dist || trace.all_solid() {\n            // Entity didn't hit the floor or is stuck.\n            Ok(false)\n        } else {\n            // Entity hit the floor. Update origin, relink and set ON_GROUND flag.\n            self.world\n                .entity_mut(ent_id)?\n                .put_vector(trace.end_point().into(), FieldAddrVector::Origin as i16)?;\n            self.link_entity(ent_id, false)?;\n            self.world\n                .entity_mut(ent_id)?\n                .add_flags(EntityFlags::ON_GROUND)?;\n            self.world\n                .entity_mut(ent_id)?\n                .put_entity_id(collide_entity.unwrap(), FieldAddrEntityId::Ground as i16)?;\n\n            Ok(true)\n        }\n    }\n\n    pub fn touch_triggers(&mut self, ent_id: EntityId) -> Result<(), ProgsError> {\n        // TODO: alloc once\n        let mut touched = Vec::new();\n        self.world.list_touched_triggers(&mut touched, ent_id, 0)?;\n\n        // Save state.\n        let restore_self = self.globals.load(GlobalAddrEntity::Self_)?;\n        let restore_other = self.globals.load(GlobalAddrEntity::Other)?;\n\n        // Activate the touched triggers.\n        for trigger_id in touched {\n            let trigger_touch = self\n                .world\n                .entity(trigger_id)\n                .load(FieldAddrFunctionId::Touch)?;\n\n            self.globals.store(GlobalAddrEntity::Self_, trigger_id)?;\n            self.globals.store(GlobalAddrEntity::Other, ent_id)?;\n            self.execute_program(trigger_touch)?;\n        }\n\n        // Restore state.\n        self.globals.store(GlobalAddrEntity::Self_, restore_self)?;\n        self.globals.store(GlobalAddrEntity::Other, restore_other)?;\n\n        Ok(())\n    }\n\n    /// Runs two entities' touch functions.\n    pub fn impact_entities(&mut self, ent_a: EntityId, ent_b: EntityId) -> Result<(), ProgsError> {\n        let restore_self = self.globals.load(GlobalAddrEntity::Self_)?;\n        let restore_other = self.globals.load(GlobalAddrEntity::Other)?;\n\n        self.globals\n            .store(GlobalAddrFloat::Time, duration_to_f32(self.time))?;\n\n        // Set up and run Entity A's touch function.\n        let touch_a = self.world.entity(ent_a).load(FieldAddrFunctionId::Touch)?;\n        let solid_a = self.world.entity(ent_a).solid()?;\n        if touch_a.0 != 0 && solid_a != EntitySolid::Not {\n            self.globals.store(GlobalAddrEntity::Self_, ent_a)?;\n            self.globals.store(GlobalAddrEntity::Other, ent_b)?;\n            self.execute_program(touch_a)?;\n        }\n\n        // Set up and run Entity B's touch function.\n        let touch_b = self.world.entity(ent_b).load(FieldAddrFunctionId::Touch)?;\n        let solid_b = self.world.entity(ent_b).solid()?;\n        if touch_b.0 != 0 && solid_b != EntitySolid::Not {\n            self.globals.store(GlobalAddrEntity::Self_, ent_b)?;\n            self.globals.store(GlobalAddrEntity::Other, ent_a)?;\n            self.execute_program(touch_b)?;\n        }\n\n        self.globals.store(GlobalAddrEntity::Self_, restore_self)?;\n        self.globals.store(GlobalAddrEntity::Other, restore_other)?;\n\n        Ok(())\n    }\n\n    // QuakeC instructions ====================================================\n\n    pub fn op_return(&mut self, a: i16, b: i16, c: i16) -> Result<(), ProgsError> {\n        let val1 = self.globals.get_bytes(a)?;\n        let val2 = self.globals.get_bytes(b)?;\n        let val3 = self.globals.get_bytes(c)?;\n\n        self.globals.put_bytes(val1, GLOBAL_ADDR_RETURN as i16)?;\n        self.globals\n            .put_bytes(val2, GLOBAL_ADDR_RETURN as i16 + 1)?;\n        self.globals\n            .put_bytes(val3, GLOBAL_ADDR_RETURN as i16 + 2)?;\n\n        self.cx.leave_function(&mut self.globals)?;\n\n        Ok(())\n    }\n\n    // LOAD_F: load float field from entity\n    pub fn op_load_f(&mut self, e_ofs: i16, e_f: i16, dest_ofs: i16) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(e_ofs)?;\n\n        let fld_ofs = self.globals.get_field_addr(e_f)?;\n\n        let f = self.world.entity(ent_id).get_float(fld_ofs.0 as i16)?;\n        self.globals.put_float(f, dest_ofs)?;\n\n        Ok(())\n    }\n\n    // LOAD_V: load vector field from entity\n    pub fn op_load_v(\n        &mut self,\n        ent_id_addr: i16,\n        ent_vector_addr: i16,\n        dest_addr: i16,\n    ) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(ent_id_addr)?;\n        let ent_vector = self.globals.get_field_addr(ent_vector_addr)?;\n        let v = self.world.entity(ent_id).get_vector(ent_vector.0 as i16)?;\n        self.globals.put_vector(v, dest_addr)?;\n\n        Ok(())\n    }\n\n    pub fn op_load_s(\n        &mut self,\n        ent_id_addr: i16,\n        ent_string_id_addr: i16,\n        dest_addr: i16,\n    ) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(ent_id_addr)?;\n        let ent_string_id = self.globals.get_field_addr(ent_string_id_addr)?;\n        let s = self\n            .world\n            .entity(ent_id)\n            .string_id(ent_string_id.0 as i16)?;\n        self.globals.put_string_id(s, dest_addr)?;\n\n        Ok(())\n    }\n\n    pub fn op_load_ent(\n        &mut self,\n        ent_id_addr: i16,\n        ent_entity_id_addr: i16,\n        dest_addr: i16,\n    ) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(ent_id_addr)?;\n        let ent_entity_id = self.globals.get_field_addr(ent_entity_id_addr)?;\n        let e = self\n            .world\n            .entity(ent_id)\n            .entity_id(ent_entity_id.0 as i16)?;\n        self.globals.put_entity_id(e, dest_addr)?;\n\n        Ok(())\n    }\n\n    pub fn op_load_fnc(\n        &mut self,\n        ent_id_addr: i16,\n        ent_function_id_addr: i16,\n        dest_addr: i16,\n    ) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(ent_id_addr)?;\n        let fnc_function_id = self.globals.get_field_addr(ent_function_id_addr)?;\n        let f = self\n            .world\n            .entity(ent_id)\n            .function_id(fnc_function_id.0 as i16)?;\n        self.globals.put_function_id(f, dest_addr)?;\n\n        Ok(())\n    }\n\n    pub fn op_address(\n        &mut self,\n        ent_id_addr: i16,\n        fld_addr_addr: i16,\n        dest_addr: i16,\n    ) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(ent_id_addr)?;\n        let fld_addr = self.globals.get_field_addr(fld_addr_addr)?;\n        self.globals.put_entity_field(\n            self.world.ent_fld_addr_to_i32(EntityFieldAddr {\n                entity_id: ent_id,\n                field_addr: fld_addr,\n            }),\n            dest_addr,\n        )?;\n\n        Ok(())\n    }\n\n    pub fn op_storep_f(\n        &mut self,\n        src_float_addr: i16,\n        dst_ent_fld_addr: i16,\n        unused: i16,\n    ) -> Result<(), ProgsError> {\n        if unused != 0 {\n            return Err(ProgsError::with_msg(\"storep_f: nonzero arg3\"));\n        }\n\n        let f = self.globals.get_float(src_float_addr)?;\n        let ent_fld_addr = self\n            .world\n            .ent_fld_addr_from_i32(self.globals.get_entity_field(dst_ent_fld_addr)?);\n        self.world\n            .entity_mut(ent_fld_addr.entity_id)?\n            .put_float(f, ent_fld_addr.field_addr.0 as i16)?;\n\n        Ok(())\n    }\n\n    pub fn op_storep_v(\n        &mut self,\n        src_vector_addr: i16,\n        dst_ent_fld_addr: i16,\n        unused: i16,\n    ) -> Result<(), ProgsError> {\n        if unused != 0 {\n            return Err(ProgsError::with_msg(\"storep_v: nonzero arg3\"));\n        }\n\n        let v = self.globals.get_vector(src_vector_addr)?;\n        let ent_fld_addr = self\n            .world\n            .ent_fld_addr_from_i32(self.globals.get_entity_field(dst_ent_fld_addr)?);\n        self.world\n            .entity_mut(ent_fld_addr.entity_id)?\n            .put_vector(v, ent_fld_addr.field_addr.0 as i16)?;\n\n        Ok(())\n    }\n\n    pub fn op_storep_s(\n        &mut self,\n        src_string_id_addr: i16,\n        dst_ent_fld_addr: i16,\n        unused: i16,\n    ) -> Result<(), ProgsError> {\n        if unused != 0 {\n            return Err(ProgsError::with_msg(\"storep_s: nonzero arg3\"));\n        }\n\n        let s = self.globals.string_id(src_string_id_addr)?;\n        let ent_fld_addr = self\n            .world\n            .ent_fld_addr_from_i32(self.globals.get_entity_field(dst_ent_fld_addr)?);\n        self.world\n            .entity_mut(ent_fld_addr.entity_id)?\n            .put_string_id(s, ent_fld_addr.field_addr.0 as i16)?;\n\n        Ok(())\n    }\n\n    pub fn op_storep_ent(\n        &mut self,\n        src_entity_id_addr: i16,\n        dst_ent_fld_addr: i16,\n        unused: i16,\n    ) -> Result<(), ProgsError> {\n        if unused != 0 {\n            return Err(ProgsError::with_msg(\"storep_ent: nonzero arg3\"));\n        }\n\n        let e = self.globals.entity_id(src_entity_id_addr)?;\n        let ent_fld_addr = self\n            .world\n            .ent_fld_addr_from_i32(self.globals.get_entity_field(dst_ent_fld_addr)?);\n        self.world\n            .entity_mut(ent_fld_addr.entity_id)?\n            .put_entity_id(e, ent_fld_addr.field_addr.0 as i16)?;\n\n        Ok(())\n    }\n\n    pub fn op_storep_fnc(\n        &mut self,\n        src_function_id_addr: i16,\n        dst_ent_fld_addr: i16,\n        unused: i16,\n    ) -> Result<(), ProgsError> {\n        if unused != 0 {\n            return Err(ProgsError::with_msg(\"storep_fnc: nonzero arg3\"));\n        }\n\n        let f = self.globals.function_id(src_function_id_addr)?;\n        let ent_fld_addr = self\n            .world\n            .ent_fld_addr_from_i32(self.globals.get_entity_field(dst_ent_fld_addr)?);\n        self.world\n            .entity_mut(ent_fld_addr.entity_id)?\n            .put_function_id(f, ent_fld_addr.field_addr.0 as i16)?;\n\n        Ok(())\n    }\n\n    pub fn op_state(\n        &mut self,\n        frame_id_addr: i16,\n        unused_b: i16,\n        unused_c: i16,\n    ) -> Result<(), ProgsError> {\n        if unused_b != 0 {\n            return Err(ProgsError::with_msg(\"storep_fnc: nonzero arg2\"));\n        } else if unused_c != 0 {\n            return Err(ProgsError::with_msg(\"storep_fnc: nonzero arg3\"));\n        }\n\n        let self_id = self.globals.entity_id(GlobalAddrEntity::Self_ as i16)?;\n        let self_ent = self.world.entity_mut(self_id)?;\n        let next_think_time = self.globals.get_float(GlobalAddrFloat::Time as i16)? + 0.1;\n\n        self_ent.put_float(next_think_time, FieldAddrFloat::NextThink as i16)?;\n\n        let frame_id = self.globals.get_float(frame_id_addr)?;\n        self_ent.put_float(frame_id, FieldAddrFloat::FrameId as i16)?;\n\n        Ok(())\n    }\n\n    // QuakeC built-in functions ==============================================\n\n    pub fn builtin_set_origin(&mut self) -> Result<(), ProgsError> {\n        let e_id = self.globals.entity_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        let origin = self.globals.get_vector(GLOBAL_ADDR_ARG_1 as i16)?;\n        self.set_entity_origin(e_id, Vector3::from(origin))?;\n\n        Ok(())\n    }\n\n    pub fn builtin_set_model(&mut self) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        let model_name_id = self.globals.string_id(GLOBAL_ADDR_ARG_1 as i16)?;\n        self.set_entity_model(ent_id, model_name_id)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_set_size(&mut self) -> Result<(), ProgsError> {\n        let e_id = self.globals.entity_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        let mins = self.globals.get_vector(GLOBAL_ADDR_ARG_1 as i16)?;\n        let maxs = self.globals.get_vector(GLOBAL_ADDR_ARG_2 as i16)?;\n        self.world.set_entity_size(e_id, mins.into(), maxs.into())?;\n\n        Ok(())\n    }\n\n    // TODO: move to Globals\n    pub fn builtin_random(&mut self) -> Result<(), ProgsError> {\n        self.globals\n            .put_float(rand::random(), GLOBAL_ADDR_RETURN as i16)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_spawn(&mut self) -> Result<(), ProgsError> {\n        let ent_id = self.spawn_entity()?;\n        self.globals\n            .put_entity_id(ent_id, GLOBAL_ADDR_RETURN as i16)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_remove(&mut self) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        self.world.remove_entity(ent_id)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_precache_sound(&mut self) -> Result<(), ProgsError> {\n        // TODO: disable precaching after server is active\n        // TODO: precaching doesn't actually load yet\n        let s_id = self.globals.string_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        self.precache_sound(s_id);\n        self.globals\n            .put_string_id(s_id, GLOBAL_ADDR_RETURN as i16)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_precache_model(&mut self) -> Result<(), ProgsError> {\n        // TODO: disable precaching after server is active\n        // TODO: precaching doesn't actually load yet\n        let s_id = self.globals.string_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        if self.model_id(s_id).is_none() {\n            self.precache_model(s_id);\n            self.world.add_model(&self.vfs, s_id)?;\n        }\n\n        self.globals\n            .put_string_id(s_id, GLOBAL_ADDR_RETURN as i16)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_dprint(&mut self) -> Result<(), ProgsError> {\n        let strs = self.string_table.borrow();\n        let s_id = self.globals.string_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        let string = strs.get(s_id).unwrap();\n        debug!(\"DPRINT: {}\", string);\n\n        Ok(())\n    }\n\n    pub fn builtin_drop_to_floor(&mut self) -> Result<(), ProgsError> {\n        let ent_id = self.globals.entity_id(GlobalAddrEntity::Self_ as i16)?;\n        let hit_floor = self.drop_entity_to_floor(ent_id)?;\n        self.globals\n            .put_float(hit_floor as u32 as f32, GLOBAL_ADDR_RETURN as i16)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_light_style(&mut self) -> Result<(), ProgsError> {\n        let index = match self.globals.get_float(GLOBAL_ADDR_ARG_0 as i16)? as i32 {\n            i if i < 0 => return Err(ProgsError::with_msg(\"negative lightstyle ID\")),\n            i => i as usize,\n        };\n        let val = self.globals.string_id(GLOBAL_ADDR_ARG_1 as i16)?;\n        self.set_lightstyle(index, val);\n\n        Ok(())\n    }\n\n    pub fn builtin_cvar(&mut self) -> Result<(), ProgsError> {\n        let s_id = self.globals.string_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        let strs = self.string_table.borrow();\n        let s = strs.get(s_id).unwrap();\n        let f = self.cvars.borrow().get_value(s).unwrap();\n        self.globals.put_float(f, GLOBAL_ADDR_RETURN as i16)?;\n\n        Ok(())\n    }\n\n    pub fn builtin_cvar_set(&mut self) -> Result<(), ProgsError> {\n        let strs = self.string_table.borrow();\n\n        let var_id = self.globals.string_id(GLOBAL_ADDR_ARG_0 as i16)?;\n        let var = strs.get(var_id).unwrap();\n        let val_id = self.globals.string_id(GLOBAL_ADDR_ARG_1 as i16)?;\n        let val = strs.get(val_id).unwrap();\n\n        self.cvars.borrow_mut().set(var, val).unwrap();\n\n        Ok(())\n    }\n\n    pub fn builtin_ambient_sound(&mut self) -> Result<(), ProgsError> {\n        let _pos = self.globals.get_vector(GLOBAL_ADDR_ARG_0 as i16)?;\n        let name = self.globals.string_id(GLOBAL_ADDR_ARG_1 as i16)?;\n        let _volume = self.globals.get_float(GLOBAL_ADDR_ARG_2 as i16)?;\n        let _attenuation = self.globals.get_float(GLOBAL_ADDR_ARG_3 as i16)?;\n\n        let _sound_index = match self.sound_id(name) {\n            Some(i) => i,\n            None => return Err(ProgsError::with_msg(\"sound not precached\")),\n        };\n\n        // TODO: write to server signon packet\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/server/precache.rs",
    "content": "use std::ops::Range;\n\nuse arrayvec::{ArrayString, ArrayVec};\n\n/// Maximum permitted length of a precache path.\nconst MAX_PRECACHE_PATH: usize = 64;\n\nconst MAX_PRECACHE_ENTRIES: usize = 256;\n\n/// A list of resources to be loaded before entering the game.\n///\n/// This is used by the server to inform clients which resources (sounds and\n/// models) they should load before joining. It also serves as the canonical\n/// mapping of resource IDs for a given level.\n// TODO: ideally, this is parameterized by the maximum number of entries, but\n// it's not currently possible to do { MAX_PRECACHE_PATH * N } where N is a\n// const generic parameter. In practice both models and sounds have a maximum\n// value of 256.\n#[derive(Debug)]\npub struct Precache {\n    str_data: ArrayString<{ MAX_PRECACHE_PATH * MAX_PRECACHE_ENTRIES }>,\n    items: ArrayVec<Range<usize>, MAX_PRECACHE_ENTRIES>,\n}\n\nimpl Precache {\n    /// Creates a new empty `Precache`.\n    pub fn new() -> Precache {\n        Precache {\n            str_data: ArrayString::new(),\n            items: ArrayVec::new(),\n        }\n    }\n\n    /// Retrieves an item from the precache if the item exists.\n    pub fn get(&self, index: usize) -> Option<&str> {\n        if index > self.items.len() {\n            return None;\n        }\n\n        let range = self.items[index].clone();\n        Some(&self.str_data[range])\n    }\n\n    /// Returns the index of the target value if it exists.\n    pub fn find<S>(&self, target: S) -> Option<usize>\n    where\n        S: AsRef<str>,\n    {\n        let (idx, _) = self\n            .iter()\n            .enumerate()\n            .find(|&(_, item)| item == target.as_ref())?;\n        Some(idx)\n    }\n\n    /// Adds an item to the precache.\n    ///\n    /// If the item already exists in the precache, this has no effect.\n    pub fn precache<S>(&mut self, item: S)\n    where\n        S: AsRef<str>,\n    {\n        let item = item.as_ref();\n\n        if item.len() > MAX_PRECACHE_PATH {\n            panic!(\n                \"precache name (\\\"{}\\\") too long: max length is {}\",\n                item, MAX_PRECACHE_PATH\n            );\n        }\n\n        if self.find(item).is_some() {\n            // Already precached.\n            return;\n        }\n\n        let start = self.str_data.len();\n        self.str_data.push_str(item);\n        let end = self.str_data.len();\n\n        self.items.push(start..end);\n    }\n\n    /// Returns an iterator over the values in the precache.\n    pub fn iter(&self) -> impl Iterator<Item = &str> {\n        self.items\n            .iter()\n            .cloned()\n            .map(move |range| &self.str_data[range])\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_precache_one() {\n        let mut p = Precache::new();\n\n        p.precache(\"hello\");\n        assert_eq!(Some(\"hello\"), p.get(0));\n    }\n\n    #[test]\n    fn test_precache_several() {\n        let mut p = Precache::new();\n\n        let items = &[\"Quake\", \"is\", \"a\", \"1996\", \"first-person\", \"shooter\"];\n\n        for item in items {\n            p.precache(item);\n        }\n\n        // Pick an element in the middle\n        assert_eq!(Some(\"first-person\"), p.get(4));\n\n        // Check all the elements\n        for (precached, &original) in p.iter().zip(items.iter()) {\n            assert_eq!(precached, original);\n        }\n    }\n}\n"
  },
  {
    "path": "src/server/progs/functions.rs",
    "content": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{cell::RefCell, convert::TryInto, rc::Rc};\n\nuse num::FromPrimitive;\n\nuse crate::server::progs::{ops::Opcode, ProgsError, StringId, StringTable};\n\npub const MAX_ARGS: usize = 8;\n\n#[derive(Clone, Debug)]\n#[repr(C)]\npub struct Statement {\n    pub opcode: Opcode,\n    pub arg1: i16,\n    pub arg2: i16,\n    pub arg3: i16,\n}\n\nimpl Statement {\n    pub fn new(op: i16, arg1: i16, arg2: i16, arg3: i16) -> Result<Statement, ProgsError> {\n        let opcode = match Opcode::from_i16(op) {\n            Some(o) => o,\n            None => return Err(ProgsError::with_msg(format!(\"Bad opcode 0x{:x}\", op))),\n        };\n\n        Ok(Statement {\n            opcode,\n            arg1,\n            arg2,\n            arg3,\n        })\n    }\n}\n\n#[derive(Copy, Clone, Debug, Default, PartialEq)]\n#[repr(C)]\npub struct FunctionId(pub usize);\n\nimpl TryInto<i32> for FunctionId {\n    type Error = ProgsError;\n\n    fn try_into(self) -> Result<i32, Self::Error> {\n        if self.0 > ::std::i32::MAX as usize {\n            Err(ProgsError::with_msg(\"function ID out of range\"))\n        } else {\n            Ok(self.0 as i32)\n        }\n    }\n}\n\n#[derive(Debug)]\npub enum FunctionKind {\n    BuiltIn(BuiltinFunctionId),\n    QuakeC(usize),\n}\n\n#[derive(Copy, Clone, Debug, FromPrimitive)]\npub enum BuiltinFunctionId {\n    // pr_builtin[0] is the null function\n    MakeVectors = 1,\n    SetOrigin = 2,\n    SetModel = 3,\n    SetSize = 4,\n    // pr_builtin[5] (PF_setabssize) was never implemented\n    Break = 6,\n    Random = 7,\n    Sound = 8,\n    Normalize = 9,\n    Error = 10,\n    ObjError = 11,\n    VLen = 12,\n    VecToYaw = 13,\n    Spawn = 14,\n    Remove = 15,\n    TraceLine = 16,\n    CheckClient = 17,\n    Find = 18,\n    PrecacheSound = 19,\n    PrecacheModel = 20,\n    StuffCmd = 21,\n    FindRadius = 22,\n    BPrint = 23,\n    SPrint = 24,\n    DPrint = 25,\n    FToS = 26,\n    VToS = 27,\n    CoreDump = 28,\n    TraceOn = 29,\n    TraceOff = 30,\n    EPrint = 31,\n    WalkMove = 32,\n    // pr_builtin[33] is not implemented\n    DropToFloor = 34,\n    LightStyle = 35,\n    RInt = 36,\n    Floor = 37,\n    Ceil = 38,\n    // pr_builtin[39] is not implemented\n    CheckBottom = 40,\n    PointContents = 41,\n    // pr_builtin[42] is not implemented\n    FAbs = 43,\n    Aim = 44,\n    Cvar = 45,\n    LocalCmd = 46,\n    NextEnt = 47,\n    Particle = 48,\n    ChangeYaw = 49,\n    // pr_builtin[50] is not implemented\n    VecToAngles = 51,\n    WriteByte = 52,\n    WriteChar = 53,\n    WriteShort = 54,\n    WriteLong = 55,\n    WriteCoord = 56,\n    WriteAngle = 57,\n    WriteString = 58,\n    WriteEntity = 59,\n    // pr_builtin[60] through pr_builtin[66] are only defined for Quake 2\n    MoveToGoal = 67,\n    PrecacheFile = 68,\n    MakeStatic = 69,\n    ChangeLevel = 70,\n    // pr_builtin[71] is not implemented\n    CvarSet = 72,\n    CenterPrint = 73,\n    AmbientSound = 74,\n    PrecacheModel2 = 75,\n    PrecacheSound2 = 76,\n    PrecacheFile2 = 77,\n    SetSpawnArgs = 78,\n}\n\n#[derive(Debug)]\npub struct FunctionDef {\n    pub kind: FunctionKind,\n    pub arg_start: usize,\n    pub locals: usize,\n    pub name_id: StringId,\n    pub srcfile_id: StringId,\n    pub argc: usize,\n    pub argsz: [u8; MAX_ARGS],\n}\n\n#[derive(Debug)]\npub struct Functions {\n    pub string_table: Rc<RefCell<StringTable>>,\n    pub defs: Box<[FunctionDef]>,\n    pub statements: Box<[Statement]>,\n}\n\nimpl Functions {\n    pub fn id_from_i32(&self, value: i32) -> Result<FunctionId, ProgsError> {\n        if value < 0 {\n            return Err(ProgsError::with_msg(\"id < 0\"));\n        }\n\n        if (value as usize) < self.defs.len() {\n            Ok(FunctionId(value as usize))\n        } else {\n            Err(ProgsError::with_msg(format!(\n                \"no function with ID {}\",\n                value\n            )))\n        }\n    }\n\n    pub fn get_def(&self, id: FunctionId) -> Result<&FunctionDef, ProgsError> {\n        if id.0 >= self.defs.len() {\n            Err(ProgsError::with_msg(format!(\n                \"No function with ID {}\",\n                id.0\n            )))\n        } else {\n            Ok(&self.defs[id.0])\n        }\n    }\n\n    pub fn find_function_by_name<S>(&self, name: S) -> Result<FunctionId, ProgsError>\n    where\n        S: AsRef<str>,\n    {\n        for (i, def) in self.defs.iter().enumerate() {\n            let strs = self.string_table.borrow();\n            let f_name = strs.get(def.name_id).ok_or_else(|| {\n                ProgsError::with_msg(format!(\"No string with ID {:?}\", def.name_id))\n            })?;\n            if f_name == name.as_ref() {\n                return Ok(FunctionId(i));\n            }\n        }\n\n        Err(ProgsError::with_msg(format!(\n            \"No function named {}\",\n            name.as_ref()\n        )))\n    }\n}\n"
  },
  {
    "path": "src/server/progs/globals.rs",
    "content": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{cell::RefCell, convert::TryInto, error::Error, fmt, rc::Rc};\n\nuse crate::server::progs::{\n    EntityId, FieldAddr, FunctionId, GlobalDef, StringId, StringTable, Type,\n};\n\nuse byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};\nuse cgmath::{Deg, Euler, InnerSpace, Matrix3, Vector3};\n\npub const GLOBAL_STATIC_START: usize = 28;\npub const GLOBAL_DYNAMIC_START: usize = 64;\n\npub const GLOBAL_STATIC_COUNT: usize = GLOBAL_DYNAMIC_START - GLOBAL_STATIC_START;\n\n#[allow(dead_code)]\npub const GLOBAL_ADDR_NULL: usize = 0;\npub const GLOBAL_ADDR_RETURN: usize = 1;\npub const GLOBAL_ADDR_ARG_0: usize = 4;\npub const GLOBAL_ADDR_ARG_1: usize = 7;\npub const GLOBAL_ADDR_ARG_2: usize = 10;\npub const GLOBAL_ADDR_ARG_3: usize = 13;\n#[allow(dead_code)]\npub const GLOBAL_ADDR_ARG_4: usize = 16;\n#[allow(dead_code)]\npub const GLOBAL_ADDR_ARG_5: usize = 19;\n#[allow(dead_code)]\npub const GLOBAL_ADDR_ARG_6: usize = 22;\n#[allow(dead_code)]\npub const GLOBAL_ADDR_ARG_7: usize = 25;\n\n#[derive(Debug)]\npub enum GlobalsError {\n    Io(::std::io::Error),\n    Address(isize),\n    Other(String),\n}\n\nimpl GlobalsError {\n    pub fn with_msg<S>(msg: S) -> Self\n    where\n        S: AsRef<str>,\n    {\n        GlobalsError::Other(msg.as_ref().to_owned())\n    }\n}\n\nimpl fmt::Display for GlobalsError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match *self {\n            GlobalsError::Io(ref err) => {\n                write!(f, \"I/O error: \")?;\n                err.fmt(f)\n            }\n            GlobalsError::Address(val) => write!(f, \"Invalid address ({})\", val),\n            GlobalsError::Other(ref msg) => write!(f, \"{}\", msg),\n        }\n    }\n}\n\nimpl Error for GlobalsError {}\n\nimpl From<::std::io::Error> for GlobalsError {\n    fn from(error: ::std::io::Error) -> Self {\n        GlobalsError::Io(error)\n    }\n}\n\npub trait GlobalAddr {\n    /// The type of value referenced by this address.\n    type Value;\n\n    /// Loads the value at this address.\n    fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError>;\n\n    /// Stores a value at this address.\n    fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(), GlobalsError>;\n}\n\n#[derive(Copy, Clone, FromPrimitive)]\npub enum GlobalAddrFloat {\n    Time = 31,\n    FrameTime = 32,\n    ForceRetouch = 33,\n    Deathmatch = 35,\n    Coop = 36,\n    TeamPlay = 37,\n    ServerFlags = 38,\n    TotalSecrets = 39,\n    TotalMonsters = 40,\n    FoundSecrets = 41,\n    KilledMonsters = 42,\n    Arg0 = 43,\n    Arg1 = 44,\n    Arg2 = 45,\n    Arg3 = 46,\n    Arg4 = 47,\n    Arg5 = 48,\n    Arg6 = 49,\n    Arg7 = 50,\n    Arg8 = 51,\n    Arg9 = 52,\n    Arg10 = 53,\n    Arg11 = 54,\n    Arg12 = 55,\n    Arg13 = 56,\n    Arg14 = 57,\n    Arg15 = 58,\n    VForwardX = 59,\n    VForwardY = 60,\n    VForwardZ = 61,\n    VUpX = 62,\n    VUpY = 63,\n    VUpZ = 64,\n    VRightX = 65,\n    VRightY = 66,\n    VRightZ = 67,\n    TraceAllSolid = 68,\n    TraceStartSolid = 69,\n    TraceFraction = 70,\n    TraceEndPosX = 71,\n    TraceEndPosY = 72,\n    TraceEndPosZ = 73,\n    TracePlaneNormalX = 74,\n    TracePlaneNormalY = 75,\n    TracePlaneNormalZ = 76,\n    TracePlaneDist = 77,\n    TraceInOpen = 79,\n    TraceInWater = 80,\n}\n\nimpl GlobalAddr for GlobalAddrFloat {\n    type Value = f32;\n\n    #[inline]\n    fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError> {\n        globals.get_float(*self as i16)\n    }\n\n    #[inline]\n    fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(), GlobalsError> {\n        globals.put_float(value, *self as i16)\n    }\n}\n\n#[derive(Copy, Clone, FromPrimitive)]\npub enum GlobalAddrVector {\n    VForward = 59,\n    VUp = 62,\n    VRight = 65,\n    TraceEndPos = 71,\n    TracePlaneNormal = 74,\n}\n\nimpl GlobalAddr for GlobalAddrVector {\n    type Value = [f32; 3];\n\n    #[inline]\n    fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError> {\n        globals.get_vector(*self as i16)\n    }\n\n    #[inline]\n    fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(), GlobalsError> {\n        globals.put_vector(value, *self as i16)\n    }\n}\n\n#[derive(FromPrimitive)]\npub enum GlobalAddrString {\n    MapName = 34,\n}\n\n#[derive(Copy, Clone, FromPrimitive)]\npub enum GlobalAddrEntity {\n    Self_ = 28,\n    Other = 29,\n    World = 30,\n    TraceEntity = 78,\n    MsgEntity = 81,\n}\n\nimpl GlobalAddr for GlobalAddrEntity {\n    type Value = EntityId;\n\n    #[inline]\n    fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError> {\n        globals.entity_id(*self as i16)\n    }\n\n    #[inline]\n    fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(), GlobalsError> {\n        globals.put_entity_id(value, *self as i16)\n    }\n}\n\n#[derive(FromPrimitive)]\npub enum GlobalAddrField {}\n\n#[derive(FromPrimitive)]\npub enum GlobalAddrFunction {\n    Main = 82,\n    StartFrame = 83,\n    PlayerPreThink = 84,\n    PlayerPostThink = 85,\n    ClientKill = 86,\n    ClientConnect = 87,\n    PutClientInServer = 88,\n    ClientDisconnect = 89,\n    SetNewArgs = 90,\n    SetChangeArgs = 91,\n}\n\n#[derive(Debug)]\npub struct Globals {\n    string_table: Rc<RefCell<StringTable>>,\n    defs: Box<[GlobalDef]>,\n    addrs: Box<[[u8; 4]]>,\n}\n\nimpl Globals {\n    /// Constructs a new `Globals` object.\n    pub fn new(\n        string_table: Rc<RefCell<StringTable>>,\n        defs: Box<[GlobalDef]>,\n        addrs: Box<[[u8; 4]]>,\n    ) -> Globals {\n        Globals {\n            string_table,\n            defs,\n            addrs,\n        }\n    }\n\n    /// Performs a type check at `addr` with type `type_`.\n    ///\n    /// The type check allows checking `QFloat` against `QVector` and vice-versa, since vectors have\n    /// overlapping definitions with their x-components (e.g. a vector `origin` and its x-component\n    /// `origin_X` will have the same address).\n    pub fn type_check(&self, addr: usize, type_: Type) -> Result<(), GlobalsError> {\n        match self.defs.iter().find(|def| def.offset as usize == addr) {\n            Some(d) => {\n                if type_ == d.type_ {\n                    return Ok(());\n                } else if type_ == Type::QFloat && d.type_ == Type::QVector {\n                    return Ok(());\n                } else if type_ == Type::QVector && d.type_ == Type::QFloat {\n                    return Ok(());\n                } else {\n                    return Err(GlobalsError::with_msg(\"type check failed\"));\n                }\n            }\n            None => return Ok(()),\n        }\n    }\n\n    /// Returns a reference to the memory at the given address.\n    pub fn get_addr(&self, addr: i16) -> Result<&[u8], GlobalsError> {\n        if addr < 0 {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        Ok(&self.addrs[addr])\n    }\n\n    /// Returns a mutable reference to the memory at the given address.\n    pub fn get_addr_mut(&mut self, addr: i16) -> Result<&mut [u8], GlobalsError> {\n        if addr < 0 {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        Ok(&mut self.addrs[addr])\n    }\n\n    /// Returns a copy of the memory at the given address.\n    pub fn get_bytes(&self, addr: i16) -> Result<[u8; 4], GlobalsError> {\n        if addr < 0 {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        Ok(self.addrs[addr])\n    }\n\n    /// Writes the provided data to the memory at the given address.\n    ///\n    /// This can be used to circumvent the type checker in cases where an operation is not dependent\n    /// of the type of the data.\n    pub fn put_bytes(&mut self, val: [u8; 4], addr: i16) -> Result<(), GlobalsError> {\n        if addr < 0 {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(GlobalsError::Address(addr as isize));\n        }\n\n        self.addrs[addr] = val;\n        Ok(())\n    }\n\n    /// Loads an `i32` from the given virtual address.\n    pub fn get_int(&self, addr: i16) -> Result<i32, GlobalsError> {\n        Ok(self.get_addr(addr)?.read_i32::<LittleEndian>()?)\n    }\n\n    /// Loads an `i32` from the given virtual address.\n    pub fn put_int(&mut self, val: i32, addr: i16) -> Result<(), GlobalsError> {\n        self.get_addr_mut(addr)?.write_i32::<LittleEndian>(val)?;\n        Ok(())\n    }\n\n    /// Loads an `f32` from the given virtual address.\n    pub fn get_float(&self, addr: i16) -> Result<f32, GlobalsError> {\n        self.type_check(addr as usize, Type::QFloat)?;\n        Ok(self.get_addr(addr)?.read_f32::<LittleEndian>()?)\n    }\n\n    /// Stores an `f32` at the given virtual address.\n    pub fn put_float(&mut self, val: f32, addr: i16) -> Result<(), GlobalsError> {\n        self.type_check(addr as usize, Type::QFloat)?;\n        self.get_addr_mut(addr)?.write_f32::<LittleEndian>(val)?;\n        Ok(())\n    }\n\n    /// Loads an `[f32; 3]` from the given virtual address.\n    pub fn get_vector(&self, addr: i16) -> Result<[f32; 3], GlobalsError> {\n        self.type_check(addr as usize, Type::QVector)?;\n\n        let mut v = [0.0; 3];\n\n        for i in 0..3 {\n            v[i] = self.get_float(addr + i as i16)?;\n        }\n\n        Ok(v)\n    }\n\n    /// Stores an `[f32; 3]` at the given virtual address.\n    pub fn put_vector(&mut self, val: [f32; 3], addr: i16) -> Result<(), GlobalsError> {\n        self.type_check(addr as usize, Type::QVector)?;\n\n        for i in 0..3 {\n            self.put_float(val[i], addr + i as i16)?;\n        }\n\n        Ok(())\n    }\n\n    /// Loads a `StringId` from the given virtual address.\n    pub fn string_id(&self, addr: i16) -> Result<StringId, GlobalsError> {\n        self.type_check(addr as usize, Type::QString)?;\n\n        Ok(StringId(\n            self.get_addr(addr)?.read_i32::<LittleEndian>()? as usize\n        ))\n    }\n\n    /// Stores a `StringId` at the given virtual address.\n    pub fn put_string_id(&mut self, val: StringId, addr: i16) -> Result<(), GlobalsError> {\n        self.type_check(addr as usize, Type::QString)?;\n\n        self.get_addr_mut(addr)?\n            .write_i32::<LittleEndian>(val.try_into().unwrap())?;\n        Ok(())\n    }\n\n    /// Loads an `EntityId` from the given virtual address.\n    pub fn entity_id(&self, addr: i16) -> Result<EntityId, GlobalsError> {\n        self.type_check(addr as usize, Type::QEntity)?;\n\n        match self.get_addr(addr)?.read_i32::<LittleEndian>()? {\n            e if e < 0 => Err(GlobalsError::with_msg(format!(\n                \"Negative entity ID ({})\",\n                e\n            ))),\n            e => Ok(EntityId(e as usize)),\n        }\n    }\n\n    /// Stores an `EntityId` at the given virtual address.\n    pub fn put_entity_id(&mut self, val: EntityId, addr: i16) -> Result<(), GlobalsError> {\n        self.type_check(addr as usize, Type::QEntity)?;\n\n        self.get_addr_mut(addr)?\n            .write_i32::<LittleEndian>(val.0 as i32)?;\n        Ok(())\n    }\n\n    /// Loads a `FieldAddr` from the given virtual address.\n    pub fn get_field_addr(&self, addr: i16) -> Result<FieldAddr, GlobalsError> {\n        self.type_check(addr as usize, Type::QField)?;\n\n        match self.get_addr(addr)?.read_i32::<LittleEndian>()? {\n            f if f < 0 => Err(GlobalsError::with_msg(format!(\n                \"Negative entity ID ({})\",\n                f\n            ))),\n            f => Ok(FieldAddr(f as usize)),\n        }\n    }\n\n    /// Stores a `FieldAddr` at the given virtual address.\n    pub fn put_field_addr(&mut self, val: FieldAddr, addr: i16) -> Result<(), GlobalsError> {\n        self.type_check(addr as usize, Type::QField)?;\n        self.get_addr_mut(addr)?\n            .write_i32::<LittleEndian>(val.0 as i32)?;\n        Ok(())\n    }\n\n    /// Loads a `FunctionId` from the given virtual address.\n    pub fn function_id(&self, addr: i16) -> Result<FunctionId, GlobalsError> {\n        self.type_check(addr as usize, Type::QFunction)?;\n        Ok(FunctionId(\n            self.get_addr(addr)?.read_i32::<LittleEndian>()? as usize\n        ))\n    }\n\n    /// Stores a `FunctionId` at the given virtual address.\n    pub fn put_function_id(&mut self, val: FunctionId, addr: i16) -> Result<(), GlobalsError> {\n        self.type_check(addr as usize, Type::QFunction)?;\n        self.get_addr_mut(addr)?\n            .write_i32::<LittleEndian>(val.try_into().unwrap())?;\n        Ok(())\n    }\n\n    // TODO: typecheck these with QPointer?\n\n    pub fn get_entity_field(&self, addr: i16) -> Result<i32, GlobalsError> {\n        Ok(self.get_addr(addr)?.read_i32::<LittleEndian>()?)\n    }\n\n    pub fn put_entity_field(&mut self, val: i32, addr: i16) -> Result<(), GlobalsError> {\n        self.get_addr_mut(addr)?.write_i32::<LittleEndian>(val)?;\n        Ok(())\n    }\n\n    pub fn load<A: GlobalAddr>(&self, addr: A) -> Result<A::Value, GlobalsError> {\n        addr.load(self)\n    }\n\n    pub fn store<A: GlobalAddr>(&mut self, addr: A, value: A::Value) -> Result<(), GlobalsError> {\n        addr.store(self, value)\n    }\n\n    /// Copies the data at `src_addr` to `dst_addr` without type checking.\n    pub fn untyped_copy(&mut self, src_addr: i16, dst_addr: i16) -> Result<(), GlobalsError> {\n        let src = self.get_addr(src_addr)?.to_owned();\n        let dst = self.get_addr_mut(dst_addr)?;\n\n        for i in 0..4 {\n            dst[i] = src[i]\n        }\n\n        Ok(())\n    }\n\n    // QuakeC instructions =====================================================\n\n    pub fn op_mul_f(&mut self, f1_id: i16, f2_id: i16, prod_id: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_id)?;\n        let f2 = self.get_float(f2_id)?;\n        self.put_float(f1 * f2, prod_id)?;\n\n        Ok(())\n    }\n\n    // MUL_V: Vector dot-product\n    pub fn op_mul_v(&mut self, v1_id: i16, v2_id: i16, dot_id: i16) -> Result<(), GlobalsError> {\n        let v1 = self.get_vector(v1_id)?;\n        let v2 = self.get_vector(v2_id)?;\n\n        let mut dot = 0.0;\n\n        for c in 0..3 {\n            dot += v1[c] * v2[c];\n        }\n        self.put_float(dot, dot_id)?;\n\n        Ok(())\n    }\n\n    // MUL_FV: Component-wise multiplication of vector by scalar\n    pub fn op_mul_fv(&mut self, f_id: i16, v_id: i16, prod_id: i16) -> Result<(), GlobalsError> {\n        let f = self.get_float(f_id)?;\n        let v = self.get_vector(v_id)?;\n\n        let mut prod = [0.0; 3];\n        for c in 0..prod.len() {\n            prod[c] = v[c] * f;\n        }\n\n        self.put_vector(prod, prod_id)?;\n\n        Ok(())\n    }\n\n    // MUL_VF: Component-wise multiplication of vector by scalar\n    pub fn op_mul_vf(&mut self, v_id: i16, f_id: i16, prod_id: i16) -> Result<(), GlobalsError> {\n        let v = self.get_vector(v_id)?;\n        let f = self.get_float(f_id)?;\n\n        let mut prod = [0.0; 3];\n        for c in 0..prod.len() {\n            prod[c] = v[c] * f;\n        }\n\n        self.put_vector(prod, prod_id)?;\n\n        Ok(())\n    }\n\n    // DIV: Float division\n    pub fn op_div(&mut self, f1_id: i16, f2_id: i16, quot_id: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_id)?;\n        let f2 = self.get_float(f2_id)?;\n        self.put_float(f1 / f2, quot_id)?;\n\n        Ok(())\n    }\n\n    // ADD_F: Float addition\n    pub fn op_add_f(&mut self, f1_ofs: i16, f2_ofs: i16, sum_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n        self.put_float(f1 + f2, sum_ofs)?;\n\n        Ok(())\n    }\n\n    // ADD_V: Vector addition\n    pub fn op_add_v(&mut self, v1_id: i16, v2_id: i16, sum_id: i16) -> Result<(), GlobalsError> {\n        let v1 = self.get_vector(v1_id)?;\n        let v2 = self.get_vector(v2_id)?;\n\n        let mut sum = [0.0; 3];\n        for c in 0..sum.len() {\n            sum[c] = v1[c] + v2[c];\n        }\n\n        self.put_vector(sum, sum_id)?;\n\n        Ok(())\n    }\n\n    // SUB_F: Float subtraction\n    pub fn op_sub_f(&mut self, f1_id: i16, f2_id: i16, diff_id: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_id)?;\n        let f2 = self.get_float(f2_id)?;\n        self.put_float(f1 - f2, diff_id)?;\n\n        Ok(())\n    }\n\n    // SUB_V: Vector subtraction\n    pub fn op_sub_v(&mut self, v1_id: i16, v2_id: i16, diff_id: i16) -> Result<(), GlobalsError> {\n        let v1 = self.get_vector(v1_id)?;\n        let v2 = self.get_vector(v2_id)?;\n\n        let mut diff = [0.0; 3];\n        for c in 0..diff.len() {\n            diff[c] = v1[c] - v2[c];\n        }\n\n        self.put_vector(diff, diff_id)?;\n\n        Ok(())\n    }\n\n    // EQ_F: Test equality of two floats\n    pub fn op_eq_f(&mut self, f1_id: i16, f2_id: i16, eq_id: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_id)?;\n        let f2 = self.get_float(f2_id)?;\n        self.put_float(\n            match f1 == f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            eq_id,\n        )?;\n\n        Ok(())\n    }\n\n    // EQ_V: Test equality of two vectors\n    pub fn op_eq_v(&mut self, v1_id: i16, v2_id: i16, eq_id: i16) -> Result<(), GlobalsError> {\n        let v1 = self.get_vector(v1_id)?;\n        let v2 = self.get_vector(v2_id)?;\n        self.put_float(\n            match v1 == v2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            eq_id,\n        )?;\n\n        Ok(())\n    }\n\n    // EQ_S: Test equality of two strings\n    pub fn op_eq_s(&mut self, s1_ofs: i16, s2_ofs: i16, eq_ofs: i16) -> Result<(), GlobalsError> {\n        if s1_ofs < 0 || s2_ofs < 0 {\n            return Err(GlobalsError::with_msg(\"eq_s: negative string offset\"));\n        }\n\n        if s1_ofs == s2_ofs || self.string_id(s1_ofs)? == self.string_id(s2_ofs)? {\n            self.put_float(1.0, eq_ofs)?;\n        } else {\n            self.put_float(0.0, eq_ofs)?;\n        }\n\n        Ok(())\n    }\n\n    // EQ_ENT: Test equality of two entities (by identity)\n    pub fn op_eq_ent(&mut self, e1_ofs: i16, e2_ofs: i16, eq_ofs: i16) -> Result<(), GlobalsError> {\n        let e1 = self.entity_id(e1_ofs)?;\n        let e2 = self.entity_id(e2_ofs)?;\n\n        self.put_float(\n            match e1 == e2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            eq_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // EQ_FNC: Test equality of two functions (by identity)\n    pub fn op_eq_fnc(&mut self, f1_ofs: i16, f2_ofs: i16, eq_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.function_id(f1_ofs)?;\n        let f2 = self.function_id(f2_ofs)?;\n\n        self.put_float(\n            match f1 == f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            eq_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // NE_F: Test inequality of two floats\n    pub fn op_ne_f(&mut self, f1_ofs: i16, f2_ofs: i16, ne_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n        self.put_float(\n            match f1 != f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            ne_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // NE_V: Test inequality of two vectors\n    pub fn op_ne_v(&mut self, v1_ofs: i16, v2_ofs: i16, ne_ofs: i16) -> Result<(), GlobalsError> {\n        let v1 = self.get_vector(v1_ofs)?;\n        let v2 = self.get_vector(v2_ofs)?;\n        self.put_float(\n            match v1 != v2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            ne_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // NE_S: Test inequality of two strings\n    pub fn op_ne_s(&mut self, s1_ofs: i16, s2_ofs: i16, ne_ofs: i16) -> Result<(), GlobalsError> {\n        if s1_ofs < 0 || s2_ofs < 0 {\n            return Err(GlobalsError::with_msg(\"eq_s: negative string offset\"));\n        }\n\n        if s1_ofs != s2_ofs && self.string_id(s1_ofs)? != self.string_id(s2_ofs)? {\n            self.put_float(1.0, ne_ofs)?;\n        } else {\n            self.put_float(0.0, ne_ofs)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn op_ne_ent(&mut self, e1_ofs: i16, e2_ofs: i16, ne_ofs: i16) -> Result<(), GlobalsError> {\n        let e1 = self.entity_id(e1_ofs)?;\n        let e2 = self.entity_id(e2_ofs)?;\n\n        self.put_float(\n            match e1 != e2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            ne_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    pub fn op_ne_fnc(&mut self, f1_ofs: i16, f2_ofs: i16, ne_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.function_id(f1_ofs)?;\n        let f2 = self.function_id(f2_ofs)?;\n\n        self.put_float(\n            match f1 != f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            ne_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // LE: Less than or equal to comparison\n    pub fn op_le(&mut self, f1_ofs: i16, f2_ofs: i16, le_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n        self.put_float(\n            match f1 <= f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            le_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // GE: Greater than or equal to comparison\n    pub fn op_ge(&mut self, f1_ofs: i16, f2_ofs: i16, ge_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n        self.put_float(\n            match f1 >= f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            ge_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // LT: Less than comparison\n    pub fn op_lt(&mut self, f1_ofs: i16, f2_ofs: i16, lt_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n        self.put_float(\n            match f1 < f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            lt_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // GT: Greater than comparison\n    pub fn op_gt(&mut self, f1_ofs: i16, f2_ofs: i16, gt_ofs: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n        self.put_float(\n            match f1 > f2 {\n                true => 1.0,\n                false => 0.0,\n            },\n            gt_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // STORE_F\n    pub fn op_store_f(\n        &mut self,\n        src_ofs: i16,\n        dest_ofs: i16,\n        unused: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg3 to STORE_F\"));\n        }\n\n        let f = self.get_float(src_ofs)?;\n        self.put_float(f, dest_ofs)?;\n\n        Ok(())\n    }\n\n    // STORE_V\n    pub fn op_store_v(\n        &mut self,\n        src_ofs: i16,\n        dest_ofs: i16,\n        unused: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg3 to STORE_V\"));\n        }\n\n        if dest_ofs > 0 && dest_ofs < GLOBAL_STATIC_START as i16 {\n            // Untyped copy is required because STORE_V is used to copy function arguments into the global\n            // argument slots.\n            //\n            // See https://github.com/id-Software/Quake-Tools/blob/master/qcc/pr_comp.c#L362\n            for c in 0..3 {\n                self.untyped_copy(src_ofs + c as i16, dest_ofs + c as i16)?;\n            }\n        } else {\n            for c in 0..3 {\n                let f = self.get_float(src_ofs + c)?;\n                self.put_float(f, dest_ofs + c)?;\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn op_store_s(\n        &mut self,\n        src_ofs: i16,\n        dest_ofs: i16,\n        unused: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg3 to STORE_S\"));\n        }\n\n        let s = self.string_id(src_ofs)?;\n        self.put_string_id(s, dest_ofs)?;\n\n        Ok(())\n    }\n\n    pub fn op_store_ent(\n        &mut self,\n        src_ofs: i16,\n        dest_ofs: i16,\n        unused: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg3 to STORE_ENT\"));\n        }\n\n        let ent = self.entity_id(src_ofs)?;\n        self.put_entity_id(ent, dest_ofs)?;\n\n        Ok(())\n    }\n\n    pub fn op_store_fld(\n        &mut self,\n        src_ofs: i16,\n        dest_ofs: i16,\n        unused: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg3 to STORE_FLD\"));\n        }\n\n        let fld = self.get_field_addr(src_ofs)?;\n        self.put_field_addr(fld, dest_ofs)?;\n\n        Ok(())\n    }\n\n    pub fn op_store_fnc(\n        &mut self,\n        src_ofs: i16,\n        dest_ofs: i16,\n        unused: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg3 to STORE_FNC\"));\n        }\n\n        let fnc = self.function_id(src_ofs)?;\n        self.put_function_id(fnc, dest_ofs)?;\n\n        Ok(())\n    }\n\n    // NOT_F: Compare float to 0.0\n    pub fn op_not_f(&mut self, f_id: i16, unused: i16, not_id: i16) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg2 to NOT_F\"));\n        }\n\n        let f = self.get_float(f_id)?;\n        self.put_float(\n            match f == 0.0 {\n                true => 1.0,\n                false => 0.0,\n            },\n            not_id,\n        )?;\n\n        Ok(())\n    }\n\n    // NOT_V: Compare vec to { 0.0, 0.0, 0.0 }\n    pub fn op_not_v(&mut self, v_id: i16, unused: i16, not_id: i16) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg2 to NOT_V\"));\n        }\n\n        let v = self.get_vector(v_id)?;\n        let zero_vec = [0.0; 3];\n        self.put_vector(\n            match v == zero_vec {\n                true => [1.0; 3],\n                false => zero_vec,\n            },\n            not_id,\n        )?;\n\n        Ok(())\n    }\n\n    // NOT_S: Compare string to null string\n    pub fn op_not_s(&mut self, s_ofs: i16, unused: i16, not_ofs: i16) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg2 to NOT_S\"));\n        }\n\n        if s_ofs < 0 {\n            return Err(GlobalsError::with_msg(\"not_s: negative string offset\"));\n        }\n\n        let s = self.string_id(s_ofs)?;\n\n        if s_ofs == 0 || s.0 == 0 {\n            self.put_float(1.0, not_ofs)?;\n        } else {\n            self.put_float(0.0, not_ofs)?;\n        }\n\n        Ok(())\n    }\n\n    // NOT_FNC: Compare function to null function (0)\n    pub fn op_not_fnc(\n        &mut self,\n        fnc_id_ofs: i16,\n        unused: i16,\n        not_ofs: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg2 to NOT_FNC\"));\n        }\n\n        let fnc_id = self.function_id(fnc_id_ofs)?;\n        self.put_float(\n            match fnc_id {\n                FunctionId(0) => 1.0,\n                _ => 0.0,\n            },\n            not_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // NOT_ENT: Compare entity to null entity (0)\n    pub fn op_not_ent(\n        &mut self,\n        ent_ofs: i16,\n        unused: i16,\n        not_ofs: i16,\n    ) -> Result<(), GlobalsError> {\n        if unused != 0 {\n            return Err(GlobalsError::with_msg(\"Nonzero arg2 to NOT_ENT\"));\n        }\n\n        let ent = self.entity_id(ent_ofs)?;\n        self.put_float(\n            match ent {\n                EntityId(0) => 1.0,\n                _ => 0.0,\n            },\n            not_ofs,\n        )?;\n\n        Ok(())\n    }\n\n    // AND: Logical AND\n    pub fn op_and(&mut self, f1_id: i16, f2_id: i16, and_id: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_id)?;\n        let f2 = self.get_float(f2_id)?;\n        self.put_float(\n            match f1 != 0.0 && f2 != 0.0 {\n                true => 1.0,\n                false => 0.0,\n            },\n            and_id,\n        )?;\n\n        Ok(())\n    }\n\n    // OR: Logical OR\n    pub fn op_or(&mut self, f1_id: i16, f2_id: i16, or_id: i16) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_id)?;\n        let f2 = self.get_float(f2_id)?;\n        self.put_float(\n            match f1 != 0.0 || f2 != 0.0 {\n                true => 1.0,\n                false => 0.0,\n            },\n            or_id,\n        )?;\n\n        Ok(())\n    }\n\n    // BIT_AND: Bitwise AND\n    pub fn op_bit_and(\n        &mut self,\n        f1_ofs: i16,\n        f2_ofs: i16,\n        bit_and_ofs: i16,\n    ) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n\n        self.put_float((f1 as i32 & f2 as i32) as f32, bit_and_ofs)?;\n\n        Ok(())\n    }\n\n    // BIT_OR: Bitwise OR\n    pub fn op_bit_or(\n        &mut self,\n        f1_ofs: i16,\n        f2_ofs: i16,\n        bit_or_ofs: i16,\n    ) -> Result<(), GlobalsError> {\n        let f1 = self.get_float(f1_ofs)?;\n        let f2 = self.get_float(f2_ofs)?;\n\n        self.put_float((f1 as i32 | f2 as i32) as f32, bit_or_ofs)?;\n\n        Ok(())\n    }\n\n    // QuakeC built-in functions ===============================================\n\n    #[inline]\n    pub fn builtin_random(&mut self) -> Result<(), GlobalsError> {\n        self.put_float(rand::random(), GLOBAL_ADDR_RETURN as i16)\n    }\n\n    /// Calculate `v_forward`, `v_right` and `v_up` from `angles`.\n    ///\n    /// This requires some careful coordinate system transformations. Angle vectors are stored\n    /// as `[pitch, yaw, roll]` -- that is, rotations about the lateral (right), vertical (up), and\n    /// longitudinal (forward) axes respectively. However, Quake's coordinate system maps `x` to the\n    /// longitudinal (forward) axis, `y` to the *negative* lateral (leftward) axis, and `z` to the\n    /// vertical (up) axis. As a result, the rotation matrix has to be calculated from `[roll,\n    /// -pitch, yaw]` instead.\n    pub fn make_vectors(&mut self) -> Result<(), GlobalsError> {\n        let angles = self.get_vector(GLOBAL_ADDR_ARG_0 as i16)?;\n\n        let rotation_matrix = make_vectors(angles);\n\n        self.put_vector(rotation_matrix.x.into(), GlobalAddrVector::VForward as i16)?;\n        self.put_vector(rotation_matrix.y.into(), GlobalAddrVector::VRight as i16)?;\n        self.put_vector(rotation_matrix.z.into(), GlobalAddrVector::VUp as i16)?;\n\n        Ok(())\n    }\n\n    /// Calculate the magnitude of a vector.\n    ///\n    /// Loads the vector from `GLOBAL_ADDR_ARG_0` and stores its magnitude at\n    /// `GLOBAL_ADDR_RETURN`.\n    pub fn builtin_v_len(&mut self) -> Result<(), GlobalsError> {\n        let v = Vector3::from(self.get_vector(GLOBAL_ADDR_ARG_0 as i16)?);\n        self.put_float(v.magnitude(), GLOBAL_ADDR_RETURN as i16)?;\n        Ok(())\n    }\n\n    /// Calculate a yaw angle from a direction vector.\n    ///\n    /// Loads the direction vector from `GLOBAL_ADDR_ARG_0` and stores the yaw value at\n    /// `GLOBAL_ADDR_RETURN`.\n    pub fn builtin_vec_to_yaw(&mut self) -> Result<(), GlobalsError> {\n        let v = self.get_vector(GLOBAL_ADDR_ARG_0 as i16)?;\n\n        let mut yaw;\n        if v[0] == 0.0 || v[1] == 0.0 {\n            yaw = 0.0;\n        } else {\n            yaw = v[1].atan2(v[0]).to_degrees();\n            if yaw < 0.0 {\n                yaw += 360.0;\n            }\n        }\n\n        self.put_float(yaw, GLOBAL_ADDR_RETURN as i16)?;\n        Ok(())\n    }\n\n    /// Round a float to the nearest integer.\n    ///\n    /// Loads the float from `GLOBAL_ADDR_ARG_0` and stores the rounded value at\n    /// `GLOBAL_ADDR_RETURN`.\n    pub fn builtin_r_int(&mut self) -> Result<(), GlobalsError> {\n        let f = self.get_float(GLOBAL_ADDR_ARG_0 as i16)?;\n        self.put_float(f.round(), GLOBAL_ADDR_RETURN as i16)?;\n        Ok(())\n    }\n\n    /// Round a float to the nearest integer less than or equal to it.\n    ///\n    /// Loads the float from `GLOBAL_ADDR_ARG_0` and stores the rounded value at\n    /// `GLOBAL_ADDR_RETURN`.\n    pub fn builtin_floor(&mut self) -> Result<(), GlobalsError> {\n        let f = self.get_float(GLOBAL_ADDR_ARG_0 as i16)?;\n        self.put_float(f.floor(), GLOBAL_ADDR_RETURN as i16)?;\n        Ok(())\n    }\n\n    /// Round a float to the nearest integer greater than or equal to it.\n    ///\n    /// Loads the float from `GLOBAL_ADDR_ARG_0` and stores the rounded value at\n    /// `GLOBAL_ADDR_RETURN`.\n    pub fn builtin_ceil(&mut self) -> Result<(), GlobalsError> {\n        let f = self.get_float(GLOBAL_ADDR_ARG_0 as i16)?;\n        self.put_float(f.ceil(), GLOBAL_ADDR_RETURN as i16)?;\n        Ok(())\n    }\n\n    /// Calculate the absolute value of a float.\n    ///\n    /// Loads the float from `GLOBAL_ADDR_ARG_0` and stores its absolute value at\n    /// `GLOBAL_ADDR_RETURN`.\n    pub fn builtin_f_abs(&mut self) -> Result<(), GlobalsError> {\n        let f = self.get_float(GLOBAL_ADDR_ARG_0 as i16)?;\n        self.put_float(f.abs(), GLOBAL_ADDR_RETURN as i16)?;\n        Ok(())\n    }\n}\n\npub fn make_vectors(angles: [f32; 3]) -> Matrix3<f32> {\n    let pitch = Deg(-angles[0]);\n    let yaw = Deg(angles[1]);\n    let roll = Deg(angles[2]);\n\n    Matrix3::from(Euler::new(roll, pitch, yaw))\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    use cgmath::SquareMatrix;\n\n    #[test]\n    fn test_make_vectors_no_rotation() {\n        let angles_zero = [0.0; 3];\n        let result = make_vectors(angles_zero);\n        assert_eq!(Matrix3::identity(), result);\n    }\n\n    #[test]\n    fn test_make_vectors_pitch() {\n        let pitch_90 = [90.0, 0.0, 0.0];\n        let result = make_vectors(pitch_90);\n        assert_eq!(Matrix3::from_angle_y(Deg(-90.0)), result);\n    }\n\n    #[test]\n    fn test_make_vectors_yaw() {\n        let yaw_90 = [0.0, 90.0, 0.0];\n        let result = make_vectors(yaw_90);\n        assert_eq!(Matrix3::from_angle_z(Deg(90.0)), result);\n    }\n\n    #[test]\n    fn test_make_vectors_roll() {\n        let roll_90 = [0.0, 0.0, 90.0];\n        let result = make_vectors(roll_90);\n        assert_eq!(Matrix3::from_angle_x(Deg(90.0)), result);\n    }\n}\n"
  },
  {
    "path": "src/server/progs/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n//! QuakeC bytecode interpreter\n//!\n//! # Loading\n//!\n//! QuakeC bytecode is typically loaded from `progs.dat` or `qwprogs.dat`. Bytecode files begin with\n//! a brief header with an `i32` format version number (which must equal VERSION) and an `i32` CRC\n//! checksum to ensure the correct bytecode is being loaded.\n//!\n//! ```text\n//! version: i32,\n//! crc: i32,\n//! ```\n//!\n//! This is followed by a series of six lumps acting as a directory into the file data. Each lump\n//! consists of an `i32` byte offset into the file data and an `i32` element count.\n//!\n//! ```text\n//! statement_offset: i32,\n//! statement_count: i32,\n//!\n//! globaldef_offset: i32,\n//! globaldef_count: i32,\n//!\n//! fielddef_offset: i32,\n//! fielddef_count: i32,\n//!\n//! function_offset: i32,\n//! function_count: i32,\n//!\n//! string_offset: i32,\n//! string_count: i32,\n//!\n//! global_offset: i32,\n//! global_count: i32,\n//! ```\n//!\n//! These offsets are not guaranteed to be in order, and in fact `progs.dat` usually has the string\n//! section first. Offsets are in bytes from the beginning of the file.\n//!\n//! ## String data\n//!\n//! The string data block is located at the offset given by `string_offset` and consists of a series\n//! of null-terminated ASCII strings laid end-to-end. The first string is always the empty string,\n//! i.e. the first byte is always the null byte. The total size in bytes of the string data is given\n//! by `string_count`.\n//!\n//! ## Statements\n//!\n//! The statement table is located at the offset given by `statement_offset` and consists of\n//! `statement_count` 8-byte instructions of the form\n//!\n//! ```text\n//! opcode: u16,\n//! arg1: i16,\n//! arg2: i16,\n//! arg3: i16,\n//! ```\n//!\n//! Not every opcode uses three arguments, but all statements have space for three arguments anyway,\n//! probably for simplicity. The semantics of these arguments differ depending on the opcode.\n//!\n//! ## Function Definitions\n//!\n//! Function definitions contain both high-level information about the function (name and source\n//! file) and low-level information necessary to execute it (entry point, argument count, etc).\n//! Functions are stored on disk as follows:\n//!\n//! ```text\n//! statement_id: i32,     // index of first statement; negatives are built-in functions\n//! arg_start: i32,        // address to store/load first argument\n//! local_count: i32,      // number of local variables on the stack\n//! profile: i32,          // incremented every time function called\n//! fnc_name_ofs: i32,     // offset of function name in string table\n//! srcfile_name_ofs: i32, // offset of source file name in string table\n//! arg_count: i32,        // number of arguments (max. 8)\n//! arg_sizes: [u8; 8],    // sizes of each argument\n//! ```\n\npub mod functions;\npub mod globals;\nmod ops;\nmod string_table;\n\nuse std::{\n    cell::RefCell,\n    convert::TryInto,\n    error::Error,\n    fmt,\n    io::{Read, Seek, SeekFrom},\n    rc::Rc,\n};\n\nuse crate::server::world::{EntityError, EntityTypeDef};\n\nuse byteorder::{LittleEndian, ReadBytesExt};\nuse num::FromPrimitive;\n\nuse self::{\n    functions::{BuiltinFunctionId, FunctionDef, FunctionKind, Statement, MAX_ARGS},\n    globals::{GLOBAL_ADDR_ARG_0, GLOBAL_STATIC_COUNT},\n};\npub use self::{\n    functions::{FunctionId, Functions},\n    globals::{\n        GlobalAddrEntity, GlobalAddrFloat, GlobalAddrFunction, GlobalAddrVector, Globals,\n        GlobalsError,\n    },\n    ops::Opcode,\n    string_table::StringTable,\n};\n\nconst VERSION: i32 = 6;\nconst CRC: i32 = 5927;\nconst MAX_CALL_STACK_DEPTH: usize = 32;\nconst MAX_LOCAL_STACK_DEPTH: usize = 2048;\nconst LUMP_COUNT: usize = 6;\nconst SAVE_GLOBAL: u16 = 1 << 15;\n\n// the on-disk size of a bytecode statement\nconst STATEMENT_SIZE: usize = 8;\n\n// the on-disk size of a function declaration\nconst FUNCTION_SIZE: usize = 36;\n\n// the on-disk size of a global or field definition\nconst DEF_SIZE: usize = 8;\n\n#[derive(Debug)]\npub enum ProgsError {\n    Io(::std::io::Error),\n    Globals(GlobalsError),\n    Entity(EntityError),\n    CallStackOverflow,\n    LocalStackOverflow,\n    Other(String),\n}\n\nimpl ProgsError {\n    pub fn with_msg<S>(msg: S) -> Self\n    where\n        S: AsRef<str>,\n    {\n        ProgsError::Other(msg.as_ref().to_owned())\n    }\n}\n\nimpl fmt::Display for ProgsError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        use self::ProgsError::*;\n        match *self {\n            Io(ref err) => {\n                write!(f, \"I/O error: \")?;\n                err.fmt(f)\n            }\n            Globals(ref err) => {\n                write!(f, \"Globals error: \")?;\n                err.fmt(f)\n            }\n            Entity(ref err) => {\n                write!(f, \"Entity error: \")?;\n                err.fmt(f)\n            }\n            CallStackOverflow => write!(f, \"Call stack overflow\"),\n            LocalStackOverflow => write!(f, \"Local stack overflow\"),\n            Other(ref msg) => write!(f, \"{}\", msg),\n        }\n    }\n}\n\nimpl Error for ProgsError {}\n\nimpl From<::std::io::Error> for ProgsError {\n    fn from(error: ::std::io::Error) -> Self {\n        ProgsError::Io(error)\n    }\n}\n\nimpl From<GlobalsError> for ProgsError {\n    fn from(error: GlobalsError) -> Self {\n        ProgsError::Globals(error)\n    }\n}\n\nimpl From<EntityError> for ProgsError {\n    fn from(error: EntityError) -> Self {\n        ProgsError::Entity(error)\n    }\n}\n\n#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]\n#[repr(C)]\npub struct StringId(pub usize);\n\nimpl TryInto<i32> for StringId {\n    type Error = ProgsError;\n\n    fn try_into(self) -> Result<i32, Self::Error> {\n        if self.0 > ::std::i32::MAX as usize {\n            Err(ProgsError::with_msg(\"string id out of i32 range\"))\n        } else {\n            Ok(self.0 as i32)\n        }\n    }\n}\n\nimpl StringId {\n    pub fn new() -> StringId {\n        StringId(0)\n    }\n}\n\n#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]\n#[repr(C)]\npub struct EntityId(pub usize);\n\n#[derive(Copy, Clone, Debug, Default, PartialEq)]\n#[repr(C)]\npub struct FieldAddr(pub usize);\n\n#[derive(Copy, Clone, Debug, Default, PartialEq)]\n#[repr(C)]\npub struct EntityFieldAddr {\n    pub entity_id: EntityId,\n    pub field_addr: FieldAddr,\n}\n\nenum LumpId {\n    Statements = 0,\n    GlobalDefs = 1,\n    Fielddefs = 2,\n    Functions = 3,\n    Strings = 4,\n    Globals = 5,\n}\n\n#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)]\n#[repr(u16)]\npub enum Type {\n    QVoid = 0,\n    QString = 1,\n    QFloat = 2,\n    QVector = 3,\n    QEntity = 4,\n    QField = 5,\n    QFunction = 6,\n    QPointer = 7,\n}\n\n#[derive(Copy, Clone, Debug)]\nstruct Lump {\n    offset: usize,\n    count: usize,\n}\n\n#[derive(Debug)]\npub struct GlobalDef {\n    save: bool,\n    type_: Type,\n    offset: u16,\n    name_id: StringId,\n}\n\n/// An entity field definition.\n///\n/// These definitions can be used to look up entity fields by name. This is\n/// required for custom fields defined in QuakeC code; their offsets are not\n/// known at compile time.\n#[derive(Debug)]\npub struct FieldDef {\n    pub type_: Type,\n    pub offset: u16,\n    pub name_id: StringId,\n}\n\n/// The values returned by loading a `progs.dat` file.\npub struct LoadProgs {\n    pub cx: ExecutionContext,\n    pub globals: Globals,\n    pub entity_def: Rc<EntityTypeDef>,\n    pub string_table: Rc<RefCell<StringTable>>,\n}\n\n/// Loads all data from a `progs.dat` file.\n///\n/// This returns objects representing the necessary context to execute QuakeC bytecode.\npub fn load<R>(mut src: R) -> Result<LoadProgs, ProgsError>\nwhere\n    R: Read + Seek,\n{\n    assert!(src.read_i32::<LittleEndian>()? == VERSION);\n    assert!(src.read_i32::<LittleEndian>()? == CRC);\n\n    let mut lumps = [Lump {\n        offset: 0,\n        count: 0,\n    }; LUMP_COUNT];\n\n    for l in 0..lumps.len() as usize {\n        lumps[l] = Lump {\n            offset: src.read_i32::<LittleEndian>()? as usize,\n            count: src.read_i32::<LittleEndian>()? as usize,\n        };\n\n        debug!(\"{:?}: {:?}\", l, lumps[l]);\n    }\n\n    let ent_addr_count = src.read_i32::<LittleEndian>()? as usize;\n    debug!(\"Field count: {}\", ent_addr_count);\n\n    // Read string data and construct StringTable\n\n    let string_lump = &lumps[LumpId::Strings as usize];\n    src.seek(SeekFrom::Start(string_lump.offset as u64))?;\n    let mut strings = Vec::new();\n    (&mut src)\n        .take(string_lump.count as u64)\n        .read_to_end(&mut strings)?;\n    let string_table = Rc::new(RefCell::new(StringTable::new(strings)));\n\n    assert_eq!(\n        src.seek(SeekFrom::Current(0))?,\n        src.seek(SeekFrom::Start(\n            (string_lump.offset + string_lump.count) as u64,\n        ))?\n    );\n\n    // Read function definitions and statements and construct Functions\n\n    let function_lump = &lumps[LumpId::Functions as usize];\n    src.seek(SeekFrom::Start(function_lump.offset as u64))?;\n    let mut function_defs = Vec::with_capacity(function_lump.count);\n    for i in 0..function_lump.count {\n        assert_eq!(\n            src.seek(SeekFrom::Current(0))?,\n            src.seek(SeekFrom::Start(\n                (function_lump.offset + i * FUNCTION_SIZE) as u64,\n            ))?\n        );\n\n        let kind = match src.read_i32::<LittleEndian>()? {\n            x if x < 0 => match BuiltinFunctionId::from_i32(-x) {\n                Some(f) => FunctionKind::BuiltIn(f),\n                None => {\n                    return Err(ProgsError::with_msg(format!(\n                        \"Invalid built-in function ID {}\",\n                        -x\n                    )))\n                }\n            },\n            x => FunctionKind::QuakeC(x as usize),\n        };\n\n        let arg_start = src.read_i32::<LittleEndian>()?;\n        let locals = src.read_i32::<LittleEndian>()?;\n\n        // throw away profile variable\n        let _ = src.read_i32::<LittleEndian>()?;\n\n        let name_id = string_table\n            .borrow()\n            .id_from_i32(src.read_i32::<LittleEndian>()?)?;\n        let srcfile_id = string_table\n            .borrow()\n            .id_from_i32(src.read_i32::<LittleEndian>()?)?;\n\n        let argc = src.read_i32::<LittleEndian>()?;\n        let mut argsz = [0; MAX_ARGS];\n        src.read(&mut argsz)?;\n\n        function_defs.push(FunctionDef {\n            kind,\n            arg_start: arg_start as usize,\n            locals: locals as usize,\n            name_id,\n            srcfile_id,\n            argc: argc as usize,\n            argsz,\n        });\n    }\n\n    assert_eq!(\n        src.seek(SeekFrom::Current(0))?,\n        src.seek(SeekFrom::Start(\n            (function_lump.offset + function_lump.count * FUNCTION_SIZE) as u64,\n        ))?\n    );\n\n    let statement_lump = &lumps[LumpId::Statements as usize];\n    src.seek(SeekFrom::Start(statement_lump.offset as u64))?;\n    let mut statements = Vec::with_capacity(statement_lump.count);\n    for _ in 0..statement_lump.count {\n        statements.push(Statement::new(\n            src.read_i16::<LittleEndian>()?,\n            src.read_i16::<LittleEndian>()?,\n            src.read_i16::<LittleEndian>()?,\n            src.read_i16::<LittleEndian>()?,\n        )?);\n    }\n\n    assert_eq!(\n        src.seek(SeekFrom::Current(0))?,\n        src.seek(SeekFrom::Start(\n            (statement_lump.offset + statement_lump.count * STATEMENT_SIZE) as u64,\n        ))?\n    );\n\n    let functions = Functions {\n        string_table: string_table.clone(),\n        defs: function_defs.into_boxed_slice(),\n        statements: statements.into_boxed_slice(),\n    };\n\n    let globaldef_lump = &lumps[LumpId::GlobalDefs as usize];\n    src.seek(SeekFrom::Start(globaldef_lump.offset as u64))?;\n    let mut globaldefs = Vec::new();\n    for _ in 0..globaldef_lump.count {\n        let type_ = src.read_u16::<LittleEndian>()?;\n        let offset = src.read_u16::<LittleEndian>()?;\n        let name_id = string_table\n            .borrow()\n            .id_from_i32(src.read_i32::<LittleEndian>()?)?;\n        globaldefs.push(GlobalDef {\n            save: type_ & SAVE_GLOBAL != 0,\n            type_: Type::from_u16(type_ & !SAVE_GLOBAL).unwrap(),\n            offset,\n            name_id,\n        });\n    }\n\n    assert_eq!(\n        src.seek(SeekFrom::Current(0))?,\n        src.seek(SeekFrom::Start(\n            (globaldef_lump.offset + globaldef_lump.count * DEF_SIZE) as u64,\n        ))?\n    );\n\n    let fielddef_lump = &lumps[LumpId::Fielddefs as usize];\n    src.seek(SeekFrom::Start(fielddef_lump.offset as u64))?;\n    let mut field_defs = Vec::new();\n    for _ in 0..fielddef_lump.count {\n        let type_ = src.read_u16::<LittleEndian>()?;\n        let offset = src.read_u16::<LittleEndian>()?;\n        let name_id = string_table\n            .borrow()\n            .id_from_i32(src.read_i32::<LittleEndian>()?)?;\n\n        if type_ & SAVE_GLOBAL != 0 {\n            return Err(ProgsError::with_msg(\n                \"Save flag not allowed in field definitions\",\n            ));\n        }\n        field_defs.push(FieldDef {\n            type_: Type::from_u16(type_).unwrap(),\n            offset,\n            name_id,\n        });\n    }\n\n    assert_eq!(\n        src.seek(SeekFrom::Current(0))?,\n        src.seek(SeekFrom::Start(\n            (fielddef_lump.offset + fielddef_lump.count * DEF_SIZE) as u64,\n        ))?\n    );\n\n    let globals_lump = &lumps[LumpId::Globals as usize];\n    src.seek(SeekFrom::Start(globals_lump.offset as u64))?;\n\n    if globals_lump.count < GLOBAL_STATIC_COUNT {\n        return Err(ProgsError::with_msg(\n            \"Global count lower than static global count\",\n        ));\n    }\n\n    let mut addrs = Vec::with_capacity(globals_lump.count);\n    for _ in 0..globals_lump.count {\n        let mut block = [0; 4];\n        src.read(&mut block)?;\n\n        // TODO: handle endian conversions (BigEndian systems should use BigEndian internally)\n        addrs.push(block);\n    }\n\n    assert_eq!(\n        src.seek(SeekFrom::Current(0))?,\n        src.seek(SeekFrom::Start(\n            (globals_lump.offset + globals_lump.count * 4) as u64,\n        ))?\n    );\n\n    let functions_rc = Rc::new(functions);\n\n    let cx = ExecutionContext::create(string_table.clone(), functions_rc.clone());\n\n    let globals = Globals::new(\n        string_table.clone(),\n        globaldefs.into_boxed_slice(),\n        addrs.into_boxed_slice(),\n    );\n\n    let entity_def = Rc::new(EntityTypeDef::new(\n        string_table.clone(),\n        ent_addr_count,\n        field_defs.into_boxed_slice(),\n    )?);\n\n    Ok(LoadProgs {\n        cx,\n        globals,\n        entity_def,\n        string_table,\n    })\n}\n\n#[derive(Debug)]\nstruct StackFrame {\n    instr_id: usize,\n    func_id: FunctionId,\n}\n\n/// A QuakeC VM context.\n#[derive(Debug)]\npub struct ExecutionContext {\n    string_table: Rc<RefCell<StringTable>>,\n    functions: Rc<Functions>,\n    pc: usize,\n    current_function: FunctionId,\n    call_stack: Vec<StackFrame>,\n    local_stack: Vec<[u8; 4]>,\n}\n\nimpl ExecutionContext {\n    pub fn create(\n        string_table: Rc<RefCell<StringTable>>,\n        functions: Rc<Functions>,\n    ) -> ExecutionContext {\n        ExecutionContext {\n            string_table,\n            functions,\n            pc: 0,\n            current_function: FunctionId(0),\n            call_stack: Vec::with_capacity(MAX_CALL_STACK_DEPTH),\n            local_stack: Vec::with_capacity(MAX_LOCAL_STACK_DEPTH),\n        }\n    }\n\n    pub fn call_stack_depth(&self) -> usize {\n        self.call_stack.len()\n    }\n\n    pub fn find_function_by_name<S: AsRef<str>>(\n        &mut self,\n        name: S,\n    ) -> Result<FunctionId, ProgsError> {\n        self.functions.find_function_by_name(name)\n    }\n\n    pub fn function_def(&self, id: FunctionId) -> Result<&FunctionDef, ProgsError> {\n        self.functions.get_def(id)\n    }\n\n    pub fn enter_function(\n        &mut self,\n        globals: &mut Globals,\n        f: FunctionId,\n    ) -> Result<(), ProgsError> {\n        let def = self.functions.get_def(f)?;\n        debug!(\n            \"Calling QuakeC function {}\",\n            self.string_table.borrow().get(def.name_id).unwrap()\n        );\n\n        // save stack frame\n        self.call_stack.push(StackFrame {\n            instr_id: self.pc,\n            func_id: self.current_function,\n        });\n\n        // check call stack overflow\n        if self.call_stack.len() >= MAX_CALL_STACK_DEPTH {\n            return Err(ProgsError::CallStackOverflow);\n        }\n\n        // preemptively check local stack overflow\n        if self.local_stack.len() + def.locals > MAX_LOCAL_STACK_DEPTH {\n            return Err(ProgsError::LocalStackOverflow);\n        }\n\n        // save locals to stack\n        for i in 0..def.locals {\n            self.local_stack\n                .push(globals.get_bytes((def.arg_start + i) as i16)?);\n        }\n\n        for arg in 0..def.argc {\n            for component in 0..def.argsz[arg] as usize {\n                let val = globals.get_bytes((GLOBAL_ADDR_ARG_0 + arg * 3 + component) as i16)?;\n                globals.put_bytes(val, def.arg_start as i16)?;\n            }\n        }\n\n        self.current_function = f;\n\n        match def.kind {\n            FunctionKind::BuiltIn(_) => {\n                panic!(\"built-in functions should not be called with enter_function()\")\n            }\n            FunctionKind::QuakeC(pc) => self.pc = pc,\n        }\n\n        Ok(())\n    }\n\n    pub fn leave_function(&mut self, globals: &mut Globals) -> Result<(), ProgsError> {\n        let def = self.functions.get_def(self.current_function)?;\n        debug!(\n            \"Returning from QuakeC function {}\",\n            self.string_table.borrow().get(def.name_id).unwrap()\n        );\n\n        for i in (0..def.locals).rev() {\n            globals.put_bytes(self.local_stack.pop().unwrap(), (def.arg_start + i) as i16)?;\n        }\n\n        let frame = match self.call_stack.pop() {\n            Some(f) => f,\n            None => return Err(ProgsError::with_msg(\"call stack underflow\")),\n        };\n\n        self.current_function = frame.func_id;\n        self.pc = frame.instr_id;\n\n        Ok(())\n    }\n\n    pub fn load_statement(&self) -> Statement {\n        self.functions.statements[self.pc].clone()\n    }\n\n    /// Performs an unconditional relative jump.\n    pub fn jump_relative(&mut self, rel: i16) {\n        self.pc = (self.pc as isize + rel as isize) as usize;\n    }\n}\n"
  },
  {
    "path": "src/server/progs/ops.rs",
    "content": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)]\n#[repr(i16)]\npub enum Opcode {\n    Done = 0,\n    MulF = 1,\n    MulV = 2,\n    MulFV = 3,\n    MulVF = 4,\n    Div = 5,\n    AddF = 6,\n    AddV = 7,\n    SubF = 8,\n    SubV = 9,\n    EqF = 10,\n    EqV = 11,\n    EqS = 12,\n    EqEnt = 13,\n    EqFnc = 14,\n    NeF = 15,\n    NeV = 16,\n    NeS = 17,\n    NeEnt = 18,\n    NeFnc = 19,\n    Le = 20,\n    Ge = 21,\n    Lt = 22,\n    Gt = 23,\n    LoadF = 24,\n    LoadV = 25,\n    LoadS = 26,\n    LoadEnt = 27,\n    LoadFld = 28,\n    LoadFnc = 29,\n    Address = 30,\n    StoreF = 31,\n    StoreV = 32,\n    StoreS = 33,\n    StoreEnt = 34,\n    StoreFld = 35,\n    StoreFnc = 36,\n    StorePF = 37,\n    StorePV = 38,\n    StorePS = 39,\n    StorePEnt = 40,\n    StorePFld = 41,\n    StorePFnc = 42,\n    Return = 43,\n    NotF = 44,\n    NotV = 45,\n    NotS = 46,\n    NotEnt = 47,\n    NotFnc = 48,\n    If = 49,\n    IfNot = 50,\n    Call0 = 51,\n    Call1 = 52,\n    Call2 = 53,\n    Call3 = 54,\n    Call4 = 55,\n    Call5 = 56,\n    Call6 = 57,\n    Call7 = 58,\n    Call8 = 59,\n    State = 60,\n    Goto = 61,\n    And = 62,\n    Or = 63,\n    BitAnd = 64,\n    BitOr = 65,\n}\n"
  },
  {
    "path": "src/server/progs/string_table.rs",
    "content": "use std::{cell::RefCell, collections::HashMap};\n\nuse crate::server::progs::{ProgsError, StringId};\n\n#[derive(Debug)]\npub struct StringTable {\n    /// Interned string data.\n    data: String,\n\n    /// Caches string lengths for faster lookup.\n    lengths: RefCell<HashMap<StringId, usize>>,\n}\n\nimpl StringTable {\n    pub fn new(data: Vec<u8>) -> StringTable {\n        StringTable {\n            data: String::from_utf8(data).unwrap(),\n            lengths: RefCell::new(HashMap::new()),\n        }\n    }\n\n    pub fn id_from_i32(&self, value: i32) -> Result<StringId, ProgsError> {\n        if value < 0 {\n            return Err(ProgsError::with_msg(\"id < 0\"));\n        }\n\n        let id = StringId(value as usize);\n\n        if id.0 < self.data.len() {\n            Ok(id)\n        } else {\n            Err(ProgsError::with_msg(format!(\"no string with ID {}\", value)))\n        }\n    }\n\n    pub fn find<S>(&self, target: S) -> Option<StringId>\n    where\n        S: AsRef<str>,\n    {\n        let target = target.as_ref();\n        for (ofs, _) in target.char_indices() {\n            let sub = &self.data[ofs..];\n            if !sub.starts_with(target) {\n                continue;\n            }\n\n            // Make sure the string is NUL-terminated. Otherwise, this could\n            // erroneously return the StringId of a String whose first\n            // `target.len()` bytes were equal to `target`, but which had\n            // additional bytes.\n            if sub.as_bytes().get(target.len()) != Some(&0) {\n                continue;\n            }\n\n            return Some(StringId(ofs));\n        }\n\n        None\n    }\n\n    pub fn get(&self, id: StringId) -> Option<&str> {\n        let start = id.0;\n\n        if start >= self.data.len() {\n            return None;\n        }\n\n        if let Some(len) = self.lengths.borrow().get(&id) {\n            let end = start + len;\n            return Some(&self.data[start..end]);\n        }\n\n        match (&self.data[start..])\n            .chars()\n            .take(1024 * 1024)\n            .enumerate()\n            .find(|&(_i, c)| c == '\\0')\n        {\n            Some((len, _)) => {\n                self.lengths.borrow_mut().insert(id, len);\n                let end = start + len;\n                Some(&self.data[start..end])\n            }\n            None => panic!(\"string data not NUL-terminated!\"),\n        }\n    }\n\n    pub fn insert<S>(&mut self, s: S) -> StringId\n    where\n        S: AsRef<str>,\n    {\n        let s = s.as_ref();\n\n        assert!(!s.contains('\\0'));\n\n        let id = StringId(self.data.len());\n        self.data.push_str(s);\n        self.lengths.borrow_mut().insert(id, s.len());\n        id\n    }\n\n    pub fn find_or_insert<S>(&mut self, target: S) -> StringId\n    where\n        S: AsRef<str>,\n    {\n        match self.find(target.as_ref()) {\n            Some(id) => id,\n            None => self.insert(target),\n        }\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = &str> {\n        self.data.split('\\0')\n    }\n}\n"
  },
  {
    "path": "src/server/world/entity.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nuse std::{cell::RefCell, convert::TryInto, error::Error, fmt, rc::Rc};\n\nuse crate::{\n    common::{engine::duration_to_f32, net::EntityState},\n    server::{\n        progs::{EntityId, FieldDef, FunctionId, ProgsError, StringId, StringTable, Type},\n        world::phys::MoveKind,\n    },\n};\n\nuse arrayvec::ArrayString;\nuse byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};\nuse cgmath::Vector3;\nuse chrono::Duration;\nuse num::FromPrimitive;\nuse uluru::LRUCache;\n\npub const MAX_ENT_LEAVES: usize = 16;\n\npub const STATIC_ADDRESS_COUNT: usize = 105;\n\n#[derive(Debug)]\npub enum EntityError {\n    Io(::std::io::Error),\n    Address(isize),\n    Other(String),\n}\n\nimpl EntityError {\n    pub fn with_msg<S>(msg: S) -> Self\n    where\n        S: AsRef<str>,\n    {\n        EntityError::Other(msg.as_ref().to_owned())\n    }\n}\n\nimpl fmt::Display for EntityError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match *self {\n            EntityError::Io(ref err) => {\n                write!(f, \"I/O error: \")?;\n                err.fmt(f)\n            }\n            EntityError::Address(val) => write!(f, \"Invalid address ({})\", val),\n            EntityError::Other(ref msg) => write!(f, \"{}\", msg),\n        }\n    }\n}\n\nimpl Error for EntityError {}\n\nimpl From<::std::io::Error> for EntityError {\n    fn from(error: ::std::io::Error) -> Self {\n        EntityError::Io(error)\n    }\n}\n\n/// A trait which covers addresses of typed values.\npub trait FieldAddr {\n    /// The type of value referenced by this address.\n    type Value;\n\n    /// Loads the value at this address.\n    fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError>;\n\n    /// Stores a value at this address.\n    fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), EntityError>;\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)]\npub enum FieldAddrFloat {\n    ModelIndex = 0,\n    AbsMinX = 1,\n    AbsMinY = 2,\n    AbsMinZ = 3,\n    AbsMaxX = 4,\n    AbsMaxY = 5,\n    AbsMaxZ = 6,\n    /// Used by mobile level geometry such as moving platforms.\n    LocalTime = 7,\n    /// Determines the movement behavior of an entity. The value must be a variant of `MoveKind`.\n    MoveKind = 8,\n    Solid = 9,\n    OriginX = 10,\n    OriginY = 11,\n    OriginZ = 12,\n    OldOriginX = 13,\n    OldOriginY = 14,\n    OldOriginZ = 15,\n    VelocityX = 16,\n    VelocityY = 17,\n    VelocityZ = 18,\n    AnglesX = 19,\n    AnglesY = 20,\n    AnglesZ = 21,\n    AngularVelocityX = 22,\n    AngularVelocityY = 23,\n    AngularVelocityZ = 24,\n    PunchAngleX = 25,\n    PunchAngleY = 26,\n    PunchAngleZ = 27,\n    /// The index of the entity's animation frame.\n    FrameId = 30,\n    /// The index of the entity's skin.\n    SkinId = 31,\n    /// Effects flags applied to the entity. See `EntityEffects`.\n    Effects = 32,\n    /// Minimum extent in local coordinates, X-coordinate.\n    MinsX = 33,\n    /// Minimum extent in local coordinates, Y-coordinate.\n    MinsY = 34,\n    /// Minimum extent in local coordinates, Z-coordinate.\n    MinsZ = 35,\n    /// Maximum extent in local coordinates, X-coordinate.\n    MaxsX = 36,\n    /// Maximum extent in local coordinates, Y-coordinate.\n    MaxsY = 37,\n    /// Maximum extent in local coordinates, Z-coordinate.\n    MaxsZ = 38,\n    SizeX = 39,\n    SizeY = 40,\n    SizeZ = 41,\n    /// The next server time at which the entity should run its think function.\n    NextThink = 46,\n    /// The entity's remaining health.\n    Health = 48,\n    /// The number of kills scored by the entity.\n    Frags = 49,\n    Weapon = 50,\n    WeaponFrame = 52,\n    /// The entity's remaining ammunition for its selected weapon.\n    CurrentAmmo = 53,\n    /// The entity's remaining shotgun shells.\n    AmmoShells = 54,\n    /// The entity's remaining shotgun shells.\n    AmmoNails = 55,\n    /// The entity's remaining rockets/grenades.\n    AmmoRockets = 56,\n    AmmoCells = 57,\n    Items = 58,\n    TakeDamage = 59,\n    DeadFlag = 61,\n    ViewOffsetX = 62,\n    ViewOffsetY = 63,\n    ViewOffsetZ = 64,\n    Button0 = 65,\n    Button1 = 66,\n    Button2 = 67,\n    Impulse = 68,\n    FixAngle = 69,\n    ViewAngleX = 70,\n    ViewAngleY = 71,\n    ViewAngleZ = 72,\n    IdealPitch = 73,\n    Flags = 76,\n    Colormap = 77,\n    Team = 78,\n    MaxHealth = 79,\n    TeleportTime = 80,\n    ArmorStrength = 81,\n    ArmorValue = 82,\n    WaterLevel = 83,\n    Contents = 84,\n    IdealYaw = 85,\n    YawSpeed = 86,\n    SpawnFlags = 89,\n    DmgTake = 92,\n    DmgSave = 93,\n    MoveDirectionX = 96,\n    MoveDirectionY = 97,\n    MoveDirectionZ = 98,\n    Sounds = 100,\n}\n\nimpl FieldAddr for FieldAddrFloat {\n    type Value = f32;\n\n    #[inline]\n    fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {\n        ent.get_float(*self as i16)\n    }\n\n    #[inline]\n    fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), EntityError> {\n        ent.put_float(value, *self as i16)\n    }\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)]\npub enum FieldAddrVector {\n    AbsMin = 1,\n    AbsMax = 4,\n    Origin = 10,\n    OldOrigin = 13,\n    Velocity = 16,\n    Angles = 19,\n    AngularVelocity = 22,\n    PunchAngle = 25,\n    Mins = 33,\n    Maxs = 36,\n    Size = 39,\n    ViewOffset = 62,\n    ViewAngle = 70,\n    MoveDirection = 96,\n}\n\nimpl FieldAddr for FieldAddrVector {\n    type Value = [f32; 3];\n\n    #[inline]\n    fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {\n        ent.get_vector(*self as i16)\n    }\n\n    #[inline]\n    fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), EntityError> {\n        ent.put_vector(value, *self as i16)\n    }\n}\n\n#[derive(Copy, Clone, Debug, FromPrimitive)]\npub enum FieldAddrStringId {\n    ClassName = 28,\n    ModelName = 29,\n    WeaponModelName = 51,\n    NetName = 74,\n    Target = 90,\n    TargetName = 91,\n    Message = 99,\n    Noise0Name = 101,\n    Noise1Name = 102,\n    Noise2Name = 103,\n    Noise3Name = 104,\n}\n\nimpl FieldAddr for FieldAddrStringId {\n    type Value = StringId;\n\n    fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {\n        ent.get_int(*self as i16)\n            .map(|val| StringId(val.try_into().unwrap()))\n    }\n\n    fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), EntityError> {\n        ent.put_int(value.0.try_into().unwrap(), *self as i16)\n    }\n}\n\n#[derive(Copy, Clone, Debug, FromPrimitive)]\npub enum FieldAddrEntityId {\n    /// The entity this entity is standing on.\n    Ground = 47,\n    Chain = 60,\n    Enemy = 75,\n    Aim = 87,\n    Goal = 88,\n    DmgInflictor = 94,\n    Owner = 95,\n}\n\nimpl FieldAddr for FieldAddrEntityId {\n    type Value = EntityId;\n\n    fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {\n        ent.entity_id(*self as i16)\n    }\n\n    fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), EntityError> {\n        ent.put_entity_id(value, *self as i16)\n    }\n}\n\n#[derive(Copy, Clone, Debug, FromPrimitive)]\npub enum FieldAddrFunctionId {\n    Touch = 42,\n    Use = 43,\n    Think = 44,\n    Blocked = 45,\n}\n\nimpl FieldAddr for FieldAddrFunctionId {\n    type Value = FunctionId;\n\n    #[inline]\n    fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {\n        ent.function_id(*self as i16)\n    }\n\n    #[inline]\n    fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), EntityError> {\n        ent.put_function_id(value, *self as i16)\n    }\n}\n\nbitflags! {\n    pub struct EntityFlags: u16 {\n        const FLY            = 0b0000000000001;\n        const SWIM           = 0b0000000000010;\n        const CONVEYOR       = 0b0000000000100;\n        const CLIENT         = 0b0000000001000;\n        const IN_WATER       = 0b0000000010000;\n        const MONSTER        = 0b0000000100000;\n        const GOD_MODE       = 0b0000001000000;\n        const NO_TARGET      = 0b0000010000000;\n        const ITEM           = 0b0000100000000;\n        const ON_GROUND      = 0b0001000000000;\n        const PARTIAL_GROUND = 0b0010000000000;\n        const WATER_JUMP     = 0b0100000000000;\n        const JUMP_RELEASED  = 0b1000000000000;\n    }\n}\n\n// TODO: if this never gets used, remove it\n#[allow(dead_code)]\nfn float_addr(addr: usize) -> Result<FieldAddrFloat, ProgsError> {\n    match FieldAddrFloat::from_usize(addr) {\n        Some(f) => Ok(f),\n        None => Err(ProgsError::with_msg(format!(\n            \"float_addr: invalid address ({})\",\n            addr\n        ))),\n    }\n}\n\n// TODO: if this never gets used, remove it\n#[allow(dead_code)]\nfn vector_addr(addr: usize) -> Result<FieldAddrVector, ProgsError> {\n    match FieldAddrVector::from_usize(addr) {\n        Some(v) => Ok(v),\n        None => Err(ProgsError::with_msg(format!(\n            \"vector_addr: invalid address ({})\",\n            addr\n        ))),\n    }\n}\n\n#[derive(Debug)]\nstruct FieldDefCacheEntry {\n    name: ArrayString<64>,\n    index: usize,\n}\n\n#[derive(Debug)]\npub struct EntityTypeDef {\n    string_table: Rc<RefCell<StringTable>>,\n    addr_count: usize,\n    field_defs: Box<[FieldDef]>,\n\n    name_cache: RefCell<LRUCache<FieldDefCacheEntry, 16>>,\n}\n\nimpl EntityTypeDef {\n    pub fn new(\n        string_table: Rc<RefCell<StringTable>>,\n        addr_count: usize,\n        field_defs: Box<[FieldDef]>,\n    ) -> Result<EntityTypeDef, EntityError> {\n        if addr_count < STATIC_ADDRESS_COUNT {\n            return Err(EntityError::with_msg(format!(\n                \"addr_count ({}) < STATIC_ADDRESS_COUNT ({})\",\n                addr_count, STATIC_ADDRESS_COUNT\n            )));\n        }\n\n        Ok(EntityTypeDef {\n            string_table,\n            addr_count,\n            field_defs,\n            name_cache: RefCell::new(LRUCache::default()),\n        })\n    }\n\n    pub fn addr_count(&self) -> usize {\n        self.addr_count\n    }\n\n    pub fn field_defs(&self) -> &[FieldDef] {\n        self.field_defs.as_ref()\n    }\n\n    /// Locate a field definition given its name.\n    pub fn find<S>(&self, name: S) -> Option<&FieldDef>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n\n        if let Some(cached) = self\n            .name_cache\n            .borrow_mut()\n            .find(|entry| &entry.name == name)\n        {\n            return Some(&self.field_defs[cached.index]);\n        }\n\n        let name_id = self.string_table.borrow().find(name)?;\n\n        let (index, def) = self\n            .field_defs\n            .iter()\n            .enumerate()\n            .find(|(_, def)| def.name_id == name_id)?;\n\n        self.name_cache.borrow_mut().insert(FieldDefCacheEntry {\n            name: ArrayString::from(name).unwrap(),\n            index,\n        });\n\n        Some(def)\n    }\n}\n\n#[derive(Debug, FromPrimitive, PartialEq)]\npub enum EntitySolid {\n    Not = 0,\n    Trigger = 1,\n    BBox = 2,\n    SlideBox = 3,\n    Bsp = 4,\n}\n\n#[derive(Debug)]\npub struct Entity {\n    string_table: Rc<RefCell<StringTable>>,\n    type_def: Rc<EntityTypeDef>,\n    addrs: Box<[[u8; 4]]>,\n\n    pub leaf_count: usize,\n    pub leaf_ids: [usize; MAX_ENT_LEAVES],\n    pub baseline: EntityState,\n}\n\nimpl Entity {\n    pub fn new(string_table: Rc<RefCell<StringTable>>, type_def: Rc<EntityTypeDef>) -> Entity {\n        let mut addrs = Vec::with_capacity(type_def.addr_count);\n        for _ in 0..type_def.addr_count {\n            addrs.push([0; 4]);\n        }\n\n        Entity {\n            string_table,\n            type_def,\n            addrs: addrs.into_boxed_slice(),\n            leaf_count: 0,\n            leaf_ids: [0; MAX_ENT_LEAVES],\n            baseline: EntityState::uninitialized(),\n        }\n    }\n\n    pub fn type_check(&self, addr: usize, type_: Type) -> Result<(), EntityError> {\n        match self\n            .type_def\n            .field_defs\n            .iter()\n            .find(|def| def.type_ != Type::QVoid && def.offset as usize == addr)\n        {\n            Some(d) => {\n                if type_ == d.type_ {\n                    return Ok(());\n                } else if type_ == Type::QFloat && d.type_ == Type::QVector {\n                    return Ok(());\n                } else if type_ == Type::QVector && d.type_ == Type::QFloat {\n                    return Ok(());\n                } else {\n                    return Err(EntityError::with_msg(format!(\n                        \"type check failed: addr={} expected={:?} actual={:?}\",\n                        addr, type_, d.type_\n                    )));\n                }\n            }\n            None => return Ok(()),\n        }\n    }\n\n    pub fn field_def<S>(&self, name: S) -> Option<&FieldDef>\n    where\n        S: AsRef<str>,\n    {\n        self.type_def.find(name)\n    }\n\n    /// Returns a reference to the memory at the given address.\n    pub fn get_addr(&self, addr: i16) -> Result<&[u8], EntityError> {\n        if addr < 0 {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        Ok(&self.addrs[addr])\n    }\n\n    /// Returns a mutable reference to the memory at the given address.\n    pub fn get_addr_mut(&mut self, addr: i16) -> Result<&mut [u8], EntityError> {\n        if addr < 0 {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        Ok(&mut self.addrs[addr])\n    }\n\n    /// Returns a copy of the memory at the given address.\n    pub fn get_bytes(&self, addr: i16) -> Result<[u8; 4], EntityError> {\n        if addr < 0 {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        Ok(self.addrs[addr])\n    }\n\n    /// Writes the provided data to the memory at the given address.\n    ///\n    /// This can be used to circumvent the type checker in cases where an operation is not dependent\n    /// of the type of the data.\n    pub fn put_bytes(&mut self, val: [u8; 4], addr: i16) -> Result<(), EntityError> {\n        if addr < 0 {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        let addr = addr as usize;\n\n        if addr > self.addrs.len() {\n            return Err(EntityError::Address(addr as isize));\n        }\n\n        self.addrs[addr] = val;\n        Ok(())\n    }\n\n    #[inline]\n    pub fn load<F>(&self, field: F) -> Result<F::Value, EntityError>\n    where\n        F: FieldAddr,\n    {\n        field.load(self)\n    }\n\n    #[inline]\n    pub fn store<F>(&mut self, field: F, value: F::Value) -> Result<(), EntityError>\n    where\n        F: FieldAddr,\n    {\n        field.store(self, value)\n    }\n\n    /// Loads an `i32` from the given virtual address.\n    pub fn get_int(&self, addr: i16) -> Result<i32, EntityError> {\n        Ok(self.get_addr(addr)?.read_i32::<LittleEndian>()?)\n    }\n\n    /// Loads an `i32` from the given virtual address.\n    pub fn put_int(&mut self, val: i32, addr: i16) -> Result<(), EntityError> {\n        self.get_addr_mut(addr)?.write_i32::<LittleEndian>(val)?;\n        Ok(())\n    }\n\n    /// Loads an `f32` from the given virtual address.\n    pub fn get_float(&self, addr: i16) -> Result<f32, EntityError> {\n        self.type_check(addr as usize, Type::QFloat)?;\n        Ok(self.get_addr(addr)?.read_f32::<LittleEndian>()?)\n    }\n\n    /// Stores an `f32` at the given virtual address.\n    pub fn put_float(&mut self, val: f32, addr: i16) -> Result<(), EntityError> {\n        self.type_check(addr as usize, Type::QFloat)?;\n        self.get_addr_mut(addr)?.write_f32::<LittleEndian>(val)?;\n        Ok(())\n    }\n\n    /// Loads an `[f32; 3]` from the given virtual address.\n    pub fn get_vector(&self, addr: i16) -> Result<[f32; 3], EntityError> {\n        self.type_check(addr as usize, Type::QVector)?;\n\n        let mut v = [0.0; 3];\n\n        for i in 0..3 {\n            v[i] = self.get_float(addr + i as i16)?;\n        }\n\n        Ok(v)\n    }\n\n    /// Stores an `[f32; 3]` at the given virtual address.\n    pub fn put_vector(&mut self, val: [f32; 3], addr: i16) -> Result<(), EntityError> {\n        self.type_check(addr as usize, Type::QVector)?;\n\n        for i in 0..3 {\n            self.put_float(val[i], addr + i as i16)?;\n        }\n\n        Ok(())\n    }\n\n    /// Loads a `StringId` from the given virtual address.\n    pub fn string_id(&self, addr: i16) -> Result<StringId, EntityError> {\n        self.type_check(addr as usize, Type::QString)?;\n\n        Ok(StringId(\n            self.get_addr(addr)?.read_i32::<LittleEndian>()? as usize\n        ))\n    }\n\n    /// Stores a `StringId` at the given virtual address.\n    pub fn put_string_id(&mut self, val: StringId, addr: i16) -> Result<(), EntityError> {\n        self.type_check(addr as usize, Type::QString)?;\n\n        self.get_addr_mut(addr)?\n            .write_i32::<LittleEndian>(val.try_into().unwrap())?;\n        Ok(())\n    }\n\n    /// Loads an `EntityId` from the given virtual address.\n    pub fn entity_id(&self, addr: i16) -> Result<EntityId, EntityError> {\n        self.type_check(addr as usize, Type::QEntity)?;\n\n        match self.get_addr(addr)?.read_i32::<LittleEndian>()? {\n            e if e < 0 => Err(EntityError::with_msg(format!(\"Negative entity ID ({})\", e))),\n            e => Ok(EntityId(e as usize)),\n        }\n    }\n\n    /// Stores an `EntityId` at the given virtual address.\n    pub fn put_entity_id(&mut self, val: EntityId, addr: i16) -> Result<(), EntityError> {\n        self.type_check(addr as usize, Type::QEntity)?;\n\n        self.get_addr_mut(addr)?\n            .write_i32::<LittleEndian>(val.0 as i32)?;\n        Ok(())\n    }\n\n    /// Loads a `FunctionId` from the given virtual address.\n    pub fn function_id(&self, addr: i16) -> Result<FunctionId, EntityError> {\n        self.type_check(addr as usize, Type::QFunction)?;\n        Ok(FunctionId(\n            self.get_addr(addr)?.read_i32::<LittleEndian>()? as usize\n        ))\n    }\n\n    /// Stores a `FunctionId` at the given virtual address.\n    pub fn put_function_id(&mut self, val: FunctionId, addr: i16) -> Result<(), EntityError> {\n        self.type_check(addr as usize, Type::QFunction)?;\n        self.get_addr_mut(addr)?\n            .write_i32::<LittleEndian>(val.try_into().unwrap())?;\n        Ok(())\n    }\n\n    /// Set this entity's minimum and maximum bounds and calculate its size.\n    pub fn set_min_max_size<V>(&mut self, min: V, max: V) -> Result<(), EntityError>\n    where\n        V: Into<Vector3<f32>>,\n    {\n        let min = min.into();\n        let max = max.into();\n        let size = max - min;\n\n        debug!(\"Setting entity min: {:?}\", min);\n        self.put_vector(min.into(), FieldAddrVector::Mins as i16)?;\n\n        debug!(\"Setting entity max: {:?}\", max);\n        self.put_vector(max.into(), FieldAddrVector::Maxs as i16)?;\n\n        debug!(\"Setting entity size: {:?}\", size);\n        self.put_vector(size.into(), FieldAddrVector::Size as i16)?;\n        Ok(())\n    }\n\n    pub fn model_index(&self) -> Result<usize, EntityError> {\n        let model_index = self.get_float(FieldAddrFloat::ModelIndex as i16)?;\n        if model_index < 0.0 || model_index > ::std::usize::MAX as f32 {\n            Err(EntityError::with_msg(format!(\n                \"Invalid value for entity.model_index ({})\",\n                model_index,\n            )))\n        } else {\n            Ok(model_index as usize)\n        }\n    }\n\n    pub fn abs_min(&self) -> Result<Vector3<f32>, EntityError> {\n        Ok(self.get_vector(FieldAddrVector::AbsMin as i16)?.into())\n    }\n\n    pub fn abs_max(&self) -> Result<Vector3<f32>, EntityError> {\n        Ok(self.get_vector(FieldAddrVector::AbsMax as i16)?.into())\n    }\n\n    pub fn solid(&self) -> Result<EntitySolid, EntityError> {\n        let solid_i = self.get_float(FieldAddrFloat::Solid as i16)? as i32;\n        match EntitySolid::from_i32(solid_i) {\n            Some(s) => Ok(s),\n            None => Err(EntityError::with_msg(format!(\n                \"Invalid value for entity.solid ({})\",\n                solid_i,\n            ))),\n        }\n    }\n\n    pub fn origin(&self) -> Result<Vector3<f32>, EntityError> {\n        Ok(self.get_vector(FieldAddrVector::Origin as i16)?.into())\n    }\n\n    pub fn min(&self) -> Result<Vector3<f32>, EntityError> {\n        Ok(self.get_vector(FieldAddrVector::Mins as i16)?.into())\n    }\n\n    pub fn max(&self) -> Result<Vector3<f32>, EntityError> {\n        Ok(self.get_vector(FieldAddrVector::Maxs as i16)?.into())\n    }\n\n    pub fn size(&self) -> Result<Vector3<f32>, EntityError> {\n        Ok(self.get_vector(FieldAddrVector::Size as i16)?.into())\n    }\n\n    pub fn velocity(&self) -> Result<Vector3<f32>, EntityError> {\n        Ok(self.get_vector(FieldAddrVector::Velocity as i16)?.into())\n    }\n\n    /// Applies gravity to the entity.\n    ///\n    /// The effect depends on the provided value of the `sv_gravity` cvar, the\n    /// amount of time being simulated, and the entity's own `gravity` field\n    /// value.\n    pub fn apply_gravity(\n        &mut self,\n        sv_gravity: f32,\n        frame_time: Duration,\n    ) -> Result<(), EntityError> {\n        let ent_gravity = match self.field_def(\"gravity\") {\n            Some(def) => self.get_float(def.offset as i16)?,\n            None => 1.0,\n        };\n\n        let mut vel = self.velocity()?;\n        vel.z -= ent_gravity * sv_gravity * duration_to_f32(frame_time);\n        self.store(FieldAddrVector::Velocity, vel.into())?;\n\n        Ok(())\n    }\n\n    /// Limits the entity's velocity by clamping each component (not the\n    /// magnitude!) to an absolute value of `sv_maxvelocity`.\n    pub fn limit_velocity(&mut self, sv_maxvelocity: f32) -> Result<(), EntityError> {\n        let mut vel = self.velocity()?;\n        for c in &mut vel[..] {\n            *c = c.clamp(-sv_maxvelocity, sv_maxvelocity);\n        }\n        self.put_vector(vel.into(), FieldAddrVector::Velocity as i16)?;\n\n        Ok(())\n    }\n\n    pub fn move_kind(&self) -> Result<MoveKind, EntityError> {\n        let move_kind_f = self.get_float(FieldAddrFloat::MoveKind as i16)?;\n        let move_kind_i = move_kind_f as i32;\n        match MoveKind::from_i32(move_kind_i) {\n            Some(m) => Ok(m),\n            None => Err(EntityError::with_msg(format!(\n                \"Invalid value for entity.move_kind ({})\",\n                move_kind_f,\n            ))),\n        }\n    }\n\n    pub fn flags(&self) -> Result<EntityFlags, EntityError> {\n        let flags_i = self.get_float(FieldAddrFloat::Flags as i16)? as u16;\n        match EntityFlags::from_bits(flags_i) {\n            Some(f) => Ok(f),\n            None => Err(EntityError::with_msg(format!(\n                \"Invalid internal flags value ({})\",\n                flags_i\n            ))),\n        }\n    }\n\n    pub fn add_flags(&mut self, flags: EntityFlags) -> Result<(), EntityError> {\n        let result = self.flags()? | flags;\n        self.put_float(result.bits() as f32, FieldAddrFloat::Flags as i16)?;\n        Ok(())\n    }\n\n    pub fn owner(&self) -> Result<EntityId, EntityError> {\n        Ok(self.entity_id(FieldAddrEntityId::Owner as i16)?)\n    }\n}\n"
  },
  {
    "path": "src/server/world/mod.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nmod entity;\npub mod phys;\n\nuse std::{\n    cell::RefCell,\n    collections::{HashMap, HashSet},\n    rc::Rc,\n};\n\nuse self::{\n    entity::Entity,\n    phys::{Collide, CollideKind},\n};\npub use self::{\n    entity::{\n        EntityError, EntityFlags, EntitySolid, EntityTypeDef, FieldAddrEntityId, FieldAddrFloat,\n        FieldAddrFunctionId, FieldAddrStringId, FieldAddrVector,\n    },\n    phys::{MoveKind, Trace, TraceEnd, TraceEndKind, TraceStart},\n};\n\nuse crate::{\n    common::{\n        bsp,\n        bsp::{BspCollisionHull, BspLeafContents},\n        mdl,\n        model::{Model, ModelKind},\n        parse, sprite,\n        vfs::Vfs,\n    },\n    server::progs::{\n        EntityFieldAddr, EntityId, FieldAddr, FieldDef, FunctionId, ProgsError, StringId,\n        StringTable, Type,\n    },\n};\n\nuse arrayvec::ArrayVec;\nuse cgmath::{InnerSpace, Vector3, Zero};\n\nconst AREA_DEPTH: usize = 4;\nconst NUM_AREA_NODES: usize = 2usize.pow(AREA_DEPTH as u32 + 1) - 1;\nconst MAX_ENTITIES: usize = 600;\n\n#[derive(Debug)]\nenum AreaNodeKind {\n    Branch(AreaBranch),\n    Leaf,\n}\n\n#[derive(Debug)]\nstruct AreaNode {\n    kind: AreaNodeKind,\n    triggers: HashSet<EntityId>,\n    solids: HashSet<EntityId>,\n}\n\n// The areas form a quadtree-like BSP tree which alternates splitting on the X\n// and Y axes.\n//\n//                               00                                X\n//               01                              02                Y\n//       03              04              05              06        X\n//   07      08      09      10      11      12      13      14    Y\n// 15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  Leaves\n//\n//  [21]      [19]      [17]      [15]\n//   ||        ||        ||        ||\n//   ||        ||        ||        ||\n//   12===05===11        08===03===07\n//   ||   ||   ||        ||   ||   ||\n//   ||   ||   ||        ||   ||   ||\n//  [22]  ||  [20]      [18]  ||  [16]\n//        ||                  ||\n//        02========00========01\n//        ||                  ||\n//  [29]  ||  [27]      [25]  ||  [23]\n//   ||   ||   ||        ||   ||   ||\n//   ||   ||   ||        ||   ||   ||\n//   14===06===13        10===04===09\n//   ||        ||        ||        ||\n//   ||        ||        ||        ||\n//  [30]      [28]      [26]      [24]\n//\n// The tree won't necessarily look like this, this just assumes a rectangular area with width\n// between 1-2x its length.\n\nimpl AreaNode {\n    /// Generate a breadth-first 2-D binary space partitioning tree with the given extents.\n    pub fn generate(mins: Vector3<f32>, maxs: Vector3<f32>) -> ArrayVec<AreaNode, NUM_AREA_NODES> {\n        let mut nodes: ArrayVec<AreaNode, NUM_AREA_NODES> = ArrayVec::new();\n\n        // we generate the skeleton of the tree iteratively -- the nodes are linked but have no\n        // geometric data.\n\n        // place internal nodes\n        for i in 0..AREA_DEPTH {\n            for _ in 0..2usize.pow(i as u32) {\n                let len = nodes.len();\n                nodes.push(AreaNode {\n                    kind: AreaNodeKind::Branch(AreaBranch {\n                        axis: AreaBranchAxis::X,\n                        dist: 0.0,\n                        front: 2 * len + 1,\n                        back: 2 * len + 2,\n                    }),\n                    triggers: HashSet::new(),\n                    solids: HashSet::new(),\n                });\n            }\n        }\n\n        // place leaves\n        for _ in 0..2usize.pow(AREA_DEPTH as u32) {\n            nodes.push(AreaNode {\n                kind: AreaNodeKind::Leaf,\n                triggers: HashSet::new(),\n                solids: HashSet::new(),\n            });\n        }\n\n        // recursively assign geometric data to the nodes\n        AreaNode::setup(&mut nodes, 0, mins, maxs);\n\n        nodes\n    }\n\n    fn setup(\n        nodes: &mut ArrayVec<AreaNode, NUM_AREA_NODES>,\n        index: usize,\n        mins: Vector3<f32>,\n        maxs: Vector3<f32>,\n    ) {\n        let size = maxs - mins;\n\n        let axis;\n        if size.x > size.y {\n            axis = AreaBranchAxis::X;\n        } else {\n            axis = AreaBranchAxis::Y;\n        }\n\n        let dist = 0.5 * (maxs[axis as usize] + mins[axis as usize]);\n\n        let mut front_mins = mins;\n        front_mins[axis as usize] = dist;\n\n        let mut back_maxs = maxs;\n        back_maxs[axis as usize] = dist;\n\n        let front;\n        let back;\n        match nodes[index].kind {\n            AreaNodeKind::Branch(ref mut b) => {\n                b.axis = axis;\n                b.dist = dist;\n                front = b.front;\n                back = b.back;\n            }\n            AreaNodeKind::Leaf => return,\n        }\n\n        AreaNode::setup(nodes, front, front_mins, maxs);\n        AreaNode::setup(nodes, back, mins, back_maxs);\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\nenum AreaBranchAxis {\n    X = 0,\n    Y = 1,\n}\n\n#[derive(Debug)]\nstruct AreaBranch {\n    axis: AreaBranchAxis,\n    dist: f32,\n    front: usize,\n    back: usize,\n}\n\n#[derive(Debug)]\nstruct AreaEntity {\n    entity: Entity,\n    area_id: Option<usize>,\n}\n\n#[derive(Debug)]\nenum AreaEntitySlot {\n    Vacant,\n    Occupied(AreaEntity),\n}\n\n/// A representation of the current state of the game world.\n#[derive(Debug)]\npub struct World {\n    string_table: Rc<RefCell<StringTable>>,\n    type_def: Rc<EntityTypeDef>,\n\n    area_nodes: ArrayVec<AreaNode, NUM_AREA_NODES>,\n    slots: Box<[AreaEntitySlot]>,\n    models: Vec<Model>,\n}\n\nimpl World {\n    pub fn create(\n        mut brush_models: Vec<Model>,\n        type_def: Rc<EntityTypeDef>,\n        string_table: Rc<RefCell<StringTable>>,\n    ) -> Result<World, ProgsError> {\n        // generate area tree for world model\n        let area_nodes = AreaNode::generate(brush_models[0].min(), brush_models[0].max());\n\n        let mut models = Vec::with_capacity(brush_models.len() + 1);\n\n        // put null model at index 0\n        models.push(Model::none());\n\n        // take ownership of all brush models\n        models.append(&mut brush_models);\n\n        // generate world entity\n        let mut world_entity = Entity::new(string_table.clone(), type_def.clone());\n        world_entity.put_string_id(\n            string_table.borrow_mut().find_or_insert(models[1].name()),\n            FieldAddrStringId::ModelName as i16,\n        )?;\n        world_entity.put_float(1.0, FieldAddrFloat::ModelIndex as i16)?;\n        world_entity.put_float(EntitySolid::Bsp as u32 as f32, FieldAddrFloat::Solid as i16)?;\n        world_entity.put_float(\n            MoveKind::Push as u32 as f32,\n            FieldAddrFloat::MoveKind as i16,\n        )?;\n\n        let mut slots = Vec::with_capacity(MAX_ENTITIES);\n        slots.push(AreaEntitySlot::Occupied(AreaEntity {\n            entity: world_entity,\n            area_id: None,\n        }));\n        for _ in 0..MAX_ENTITIES - 1 {\n            slots.push(AreaEntitySlot::Vacant);\n        }\n\n        Ok(World {\n            string_table,\n            area_nodes,\n            type_def,\n            slots: slots.into_boxed_slice(),\n            models,\n        })\n    }\n\n    pub fn add_model(&mut self, vfs: &Vfs, name_id: StringId) -> Result<(), ProgsError> {\n        let strs = self.string_table.borrow();\n        let name = strs.get(name_id).unwrap();\n\n        if name.ends_with(\".bsp\") {\n            let data = vfs.open(name).unwrap();\n            let (mut brush_models, _) = bsp::load(data).unwrap();\n            if brush_models.len() > 1 {\n                return Err(ProgsError::with_msg(\n                    \"Complex brush models must be loaded before world creation\",\n                ));\n            }\n            self.models.append(&mut brush_models);\n        } else if name.ends_with(\".mdl\") {\n            let data = vfs.open(&name).unwrap();\n            let alias_model = mdl::load(data).unwrap();\n            self.models\n                .push(Model::from_alias_model(&name, alias_model));\n        } else if name.ends_with(\".spr\") {\n            let data = vfs.open(&name).unwrap();\n            let sprite_model = sprite::load(data);\n            self.models\n                .push(Model::from_sprite_model(&name, sprite_model));\n        } else {\n            return Err(ProgsError::with_msg(format!(\n                \"Unrecognized model type: {}\",\n                name\n            )));\n        }\n\n        Ok(())\n    }\n\n    fn find_def<S>(&self, name: S) -> Result<&FieldDef, ProgsError>\n    where\n        S: AsRef<str>,\n    {\n        let name = name.as_ref();\n\n        match self\n            .type_def\n            .field_defs()\n            .iter()\n            .find(|def| self.string_table.borrow().get(def.name_id).unwrap() == name)\n        {\n            Some(d) => Ok(d),\n            None => Err(ProgsError::with_msg(format!(\"no field with name {}\", name))),\n        }\n    }\n\n    /// Convert an entity ID and field address to an internal representation used by the VM.\n    ///\n    /// This representation should be compatible with the one used by the original Quake.\n    pub fn ent_fld_addr_to_i32(&self, ent_fld_addr: EntityFieldAddr) -> i32 {\n        let total_addr =\n            (ent_fld_addr.entity_id.0 * self.type_def.addr_count() + ent_fld_addr.field_addr.0) * 4;\n\n        if total_addr > ::std::i32::MAX as usize {\n            panic!(\"ent_fld_addr_to_i32: total_addr overflow\");\n        }\n\n        total_addr as i32\n    }\n\n    /// Convert the internal representation of a field offset back to struct form.\n    pub fn ent_fld_addr_from_i32(&self, val: i32) -> EntityFieldAddr {\n        if val < 0 {\n            panic!(\"ent_fld_addr_from_i32: negative value ({})\", val);\n        }\n\n        if val % 4 != 0 {\n            panic!(\"ent_fld_addr_from_i32: value % 4 != 0 ({})\", val);\n        }\n\n        let total_addr = val as usize / 4;\n        EntityFieldAddr {\n            entity_id: EntityId(total_addr / self.type_def.addr_count()),\n            field_addr: FieldAddr(total_addr % self.type_def.addr_count()),\n        }\n    }\n\n    fn find_vacant_slot(&self) -> Result<usize, ()> {\n        for (i, slot) in self.slots.iter().enumerate() {\n            if let &AreaEntitySlot::Vacant = slot {\n                return Ok(i);\n            }\n        }\n\n        panic!(\"no vacant slots\");\n    }\n\n    pub fn alloc_uninitialized(&mut self) -> Result<EntityId, ProgsError> {\n        let slot_id = self.find_vacant_slot().unwrap();\n\n        self.slots[slot_id] = AreaEntitySlot::Occupied(AreaEntity {\n            entity: Entity::new(self.string_table.clone(), self.type_def.clone()),\n            area_id: None,\n        });\n\n        Ok(EntityId(slot_id))\n    }\n\n    /// Allocate a new entity and initialize it with the data in the given map.\n    ///\n    /// For each entry in `map`, this will locate a field definition for the entry key, parse the\n    /// entry value to the correct type, and store it at that field. It will then locate the spawn\n    /// method for the entity's `classname` and execute it.\n    ///\n    /// ## Special cases\n    ///\n    /// There are two cases where the keys do not directly correspond to entity fields:\n    ///\n    /// - `angle`: This allows QuakeEd to write a single value instead of a set of Euler angles.\n    ///   The value should be interpreted as the second component of the `angles` field.\n    /// - `light`: This is simply an alias for `light_lev`.\n    pub fn alloc_from_map(&mut self, map: HashMap<&str, &str>) -> Result<EntityId, ProgsError> {\n        let mut ent = Entity::new(self.string_table.clone(), self.type_def.clone());\n\n        for (key, val) in map.iter() {\n            debug!(\".{} = {}\", key, val);\n            match *key {\n                // ignore keys starting with an underscore\n                k if k.starts_with(\"_\") => (),\n\n                \"angle\" => {\n                    // this is referred to in the original source as \"anglehack\" -- essentially,\n                    // only the yaw (Y) value is given. see\n                    // https://github.com/id-Software/Quake/blob/master/WinQuake/pr_edict.c#L826-L834\n                    let def = self.find_def(\"angles\")?.clone();\n                    ent.put_vector([0.0, val.parse().unwrap(), 0.0], def.offset as i16)?;\n                }\n\n                \"light\" => {\n                    // more fun hacks brought to you by Carmack & Friends\n                    let def = self.find_def(\"light_lev\")?.clone();\n                    ent.put_float(val.parse().unwrap(), def.offset as i16)?;\n                }\n\n                k => {\n                    let def = self.find_def(k)?.clone();\n\n                    match def.type_ {\n                        // void has no value, skip it\n                        Type::QVoid => (),\n\n                        // TODO: figure out if this ever happens\n                        Type::QPointer => unimplemented!(),\n\n                        Type::QString => {\n                            let s_id = self.string_table.borrow_mut().insert(val);\n                            ent.put_string_id(s_id, def.offset as i16)?;\n                        }\n\n                        Type::QFloat => ent.put_float(val.parse().unwrap(), def.offset as i16)?,\n                        Type::QVector => ent.put_vector(\n                            parse::vector3_components(val).unwrap(),\n                            def.offset as i16,\n                        )?,\n                        Type::QEntity => {\n                            let id: usize = val.parse().unwrap();\n\n                            if id > MAX_ENTITIES {\n                                panic!(\"out-of-bounds entity access\");\n                            }\n\n                            match self.slots[id] {\n                                AreaEntitySlot::Vacant => panic!(\"no entity with id {}\", id),\n                                AreaEntitySlot::Occupied(_) => (),\n                            }\n\n                            ent.put_entity_id(EntityId(id), def.offset as i16)?\n                        }\n                        Type::QField => panic!(\"attempted to store field of type Field in entity\"),\n                        Type::QFunction => {\n                            // TODO: need to validate this against function table\n                        }\n                    }\n                }\n            }\n        }\n\n        let entry_id = self.find_vacant_slot().unwrap();\n\n        self.slots[entry_id] = AreaEntitySlot::Occupied(AreaEntity {\n            entity: ent,\n            area_id: None,\n        });\n\n        Ok(EntityId(entry_id))\n    }\n\n    pub fn free(&mut self, entity_id: EntityId) -> Result<(), ProgsError> {\n        // TODO: unlink entity from world\n\n        if entity_id.0 as usize > self.slots.len() {\n            return Err(ProgsError::with_msg(format!(\n                \"Invalid entity ID ({:?})\",\n                entity_id\n            )));\n        }\n\n        if let AreaEntitySlot::Vacant = self.slots[entity_id.0 as usize] {\n            return Ok(());\n        }\n\n        self.slots[entity_id.0 as usize] = AreaEntitySlot::Vacant;\n        Ok(())\n    }\n\n    /// Returns a reference to an entity.\n    ///\n    /// # Panics\n    ///\n    /// This method panics if `entity_id` does not refer to a valid slot or if\n    /// the slot is vacant.\n    #[inline]\n    pub fn entity(&self, entity_id: EntityId) -> &Entity {\n        match self.slots[entity_id.0 as usize] {\n            AreaEntitySlot::Vacant => panic!(\"no such entity: {:?}\", entity_id),\n            AreaEntitySlot::Occupied(ref e) => &e.entity,\n        }\n    }\n\n    pub fn try_entity(&self, entity_id: EntityId) -> Result<&Entity, ProgsError> {\n        if entity_id.0 as usize > self.slots.len() {\n            return Err(ProgsError::with_msg(format!(\n                \"Invalid entity ID ({})\",\n                entity_id.0 as usize\n            )));\n        }\n\n        match self.slots[entity_id.0 as usize] {\n            AreaEntitySlot::Vacant => Err(ProgsError::with_msg(format!(\n                \"No entity at list entry {}\",\n                entity_id.0 as usize\n            ))),\n            AreaEntitySlot::Occupied(ref e) => Ok(&e.entity),\n        }\n    }\n\n    pub fn entity_mut(&mut self, entity_id: EntityId) -> Result<&mut Entity, ProgsError> {\n        if entity_id.0 as usize > self.slots.len() {\n            return Err(ProgsError::with_msg(format!(\n                \"Invalid entity ID ({})\",\n                entity_id.0 as usize\n            )));\n        }\n\n        match self.slots[entity_id.0 as usize] {\n            AreaEntitySlot::Vacant => Err(ProgsError::with_msg(format!(\n                \"No entity at list entry {}\",\n                entity_id.0 as usize\n            ))),\n            AreaEntitySlot::Occupied(ref mut e) => Ok(&mut e.entity),\n        }\n    }\n\n    pub fn entity_exists(&mut self, entity_id: EntityId) -> bool {\n        matches!(\n            self.slots[entity_id.0 as usize],\n            AreaEntitySlot::Occupied(_)\n        )\n    }\n\n    pub fn list_entities(&self, list: &mut Vec<EntityId>) {\n        for (id, slot) in self.slots.iter().enumerate() {\n            if let &AreaEntitySlot::Occupied(_) = slot {\n                list.push(EntityId(id));\n            }\n        }\n    }\n\n    fn area_entity(&self, entity_id: EntityId) -> Result<&AreaEntity, ProgsError> {\n        if entity_id.0 as usize > self.slots.len() {\n            return Err(ProgsError::with_msg(format!(\n                \"Invalid entity ID ({})\",\n                entity_id.0 as usize\n            )));\n        }\n\n        match self.slots[entity_id.0 as usize] {\n            AreaEntitySlot::Vacant => Err(ProgsError::with_msg(format!(\n                \"No entity at list entry {}\",\n                entity_id.0 as usize\n            ))),\n            AreaEntitySlot::Occupied(ref e) => Ok(e),\n        }\n    }\n\n    fn area_entity_mut(&mut self, entity_id: EntityId) -> Result<&mut AreaEntity, ProgsError> {\n        if entity_id.0 as usize > self.slots.len() {\n            return Err(ProgsError::with_msg(format!(\n                \"Invalid entity ID ({})\",\n                entity_id.0 as usize\n            )));\n        }\n\n        match self.slots[entity_id.0 as usize] {\n            AreaEntitySlot::Vacant => Err(ProgsError::with_msg(format!(\n                \"No entity at list entry {}\",\n                entity_id.0 as usize\n            ))),\n            AreaEntitySlot::Occupied(ref mut e) => Ok(e),\n        }\n    }\n\n    /// Lists the triggers touched by an entity.\n    ///\n    /// The triggers' IDs are stored in `touched`.\n    pub fn list_touched_triggers(\n        &mut self,\n        touched: &mut Vec<EntityId>,\n        ent_id: EntityId,\n        area_id: usize,\n    ) -> Result<(), ProgsError> {\n        'next_trigger: for trigger_id in self.area_nodes[area_id].triggers.iter().copied() {\n            if trigger_id == ent_id {\n                // Don't trigger self.\n                continue;\n            }\n\n            let ent = self.entity(ent_id);\n            let trigger = self.entity(trigger_id);\n\n            let trigger_touch = trigger.load(FieldAddrFunctionId::Touch)?;\n            if trigger_touch == FunctionId(0) || trigger.solid()? == EntitySolid::Not {\n                continue;\n            }\n\n            for i in 0..3 {\n                if ent.abs_min()?[i] > trigger.abs_max()?[i]\n                    || ent.abs_max()?[i] < trigger.abs_min()?[i]\n                {\n                    // Entities are not touching.\n                    continue 'next_trigger;\n                }\n            }\n\n            touched.push(trigger_id);\n        }\n\n        // Touch all triggers in sub-areas.\n        if let AreaNodeKind::Branch(AreaBranch { front, back, .. }) = self.area_nodes[area_id].kind\n        {\n            self.list_touched_triggers(touched, ent_id, front)?;\n            self.list_touched_triggers(touched, ent_id, back)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn unlink_entity(&mut self, e_id: EntityId) -> Result<(), ProgsError> {\n        // if this entity has been removed or freed, do nothing\n        if let AreaEntitySlot::Vacant = self.slots[e_id.0 as usize] {\n            return Ok(());\n        }\n\n        let area_id = match self.area_entity(e_id)?.area_id {\n            Some(i) => i,\n\n            // entity not linked\n            None => return Ok(()),\n        };\n\n        if self.area_nodes[area_id].triggers.remove(&e_id) {\n            debug!(\"Unlinking entity {} from area triggers\", e_id.0);\n        } else if self.area_nodes[area_id].solids.remove(&e_id) {\n            debug!(\"Unlinking entity {} from area solids\", e_id.0);\n        }\n\n        self.area_entity_mut(e_id)?.area_id = None;\n\n        Ok(())\n    }\n\n    pub fn link_entity(&mut self, e_id: EntityId) -> Result<(), ProgsError> {\n        // don't link the world entity\n        if e_id.0 == 0 {\n            return Ok(());\n        }\n\n        // if this entity has been removed or freed, do nothing\n        if let AreaEntitySlot::Vacant = self.slots[e_id.0 as usize] {\n            return Ok(());\n        }\n\n        self.unlink_entity(e_id)?;\n\n        let mut abs_min;\n        let mut abs_max;\n        let solid;\n        {\n            let ent = self.entity_mut(e_id)?;\n\n            let origin = Vector3::from(ent.get_vector(FieldAddrVector::Origin as i16)?);\n            let mins = Vector3::from(ent.get_vector(FieldAddrVector::Mins as i16)?);\n            let maxs = Vector3::from(ent.get_vector(FieldAddrVector::Maxs as i16)?);\n            debug!(\"origin = {:?} mins = {:?} maxs = {:?}\", origin, mins, maxs);\n            abs_min = origin + mins;\n            abs_max = origin + maxs;\n\n            let flags_f = ent.get_float(FieldAddrFloat::Flags as i16)?;\n            let flags = EntityFlags::from_bits(flags_f as u16).unwrap();\n            if flags.contains(EntityFlags::ITEM) {\n                abs_min.x -= 15.0;\n                abs_min.y -= 15.0;\n                abs_max.x += 15.0;\n                abs_max.y += 15.0;\n            } else {\n                abs_min.x -= 1.0;\n                abs_min.y -= 1.0;\n                abs_min.z -= 1.0;\n                abs_max.x += 1.0;\n                abs_max.y += 1.0;\n                abs_max.z += 1.0;\n            }\n\n            ent.put_vector(abs_min.into(), FieldAddrVector::AbsMin as i16)?;\n            ent.put_vector(abs_max.into(), FieldAddrVector::AbsMax as i16)?;\n\n            // Mark leaves containing entity for PVS.\n            ent.leaf_count = 0;\n            let model_index = ent.get_float(FieldAddrFloat::ModelIndex as i16)?;\n            if model_index != 0.0 {\n                // TODO: SV_FindTouchedLeafs\n                todo!(\"SV_FindTouchedLeafs\");\n            }\n\n            solid = ent.solid()?;\n\n            if solid == EntitySolid::Not {\n                // this entity has no touch interaction, we're done\n                return Ok(());\n            }\n        }\n\n        let mut node_id = 0;\n        loop {\n            match self.area_nodes[node_id].kind {\n                AreaNodeKind::Branch(ref b) => {\n                    debug!(\n                        \"abs_min = {:?} | abs_max = {:?} | dist = {}\",\n                        abs_min, abs_max, b.dist\n                    );\n                    if abs_min[b.axis as usize] > b.dist {\n                        node_id = b.front;\n                    } else if abs_max[b.axis as usize] < b.dist {\n                        node_id = b.back;\n                    } else {\n                        // entity spans both sides of the plane\n                        break;\n                    }\n                }\n\n                AreaNodeKind::Leaf => break,\n            }\n        }\n\n        if solid == EntitySolid::Trigger {\n            debug!(\"Linking entity {} into area {} triggers\", e_id.0, node_id);\n            self.area_nodes[node_id].triggers.insert(e_id);\n            self.area_entity_mut(e_id)?.area_id = Some(node_id);\n        } else {\n            debug!(\"Linking entity {} into area {} solids\", e_id.0, node_id);\n            self.area_nodes[node_id].solids.insert(e_id);\n            self.area_entity_mut(e_id)?.area_id = Some(node_id);\n        }\n\n        Ok(())\n    }\n\n    pub fn set_entity_model(&mut self, e_id: EntityId, model_id: usize) -> Result<(), ProgsError> {\n        if model_id == 0 {\n            self.set_entity_size(e_id, Vector3::zero(), Vector3::zero())?;\n        } else {\n            let min = self.models[model_id].min();\n            let max = self.models[model_id].max();\n            self.set_entity_size(e_id, min, max)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn set_entity_size(\n        &mut self,\n        e_id: EntityId,\n        min: Vector3<f32>,\n        max: Vector3<f32>,\n    ) -> Result<(), ProgsError> {\n        let ent = self.entity_mut(e_id)?;\n        ent.set_min_max_size(min, max)?;\n        Ok(())\n    }\n\n    /// Unlink an entity from the world and remove it.\n    pub fn remove_entity(&mut self, e_id: EntityId) -> Result<(), ProgsError> {\n        self.unlink_entity(e_id)?;\n        self.free(e_id)?;\n        Ok(())\n    }\n\n    // TODO: handle the offset return value internally\n    pub fn hull_for_entity(\n        &self,\n        e_id: EntityId,\n        min: Vector3<f32>,\n        max: Vector3<f32>,\n    ) -> Result<(BspCollisionHull, Vector3<f32>), ProgsError> {\n        let solid = self.entity(e_id).solid()?;\n        debug!(\"Entity solid type: {:?}\", solid);\n\n        match solid {\n            EntitySolid::Bsp => {\n                if self.entity(e_id).move_kind()? != MoveKind::Push {\n                    return Err(ProgsError::with_msg(format!(\n                        \"Brush entities must have MoveKind::Push (has {:?})\",\n                        self.entity(e_id).move_kind()\n                    )));\n                }\n\n                let size = max - min;\n                match self.models[self.entity(e_id).model_index()?].kind() {\n                    &ModelKind::Brush(ref bmodel) => {\n                        let hull_index;\n\n                        // TODO: replace these magic constants\n                        if size[0] < 3.0 {\n                            debug!(\"Using hull 0\");\n                            hull_index = 0;\n                        } else if size[0] <= 32.0 {\n                            debug!(\"Using hull 1\");\n                            hull_index = 1;\n                        } else {\n                            debug!(\"Using hull 2\");\n                            hull_index = 2;\n                        }\n\n                        let hull = bmodel.hull(hull_index).unwrap();\n\n                        let offset = hull.min() - min + self.entity(e_id).origin()?;\n\n                        Ok((hull, offset))\n                    }\n                    _ => Err(ProgsError::with_msg(format!(\n                        \"Non-brush entities may not have MoveKind::Push\"\n                    ))),\n                }\n            }\n\n            _ => {\n                let hull = BspCollisionHull::for_bounds(\n                    self.entity(e_id).min()?,\n                    self.entity(e_id).max()?,\n                )\n                .unwrap();\n                let offset = self.entity(e_id).origin()?;\n\n                Ok((hull, offset))\n            }\n        }\n    }\n\n    // TODO: come up with a better name. This doesn't actually move the entity!\n    pub fn move_entity(\n        &mut self,\n        e_id: EntityId,\n        start: Vector3<f32>,\n        min: Vector3<f32>,\n        max: Vector3<f32>,\n        end: Vector3<f32>,\n        kind: CollideKind,\n    ) -> Result<(Trace, Option<EntityId>), ProgsError> {\n        debug!(\n            \"start={:?} min={:?} max={:?} end={:?}\",\n            start, min, max, end\n        );\n\n        debug!(\"Collision test: Entity {} with world entity\", e_id.0);\n        let world_trace = self.collide_move_with_entity(EntityId(0), start, min, max, end)?;\n\n        debug!(\n            \"End position after collision test with world hull: {:?}\",\n            world_trace.end_point()\n        );\n\n        // if this is a rocket or a grenade, expand the monster collision box\n        let (monster_min, monster_max) = match kind {\n            CollideKind::Missile => (\n                min - Vector3::new(15.0, 15.0, 15.0),\n                max + Vector3::new(15.0, 15.0, 15.0),\n            ),\n            _ => (min, max),\n        };\n\n        let (move_min, move_max) =\n            self::phys::bounds_for_move(start, monster_min, monster_max, end);\n\n        let collide = Collide {\n            e_id: Some(e_id),\n            move_min,\n            move_max,\n            min,\n            max,\n            monster_min,\n            monster_max,\n            start,\n            end,\n            kind,\n        };\n\n        let (collide_trace, collide_ent) = self.collide(&collide)?;\n\n        if collide_trace.all_solid()\n            || collide_trace.start_solid()\n            || collide_trace.ratio() < world_trace.ratio()\n        {\n            Ok((collide_trace, collide_ent))\n        } else {\n            Ok((world_trace, Some(EntityId(0))))\n        }\n    }\n\n    pub fn collide(&self, collide: &Collide) -> Result<(Trace, Option<EntityId>), ProgsError> {\n        self.collide_area(0, collide)\n    }\n\n    fn collide_area(\n        &self,\n        area_id: usize,\n        collide: &Collide,\n    ) -> Result<(Trace, Option<EntityId>), ProgsError> {\n        let mut trace = Trace::new(\n            TraceStart::new(Vector3::zero(), 0.0),\n            TraceEnd::terminal(Vector3::zero()),\n            BspLeafContents::Empty,\n        );\n\n        let mut collide_entity = None;\n\n        let area = &self.area_nodes[area_id];\n\n        for touch in area.solids.iter() {\n            // don't collide an entity with itself\n            if let Some(e) = collide.e_id {\n                if e == *touch {\n                    continue;\n                }\n            }\n\n            match self.entity(*touch).solid()? {\n                // if the other entity has no collision, skip it\n                EntitySolid::Not => continue,\n\n                // triggers should not appear in the solids list\n                EntitySolid::Trigger => {\n                    return Err(ProgsError::with_msg(format!(\n                        \"Trigger in solids list with ID ({})\",\n                        touch.0\n                    )))\n                }\n\n                // don't collide with monsters if the collide specifies not to do so\n                s => {\n                    if s != EntitySolid::Bsp && collide.kind == CollideKind::NoMonsters {\n                        continue;\n                    }\n                }\n            }\n\n            // if bounding boxes never intersect, skip this entity\n            for i in 0..3 {\n                if collide.move_min[i] > self.entity(*touch).abs_max()?[i]\n                    || collide.move_max[i] < self.entity(*touch).abs_min()?[i]\n                {\n                    continue;\n                }\n            }\n\n            if let Some(e) = collide.e_id {\n                if self.entity(e).size()?[0] != 0.0 && self.entity(*touch).size()?[0] == 0.0 {\n                    continue;\n                }\n            }\n\n            if trace.all_solid() {\n                return Ok((trace, collide_entity));\n            }\n\n            if let Some(e) = collide.e_id {\n                // don't collide against owner or owned entities\n                if self.entity(*touch).owner()? == e || self.entity(e).owner()? == *touch {\n                    continue;\n                }\n            }\n\n            // select bounding boxes based on whether or not candidate is a monster\n            let tmp_trace;\n            if self.entity(*touch).flags()?.contains(EntityFlags::MONSTER) {\n                tmp_trace = self.collide_move_with_entity(\n                    *touch,\n                    collide.start,\n                    collide.monster_min,\n                    collide.monster_max,\n                    collide.end,\n                )?;\n            } else {\n                tmp_trace = self.collide_move_with_entity(\n                    *touch,\n                    collide.start,\n                    collide.min,\n                    collide.max,\n                    collide.end,\n                )?;\n            }\n\n            let old_dist = (trace.end_point() - collide.start).magnitude();\n            let new_dist = (tmp_trace.end_point() - collide.start).magnitude();\n\n            // check to see if this candidate is the closest yet and update trace if so\n            if tmp_trace.all_solid() || tmp_trace.start_solid() || new_dist < old_dist {\n                collide_entity = Some(*touch);\n                trace = tmp_trace;\n            }\n        }\n\n        match area.kind {\n            AreaNodeKind::Leaf => (),\n\n            AreaNodeKind::Branch(ref b) => {\n                if collide.move_max[b.axis as usize] > b.dist {\n                    self.collide_area(b.front, collide)?;\n                }\n\n                if collide.move_min[b.axis as usize] < b.dist {\n                    self.collide_area(b.back, collide)?;\n                }\n            }\n        }\n\n        Ok((trace, collide_entity))\n    }\n\n    pub fn collide_move_with_entity(\n        &self,\n        e_id: EntityId,\n        start: Vector3<f32>,\n        min: Vector3<f32>,\n        max: Vector3<f32>,\n        end: Vector3<f32>,\n    ) -> Result<Trace, ProgsError> {\n        let (hull, offset) = self.hull_for_entity(e_id, min, max)?;\n        debug!(\"hull offset: {:?}\", offset);\n        debug!(\n            \"hull contents at start: {:?}\",\n            hull.contents_at_point(start).unwrap()\n        );\n\n        Ok(hull\n            .trace(start - offset, end - offset)\n            .unwrap()\n            .adjust(offset))\n    }\n}\n"
  },
  {
    "path": "src/server/world/phys.rs",
    "content": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n// and associated documentation files (the \"Software\"), to deal in the Software without\n// restriction, including without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n// Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n//! Physics and collision detection.\n\nuse crate::{\n    common::{bsp::BspLeafContents, math::Hyperplane},\n    server::progs::EntityId,\n};\n\nuse bitflags::bitflags;\nuse cgmath::{InnerSpace, Vector3, Zero};\n\n/// Velocity in units/second under which a *component* (not the entire\n/// velocity!) is instantly reduced to zero.\n///\n/// This prevents objects from sliding indefinitely at low velocity.\nconst STOP_THRESHOLD: f32 = 0.1;\n\n#[derive(Copy, Clone, Debug, Eq, FromPrimitive, PartialEq)]\npub enum MoveKind {\n    /// Does not move.\n    None = 0,\n    AngleNoClip = 1,\n    AngleClip = 2,\n    /// Player-controlled.\n    Walk = 3,\n    /// Moves in discrete steps (monsters).\n    Step = 4,\n    Fly = 5,\n    Toss = 6,\n    Push = 7,\n    NoClip = 8,\n    FlyMissile = 9,\n    Bounce = 10,\n}\n\n#[derive(Copy, Clone, Debug, Eq, FromPrimitive, PartialEq)]\npub enum CollideKind {\n    Normal = 0,\n    NoMonsters = 1,\n    Missile = 2,\n}\n\n#[derive(Debug)]\npub struct Collide {\n    /// The ID of the entity being moved.\n    pub e_id: Option<EntityId>,\n\n    /// The minimum extent of the entire move.\n    pub move_min: Vector3<f32>,\n\n    /// The maximum extent of the entire move.\n    pub move_max: Vector3<f32>,\n\n    /// The minimum extent of the moving object.\n    pub min: Vector3<f32>,\n\n    /// The maximum extent of the moving object.\n    pub max: Vector3<f32>,\n\n    /// The minimum extent of the moving object when colliding with a monster.\n    pub monster_min: Vector3<f32>,\n\n    /// The maximum extent of the moving object when colliding with a monster.\n    pub monster_max: Vector3<f32>,\n\n    /// The start point of the move.\n    pub start: Vector3<f32>,\n\n    /// The end point of the move.\n    pub end: Vector3<f32>,\n\n    /// How this move collides with other entities.\n    pub kind: CollideKind,\n}\n\n/// Calculates a new velocity after collision with a surface.\n///\n/// `overbounce` approximates the elasticity of the collision. A value of `1`\n/// reduces the component of `initial` antiparallel to `surface_normal` to zero,\n/// while a value of `2` reflects that component to be parallel to\n/// `surface_normal`.\npub fn velocity_after_collision(\n    initial: Vector3<f32>,\n    surface_normal: Vector3<f32>,\n    overbounce: f32,\n) -> (Vector3<f32>, CollisionFlags) {\n    let mut flags = CollisionFlags::empty();\n\n    if surface_normal.z > 0.0 {\n        flags |= CollisionFlags::HORIZONTAL;\n    } else if surface_normal.z == 0.0 {\n        flags |= CollisionFlags::VERTICAL;\n    }\n\n    let change = (overbounce * initial.dot(surface_normal)) * surface_normal;\n    let mut out = initial - change;\n\n    for i in 0..3 {\n        if out[i].abs() < STOP_THRESHOLD {\n            out[i] = 0.0;\n        }\n    }\n\n    (out, flags)\n}\n\n/// Calculates a new velocity after collision with multiple surfaces.\npub fn velocity_after_multi_collision(\n    initial: Vector3<f32>,\n    planes: &[Hyperplane],\n    overbounce: f32,\n) -> Option<Vector3<f32>> {\n    // Try to find a plane which produces a post-collision velocity that will\n    // not cause a subsequent collision with any of the other planes.\n    for (a, plane_a) in planes.iter().enumerate() {\n        let (velocity_a, _flags) = velocity_after_collision(initial, plane_a.normal(), overbounce);\n\n        for (b, plane_b) in planes.iter().enumerate() {\n            if a == b {\n                // Don't test a plane against itself.\n                continue;\n            }\n\n            if velocity_a.dot(plane_b.normal()) < 0.0 {\n                // New velocity would be directed into another plane.\n                break;\n            }\n        }\n\n        // This velocity is not expected to cause immediate collisions with\n        // other planes, so return it.\n        return Some(velocity_a);\n    }\n\n    if planes.len() > 2 {\n        // Quake simply gives up in this case. This is distinct from returning\n        // the zero vector, as it indicates that the trajectory has really\n        // wedged something in a corner.\n        None\n    } else {\n        // Redirect velocity along the intersection of the planes.\n        let dir = planes[0].normal().cross(planes[1].normal());\n        let scale = initial.dot(dir);\n        Some(scale * dir)\n    }\n}\n\n/// Represents the start of a collision trace.\n#[derive(Clone, Debug)]\npub struct TraceStart {\n    point: Vector3<f32>,\n    /// The ratio along the original trace length at which this (sub)trace\n    /// begins.\n    ratio: f32,\n}\n\nimpl TraceStart {\n    pub fn new(point: Vector3<f32>, ratio: f32) -> TraceStart {\n        TraceStart { point, ratio }\n    }\n}\n\n/// Represents the end of a trace which crossed between leaves.\n#[derive(Clone, Debug)]\npub struct TraceEndBoundary {\n    pub ratio: f32,\n    pub plane: Hyperplane,\n}\n\n/// Indicates the the nature of the end of a trace.\n#[derive(Clone, Debug)]\npub enum TraceEndKind {\n    /// This endpoint falls within a leaf.\n    Terminal,\n\n    /// This endpoint falls on a leaf boundary (a plane).\n    Boundary(TraceEndBoundary),\n}\n\n/// Represents the end of a trace.\n#[derive(Clone, Debug)]\npub struct TraceEnd {\n    point: Vector3<f32>,\n    kind: TraceEndKind,\n}\n\nimpl TraceEnd {\n    pub fn terminal(point: Vector3<f32>) -> TraceEnd {\n        TraceEnd {\n            point,\n            kind: TraceEndKind::Terminal,\n        }\n    }\n\n    pub fn boundary(point: Vector3<f32>, ratio: f32, plane: Hyperplane) -> TraceEnd {\n        TraceEnd {\n            point,\n            kind: TraceEndKind::Boundary(TraceEndBoundary { ratio, plane }),\n        }\n    }\n\n    pub fn kind(&self) -> &TraceEndKind {\n        &self.kind\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct Trace {\n    start: TraceStart,\n    end: TraceEnd,\n    contents: BspLeafContents,\n    start_solid: bool,\n}\n\nimpl Trace {\n    pub fn new(start: TraceStart, end: TraceEnd, contents: BspLeafContents) -> Trace {\n        let start_solid = contents == BspLeafContents::Solid;\n        Trace {\n            start,\n            end,\n            contents,\n            start_solid,\n        }\n    }\n\n    /// Join this trace end-to-end with another.\n    ///\n    /// - If `self.end_point()` does not equal `other.start_point()`, returns `self`.\n    /// - If `self.contents` equals `other.contents`, the traces are combined (e.g. the new trace\n    ///   starts with `self.start` and ends with `other.end`).\n    /// - If `self.contents` is `Solid` but `other.contents` is not, the trace is allowed to move\n    ///   out of the solid area. The `startsolid` flag should be set accordingly.\n    /// - Otherwise, `self` is returned, representing a collision or transition between leaf types.\n    ///\n    /// ## Panics\n    /// - If `self.end.kind` is `Terminal`.\n    /// - If `self.end.point` does not equal `other.start.point`.\n    pub fn join(self, other: Trace) -> Trace {\n        debug!(\n            \"start1={:?} end1={:?} start2={:?} end2={:?}\",\n            self.start.point, self.end.point, other.start.point, other.end.point\n        );\n        // don't allow chaining after terminal\n        // TODO: impose this constraint with the type system\n        if let TraceEndKind::Terminal = self.end.kind {\n            panic!(\"Attempted to join after terminal trace\");\n        }\n\n        // don't allow joining disjoint traces\n        if self.end.point != other.start.point {\n            panic!(\"Attempted to join disjoint traces\");\n        }\n\n        // combine traces with the same contents\n        if self.contents == other.contents {\n            return Trace {\n                start: self.start,\n                end: other.end,\n                contents: self.contents,\n                start_solid: self.start_solid,\n            };\n        }\n\n        if self.contents == BspLeafContents::Solid && other.contents != BspLeafContents::Solid {\n            return Trace {\n                start: self.start,\n                end: other.end,\n                contents: other.contents,\n                start_solid: true,\n            };\n        }\n\n        self\n    }\n\n    /// Adjusts the start and end points of the trace by an offset.\n    pub fn adjust(self, offset: Vector3<f32>) -> Trace {\n        Trace {\n            start: TraceStart {\n                point: self.start.point + offset,\n                ratio: self.start.ratio,\n            },\n            end: TraceEnd {\n                point: self.end.point + offset,\n                kind: self.end.kind,\n            },\n            contents: self.contents,\n            start_solid: self.start_solid,\n        }\n    }\n\n    /// Returns the point at which the trace began.\n    pub fn start_point(&self) -> Vector3<f32> {\n        self.start.point\n    }\n\n    /// Returns the end of this trace.\n    pub fn end(&self) -> &TraceEnd {\n        &self.end\n    }\n\n    /// Returns the point at which the trace ended.\n    pub fn end_point(&self) -> Vector3<f32> {\n        self.end.point\n    }\n\n    /// Returns true if the entire trace is within solid leaves.\n    pub fn all_solid(&self) -> bool {\n        self.contents == BspLeafContents::Solid\n    }\n\n    /// Returns true if the trace began in a solid leaf but ended outside it.\n    pub fn start_solid(&self) -> bool {\n        self.start_solid\n    }\n\n    pub fn in_open(&self) -> bool {\n        self.contents == BspLeafContents::Empty\n    }\n\n    pub fn in_water(&self) -> bool {\n        self.contents != BspLeafContents::Empty && self.contents != BspLeafContents::Solid\n    }\n\n    /// Returns whether the trace ended without a collision.\n    pub fn is_terminal(&self) -> bool {\n        if let TraceEndKind::Terminal = self.end.kind {\n            true\n        } else {\n            false\n        }\n    }\n\n    /// Returns the ratio of travelled distance to intended distance.\n    ///\n    /// This indicates how far along the original trajectory the trace proceeded\n    /// before colliding with a different medium.\n    pub fn ratio(&self) -> f32 {\n        match &self.end.kind {\n            TraceEndKind::Terminal => 1.0,\n            TraceEndKind::Boundary(boundary) => boundary.ratio,\n        }\n    }\n}\n\nbitflags! {\n    pub struct CollisionFlags: u32 {\n        const HORIZONTAL = 1;\n        const VERTICAL = 2;\n        const STOPPED = 4;\n    }\n}\n\npub fn bounds_for_move(\n    start: Vector3<f32>,\n    min: Vector3<f32>,\n    max: Vector3<f32>,\n    end: Vector3<f32>,\n) -> (Vector3<f32>, Vector3<f32>) {\n    let mut box_min = Vector3::zero();\n    let mut box_max = Vector3::zero();\n\n    for i in 0..3 {\n        if end[i] > start[i] {\n            box_min[i] = start[i] + min[i] - 1.0;\n            box_max[i] = end[i] + max[i] + 1.0;\n        } else {\n            box_min[i] = end[i] + min[i] - 1.0;\n            box_max[i] = start[i] + max[i] + 1.0;\n        }\n    }\n\n    (box_min, box_max)\n}\n"
  }
]