├── .circleci └── config.yml ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── STYLEGUIDE.md ├── build.py ├── imgsrc ├── agstplot.py ├── agstplot.txt ├── bullet-spread-angle.tex ├── bullet-spread-distribution.tex ├── circular-wall.pdf ├── circular-wall.tex ├── collision-overbounce.tex ├── doublegauss-parts.tex ├── duckbug-bbox.tex ├── ducking-sequence-air.tex ├── ducking-sequence-ducktap.tex ├── ducking-sequence-ground.tex ├── edgefriction-1.tex ├── entitypunch-1.ai ├── explosion-contact-grenades.ai ├── explosion-nuking.tex ├── explosion-parts.tex ├── frame_rate_unsync.py ├── gauss-flowchart.tex ├── handgrenade-vel-1.py ├── headcrab-jump.tex ├── hlprlogo-favicon.ai ├── hlprlogo-favicon.png ├── hlprlogo.ai ├── jumpbug-bbox.pdf ├── jumpbug-bbox.tex ├── ladder-angles-1.py ├── multigauss-1.ai ├── player-hulls.tex ├── player_hp.py ├── plotfonts │ └── __init__.py ├── reflection-bypass.tex ├── shotgun-reload.tex ├── simple_gauss_boosts.py ├── sloped-plane-jump.tex ├── stairparts.ai ├── strafe-framerate.py ├── strafe-plots.py ├── strafing-intuition-1.ai ├── templates │ ├── hlpr-flowchart.graffle │ ├── hlpr-flowchart.gtemplate │ ├── hlpr.ait │ └── hlprtikz.cls ├── tools │ └── optigif └── viewangles.tex ├── package.json ├── requirements.txt ├── scripts ├── deploy ├── hlprpdf2svg.inx ├── hlprpdf2svg.py ├── launchinkscape.sh └── mjrender.js ├── source ├── _templates │ ├── extraabout.html │ └── layout.html ├── algorithms.rst ├── automation.rst ├── basicphy.rst ├── casestudies.rst ├── conf.py ├── damage.rst ├── duckjump.rst ├── entity.rst ├── explosions.rst ├── funcs.rst ├── game.rst ├── glossary.rst ├── gravitymotion.rst ├── images │ ├── 90-degrees-bend-c2a2e.jpg │ ├── 90-degrees-strafe-radius.png │ ├── agstplot.svg │ ├── boot_camp_selfgauss.jpg │ ├── bullet-spread-angle.svg │ ├── bullet-spread-distribution.svg │ ├── circular-wall.svg │ ├── collision-overbounce.svg │ ├── conveyor-residue-processing.jpg │ ├── crossbow-bolt.jpg │ ├── doublegauss-crate.jpg │ ├── doublegauss-parts.svg │ ├── duckbug-bbox.svg │ ├── ducking-sequence-air.svg │ ├── ducking-sequence-ducktap.svg │ ├── ducking-sequence-ground.svg │ ├── edgefriction-1.svg │ ├── entitypunch-1.png │ ├── explosion-contact-grenades.svg │ ├── explosion-mortar.jpg │ ├── explosion-nuking.svg │ ├── explosion-parts.svg │ ├── frame_rate_unsync.png │ ├── gauss-entity-punch.jpg │ ├── gauss-flowchart.svg │ ├── gauss.jpg │ ├── gordon-scientist.jpg │ ├── handgrenade-vel-1.svg │ ├── headcrab-jump.svg │ ├── hitboxes-2.jpg │ ├── houndeyes.jpg │ ├── jumpbug-bbox.svg │ ├── ladder-angles-1.png │ ├── ladder-exit-c1a0e.jpg │ ├── ladder-sloped.jpg │ ├── movable-box.jpg │ ├── player-hulls.svg │ ├── player_hp.png │ ├── radius-estimate-xy.png │ ├── reflection-bypass.svg │ ├── selfgauss-1.png │ ├── shotgun-reload.svg │ ├── simple_gauss_boosts.gif │ ├── sloped-plane-jump.svg │ ├── speed-preserving-c1a2.jpg │ ├── stairparts.png │ ├── strafing-intuition-1.png │ ├── timed-grenade.jpg │ ├── triggers-c3a2c.jpg │ ├── veccom-1.png │ └── viewangles.svg ├── index.rst ├── ladder.rst ├── monsters.rst ├── movement.rst ├── othergames.rst ├── player.rst ├── practical.rst ├── static │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── hlprlogo.png │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── strafing.rst ├── surfing.rst ├── triggers.rst └── weapons.rst └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | python: circleci/python@0.3.2 5 | node: circleci/node@3.0.1 6 | aws-s3: circleci/aws-s3@1.0.16 7 | 8 | jobs: 9 | build: 10 | docker: 11 | - image: cimg/python:3.7 12 | resource_class: small 13 | steps: 14 | - checkout 15 | - python/load-cache 16 | - python/install-deps 17 | - python/save-cache 18 | - node/install: 19 | install-yarn: true 20 | node-version: lts 21 | - node/install-packages: 22 | pkg-manager: yarn 23 | - run: 24 | name: Build and render 25 | command: python3 build.py build 26 | - persist_to_workspace: 27 | root: build 28 | paths: 29 | - html 30 | 31 | deploy: 32 | docker: 33 | - image: cimg/python:3.7 34 | resource_class: small 35 | steps: 36 | - attach_workspace: 37 | at: build 38 | - aws-s3/sync: 39 | from: build/html 40 | to: s3://jwchong.com/hl 41 | arguments: | 42 | --exclude .buildinfo 43 | 44 | workflows: 45 | version: 2 46 | build-and-deploy: 47 | jobs: 48 | - build 49 | - deploy: 50 | context: AWS 51 | requires: 52 | - build 53 | filters: 54 | branches: 55 | only: master 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .DS_Store 3 | *.swp 4 | *.pyc 5 | *~ 6 | 7 | *.aux 8 | *.log 9 | 10 | imgsrc/*.pdf 11 | imgsrc/*.svg 12 | imgsrc/*.png 13 | imgsrc/*.gif 14 | 15 | venv/ 16 | node_modules/ 17 | .cache/ 18 | dist/ 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.enabled": true, 3 | "cSpell.ignoreWords": [ 4 | "a rline", 5 | "accel", 6 | "alwaysgib", 7 | "amsmath", 8 | "arccos", 9 | "arctan", 10 | "baselw", 11 | "baskerville", 12 | "bbox", 13 | "bmatrix", 14 | "circ", 15 | "colorlet", 16 | "d", 17 | "displaystyle", 18 | "dmg", 19 | "dmg alwaysgib", 20 | "dmg energybeam", 21 | "dmg nevergib", 22 | "drawentities", 23 | "energybeam", 24 | "entitypunch", 25 | "entvars", 26 | "entvars t", 27 | "fcbase", 28 | "flafla", 29 | "fontsize", 30 | "fontspec", 31 | "forwardmove", 32 | "ghostscript", 33 | "glockfile", 34 | "graffle", 35 | "hlpr", 36 | "hlprtikz", 37 | "ifhlpr", 38 | "ifthen", 39 | "ifthenelse", 40 | "implement", 41 | "implement saverestore", 42 | "infty", 43 | "injx", 44 | "jrsala", 45 | "kared", 46 | "lvert", 47 | "mapsto", 48 | "mathbb", 49 | "mathbf", 50 | "mathfrak", 51 | "mathit", 52 | "matplotlib", 53 | "max", 54 | "max accel", 55 | "maxaccel", 56 | "maxdecel", 57 | "maxspeed", 58 | "maxvelocity", 59 | "metapost", 60 | "movetype", 61 | "multipass", 62 | "nevergib", 63 | "newcommand", 64 | "newif", 65 | "newlength", 66 | "newvelmat", 67 | "nextspeed", 68 | "noclip", 69 | "nonshared", 70 | "numref", 71 | "omni", 72 | "omni graffle", 73 | "operatorname", 74 | "optigif", 75 | "optipng", 76 | "overline", 77 | "pgfmathsetmacro", 78 | "poppler", 79 | "powerup", 80 | "powerup think", 81 | "prestrafing", 82 | "providecommand", 83 | "r drawentities", 84 | "r vert", 85 | "rline", 86 | "rvert", 87 | "s", 88 | "saverestore", 89 | "scshape", 90 | "selectfont", 91 | "semithick", 92 | "setlength", 93 | "setmainfont", 94 | "setmathfont", 95 | "setmonofont", 96 | "sidemove", 97 | "smallerfont", 98 | "smallerfonttrue", 99 | "solid", 100 | "solid bbox", 101 | "spawnflags", 102 | "srand", 103 | "struct", 104 | "sv", 105 | "sv maxspeed", 106 | "sv maxvelocity", 107 | "t", 108 | "texttt", 109 | "think", 110 | "tikz", 111 | "tikzpicture", 112 | "tikzset", 113 | "typedescription", 114 | "u", 115 | "u srand", 116 | "underset", 117 | "usepackage", 118 | "usercmd", 119 | "usercmd s", 120 | "usetikzlibrary", 121 | "varphi", 122 | "varphi g", 123 | "vartheta", 124 | "vert", 125 | "weapon", 126 | "weapon noclip", 127 | "x", 128 | "xash", 129 | "xash d", 130 | "yesno", 131 | "yshift", 132 | "zdrytch", 133 | "zdrytch x" 134 | ], 135 | "restructuredtext.confPath": "${workspaceFolder}/source", 136 | "editor.wordWrap": "on", 137 | "cSpell.words": [ 138 | "disattenuation", 139 | "headshots", 140 | "hitbox" 141 | ] 142 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Half-Life Physics Reference 2 | 3 | ![Gordon and Scientist](https://raw.githubusercontent.com/Matherunner/hldoc/master/source/images/gordon-scientist.jpg) 4 | 5 | [![hldoc](https://circleci.com/gh/Matherunner/hldoc.svg?style=svg)](https://app.circleci.com/pipelines/github/Matherunner/hldoc) 6 | 7 | This repo contains the source files for the Half-Life physics documentation hosted at **https://www.jwchong.com/hl/**. 8 | 9 | ## Building 10 | 11 | Install python. Then first create a python `venv` folder: 12 | 13 | $ python -m venv venv 14 | 15 | Activate the venv: 16 | 17 | $ . ./venv/bin/activate 18 | 19 | Then install the packages: 20 | 21 | $ pip install -r requirements.txt 22 | 23 | Install the node packages needed for the MathJax rendering script: 24 | 25 | $ yarn install 26 | 27 | To build the HTML files, run 28 | 29 | $ python build.py build 30 | 31 | ## How to contribute 32 | 33 | You are encouraged to help out this documentation by researching and writing. You will be credited. This project will be kept online for as long as I'm alive. You can begin by checking out the list of topics below and research the ones you find interesting, or simply look at the empty sections of the documentation and contribute to research and writing for it. 34 | 35 | | Difficulty | Meaning | 36 | | --- | --- | 37 | | Low | Requires a few hours worth of effort to gain a thorough understanding | 38 | | Medium | Requires a few days worth of focused effort to gain a thorough understanding | 39 | | High | Requires a few days to a few weeks worth of focused effort, while possessing prerequisite knowledge and skills, to gain a reasonable level of understanding | 40 | | Very high | Requires a few weeks to a few months worth of focused effort, while possessing deep prerequisite knowledge and skills, to gain a reasonable level of understanding | 41 | | Extremely high | Requires months or years of focused effort, while possessing expert-level understanding of relevant Half-Life physics and very well trained on specific mathematical skills, to make meaningful contributions | 42 | 43 | ### Engine internals 44 | 45 | Engine internals are generally tedious to research on due to the unavailability of source code. 46 | 47 | | Topic | Issues | Difficulty | 48 | | --- | --- | --- | 49 | | Loading, saving, level change | | High | 50 | | Save warping | | Medium | 51 | | BSP collision structure in memory | | High | 52 | | Tracing | | High | 53 | | Frame walkthrough | | High | 54 | | 0ms frames | | Medium | 55 | | Network messages and DELTA | | Medium | 56 | | Demos | | Medium | 57 | 58 | ### Movement physics 59 | 60 | Naturally, movement physics are some of the most maths-heavy parts of Half-Life physics, and thus some of these topics may be very difficult and require a high level of skill and knowledge. 61 | 62 | | Topic | Issues | Difficulty | 63 | | --- | --- | --- | 64 | | Surfing | | Extremely high | 65 | | Ducktapping on stairs | | High | 66 | | Entity movements | | Medium | 67 | | Base velocity | | Medium | 68 | | Optimal ladder side exit | | High | 69 | | Optimal tight strafing | | Very high | 70 | | Optimal vent movement with bunnyhop cap | | High | 71 | | Optimal damage boost timing | | High | 72 | | Computing optimal route | | Extremely high | 73 | | Constrained vectorial compensation | | High | 74 | | Implementation of autoactions | | High | 75 | | Characterising human strafing | | Very high | 76 | 77 | ### Weapons 78 | 79 | Some of these topics may require some mathematics, and others may require an understanding of engine internals. 80 | 81 | | Topic | Issues | Difficulty | 82 | | --- | --- | --- | 83 | | Optimal snark climbing | | Very high | 84 | | Hornet manipulation and penetration | | Medium | 85 | | Crowbar | | Low | 86 | | Exact selfgauss conditions | | High | 87 | | Exact nuking conditions | | High | 88 | | Tripmines | | Low | 89 | | Rockets | | Medium | 90 | | Mid-air projectile collisions | | Very high | 91 | 92 | ### Entities 93 | 94 | Understanding entities may require venturing into the engine, which again, may be tedious. 95 | 96 | | Topic | Issues | Difficulty | 97 | | --- | --- | --- | 98 | | func\_wall vs worldspawn | | Medium | 99 | | trigger\_once and trigger\_multiple | | Medium | 100 | | trigger\_push | | Medium | 101 | | How moving platforms carry entities | | Medium | 102 | | func\_rotating (and friends) crushing mechanism | | Medium | 103 | | Multimanager and scripting | | Medium | 104 | 105 | ### Monsters 106 | 107 | A monster refers to an entity that has an AI, both friendly and unfriendly. Researching on AI can be very tedious due to the complex interactions between different parts of the code. 108 | 109 | | Topic | Issues | Difficulty | 110 | | --- | --- | --- | 111 | | Node graphs and monster movements | | High | 112 | | Enemy behaviour | | High | 113 | | Talkmonster behaviour | | High | 114 | | Why models affects behaviour | | High | 115 | | Ammo duplication with save loading, and reportedly without | | High | 116 | 117 | ### TAS 118 | 119 | TAS topics are generally long term. 120 | 121 | | Topic | Issues | Difficulty | 122 | | --- | --- | --- | 123 | | Better TAS process and tools | | Very high | 124 | | Application of ML | | Extremely high | 125 | 126 | ## Licence 127 | 128 | [![Creative Commons License](https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png)](http://creativecommons.org/licenses/by-nc-nd/4.0/) 129 | 130 | This work is licensed under a [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/). 131 | -------------------------------------------------------------------------------- /STYLEGUIDE.md: -------------------------------------------------------------------------------- 1 | # STYLE GUIDE FOR HLPR 2 | 3 | ## General 4 | 5 | Note that 1 px = 0.75 pt on the web as defined [here](https://www.w3.org/TR/css3-values/#absolute-lengths), but 1 px = 1 pt in Illustrator. Convert appropriately. The web is always assumed to be in 96 ppi as implied in the document. 6 | 7 | ## Images 8 | 9 | All images should be captioned. 10 | 11 | All PNG files must be optimised by the `optipng` program. 12 | 13 | All GIF files must be optimised using the provided `optigif` script. 14 | 15 | All SVG files must be optimised using the `svgo` program. 16 | 17 | Images should preferably not exceed 625px in width. The height should preferably be within a screen height, but for some diagrams such as flow charts exceeding one screen is unavoidable. 18 | 19 | ### Screenshots 20 | 21 | Photos or screenshots should be served in JPEG format. While 2x resolution is desirable, this requirement is optional if the degradation in quality is not too severe. If 2x resolution is used, the :scale: 50% attribute must be specified. 22 | 23 | ### Generating SVG from PDF 24 | 25 | The following method generates the smallest SVG files, while ensuring maximal browser compatibility by not using any advanced SVG features. The downside is the reliance of a proprietary software, Adobe Illustrator. In addition, as texts are outlined, if the graphic contains many characters, the file size can increase greatly. 26 | 27 | 1. Open Adobe Illustrator 2017 28 | 2. Open the PDF 29 | 3. Check that the width of the image is within limits 30 | 4. Go to File -> Export -> Export for Screens... 31 | 5. Choose output directory as `images/` 32 | 6. Choose "Full Document" under "Select" 33 | 7. Choose "SVG" as the only entry under Formats 34 | 8. Click on the gear icon to access SVG settings, set "Styling" to "Internal CSS", "Font" to "Convert to Outlines", "Images" to "Embed", "Object IDs" to "Minimal", "Decimal" to "2", and **un-check "Responsive"**. 35 | 9. Click "Export Artboard" 36 | 10. Double-check that the `width` attribute in the `svg` tag is within limits 37 | 11. Run `svgo` on the generated SVG 38 | 39 | Steps 5 to 8 need to be done only once, unless the settings have been modified. 40 | 41 | The "Convert To Outlines" is crucial to allow the SVG to be read everywhere. Note that we must not embed any fonts because the `` attribute has very limited browser support at the time of writing. The output SVG must then be processed using the `svgo` to further reduce the file size. In case Illustrator makes this feature difficult, or if Illustrator is not available, ghostscript has a `-dNoOutputFonts` parameter for PDFs. 42 | 43 | Alternatively, if Adobe Illustrator is not available: 44 | 45 | 1. Open Inkscape 1.0 46 | 2. Open the PDF, choose "Poppler/Cairo import" (do not use "Internal import" as the text will be messed up) 47 | 3. Go to File -> Document Properties..., set "Scale x" to 1 for "User units per px" 48 | 4. Click "Resize page to drawing or selection" 49 | 5. Check that the width of the image is within limits *in px* 50 | 6. Go to File -> Save a Copy, choose "Plain SVG" as the format (do not choose "Optimized SVG" as it is not as good as using `svgo`) 51 | 7. Run `svgo --multipass -p 2` on the generated SVG 52 | 53 | These steps may be partially automated by installing the `hlprpdf2svg` Inkscape extension. 54 | 55 | The file size generated using this method will tend to be bigger. Nevertheless, each glyph is defined as a `` and reused throughout by Inkscape, which implies that an image with many characters may be able to keep the file size small, compared to the Illustrator method. That is, as the number of characters increases, there may be a point at which Inkscape would generate smaller files than Illustrator. 56 | 57 | ### Technical drawings 58 | 59 | Recommended drawing programs: 60 | 61 | - Adobe Illustrator (general purpose, and essential for producing SVGs with embedded fonts) 62 | - OmniGraffle (for flowcharts) 63 | - TikZ (for mathematical and precise drawings, and possibly flowcharts) 64 | - Asymptote/Metapost (alternative to TikZ) 65 | - matplotlib (for graphs) 66 | 67 | Drawings should be ideally served be as SVG files. This is so that a line that is supposed to be 1px thick will actually appear so. If PNG is created, the line widths will be scaled up instead. 68 | 69 | The background should usually be transparent unless a different background colour has a significant meaning or provides clearer presentation. 70 | 71 | Texts should generally be set in Baskerville or its variants. 72 | 73 | Arrowheads should preferably be "Arrow 9" in Illustrator. 74 | 75 | Lines should have a minimum stroke width of 1px. 76 | 77 | Dotted lines should have 2px dashes and 2px gaps. 78 | 79 | Font sizes in SVG files must be specified at 125% of the intended web `pt` size for reasons already explained earlier. For example, if a text of size 13pt is desired, it must Abe specified at 17pt in Illustrator. 80 | 81 | Use the provided `hlpr.ait` template if Illustrator is used. To create flowcharts, it's recommended to use OmniGraffle and `hlpr-flowchart.gtemplate`. 82 | 83 | If other vector graphics programs are used, unless they support converting text to outline *at the export step*, independent of source file, do not export SVGs from them. Instead, export to PDF or EPS, and then export as SVG from Illustrator. 84 | 85 | #### TikZ 86 | 87 | Always use `bp` instead of `pt` whenever TeX is involved. This is very important. 88 | 89 | Standard procedure for preparing a TikZ graphic: 90 | 91 | 1. Make sure Baskerville 10 Pro and Dark Modern typefaces are installed 92 | 2. Create a `.tex` file under `imgsrc/` 93 | 3. Use the `imgsrc/templates/hlprtikz` document class 94 | 4. Generate PDF by running with `lualatex` 95 | 5. Generate the production SVG by standard methods 96 | 97 | #### Flowcharts 98 | 99 | Draw flowcharts using TikZ or OmniGraffle only. There are a few guidelines to the style of flowcharts: 100 | 101 | 1. Flow charts should not have boxes drawn around them. While this runs counter how "professional" flowcharts are usually drawn, it has the benefits of reducing visual noise and make the entire figure more compact. Flowcharts may contain many nodes, and putting boxes in every node will dramatically bloat the size of the figure for little good. Another minor reason is that, while standardisation organisations like ISO has defined the meaning of different shapes in a flowchart, unless you are a flowchart expert, or made conscious effort to learn them, or are forced to learn them at some point, you wouldn't be able to tell the difference in meaning between a normal rectangle and a parallelogram, or a circle and a rounded rectangle. 102 | 103 | 2. Always expand vertically, never horizontally, because the screen is meant to be scrolled vertically. Go in horizontal directions only for short branches. 104 | 105 | 3. Prefer drawing arrows in horizontal or vertical directions. Diagonal arrows may be used for short branches. 106 | 107 | 4. If there is a need to loop back, and there are multiple points at which this can happen, draw a big continuous arrow starting the *lowest* node that loops back, and for all the nodes above that loop back, draw a horizontal arrow that touches the big arrow. 108 | 109 | 110 | %%%%% NOTE: only put "on grid" for nodes that are left or right of, otherwise don't 111 | %%%%% Also, don't use "start branch", it just doesn't work well 112 | %%%%% If branching out and need to change direction, specify continue chain for the second node, or on chain on second and all the rest 113 | 114 | %%%%% Use near start for "yes" and "no" 115 | 116 | ### Screenshots 117 | 118 | In game screenshots should have higher exposures, warmer tint, slightly desaturated, and sharpened. None of these effects should be overdone. The rationale is for the screenshots to fit the background colour of the documentation. 119 | 120 | ### Font size series 121 | 122 | 13, 16 123 | 124 | ### TikZ animations 125 | 126 | Tikz animations can be created by generating a PDF with multiple pages, each page representing one frame, and then extracting the pages out into PNG files with ImageMagick. To create a new page, simply create a new `tikzpicture` environment. To generate multiple `tikzpicture` blocks iteratively, surround a `tikzpicture` with a `\foreach` construct. This is valid because `\foreach` is more powerful than repeating some draw commands -- it's a full-fledged command provided by `pgffor` that is independent of `tikzpicture`, and you can put anything inside the curly braces. 127 | 128 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import glob 5 | import shutil 6 | import argparse 7 | import subprocess 8 | 9 | def postprocess_html(root): 10 | print('Rendering MathJax') 11 | to_process = [] 12 | for root, _dirs, files in os.walk(root): 13 | for file in files: 14 | if file.endswith('.html'): 15 | to_process.append(os.path.join(root, file)) 16 | subprocess.run(['yarn', 'mjrender', *to_process]).check_returncode() 17 | 18 | print('Fixing up alabaster.css') 19 | with open('build/html/_static/alabaster.css', 'r+') as cssfile: 20 | lines = cssfile.readlines() 21 | for i, line in enumerate(lines): 22 | if line.startswith('@import url("basic.css");'): 23 | lines[i] = '@import "./basic.css";\n' 24 | break 25 | cssfile.seek(0, 0) 26 | cssfile.writelines(lines) 27 | 28 | print('Delete unused files') 29 | for file in glob.glob('build/html/_static/underscore-*.js'): 30 | os.remove(file) 31 | for file in glob.glob('build/html/_static/jquery-*.js'): 32 | os.remove(file) 33 | os.remove('build/html/objects.inv') 34 | 35 | def clean_command(args): 36 | shutil.rmtree('build', ignore_errors=True) 37 | shutil.rmtree('dist', ignore_errors=True) 38 | shutil.rmtree('.cache', ignore_errors=True) 39 | 40 | def build_command(args): 41 | subprocess.run(['sphinx-build', '-M', 'html', 'source', 'build']).check_returncode() 42 | postprocess_html('build/html') 43 | 44 | def main(): 45 | parser = argparse.ArgumentParser() 46 | subparsers = parser.add_subparsers(required=True, help='sub-command help') 47 | 48 | clean_parser = subparsers.add_parser('clean', help='clean help') 49 | clean_parser.set_defaults(func=clean_command) 50 | 51 | build_parser = subparsers.add_parser('build', help='build help') 52 | build_parser.add_argument('-p, --production', dest='production', action='store_true', help='build for production') 53 | build_parser.set_defaults(func=build_command) 54 | 55 | args = parser.parse_args() 56 | args.func(args) 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /imgsrc/agstplot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import plotfonts 3 | import numpy as np 4 | 5 | data = np.genfromtxt('agstplot.txt', delimiter=',') 6 | 7 | plt.figure(figsize=(8, 5)) 8 | plt.plot(data[:, 0], data[:, 1], label='Air') 9 | plt.plot(data[:, 0], data[:, 2], label='Ground') 10 | plt.grid() 11 | plt.legend(loc=0) 12 | plt.ylabel('Acceleration') 13 | plt.xlabel('Speed') 14 | plt.tight_layout() 15 | plt.savefig('agstplot.pdf', transparent=True) 16 | -------------------------------------------------------------------------------- /imgsrc/bullet-spread-angle.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\BaseLen}{300} 7 | \pgfmathsetmacro{\Angle}{30} 8 | 9 | \coordinate (A) at (0,0); 10 | \coordinate (B) at (\BaseLen,0); 11 | \coordinate (C) at (\Angle:\BaseLen); 12 | \coordinate (C') at (C |- A); 13 | \coordinate (D) at (C -| B); 14 | 15 | \draw (C) node[above]{$C$} -- (A) node[below]{$A$} -- (B) node[below]{$B$} -- (D) node[above]{$D$} -- (A); 16 | \draw (C) -- (C') node[below]{$C'$}; 17 | \draw (B) arc (0:\Angle:\BaseLen); 18 | \draw[densely dotted] (D) -- (C); 19 | \end{tikzpicture} 20 | 21 | \end{document} -------------------------------------------------------------------------------- /imgsrc/bullet-spread-distribution.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\AxisLen}{200} 7 | 8 | \coordinate (O) at (0,0); 9 | \coordinate (XA) at ($(O) - 0.5*(\AxisLen,0)$); 10 | \coordinate (XB) at ($(XA) + (\AxisLen,0)$); 11 | \coordinate (YA) at ($(O) - 0.5*(0,\AxisLen)$); 12 | \coordinate (YB) at ($(YA) + (0,\AxisLen)$); 13 | 14 | \draw[densely dotted] (O) circle (\AxisLen*0.5); 15 | \draw (O) node[below left]{$O$} ++(0.5*\AxisLen, 0.5*\AxisLen) node[right]{$A$} 16 | -- ++(0,-\AxisLen) node[right]{$B$} 17 | -- ++(-\AxisLen,0) node[left]{$C$} 18 | -- ++(0,\AxisLen) node[left]{$D$} 19 | -- cycle; 20 | \draw[thick] 21 | (XA) node[left]{$\displaystyle\left(-\frac{\theta_S}{2}, 0\right)$} 22 | -- (XB) node[right]{$\displaystyle\left(\frac{\theta_S}{2}, 0\right)$} 23 | (YA) node[below]{$\displaystyle\left(0,-\frac{\theta_S}{2}\right)$} 24 | -- (YB) node[above]{$\displaystyle\left(0,\frac{\theta_S}{2}\right)$}; 25 | 26 | \foreach \x/\y in {0.622/0.683,0.86/0.699,0.683/0.841,0.699/0.681,0.841/0.364,0.681/-0.0399} { 27 | \draw[fill] (0.5*\AxisLen*\x,0.5*\AxisLen*\y) circle (2); 28 | } 29 | \end{tikzpicture} 30 | 31 | \end{document} 32 | -------------------------------------------------------------------------------- /imgsrc/circular-wall.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/circular-wall.pdf -------------------------------------------------------------------------------- /imgsrc/circular-wall.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\walldeg}{30} 7 | \pgfmathsetmacro{\walllen}{100} 8 | 9 | \coordinate (O) at (0,0); 10 | \coordinate (A) at ($ (O) + (3*\walldeg:\walllen) $); 11 | \coordinate (B) at ($ (A) + (2*\walldeg:\walllen) $); 12 | \coordinate (C) at ($ (B) + (1*\walldeg:\walllen) $); 13 | \coordinate (D) at ($ (C) + (0*\walldeg:\walllen) $); 14 | \coordinate (E) at ($ (D) + (-1*\walldeg:\walllen) $); 15 | 16 | \coordinate (mid-O-A) at ($ (O)!0.5!(A) $); 17 | \coordinate (mid-A-B) at ($ (A)!0.5!(B) $); 18 | \coordinate (mid-B-C) at ($ (B)!0.5!(C) $); 19 | \coordinate (mid-C-D) at ($ (C)!0.5!(D) $); 20 | \coordinate (mid-D-E) at ($ (D)!0.5!(E) $); 21 | \coordinate (center) at (mid-C-D |- mid-O-A); 22 | 23 | \draw (mid-O-A) node[left]{1}; 24 | \draw (mid-A-B) node[above left]{2}; 25 | \draw (mid-B-C) node[above left]{3}; 26 | \draw (mid-C-D) node[above]{4}; 27 | \draw (mid-D-E) node[above right]{5}; 28 | 29 | \path pic[angle radius=40,angle eccentricity=1.1,"$\beta$"] {angle=mid-A-B--center--mid-O-A}; 30 | \path pic[angle radius=40,angle eccentricity=1.1,"$\beta$"] {angle=mid-B-C--center--mid-A-B}; 31 | \path pic[angle radius=40,angle eccentricity=1.1,"$\beta$"] {angle=mid-C-D--center--mid-B-C}; 32 | \path pic[angle radius=40,angle eccentricity=1.1,"$\beta$"] {angle=mid-D-E--center--mid-C-D}; 33 | 34 | \foreach \p in {mid-O-A, mid-A-B, mid-B-C, mid-C-D, mid-D-E} { 35 | \draw[densely dotted] (\p) -- (center); 36 | } 37 | 38 | \fill[black] (center) node[below right]{\emph{C}} circle (3); 39 | \draw[thick] (O) -- (A) -- (B) -- (C) -- (D) -- (E); 40 | \end{tikzpicture} 41 | 42 | \end{document} -------------------------------------------------------------------------------- /imgsrc/collision-overbounce.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\walldeg}{70} 7 | \pgfmathsetmacro{\walllen}{300} 8 | \pgfmathsetmacro{\wallthick}{150} 9 | \pgfmathsetmacro{\overbounce}{0.4}; 10 | 11 | \coordinate (O) at (0,0); 12 | \coordinate (W) at ($ (O) + (\walldeg:\walllen) $); 13 | \coordinate (O') at ($ (O) + (\walldeg - 90:\wallthick) $); 14 | \coordinate (W') at ($ (W) + (\walldeg - 90:\wallthick) $); 15 | 16 | \coordinate (A) at ($ (O)!0.3!(W) $); 17 | \coordinate (B) at ($ (A) + (\walldeg - 40:170) $); 18 | 19 | \fill[brown20] (O) -- (W) -- (W') -- (O') -- cycle; 20 | \draw[name path=O-W,brown80,thick] (O) node[below left,xshift=5]{$O$} -- (W) node[above,xshift=5]{$W$}; 21 | 22 | \coordinate (C') at ($ (O)!(B)!(W) $); 23 | \coordinate (B-C') at ($ (C') - (B) $); 24 | \coordinate (C) at ($ (C') + \overbounce*(B-C') $); 25 | \coordinate (N) at ($ (A) - 0.3*(B-C') $); 26 | \coordinate (B') at ($ (A)!(B)!(N) $); 27 | 28 | \draw[densely dotted] (A) -- (B') node[below]{$B'$} -- (B) -- (C); 29 | \draw[semithick,->] (A) -- (C) node[above]{$C$}; 30 | \draw[semithick,->] (A) node[left]{$A$} -- (B) node[above right]{$B$}; 31 | \draw[semithick,->] (N) node[below]{$\symbf{n}$} -- (A); 32 | 33 | \draw (C') node[shift={(2,-3)},above right]{$C'$}; 34 | \end{tikzpicture} 35 | 36 | \end{document} 37 | -------------------------------------------------------------------------------- /imgsrc/doublegauss-parts.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\groundWidth}{300} 7 | \pgfmathsetmacro{\boxWidth}{100} 8 | \pgfmathsetmacro{\boxHeight}{80} 9 | \pgfmathsetmacro{\boxGroundGap}{10} 10 | \pgfmathsetmacro{\reflectionAngle}{30} 11 | \pgfmathsetmacro{\beamOneLen}{150} 12 | \pgfmathsetmacro{\beamTwoLen}{100} 13 | \pgfmathsetmacro{\skipLen}{30} 14 | 15 | \coordinate (GA) at (0,0); 16 | \coordinate (GB) at (\groundWidth,0); 17 | \coordinate (R) at ($(GA)!0.5!(GB)$); 18 | \coordinate (O) at ($(R) + (\reflectionAngle:\beamOneLen)$); 19 | \coordinate (O-2) at ($(R) + (180-\reflectionAngle:\skipLen)$); 20 | \coordinate (E) at ($(O-2) + (180-\reflectionAngle:\beamTwoLen)$); 21 | \coordinate (boxA) at ($ 0.5*(GA) + 0.5*(GB) + (-0.5*\boxWidth,\boxGroundGap) $); 22 | %\coordinate (boxB) at ($ (boxA) + (\boxWidth,0) $); 23 | %\coordinate (beam-start) at ($ (GB) - 0.2*(GB) + 0.2*(\boxWidth,0) + (0,\boxGroundGap + 0.7*\boxHeight) $); 24 | 25 | % Ground 26 | \draw[thick] (GA) -- node[very near start,below]{GR Entity} (GB); 27 | 28 | % Box 29 | \path[name path=box-border,draw] (boxA) -- ++(\boxWidth,0) -- ++(0,\boxHeight) -- node[above]{Non-GR Entity} ++(-\boxWidth,0) -- cycle; 30 | 31 | % Beam 32 | \path[name path=inc-beam,draw,semithick,<-|] (R) node[below]{$R$} -- (O) node[above right=-2]{$O$}; 33 | \draw[densely dotted] (R) -- (O-2) node[above=5]{$B$}; 34 | \draw[semithick,|->] (O-2) -- (E) node[above left=-5]{$C$}; 35 | 36 | \path[name intersections={of=box-border and inc-beam}]; 37 | \node[above left] at (intersection-2) {$A$}; 38 | \end{tikzpicture} 39 | 40 | \end{document} 41 | -------------------------------------------------------------------------------- /imgsrc/duckbug-bbox.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\slopedeg}{140} 7 | \pgfmathsetmacro{\ongroundThickness}{15} 8 | \pgfmathsetmacro{\slopelen}{300} 9 | \pgfmathsetmacro{\guidelen}{150} 10 | \pgfmathsetmacro{\bboxscale}{2} 11 | \pgfmathsetmacro{\ABlen}{80} 12 | 13 | \tikzset{ 14 | pics/bboxrect/.style args={#1}{ 15 | code={ 16 | \fill[opacity=0.2] (-16*\bboxscale,0) rectangle +(32*\bboxscale,#1*\bboxscale); 17 | \draw (-16*\bboxscale,0) rectangle +(32*\bboxscale,#1*\bboxscale); 18 | \fill[black] +(0,#1*0.5*\bboxscale) coordinate (bbox-center) circle (2); 19 | }, 20 | }, 21 | } 22 | 23 | \coordinate (O) at (0,0); 24 | \coordinate (O') at ($ (O) + (0,\ongroundThickness) $); 25 | \coordinate (S) at (\slopedeg:\slopelen); 26 | \coordinate (S') at ($ (S) + (0,\ongroundThickness) $); 27 | \coordinate (H) at (S |- O); 28 | 29 | \fill[green20] (O') -- (S') -- (S) -- (O) -- cycle; 30 | \draw[green50] (O') -- (S'); 31 | \fill[brown20] (O) -- (S) -- (H) -- cycle; 32 | \draw[name path=O-S,thick,brown80] (O) -- (S); 33 | \draw[<->] (S) -- node[left]{$2$} (S'); 34 | 35 | \draw pic [draw=green!20!black,<-,angle radius=40,"$\theta$",angle eccentricity=0.8] {angle=S--O--H}; 36 | 37 | \coordinate (B') at ($ (O)!0.4!(S) $); 38 | \coordinate (C') at ($ (B') + (\ABlen,0) $); 39 | 40 | % g-g' guideline 41 | \draw[gray50,densely dotted] ($ (B') - (\guidelen,0) $) node[left]{$g$} -- ($ (B') + (\guidelen,0) $) node[right]{$g'$}; 42 | 43 | \path[gray50] (B') pic {bboxrect=72}; 44 | \coordinate (B) at (bbox-center); 45 | \draw (B) node[above right]{$B$}; 46 | \fill[black] (B') circle (2); 47 | \node[below,xshift=-5] at (B') {$B'$}; 48 | 49 | \path[gray50] ($ (B') + (-\ABlen,100) + (0,18*\bboxscale) $) pic {bboxrect=36}; 50 | \coordinate (A) at (bbox-center); 51 | \coordinate (A') at (A |- B'); 52 | \draw (A) node[above]{$A$}; 53 | \fill[black] (A') circle (2); 54 | \draw (A') node[below]{$A'$}; 55 | 56 | \path[gray50] (C') pic {bboxrect=72}; 57 | \coordinate (C) at (bbox-center); 58 | \draw (C) node[above]{$C$}; 59 | \fill[black] (C') circle (2); 60 | \draw (C') node[below]{$C'$}; 61 | 62 | \draw (A) node[above=20*\bboxscale] {\emph{Ducked}}; 63 | \draw (B) node[above=38*\bboxscale] {\verb|-duck|}; 64 | 65 | \draw[->] (A) -- (B); 66 | \draw[->] (B) -- (C); 67 | 68 | \draw[densely dotted,gray50] (A) -- (A'); 69 | \draw[densely dotted,gray50] (B) -- (B'); 70 | \draw[densely dotted,gray50] (C) -- (C'); 71 | \end{tikzpicture} 72 | 73 | \end{document} 74 | -------------------------------------------------------------------------------- /imgsrc/ducking-sequence-air.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\xscale}{60} 7 | \pgfmathsetmacro{\cmdshift}{-25} 8 | \pgfmathsetmacro{\stateY}{115} 9 | \pgfmathsetmacro{\stateshift}{8} 10 | \pgfmathsetmacro{\hullshift}{55} 11 | 12 | \tikzset{ 13 | pics/bboxrect/.style args={#1}{ 14 | code={ 15 | \draw (-16,-#1*0.5) rectangle +(32,#1); 16 | }, 17 | }, 18 | } 19 | 20 | \coordinate (before-ducked) at (-1.5*\xscale,0); 21 | \coordinate (start-ducked) at (0,0); 22 | \coordinate (mid-ducked-1) at (1*\xscale,0); 23 | \coordinate (mid-ducked-2) at (1*\xscale,0); 24 | \coordinate (mid-ducked-3) at (3*\xscale,0); 25 | \coordinate (end-ducked) at (4*\xscale,0); 26 | 27 | \coordinate (time-start) at (-120,0); 28 | \coordinate (time-end) at (350,0); 29 | \coordinate (state-O) at (0,\stateY); 30 | 31 | \draw[thick,->] (time-start) -- (time-end); 32 | \draw[gray50,densely dotted] (time-start |- 0,\hullshift) -- (time-end |- 0,\hullshift); 33 | 34 | \draw[->] (time-start |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Standing}} (start-ducked |- state-O); 35 | \draw[<->] (start-ducked |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Ducked}} (end-ducked |- state-O); 36 | \draw[<-] (end-ducked |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Standing}} (time-end |- state-O); 37 | 38 | \foreach \p in {start-ducked |- state-O, start-ducked |- state-O, end-ducked |- state-O} { 39 | \draw ($ (\p) - (0,10) $) -- ($ (\p) + (0,10) $); 40 | } 41 | 42 | \path ([yshift=\hullshift]before-ducked) pic {bboxrect=72}; 43 | \path ([yshift=\hullshift]start-ducked) pic {bboxrect=36}; 44 | \path ([yshift=\hullshift]mid-ducked-1) pic {bboxrect=36}; 45 | \path ([yshift=\hullshift]mid-ducked-3) pic {bboxrect=36}; 46 | \path ([yshift=\hullshift]end-ducked) pic {bboxrect=72}; 47 | 48 | \foreach \p in {before-ducked, start-ducked, mid-ducked-1, mid-ducked-3, start-ducked, mid-ducked-1, end-ducked} { 49 | \fill[black] (\p |- 0,\hullshift) circle (2); 50 | \fill[black] (\p) circle (3); 51 | }; 52 | 53 | \node[yshift=\cmdshift,anchor=base,darkBlue50] (start-ducked-n) at (start-ducked) {\verb|+duck|}; 54 | \node[yshift=\cmdshift,anchor=base,darkBlue50] (end-ducked-n) at (end-ducked) {\verb|-duck|}; 55 | \draw[darkBlue50,densely dotted] (start-ducked-n) edge (end-ducked-n); 56 | \end{tikzpicture} 57 | 58 | \end{document} 59 | -------------------------------------------------------------------------------- /imgsrc/ducking-sequence-ducktap.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\xscale}{60} 7 | \pgfmathsetmacro{\tshift}{-20} 8 | \pgfmathsetmacro{\cmdshift}{-50} 9 | \pgfmathsetmacro{\stateY}{110} 10 | \pgfmathsetmacro{\stateshift}{8} 11 | 12 | \tikzset{ 13 | pics/bboxrect/.style args={#1}{ 14 | code={ 15 | \draw (-16,0) rectangle +(32,#1); 16 | }, 17 | }, 18 | } 19 | 20 | \coordinate (before-ducking) at (-1.5*\xscale,0); 21 | \coordinate (start-ducking) at (0,0); 22 | \coordinate (mid-ducking-1) at (1*\xscale,0); 23 | \coordinate (end-ducking) at (2*\xscale,0); 24 | 25 | \coordinate (time-start) at (-120,0); 26 | \coordinate (time-end) at (250,0); 27 | \coordinate (state-O) at (0,\stateY); 28 | 29 | \draw[thick,->] (time-start) -- (time-end); 30 | 31 | \draw[->] (time-start |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Standing}} (start-ducking |- state-O); 32 | \draw[<->] (start-ducking |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{In-duck}} (end-ducking |- state-O); 33 | \draw[<-] (end-ducking |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Standing}} (time-end |- state-O); 34 | 35 | \foreach \p in {start-ducking |- state-O, end-ducking |- state-O} { 36 | \draw ($ (\p) - (0,10) $) -- ($ (\p) + (0,10) $); 37 | } 38 | 39 | \draw (end-ducking) ++(20,18) -- +(25,0); 40 | \draw[<->] (end-ducking) ++(30,0) -- node[right=5]{$18$} +(0,18); 41 | 42 | \path (before-ducking) pic {bboxrect=72}; 43 | \path (start-ducking) pic {bboxrect=72}; 44 | \path (mid-ducking-1) pic {bboxrect=72}; 45 | \path ([yshift=18]end-ducking) pic {bboxrect=72}; 46 | 47 | \foreach \p in {before-ducking, start-ducking, mid-ducking-1, end-ducking} { 48 | \fill[black] (\p) circle (3); 49 | }; 50 | 51 | \node[yshift=\tshift,anchor=base] at (start-ducking) {$t$}; 52 | \node[yshift=\tshift,anchor=base] at (mid-ducking-1) {$t + 0.1$}; 53 | \node[yshift=\tshift,anchor=base] at (end-ducking) {$t + 0.2$}; 54 | 55 | \node[yshift=\cmdshift,anchor=base,darkBlue50] (start-ducking-n) at (start-ducking) {\verb|+duck|}; 56 | \node[yshift=\cmdshift,anchor=base,darkBlue50] (end-ducking-n) at (end-ducking) {\verb|-duck|}; 57 | \draw[darkBlue50,densely dotted] (start-ducking-n) edge (end-ducking-n); 58 | \end{tikzpicture} 59 | 60 | \end{document} 61 | -------------------------------------------------------------------------------- /imgsrc/ducking-sequence-ground.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\xscale}{60} 7 | \pgfmathsetmacro{\tshift}{-20} 8 | \pgfmathsetmacro{\cmdshift}{-50} 9 | \pgfmathsetmacro{\stateY}{90} 10 | \pgfmathsetmacro{\stateshift}{8} 11 | 12 | \tikzset{ 13 | pics/bboxrect/.style args={#1}{ 14 | code={ 15 | \draw (-16,0) rectangle +(32,#1); 16 | }, 17 | }, 18 | } 19 | 20 | \coordinate (before-ducking) at (-1.5*\xscale,0); 21 | \coordinate (start-ducking) at (0,0); 22 | \coordinate (mid-ducking-1) at (1*\xscale,0); 23 | \coordinate (mid-ducking-2) at (2*\xscale,0); 24 | \coordinate (mid-ducking-3) at (3*\xscale,0); 25 | \coordinate (start-ducked) at (4*\xscale,0); 26 | \coordinate (mid-ducked-1) at (6*\xscale,0); 27 | \coordinate (end-ducked) at (7*\xscale,0); 28 | 29 | \coordinate (time-start) at (-120,0); 30 | \coordinate (time-end) at (500,0); 31 | \coordinate (state-O) at (0,\stateY); 32 | 33 | \draw[thick,->] (time-start) -- (time-end); 34 | 35 | \draw[->] (time-start |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Standing}} (start-ducking |- state-O); 36 | \draw[<->] (start-ducking |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{In-duck}} (start-ducked |- state-O); 37 | \draw[<->] (start-ducked |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Ducked}} (end-ducked |- state-O); 38 | \draw[<-] (end-ducked |- state-O) -- node[anchor=base,yshift=\stateshift]{\emph{Standing}} (time-end |- state-O); 39 | 40 | \foreach \p in {start-ducking |- state-O, start-ducked |- state-O, end-ducked |- state-O} { 41 | \draw ($ (\p) - (0,10) $) -- ($ (\p) + (0,10) $); 42 | } 43 | 44 | \path (before-ducking) pic {bboxrect=72}; 45 | \path (start-ducking) pic {bboxrect=72}; 46 | \path (mid-ducking-1) pic {bboxrect=72}; 47 | \path (mid-ducking-3) pic {bboxrect=72}; 48 | \path (start-ducked) pic {bboxrect=36}; 49 | \path (mid-ducked-1) pic {bboxrect=36}; 50 | \path (end-ducked) pic {bboxrect=72}; 51 | 52 | \node[left] at ($ (start-ducking) + (-16,36) $) {$72$}; 53 | \node[right] at ($ (start-ducked) + (16,18) $) {$36$}; 54 | 55 | \foreach \p in {before-ducking, start-ducking, mid-ducking-1, mid-ducking-3, start-ducked, mid-ducked-1, end-ducked} { 56 | \fill[black] (\p) circle (3); 57 | }; 58 | 59 | \node[yshift=\tshift,anchor=base] at (start-ducking) {$t$}; 60 | \node[yshift=\tshift,anchor=base] at (mid-ducking-1) {$t + 0.1$}; 61 | \node[yshift=\tshift,anchor=base] at (mid-ducking-2) {\ldots}; 62 | \node[yshift=\tshift,anchor=base] at (mid-ducking-3) {$t + 0.3$}; 63 | \node[yshift=\tshift,anchor=base] at (start-ducked) {$t + 0.4$}; 64 | 65 | \node[yshift=\cmdshift,anchor=base,darkBlue50] (start-ducking-n) at (start-ducking) {\verb|+duck|}; 66 | \node[yshift=\cmdshift,anchor=base,darkBlue50] (end-ducked-n) at (end-ducked) {\verb|-duck|}; 67 | \draw[darkBlue50,densely dotted] (start-ducking-n) edge (end-ducked-n); 68 | \end{tikzpicture} 69 | 70 | \end{document} 71 | -------------------------------------------------------------------------------- /imgsrc/edgefriction-1.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\PlayerHeight}{130} 7 | \pgfmathsetmacro{\PlayerWidth}{32/72*\PlayerHeight} 8 | \pgfmathsetmacro{\PlayerHOffset}{0.2*\PlayerWidth} 9 | 10 | \coordinate (OA) at (0,0); 11 | \coordinate (OB) at (100,0); 12 | \coordinate (OC) at ($(OB) - (0,180)$); 13 | \coordinate (PC) at ($(OB) + (\PlayerHOffset,0.5*\PlayerHeight)$); 14 | \coordinate (TraceStart) at ($(PC) + (16/32*\PlayerWidth,-0.5*\PlayerHeight)$); 15 | \coordinate (TraceEnd) at ($(TraceStart) + (0,-34/72*\PlayerHeight)$); 16 | 17 | \draw[thick] (OA) -- (OB) node[below left]{edge} -- (OC); 18 | 19 | % Draw player hull 20 | \draw[semithick] (PC) node[above]{$\mathbf{r}$} ++(-0.5*\PlayerWidth,-0.5*\PlayerHeight) rectangle ++(\PlayerWidth,\PlayerHeight); 21 | 22 | % Trace start player hull 23 | \draw[semithick,densely dotted,darkRed50] (TraceStart) ++(-0.5*\PlayerWidth,-0.5*\PlayerHeight) rectangle ++(\PlayerWidth,\PlayerHeight); 24 | 25 | % Trace end player hull 26 | \draw[semithick,densely dotted,darkGreen50] (TraceEnd) ++(-0.5*\PlayerWidth,-0.5*\PlayerHeight) rectangle ++(\PlayerWidth,\PlayerHeight); 27 | 28 | \path[darkRed50] (TraceStart) [fill=darkRed50] circle (2) node[below]{$A$}; 29 | \path[darkGreen50] (TraceEnd) [fill=darkGreen50] circle (2) node[below]{$B$}; 30 | \path (TraceEnd) +(0,-0.5*\PlayerHeight) [fill=black] circle (2) node[below]{$C$}; 31 | \path[fill=black] (PC) circle (2); 32 | \end{tikzpicture} 33 | 34 | \end{document} 35 | -------------------------------------------------------------------------------- /imgsrc/entitypunch-1.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/entitypunch-1.ai -------------------------------------------------------------------------------- /imgsrc/explosion-contact-grenades.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/explosion-contact-grenades.ai -------------------------------------------------------------------------------- /imgsrc/explosion-nuking.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \coordinate (O) at (0,0); 7 | \coordinate (Dstart) at (0,70); 8 | \coordinate (Xend) at (300,0); 9 | \coordinate (Yend) at ($3.5*(Dstart)$); 10 | \coordinate (Dend) at ($(O)!0.85!(Xend)$); 11 | \coordinate (DstartHS) at ($3*(Dstart)$); 12 | 13 | \draw[densely dotted,gray20] (Dend) -- (DstartHS -| Dend); 14 | 15 | \foreach \y/\ytext in {1/$D$,2/$2D$,3/$3D$} { 16 | \path ($\y*(Dstart)$) node[left]{\ytext}; 17 | } 18 | 19 | \draw[semithick,<->] (Yend) node[above]{Damage} -- (O) node[below left]{$0$} -- (Xend) node[right]{$r$}; 20 | \draw[darkBlue50] (Dstart) -- node[sloped,very near start,anchor=north west]{normal} (Dend); 21 | \draw[darkRed50] (Dstart) -- node[very near start,anchor=south west]{nuking} (Dstart -| Dend); 22 | 23 | \draw[darkBlue50] (DstartHS) -- node[sloped,very near start,anchor=south west]{normal headshot} (Dend); 24 | \draw[darkRed50] (DstartHS) -- node[very near start,anchor=south west]{nuking headshot} (DstartHS -| Dend); 25 | 26 | \path[fill=black] (Dend) node[below]{$R$}; 27 | \end{tikzpicture} 28 | 29 | \end{document} 30 | -------------------------------------------------------------------------------- /imgsrc/explosion-parts.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\radius}{110} 7 | \pgfmathsetmacro{\smallR}{0.25} 8 | \pgfmathsetmacro{\dotR}{2}; 9 | 10 | \coordinate (O) at (0,0); 11 | \coordinate (circle-right) at (\radius,0); 12 | \coordinate (guide-1) at ($ (O)!0.33!(circle-right) $); 13 | \coordinate (guide-2) at ($ (O)!0.67!(circle-right) $); 14 | 15 | \begin{scope}[shift={(0,\radius + 20)}] 16 | \pgfmathsetmacro{\guideOpacity}{0.7} 17 | 18 | \coordinate (O') at (0,0); 19 | \coordinate (D) at (0,0.4*\radius); 20 | \coordinate (R) at (\radius,0); 21 | 22 | \fill[red20] (D) -- (O') -- (R) -- cycle; 23 | \draw[semithick,<->] (0,0.6*\radius) node[above]{Damage} -- (O') -- (1.3*\radius,0) node[right,align=left]{Distance \\ from \emph{O}}; 24 | 25 | \draw[gray50,opacity=\guideOpacity] (circle-right) -- (R); 26 | \path[fill=gray50] (R) node[above]{\textit{R}} circle (\dotR); 27 | 28 | \draw[gray50,opacity=\guideOpacity] (O) -- (D); 29 | \path[fill=gray50] (D) node[left]{\textit{D}} circle (\dotR); 30 | 31 | \path[name path={damage line}] (D) -- (R); 32 | 33 | \path[name path={guide up 1}] (guide-1) -- (guide-1 |- D); 34 | \path[name intersections={of=damage line and guide up 1}]; 35 | \draw[gray50,opacity=\guideOpacity] (guide-1) -- (intersection-1); 36 | \fill[gray50] (intersection-1) circle (\dotR); 37 | 38 | \path[name path={guide up 2}] (guide-2) -- (guide-2 |- D); 39 | \path[name intersections={of=damage line and guide up 2}]; 40 | \draw[gray50,opacity=\guideOpacity] (guide-2) -- (intersection-1); 41 | \fill[gray50] (intersection-1) circle (\dotR); 42 | 43 | \node[below left=-3] at (O') {0}; 44 | \end{scope} 45 | 46 | \draw[darkRed50] (O) circle (\radius); 47 | \fill[red,opacity=0.1] (O) circle (\radius); 48 | \draw[darkRed50] ($ (O) - (\radius,0) $) arc (180:360:{\radius} and \smallR*\radius); 49 | \draw[darkRed50,densely dotted] ($ (O) + (\radius,0) $) arc (0:180:{\radius} and \smallR*\radius); 50 | 51 | \draw[gray50] (O) -- node[above,black]{\textit{R}} (-\radius,0); 52 | 53 | \fill[gray50] (guide-1) circle (\dotR); 54 | \fill[gray50] (guide-2) circle (\dotR); 55 | \fill[gray50] (circle-right) circle (\dotR); 56 | \fill[black] (O) node[below right]{\textit{O}} circle (\dotR); 57 | \end{tikzpicture} 58 | 59 | \end{document} 60 | -------------------------------------------------------------------------------- /imgsrc/frame_rate_unsync.py: -------------------------------------------------------------------------------- 1 | import plotfonts 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | frate = np.linspace(1, 1000, 100000) 6 | ratio = 1000 / np.floor(1000 / frate) / frate 7 | 8 | plt.figure(figsize=(6, 3)) 9 | plt.plot(frate, ratio, 'k', lw=0.7) 10 | plt.xlim((0, 1000)) 11 | plt.ylim((1, 2)) 12 | plt.xticks([0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]) 13 | plt.yticks([1, 1.5, 2]) 14 | # plt.xscale('log') 15 | plt.xlabel('frame rate') 16 | plt.ylabel('slow-down factor') 17 | plt.grid(True) 18 | plt.tight_layout() 19 | plt.savefig('frame_rate_unsync.png', dpi=220, transparent=True) 20 | plt.show() 21 | -------------------------------------------------------------------------------- /imgsrc/gauss-flowchart.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture}[start chain=main going below,node distance=30 and 180] 6 | \node[fcbase,join] (start) {Start}; 7 | \node[fcbase,join] (init-dir) {$\symbf{d} \gets \symbf{f}$}; 8 | \node[fcbase,join] (init-source) {$\symbf{a} \gets \mathit{gun\ position}$}; 9 | \node[fcbase,join] (init-dest) {$\symbf{b} \gets \symbf{a} + 8192 \symbf{d}$}; 10 | \node[fcbase,join] (init-ignore) {$I \gets \mathit{player}$}; 11 | \node[fcbase,join] (init-has-punched) {$\mathit{punched} \gets 0$}; 12 | \node[fcbase,join] (init-damage) {$D \gets \mathit{initial\ damage}$}; 13 | \node[fcbase,join] (init-i) {$i \gets 10$}; 14 | \node[fcbase,join] (loop-condition) {$\left(D > 10 \land i > 0\right)$?}; 15 | \node[fcbase,join] (decrement-i) {$i \gets i - 1$}; 16 | \node[fcbase,join] (trace-1) {$\left(\symbf{e}_1, \mathit{ent}, \symbf{n}\right) \gets \operatorname{Tr}(\symbf{a}, \symbf{b}, I)$}; 17 | \node[fcbase,join] (all-solid-1) {All solid?}; 18 | 19 | \node[fcbase,join] (entity-is-null) {$(\mathit{ent} = \emptyset)$?}; 20 | \node[fcbase,join] (damageable) {Damageable?}; 21 | \node[fcbase,join] (reflectable) {Reflectable?}; 22 | 23 | \node[fcbase,join] (set-ignore-1) {$I \gets \emptyset$}; 24 | \node[fcbase,join] (compute-a) {$p \gets -\symbf{n} \cdot \symbf{d}$}; 25 | \node[fcbase,join] (a-lt-0_5) {$\left(p < 0.5\right)$?}; 26 | 27 | \node[fcbase,join] (has-punched) {$\left(\mathit{punched} = 1\right)$?}; 28 | \node[fcbase,join] (set-punched) {$\mathit{punched} \gets 1$}; 29 | \node[fcbase,join] (primary-fire) {Primary fire?}; 30 | \node[fcbase,join] (trace-2) {$\symbf{e}_2 \gets \operatorname{Tr}(\symbf{e}_1 + 8\symbf{d}, \symbf{b}, I)$}; 31 | \node[fcbase,join] (all-solid-2) {All solid?}; 32 | \node[fcbase,join] (trace-3) {$\symbf{e}_3 \gets \operatorname{Tr}(\symbf{e}_2, \symbf{e}_1, I)$}; 33 | \node[fcbase,join] (compute-a-dist) {$r \gets \lVert \symbf{e}_3 - \symbf{e}_1 \rVert$}; 34 | \node[fcbase,join] (a-lt-D) {$\left(r < D\right)$?}; 35 | 36 | \node[fcbase,join] (a-eq-0) {$\left(r = 0\right)$?}; 37 | \node[fcbase,join] (reduce-damage) {$D \gets D - r$}; 38 | \node[fcbase,join] (radius-damage) {$\operatorname{RadDmg}(\symbf{e}_3 + 8\symbf{d}, D)$}; 39 | \node[fcbase,join] (update-source-1) {$\symbf{a} \gets \symbf{e}_3 + \symbf{d}$}; 40 | 41 | \node[fcbase,on grid,left=of reflectable] (update-source-not-refl) {$\symbf{a} \gets \symbf{e}_1 + \symbf{d}$}; 42 | \node[fcbase,join] (update-ignore-not-refl) {$I \gets \mathit{ent}$}; 43 | 44 | \node[fcbase,on grid,left=of damageable] (apply-damage) {Dmg $\mathit{ent}$ by $D$}; 45 | 46 | \node[fcbase,on grid,right=of loop-condition] (break-1) {Stop}; 47 | 48 | \node[fcbase,on grid,left=of a-eq-0] (a-set-1) {$p \gets 1$}; 49 | 50 | \node[fcbase,on grid,left=of a-lt-0_5] (update-dir-refl) {$\symbf{d} \gets \symbf{d} + 2p\symbf{n}$}; 51 | \node[fcbase,join] (update-source-refl) {$\symbf{a} \gets \symbf{e}_1 + 8\symbf{d}$}; 52 | \node[fcbase,join] (update-dest-refl) {$\symbf{b} \gets \symbf{a} + 8192\symbf{d}$}; 53 | \node[fcbase,join] (reflect-damage) {$\operatorname{RadDmg}(\symbf{e}_1, pD)$}; 54 | \node[fcbase,join] (update-damage-refl) {$D \gets D \left(1 - p\right)$}; 55 | 56 | % Coordinates 57 | 58 | \coordinate (loop-back-bend) at ($ (update-source-1) - (280,0) $); 59 | 60 | % Drawing lines 61 | 62 | \draw[->] (damageable.west) -- node[yesno,above]{yes} (apply-damage); 63 | \path (damageable) to node[yesno,right]{no} (reflectable); 64 | \draw[->] (apply-damage) -- (reflectable); 65 | 66 | \draw[->] (reflectable.west) -- node[yesno,below]{no} (update-source-not-refl); 67 | \path (reflectable) to node[yesno,right]{yes} (set-ignore-1); 68 | 69 | \draw[->] (a-lt-0_5.west) -- node[yesno,above]{yes} (update-dir-refl); 70 | \path (a-lt-0_5) to node[yesno,right]{no} (has-punched); 71 | \draw[->] (update-damage-refl.west) -- (loop-back-bend |- update-damage-refl); 72 | 73 | \draw[->] (update-ignore-not-refl.west) -- (update-ignore-not-refl -| loop-back-bend); 74 | 75 | \draw[->] (all-solid-2.east) -| (break-1); 76 | \path (all-solid-2) to node[yesno,above]{yes} (all-solid-2 -| break-1); 77 | \path (all-solid-2) to node[yesno,right]{no} (trace-3); 78 | \draw[->] (primary-fire) -- node[yesno,above]{yes} (primary-fire -| break-1); 79 | \path (primary-fire) to node[yesno,right]{no} (trace-2); 80 | \draw[->] (has-punched) -- node[yesno,above]{yes} (has-punched -| break-1); 81 | \path (has-punched) to node[yesno,right]{no} (set-punched); 82 | \draw[->] (entity-is-null) -- node[yesno,above]{yes} (entity-is-null -| break-1); 83 | \path (entity-is-null) to node[yesno,right]{no} (damageable); 84 | \draw[->] (all-solid-1) -- node[yesno,above]{yes} (all-solid-1 -| break-1); 85 | \path (all-solid-1) to node[yesno,right]{no} (entity-is-null); 86 | \draw[->] (loop-condition) -- node[yesno,above]{no} (break-1); 87 | \path (loop-condition) to node[yesno,right]{yes} (decrement-i); 88 | 89 | \draw[->] (a-lt-D) -- node[yesno,above]{no} (a-lt-D -| loop-back-bend); 90 | \path (a-lt-D) to node[yesno,right]{yes} (a-eq-0); 91 | 92 | \draw[->] (a-eq-0) -- node[yesno,above]{yes} (a-set-1); 93 | \path (a-eq-0) to node[yesno,right]{no} (reduce-damage); 94 | \draw[->] (a-set-1) -- (reduce-damage.west); 95 | 96 | \draw[->] (update-source-1) -- (loop-back-bend) |- (loop-condition); 97 | \end{tikzpicture} 98 | 99 | \end{document} 100 | -------------------------------------------------------------------------------- /imgsrc/handgrenade-vel-1.py: -------------------------------------------------------------------------------- 1 | import plotfonts 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | def gpitch(pitch): 6 | if pitch < 0: 7 | return -10 + 8 / 9 * pitch 8 | return -10 + 10 / 9 * pitch 9 | 10 | # pitch is in degrees 11 | def gvel(pitch): 12 | gp = gpitch(pitch) 13 | scale = min(500, 360 - 4 * gp) 14 | gp = np.deg2rad(gp) 15 | return [scale * np.cos(gp), -scale * np.sin(gp)] 16 | 17 | pitch = np.linspace(-180, 180, 10000) 18 | gvels = [] 19 | for p in pitch: 20 | gvels.append(gvel(p)) 21 | gvels = np.array(gvels) 22 | 23 | plt.figure(figsize=(8.5, 5.5)) 24 | plt.axes().set_aspect('equal') 25 | plt.plot(gvels[:, 0], gvels[:, 1], 'k') 26 | 27 | for pitch, offs in [[-180, (10, 5)], [-135, (10, -10)], [-90, (0, -30)], 28 | [-60, (10, 10)], [-28.125, (-150, -10)], [0, (15, 0)], 29 | [30, (15, -20)], [60, (-70, -15)], [90, (-70, 0)], 30 | [120, (-70, 20)], [150, (15, 15)], [180, (15, 0)]]: 31 | vel = gvel(pitch) 32 | plt.plot(vel[0], vel[1], 'ok') 33 | paren = '' if pitch != -180 else 'player pitch = ' 34 | plt.text(vel[0] + offs[0], vel[1] + offs[1], fr'{paren}{pitch}°'.replace('-', '−'), verticalalignment='center') 35 | 36 | plt.xlim((-550, 500)) 37 | plt.xticks(np.arange(-500, 501, 100)) 38 | plt.xlabel('relative horizontal velocity') 39 | plt.ylabel('relative vertical velocity') 40 | plt.grid() 41 | # plt.tight_layout(pad=0, w_pad=0, h_pad=0) 42 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 43 | plt.savefig('handgrenade-vel-1.pdf', transparent=True) 44 | -------------------------------------------------------------------------------- /imgsrc/headcrab-jump.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\hcW}{50} 7 | \pgfmathsetmacro{\eneH}{150} 8 | \pgfmathsetmacro{\eneW}{66} 9 | \pgfmathsetmacro{\eneOfs}{30} 10 | \pgfmathsetmacro{\dimLen}{15} 11 | 12 | \coordinate (O) at (0,0); 13 | \coordinate (A) at (310,0); 14 | \coordinate (HC) at (50,5); 15 | \coordinate (HCcentre) at ($ (HC) + 0.5*(\hcW,\hcW) $); 16 | \coordinate (ENE) at (200,0); 17 | \coordinate (ENEcentre) at ($ (ENE) + 0.5*(\eneW,\eneH) $); 18 | \coordinate (ENEvofs) at ($ (ENEcentre) + (0,\eneOfs) $); 19 | 20 | \coordinate (dimA) at ($ (HC) + (\hcW+5,0) $); 21 | \coordinate (dimB) at ($ (dimA) + (40,0) $); 22 | 23 | \node[right,yshift=5] at (dimB) {$1$}; 24 | 25 | \draw[thick] (O) -- (A); 26 | 27 | \node[below] at (HCcentre |- O) {Headcrab}; 28 | \node[below] at (ENEcentre |- O) {Enemy}; 29 | 30 | \draw (HC) rectangle +(\hcW,\hcW); 31 | \draw (ENE) rectangle +(\eneW,\eneH); 32 | 33 | \draw (HCcentre) -- +(-0.5*\hcW-20,0) node[left]{$C_H$}; 34 | \draw (ENEcentre) -- +(0.5*\eneW+20,0) node[right]{$C_E$}; 35 | \draw (ENEvofs) -- +(0.5*\eneW+20,0) node[right]{$V$}; 36 | 37 | \draw[densely dotted] (HCcentre) -- (ENEvofs); 38 | \draw[semithick,->] (HCcentre) parabola[bend at end] (ENEvofs); 39 | 40 | \draw (dimA) -- (dimB); 41 | \draw[<-] (dimB) ++(-10,0) -- ++(0,\dimLen); 42 | \draw[<-] (dimB |- O) ++(-10,0) -- ++(0,-\dimLen); 43 | 44 | \fill[black] (HCcentre) circle (\DotRadius); 45 | \fill[black] (ENEcentre) circle (\DotRadius); 46 | \fill[black] (ENEvofs) circle (\DotRadius); 47 | \end{tikzpicture} 48 | 49 | \end{document} -------------------------------------------------------------------------------- /imgsrc/hlprlogo-favicon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/hlprlogo-favicon.ai -------------------------------------------------------------------------------- /imgsrc/hlprlogo-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/hlprlogo-favicon.png -------------------------------------------------------------------------------- /imgsrc/hlprlogo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/hlprlogo.ai -------------------------------------------------------------------------------- /imgsrc/jumpbug-bbox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/jumpbug-bbox.pdf -------------------------------------------------------------------------------- /imgsrc/jumpbug-bbox.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\ongroundThickness}{15} 7 | \pgfmathsetmacro{\groundlen}{400} 8 | \pgfmathsetmacro{\groundheight}{50} 9 | \pgfmathsetmacro{\bboxscale}{2} 10 | 11 | \tikzset{ 12 | pics/bboxrect/.style args={#1}{ 13 | code={ 14 | \fill[opacity=0.2] (-16*\bboxscale,0) rectangle +(32*\bboxscale,#1*\bboxscale); 15 | \draw (-16*\bboxscale,0) rectangle +(32*\bboxscale,#1*\bboxscale); 16 | \fill[black] +(0,#1*0.5*\bboxscale) coordinate (bbox-center) circle (2); 17 | }, 18 | }, 19 | } 20 | 21 | \coordinate (O) at (0,0); 22 | \coordinate (O') at (0,\ongroundThickness); 23 | \coordinate (P) at (0,-\groundheight); 24 | \coordinate (G) at (\groundlen,0); 25 | \coordinate (G') at (\groundlen,\ongroundThickness); 26 | \coordinate (Q) at (\groundlen,-\groundheight); 27 | 28 | \coordinate (A') at ($ (O)!0.25!(G) $); 29 | 30 | \fill[brown20] (O) -- (G) -- (Q) -- (P) -- cycle; 31 | \fill[green20] (O) -- (G) -- (G') -- (O') -- cycle; 32 | \draw[thick,brown80] (O) -- (G); 33 | \draw[green50] (O') -- (G'); 34 | 35 | \draw[<->] (O) -- node[left]{2} (O'); 36 | 37 | \draw[gray50] (A') +(0,80) pic {bboxrect=36}; 38 | 39 | \coordinate (A) at (bbox-center); 40 | \coordinate (B') at ($ (A') + (120,0.5*\ongroundThickness) $); 41 | \draw (A) node[above]{\emph{A}}; 42 | \draw (A) node[above=20*\bboxscale]{\emph{Ducked}}; 43 | 44 | \draw[gray50] (B') pic {bboxrect=72}; 45 | 46 | \coordinate (B) at (bbox-center); 47 | \coordinate (C') at ($ (B' |- O) + (70,0) $); 48 | \draw (B) node[above]{\emph{B}}; 49 | \draw (B) node[above=38*\bboxscale]{\verb|-duck|}; 50 | 51 | \draw[->] (A) -- (B); 52 | 53 | \draw[gray50] (C') pic {bboxrect=72}; 54 | \coordinate (C) at (bbox-center); 55 | \draw (C) node[above=38*\bboxscale]{\verb|+jump|}; 56 | \draw[->] (C) node[below]{\emph{C}} -- +(0,30) node[above]{$v_z$}; 57 | \end{tikzpicture} 58 | 59 | \end{document} 60 | -------------------------------------------------------------------------------- /imgsrc/ladder-angles-1.py: -------------------------------------------------------------------------------- 1 | import plotfonts 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | def ladder_yaw(alpha, S, F, vz): 6 | nx = np.cos(alpha) 7 | nz = np.sin(alpha) 8 | return np.arctan2(-np.copysign(1, S * vz), -np.copysign(1, F * vz) * np.sqrt(2 * nz * nx)) 9 | 10 | def ladder_pitch(alpha, S, F, vz): 11 | nx = np.cos(alpha) 12 | nz = np.sin(alpha) 13 | return -np.copysign(1, F * vz * (1 / np.sqrt(2) - nz)) * np.arccos(np.sqrt(2 * nz * nx)) 14 | 15 | alphas = np.linspace(0, np.pi / 2, 100000) 16 | yaws = ladder_yaw(alphas, 200, 200, 1) 17 | pitches = ladder_pitch(alphas, 200, 200, 1) 18 | 19 | yaws = np.rad2deg(yaws) 20 | pitches = np.rad2deg(pitches) 21 | zipped = np.stack((yaws, pitches)) 22 | 23 | #plt.figure(figsize=(6, 3)) 24 | plt.figure(figsize=(2.5, 5)) 25 | plt.axes().set_aspect('equal') 26 | plt.plot(zipped[0], zipped[1], 'k') 27 | 28 | for a in [5, 15, 30, 45, 60, 75, 85]: 29 | alpha = np.deg2rad(a) 30 | yaw = np.rad2deg(ladder_yaw(alpha, 200, 200, 1)) 31 | pitch = np.rad2deg(ladder_pitch(alpha, 200, 200, 1)) 32 | plt.plot(yaw, pitch, 'ok') 33 | plt.text(yaw + 5, pitch, rf'α = {a}', verticalalignment='center') 34 | 35 | plt.xlim((-150, -90)) 36 | plt.ylim((-90, 90)) 37 | plt.xticks([-150, -130, -110, -90]) 38 | plt.yticks(np.arange(-90, 91, 30)) 39 | plt.xlabel('yaw') 40 | plt.ylabel('pitch') 41 | plt.grid() 42 | plt.tight_layout() 43 | plt.savefig('ladder-angles-1.png', dpi=200, transparent=True) 44 | plt.show() 45 | -------------------------------------------------------------------------------- /imgsrc/multigauss-1.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/multigauss-1.ai -------------------------------------------------------------------------------- /imgsrc/player-hulls.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture}[scale=3] 6 | \pgfmathsetmacro{\s}{32} 7 | \pgfmathsetmacro{\hS}{72} 8 | \pgfmathsetmacro{\hD}{36} 9 | 10 | \tikzset{ 11 | pics/cuboid/.style args={#1/#2}{ 12 | code={ 13 | \coordinate (A) at (0,0,0); 14 | \coordinate (B) at (#1,0,0); 15 | \coordinate (C) at (#1,0,#1); 16 | \coordinate (D) at (0,0,#1); 17 | \coordinate (E) at ($ (A) + (0,#2,0) $); 18 | \coordinate (F) at ($ (B) + (0,#2,0) $); 19 | \coordinate (G) at ($ (C) + (0,#2,0) $); 20 | \coordinate (H) at ($ (D) + (0,#2,0) $); 21 | 22 | \draw[gray50,fill=gray20] (A) -- (B) -- (C) -- (D) -- cycle; 23 | \draw[gray50,fill=gray5] (A) -- (B) -- (F) -- (E) -- cycle; 24 | \draw[gray50,fill=gray5] (A) -- (D) -- (H) -- (E) -- cycle; 25 | \draw[gray50,fill=gray5,opacity=0.7] (B) -- (C) -- (G) -- (F) -- cycle; 26 | \draw[gray50,fill=gray5,opacity=0.7] (E) -- (F) -- (G) -- (H) -- cycle; 27 | \draw[gray50,fill=gray5,opacity=0.7] (C) -- (D) -- (H) -- (G) -- cycle; 28 | 29 | \path (E) -- node[midway,above]{$#1$} (F); 30 | \path (B) -- node[midway,right]{$#2$} (F); 31 | \path (H) -- node[midway,above left=-2]{$#1$} (E); 32 | }, 33 | }, 34 | } 35 | 36 | \path (0,0,0) pic[scale=3] {cuboid=\s/\hS}; 37 | \path (70,0,0) pic[scale=3] {cuboid=\s/\hD}; 38 | \end{tikzpicture} 39 | 40 | \end{document} 41 | -------------------------------------------------------------------------------- /imgsrc/player_hp.py: -------------------------------------------------------------------------------- 1 | import plotfonts 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | H = 100. 6 | A = 40. 7 | 8 | dmg = np.linspace(0, 200, 4000) 9 | armour = np.array([max(0, A - 2 * d / 5) for d in dmg]) 10 | health = H - np.array([int(d / 5) if a != 0 else int(d - 2 * A) for a, d in zip(armour, dmg)]) 11 | 12 | plt.figure(figsize=(5, 4)) 13 | plt.plot(dmg, health, 'r', label='Health') 14 | plt.plot(dmg, armour, 'b', label='Armour') 15 | plt.xlim((0, dmg[-1])) 16 | plt.xlabel('Damage') 17 | plt.grid(True) 18 | plt.legend(loc=0) 19 | plt.tight_layout() 20 | plt.savefig('player_hp.png', dpi=200, transparent=True) 21 | plt.show() 22 | -------------------------------------------------------------------------------- /imgsrc/plotfonts/__init__.py: -------------------------------------------------------------------------------- 1 | import cycler 2 | import matplotlib 3 | 4 | matplotlib.rcParams['font.family'] = 'Baskerville 10 Pro' 5 | matplotlib.rcParams['font.weight'] = 'normal' 6 | matplotlib.rcParams['font.size'] = 16 7 | matplotlib.rcParams['axes.prop_cycle'] = cycler.cycler(color=[(0.7, 0, 0), (0, 0, 0.7)]) 8 | -------------------------------------------------------------------------------- /imgsrc/reflection-bypass.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\groundWidth}{200} 7 | \pgfmathsetmacro{\entThickness}{10} 8 | \pgfmathsetmacro{\entHeight}{110} 9 | \pgfmathsetmacro{\hitOffset}{20} 10 | \pgfmathsetmacro{\hitAngle}{30} 11 | \pgfmathsetmacro{\arrowLen}{80} 12 | \pgfmathsetmacro{\skipLen}{40} 13 | 14 | \coordinate (O) at (0,0); 15 | \coordinate (A) at (\groundWidth,0); 16 | \coordinate (midOA) at ($ (O)!0.5!(A) $); 17 | \coordinate (entA) at ($ (midOA) - (0.5*\entThickness,0) $); 18 | \coordinate (entB) at ($ (midOA) + (0.5*\entThickness,\entHeight) $); 19 | \coordinate (inc-end) at ($ (midOA) + (\hitOffset,0) $); 20 | \coordinate (inc-start) at ($ (inc-end) + (\hitAngle:\arrowLen) $); 21 | \coordinate (out-start) at ($ (inc-end) + (180-\hitAngle:\skipLen) $); 22 | \coordinate (out-end) at ($ (out-start) + (180-\hitAngle:\arrowLen) $); 23 | 24 | \draw (entA) rectangle (entB); 25 | \node[above] at ($ (midOA) + (0,\entHeight) $) {Entity}; 26 | \draw[thick] (O) -- (A); 27 | 28 | \draw[semithick,|->] (inc-start) -- node[near start,sloped,above=3,align=right]{Gauss \\[-5bp] beam} (inc-end); 29 | \draw[densely dotted] (inc-end) -- (out-start); 30 | \draw[semithick,|->] (out-start) -- (out-end); 31 | 32 | \draw ($ (out-start)!10bp!90:(out-end) $) -- ($ (out-start)!50bp!90:(out-end) $); 33 | \draw ($ (inc-end)!6bp!90:(out-end) $) -- ($ (inc-end)!50bp!90:(out-end) $); 34 | \draw[<->] ($ (out-start)!40bp!90:(out-end) $) -- node[below left=-3]{$8$} ($ (inc-end)!40bp!90:(out-end) $); 35 | \end{tikzpicture} 36 | 37 | \end{document} 38 | -------------------------------------------------------------------------------- /imgsrc/shotgun-reload.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | \usepackage{ifthen} 3 | 4 | \begin{document} 5 | 6 | \begin{tikzpicture} 7 | \pgfmathsetmacro{\GuidelineLen}{300} 8 | \pgfmathsetmacro{\TickGap}{50} 9 | \pgfmathsetmacro{\SignalTickClearance}{2} 10 | \pgfmathsetmacro{\SignalHeight}{40} 11 | \pgfmathsetmacro{\SignalAxisGap}{2} 12 | \pgfmathsetmacro{\TickHeight}{6} 13 | 14 | \coordinate (O) at (0,0); 15 | \coordinate (T) at ($(O) + (\GuidelineLen,0)$); 16 | 17 | % Axis and ticks 18 | \draw[thick,->] (O) -- (T) node[right]{$t$}; 19 | \foreach \x/\xtext in {0/0,1/\frac{1}{2},2/1,3/{1\frac{1}{2}},4/2,5/{2\frac{1}{2}}} { 20 | \draw (\x*\TickGap,-\TickHeight) node[below]{$\xtext$} -- +(0,\TickHeight); 21 | } 22 | 23 | % First second signal 24 | \draw[yshift=\SignalAxisGap,darkBlue50,fill=blue20] 25 | (0,0) -- ++(0,\SignalHeight) -- ++(2*\TickGap,0) -- ++(0,-\SignalHeight); 26 | 27 | % Subsequent signals 28 | \foreach \x in {2,3,4} { 29 | \draw[yshift=\SignalAxisGap,darkRed50,fill=red20] 30 | (\x*\TickGap+\SignalTickClearance,0) -- ++(0,\SignalHeight) -- 31 | ++(\TickGap-\SignalTickClearance,0) -- ++(0,-\SignalHeight); 32 | } 33 | 34 | \pgfmathsetmacro{\ShellGraphYShift}{\SignalHeight+30} 35 | \pgfmathsetmacro{\ShellStartHeight}{10} 36 | \pgfmathsetmacro{\ShellIncHeight}{20} 37 | \pgfmathsetmacro{\VertAxisHeight}{90} 38 | 39 | \begin{scope}[yshift=\ShellGraphYShift] 40 | \coordinate (shellS) at (0,\VertAxisHeight); 41 | \coordinate (shellO) at (0,0); 42 | \coordinate (shellT) at ($(shellO) + (\GuidelineLen,0)$); 43 | 44 | \draw[thick,<->] (shellS) -- (shellO) -- (shellT) node[right]{$t$}; 45 | \draw (0,\ShellStartHeight) -- ++(3*\TickGap,0) -- ++(0,\ShellIncHeight) 46 | -- ++(\TickGap,0) -- ++(0,\ShellIncHeight) -- ++(\TickGap,0) 47 | -- ++(0,\ShellIncHeight) -- ++(\TickGap,0); 48 | 49 | \foreach \x in {1,2,3,4,5} { 50 | \draw (\x*\TickGap,-\TickHeight) -- +(0,\TickHeight); 51 | } 52 | \foreach \y/\shellText in {0/5,1/6,2/7,3/8} { 53 | \draw (-\TickHeight,\ShellStartHeight+\y*\ShellIncHeight) node[left]{$\shellText$} -- ++(\TickHeight,0); 54 | \ifthenelse{\y=0}{}{ 55 | \draw[densely dotted,gray50] (2,\ShellStartHeight+\y*\ShellIncHeight) -- ++(2*\TickGap+\y*\TickGap-4,0); 56 | } 57 | } 58 | \end{scope} 59 | 60 | \begin{scope}[yshift=\ShellGraphYShift+\VertAxisHeight+20] 61 | \coordinate (shellS) at (0,\VertAxisHeight); 62 | \coordinate (shellO) at (0,0); 63 | \coordinate (shellT) at ($(shellO) + (\GuidelineLen,0)$); 64 | \pgfmathsetmacro{\ReloadArrowStart}{\VertAxisHeight+5} 65 | 66 | \draw[thick,<->] (shellS) node[above]{Shells} -- (shellO) -- (shellT) node[right]{$t$}; 67 | \draw (0,\ShellStartHeight) -- ++(2*\TickGap+\SignalAxisGap,0) -- ++(0,\ShellIncHeight) 68 | -- ++(\TickGap,0) -- ++(0,\ShellIncHeight) -- ++(\TickGap,0) 69 | -- ++(0,\ShellIncHeight) -- ++(2*\TickGap-\SignalAxisGap,0); 70 | 71 | \foreach \x in {1,2,3,4,5} { 72 | \draw (\x*\TickGap,-\TickHeight) -- +(0,\TickHeight); 73 | } 74 | \foreach \y/\shellText in {0/5,1/6,2/7,3/8} { 75 | \draw (-\TickHeight,\ShellStartHeight+\y*\ShellIncHeight) node[left]{$\shellText$} -- ++(\TickHeight,0); 76 | \ifthenelse{\y=0}{}{ 77 | \draw[densely dotted,gray50] (2,\ShellStartHeight+\y*\ShellIncHeight) -- ++(\TickGap+\y*\TickGap-2,0); 78 | } 79 | } 80 | 81 | \foreach \x in {2,3,4} { 82 | \draw[semithick,->] (\x*\TickGap+\SignalTickClearance,\ReloadArrowStart) 83 | -- ++(0,-\ReloadArrowStart+\ShellStartHeight+\x*\ShellIncHeight-\ShellIncHeight+2); 84 | } 85 | \node[above] at (2*\TickGap,\ReloadArrowStart) {$R$}; 86 | \node[above] at (3*\TickGap,\ReloadArrowStart) {$R$}; 87 | \node[above] at (4*\TickGap,\ReloadArrowStart) {$R$}; 88 | \end{scope} 89 | \end{tikzpicture} 90 | 91 | \end{document} 92 | -------------------------------------------------------------------------------- /imgsrc/simple_gauss_boosts.py: -------------------------------------------------------------------------------- 1 | import plotfonts 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from matplotlib.animation import FuncAnimation 5 | 6 | def compute_times_speeds(times): 7 | dt = times[1] - times[0] 8 | new_times = np.array([0]) 9 | speeds = np.array([0]) 10 | for time in times[1:]: 11 | new_times = np.append(new_times, time) 12 | new_times = np.append(new_times, time) 13 | speeds = np.append(speeds, speeds[-1]) 14 | speeds = np.append(speeds, speeds[-1] + dt * 250) 15 | return new_times[:-1], speeds[:-1] 16 | 17 | def init(): 18 | ax.set_xlim(0, 8) 19 | ax.set_ylim(0, 2000) 20 | return ln, guideline 21 | 22 | def update(frame): 23 | xdata, ydata = compute_times_speeds(np.linspace(0, TIME_MAX, frame)) 24 | ln.set_data(xdata, ydata) 25 | return ln, guideline 26 | 27 | TIME_MAX = 8 28 | 29 | fig, ax = plt.subplots() 30 | fig.set_figwidth(6) 31 | fig.set_figheight(4) 32 | xdata, ydata = [], [] 33 | ln, = plt.plot([], [], animated=True, label='Discrete boosting') 34 | guideline, = ax.plot([0, 8], [0, 2000], 'gray', animated=True, label='Continuous boosting') 35 | 36 | ax.grid(True) 37 | init() 38 | 39 | plt.xlabel('Time (s)') 40 | plt.ylabel('Horizontal speed (ups)') 41 | plt.tight_layout() 42 | plt.legend(loc=0) 43 | 44 | ani = FuncAnimation(fig, update, frames=np.arange(3, 18), 45 | init_func=init, blit=True) 46 | ani.save('simple_gauss_boosts.gif', dpi=110, writer='imagemagick') 47 | plt.show() 48 | -------------------------------------------------------------------------------- /imgsrc/sloped-plane-jump.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | \usepackage{ifthen} 3 | 4 | \begin{document} 5 | 6 | \begin{tikzpicture} 7 | \pgfmathsetmacro{\PlaneLen}{400} 8 | \pgfmathsetmacro{\PlaneAng}{35} 9 | \pgfmathsetmacro{\InitVAng}{80} 10 | 11 | \coordinate (O) at (0,0); 12 | \coordinate (plane-end) at (\PlaneAng:\PlaneLen); 13 | \coordinate (vv-start) at (O |- plane-end); 14 | \coordinate (h-end) at (O -| plane-end); 15 | 16 | \draw[name path=vvel-guide] (O) -- (vv-start) node[left]{$Z$} -- (plane-end) -- (h-end) node[right]{$H$}-- cycle; 17 | \draw (O) node[left]{$O$} -- (plane-end) node[right]{$C$}; 18 | 19 | \path[name path=initial-ray] (O) -- (\InitVAng:\PlaneLen |- plane-end); 20 | \path[name intersections={of=vvel-guide and initial-ray}]; 21 | 22 | \coordinate (V) at (intersection-2); 23 | \foreach \i in {1,...,20} { 24 | \coordinate (V') at ($(O)!(V)!(plane-end)$); 25 | \coordinate (V'p) at (V' |- O); 26 | \ifthenelse{\i < 4}{ 27 | \draw (O) -- (V); 28 | \draw (V') -- (V'p); 29 | \path (V) node[above]{$V_\i$}; 30 | \path (V') node[below right]{$V_\i'$}; 31 | \path (V'p) node[below]{$V_\i''$}; 32 | }{} 33 | \ifthenelse{\i = 4}{ 34 | \path (V) node[above=5]{$\cdots$}; 35 | \path (O) -- node[below right=10,sloped,at end]{$\cdots$} (V'); 36 | \path (V'p) node[below=7]{$\cdots$}; 37 | }{} 38 | \draw (V) -- (V'); 39 | \draw (V'p) circle (1); 40 | \coordinate (V) at (V' |- vv-start); 41 | \draw (V') -- (V); 42 | } 43 | \end{tikzpicture} 44 | 45 | \end{document} 46 | -------------------------------------------------------------------------------- /imgsrc/stairparts.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/stairparts.ai -------------------------------------------------------------------------------- /imgsrc/strafe-framerate.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | def strafe_maxaccel(speed, L, tau, M, A): 6 | tauMA = tau * M * A 7 | LtauMA = L - tauMA 8 | if LtauMA <= 0: 9 | return math.sqrt(speed * speed + L * L) 10 | elif LtauMA <= speed: 11 | return math.sqrt(speed * speed + tauMA * (L + LtauMA)) 12 | else: 13 | return speed + tauMA 14 | 15 | speed = 3000 16 | framerates = np.linspace(1, 1000, 1000) 17 | newspeeds = [] 18 | for fr in framerates: 19 | newspeeds.append(strafe_maxaccel(speed, 30, 1 / fr, 320, 10)) 20 | newspeeds = np.array(newspeeds) 21 | accels = (newspeeds - speed) * framerates 22 | 23 | plt.plot(framerates, accels) 24 | plt.grid() 25 | plt.show() 26 | -------------------------------------------------------------------------------- /imgsrc/strafe-plots.py: -------------------------------------------------------------------------------- 1 | import plotfonts 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from matplotlib.animation import FuncAnimation 5 | 6 | def strafe_speed(v, L, tauMA, theta): 7 | gamma1 = tauMA 8 | gamma2 = L - v * np.cos(theta) 9 | if gamma2 < 0: 10 | return v 11 | mu = min(gamma1, gamma2) 12 | return np.sqrt(v * v + mu * mu + 2 * v * mu * np.cos(theta)) 13 | 14 | def opt_theta(v, L, tauMA): 15 | tmp = L - tauMA 16 | if tmp < 0: 17 | return np.pi 18 | if tmp < v: 19 | return np.arccos(tmp / v) 20 | return 0 21 | 22 | def genpoints(v, L, tau, MA): 23 | tauMA = tau * MA 24 | thetas = np.linspace(0, 2 * np.pi, 4000) 25 | speeds = np.array([strafe_speed(v, L, tauMA, theta) for theta in thetas]) 26 | accels = (speeds - v) / tau 27 | return thetas, accels 28 | 29 | def update_line(frame, line, optline1, optline2, dots, text): 30 | L = 30 31 | tau = 0.001 32 | MA = 320 * 10 33 | 34 | v = frame 35 | thetas, accels = genpoints(v, L, tau, MA) 36 | line.set_data(thetas, accels) 37 | optang = opt_theta(v, L, tau * MA) 38 | optline1.set_data([optang] * 2, [0, 4000]) 39 | optline2.set_data([-optang] * 2, [0, 4000]) 40 | dots.set_data([optang, -optang], [4000] * 2) 41 | text.set_text(f'{int(v)} ups') 42 | return line, optline1, optline2, dots, text 43 | 44 | # thetas, accels = genpoints(200, 30, 0.01, 320 * 10) 45 | # plt.polar(thetas, accels) 46 | 47 | fig = plt.figure(figsize=(3.5, 3.5)) 48 | 49 | line, = plt.polar([], [], color='darkblue', label='Acceleration') 50 | optline1, = plt.polar([], [], color='gray', dashes=[5, 3], lw=1) 51 | optline2, = plt.polar([], [], color='gray', dashes=[5, 3], lw=1) 52 | dots, = plt.polar([], [], 'o', color='darkred') 53 | text = plt.text(np.deg2rad(70), 4500, '', color='darkblue') 54 | 55 | plt.ylim((-4000, 4000)) 56 | # plt.yticks([-4000, -2000, 0, 2000, 4000]) 57 | plt.yticks([0, 4000, -4000]) 58 | 59 | line_ani = FuncAnimation( 60 | fig, update_line, 80, 61 | fargs=(line, optline1, optline2, dots, text), 62 | interval=200, blit=False) 63 | line_ani.save('strafe-plots.gif', dpi=110, writer='imagemagick') 64 | 65 | plt.tight_layout() 66 | plt.show() 67 | -------------------------------------------------------------------------------- /imgsrc/strafing-intuition-1.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/strafing-intuition-1.ai -------------------------------------------------------------------------------- /imgsrc/templates/hlpr-flowchart.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/templates/hlpr-flowchart.graffle -------------------------------------------------------------------------------- /imgsrc/templates/hlpr.ait: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/imgsrc/templates/hlpr.ait -------------------------------------------------------------------------------- /imgsrc/templates/hlprtikz.cls: -------------------------------------------------------------------------------- 1 | \ProvidesClass{hlprtikz}[2018/07/22 version 1.0 HLPR Tikz Template] 2 | \NeedsTeXFormat{LaTeX2e} 3 | 4 | \newif\ifhlpr@smallerfont 5 | \DeclareOption{smaller}{\hlpr@smallerfonttrue} 6 | \ProcessOptions\relax 7 | 8 | \LoadClass[tikz]{standalone} 9 | 10 | \RequirePackage{amsmath} 11 | \RequirePackage{fontspec} 12 | \RequirePackage{unicode-math} 13 | 14 | \setmainfont{Baskerville 10 Pro}[Numbers=Proportional] 15 | \setmonofont{Input Mono} 16 | 17 | \ifhlpr@smallerfont 18 | \setmathfont[ 19 | SizeFeatures={ 20 | {Size=-8.2, Script=Math, Style=MathScriptScript}, 21 | {Size=8.2-12.2, Script=Math, Style=MathScript}, 22 | {Size=12.2-, Script=Math}, 23 | }, 24 | ]{Dark Modern Math Regular} 25 | \else 26 | \setmathfont[ 27 | SizeFeatures={ 28 | {Size=-10.2, Script=Math, Style=MathScriptScript}, 29 | {Size=10.2-14.5, Script=Math, Style=MathScript}, 30 | {Size=14.5-, Script=Math}, 31 | }, 32 | ]{Dark Modern Math Regular} 33 | \fi 34 | 35 | \usetikzlibrary{ 36 | angles, 37 | arrows.meta, 38 | calc, 39 | chains, 40 | intersections, 41 | positioning, 42 | quotes, 43 | scopes, 44 | shapes, 45 | through, 46 | } 47 | 48 | \newlength{\hlpr@baselw} 49 | \setlength{\hlpr@baselw}{1bp} 50 | 51 | \providecommand{\FSNegTwo}{\fontsize{11bp}{13bp}\selectfont} 52 | \providecommand{\FSNegOne}{\fontsize{14bp}{16bp}\selectfont} 53 | \providecommand{\FSZero}{\fontsize{17bp}{20bp}\selectfont} 54 | \providecommand{\FSOne}{\fontsize{20bp}{23bp}\selectfont} 55 | \providecommand{\FSTwo}{\fontsize{23bp}{26bp}\selectfont} 56 | \providecommand{\FSThree}{\fontsize{25bp}{28bp}\selectfont} 57 | 58 | \colorlet{darkRed50}{red!50!black} 59 | \colorlet{red20}{red!20!white} 60 | \colorlet{darkBlue50}{blue!50!black} 61 | \colorlet{blue20}{blue!20!white} 62 | \colorlet{darkGreen50}{green!50!black} 63 | \colorlet{green50}{green!50!white} 64 | \colorlet{green20}{green!20!white} 65 | \colorlet{brown80}{brown!80!white} 66 | \colorlet{brown20}{brown!20!white} 67 | \colorlet{gray50}{black!50} 68 | \colorlet{gray20}{black!20} 69 | \colorlet{gray5}{black!5} 70 | 71 | % Line widths 72 | \tikzset{ 73 | ultra thin/.style = {line width=0.25\hlpr@baselw}, 74 | very thin/.style = {line width=0.5\hlpr@baselw}, 75 | thin/.style = {line width=1\hlpr@baselw}, 76 | semithick/.style = {line width=1.5\hlpr@baselw}, 77 | thick/.style = {line width=2\hlpr@baselw}, 78 | very thick/.style = {line width=3\hlpr@baselw}, 79 | ultra thick/.style = {line width=4\hlpr@baselw}, 80 | } 81 | 82 | % Font sizes 83 | \tikzset{ 84 | fsn2/.style = {font=\FSNegTwo}, 85 | fsn1/.style = {font=\FSNegOne}, 86 | fs0/.style = {font=\FSZero}, 87 | fs1/.style = {font=\FSOne}, 88 | fs2/.style = {font=\FSTwo}, 89 | fs3/.style = {font=\FSThree}, 90 | } 91 | 92 | % Flowcharts 93 | \tikzset{ 94 | fcbase/.style = {on chain, align=center}, 95 | yesno/.style = {font=\FSZero\scshape}, 96 | every join/.style = {->}, 97 | } 98 | 99 | % General 100 | \tikzset{ 101 | every picture/.style = { 102 | thin, 103 | x=1bp, 104 | y=1bp, 105 | z=-0.385bp, 106 | >=latex, 107 | }, 108 | } 109 | 110 | \ifhlpr@smallerfont 111 | \tikzset{ 112 | every picture/.append style = {fsn1}, 113 | } 114 | \else 115 | \tikzset{ 116 | every picture/.append style = {fs0}, 117 | } 118 | \fi 119 | 120 | \pgfmathsetmacro{\DotRadius}{2} 121 | -------------------------------------------------------------------------------- /imgsrc/tools/optigif: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec gifsicle --colors 256 --change-color '#ffffff' '#fafaf4' -O3 $@ 4 | -------------------------------------------------------------------------------- /imgsrc/viewangles.tex: -------------------------------------------------------------------------------- 1 | \documentclass{templates/hlprtikz} 2 | 3 | \begin{document} 4 | 5 | \begin{tikzpicture} 6 | \pgfmathsetmacro{\axisLength}{200} 7 | \pgfmathsetmacro{\viewLength}{300} 8 | \pgfmathsetmacro{\thetaAng}{-40} 9 | \pgfmathsetmacro{\phiAng}{45} 10 | 11 | \coordinate (O) at (0,0,0); 12 | \coordinate (Ax) at (\axisLength,0,0); 13 | \coordinate (Ay) at (0,\axisLength,0); 14 | \coordinate (Az) at (0,0,\axisLength); 15 | 16 | \draw[<->] (Ay) node[above]{$z$} -- (O) node[left]{$O$} -- (Ax) node[right]{$y$}; 17 | 18 | \begin{scope}[rotate around y=\thetaAng] 19 | \coordinate (B) at (\phiAng:\viewLength); 20 | \end{scope} 21 | 22 | \coordinate (Bprojx) at ({cos(\phiAng)*cos(\thetaAng)*\viewLength},0,0); 23 | \coordinate (Bprojz) at (0,0,{cos(\phiAng)*sin(-\thetaAng)*\viewLength}); 24 | 25 | \path[name path=horiz-guide] (Bprojz) -- +(\viewLength,0,0); 26 | \path[name path=vert-guide] (Bprojx) -- +(0,0,\viewLength); 27 | \path[name intersections={of=horiz-guide and vert-guide}]; 28 | \coordinate (B') at (intersection-1); 29 | 30 | \draw[densely dotted] (B') -- (B); 31 | \draw[densely dotted] (Bprojx) -- (B') -- (Bprojz); 32 | 33 | \draw pic[draw,fill=white,->,angle radius=40,"$-\varphi$"] {angle=B'--O--B}; 34 | \draw pic[draw,fill=white,->,angle radius=45,"$\vartheta$"] {angle=Az--O--B'}; 35 | 36 | \draw[->] (O) -- (Az) node[below left]{$x$}; 37 | \draw[semithick,->] (O) -- (B') node[below right]{$F$}; 38 | \draw[thick,->] (O) -- node[above left]{$1$} (B) node[above right]{$V$}; 39 | \end{tikzpicture} 40 | 41 | \end{document} 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "mjrender": "node ./scripts/mjrender" 5 | }, 6 | "dependencies": { 7 | "html-minifier-terser": "^5.1.1", 8 | "jsdom": "^16.2.2", 9 | "mathjax-node-page": "^3.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | Babel==2.6.0 3 | certifi==2019.3.9 4 | chardet==3.0.4 5 | docutils==0.14 6 | idna==2.8 7 | imagesize==1.1.0 8 | Jinja2==2.10.1 9 | MarkupSafe==1.1.1 10 | packaging==19.0 11 | Pygments==2.3.1 12 | pyparsing==2.3.1 13 | pytz==2018.9 14 | requests==2.21.0 15 | six==1.12.0 16 | snowballstemmer==1.2.1 17 | Sphinx==3.1.2 18 | sphinxcontrib-applehelp==1.0.2 19 | sphinxcontrib-devhelp==1.0.2 20 | sphinxcontrib-htmlhelp==1.0.3 21 | sphinxcontrib-jsmath==1.0.1 22 | sphinxcontrib-qthelp==1.0.3 23 | sphinxcontrib-serializinghtml==1.1.4 24 | sphinxcontrib-websupport==1.1.0 25 | urllib3==1.24.2 26 | -------------------------------------------------------------------------------- /scripts/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z $1 ]]; then 6 | printf "usage: $0 bucket/path\n" 7 | exit 1 8 | fi 9 | 10 | exec aws s3 sync --exclude .buildinfo build/html "$1" 11 | -------------------------------------------------------------------------------- /scripts/hlprpdf2svg.inx: -------------------------------------------------------------------------------- 1 | 2 | 3 | HLPR PDF to SVG 4 | com.jwchong.www.hlprpdf2svg 5 | hlprpdf2svg.py 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | 14 | 15 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /scripts/hlprpdf2svg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import inkex 5 | import inkex.command 6 | 7 | class MyExtension(inkex.EffectExtension): 8 | def add_arguments(self, pars): 9 | pars.add_argument('--file', help='File name to save the plain SVG') 10 | 11 | def effect(self): 12 | if not self.options.file: 13 | inkex.errormsg('Please specify an output file') 14 | return 15 | 16 | scale = self.svg.uutounit(1, 'px') 17 | new_width = self.svg.width * scale 18 | new_height = self.svg.height * scale 19 | self.svg.set('viewBox', '0 0 {} {}'.format(new_width, new_height)) 20 | self.svg.set('width', new_width) 21 | self.svg.set('height', new_height) 22 | self.document = inkex.load_svg( 23 | inkex.command.inkscape_command( 24 | self.svg, verbs=['FitCanvasToDrawing'])) 25 | 26 | try: 27 | # Need to remove existing file or write_svg will fail silently 28 | os.remove(self.options.file) 29 | except: 30 | pass 31 | inkex.command.write_svg(self.document, self.options.file) 32 | 33 | if __name__ == '__main__': 34 | MyExtension().run() 35 | -------------------------------------------------------------------------------- /scripts/launchinkscape.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -z $INKSCAPE_BIN ]]; then 4 | printf "The INKSCAPE_BIN environmental variable must be set\n" 5 | exit 1 6 | fi 7 | 8 | "$INKSCAPE_BIN" -g --verb=com.jwchong.www.hlprpdf2svg $@ 9 | -------------------------------------------------------------------------------- /scripts/mjrender.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { JSDOM } = require("jsdom"); 3 | const { mjpage } = require('mathjax-node-page'); 4 | const htmlMinify = require('html-minifier-terser').minify; 5 | const cluster = require('cluster'); 6 | const numCPUs = require('os').cpus().length; 7 | 8 | /** 9 | * Remove the 225 | ''' 226 | 227 | 228 | # -- Options for LaTeX output --------------------------------------------- 229 | 230 | latex_elements = { 231 | # The paper size ('letterpaper' or 'a4paper'). 232 | # 'papersize': 'a4paper', 233 | 234 | # The font size ('10pt', '11pt' or '12pt'). 235 | #'pointsize': '10pt', 236 | 237 | # Additional stuff for the LaTeX preamble. 238 | # 'preamble': '\\usepackage{{amsmath}}', 239 | } 240 | 241 | # Grouping the document tree into LaTeX files. List of tuples 242 | # (source start file, target name, title, 243 | # author, documentclass [howto, manual, or own class]). 244 | latex_documents = [ 245 | ('index', 'TasTools.tex', 'Half-Life Physics Documentation', 246 | 'Matherunner', 'manual'), 247 | ] 248 | 249 | # The name of an image file (relative to this directory) to place at the top of 250 | # the title page. 251 | #latex_logo = None 252 | 253 | # For "manual" documents, if this is true, then toplevel headings are parts, 254 | # not chapters. 255 | #latex_use_parts = False 256 | 257 | # If true, show page references after internal links. 258 | #latex_show_pagerefs = False 259 | 260 | # If true, show URL addresses after external links. 261 | #latex_show_urls = False 262 | 263 | # Documents to append as an appendix to all manuals. 264 | #latex_appendices = [] 265 | 266 | # If false, no module index is generated. 267 | #latex_domain_indices = True 268 | 269 | 270 | # -- Options for manual page output --------------------------------------- 271 | 272 | # One entry per manual page. List of tuples 273 | # (source start file, name, description, authors, manual section). 274 | man_pages = [ 275 | ('index', 'tastools', 'TasTools Documentation', 276 | ['Airstrafers'], 1) 277 | ] 278 | 279 | # If true, show URL addresses after external links. 280 | #man_show_urls = False 281 | 282 | 283 | # -- Options for Texinfo output ------------------------------------------- 284 | 285 | # Grouping the document tree into Texinfo files. List of tuples 286 | # (source start file, target name, title, author, 287 | # dir menu entry, description, category) 288 | texinfo_documents = [ 289 | ('index', 'TasTools', 'TasTools Documentation', 290 | 'Airstrafers', 'TasTools', 'One line description of project.', 291 | 'Miscellaneous'), 292 | ] 293 | 294 | # Documents to append as an appendix to all manuals. 295 | #texinfo_appendices = [] 296 | 297 | # If false, no module index is generated. 298 | #texinfo_domain_indices = True 299 | 300 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 301 | #texinfo_show_urls = 'footnote' 302 | 303 | # If true, do not generate a @detailmenu in the "Top" node's menu. 304 | #texinfo_no_detailmenu = False 305 | -------------------------------------------------------------------------------- /source/glossary.rst: -------------------------------------------------------------------------------- 1 | Glossary 2 | ======== 3 | 4 | .. glossary:: 5 | :sorted: 6 | 7 | explosion origin 8 | explosion radius 9 | An explosion may be modelled as a sphere in which entities receive damage. The explosion origin is then the centre of this sphere, and the explosion radius is its radius. 10 | 11 | viewangles 12 | A group of three angles comprising of yaw, pitch and roll. This is associated with every entity, representing the view orientation. 13 | 14 | unit acceleration vector 15 | The result of 16 | 17 | ducktapping 18 | One of the ground avoidance tricks which involves tapping the duck key while on the ground, resulting in the player popping out 18 units above the ground. 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /source/gravitymotion.rst: -------------------------------------------------------------------------------- 1 | Motion under gravity 2 | ==================== 3 | 4 | Motion under gravity, with or without strafing, is ubiquitous in Half-Life. In speedrunning, such questions are sometimes raised: 5 | 6 | - Is it possible at all to clear that gap? 7 | - What is the minimum long jump speed required to clear that gap? 8 | - How much speed boost is needed to jump over that wall? 9 | - How much speed boost is needed to clear that gap? 10 | - At what angle should I toss the MP5 grenade to hit the target? 11 | 12 | Here we will attempt to answer these questions, and provide formulae or algorithms to solve many of these common cases. 13 | 14 | Preliminaries 15 | ------------- 16 | 17 | We will restrict discussions to a vertical plane, where the horizontal and vertical axes are denoted as the :math:`x` and :math:`z` axes respectively. We will assume all bodies have initial position at the origin :math:`(x_0, z_0) = (0, 0)`. 18 | 19 | Recall from classical kinematics that a particle under projectile motion (motion under the sole influence of gravity) with initial velocities :math:`v_{i,x}`, :math:`v_{i,z}`, and gravitational acceleration :math:`g` has final positions 20 | 21 | .. math:: x_f = v_{i,x} t \qquad z_f = v_{i,z} t - \frac{1}{2} gt^2 22 | 23 | In most of the discussions below, the equation for :math:`z` will remain unchanged, but the equation for :math:`x` will depend on whether strafing is done. 24 | 25 | Recall from strafing physics that given initial velocity :math:`v_{i,x}` and strafing variables, the final horizontal speed and position after :math:`t` seconds of strafing is 26 | 27 | .. math:: v_{f,x} = \sqrt{v_{i,x}^2 + tK} \qquad x_f = \frac{2}{3K} \left( \left( v_{i,x}^2 + tK \right)^{3/2} - v_{i,x}^3 \right) 28 | 29 | Here, :math:`K` depends on the type of strafing. Possible values are :math:`K = MA(2L - \tau MA)` or :math:`K = L^2/\tau`. 30 | 31 | Position and a velocity component 32 | --------------------------------- 33 | 34 | In speedrunning situations, the final position is often given, and the goal would be to move from the current position, to the final position, subject to some constraints in one of the velocity components. For example, determining the minimum initial horizontal velocity needed to clear a gap by jumping and strafing alone (initial vertical velocity is known), or determining the minimum boost needed to reach a certain height (the final vertical velocity is zero). 35 | 36 | Assuming initial and final positions :math:`(0, 0)` and :math:`(x_f, z_f)`, we may be given just one of the following: 37 | 38 | .. math:: v_{i,x} \quad v_{i,z} \quad v_{f,x} \quad v_{f,z} 39 | 40 | Knowing just one of these quantities is sufficient to solve the entire equation of motion in both axes. 41 | 42 | Time constraint equation 43 | ~~~~~~~~~~~~~~~~~~~~~~~~ 44 | 45 | The time it takes to reach the final position must be the same for both :math:`x` and :math:`z` directions. In other words, the position in both directions must reach the final position simultaneously. Solving the time in the vertical equation of motion yields 46 | 47 | .. math:: t_z = \frac{v_{i,z} \pm \sqrt{v_{i,z}^2 - 2gz_f}}{g} 48 | 49 | If strafing is performed, the time it takes to reach the final horizontal position is found to be 50 | 51 | .. math:: t_x = \frac{1}{K} \left( \left( v_{i,x}^3 + \frac{3}{2} Kx_f \right)^{2/3} - v_{i,x}^2 \right) 52 | 53 | Then, :math:`t_z = t_x` must be satisfied for the body to reach the final position. This is the time constraint equation that will be used in later analyses. 54 | 55 | Note also the maximum :math:`t_x` is achieved by having zero initial horizontal velocity, while keeping the other variables constant. This yields 56 | 57 | .. math:: t_{x,\mathrm{max}} = \left. t_x \right\rvert_{v_{i,x} = 0} = K^{-1/3} \left( \frac{3}{2} x_f \right)^{2/3} 58 | 59 | The purpose of this quantity will be illustrated later as well. 60 | 61 | Given :math:`v_{i,x}` 62 | ~~~~~~~~~~~~~~~~~~~~~ 63 | 64 | With :math:`v_{i,x}` known, we can simply compute the horizontal time :math:`t_x`, and compute 65 | 66 | .. math:: v_{i,z} = \frac{gt_x^2 + 2z_f}{2t_x} 67 | 68 | The calculated initial vertical speed is guaranteed to reach the final position in the same time. However, the trajectory may take a different shape. That is, the path might either be strictly increasing or ends with a decreasing curve. To check this, we check the sign of the final vertical velocity. If the path is strictly increasing, we have 69 | 70 | .. math:: 71 | \begin{aligned} 72 | v_{f,z} = v_{i,z} - gt_x = \frac{gt_x^2 + 2z_f}{2t_x} - gt_x &\ge 0 \\ 73 | \implies 2z_f &\ge gt_x^2 74 | \end{aligned} 75 | 76 | Observe that if :math:`t_x` increases while all else kept constant, the inequality will eventually be violated, resulting in a inverted bell shaped path. In addition, if :math:`z_f < 0`, that is when the final position is below the starting position, then the inequality will always be violated regardless of :math:`t_x`. 77 | 78 | Given :math:`v_{f,x}` 79 | ~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | TODO TODO 82 | 83 | Given :math:`v_{i,z}` 84 | ~~~~~~~~~~~~~~~~~~~~~ 85 | 86 | Firstly, the final height is reachable if :math:`v_{i,z}^2 \ge 2gz_f`, which ensures the square root in the :math:`t_z` equation give a real number. Notice that if :math:`z_f < 0` then this condition will always hold, which is intuitive. 87 | 88 | This is where we will make use of the :math:`t_{x,\mathrm{max}}` described earlier. There are four different cases we need to handle. Namely, two from the the sign of the square root when computing :math:`t_z`, and two from the sign of :math:`t_{x,\mathrm{max}} - t_z`. In most cases, taking the negative square root is desirable as this minimises the time :math:`t_z`. However, the negative square root may lead to :math:`t_z < 0`, which must be rejected. The sign of :math:`t_{x,\mathrm{max}} - t_z` indicates the need to manually slow down the horizontal motion (by taking a longer curve or stop strafing altogether). Namely, :math:`t_{x,\mathrm{max}} < t_z` implies reaching the final :math:`x` position before reaching the final :math:`z` position. Therefore, the horizontal motion needs to be slowed, and no subsequent computation needs to be done. If :math:`t_{x,\mathrm{max}} > t_z`, then the horizontal speed is not sufficiently high, and therefore the initial horizontal velocity is nonzero and an additional step is needed to compute its value. 89 | 90 | In the last case, we must solve for the initial horizontal velocity :math:`v_{i,x}` from the time constraint equation. The time constraints equation cannot be solved analytically with any ease, therefore a numerical solution should be computed using any root finding algorithm. For example, the Newton's method appears to work in many cases. For reference, the derivative of the time constraint equation needed for Newton's method is 91 | 92 | .. math:: \frac{2}{K} v_{i,x} \left( v_{i,x} \left( v_{i,x}^3 + \frac{3}{2} Kx_f \right)^{-1/3} - 1 \right) 93 | 94 | An alternative to the Newton's method, but still require a numerical solution, is to solve the quartic equation 95 | 96 | .. math:: 3t_z v_{i,x}^4 - 3x_fv_{i,x}^3 + 3t_z^2Kv_{i,x}^2 + t_z^3K^2 - \frac{9}{4} x_f^2K = 0 97 | 98 | using a standard polynomial solver. Typically, there are two complex roots that do not satisfy the time constraint equation, and a root that is negative. 99 | 100 | Given :math:`v_{f,z}` 101 | ~~~~~~~~~~~~~~~~~~~~~ 102 | 103 | It is common to have the final vertical velocity given as well. For example, this gives the minimum required initial velocity to reach a the given height. Or, with :math:`v_{f,z} = 180` this gives the maximum initial velocity such that the player is barely onground when landing on some platform. 104 | 105 | The approach to solving this problem is very similar to that when given the initial vertical velocity. The only difference is the equation for :math:`t_z`, which must be rewritten in terms of :math:`v_{f,z}`, giving 106 | 107 | .. math:: t_z = \frac{v_{f,z} \pm \sqrt{v_{f,z}^2 + 2gz_f}}{g} 108 | -------------------------------------------------------------------------------- /source/images/90-degrees-bend-c2a2e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/90-degrees-bend-c2a2e.jpg -------------------------------------------------------------------------------- /source/images/90-degrees-strafe-radius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/90-degrees-strafe-radius.png -------------------------------------------------------------------------------- /source/images/boot_camp_selfgauss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/boot_camp_selfgauss.jpg -------------------------------------------------------------------------------- /source/images/bullet-spread-angle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/bullet-spread-distribution.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/collision-overbounce.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/conveyor-residue-processing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/conveyor-residue-processing.jpg -------------------------------------------------------------------------------- /source/images/crossbow-bolt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/crossbow-bolt.jpg -------------------------------------------------------------------------------- /source/images/doublegauss-crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/doublegauss-crate.jpg -------------------------------------------------------------------------------- /source/images/doublegauss-parts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/edgefriction-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/entitypunch-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/entitypunch-1.png -------------------------------------------------------------------------------- /source/images/explosion-contact-grenades.svg: -------------------------------------------------------------------------------- 1 | explosion-contact-grenades -------------------------------------------------------------------------------- /source/images/explosion-mortar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/explosion-mortar.jpg -------------------------------------------------------------------------------- /source/images/frame_rate_unsync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/frame_rate_unsync.png -------------------------------------------------------------------------------- /source/images/gauss-entity-punch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/gauss-entity-punch.jpg -------------------------------------------------------------------------------- /source/images/gauss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/gauss.jpg -------------------------------------------------------------------------------- /source/images/gordon-scientist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/gordon-scientist.jpg -------------------------------------------------------------------------------- /source/images/hitboxes-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/hitboxes-2.jpg -------------------------------------------------------------------------------- /source/images/houndeyes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/houndeyes.jpg -------------------------------------------------------------------------------- /source/images/jumpbug-bbox.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/ladder-angles-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/ladder-angles-1.png -------------------------------------------------------------------------------- /source/images/ladder-exit-c1a0e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/ladder-exit-c1a0e.jpg -------------------------------------------------------------------------------- /source/images/ladder-sloped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/ladder-sloped.jpg -------------------------------------------------------------------------------- /source/images/movable-box.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/movable-box.jpg -------------------------------------------------------------------------------- /source/images/player-hulls.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/player_hp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/player_hp.png -------------------------------------------------------------------------------- /source/images/radius-estimate-xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/radius-estimate-xy.png -------------------------------------------------------------------------------- /source/images/reflection-bypass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/selfgauss-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/selfgauss-1.png -------------------------------------------------------------------------------- /source/images/simple_gauss_boosts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/simple_gauss_boosts.gif -------------------------------------------------------------------------------- /source/images/sloped-plane-jump.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/images/speed-preserving-c1a2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/speed-preserving-c1a2.jpg -------------------------------------------------------------------------------- /source/images/stairparts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/stairparts.png -------------------------------------------------------------------------------- /source/images/strafing-intuition-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/strafing-intuition-1.png -------------------------------------------------------------------------------- /source/images/timed-grenade.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/timed-grenade.jpg -------------------------------------------------------------------------------- /source/images/triggers-c3a2c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/triggers-c3a2c.jpg -------------------------------------------------------------------------------- /source/images/veccom-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/images/veccom-1.png -------------------------------------------------------------------------------- /source/images/viewangles.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/index.rst: -------------------------------------------------------------------------------- 1 | Half-Life Physics Reference 2 | =========================== 3 | 4 | .. caution:: This documentation is work in progress! Some parts may be 5 | incomplete, and others may be bleeding edge research. 6 | 7 | .. image:: images/gordon-scientist.jpg 8 | 9 | This is an unofficial documentation for the physics governing the Half-Life_ universe. There have been many very comprehensive wikis for games in the Half-Life series, such as the `Half-Life Wikia`_ and the `Combine OverWiki`_. These wikis focus on the storyline and casual gaming aspects of the Half-Life series video games. There is also a wiki for the practical speedrunning aspects of these games, namely the `SourceRuns Wiki`_, though it has been virtually abandoned with little to no updates. Despite the wealth of strategy guides available for Half-Life, it is almost impossible to find documentations describing the physics of the game with a satisfying level of technical accuracy. 10 | 11 | .. _Half-Life: https://en.wikipedia.org/wiki/Half-Life_(video_game) 12 | .. _Half-Life Wikia: http://half-life.wikia.com/wiki/Main_Page 13 | .. _Combine OverWiki: http://combineoverwiki.net/wiki/Main_Page 14 | .. _SourceRuns Wiki: http://wiki.sourceruns.org/wiki/Main_Page 15 | 16 | Knowledge about the physics of Half-Life is important for developing tools for Half-Life TAS production and for the process of TASing itself. Highly precise tools are required to exploit the in-game physics to the fullest extent. Perhaps more importantly, developing an understanding and intuition for Half-Life physics is vital in producing a highly optimised TAS for the game and resolving tricky physics issues arising out of speedrunning. 17 | 18 | Thus, this documentation strives to detail all aspects of the physics in a way that would help any curious minds to gain a much deeper appreciation for Half-Life and its speedruns. The potential tool developers will also find this documentation a helpful guide. This documentation aims to serve as a definitive reference material for Half-Life physics. 19 | 20 | Frequently asked questions 21 | -------------------------- 22 | 23 | **Who are you?** I'm someone who played Half-Life as a kid, and became deeply 24 | fascinated by its physics much later when this_ monumental single-segment run 25 | was published in 2011 by quadrazid. I sought to understand how every trick in 26 | the run worked, which necessitated the study of the physics of the entire game. 27 | 28 | .. _this: https://youtu.be/AKIpyz0EjuY 29 | 30 | **Would I be able to understand this documentation?** It depends on how much 31 | mathematics and programming you know. You are assumed to have an *intermediate* 32 | level of understanding of the latest Half-Life SDK, and have it available at all 33 | times when you need to reference it. By extension, you are assumed to be 34 | proficient in C or C++. Since this documentation is heavy in mathematics, you 35 | are assumed to be fluent in vector algebra and some linear algebra, along with a 36 | high proficiency in trigonometry. Some knowledge of calculus is also assumed. 37 | 38 | **Couldn't you write this documentation in a simpler way?** Our goal with this 39 | documentation is to describe the physics of Half-Life as precisely as possible. 40 | Many of the concepts in Half-Life are highly intricate and precise. Attempts to 41 | simplify these concepts may help in many situations, but the simplified 42 | explanations will fail under some edge cases. It is often these edge cases that 43 | we seek to exploit in a TAS. A quote often attributed to Albert Einstein sums 44 | this up aptly: 45 | 46 | *Everything should be made as simple as possible, but no simpler.* 47 | 48 | **Are the equations made up from thin air?** We do not conjure up any equation 49 | based on conjectures or guesswork, unless *clearly* stated otherwise. All 50 | equations and mathematics in this documentation are ultimately derived from the 51 | Half-Life SDK or the reverse-engineered engine code. Empirically derived 52 | equations will also be stated clearly. 53 | 54 | **How did you create this documentation?** We experimented with many different 55 | tools, including LaTeX, but ultimately settled on reStructuredText with Sphinx_ 56 | in combination with `pre-rendered`_ MathJax_. Sphinx is a really good system for 57 | generating highly structured documentations. In fact, it is used to document 58 | most Python modules, including the heavy hitters like numpy_, requests_, etc. In 59 | addition, reStructuredText is the most extensible and structured markup language 60 | that is not LaTeX, rivalled only by AsciiDoc or AsciiDoctor. For mathematical 61 | typesetting, MathJax is by far the most mature for the web which runs well on 62 | many browsers. Prominent sites such as MathOverflow_ use it. By pre-rendering 63 | MathJax, the loading times of pages can be dramatically reduced. 64 | 65 | .. _Sphinx: http://www.sphinx-doc.org/en/master/ 66 | .. _pre-rendered: https://github.com/mathjax/MathJax-node 67 | .. _MathJax: https://www.mathjax.org 68 | .. _numpy: http://www.numpy.org 69 | .. _requests: http://docs.python-requests.org/en/master/ 70 | .. _MathOverflow: https://mathoverflow.net 71 | 72 | .. _notations: 73 | 74 | Notations Used 75 | -------------- 76 | 77 | One of the most important mathematical objects in discussions of Half-Life physics is the Euclidean vector. All vectors are in either :math:`\mathbb{R}^2` or :math:`\mathbb{R}^3`, where :math:`\mathbb{R}` denotes the real numbers. This is sometimes not specified explicitly if the contextual clues are sufficient for disambiguation. 78 | 79 | All vectors are written in boldface like so: 80 | 81 | .. math:: \mathbf{v} 82 | 83 | Every vector has an associated length, which is referred to as the *norm*. The norm of some vector :math:`\mathbf{v}` is thus denoted as 84 | 85 | .. math:: \lVert\mathbf{v}\rVert 86 | 87 | A vector of length one is called a *unit vector*. So the unit vector in the direction of some vector :math:`\mathbf{v}` is written with a hat: 88 | 89 | .. math:: \mathbf{\hat{v}} = \frac{\mathbf{v}}{\lVert\mathbf{v}\rVert} 90 | 91 | There are three special unit vectors, namely 92 | 93 | 94 | .. math:: \mathbf{\hat{i}} \quad \mathbf{\hat{j}} \quad \mathbf{\hat{k}} 95 | 96 | These vectors point towards the positive :math:`x`, :math:`y` and :math:`z` axes respectively. 97 | 98 | Every vector also has components in each axis. For a vector in :math:`\mathbb{R}^2`, it has an :math:`x` component and a :math:`y` component. A vector in :math:`\mathbb{R}^3` has an additional :math:`z` component. To write out the components of a vector explicitly, we have 99 | 100 | .. math:: \mathbf{v} = \langle v_x, v_y, v_z\rangle 101 | 102 | This is equivalent to writing :math:`\mathbf{v} = v_x \mathbf{\hat{i}} + v_y \mathbf{\hat{j}} + v_z \mathbf{\hat{k}}`. However, we never write out the components this way in this documentation as it is tedious. Notice that we are writing vectors as row vectors. This will be important to keep in mind when we apply matrix transformations to vectors. 103 | 104 | The dot product between two vectors :math:`\mathbf{a}` and :math:`\mathbf{b}` is written as 105 | 106 | .. math:: \mathbf{a} \cdot \mathbf{b} 107 | 108 | On the other hand, the cross product between :math:`\mathbf{a}` and :math:`\mathbf{b}` is 109 | 110 | .. math:: \mathbf{a} \times \mathbf{b} 111 | 112 | Contact 113 | ------- 114 | 115 | This documentation is currently a one-man project. `Contact me`_. 116 | 117 | .. _Contact me: jwcchong@gmail.com 118 | 119 | Contents 120 | -------- 121 | 122 | .. toctree:: 123 | :numbered: 124 | :maxdepth: 2 125 | 126 | game 127 | entity 128 | player 129 | movement 130 | duckjump 131 | strafing 132 | gravitymotion 133 | surfing 134 | ladder 135 | automation 136 | damage 137 | explosions 138 | weapons 139 | monsters 140 | triggers 141 | funcs 142 | casestudies 143 | practical 144 | othergames 145 | glossary 146 | -------------------------------------------------------------------------------- /source/othergames.rst: -------------------------------------------------------------------------------- 1 | Sister games 2 | ============ 3 | 4 | Throughout this documentation, we have devoted much of our attention on Half-Life. In this chapter, we will briefly discuss the interesting aspects of games related to Half-Life in the context of speedrunning. 5 | 6 | Counter-Strike 7 | -------------- 8 | 9 | .. TODO movement physics, stamina 10 | 11 | Gunman Chronicles 12 | ----------------- 13 | 14 | .. TODO weapons, cust abuse 15 | 16 | Opposing Force 17 | -------------- 18 | 19 | .. TODO rope physics, machine gun boosting, water slowdown 20 | 21 | Quake 22 | ----- 23 | 24 | .. TODO: quake movement 25 | 26 | -------------------------------------------------------------------------------- /source/practical.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Practical TASing 3 | ================ 4 | 5 | TODO incomplete 6 | 7 | TASing is fundamentally much more practical than theoretical. Mastery of any 8 | practical skill, be it playing the piano or tennis, can only come through 9 | practice, and so creating many TASes yourself is essential in understanding and 10 | appreciating the nuances of TASing and the Half-Life game physics. On this account, this document alone will not teach you how to be 11 | good at TASing. Instead, we will provide some tips and general notes. 12 | 13 | Console setup 14 | ============= 15 | 16 | There are many commands and variables that are usually considered as cheats in a 17 | human speedrun, but are permissible in the process of developing a TAS. These 18 | commands include the various HUD aids like the viewing of target entity health, 19 | making triggers visible, and so on. Other cheat commands that require 20 | ``sv_cheats 1`` are still banned for us except during the planning phase of a 21 | run. These are the familiar commands such as ``noclip``, ``impulse 101``, or 22 | commands that modify player health directly. In the first category of cheat 23 | commands, there are many of which a TAS runner should enable to aid the process. 24 | 25 | ``r_fullbright 1`` 26 | This is essential to illuminate the entire map so that the runner can view 27 | the run clearly. 28 | 29 | ``r_drawentities 3`` 30 | Consider turning this on at all times to display the hitboxes of 31 | 32 | Workflow tips 33 | ============= 34 | 35 | A run must begin with a initial planning and testing phase. Without at least a 36 | rough idea on how various parts of the map are to be run, the resulting TAS will 37 | tend to be suboptimal. It may be helpful to have some human runs of the map or 38 | game that you are going to TAS already available, because they can give you a 39 | clearer idea of the routes and strategy to be used in this game. A downside of 40 | having a human run available in the public, especially a highly optimised one, 41 | is that the novelty of the TAS version could be reduced significantly, and 42 | unless new tricks and routes are used to spectacular effect, the TAS may be 43 | viewed as a mere imitation or repeat of the human speedrun and lack novelty in 44 | itself. 45 | -------------------------------------------------------------------------------- /source/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /source/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /source/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/apple-touch-icon.png -------------------------------------------------------------------------------- /source/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffc40d 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /source/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/favicon-16x16.png -------------------------------------------------------------------------------- /source/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/favicon-32x32.png -------------------------------------------------------------------------------- /source/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/favicon.ico -------------------------------------------------------------------------------- /source/static/hlprlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/hlprlogo.png -------------------------------------------------------------------------------- /source/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matherunner/hldoc/93fb6f6f48b52b2b13f3c646c86fda4b2f53cce6/source/static/mstile-150x150.png -------------------------------------------------------------------------------- /source/static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/_static/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/_static/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /source/triggers.rst: -------------------------------------------------------------------------------- 1 | Triggers 2 | ======== 3 | 4 | Triggers are one of the most important entities in Half-Life. They are hidden in 5 | plain sight, literally. A trigger typically has a BSP model associated with it, 6 | though some map or modding tools such as the `Vluzacn's map compile tools 7 | `_ 8 | removes the associated BSP model. If the associated model is present, then they 9 | can be made visible in a speedrunning mod to make planning and routing easier. 10 | 11 | .. figure:: images/triggers-c3a2c.jpg 12 | 13 | trigger_once and trigger_hurt in c3a2c displayed using a custom mod. 14 | 15 | Mechanism 16 | --------- 17 | 18 | A trigger entity usually has solid type ``SOLID_TRIGGER``. Solids of this type 19 | do not get "touched" by conventional means where a line tracing of some sort is 20 | done to check if the line intersects with them. Instead, these trigger entities 21 | are maintained in a BSP tree called "area nodes", pointed to by the 22 | ``sv_areanodes`` global variable in the engine library. They are placed there by 23 | calling ``SV_LinkEdict`` with each of them as the first argument somewhere at 24 | the beginning of a level load. In every subsequent frame, ``SV_LinkEdict`` is 25 | called with the player entity as the argument at some point, which causes 26 | ``SV_TouchLinks`` to get called if the second argument is set to true. 27 | 28 | The ``SV_TouchLinks`` function walks through each of the triggers stored in the 29 | area nodes, and checks if the player intersects with the model using hull 30 | information. If the player and the trigger intersects, then ``DispatchTouch`` in 31 | the game library will get called, which in turn calls the ``Touch`` member 32 | function of the trigger in question. The ``Touch`` function then runs whatever 33 | logic specific to the trigger type. 34 | 35 | Some triggers, such as trigger_once, may get removed after touching, though not 36 | immediately. Typically a trigger is scheduled to be removed after 0.1 seconds. 37 | The reason for this delay may be illustrated by this comment in 38 | ``CBaseTrigger::ActivateMultiTrigger``: 39 | 40 | .. code-block:: c++ 41 | 42 | // we can't just remove (self) here, because this is a touch function 43 | // called while C code is looping through area links... 44 | SetTouch( NULL ); 45 | pev->nextthink = gpGlobals->time + 0.1; 46 | SetThink( &CBaseTrigger::SUB_Remove ); 47 | 48 | In other words, the delay is likely to prevent a crash as ``SV_TouchLinks`` is 49 | traversing the area nodes and holding a reference to the trigger. 50 | 51 | Base trigger 52 | ------------ 53 | 54 | The base trigger refers to the base class from which all other trigger types are 55 | inherited. Two important properties associated with a trigger that are not so 56 | obvious are the *delay on activation* and the *delay after use*. The property 57 | names of these values are ``delay`` and ``wait`` respectively. The ``delay`` 58 | property is read by the superclass ``CBaseDelay`` and ``wait`` is read from 59 | ``CBaseToggle``. 60 | 61 | trigger_multiple 62 | ---------------- 63 | 64 | This type of trigger is typically used for actions that can be triggered 65 | multiple times. When triggered, the assigned target will be fired using 66 | ``SUB_UseTargets``. For this trigger, the delays on activation or after use 67 | properties are important to know if hitting the trigger is important in 68 | progressing in the run. For example, sometimes a trigger_multiple in front of a 69 | door is activated or enabled only after something other action has been done. 70 | This means that standing in the trigger area can result in premature firing even 71 | before the trigger has been activated. When the trigger finally activates, the 72 | delay after use can cause the trigger is not fire immediately, thus wasting 73 | crucial time. To avoid this issue, we should avoid touching trigger_multiple 74 | prematurely until we are sure they have been activated. And of course, this 75 | requires knowing precisely when the trigger activates. 76 | 77 | trigger_changelevel 78 | ------------------- 79 | 80 | .. _trigger_push: 81 | 82 | trigger_push 83 | ------------ 84 | 85 | A push trigger or a push field is associated with a basevelocity :math:`\mathbf{b}_p` set by the map designer. It imparts :math:`\mathbf{b}_p` onto the velocity of each of the entities that touches it, provided the entities in question satisfy certain conditions written in ``CTriggerPush::Touch`` in the Half-Life SDK. The push trigger also sets the ``FL_BASEVELOCITY`` flag in the entities it touches. 86 | 87 | If the touching entities satisfy the necessary conditions, the behaviour differs slightly depending on the spawn flags of the trigger. Let :math:`\mathbf{v}` be the velocity of a touching entity. If ``SF_TRIG_PUSH_ONCE`` is set, then we have 88 | 89 | .. math:: \mathbf{v} \gets \mathbf{v} + \mathbf{b}_p 90 | 91 | Then, the push trigger will remove itself. 92 | 93 | On the other hand, if ``SF_TRIG_PUSH_ONCE`` is not set, then if ``FL_BASEVELOCITY`` is set in the flags of the entity, the new velocity is given by 94 | 95 | .. math:: \mathbf{b} \gets \mathbf{b} + \mathbf{b}_p 96 | 97 | where :math:`\mathbf{b}` is the current basevelocity of the entity. If ``FL_BASEVELOCITY`` is not set for the entity, then we instead have 98 | 99 | .. math:: \mathbf{b} \gets \mathbf{b}_p 100 | 101 | Subsequently, the ``FL_BASEVELOCITY`` flag will be set for the entity, which is important for the player entity. 102 | 103 | Boosting by rapid touching 104 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 105 | 106 | There exists a function in the closed source engine code, ``SV_CheckMovingGround``, which is called unconditionally before all of player physics. At a high level, this function modifies the player velocity and/or basevelocity depending on various conditions. One such condition is when the player stands on a conveyor belt, which is described in :ref:`func_conveyor`. Another condition is when the ``FL_BASEVELOCITY`` flag is *not* set in the player entity. In this case, the player velocity and basevelocity will be modified to be 107 | 108 | .. math:: 109 | \begin{aligned} 110 | \mathbf{v} &\gets \mathbf{v} + \left( 1 + \frac{1}{2} \tau_p \right) \mathbf{b} \\ 111 | \mathbf{b} &\gets \mathbf{0} 112 | \end{aligned} 113 | :label: player basevelocity exit 114 | 115 | Equation :eq:`player basevelocity exit` may be referred to as the *basevelocity exit equation*. One frame after the player exits a push field (or any entity that imparts a basevelocity), the ``FL_BASEVELOCITY`` flag will no longer be set by the ``trigger_push``. Since this flag is always reset every frame, :eq:`player basevelocity exit` will be invoked in the beginning of the next frame. If the player is able to repeatedly enter and leave a push trigger, the basevelocity will be added to the player velocity rapidly, resulting in a massive acceleration. This has been used to great effect in many Half-Life speedruns, typically achieved by ducking and unducking rapidly above a ``trigger_push``, which changes the player hull repeatedly (see :ref:`ducking`). If the ducking and unducking sequence is done at an extremely high frame rate, the resulting acceleration is one of the highest possible in the game. 116 | 117 | trigger_hurt 118 | ------------ 119 | 120 | The hurt trigger applies damage to entities that touch it. A hurt trigger always 121 | has a delay of half a second after it damages some entity. 122 | --------------------------------------------------------------------------------