diff --git a/.github/workflows/lua-build.yml b/.github/workflows/lua-build.yml new file mode 100644 index 0000000000..f517538f1d --- /dev/null +++ b/.github/workflows/lua-build.yml @@ -0,0 +1,133 @@ +name: lua-build + +on: + workflow_dispatch: + inputs: + push: + default: true + type: boolean + description: "Push built dynamic libraries" + workflow_call: + inputs: + push: + default: true + type: boolean + description: "Push built dynamic libraries" +permissions: + contents: write + +defaults: + run: + shell: bash + +env: + LUA_SOURCE_DIR: ${{ github.workspace }}/src/kOS/Lua/lua + LUA_BUILD_DIR: ${{ github.workspace }}/src/kOS/Lua/lua/_build + LUA_OUTPUT_DIR: ${{ github.workspace }}/src/kOS/Lua/include + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Set vars + id: vars + run: | + if [ "$RUNNER_OS" == "Linux" ]; then + echo 'LUA_LIB_NAME=liblua54.so" >> "$GITHUB_OUTPUT' + echo 'LUA_LIB_PATH=$LUA_BUILD_DIR/lib64/liblua54.so' >> "$GITHUB_OUTPUT" + echo 'LUA_LUAROCKS_LIB_PATH=/usr/local/lib/lua/5.4' >> "$GITHUB_OUTPUT" + elif [ "$RUNNER_OS" == "Windows" ]; then + echo 'LUA_LIB_NAME=lua54.dll" >> "$GITHUB_OUTPUT' + echo 'LUA_LIB_PATH=$LUA_BUILD_DIR/bin/lua54.dll' >> "$GITHUB_OUTPUT" + echo 'LUA_LUAROCKS_LIB_PATH=~/.luarocks/lib/lua/5.4' >> "$GITHUB_OUTPUT" + elif [ "$RUNNER_OS" == "macOS" ]; then + echo 'LUA_LIB_NAME=liblua54.dylib" >> "$GITHUB_OUTPUT' + echo 'LUA_LIB_PATH=$LUA_BUILD_DIR/lib64/liblua54.dylib' >> "$GITHUB_OUTPUT" + echo 'LUA_LUAROCKS_LIB_PATH=/usr/local/lib/lua/5.4' >> "$GITHUB_OUTPUT" + fi + + - name: Configure CMake + run: cmake -S $LUA_SOURCE_DIR -B $LUA_BUILD_DIR + + - name: Build + run: cmake --build $LUA_BUILD_DIR --config Release + + - name: Test + working-directory: ${{ env.LUA_BUILD_DIR }} + run: ctest --build-config Release + + - name: Install LuaRocks + run: | + mv $LUA_BUILD_DIR/bin64 $LUA_BUILD_DIR/bin # luarocks gets lost if lua binary is not in "bin" directory + if [ "$RUNNER_OS" == "Windows" ]; then + curl http://luarocks.github.io/luarocks/releases/luarocks-3.11.1-windows-64.zip --output luarocks.zip + unzip luarocks.zip -d ~/luarocks + mv ~/luarocks/luarocks-3.11.1-windows-64/* ~/luarocks + rm -rf ~/luarocks/luarocks-3.11.1-windows-64 + else + wget https://luarocks.org/releases/luarocks-3.11.1.tar.gz + tar zxpf luarocks-3.11.1.tar.gz + cd luarocks-3.11.1 + ./configure --with-lua-bin="$LUA_BUILD_DIR/bin" + make && sudo make install + fi + + - name: Install modules + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + PATH=~/luarocks:$LUA_BUILD_DIR/bin:$PATH + luarocks install lua-cjson 2.1.0.10-1 + else + sudo luarocks install lua-cjson 2.1.0.10-1 + fi + + - name: Create artifact + run: | + rm -rf artifact + mkdir artifact + cp ${{ steps.vars.outputs.LUA_LIB_PATH }} artifact/${{ steps.vars.outputs.LUA_LIB_NAME }} + mkdir artifact/LuaModules artifact/LuaModules/${{ runner.os }} + cp -r ${{ steps.vars.outputs.LUA_LUAROCKS_LIB_PATH }}/. artifact/LuaModules/${{ runner.os }} + + - uses: actions/upload-artifact@v4 + with: + name: ${{ runner.os }} + if-no-files-found: error + retention-days: 7 + path: ${{ github.workspace }}/artifact + + push: + runs-on: ubuntu-latest + needs: build + if: inputs.push + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Clear output directory + run: rm -rf $LUA_OUTPUT_DIR + + - uses: actions/download-artifact@v4 + with: + merge-multiple: 'true' + path: ${{ env.LUA_OUTPUT_DIR }} + + - name: Push + env: + GIT_COMMITTER_NAME: lua-build automation + GIT_AUTHOR_NAME: lua-build automation + GIT_COMMITTER_EMAIL: noreply@github.com + GIT_AUTHOR_EMAIL: noreply@github.com + run: | + git add $LUA_OUTPUT_DIR + git commit -am "Build lua" + git push \ No newline at end of file diff --git a/.gitignore b/.gitignore index 71a83d5306..8dc821bb42 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ packages/ # VisualStudio Code .vscode/ +# Rider +.idea/ + # Vim *.swp @@ -35,10 +38,13 @@ packages/ [Rr]esources/GameData/kOS/Plugins/ [Rr]esources/*.dll [Rr]esources/GameData/*.dll +[Rr]esources/GameData/kOS/Parts/@thumbs/ +[Rr]esources/GameData/kOS/PluginData/LuaModules/ !Resources/GameData/kOS/ICSharpCode.SharpZipLib.dll # Symlink to KSP directory KSPdirlink +KSP # OS Specific .DS_STORE* @@ -53,3 +59,5 @@ netkan.exe # .swp files from vim (temporary files that vim creates when editing a file) *.swp + +!Resources/GameData/kOS/PluginData/LuaLSAddon/library/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..29d27268fb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/kOS/Lua/lua"] + path = src/kOS/Lua/lua + url = git@github.com:sug44/lua.git diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/.luarc.json b/Resources/GameData/kOS/PluginData/LuaLSAddon/.luarc.json new file mode 100644 index 0000000000..1d91d5c51f --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/.luarc.json @@ -0,0 +1,9 @@ +{ + "diagnostics.disable": ["lowercase-global"], + "workspace.library": [ + "../../GameData/kOS/PluginData/LuaLSAddon/library", + "../../GameData/kOS/PluginData/LuaModules" + ], + "runtime.plugin": "../../GameData/kOS/PluginData/LuaLSAddon/plugin/patcher.lua", + "runtime.special": { "_G": "_G" } +} diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/README.md b/Resources/GameData/kOS/PluginData/LuaLSAddon/README.md new file mode 100644 index 0000000000..736caa36a5 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/README.md @@ -0,0 +1,13 @@ +This is an addon for [lua language server](https://github.com/luals/lua-language-server). +It includes: +- Patcher plugin that adds support for the arrow function syntax +- Annotations for kOS structures, functions and bound variables + +### Setup for VSCode +1. Install the lua language server extension made by sumneko. +2. Copy the `.luarc.json` file to the archive at `*KSP_Folder*/Ships/Script/.luarc.json`. +3. Open the folder with VSCode and you will be asked by the patcher plugin to patch lua language server. +4. Read the message and press "Ok". +5. Restart VSCode. + +After setup you should have the syntax support and annotations available. diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/library/base.lua b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/base.lua new file mode 100644 index 0000000000..35206697d0 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/base.lua @@ -0,0 +1,14 @@ +---Function that gets called by kOS each physics tick. +---@type function | nil +fixedupdate = nil + +---Function that gets called by kOS each frame. +---@type function | nil +update = nil + +---Function that gets called by kOS when `Ctrl+C` is pressed. +---If `Ctrl+C` was pressed 3 times while the command code was deprived of instructions by the `fixedupdate` function +---it will be set to `nil` to prevent the core from geting stuck. +---To prevent it from happening this function must ensure the terminal is not deprived of instructions. +---@type function | nil +breakexecution = nil diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/library/functions.lua b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/functions.lua new file mode 100644 index 0000000000..e0a081df89 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/functions.lua @@ -0,0 +1,1761 @@ +---@param AlarmType string +---@param UT number +---@param Name string +---@param Notes string +---@return KACAlarm +---Creates alarm of type `KACAlarm:ALARMTYPE` at `UT` with `Name` and `Notes` attributes set. Attaches alarm to current :ref:`CPU Vessel `. Returns `KACAlarm` object if creation was successful and empty string otherwise:: +--- +--- set na to addAlarm("Raw",time:seconds+300, "Test", "Notes"). +--- print na:NAME. //prints 'Test' +--- set na:NOTES to "New Description". +--- print na:NOTES. //prints 'New Description' +function addalarm(AlarmType, UT, Name, Notes) end + +---@param alarmType string +---@return List +---If `alarmType` equals "All", returns `List` of *all* `KACAlarm` objects attached to current vessel or have no vessel attached. +---Otherwise returns `List` of all `KACAlarm` objects with `KACAlarm:TYPE` equeal to `alarmType` and attached to current vessel or have no vessel attached.:: +--- +--- set al to listAlarms("All"). +--- for i in al +--- { +--- print i:ID + " - " + i:name. +--- } +function listalarms(alarmType) end + +---@param alarmID string +---@return boolean +---Deletes alarm with ID equal to alarmID. Returns True if successful, false otherwise:: +--- +--- set na to addAlarm("Raw",time:seconds+300, "Test", "Notes"). +--- if (DELETEALARM(na:ID)) +--- { +--- print "Alarm Deleted". +--- } +function deletealarm(alarmID) end + +---@param path string +---argument 1 +--- Path of the file for editing. +--- +---Edits or creates a program file described by filename :code:`PATH`. +---If the file referred to by :code:`PATH` already exists, then it will +---open that file in the built-in editor. If the file referred to by +---:code:`PATH` does not already exist, then this command will create it +---from scratch and let you start editing it. +--- +---It is important to type the command using the filename's :code:`.ks` +---extension when using this command to create a new file. (Don't omit +---it like you sometimes can in other places in kOS). The logic to +---automatically assume the :code:`.ks` extension when the filename has +---no extension only works when kOS can find an existing file by doing so. +---If you are creating a brand new file from scratch with the :code:`EDIT` +---command, and leave off the :code:`.ks` extension, you will get a file +---created just like you described it (without the extension). +function edit(path) end + +---@param listType string | "bodies" | "targets" | "resources" | "parts" | "engines" | "rcs" | "sensors" | "elements" | "dockingports" | "files" | "volumes" | "processors" | "fonts" +---@return List +---Build a list with elements of the specified "listType" argument +function buildlist(listType) end + +---@param time TimeSpan | TimeStamp | number `TimeSpan` (ETA), `TimeStamp` (UT), or `number` (UT) +---@param radial number (m/s) Delta-V in radial-out direction +---@param normal number (m/s) Delta-V normal to orbital plane +---@param prograde number (m/s) Delta-V in prograde direction +---@return Node +---You can make a maneuver node in a variable using the :func:`NODE` function. +---The radial, normal, and prograde parameters represent the 3 axes you can +---adjust on the manuever node. The time parameter represents when the node +---is along a vessel's path. The time parameter has two different possible +---meanings depending on what kind of value you pass in for it. It's either +---an absolute time since the game started, or it's a relative time (ETA) +---from now, according to the following rule: +--- +---Using a TimeSpan for time means it's an ETA time offset +---relative to right now at the moment you called this function:: +--- +--- // Example: This makes a node 2 minutes and 30 seconds from now: +--- SET myNode to NODE( TimeSpan(0, 0, 0, 2, 30), 0, 50, 10 ). +--- // Example: This also makes a node 2 minutes and 30 seconds from now, +--- // but does it by total seconds (2*60 + 30 = 150): +--- SET myNode to NODE( TimeSpan(150), 0, 50, 10 ). +--- +--- Using a TimeStamp, or a Scalar number of seconds for time means +--- it's a time expressed in absolute universal time since game +--- start:: +--- +--- // Example: A node at: year 5, day 23, hour 1, minute 30, second zero: +--- SET myNode to NODE( TimeStamp(5,23,1,30,0), 0, 50, 10 ). +--- +--- // Using a Scalar number of seconds for time also means it's +--- // a time expressed in absolute universal time (seconds since +--- // epoch): +--- // Example: A node exactly one hour (3600 seconds) after the +--- // campaign started: +--- SET myNode to NODE( 3600, 0, 50, 10 ). +--- +--- Either way, once you have a maneuver node in a variable, you use the :global:`ADD` and :global:`REMOVE` commands to attach it to your vessel's flight plan. A kOS CPU can only manipulate the flight plan of its :ref:`CPU vessel `. +--- +---Once you have created a node, it's just a hypothetical node that hasn't +---been attached to anything yet. To attach a node to the flight path, you must use the command :global:`ADD` to attach it to the ship. +function node(time, radial, normal, prograde) end + +---@param x number (scalar) :math:`x` coordinate +---@param y number (scalar) :math:`y` coordinate +---@param z number (scalar) :math:`z` coordinate +---@return Vector +---This creates a new vector from 3 components in :math:`(x,y,z)`:: +--- +--- SET vec TO V(x,y,z). +--- +---Here, a new `Vector` called ``vec`` is created . The object `Vector` represents a `three-dimensional euclidean vector `__ To deeply understand most vectors in kOS, you have to understand a bit about the :ref:`underlying coordinate system of KSP `. If you are having trouble making sense of the direction the axes point in, go read that page. +function v(x, y, z) end + +---@param pitch number +---@param yaw number +---@param roll number +---@return Direction +---A `Direction` can be created out of a Euler Rotation, indicated with the :func:`R()` function, as shown below where the ``pitch``, ``yaw`` and ``roll`` values are in degrees:: +--- +--- SET myDir TO R( a, b, c ). +function r(pitch, yaw, roll) end + +---@param x number +---@param y number +---@param z number +---@param rot number +---@return Direction +---A `Direction` can also be created out of a *Quaternion* tuple, +---indicated with the :func:`Q()` function, passing it the x, y, z, w +---values of the Quaternion. +---`The concept of a Quaternion `__ +---uses complex numbers and is beyond the scope of the kOS +---documentation, which is meant to be simple to understand. It is +---best to not use the Q() function unless Quaternions are something +---you already understand. +--- +---:: +--- +--- SET myDir TO Q( x, y, z, w ). +function q(x, y, z, rot) end + +---@param inc number (`number`) inclination, in degrees. +---@param e number (`number`) eccentricity +---@param sma number (`number`) semi-major axis +---@param lan number (`number`) longitude of ascending node, in degrees. +---@param argPe number (`number`) argument of periapsis +---@param mEp number (`number`) mean anomaly at epoch, in degrees. +---@param t number (`number`) epoch +---@param body Body (`Body`) body to orbit around +---@return Orbit +---This creates a new orbit around the Mun:: +--- +--- SET myOrbit TO CREATEORBIT(0, 0, 270000, 0, 0, 0, 0, Mun). +function createorbit(inc, e, sma, lan, argPe, mEp, t, body) end + +---@param pos Vector (`Vector`) position (relative to center of body, NOT the usual relative to current ship most positions in kOS use. Remember to offset a kOS position from the body's position when calculating what to pass in here.) +---@param vel Vector (`Vector`) velocity +---@param body Body (`Body`) body to orbit around +---@param ut number (`number`) time (universal) +---@return Orbit +---This creates a new orbit around Kerbin:: +--- +--- SET myOrbit TO CREATEORBIT(V(2295.5, 0, 0), V(0, 0, 70000 + Kerbin:RADIUS), Kerbin, 0). +function createorbit(pos, vel, body, ut) end + +---@param fromVec Vector +---@param toVec Vector +---@return Direction +---A `Direction` can be created with the ``ROTATEFROMTO`` function. It is *one of the infinite number of* rotations that could rotate vector *fromVec* to become vector *toVec* (or at least pointing in the same direction as toVec, since fromVec and toVec need not be the same magnitude). Note the use of the phrase "**infinite number of**". Because there's no guarantee about the roll information, there are an infinite number of rotations that could qualify as getting you from one vector to another, because there's an infinite number of roll angles that could result and all still fit the requirement:: +--- +--- SET myDir to ROTATEFROMTO( v1, v2 ). +function rotatefromto(fromVec, toVec) end + +---@param lookAt Vector +---@param lookUp Vector +---@return Direction +---A `Direction` can be created with the LOOKDIRUP function by using two vectors. This is like converting a vector to a direction directly, except that it also provides roll information, which a single vector lacks. *lookAt* is a vector describing the Direction's FORE orientation (its local Z axis), and *lookUp* is a vector describing the direction's TOP orientation (its local Y axis). Note that *lookAt* and *lookUp* need not actually be perpendicualr to each other - they just need to be non-parallel in some way. When they are not perpendicular, then a vector resulting from projecting *lookUp* into the plane that is normal to *lookAt* will be used as the effective *lookUp* instead:: +--- +--- // Aim up the SOI's north axis (V(0,1,0)), rolling the roof to point to the sun. +--- LOCK STEERING TO LOOKDIRUP( V(0,1,0), SUN:POSITION ). +--- // +--- // A direction that aims normal to orbit, with the roof pointed down toward the planet: +--- LOCK normVec to VCRS(SHIP:BODY:POSITION,SHIP:VELOCITY:ORBIT). // Cross-product these for a normal vector +--- LOCK STEERING TO LOOKDIRUP( normVec, SHIP:BODY:POSITION). +function lookdirup(lookAt, lookUp) end + +---@param degrees number +---@param axisVector Vector +---@return Direction +---A `Direction` can be created with the ANGLEAXIS function. It represents a rotation of *degrees* around an axis of *axisVector*. To know which way a positive or negative number of degrees rotates, remember this is a left-handed coordinate system:: +--- +--- // Pick a new rotation that is pitched 30 degrees from the current one, taking into account +--- // the ship's current orientation to decide which direction is the 'pitch' rotation: +--- // +--- SET pitchUp30 to ANGLEAXIS(-30,SHIP:STARFACING). +--- SET newDir to pitchUp30*SHIP:FACING. +--- LOCK STEERING TO newDir. +function angleaxis(degrees, axisVector) end + +---@param lat number (deg) Latitude +---@param lng number (deg) Longitude +---@return GeoCoordinates +---This function creates a `GeoCoordinates` object with the given +---latitude and longitude, assuming the current SHIP's Body is the body +---to make it for. +--- +---Once created it can't be changed. The :attr:`GeoCoordinates:LAT` and +---:attr:`GeoCoordinates:LNG` suffixes are get-only (they cannot be +---set.) To switch to a new location, make a new call to :func:`LATLNG()`. +--- +---If you wish to create a `GeoCoordinates` object for a latitude +---and longitude around a *different* body than the ship's current sphere +---of influence body, see :meth:`Body:GEOPOSITIONLATLNG` for a means to do that. +--- +---It is also possible to obtain a `GeoCoordinates` from some suffixes of some other structures. For example:: +--- +--- SET spot to SHIP:GEOPOSITION. +function latlng(lat, lng) end + +---@param name string +---@return Vessel +---Get vessel with the specified name +function vessel(name) end + +---@param name string +---@return Body +---Get body with the specified name +function getbody(name) end + +---@param name string +---@return boolean +function bodyexists(name) end + +---@param name string +---@return Atmosphere +---Passing in a string (``name``) parameter, this function returns the +---:attr:`ATM ` of the body that has that name. It's identical +---to calling ``BODY(name):ATM``, but accomplishes the goal in fewer steps. +--- +---It will crash with an error if no such body is found in the game. +function bodyatmosphere(name) end + +---@param absOrigin Vector +---@param facing Direction +---@param relMin Vector +---@param relMax Vector +---@return Bounds +function bounds(absOrigin, facing, relMin, relMax) end + +---@param dir number +---@param pitch number +---@param roll? number +---@return Direction +---A `Direction` can be created out of a :func:`HEADING()` function. The first parameter is the compass heading, and the second parameter is the pitch above the horizon:: +--- +--- SET myDir TO HEADING(degreesFromNorth, pitchAboveHorizon). +--- +---The third parameter, *roll*, is optional. Roll indicates rotation about the longitudinal axis. +function heading(dir, pitch, roll) end + +---@param frequency number | string +---@param endFrequency number | string +---@param duration number +---@param keyDownLength? number +---@param volume? number +---@return Note +---This global function creates a note object that makes a sliding note +---that changes linearly from the start frequency to the end frequency +---across the duration of the note. +--- +---where: +--- +---``frequency`` +--- **Mandatory**: This is the frequency the sliding note begins at. +--- If it is a number, then it is the frequency in hertz (Hz). +--- If it is a string, then it's using the letter notation +--- :ref:`described here `. +---``endFrequency`` +--- **Mandatory**: This is the frequency the sliding note ends at. +--- If it is a number, then it is the frequency in hertz (Hz). +--- If it is a string, then it's using the letter notation +--- :ref:`described here `. +---``duration`` +--- **Mandatory**: Same as the duration for the :func:`NOTE()` +--- built-in function. If it is missing it will be the same thing +--- as the keyDownLength. +---``keyDownLength`` +--- **Optional**: Same as the keyDownLength for the :func:`NOTE()` +--- built-in function. +---``volume`` +--- **Optional**: Same as the volume for the :func:`NOTE()` +--- built-in function. +--- +---The note's frequency will change linearly from the starting to +---the ending frequency over the note's duration. (For example, If the +---duration is shorter, but all the other values are the kept the same, +---that makes the frequency change go faster so it can all fit within the +---given duration.) +--- +---You can make the note pitch up over time or pitch down over time +---depending on whether the endFrequency is higher or lower than +---the initial frequency. +--- +---This is an example of it being used in conjunction with the Voice's +---PLAY() suffix method:: +--- +--- SET V1 TO GETVOICE(0). +--- // A fast "whoop" sound that pitches up from 300 Hz to 600 Hz quickly: +--- V1:PLAY( SLIDENOTE(300, 600, 0.2, 0.25, 1) ). +function slidenote(frequency, endFrequency, duration, keyDownLength, volume) end + +---@param frequency number | string +---@param duration number +---@param keyDownLength? number +---@param volume? number +---@return Note +---This global function creates a note object from the given values. +--- +---where: +--- +---``frequency`` +--- **Mandatory**: The frequency can be given as either a number or a +--- string. If it is a number, then it is the frequency in hertz (Hz). +--- If it is a string, then it's using the letter notation +--- :ref:`described here `. +---``duration`` +--- **Mandatory**: The total amount of time the note takes up before +--- the next note can begin, *including* the small gap between the end +--- of its keyDownLength and the start of the next note. +--- Note that the value here gets multiplied by the voice's +--- :meth:`TEMPO` to decide the actual duration in seconds when +--- it gets played. +---``keyDownLength`` +--- **Optional**: The amount of time the note takes up before the +--- "synthesizer key" is released. In terms of the +--- :ref:`ADSR Envelope `, this is the portion of +--- the note's time taken up by the Attack, Decay, and Sustain part +--- of the note, but not including the Release part of the note. In +--- order to hear the note fade away during its Release portion, the +--- keyDownLength must be shorter than the Duration, or else there's +--- no gap of time to fit the release in before the next note starts. +--- By default, if you leave the KeyDownLength off, you get a default +--- KeyDownLength of 90% of the Duration, leaving 10% of the Duration +--- left to hear the "Release" time before the next note starts. +--- If you wish to force the notes to immediately blend from one to the +--- next with no audible gaps between them, then for each note you +--- need to specify a keyDownLength that is equal to the Duration. +--- Note that the value here gets +--- multiplied by the voice's :meth:`TEMPO` to decide the actual +--- duration in seconds when it gets played. +---``volume`` +--- **Optional**: If present, then the note can be given a different +--- volume than the default for the voice it's being played on, to +--- make it louder or quieter than the other notes this voice is +--- playing. This setting is a relative multiplier applied to the +--- voice's volume. (i.e. 1.0 means play at the same volume as the +--- voice's setting, 1.1 means play a bit louder than the voice's +--- setting, and 0.9 means play a bit quieter than the voice's +--- setting). +--- +---This is an example of it being used in conjunction with the Voice's +---:meth:`PLAY()` suffix method:: +--- +--- SET V1 TO GETVOICE(0). +--- V1:PLAY( NOTE(440, 0.2, 0.25, 1) ). +function note(frequency, duration, keyDownLength, volume) end + +---@param num number +---@return Voice +---To access one of the :ref:`voices ` of the +---:ref:`SKID ` chip, you use the ``GetVoice(num)`` built-in +---function. +--- +---Where ``num`` is the number of the hardware voice you're interested +---in accessing. (The numbering starts with the first voice being +---called 0). +function getvoice(num) end + +---This will stop all voices. If the voice is scheduled to play additional +---notes, they will not be played. If the voice in the middle of playing a note, +---that note will be stopped. +function stopallvoices() end + +---@param universal_time? number (`number`) +---@return TimeStamp +---:return: A :struct`TimeStamp` of the time represented by the seconds passed in. +---This creates a `TimeStamp` given a "universal time", +---which is a number of seconds since the current game began, +---IN GAMETIME. example: ``TIME(3600)`` will give you a +---`TimeSpan` representing the moment exactly 1 hour +---(3600 seconds) since the current game first began. +--- +---The parameter is OPTIONAL. If you leave it off, +---and just call ``TIMESTAMP()``, then you end up getting +---the current time, which is the same thing that :global:`TIME` +---gives you (without the parentheses). +function timestamp(universal_time) end + +---@param year number (`number`) +---@param day number (`number`) +---@param hour? number (`number`) [optional] +---@param min? number (`number`) [optional] +---@param sec? number (`number`) [optional] +---@return TimeStamp +---:return: A :struct`TimeStamp` of the time represented by the values passed in. +---This creates a `TimeStamp` given a year, day, hour-hand, +---minute-hand, and second-hand. +--- +---Because a `TimeStamp` is a calendar reckoning, the values +---you use for the year and the day should start counting at 1, not +---at 0. (The hour, minute, and second still start at zero). +--- +---In other words:: +--- +--- // Notice these are equal because year and day start at 1 not 0: +--- set t1 to TIMESTAMP(0). +--- set t2 to TIMESTAMP(1,1,0,0,0). +--- print t1:full. +--- print t2:full. // Prints same as above. +--- +---Note that the year and day are mandatory, but the remaining +---parameters are optional and if you leave them off it assumes you +---meant them to be zero (meaning it will give you a timestamp at +---the very start of that date, right at midnight 0:00:00 O'clock). +function timestamp(year, day, hour, min, sec) end + +---@param universal_time? number (`number`) +---@return TimeSpan +---:return: A :struct`TimeSpan` of the time represented by the seconds passed in. +---This creates a `TimeSpan` equal to the number of seconds +---passed in. Fractional seconds are allowed for more precise spans. +--- +---The parameter is OPTIONAL. If you leave it off, and just call +---``TIMESPAN()``, then you end up getting a timespan of zero duration. +function timespan(universal_time) end + +---@param year number (`number`) +---@param day number (`number`) +---@param hour? number (`number`) [optional] +---@param min? number (`number`) [optional] +---@param sec? number (`number`) [optional] +---@return TimeSpan +---:return: A :struct`TimeSpan` of the time represented by the values passed in. +---This creates a `TimeSpan` that lasts this number of years +---plus this number of days plus this number of hours plus this number +---of minutes plus this number of seconds. +--- +---Because a `TimeSpan` is NOT a calendar reckoning, but +---an actual duration, the values you use for the year and the day +---should start counting at 0, not at 1. +--- +---In other words:: +--- +--- // Notice these are equal because year and day start at 0 not 1: +--- set span1 to TIMESPAN(0). +--- set span2 to TIMESPAN(0,0,0,0,0). +--- print span1:full. +--- print span2:full. // Prints same as above. +--- +---Note that the year and day are mandatory in this function, but the +---remaining parameters are optional and if you leave them off it +---assumes you meant them to be zero (meaning it will give you a +---timespan exactly equal to that many years and days, with no leftover +---hours or minutes or seconds.) +function timespan(year, day, hour, min, sec) end + +---@param h number +---@param s number +---@param v number +---@return HSVA +---This global function creates a color from hue, saturation and value:: +--- +--- SET myColor TO HSV(h,s,v). +--- +--- `More Information about HSV `_, +--- +---where: +--- +---``h`` +--- A floating point number from 0.0 to 1.0 for the hue component. +---``s`` +--- A floating point number from 0.0 to 1.0 for the saturation component. +---``v`` +--- A floating point number from 0.0 to 1.0 for the value component. +function hsv(h, s, v) end + +---@param h number +---@param s number +---@param v number +---@param a number +---@return HSVA +---Same as :func:`HSV()` but with an alpha (transparency) channel:: +--- +--- SET myColor TO HSVA(h,s,v,a). +--- +---``h, s, v`` are the same as above. +--- +---``a`` +--- A floating point number from 0.0 to 1.0 for the alpha component. (1.0 means opaque, 0.0 means invisibly transparent). +function hsva(h, s, v, a) end + +---@param r number +---@param g number +---@param b number +---@return RGBA +---This global function creates a color from red green and blue values:: +--- +--- SET myColor TO RGB(r,g,b). +--- +---where: +--- +---``r`` +--- A floating point number from 0.0 to 1.0 for the red component. +---``g`` +--- A floating point number from 0.0 to 1.0 for the green component. +---``b`` +--- A floating point number from 0.0 to 1.0 for the blue component. +function rgb(r, g, b) end + +---@param r number +---@param g number +---@param b number +---@param a number +---@return RGBA +---Same as :func:`RGB()` but with an alpha (transparency) channel:: +--- +--- SET myColor TO RGBA(r,g,b,a). +--- +---``r, g, b`` are the same as above. +--- +---``a`` +--- A floating point number from 0.0 to 1.0 for the alpha component. (1.0 means opaque, 0.0 means invisibly transparent). +function rgba(r, g, b, a) end + +---@param start? Vector | Delegate +---@param vec? Vector | Delegate +---@param color? RGBA | Delegate +---@param label? string +---@param scale? number +---@param show? boolean +---@param width? number +---@param pointy? boolean +---@param wiping? boolean +---@return Vecdraw +---Both these two function names do the same thing. For historical +---reasons both names exist, but now they both do the same thing. +---They create a new ``vecdraw`` object that you can then manipulate +---to show things on the screen. +--- +---For an explanation what the parameters start, vec, color, label, scale, show, +---width, pointy, and wiping mean, they correspond to the same suffix names +---below in the table. +--- +---Here are some examples:: +--- +--- SET anArrow TO VECDRAW( +--- V(0,0,0), +--- V(a,b,c), +--- RGB(1,0,0), +--- "See the arrow?", +--- 1.0, +--- TRUE, +--- 0.2, +--- TRUE, +--- TRUE +--- ). +--- +--- SET anArrow TO VECDRAWARGS( +--- V(0,0,0), +--- V(a,b,c), +--- RGB(1,0,0), +--- "See the arrow?", +--- 1.0, +--- TRUE, +--- 0.2, +--- TRUE, +--- TRUE +--- ). +--- +---Vector arrows can also be created with dynamic positioning and color. To do +---this, instead of passing static values for the first three arguments of +---``VECDRAW()`` or ``VECDRAWARGS()``, you can pass a +---:ref:`Delegate ` for any of them, which returns a value of the +---correct type. Here's an example where the Start, Vec, and Color are all +---dynamically adjusted by anonymous delegates that kOS will frequently call +---for you as it draws the arrow:: +--- +--- // Small dynamically moving vecdraw example: +--- SET anArrow TO VECDRAW( +--- { return (6-4*cos(100*time:seconds)) * up:vector. }, +--- { return (4*sin(100*time:seconds)) * up:vector. }, +--- { return RGBA(1, 1, RANDOM(), 1). }, +--- "Jumping arrow!", +--- 1.0, +--- TRUE, +--- 0.2, +--- TRUE, +--- TRUE +--- ). +--- wait 20. // Give user time to see it in motion. +--- set anArrow:show to false. // Make it stop drawing. +--- +---In the above example, ``VECDRAW()`` detects that the first argument +---is a delegate, and it uses this information to decide to assign +---it into :attr:`VecDraw:STARTUPDATER`, instead of into :attr:`VecDraw:START`. +---Similarly it detects that the second argument is a delegate, so it +---assigns it into :attr:`VecDraw:VECUPDATER` instead of into :attr:`VecDraw:VEC`. +---And it does the same thing with the third argument, assigning it into +---:attr:`VecDraw:COLORUPDATER`, instead of :attr:`VecDraw:COLOR`. +--- +---All the parameters of the ``VECDRAW()`` and ``VECDRAWARGS()`` are +---optional. You can leave any of the lastmost parameters off and they +---will be given a default:: +--- +--- Set anArrow TO VECDRAW(). +--- +---Causes it to have these defaults: +--- +---.. list-table:: Defaults +--- :header-rows: 1 +--- :widths: 1 3 +--- +--- * - Suffix +--- - Default +--- +--- * - :attr:`START` +--- - V(0,0,0) (center of the ship is the origin) +--- * - :attr:`VEC` +--- - V(0,0,0) (no length, so nothing appears) +--- * - :attr:`COLO[U]R` +--- - White +--- * - :attr:`LABEL` +--- - Empty string "" +--- * - :attr:`SCALE` +--- - 1.0 +--- * - :attr:`SHOW` +--- - false +--- * - :attr:`WIDTH` +--- - 0.2 +--- * - :attr:`POINTY` +--- - true +--- * - :attr:`WIPING` +--- - true +--- +---Examples:: +--- +--- // Makes a red vecdraw at the origin, pointing 5 meters north, +--- // with defaults for the un-mentioned +--- // paramters LABEL, SCALE, SHOW, and WIDTH. +--- SET vd TO VECDRAW(V(0,0,0), 5*north:vector, red). +--- +---To make a `VecDraw` disappear, you can either set its :attr:`VecDraw:SHOW` to false or just :ref:`UNSET ` the variable, or re-assign it. An example using `VecDraw` can be seen in the documentation for :func:`POSITIONAT()`. +function VECDRAW(start, vec, color, label, scale, show, width, pointy, wiping) end + +---@param start? Vector | Delegate +---@param vec? Vector | Delegate +---@param color? RGBA | Delegate +---@param label? string +---@param scale? number +---@param show? boolean +---@param width? number +---@param pointy? boolean +---@param wiping? boolean +---@return Vecdraw +---Both these two function names do the same thing. For historical +---reasons both names exist, but now they both do the same thing. +---They create a new ``vecdraw`` object that you can then manipulate +---to show things on the screen. +--- +---For an explanation what the parameters start, vec, color, label, scale, show, +---width, pointy, and wiping mean, they correspond to the same suffix names +---below in the table. +--- +---Here are some examples:: +--- +--- SET anArrow TO VECDRAW( +--- V(0,0,0), +--- V(a,b,c), +--- RGB(1,0,0), +--- "See the arrow?", +--- 1.0, +--- TRUE, +--- 0.2, +--- TRUE, +--- TRUE +--- ). +--- +--- SET anArrow TO VECDRAWARGS( +--- V(0,0,0), +--- V(a,b,c), +--- RGB(1,0,0), +--- "See the arrow?", +--- 1.0, +--- TRUE, +--- 0.2, +--- TRUE, +--- TRUE +--- ). +--- +---Vector arrows can also be created with dynamic positioning and color. To do +---this, instead of passing static values for the first three arguments of +---``VECDRAW()`` or ``VECDRAWARGS()``, you can pass a +---:ref:`Delegate ` for any of them, which returns a value of the +---correct type. Here's an example where the Start, Vec, and Color are all +---dynamically adjusted by anonymous delegates that kOS will frequently call +---for you as it draws the arrow:: +--- +--- // Small dynamically moving vecdraw example: +--- SET anArrow TO VECDRAW( +--- { return (6-4*cos(100*time:seconds)) * up:vector. }, +--- { return (4*sin(100*time:seconds)) * up:vector. }, +--- { return RGBA(1, 1, RANDOM(), 1). }, +--- "Jumping arrow!", +--- 1.0, +--- TRUE, +--- 0.2, +--- TRUE, +--- TRUE +--- ). +--- wait 20. // Give user time to see it in motion. +--- set anArrow:show to false. // Make it stop drawing. +--- +---In the above example, ``VECDRAW()`` detects that the first argument +---is a delegate, and it uses this information to decide to assign +---it into :attr:`VecDraw:STARTUPDATER`, instead of into :attr:`VecDraw:START`. +---Similarly it detects that the second argument is a delegate, so it +---assigns it into :attr:`VecDraw:VECUPDATER` instead of into :attr:`VecDraw:VEC`. +---And it does the same thing with the third argument, assigning it into +---:attr:`VecDraw:COLORUPDATER`, instead of :attr:`VecDraw:COLOR`. +--- +---All the parameters of the ``VECDRAW()`` and ``VECDRAWARGS()`` are +---optional. You can leave any of the lastmost parameters off and they +---will be given a default:: +--- +--- Set anArrow TO VECDRAW(). +--- +---Causes it to have these defaults: +--- +---.. list-table:: Defaults +--- :header-rows: 1 +--- :widths: 1 3 +--- +--- * - Suffix +--- - Default +--- +--- * - :attr:`START` +--- - V(0,0,0) (center of the ship is the origin) +--- * - :attr:`VEC` +--- - V(0,0,0) (no length, so nothing appears) +--- * - :attr:`COLO[U]R` +--- - White +--- * - :attr:`LABEL` +--- - Empty string "" +--- * - :attr:`SCALE` +--- - 1.0 +--- * - :attr:`SHOW` +--- - false +--- * - :attr:`WIDTH` +--- - 0.2 +--- * - :attr:`POINTY` +--- - true +--- * - :attr:`WIPING` +--- - true +--- +---Examples:: +--- +--- // Makes a red vecdraw at the origin, pointing 5 meters north, +--- // with defaults for the un-mentioned +--- // paramters LABEL, SCALE, SHOW, and WIDTH. +--- SET vd TO VECDRAW(V(0,0,0), 5*north:vector, red). +--- +---To make a `VecDraw` disappear, you can either set its :attr:`VecDraw:SHOW` to false or just :ref:`UNSET ` the variable, or re-assign it. An example using `VecDraw` can be seen in the documentation for :func:`POSITIONAT()`. +function vecdrawargs(start, vec, color, label, scale, show, width, pointy, wiping) end + +---Sets all visible vecdraws to invisible, everywhere in this kOS CPU. +---This is useful if you have lost track of the handles to them and can't +---turn them off one by one, or if you don't have the variable scopes +---present anymore to access the variables that hold them. The system +---does attempt to clear any vecdraws that go "out of scope", however +---the "closures" that keep local variables alive for LOCK statements +---and for other reasons can keep them from every truely going away +---in some circumstances. To make the arrow drawings all go away, just call +---CLEARVECDRAWS() and it will have the same effect as if you had +---done ``SET varname:show to FALSE`` for all vecdraw varnames in the +---entire system. +function CLEARVECDRAWS() end + +---If you want to conveniently clear away all GUI windows that you +---created from this CPU, you can do so with the ``CLEARGUIS()`` +---built-in function. It will call :meth:`GUI:HIDE` and :meth:`GUI:DISPOSE` +---for all the gui windows that were made using this particular CPU part. +---(If you have multiple kOS CPUs, and some GUIs are showing that were made +---by other kOS CPUs, those will not be cleared by this.) +--- +---.. note:: +--- +--- This built-in function was added mainly so you have a way +--- to easily clean up after a program has crashed which left +--- behind some GUI windows that are now unresponsive because +--- the program isn't running anymore. +function clearguis() end + +---@param width number +---@param height? number +---@return GUI +---This is the first place any GUI control panel starts. +--- +---The GUI built-in function creates a new `GUI` object that you can then +---manipulate to build up a GUI. If no height is specified, it will resize +---automatically to fit the contents you put inside it. The width can be set +---to 0 to force automatic width resizing too:: +--- +--- SET my_gui TO GUI(200). +--- SET button TO my_gui:ADDBUTTON("OK"). +--- my_gui:SHOW(). +--- UNTIL button:TAKEPRESS WAIT(0.1). +--- my_gui:HIDE(). +--- +---See the "ADD" functions in the `BOX` structure for +---the other widgets you can add. +--- +---Warning: Setting BOTH width and height to 0 to let it choose automatic +---resizing in both dimensions will often lead to a look you won't like. +---You may find that to have some control over the layout you will need to +---specify one of the two dimensions and only let it resize the other. +function gui(width, height) end + +---@param orbitable Orbitable A `Vessel`, `Body` or other `Orbitable` object +---@param time TimeStamp | number Time of prediction +---@return Vector +---:type orbitable: `Orbitable` +---:type time: `TimeStamp` or `number` universal seconds +---:return: A position `Vector` expressed as the coordinates in the :ref:`ship-center-raw-rotation ` frame +--- +---Returns a prediction of where the `Orbitable` will be at some :ref:`universal Time `. If the `Orbitable` is a `Vessel`, and the `Vessel` has planned :ref:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. +--- +---*Refrence Frame:* The reference frame that the future position +---gets returned in is the same reference frame as the current position +---vectors use. In other words it's in ship:raw coords where the origin +---is the current ``SHIP``'s center of mass. +--- +---*Prerequisite:* If you are in a career mode game rather than a +---sandbox mode game, This function requires that you have your space +---center's buildings advanced to the point where you can make maneuver +---nodes on the map view, as described in :struct:`Career:CANMAKENODES`. +function positionat(orbitable, time) end + +---@param orbitable Orbitable A `Vessel`, `Body` or other `Orbitable` object +---@param time TimeStamp | number Time of prediction +---@return OrbitableVelocity +---:type orbitable: `Orbitable` +---:type time: `TimeStamp` or `number` universal seconds +---:return: An :ref:`ObitalVelocity ` structure. +--- +---Returns a prediction of what the :ref:`Orbitable's ` velocity will be at some :ref:`universal Time `. If the `Orbitable` is a `Vessel`, and the `Vessel` has planned :struct:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. +--- +---*Prerequisite:* If you are in a career mode game rather than a +---sandbox mode game, This function requires that you have your space +---center's buildings advanced to the point where you can make manuever +---nodes on the map view, as described in :struct:`Career:CANMAKENODES`. +--- +---*Refrence Frame:* The reference frame that the future velocity gets +---returned in is the same reference frame as the current velocity +---vectors use. In other words it's relative to the ship's CURRENT +---body it's orbiting just like ``ship:velocity`` is. For example, +---if the ship is currently in orbit of Kerbin, but will be in the Mun's +---SOI in the future, then the ``VELOCITYAT`` that future time will return +---is still returned relative to Kerbin, not the Mun, because that's the +---current reference for current velocities. Here is an example +---illustrating that:: +--- +--- // This example imagines you are on an orbit that is leaving +--- // the current body and on the way to transfer to another orbit: +--- +--- // Later_time is 1 minute into the Mun orbit patch: +--- local later_time is time:seconds + ship:obt:NEXTPATCHETA + 60. +--- local later_ship_vel is VELOCITYAT(ship, later_time):ORBIT. +--- local later_body_vel is VELOCITYAT(ship:obt:NEXTPATCH:body, later_time):ORBIT. +--- +--- local later_ship_vel_rel_to_later_body is later_ship_vel - later_body_vel. +--- +--- print "My later velocity relative to this body is: " + later_ship_vel. +--- print "My later velocity relative to the body I will be around then is: " + +--- later_ship_vel_rel_to_later_body. +function velocityat(orbitable, time) end + +---@param p Part | List | Element +---@param c RGBA +---@return HIGHLIGHT +---This global function creates a part highlight:: +--- +--- SET foo TO HIGHLIGHT(p,c). +--- +---where: +--- +---``p`` +--- A single :ref:`part `, a list of parts or an :ref:`element ` +---``c`` +--- A :ref:`color ` +function highlight(p, c) end + +---@param orbitable Orbitable A :Ref:`Vessel `, `Body` or other `Orbitable` object +---@param time TimeStamp | number Time of prediction +---@return Orbit +---:type orbitable: `Orbitable` +---:type time: `TimeStamp` or `number` universal seconds +---:return: An `Orbit` structure. +--- +---Returns the :ref:`Orbit patch ` where the `Orbitable` object is predicted to be at some :ref:`universal Time `. If the `Orbitable` is a `Vessel`, and the `Vessel` has planned :ref:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. +--- +---*Prerequisite:* If you are in a career mode game rather than a +---sandbox mode game, This function requires that you have your space +---center's buildings advanced to the point where you can make maneuver +---nodes on the map view, as described in :struct:`Career:CANMAKENODES`. +function orbitat(orbitable, time) end + +---@return Career +function career() end + +---@return List +---:return: `List` of `Waypoint` +--- +---This creates a `List` of `Waypoint` structures for all accepted contracts. Waypoints for proposed contracts you haven't accepted yet do not appear in the list. +function allwaypoints() end + +---@param name string (`string`) Name of the waypoint as it appears on the map or in the contract description +---@return Waypoint +---This creates a new Waypoint from a name of a waypoint you read from the contract paramters. Note that this only works on contracts you've accpted. Waypoints for proposed contracts haven't accepted yet do not actually work in kOS. +--- +---SET spot TO WAYPOINT("herman's folly beta"). +--- +---The name match is case-insensitive. +function waypoint(name) end + +---@param resourceName string +---@param from Part | Element | List +---@param to Part | Element | List +---@return Transfer +function transferall(resourceName, from, to) end + +---@param resourceName string +---@param from Part | Element | List +---@param to Part | Element | List +---@param amount number +---@return Transfer +function transfer(resourceName, from, to, amount) end + +---Clears the screen and places the cursor at the top left +function clearscreen() end + +---@param text string The message to show to the user on screen +---@param delay number How long to make the message remain onscreen before it goes away +---@param style number Where to show the message on the screen:; - 1 = upper left; - 2 = upper center; - 3 = upper right; - 4 = lower center +---@param size number +---@param colour RGBA +---@param doEcho boolean +---You can make text messages appear on the heads-up display, in the +---same way that the in-game stock messages appear, by calling the +---HUDTEXT function, as follows:: +--- +---HUDTEXT( string Message, +--- integer delaySeconds, +--- integer style, +--- integer size, +--- RGBA colour, +--- boolean doEcho). +--- +---Message +--- The message to show to the user on screen +---delaySeconds +--- How long to make the message remain onscreen before it goes away. +--- If another message is drawn while an old message is still displaying, +--- both messages remain, the new message scrolls up the old message. +---style +--- Where to show the message on the screen: +--- - 1 = upper left +--- - 2 = upper center +--- - 3 = upper right +--- - 4 = lower center +--- Note that all these locations have their own defined slightly +--- different fonts and default sizes, enforced by the stock KSP game. +---size +--- A number describing the font point size: NOTE that the actual size +--- varies depending on which of the above styles you're using. Some +--- of the locations have a magnifying factor attached to their fonts. +---colour +--- The colour to show the text in, using `one of the built-in colour names +--- or the RGB constructor to make one up <../structures/misc/colors.html>`__ +---doEcho +--- If true, then the message is also echoed to the terminal as "HUD: message". +--- +---Examples:: +--- +--- HUDTEXT("Warning: Vertical Speed too High", 5, 2, 15, red, false). +--- HUDTEXT("docking mode begun", 8, 1, 12, rgb(1,1,0.5), false). +function hudtext(text, delay, style, size, colour, doEcho) end + +---Activates the next stage if the cpu vessel is the active vessel. This will +---trigger engines, decouplers, and any other parts that would normally be +---triggered by manually staging. The default equivalent key binding is the +---space bar. As with other parameter-less functions, both ``STAGE.`` and +---``STAGE().`` are acceptable ways to call the function. +--- +---.. note:: +--- .. versionchanged:: 1.0.1 +--- +--- The stage function will automatically pause execution until the next +--- tick. This is because some of the results of the staging event take +--- effect immediately, while others do not update until the next time +--- that physics are calculated. Calling ``STAGE.`` is essentially +--- equivalent to:: +--- +--- STAGE. +--- WAIT 0. +--- +---.. warning:: +--- Calling the :global:`Stage` function on a vessel other than the active +--- vessel will throw an exception. +function stage() end + +---@param node Node +---To put a maneuver node into the flight plan of the current :ref:`CPU vessel ` (i.e. ``SHIP``), just :global:`ADD` it like so:: +--- +--- SET myNode to NODE( TIME:SECONDS+200, 0, 50, 10 ). +--- ADD myNode. +--- +---You should immediately see it appear on the map view when you do this. The :global:`ADD` command can add nodes anywhere within the flight plan. To insert a node earlier in the flight than an existing node, simply give it a smaller :attr:`ETA ` time and then :global:`ADD` it. +--- +---.. warning:: +--- As per the warning above at the top of the section, ADD won't work on vessels that are not the active vessel. +function add(node) end + +---@param node Node +---To remove a maneuver node from the flight path of the current :ref:`CPU vessel ` (i.e. ``SHIP``), just :global:`REMOVE` it like so:: +--- +--- REMOVE myNode. +--- +---.. warning:: +--- As per the warning above at the top of the section, REMOVE won't work on vessels that are not the active vessel. +function remove(node) end + +---@param timestamp number +---This is identical to :meth:`WARPTO` above. +---:: +--- +--- // These two do the same thing: +--- WARPTO(time:seconds + 60*60). // warp 1 hour into the future. +--- KUNIVERSE:TIMEWARP:WARPTO(time:seconds + 60*60). +function warpto(timestamp) end + +---@param volumeOrNameTag Volume | string (`Volume` | `String`) can be either an instance of `Volume` or a string +---@return KOSProcessor +---Depending on the type of the parameter value will either return the processor associated with the given +---`Volume` or the processor with the given name tag. +function processor(volumeOrNameTag) end + +---@param file VolumeItem | string +---@param volumeOrNameTag Volume | string +function runfileon(file, volumeOrNameTag) end + +---@param command string +---@param volumeOrNameTag Volume | string +function runcommandon(command, volumeOrNameTag) end + +---@param listType string | "bodies" | "targets" | "resources" | "parts" | "engines" | "rcs" | "sensors" | "elements" | "dockingports" | "files" | "volumes" | "processors" | "fonts" +---Display elements of the specified "listType" argument +function printlist(listType) end + +---@param v1 Vector (`Vector`) +---@param v2 Vector (`Vector`) +---@return Vector +---:return: The `vector cross-product `__ +---The vector `cross product `__ of two vectors in the order ``(v1,v2)`` returning a new `Vector`:: +--- +--- SET vec1 TO V(1,2,3). +--- SET vec2 TO V(2,3,4). +--- +--- // These will both print: V(-1,2,-1) +--- PRINT VCRS(vec1, vec2). +--- PRINT VECTORCROSSPRODUCT(vec1, vec2). +--- +---When visualizing the direction that a vector cross product will +---point, remember that KSP is using a :ref:`left-handed ` +---coordinate system, and this means a cross-product of two vectors +---will point in the opposite direction of what it would had KSP been +---using a right-handed coordinate system. +function vcrs(v1, v2) end + +---@param v1 Vector (`Vector`) +---@param v2 Vector (`Vector`) +---@return Vector +---:return: The `vector cross-product `__ +---The vector `cross product `__ of two vectors in the order ``(v1,v2)`` returning a new `Vector`:: +--- +--- SET vec1 TO V(1,2,3). +--- SET vec2 TO V(2,3,4). +--- +--- // These will both print: V(-1,2,-1) +--- PRINT VCRS(vec1, vec2). +--- PRINT VECTORCROSSPRODUCT(vec1, vec2). +--- +---When visualizing the direction that a vector cross product will +---point, remember that KSP is using a :ref:`left-handed ` +---coordinate system, and this means a cross-product of two vectors +---will point in the opposite direction of what it would had KSP been +---using a right-handed coordinate system. +function vectorcrossproduct(v1, v2) end + +---@param v1 Vector (`Vector`) +---@param v2 Vector (`Vector`) +---@return number +---:return: The `vector dot-product `__ +---This is the `dot product `__ of two vectors returning a scalar number. This is the same as :ref:`v1 * v2 `:: +--- +--- SET vec1 TO V(1,2,3). +--- SET vec2 TO V(2,3,4). +--- +--- // These are different ways to perform the same operation. +--- // All of them will print the value: 20 +--- // ------------------------------------------------------- +--- PRINT VDOT(vec1, vec2). +--- PRINT VECTORDOTPRODUCT(vec1, vec2). +--- PRINT vec1 * vec2. // multiplication of two vectors with asterisk "*" performs a VDOT(). +function vdot(v1, v2) end + +---@param v1 Vector (`Vector`) +---@param v2 Vector (`Vector`) +---@return number +---:return: The `vector dot-product `__ +---This is the `dot product `__ of two vectors returning a scalar number. This is the same as :ref:`v1 * v2 `:: +--- +--- SET vec1 TO V(1,2,3). +--- SET vec2 TO V(2,3,4). +--- +--- // These are different ways to perform the same operation. +--- // All of them will print the value: 20 +--- // ------------------------------------------------------- +--- PRINT VDOT(vec1, vec2). +--- PRINT VECTORDOTPRODUCT(vec1, vec2). +--- PRINT vec1 * vec2. // multiplication of two vectors with asterisk "*" performs a VDOT(). +function vectordotproduct(v1, v2) end + +---@param v1 Vector +---@param v2 Vector +---@return Vector +---This is a vector, ``v2`` with all of ``v1`` excluded from it. In other words, the projection of ``v2`` onto the plane that is normal to ``v1``. +function vxcl(v1, v2) end + +---@param v1 Vector +---@param v2 Vector +---@return Vector +---This is a vector, ``v2`` with all of ``v1`` excluded from it. In other words, the projection of ``v2`` onto the plane that is normal to ``v1``. +function vectorexclude(v1, v2) end + +---@param v1 Vector (`Vector`) +---@param v2 Vector (`Vector`) +---@return number +---:return: Angle between two vectors +---This returns the angle between v1 and v2. It is the same result as: +--- +---.. math:: +--- +--- \arccos\left( +--- \frac{ +--- \vec{v_1}\cdot\vec{v_2} +--- }{ +--- \left|\vec{v_1}\right|\cdot\left|\vec{v_2}\right| +--- } +--- \right) +--- +---or in **KerboScript**:: +--- +--- arccos( (VDOT(v1,v2) / (v1:MAG * v2:MAG) ) ) +function vang(v1, v2) end + +---@param v1 Vector (`Vector`) +---@param v2 Vector (`Vector`) +---@return number +---:return: Angle between two vectors +---This returns the angle between v1 and v2. It is the same result as: +--- +---.. math:: +--- +--- \arccos\left( +--- \frac{ +--- \vec{v_1}\cdot\vec{v_2} +--- }{ +--- \left|\vec{v_1}\right|\cdot\left|\vec{v_2}\right| +--- } +--- \right) +--- +---or in **KerboScript**:: +--- +--- arccos( (VDOT(v1,v2) / (v1:MAG * v2:MAG) ) ) +function vectorangle(v1, v2) end + +---@param volumeIdOrName? number | string +---@return Volume +---Will return a `Volume` structure representing the volume with a given +---id or name. You can omit the argument to create a `Volume` +---for the current volume. +function volume(volumeIdOrName) end + +---@param path string | VolumeItem | Volume +---@return Path +---Will create a `Path` structure representing the given path string. You +---can omit the argument to create a `Path` for the current directory. +function path(path) end + +---@param ... +---@return List +---Creates a List structure with arguments as elements +function list(...) end + +---@param ... +---@return Lexicon +---Creates a Lexicon structure with odd arguments as keys and even arguments as values +function lex(...) end + +---@param ... +---@return Lexicon +---Creates a Lexicon structure with odd arguments as keys and even arguments as values +function lexicon(...) end + +---@param ... +---@return Queue +---Creates a Queue structure with arguments as elements +function queue(...) end + +---@param ... +---@return UniqueSet +---Creates a UniqueSet structure with arguments as elements +function uniqueset(...) end + +---@param kp number +---@param ki number +---@param kd number +---@param minOutput number +---@param maxOutput number +---@param epsilon number +---@return PIDLoop +---Creates a new PIDLoop +function pidloop(kp, ki, kd, minOutput, maxOutput, epsilon) end + +---@param kp number +---@param ki number +---@param kd number +---@param minOutput number +---@param maxOutput number +---@return PIDLoop +---Creates a new PIDLoop +function pidloop(kp, ki, kd, minOutput, maxOutput) end + +---@param kp number +---@param ki number +---@param kd number +---@return PIDLoop +---Creates a new PIDLoop +function pidloop(kp, ki, kd) end + +---@param kp number +---@return PIDLoop +---Creates a new PIDLoop +function pidloop(kp) end + +---@return PIDLoop +---Creates a new PIDLoop +function pidloop() end + +---@param ... +---@return Stack +---Creates a Stack structure with arguments as elements +function stack(...) end + +---@param volume Volume | number | string +---Changes the current directory to the root directory of the specified volume. +---Volumes can be referenced by instances of `Volume`, their ID numbers +---or their names if they've been given one. Understanding how +---:ref:`volumes work ` is important to understanding this command. +function switch(volume) end + +---@param path string | VolumeItem | Volume +---Changes the current directory to the one pointed to by the :code:`PATH` +---argument. This command will fail if the path is invalid or does not point +---to an existing directory. +function cd(path) end + +---@param path string | VolumeItem | Volume +---Changes the current directory to the one pointed to by the :code:`PATH` +---argument. This command will fail if the path is invalid or does not point +---to an existing directory. +function chdir(path) end + +---@param fromPath string | VolumeItem | Volume +---@param toPath string | VolumeItem | Volume +---@return boolean +---Copies the file or directory pointed to by :code:`FROMPATH` to the location +---pointed to :code:`TOPATH`. Depending on what kind of items both paths point +---to the exact behaviour of this command will differ: +--- +---1. :code:`FROMPATH` points to a file +--- +--- - :code:`TOPATH` points to a directory +--- +--- The file from :code:`FROMPATH` will be copied to the directory. +--- +--- - :code:`TOPATH` points to a file +--- +--- Contents of the file pointed to by :code:`FROMPATH` will overwrite +--- the contents of the file pointed to by :code:`TOPATH`. +--- +--- - :code:`TOPATH` points to a non-existing path +--- +--- New file will be created at :code:`TOPATH`, along with any parent +--- directories if necessary. Its contents will be set to the contents of +--- the file pointed to by :code:`FROMPATH`. +--- +---2. :code:`FROMPATH` points to a directory +--- +--- If :code:`FROMPATH` points to a directory kOS will copy recursively all +--- contents of that directory to the target location. +--- +--- - :code:`TOPATH` points to a directory +--- +--- The directory from :code:`FROMPATH` will be copied inside the +--- directory pointed to by :code:`TOPATH`. +--- +--- - :code:`TOPATH` points to a file +--- +--- The command will fail. +--- +--- - :code:`TOPATH` points to a non-existing path +--- +--- New directory will be created at :code:`TOPATH`, along with any +--- parent directories if necessary. Its contents will be set to the +--- contents of the directory pointed to by :code:`FROMPATH`. +--- +---3. :code:`FROMPATH` points to a non-existing path +--- +--- The command will fail. +function copypath(fromPath, toPath) end + +---@param fromPath string | VolumeItem | Volume +---@param toPath string | VolumeItem | Volume +---@return boolean +---Moves the file or directory pointed to by :code:`FROMPATH` to the location +---pointed to :code:`TOPATH`. Depending on what kind of items both paths point +---to the exact behaviour of this command will differ, see :code:`COPYPATH` above. +function movepath(fromPath, toPath) end + +---@param path string | VolumeItem +---@return boolean +---Deleted the file or directory pointed to by :code:`FROMPATH`. Directories are +---removed along with all the items they contain. +function deletepath(path) end + +---@param object +---@param path string | VolumeItem | Volume +---@return VolumeFile +---Serializes the given object to JSON format and saves it under the given path. +--- +---Go to :ref:`Serialization page ` to read more about serialization. +function writejson(object, path) end + +---@param path string | VolumeItem +---Reads the contents of a file previously created using ``WRITEJSON`` and deserializes them. +--- +---Go to :ref:`Serialization page ` to read more about serialization. +function readjson(path) end + +---@param path string | VolumeItem | Volume +---@return boolean +---Returns true if there exists a file or a directory under the given path, +---otherwise returns false. Also see :meth:`Volume:EXISTS`. +function exists(path) end + +---@param path string | VolumeItem | Volume +---@return VolumeFile | VolumeDirectory +---Will return a `VolumeFile` or `VolumeDirectory` representing the item +---pointed to by :code:`PATH`. It will return a `boolean` false if there's +---nothing present under the given path. Also see :meth:`Volume:OPEN`. +function open(path) end + +---@param path string | VolumeItem +---@return VolumeFile +---Creates a file under the given path. Will create parent directories if needed. +---It will fail if a file or a directory already exists under the given path. +---Also see :meth:`Volume:CREATE`. +function create(path) end + +---@param path string | VolumeItem +---@return VolumeDirectory +---Creates a directory under the given path. Will create parent directories +---if needed. It will fail if a file or a directory already exists under the +---given path. Also see :meth:`Volume:CREATEDIR`. +function createdir(path) end + +---@param a number (deg) angle +---@return number +---:return: sine of the angle +--- +---:: +--- +--- PRINT SIN(6). // prints 0.10452846326 +function sin(a) end + +---@param a number (deg) angle +---@return number +---:return: cosine of the angle +--- +---:: +--- +--- PRINT COS(6). // prints 0.99452189536 +function cos(a) end + +---@param a number (deg) angle +---@return number +---:return: tangent of the angle +--- +---:: +--- +--- PRINT TAN(6). // prints 0.10510423526 +function tan(a) end + +---@param x number (`number`) +---@return number +---:return: (deg) angle whose sine is x +--- +---:: +--- +--- PRINT ARCSIN(0.67). // prints 42.0670648 +function arcsin(x) end + +---@param x number (`number`) +---@return number +---:return: (deg) angle whose cosine is x +--- +---:: +--- +--- PRINT ARCCOS(0.67). // prints 47.9329352 +function arccos(x) end + +---@param x number (`number`) +---@return number +---:return: (deg) angle whose tangent is x +--- +---:: +--- +--- PRINT ARCTAN(0.67). // prints 33.8220852 +function arctan(x) end + +---@param y number (`number`) +---@param x number (`number`) +---@return number +---:return: (deg) angle whose tangent is :math:`\frac{y}{x}` +--- +---:: +--- +--- PRINT ARCTAN2(0.67, 0.89). // prints 36.9727625 +--- +---The two parameters resolve ambiguities when taking the arctangent. See the `wikipedia page about atan2 `_ for more details. +function arctan2(y, x) end + +---@param a1 number +---@param a2 number +---@return number +---Gets the minimal angle between two angles +function anglediff(a1, a2) end + +---@param from number +---@param to number +---@param step number +---@return Range +function range(from, to, step) end + +---@param from number +---@param to number +---@return Range +function range(from, to) end + +---@param to number +---@return Range +function range(to) end + +---@param item +---@param column number +---@param row number +function printat(item, column, row) end + +---@param paramName string | "steering" | "throttle" | "wheelsteering" | "wheelthrottle" | "flightcontrol" +---@param enabled boolean +function toggleflybywire(paramName, enabled) end + +---@param mode string | "maneuver" | "prograde" | "retrograde" | "normal" | "antinormal" | "radialin" | "radialout" | "target" | "antitarget" | "stability" | "stabilityassist" +function selectautopilotmode(mode) end + +---@param value +---@param path VolumeFile | string +function logfile(value, path) end + +---Reboots the core +function reboot() end + +---Shutsdown the core +function shutdown() end + +---@param milliseconds number +---Deliberately cause physics lag by making the main game thread sleep. +function debugfreezegame(milliseconds) end + +---@param a number +---@return number +---Returns absolute value of input:: +--- +--- PRINT ABS(-1). // prints 1 +function abs(a) end + +---@param a number +---@param b number +---@return number +---Returns remainder from integer division. +---Keep in mind that it's not a traditional mathematical Euclidean division where the result is always positive. The result has the same absolute value as mathematical modulo operation but the sign is the same as the sign of dividend:: +--- +--- PRINT MOD(21,6). // prints 3 +--- PRINT MOD(-21,6). // prints -3 +function mod(a, b) end + +---@param a number +---@return number +---Rounds down to the nearest whole number:: +--- +--- PRINT FLOOR(1.887). // prints 1 +function floor(a) end + +---@param a number +---@param b number +---@return number +---Rounds down to the nearest place value:: +--- +--- PRINT CEILING(1.887,2). // prints 1.88 +function floor(a, b) end + +---@param a number +---@return number +---Rounds up to the nearest whole number:: +--- +--- PRINT CEILING(1.887). // prints 2 +function ceiling(a) end + +---@param a number +---@param b number +---@return number +---Rounds up to the nearest place value:: +--- +--- PRINT CEILING(1.887,2). // prints 1.89 +function ceiling(a, b) end + +---@param a number +---@return number +---Rounds to the nearest whole number:: +--- +--- PRINT ROUND(1.887). // prints 2 +function round(a) end + +---@param a number +---@param b number +---@return number +---Rounds to the nearest place value:: +--- +--- PRINT ROUND(1.887,2). // prints 1.89 +function round(a, b) end + +---@param a number +---@return number +---Returns square root:: +--- +--- PRINT SQRT(7.89). // prints 2.80891438103763 +function sqrt(a) end + +---@param a number +---@return number +---Gives the natural log of the provided number:: +--- +--- PRINT LN(2). // prints 0.6931471805599453 +function ln(a) end + +---@param a number +---@return number +---Gives the log base 10 of the provided number:: +--- +--- PRINT LOG10(2). // prints 0.30102999566398114 +function log10(a) end + +---@param a number +---@param b number +---@return number +---Returns The lower of the two values:: +--- +--- PRINT MIN(0,100). // prints 0 +function min(a, b) end + +---@param a number +---@param b number +---@return number +---Returns The higher of the two values:: +--- +--- PRINT MAX(0,100). // prints 100 +function max(a, b) end + +---@param key? +---@return number +---Returns the next random floating point number from a random +---number sequence. The result is always in the range [0..1] +--- +---This uses what is called a `pseudo-random number generator +---`_. +--- +---For basic usage you can leave the ``key`` parameter off and it +---works fine, like so: +--- +---Example, basic usage:: +--- +--- PRINT RANDOM(). //prints a random number +--- PRINT "Let's roll a 6-sided die 10 times:". +--- FOR n in range(0,10) { +--- +--- // To make RANDOM give you an integer in the range [0..n-1], you do this: +--- // floor(n*RANDOM()). +--- +--- // So for example : a die giving values from 1 to 6 is like this: +--- print (1 + floor(6*RANDOM())). +--- } +--- +---The parameter ``key`` is a string, and it's used when you want +---to track separate psuedo-random number sequences by name and +---have them be deterministically repeatable. *Like other +---string keys in kOS, this key is case-insensitive.* +--- +---* If you leave the parameter ``key`` off, you get the next number +--- from a default unnamed random number sequencer. +---* If you supply the parameter ``key``, you get the next number +--- from a named random number sequencer. You can invent however +--- many keys you like and each one is a new random number sequencer. +--- Supplying a key probably only means something if you have +--- previously used :func:`RANDOMSEED(key, seed)`. +--- +---The following example is more complex and shows the repeatability +---of the "random" sequence using seeds. For most simple uses you +---probably don't need to bother with this. If words like "random +---number seed" are confusing, you can probably skip this part and +---get by just fine with the basic usage shown above. (Explaining +---how pseudorandom number generators work is a bit beyond this +---page - check the wikipedia link above to learn more.) +--- +---Example, deterministic usage:: +--- +--- // create two different random number sequencers, both starting +--- // with seed 12345 so they should have the same exact values. +--- RANDOMSEED("sequence1",12345). +--- RANDOMSEED("sequence2",12345). +--- +--- PRINT "5 coin flips from SEQUENCE 1:". +--- FOR n in range(0,5) { +--- print choose "heads" if RANDOM("sequence1") < 0.5 else "tails". +--- } +--- +--- PRINT "5 coin flips from SEQUENCE 2, which should be the same:". +--- FOR n in range(0,5) { +--- print choose "heads" if RANDOM("sequence2") < 0.5 else "tails". +--- } +--- +--- PRINT "5 more coin flips from SEQUENCE 1:". +--- FOR n in range(0,5) { +--- print choose "heads" if RANDOM("sequence1") < 0.5 else "tails". +--- } +--- +--- PRINT "5 more coin flips from SEQUENCE 2, which should be the same:". +--- FOR n in range(0,5) { +--- print choose "heads" if RANDOM("sequence2") < 0.5 else "tails". +--- } +function random(key) end + +---@param key +---@param seed number +---No Return Value. +--- +---Initializes a new random number sequence from a seed, giving it a +---key name you can use to refer to it in future calls to :func:`RANDOM(key)` +--- +---Using this you can make psuedo-random number sequences that can be +---re-run using the same seed to get the same result a second time. +--- +---Parameter ``key`` is a string - a name you can use to refer to this +---random series later. Calls to ``RANDOMSEED`` that use different +---keys actually cause different new random number sequences to be +---created that are tracked separately from each other. *Like other +---string keys in kOS, this key is case-insensitive.* +--- +---Parameter ``seed`` is an integer - an initial value to cause a +---deterministic series of random numbers to come out of the random +---function. +--- +---Whenever you call ``RANDOMSEED(key, seed)``, it starts a new +---random number sequence using the integer seed you give it, and names +---that sequence with a string key you can use later to retrive +---values from that random number sequence. +--- +---Example:: +--- +--- RANDOMSEED("generator A",1000). +--- RANDOMSEED("generator B",1000). +--- PRINT "Generators A and B should emit identical ". +--- PRINT "sequences because they both started at seed 1000.". +--- PRINT "3 numbers from Generator A:". +--- PRINT floor(RANDOM("generator A")*100). +--- PRINT floor(RANDOM("generator A")*100). +--- PRINT floor(RANDOM("generator A")*100). +--- PRINT "3 numbers from Generator B - they should ". +--- PRINT "be the same as above:". +--- PRINT floor(RANDOM("generator B")*100). +--- PRINT floor(RANDOM("generator B")*100). +--- PRINT floor(RANDOM("generator B")*100). +--- +--- PRINT "Resetting generator A but not Generator B:". +--- RANDOMSEED("generator A",1000). +--- +--- PRINT "3 more numbers from Generator A which got reset". +--- PRINT "so they should match the first ones again:". +--- PRINT floor(RANDOM("generator A")*100). +--- PRINT floor(RANDOM("generator A")*100). +--- PRINT floor(RANDOM("generator A")*100). +--- PRINT "3 numbers from Generator B, which didn't get reset:". +--- PRINT floor(RANDOM("generator B")*100). +--- PRINT floor(RANDOM("generator B")*100). +--- PRINT floor(RANDOM("generator B")*100). +--- +--- +---If you call ``RANDOMSEED`` using the same key as a key you already used +---before, it just forgets the previous random number sequence and starts +---a new one using the new seed. You can use this to reset the sequence. +function randomseed(key, seed) end + +---@param a number (number) +---@return string +---:return: (string) single-character string containing the unicode character specified +--- +---:: +--- +--- PRINT CHAR(34) + "Apples" + CHAR(34). // prints "Apples" +function char(a) end + +---@param a string (string) +---@return number +---:return: (number) unicode number representing the character specified +--- +---:: +--- +--- PRINT UNCHAR("A"). // prints 65 +function unchar(a) end + diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/library/modules.lua b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/modules.lua new file mode 100644 index 0000000000..ece8899b68 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/modules.lua @@ -0,0 +1,2 @@ +callbacks = require("callbacks") +misc = require("misc") diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/library/structures.lua b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/structures.lua new file mode 100644 index 0000000000..5758f0ecc1 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/structures.lua @@ -0,0 +1,5905 @@ +---@class MessageQueue : Structure +---True if there are no messages in this queue. +---@field empty boolean +---Number of messages in this queue. +---@field length number +---Returns the first (oldest) message in the queue and removes it. Messages in the queue are always ordered by their arrival date. +---@field pop fun(): Message +---:return: `Message` +--- +---Returns the oldest message in the queue without removing it from the queue. +---@field peek fun(): Message +---Removes all messages from the queue. +---@field clear fun() +---:parameter message: `Message` message to be added +--- +---You can use this message to explicitly add a message to this queue. This will insert this exact message to the queue, all attributes that are normally +---added automatically by kOS (:attr:`Message:SENTAT`, :attr:`Message:RECEIVEDAT` and :attr:`Message:SENDER`) will not be changed. +---@field push fun(message: any) + +---@class Message : Structure +---Date this message was sent at. +---@field sentat TimeStamp +---Date this message was received at. +---@field receivedat TimeStamp +---:type: `Vessel` or `boolean` +--- +---Vessel which has sent this message, or a boolean false value if +---the sender vessel no longer exists. +--- +---If the sender of the message doesn't exist anymore (see the explanation +---for :attr:`HASSENDER`), this suffix will return a different type +---altogether. It will be a `boolean` (which is false). +--- +---You can check for this condition either by using the :attr:`HASSENDER` +---suffix, or by checking the ``:ISTYPE`` suffix of the sender to +---detect if it's really a vessel or not. +---@field sender any +---Because there can be a delay between when the message was sent and +---when it was processed by the receiving script, it's possibile that +---the vessel that sent the message might not exist anymore. It could +---have either exploded, or been recovered, or been merged into another +---vessel via docking. You can check the value of the ``:HASSENDER`` +---suffix to find out if the sender of the message is still a valid vessel. +---If :attr:`HASSENDER` is false, then :attr:`SENDER` won't give you an +---object of type `Vessel` and instead will give you just a +---`boolean` false. +---@field hassender boolean +---Content of this message. +---@field content any + +---@class Note : Structure +---@field frequency number +---@field endfrequency number +---@field volume number +---@field keydownlength number +---@field duration number + +---@class Voice : Structure +---Settable. +---:type: `number` (seconds) +--- +---The *Attack* setting of the SKID voice's +---:ref:`ADSR Envelope `. This value is +---in seconds (usually a fractional portion of a second). +---@field attack number +---Settable. +---:type: `number` (seconds) +--- +---The *Decay* setting of the SKID voice's +---:ref:`ADSR Envelope `. This value is +---in seconds (usually a fractional portion of a second). +---@field decay number +---Settable. +---:type: `number` in the range [0..1] +--- +---The *Sustain* setting of the SKID voice's +---:ref:`ADSR Envelope `. Unlike the other +---values in the ASDR Envelope, this setting is NOT a measure +---of time. This is a coefficient to multiply the volume by +---during the sustain portion of the notes that are being played +---on this voice. (i.e. 0.5 would mean "sustain at half volume"). +---@field sustain number +---Settable. +---:type: `number` (seconds) +--- +---The *Release* setting of the SKID voice's +---:ref:`ADSR Envelope `. This value is +---in seconds (usually a fractional portion of a second). +---Note, that in order for this setting to have any real +---effect, the notes that are being played have to +---have their :attr:`KeyDownLength` set to be shorter than +---their :attr:`Duration`. While conceptually the +---max value is 1.0, in practice it can often go higher because +---the KSP game setting for User Interface volume is usually only +---at 50%, and in that scenario putting a 1.0 here would put the +---max at 50%, *really*. Setting this value to 0 will silence the voice. +---@field volume number +---Settable. +---To select which of the SKID chip's +---:ref:`waveform generators ` you want this voice +---to use, set this to the string name of that waveform. If you +---use a string that isn't one of the ones listed there (i.e. +---"triangle", "noise", "square", etc) then the attempt to set this +---value will be ignored and it will remain at its previous value. +---@field wave string +---:parameter note_or_list: Either one `Note` or a `List` of `Note`'s +---:returns: None +--- +---To cause the SKID chip to actually emit a sound, you need to +---use this suffix method. There are two ways it can be called: +--- +---**Play just one note** : To play a single note, you can call +---PLAY(), passing it one note object. Usually you construct +---the note object on the fly as you call Play, like so:: +--- +--- SET V0 to GetVoice(0). +--- V0:PLAY(NOTE(440,0.5)). +--- +---**Play a list of notes** : To play a full list of notes (which +---could even encode an entire song), you can call PLAY, passing it +---a `List` of `Note`'s. It will recognize that it +---is receiving a list of notes, and begin playing through them +---one at a time, only playing the next note when the previous +---note's :attr:`DURATION` is finished:: +--- +--- SET V0 to GetVoice(0). +--- V0:PLAY( +--- LIST( +--- NOTE(440, 0.5), +--- NOTE(400, 0.2), +--- SLIDENOTE(410, 350, 0.3) +--- ) +--- ). +--- +---**Notes play in the background**: In *either case*, whether +---playing a single note or a list of notes, the ``PLAY()`` +---method will return immediately, *before even the first note +---has begun playing*. It queues the note(s) to play, rather +---than waiting for them to finish. This lets your main program +---continue doing its work without waiting for the sound to finish. +--- +---**Calling PLAY() again on the same voice aborts the previous +---PLAY()**: Because the notes play in the background, it's possible +---to execute another PLAY() call while a previous one hasn't +---finished its work yet. If you do this, then the previous thing +---that was playing will quit, to be replaced by the new thing. +--- +---**But PLAY() can be called simultaneously on different voices**: +---(In fact that's the whole point of having different voices.). +---Calling PLAY() again on a *different* voice number will not +---abort the previous call to PLAY(). It only aborts the previous +---PLAY() when it's being done on the *same* voice. +---@field play fun(note_or_list: any) +---:returns: None +--- +---Calling this method will tell the voice to stop playing notes. If there are +---any notes queued to be played, they will not be played. If a note is +---currently being played, that note will be stopped. +---@field stop fun() +---Settable. +---If this is set to true, then the PLAY() method of this voice will +---keep on playing the same list of notes continually (starting over +---with the first note after the last note has finished). Note that +---for the purpose of this, a play command that was only given a single +---note to play still counts as a 'song' that is one note long (i.e. +---it will keep repeating the same note continually). +---@field loop boolean +---Settable. +---**Get**: If this voice is currently playing a note or list of notes +---that was previously passed in to the ``PLAY()`` method, then this +---returns true. Note that if :attr:`LOOP` is true, then this +---will never become false unless you set it to become false. +--- +---**Set**: If you set this value to FALSE, that will force the voice +---to stop playing whatever it was playing, and shut it up. (Setting +---it to true doesn't really mean anything. It becomes true because +---the PLAY() method was called. You can't restart a song just by +---setting this to true because when it becomes false, the voice +---"throws away" its memory of the song it was playing.) +---@field isplaying boolean +---Settable. +---When the voice is playing a `Note` or (more usefully) a +---`List` of `Note`'s, it will stretch or shrink the +---durations of those notes by multiplying them by this scaling +---factor. At 1.0 (the default), that means that when a note +---*says* it lasts for 1 second, then it really does. But if +---this tempo was set to, say 1.5, then that would mean that each +---time a note claims it wants to play for 1 second, it would really +---end up playing for 1.5 seconds on this voice. (or if you set +---the tempo to 0.5, then all songs will play their notes at double +---speed (each note only lasting half as long as it "should").) +--- +---In other words, setting this to a value less than 1.0 will +---speed up the song, and setting it to a value greater than 1.0 +---will slow it down (which might be the opposite of what you'd +---expect with it being called "tempo", but what else should +---we have called it? "slowpo"?) +--- +---Changes to this value take effect as soon as the next note in +---the song starts. (You do not need to re-run the PLAY() method. +---It will change the speed in mid-song.) +--- +---Be aware that this *only* scales the timings of the `Note`'s +---:attr:`KEYDOWNLENGTH` and :attr:`DURATION` +---timings. It does not +---affect the timings in the :ref:`ADSR Envelope `, as +---those represent what are meant to be physical properties of the +---"instrument" the voice is playing on. This means if you set the +---tempo too fast, it will start cutting off the full duration of the +---"envelope" of the notes, if you are playing the notes with settings +---that have a slow attack or decay. +---@field tempo number + +---@class Core : KOSProcessor +---The kOS version currently running. +---@field version Version +---The vessel containing the current processor. +---@field vessel Vessel +---The element object containing the current processor. +---@field element Element +---The currently selected volume for the current processor. This may be useful to prevent deleting files on the Archive, or for interacting with multiple local hard disks. +---@field currentvolume Volume +---Returns this processsor's message queue. +---@field messages MessageQueue + +---@class Bounds : Structure +---Settable. +---The position of the origin point of the bounding box, expressed +---in absolute coordinates (what kOS calls the ship-raw reference +---frame, that the rest of the position vectors in kOS use.) +--- +---If this bounding box came from a Part, this will be the same as +---that part's ``Part:POSITION``, and will keep being "magically" +---updated to stay with that part's position if it moves or rotates +---(but see note below). +--- +---If this bounding box came from a vessel, this will be the same as +---that vessel's ``Vessel:PARTS[0]:POSITION`` (the position of its +---root part) and be "magically" updated to stay with that part's +---position if it moves or rotates (but see note below). It is +---NOT ``Vessel:position``, which is important. ``Vessel:position`` +---is the *center of mass* of a vessel. While kOS prefers to use +---CoM as the official position of a vessel most of the time, the fact +---that using fuel shifts the position of the CoM within the vessel made +---it impractical to use CoM for the vessel's bounding box origin. +--- +---**WARNING about using SET with this suffix:** *If this bounds box +---was obtained using :attr:`Part:BOUNDS` or :attr:`Vessel:BOUNDS`, +---then this suffix keeps changing its value to remain correct as the +---vessel rotates or moves. But ONLY if you restrict your use of this +---suffix to GET-only. If you ever SET this suffix, kOS stops that +---auto-updating so it won't override the value you gave. Generally, +---using SET on this suffix was only ever intended for Bounds you +---created manually with the BOUNDS() function.* +---@field absorigin Vector +---Settable. +---This defines the orientation of this bounding box's local +---reference frame, by providing a rotation that will get you +---from the bounding-box relative orientation (in which the +---X, Y, and Z axes are parallel to the bounding box's edges) +---to the absolute orientation (the ship-raw orientation the +---rest of kOS uses). +--- +---If this bounding box came from a Part, this will be the same as +---that part's ``Part:FACING``, and will keep being "magically" +---updated to stay aligned with that part's facing if it moves or +---rotates (but see note below). +--- +---If this bounding box came from a Vessel, this will be the same as +---that Vessel's ``Vessel:FACING``, and will keep being "magically" +---updated to stay aligned with that part's facing if it moves or +---rotates (but see note below). +--- +---**WARNING about using SET with this suffix:** *If this bounds box +---was obtained using :attr:`Part:BOUNDS` or :attr:`Vessel:BOUNDS`, +---then this suffix keeps changing its value to remain correct as the +---vessel rotates or moves. But ONLY if you restrict your use of this +---suffix to GET-only. If you ever SET this suffix, kOS stops that +---auto-updating so it won't override the value you gave. Generally, +---using SET on this suffix was only ever intended for Bounds you +---created manually with the BOUNDS() function.* +---@field facing Direction +---Settable. +---:type: `Vector` **in bounding-box relative reference frame** +--- +---A vector expressed in the bounding-box-relative reference frame +---(where the XYZ axes are parallel to the bounding box's edges). +--- +---This defines one corner of the bounding box. It is the +---"negative-most" corner of the box. If you drew a vector from +---the box's origin point to its "negative-most" corner, that would +---be this vector. By "negative-most" that simply means the corner +---where the X, Y, and Z coordinates have their smallest values. +---(again, in the bounding box's own reference frame, not the absolute +---world (ship-raw) frame.) +--- +---This corner will always be the diagonally opposite corner from +---:attr:`Bounds:RELMAX`. +--- +---If you SET this value, you are changing the size of the +---bounding box, making it larger (or smaller), as well as +---stretching or shrinking it, depending on the new value +---you pick. Doing so doesn't *actually* change the size of +---a part or vessel, and is really only useful if you are +---working with your own ``Bounds`` you created manually with +---the ``Bounds()`` built-in function. +--- +---Be careful when trying to "add" the RELMIN vector to other +---vectors in the game. It's not oriented in ship-raw coords. +---To rotate it into ship-raw coords you can multiply it by +---the bounds facing like so: ``MyBounds:FACING * MyBounds:RELMIN``. +---@field relmin Vector +---Settable. +---:type: `Vector` **in bounding-box relative reference frame** +--- +---A vector expressed in the bounding-box-relative reference frame +---(where the XYZ axes are parallel to the bounding box's edges). +--- +---This defines one corner of the bounding box. It is the +---"positive-most" corner of the box. If you drew a vector from +---the box's origin point to its "positive-most" corner, that would +---be this vector. By "positive-most" that simply means the corner +---where the X, Y, and Z coordinates have their greatest values. +---(again, in the bounding box's own reference frame, not the absolute +---world (ship-raw) frame.) +--- +---This corner will always be the diagonally opposite corner from +---:attr:`Bounds:RELMIN`. +--- +---If you SET this value, you are changing the size of the +---bounding box, making it larger (or smaller), as well as +---stretching or shrinking it, depending on the new value +---you pick. Doing so doesn't *actually* change the size of +---a part or vessel, and is really only useful if you are +---working with your own ``Bounds`` you created manually with +---the ``Bounds()`` built-in function. +--- +---Be careful when trying to "add" the RELMAX vector to other +---vectors in the game. It's not oriented in ship-raw coords. +---To rotate it into ship-raw coords you can multiply it by +---the bounds facing like so: ``MyBounds:FACING * MyBounds:RELMAX``. +---@field relmax Vector +---This is the same point as :attr:`Bounds:RELMIN`, except it has +---been rotated and translated into absolute coordinates (what +---kOS calls the ship-raw reference frame, that the rest of the +---position vectors in kOS use.) +--- +---You cannot SET this value, because it is generated +---from the ABSORIGIN, the FACING, and the RELMIN. +--- +---Calculating the ABSMIN could be done in kerboscript from the +---other Bounds suffixes (see example below), but this is provided +---for convenience:: +--- +--- // The following two print lines should print +--- // the same vector, within reason. (There may be a +--- // small floating point precision variance between them): +--- set B to ship:bounds. +--- print B:ABSMIN. +--- print B:ABSORIGIN + (B:FACING * B:RELMIN). +---@field absmin Vector +---This is the same point as :attr:`Bounds:RELMAX`, except it has +---been rotated and translated into absolute coordinates (what +---kOS calls the ship-raw reference frame, that the rest of the +---position vectors in kOS use.) +--- +---You cannot SET this value, because it is generated +---from the ABSORIGIN, the FACING, and the RELMAX. +--- +---Calculating the ABSMAX could be done in kerboscript from the +---other Bounds suffixes (see example below), but this is provided +---for convenience:: +--- +--- // The following two print lines should print +--- // the same vector, within reason. (There may be a +--- // small floating point precision variance between them): +--- set B to ship:bounds. +--- print B:ABSMAX. +--- print B:ABSORIGIN + (B:FACING * B:RELMAX). +---@field absmax Vector +---:type: `Vector` **in bounding-box relative reference frame** +--- +---The center of the bounding box, in its own relative reference frame. +---(Not the absolute ship-raw reference frame the rest of kOS uses.) +--- +---This is the offset between the bounding box's origin and its center. +--- +---The origin of a bounding box is often not at its center because a +---bounding box can extend further in one direction than the other. +---For example a vessel's root part is often up at the top of the rocket, +---such a vessel's bounding box will extend much further in the "aft" +---direction than it does in the "fore" direction. The wing parts in the +---game are often defined with their origin point at the base where they +---glue to the fuselage, not out in the middle of the wing. +--- +---Instead of being provided directly, this value could be calculated +---from the RELMIN and RELMAX. It's simply the point exactly halfway +---between those two opposite corners. +---@field relcenter Vector +---This is just the same thing as :attr:`Bounds:RELCENTER`, but +---in the absolute (ship-raw) reference frame which scripts might find +---more useful. +--- +---It's exactly equivalent to doing this:: +--- +--- MyBounds:ABSORIGIN + (MyBounds:FACING * MyBounds:RELCENTER). +--- +---Instead of being provided directly, this value could be calculated +---from the ABSMIN and ABSMAX. It's simply the point exactly halfway +---between those two opposite corners. +---@field abscenter Vector +---Settable. +---:type: `Vector` **in bounding-box relative reference frame** +--- +---A vector (in bounding-box relative reference frame, NOT the +---absolute (ship-raw) reference frame the rest of kOS uses) +---that describes where :attr:`Bounds:RELMAX` is, relative to +---to the box's center (rather than to its origin). +--- +---Note that the vector in the inverse direction of this one (that you'd +---get by multiplying it by -1), points from the center to +---the oppposite corner, the :attr:`Bounds:RELMIN`. +---@field extents Vector +---Settable. +---:type: `Vector` **in bounding-box relative reference frame** +--- +---A vector (in bounding-box relative reference frame, NOT the +---absolute (ship-raw) reference frame the rest of kOS uses) +---that describes the ray from RELMIN to RELMAX that goes diagonally +---across the whole box. It's always just the same thing you'd +---get if you took the :attr:`Bounds:EXTENTS` vector and multiplied +---it by the scalar 2. +---@field size Vector +---:parameter ray: The "that-a-way" `Vector` in absolute (ship-raw) reference frame. +---:return: `Vector` in absolute (ship-raw) referece frame. +--- +---Returns the position (in absolute (ship-raw) reference frame) of +---whichever of the 8 corners of this bounding box is "furthest" in +---the direction the ray vector is pointing. Useful when you want +---to know the furthest a bounding box extends in some gameworld +---direction. +--- +---Examples:: +--- +--- // Assume other_vessel has been set to some vessel nearby +--- // other than the current SHIP: +--- // +--- local ves_box is other_vessel:bounds. +--- local top is ves_box:furthestcorner(up:vector). +--- local bottom is ves_box:furthestcorner(-up:vector). +--- +--- local from_other_to_me is ship:position - other_vessel:position. +--- local nearest is ves_box:furthestcorner(from_other_to_me). +--- print "The closest point on the other vessel's bounds box is this far away:". +--- print nearest:mag. +--- +--- // A more complex example showing how you might use bounds boxes +--- // when trying to figure out how big a target vessel is so you +--- // know how to go around it: +--- // +--- local my_left is -ship:facing:starvector. +--- local leftmost is ves_box:furthestcorner(my_left). +--- print "In order to go around the other vessel, to the left, ". +--- pritn "I would need to shift myself this far to my left:". +--- print vdot(-ship:facing:starvector, leftmost). +---@field furthestcorner fun(ray: Vector): Vector +---The above-sea-level altitude reading from the bottom-most corner of +---this bounding box, toward whichever Body the current CPU vessel is +---orbiting. +--- +---Note that it's always using the CPU vessel's *current* +---body to decide which body is the one that defines the bounding +---box's "downward" direction for picking its bottom-most corner, +---and it uses that same body to decide what counts as "altitude", +---regardless of wether the bounds box is a bounds box of the current +---CPU vessel or something else. +--- +---To put it another way: You can't "read" what the altitude of a +---bounding box above the Mun is if your ship is currently +---in Kerbin's sphere of influence. If you are currently orbiting +---Kerbin, it will assume that the "bottom" of any Bounds box you +---refer to means "corner closest to Kerbin" and "altitude" means +---"distance from Kerbin's Sea level". Once your CPU vessel moves +---into the Mun's sphere of influence this will change it it will +---now assume that the "bottom" of a Bounds is the corner closest +---to the Mun and the altitude you care about is the altitude above +---the Mun. +--- +---This may seem like a limitation, but it really isn't, since you +---wouldn't be able to query a vessel or a part for its bounding +---box if that vessel was far enough away to be outside the loading +---distance and thus its full set of parts isn't "there". +---It would only be a limitation for cases where you are inventing +---your own bounds boxes from scratch. +---@field bottomalt number +---The radar-altitude reading from the bottom-most corner of +---this bounding box, toward whichever Body the current CPU vessel is +---orbiting. Same as :attr:`Bounds:BOTTOMALT` except for the +---difference between above-sea-level altitude versus radar altitude. +---@field bottomaltradar number + +---@class ScienceExperimentModule : PartModule +---Call this method to deploy and run this science experiment. This method will fail if the experiment already contains scientific +---data or is inoperable. +---@field deploy fun() +---Call this method to reset this experiment. This method will fail if the experiment is inoperable. +---@field reset fun() +---Call this method to transmit the results of the experiment back to Kerbin. This will render the experiment +---inoperable if it is not rerunnable. This method will fail if there is no data to send. +---@field transmit fun() +---Call this method to discard the data obtained as a result of running this experiment. +---@field dump fun() +---True if this experiment is no longer operable. +---@field inoperable boolean +---True if this experiment is deployed. +---@field deployed boolean +---True if this experiment can be run multiple times. +---@field rerunnable boolean +---True if this experiment has scientific data stored. +---@field hasdata boolean +---:type: `List` of `ScienceData` +--- +---List of scientific data obtained by this experiment +---@field data List + +---@class PartModule : Structure +---:test: string +--- +---Get the name of the module. Note that it's the same as the name given in the MODULE section of the Part.cfg file for the part. +---@field name string +---:test: `Part` +--- +---Get the `Part` that this PartModule is attached to. +---@field part Part +---:test: `List` of strings +--- +---Get a list of all the names of KSPFields on this PartModule that the kos script is CURRENTLY allowed to get or set with :GETFIELD or :SETFIELD. Note the Security access comments below. This list can become obsolete as the game continues running depending on what the PartModule chooses to do. +---@field allfields List +---:test: `List` of strings +--- +--- Similar to :ALLFIELDS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do. +---@field allfieldnames List +---:parameter name: (`string`) Name of the field +---:return: `boolean` +--- +---Return true if the given field name is currently available for use with :GETFIELD or :SETFIELD on this PartModule, false otherwise. +---@field hasfield fun(name: string): boolean +---:test: `List` of strings +--- +---Get a list of all the names of KSPEvents on this PartModule that the kos script is CURRENTLY allowed to trigger with :DOEVENT. Note the Security access comments below. This list can become obsolete as the game continues running depending on what the PartModule chooses to do. +---@field allevents List +---:test: `List` of strings +--- +--- Similar to :ALLEVENTS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do. +---@field alleventnames List +---:parameter name: (`string`) Name of the event +---:return: `boolean` +--- +---Return true if the given event name is currently available for use with :DOEVENT on this PartModule, false otherwise. +---@field hasevent fun(name: string): boolean +---:test: `List` of strings +--- +---Get a list of all the names of KSPActions on this PartModule that the kos script is CURRENTLY allowed to trigger with :DOACTION. Note the Security access comments below. +---@field allactions List +---:test: `List` of strings +--- +--- Similar to :ALLACTIONS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do. +---@field allactionnames List +---:parameter name: (`string`) Name of the action +---:return: `boolean` +--- +---Return true if the given action name is currently available for use with :DOACTION on this PartModule, false otherwise. +---@field hasaction fun(name: string): boolean +---:parameter name: (`string`) Name of the field +---:return: varies +--- +---Get the value of one of the fields that this PartModule has placed onto the rightclick menu for the part. Note the Security comments below. +---@field getfield fun(name: string): any +---:parameter name: (`string`) Name of the field +--- +---Set the value of one of the fields that this PartModule has placed onto the rightclick menu for the part. Note the Security comments below. +--- +---WARNING: This suffix is only settable for parts attached to the :ref:`CPU Vessel ` +--- +---SYMMETRY NOTE: There is one important difference between using +---SETFIELD to set a field versus what happens when you use the mouse +---to do it in the game's GUI. In the GUI, often if the part is +---in a 2x, 3x, 4x, 6x, or 8x symmetry group, setting a field on +---one part will cause the other parts' fields to also change along +---with it. Generally that does NOT happen when you use kOS to set +---the field. If you want to set the same value to all the parts in +---a symmetry group, you need to iterate over all the parts yourself +---using the part's :attr:`Part::SYMMETRYCOUNT` suffix to see how +---many symmetrical parts there are, and iterate over them with +---:attr:`Part:SYMMETRYPARTNER(index)`, calling ``SETFIELD`` on +---them one at a time. +---@field setfield fun(name: string, value: any) +---:parameter name: (`string`) Name of the event +--- +---Trigger an "event button" that is on the rightclick part menu at the moment. Note the Security comments below. +--- +---WARNING: This suffix is only callable for parts attached to the :ref:`CPU Vessel ` +---@field doevent fun(name: string) +---:parameter name: (`string`) Name of the action +---:parameter bool: (`boolean`) Value to set: True or False +--- +---Activate one of this PartModule's action-group-able actions, bypassing the action group system entirely by just activating it for this one part directly. The `boolean` value decides whether you are toggling the action ON or toggling it OFF. Note the Security comments below. +--- +---WARNING: This suffix is only callable for parts attached to the :ref:`CPU Vessel ` +---@field doaction fun(name: string, bool: boolean) + +---@class KOSProcessor : PartModule +---Indicates the current state of this processor. `OFF` - deactivated, `READY` - active, or `STARVED` - no power. +---@field mode string +---:returns: None +--- +---Activate this processor +---@field activate fun() +---:returns: None +--- +---Deactivate this processor +---@field deactivate fun() +---This processor's hard disk. +---@field volume Volume +---Settable. +---This processor's name tag +---@field tag string +---Settable. +---The filename for the boot file on this processor. This may be set to an empty :ref:`string ` “” or to “None” to disable the use of a boot file. +---@field bootfilename string +---:return: `Connection` +--- +---Returns your connection to this processor. +---@field connection Connection + +---@class ScienceContainerModule : PartModule +---:type: :ref:`Boolean ` +--- +---True if this container has scientific data stored. +---@field hasdata boolean +---:type: `List` of `ScienceData` +--- +---List of scientific data contained by this part +---@field data List +---Call this method to dump the particular experiment provided +---@field dumpdata fun(DATA: number) +---Call this method to run the unit's "collect all" action +---@field collectall fun() + +---@class Gimbal : PartModule +---Settable. +---Is this gimbal locked to neutral position and not responding to steering controls right now? When you set it to true it will snap the engine back to 0s for pitch, yaw and roll +---@field lock boolean +---Settable. +---Is the gimbal responding to pitch controls? Relevant only if the gimbal is not locked. +---@field pitch boolean +---Settable. +---Is the gimbal responding to yaw controls? Relevant only if the gimbal is not locked. +---@field yaw boolean +---Settable. +---Is the gimbal responding to roll controls? Relevant only if the gimbal is not locked. +---@field roll boolean +---Settable. +---:type: `number` (%) +--- +---Percentage of maximum range this gimbal is allowed to travel +---@field limit number +---:type: `number` (deg) +--- +---The maximum extent of travel possible for the gimbal along all 3 axis (Pitch, Yaw, Roll) +---@field range number +---A Measure of the rate of travel for the gimbal +---@field responsespeed number +---The gimbals current pitch, has a range of -1 to 1. Will always be 0 when LOCK is true +---@field pitchangle number +---The gimbals current yaw, has a range of -1 to 1. Will always be 0 when LOCK is true +---@field yawangle number +---The gimbals current roll, has a range of -1 to 1. Will always be 0 when LOCK is true +---@field rollangle number + +---@class Addons : Structure +---@field id RTAddon +---@field available fun(param1: string): boolean +---@field hasaddon fun(param1: string): boolean + +---@class Skin : Structure +---Settable. +---@field name Style +---Settable. +---@field name Style +---Settable. +---@field font string +---Settable. +---@field selectioncolor RGBA +---@field add fun(param1: string, param2: Style): Style +---@field get fun(param1: string): Style +---@field has fun(param1: string): boolean + +---@class TipDisplay : Label + +---@class Button : Label +---Settable. +---@field pressed boolean +---@field takepress boolean +---Settable. +---@field toggle boolean +---Settable. +---@field exclusive boolean +---Settable. +---@field ontoggle UserDelegate +---Settable. +---@field onclick UserDelegate + +---@class Slider : Widget +---Settable. +---@field value number +---Settable. +---@field min number +---Settable. +---@field max number +---Settable. +---@field onchange UserDelegate + +---@class Label : Widget +---Settable. +---@field text string +---Settable. +---@field image string +---Settable. +---@field textupdater UserDelegate +---Settable. +---@field tooltip string + +---@class PopupMenu : Button +---Settable. +---@field options List +---@field addoption fun(param1: any) +---Settable. +---@field value any +---Settable. +---@field index number +---@field clear fun() +---Settable. +---@field changed boolean +---Settable. +---@field maxvisible number +---Settable. +---@field onchange UserDelegate +---Settable. +---@field optionsuffix string + +---@class StyleState : Structure +---Settable. +---@field bg string +---Settable. +---@field textcolor RGBA + +---@class GUI : Box +---Settable. +---@field x number +---Settable. +---@field y number +---Settable. +---@field draggable boolean +---Settable. +---@field extradelay number +---Settable. +---@field skin Skin +---@field tooltip string + +---@class StyleRectOffset : Structure +---Settable. +---@field h number +---Settable. +---@field v number +---Settable. +---@field left number +---Settable. +---@field right number +---Settable. +---@field top number +---Settable. +---@field bottom number + +---@class ScrollBox : Box +---Settable. +---@field halways boolean +---Settable. +---@field valways boolean +---Settable. +---@field position Vector + +---@class Style : Structure +---@field margin StyleRectOffset +---@field padding StyleRectOffset +---@field border StyleRectOffset +---@field overflow StyleRectOffset +---Settable. +---@field width number +---Settable. +---@field height number +---Settable. +---@field hstretch boolean +---Settable. +---@field vstretch boolean +---Settable. +---@field bg string +---Settable. +---@field textcolor RGBA +---@field normal StyleState +---@field focused StyleState +---@field active StyleState +---@field hover StyleState +---@field on StyleState +---@field normal StyleState +---@field on StyleState +---@field focused StyleState +---@field on StyleState +---@field active StyleState +---@field on StyleState +---@field hover StyleState +---@field on StyleState +---Settable. +---@field font string +---Settable. +---@field fontsize number +---Settable. +---@field richtext boolean +---Settable. +---@field align string +---Settable. +---@field wordwrap boolean + +---@class Spacing : Widget +---Settable. +---@field amount number + +---@class Box : Widget +---@field addlabel fun(...): Label +---@field addtextfield fun(...): TextField +---@field addbutton fun(...): Button +---@field addradiobutton fun(...): Button +---@field addcheckbox fun(...): Button +---@field addpopupmenu PopupMenu +---@field addhslider fun(...): Slider +---@field addvslider fun(...): Slider +---@field addhbox Box +---@field addvbox Box +---@field addhlayout Box +---@field addvlayout Box +---@field addscrollbox ScrollBox +---@field addstack Box +---@field addspacing fun(...): Spacing +---@field addtipdisplay fun(...): Label +---@field widgets List +---@field radiovalue string +---Settable. +---@field onradiochange UserDelegate +---@field showonly fun(param1: Widget) +---@field clear fun() + +---@class TextField : Label +---Settable. +---@field changed boolean +---Settable. +---@field confirmed boolean +---Settable. +---@field onchange UserDelegate +---Settable. +---@field onconfirm UserDelegate + +---@class Widget : Structure +---Settable. +---@field enabled boolean +---Settable. +---@field visible boolean +---@field show fun() +---@field hide fun() +---@field dispose fun() +---Settable. +---@field style Style +---@field gui GUI +---@field parent Widget +---@field hasparent boolean + +---@class Vessel : Orbitable +---:parameter name: (`string`) Name of the parts +---:return: `List` of `Part` objects +--- +---Returns a list of all the parts that have this as their +---``Part:NAME``. The matching is done case-insensitively. For more information, see :ref:`ship parts and modules `. +---@field partsnamed fun(name: string): List +---:parameter namePattern: (`string`) Pattern of the name of the parts +---:return: `List` of `Part` objects +--- +---Returns a list of all the parts that have this Regex pattern in their +---``Part:NAME``. The matching is done identically as in :meth:`String:MATCHESPATTERN`\ . For more information, see :ref:`ship parts and modules `. +---@field partsnamedpattern fun(namePattern: string): List +---:parameter title: (`string`) Title of the parts +---:return: `List` of `Part` objects +--- +---Returns a list of all the parts that have this as their +---``Part:TITLE``. The matching is done case-insensitively. For more information, see :ref:`ship parts and modules `. +---@field partstitled fun(title: string): List +---:parameter titlePattern: (`string`) Patern of the title of the parts +---:return: `List` of `Part` objects +--- +---Returns a list of all the parts that have this Regex pattern in their +---``Part:TITLE``. The matching is done identically as in :meth:`String:MATCHESPATTERN`\ . For more information, see :ref:`ship parts and modules `. +---@field partstitledpattern fun(titlePattern: string): List +---:parameter name: (`string`) name, title or tag of the parts +---:return: `List` of `Part` objects +--- +---Return a list of all the parts that match this +---name regardless of whether that name is the Part:Name, the Part:Tag, or the Part:Title. It is effectively the distinct union of :PARTSNAMED(val), :PARTSTITLED(val), :PARTSTAGGED(val). The matching is done case-insensitively. For more information, see :ref:`ship parts and modules `. +---@field partsdubbed fun(name: string): List +---:parameter namePattern: (`string`) Pattern of the name, title or tag of the parts +---:return: `List` of `Part` objects +--- +---Return a list of parts that match this Regex pattern +---regardless of whether that name is the Part:Name, the Part:Tag, or the Part:Title. It is effectively the distinct union of :PARTSNAMEDPATTERN(val), :PARTSTITLEDPATTERN(val), :PARTSTAGGEDPATTERN(val). The matching is done identically as in :meth:`String:MATCHESPATTERN`\ . For more information, see :ref:`ship parts and modules `. +---@field partsdubbedpattern fun(namePattern: string): List +---:parameter name: (`string`) Name of the part modules +---:return: `List` of `PartModule` objects +--- +---Return a list of all the `PartModule` objects that +---match the given name. The matching is done case-insensitively. For more information, see :ref:`ship parts and modules `. +---@field modulesnamed fun(name: string): List +---:parameter group: (integer) the action group number +---:return: `List` of `Part` objects +--- +---one action triggered by the given action group. For more information, see :ref:`ship parts and modules `. +---@field partsingroup fun(group: string): List +---:parameter group: (integer) the action group number +---:return: `List` of `PartModule` objects +--- +---have at least one action triggered by the given action group. For more information, see :ref:`ship parts and modules `. +---@field modulesingroup fun(group: string): List +---:parameter tag: (`string`) Tag of the parts +---:return: `List` of `Part` objects +--- +---Returns a list of all the parts that have this name as their +---``Part:TAG`` value. The matching is done case-insensitively. For more information, see :ref:`ship parts and modules `. +---@field partstagged fun(tag: string): List +---:parameter tagPattern: (`string`) Pattern of the tag of the parts +---:return: `List` of `Part` objects +--- +---Returns a list of all the parts that match this Regex pattern in their +---``part:TAG`` value. The matching is done identically as in :meth:`String:MATCHESPATTERN`\ . For more information, see :ref:`ship parts and modules `. +---@field partstaggedpattern fun(tagPattern: string): List +---:return: `List` of `Part` objects +--- +---Return all parts who's nametag isn't blank. +---For more information, see :ref:`ship parts and modules `. +---@field alltaggedparts List +---:type: `List` of `Part` objects +--- +---A List of all the :ref:`parts ` on the vessel. ``SET FOO TO SHIP:PARTS.`` has exactly the same effect as ``LIST PARTS IN FOO.``. For more information, see :ref:`ship parts and modules `. +---@field parts List +---:type: `List` of `DockingPort` objects +--- +---A List of all the :ref:`docking ports ` on the Vessel. +---@field dockingports List +---@field decouplers List +---@field separators List +---:type: `List` of `Element` objects +--- +---A List of all the :ref:`elements ` on the Vessel. +---@field elements List +---:type: `List` of `Engine` objects +--- +---A List of all the :ref:`engines ` on the Vessel. +---@field engines List +---:type: `List` of `RCS` objects +--- +---A List of all the :ref:`RCS thrusters ` on the Vessel. +---@field rcs List +---The structure representing the raw flight controls for the vessel. +--- +---WARNING: This suffix is only gettable for :ref:`CPU Vessel ` +---@field control Control +---*relative* compass heading (degrees) to this vessel from the :ref:`CPU Vessel `, taking into account the CPU Vessel's own heading. +---@field bearing number +---*absolute* compass heading (degrees) to this vessel from the :ref:`CPU Vessel ` +---@field heading number +---Sum of all the :ref:`engines' THRUSTs ` of all the currently active engines In Kilonewtons. +---@field thrust number +---Sum of all the :ref:`engines' AVAILABLETHRUSTs ` of all the currently active engines taking into account their throttlelimits. Result is in Kilonewtons. +---@field availablethrust number +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---:type: `number` (kN) +--- +---Sum of all the :ref:`engines' AVAILABLETHRUSTATs ` of all the currently active engines taking into account their throttlelimits at the given atmospheric pressure. Result is in Kilonewtons. Use a pressure of 0 for vacuum, and 1 for sea level (on Kerbin). +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field availablethrustat fun(pressure: number): number +---Sum of all the :ref:`engines' MAXTHRUSTs ` of all the currently active engines In Kilonewtons. +---@field maxthrust number +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---:type: `number` (kN) +--- +---Sum of all the :ref:`engines' MAXTHRUSTATs ` of all the currently active engines In Kilonewtons at the given atmospheric pressure. Use a pressure of 0 for vacuum, and 1 for sea level (on Kerbin). +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field maxthrustat fun(pressure: number): number +---The way the vessel is pointed, which is also the rotation +---that would transform a vector from a coordinate space where the +---axes were oriented to match the vessel's orientation, to one +---where they're oriented to match the world's ship-raw coordinates. +--- +---i.e. ``SHIP:FACING * V(0,0,1)`` gives the direction the +---ship is pointed (it's Z-axis) in absolute ship-raw coordinates +---@field facing Direction +---Constructs a "bounding box" structure that can be used to +---give your script some idea of the extents of the vessel's shape - how +---wide, long, and tall it is. +--- +---It is rather expensive in terms of CPU time to call this suffix. +---(Calling :attr:`Part:BOUNDS` on ONE part on the ship is itself a +---*little* expensive, and this has to perform that same work on +---every part on the ship, finding the bounding box that would +---surround all the parts.) Because of that expense, kOS **forces** +---your script to give up its remaining instructions this update when +---you call this (It forces the equivalent of doing a ``WAIT 0.`` +---right after you call it). This is to discourage you from +---calling this suffix again and again in a fast loop. The proper +---way to use this suffix is to call it once, storing the result in +---a variable, and then use that variable repeatedly, rather than +---using the suffix itself repeatedly. Only call the suffix again +---when you have reason to expect the bounding box to change or +---become invalid, such as docking, staging, changing facing to a +---new control-from part, and so on. +--- +---More detailed information about how to read the bounds box, and +---what circumstances call for getting a re-generated copy of the +---bounds box, is found on the documentation page for `Bounds`. +---@field bounds Bounds +---Given in :ref:`SHIP_RAW ` reference frame. The vector +---represents the axis of the rotation (in left-handed convention, +---not right handed as most physics textbooks show it), and its +---magnitude is the angular momentum of the rotation, which varies +---not only with the speed of the rotation, but also with the angular +---inertia of the vessel. +--- +---Units are expressed in: (Megagrams * meters^2) / (seconds * radians) +--- +---(Normal SI units would use kilograms, but in KSP all masses use a +---1000x scaling factor.) +--- +---**Justification for radians here:** +---Unlike the trigonometry functions in kOS, this value uses radians +---rather than degrees. The convention of always expressing angular +---momentum using a formula that assumes you're using radians is a very +---strongly adhered to universal convention, for... reasons. +---It's so common that it's often not even explicitly +---mentioned in information you may find when doing a web search on +---helpful formulae about angular momentum. This is why kOS doesn't +---use degrees here. (That an backward compatibility for old scripts. +---It's been like this for quite a while.). +---@field angularmomentum Vector +---Angular velocity of the body's rotation about its axis (its +---day) expressed as a vector. +--- +---The direction the angular velocity points is in Ship-Raw orientation, +---and represents the axis of rotation. Remember that everything in +---Kerbal Space Program uses a *left-handed coordinate system*, which +---affects which way the angular velocity vector will point. If you +---curl the fingers of your **left** hand in the direction of the rotation, +---and stick out your thumb, the thumb's direction is the way the +---angular velocity vector will point. +--- +---The magnitude of the vector is the speed of the rotation. +--- +---Note, unlike many of the other parts of kOS, the rotation speed is +---expressed in radians rather than degrees. This is to make it +---congruent with how VESSEL:ANGULARMOMENTUM is expressed, and for +---backward compatibility with older kOS scripts. +---@field angularvel Vector +---:type: `number` (metric tons) +--- +---The mass of the ship +---@field mass number +---:type: `number` (m/s) +--- +---How fast the ship is moving. in the "up" direction relative to the SOI Body's sea level surface. +---@field verticalspeed number +---:type: `number` (m/s) +--- +---How fast the ship is moving in the two dimensional plane horizontal +---to the SOI body's sea level surface. The vertical component of the +---ship's velocity is ignored when calculating this. +--- +---.. note:: +--- +--- .. versionadded:: 0.18 +--- The old name for this value was SURFACESPEED. The name was changed +--- because it was confusing before. "surface speed" implied it's the +--- `number` magnitude of "surface velocity", but it wasn't, because of how +--- it ignores the vertical component. +---@field groundspeed number +---@field surfacespeed number +---:type: `number` (m/s) +--- +---How fast the ship is moving relative to the air. KSP models atmosphere as simply a solid block of air "glued" to the planet surface (the weather on Kerbin is boring and there's no wind). Therefore airspeed is generally the same thing as as the magnitude of the surface velocity. +---@field airspeed number +---Settable. +---The name of the vessel as it appears in the tracking station. When you set this, it cannot be empty. +---@field shipname string +---Settable. +---The name of the vessel as it appears in the tracking station. When you set this, it cannot be empty. +---@field name string +---Settable. +---The ship's type as described `on the KSP wiki `_. +---@field type string +---Structure holding summary information of sensor data for the vessel +---@field sensors VesselSensors +---:type: `number` (m/s) +--- +---(Deprecated with KSP 1.0 atmospheric model) +--- +---Terminal velocity of the vessel in freefall through atmosphere, based on the vessel's current altitude above sea level, and its drag properties. Warning, can cause values of Infinity if used in a vacuum, and kOS sometimes does not let you store Infinity in a variable. +--- +---.. note:: +--- +--- .. deprecated:: 0.17.2 +--- +--- Removed to account for significant changes to planetary atmosphere mechanics introduced in KSP 1.0 +---@field termvelocity number +---:type: `number` (ATM's) +--- +---Returns what the air pressure is in the atmosphere surrounding the vessel. +---The value is returned in units of sea-level Kerbin atmospheres. Many +---formulae expect you to plug in a value expressed in kiloPascals, or +---kPA. You can convert this value into kPa by multiplying it by +---`constant:ATMtokPa`. +---@field dynamicpressure number +---:type: `number` (ATM's) +--- +---Returns what the air pressure is in the atmosphere surrounding the vessel. +---The value is returned in units of sea-level Kerbin atmospheres. Many +---formulae expect you to plug in a value expressed in kiloPascals, or +---kPA. You can convert this value into kPa by multiplying it by +---`constant:ATMtokPa`. +---@field q number +---:type: :ref:`Boolean ` +--- +---True if the vessel is fully loaded into the complete KSP physics engine (false if it's "on rails"). +---See `LoadDistance` for details. +---@field loaded boolean +---:type: :ref:`Boolean ` +--- +---True if the vessel is fully unpacked. That is to say that all of the individual parts are loaded +---and can be interacted with. This allows docking ports to be targeted, and controls if some +---actions/events on parts will actually trigger. See `LoadDistance` for details. +---@field unpacked boolean +---The ROOTPART is usually the first `Part` that was used to begin the ship design - the command core. Vessels in KSP are built in a tree-structure, and the first part that was placed is the root of that tree. It is possible to change the root part in VAB/SPH by using Root tool, so ROOTPART does not always point to command core or command pod. Vessel:ROOTPART may change in flight as a result of docking/undocking or decoupling of some part of a Vessel. +---@field rootpart Part +---Returns the `Part` serving as the control reference, relative to +---which the directions (as displayed on the navball and returned in +---:attr:`FACING`) are determined. A part may be set as the control reference +---part by "Control From Here" action or :meth:`PART:CONTROLFROM` command +---(available for parts of specific types). **NOTE:** It is possible for this +---to return unexpected values if the root part of the vessel cannot serve as a +---control reference, and the control has not been directly selected. +---@field controlpart Part +---:type: `number` (metric tons) +--- +---The mass of the ship if all resources were empty +---@field drymass number +---:type: `number` (metric tons) +--- +---The mass of the ship if all resources were full +---@field wetmass number +---:type: `List` of `AggregateResource` objects +--- +---A List of all the :ref:`AggregateResources ` on the vessel. ``SET FOO TO SHIP:RESOURCES.`` has exactly the same effect as ``LIST RESOURCES IN FOO.``. +---@field resources List +---Returns the load distance object for this vessel. The suffixes of this object may be adjusted to change the loading behavior of this vessel. Note: these settings are not persistent across flight instances, and will reset the next time you launch a craft from an editor or the tracking station. +---@field loaddistance LoadDistances +---:type: :ref:`Boolean ` +--- +---It is possible to have a variable that refers to a vessel that +---doesn't exist in the Kerbal Space Program universe anymore, but +---did back when you first got it. For example: you could do: +---SET VES TO VESSEL("OTHER"). WAIT 10. And in that intervening +---waiting time, the vessel might have crashed into the ground. +---Checking :ISDEAD lets you see if the vessel that was previously +---valid isn't valid anymore. +---@field isdead boolean +---The current status of the vessel possible results are: `LANDED`, `SPLASHED`, `PRELAUNCH`, `FLYING`, `SUB_ORBITAL`, `ORBITING`, `ESCAPING` and `DOCKED`. +---@field status string +---:return: `number` +--- +---The total delta-v of this vessel in its current situation, using the stock +---calulations the KSP game shows in the staging list. Note that this is only +---as accurate as the stock KSP game's numbers are. +---@field deltav DeltaV +---:parameter num: `number` the stage number to query for +---:return: `DeltaV` +--- +---One stage's Delta-V info. Pass in the stage number for which stage. The +---curent stage can be found with ``:STAGENUM``, and they count down from +---there to stage 0 at the "top" of the staging list. +--- +---If you pass in a number that is less than zero, it will return the info about +---stage 0. If you pass in a number that is greater than the current stage, it +---will return the info about the current stage. In other words, if there are +---currently stages 5, 4, 3, 2, 1, and 0, then passing in -99 gives you stage 0, +---and passing in stage 9999 gets you stage 5. +---@field stagedeltav fun(num: number): DeltaV +---Tells you which stage number is current. Stage numbers always count down, which +---is backward from how you might usually refer to stages in most space lingo, but +---in KSP, it's how it's done. (Stage 5 on bottom, Stage 0 on top, for example). +--- +---e.g. if STAGENUM is 4, that tells you the vessel has 5 total stages remaining, +---numbered 4, 3, 2, 1, and 0. +---@field stagenum number +---@field latitude number +---@field longitude number +---@field altitude number +---:return: `List` of `CrewMember` objects +--- +---list of all :struct:`kerbonauts ` aboard this vessel +---@field crew List +---crew capacity of this vessel +---@field crewcapacity number +---:return: `Connection` +--- +---Returns your connection to this vessel. +---@field connection Connection +---:return: `MessageQueue` +--- +---Returns this vessel's message queue. You can only access this attribute for your current vessel (using for example `SHIP:MESSAGES`). +---@field messages MessageQueue +---:return: None +--- +---Call this method to start tracking the object. This is functionally the +---same as clicking on the "Start Tracking" button in the Tracking Station +---interface. The primary purpose is to change asteroids from being displayed +---in the tracking station or on the map as ``"Unknown"`` to being displayed as +---``"SpaceObject"``. By doing so, the asteroid will not be de-spawned by +---KSP's asteroid management system. +--- +---.. note:: +--- This does not change the value returned by :attr:`Vessel:TYPE`. KSP +--- internally manages the "discovery information" for vessels, including +--- assteroids, in a different system. As a result, the value kOS reads for +--- ``TYPE`` may be different from that displayed on the map. +---@field starttracking fun() +---:return: None +--- +---Call this method to stop tracking an asteroid or asteroid-like object. +---This is functionally the same as using the Tracking Station interface +---to tell KSP to forget the asteroid. Doing so also tells the Tracking +---Station that it's okay to de-spawn the object if it feels the need +---to clean it up to avoid clutter. +---@field stoptracking fun() +---Returns the size class for an asteroid or asteroid-like object (which +---is modeled in the game as a vessel). (i.e. class A, B, C, D, or E +---for varying size ranges of asteroid.) For objects that the tracking +---station is tracking but you have not yet rendezvous'ed with, sometimes +---all the game lets you know is the general class and not the specific +---dimensions or mass. +--- +---If you are not tracking the object yet, the returned string can come +---back as "UNKNOWN" rather than one of the known class sizes. +---@field sizeclass string +---@field soichangewatchers UniqueSet + +---@class Addon : Structure +---@field available boolean + +---@class TimeStamp : TimeBase +---@operator add(TimeStamp):TimeStamp +---@operator add(TimeSpan):TimeStamp +---@operator add(number):TimeStamp +---@operator sub(TimeStamp):TimeSpan +---@operator sub(TimeSpan):TimeStamp +---@operator sub(number):TimeStamp +---@operator mul(TimeStamp):TimeStamp +---@operator mul(TimeSpan):TimeStamp +---@operator mul(number):TimeStamp +---@operator div(TimeStamp):TimeStamp +---@operator div(TimeSpan):TimeStamp +---@operator div(number):TimeStamp +---Settable. +---Year-hand number. Note that the first year of the game, at "epoch" +---time is actullay year 1, not year 0. +---@field year number +---Settable. +---:type: `number` (range varies by universe) +--- +---Day-hand number. Kerbin has 426 days in its year if using Kerbin's +---6 hour day (one fourth of that if :attr:`Kuniverse:HOURSPERDAY` is +---24 meaning the game is configured to show Earthlike days not Kerbin +---days.) +--- +---Also note that with mods installed you might not be looking at +---the stock universe, which could change the range this field could +---be if it changes how long a year is in your solar system. +--- +---Note that the first day of the year is actually day 1, not day 0. +---@field day number +---Settable. +---:type: `number` (0-5) or (0-23) +--- +---Hour-hand number. Note the setting :attr:`Kuniverse:HOURSPERDAY` affects +---whether this will be a number from 0 to 5 (6 hour day) or a number +---from 0 to 23 (24 hour day). +---@field hour number +---Settable. +---:type: `number` (0-59) +--- +---Minute-hand number +---@field minute number +---Settable. +---:type: `number` (0-59) +--- +---Second-hand number. +---@field second number +---:type: `number` (float) +--- +---Total Seconds since Epoch. Epoch is defined as the moment your +---current saved game's universe began (the point where you started +---your campaign). Can be very precise to the current "physics tick" +---and store values less than one second. (i.e. a typical value +---might be something like 50402.103019 seconds). Please note +---that if you print this in a loop again and again it will appear +---to be "frozen" for a bit before moving in discrete jumps. This +---reflects the fact that Kerbal Space Program is a computer simulated +---world where time advances in discrete chunks, not smoothly. +---@field seconds number +---The full string for the timestamp. (Down to the second anyway. Fractions of +---seconds not shown), including year, day, hour, minute, and second. +---The format is: +--- +---``Year XX Day XX HH:MM:SS`` +---@field full string +---Time in (HH:MM:SS) format. Does not show which year or day. +---@field clock string +---Date in ``Year XX Day XX`` format. +---@field calendar string + +---@class ScienceData : Structure +---Amount of data +---@field dataamount number +---Amount of science that would be gained by returning this data to KSC +---@field sciencevalue number +---Amount of science that would be gained by transmitting this data to KSC +---@field transmitvalue number +---Experiment title +---@field title string + +---@class Vecdraw : Structure +---Settable. +---Mandatory - The vector to draw, SHIP-RAW Coords. +---@field vec Vector +---Settable. +---Mandatory - The vector to draw, SHIP-RAW Coords. +---@field vector Vector +---Settable. +---:type: `KosDelegate` with no parameters, returning a `Vector` +--- +---This allows you to tell the VecDraw that you'd like it to update the ``VEC`` suffix +---of the vector regularly every update, according to your own scripted code. +--- +---You create a `KosDelegate` that takes no parameters, and returns a vector, +---which the system will automatically assign to the :attr:`VEC` suffix every update. +---Be aware that this system does eat into the instructions available per update, so if +---you make this delegate do too much work, it will slow down your script's performance. +--- +---To make the system stop calling your delegate, set this suffix to the magic +---keyword :global:`DONOTHING`. +--- +---Example:: +--- +--- // This example will spin the arrow around in a circle by leaving the start +--- // where it is but moving the tip by trig functions: +--- set vd to vecdraw(v(0,0,0), v(5,0,0), green, "spinning arrow", 1.0, true, 0.2). +--- print "Moving the arrow in a circle for a few seconds.". +--- set vd:vecupdater to { +--- return ship:up:vector*5*sin(time:seconds*180) + ship:north:vector*5*cos(time:seconds*180). }. +--- wait 5. +--- print "Stopping the arrow movement.". +--- set vd:vecupdater to DONOTHING. +--- wait 3. +--- print "Removing the arrow.". +--- set vd to 0. +--- +--- +---.. versionadded:: 1.1.0 +--- +--- scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0 +---@field vecupdater UserDelegate +---Settable. +---:type: `KosDelegate` with no parameters, returning a `Vector` +--- +---This allows you to tell the VecDraw that you'd like it to update the ``VEC`` suffix +---of the vector regularly every update, according to your own scripted code. +--- +---You create a `KosDelegate` that takes no parameters, and returns a vector, +---which the system will automatically assign to the :attr:`VEC` suffix every update. +---Be aware that this system does eat into the instructions available per update, so if +---you make this delegate do too much work, it will slow down your script's performance. +--- +---To make the system stop calling your delegate, set this suffix to the magic +---keyword :global:`DONOTHING`. +--- +---Example:: +--- +--- // This example will spin the arrow around in a circle by leaving the start +--- // where it is but moving the tip by trig functions: +--- set vd to vecdraw(v(0,0,0), v(5,0,0), green, "spinning arrow", 1.0, true, 0.2). +--- print "Moving the arrow in a circle for a few seconds.". +--- set vd:vecupdater to { +--- return ship:up:vector*5*sin(time:seconds*180) + ship:north:vector*5*cos(time:seconds*180). }. +--- wait 5. +--- print "Stopping the arrow movement.". +--- set vd:vecupdater to DONOTHING. +--- wait 3. +--- print "Removing the arrow.". +--- set vd to 0. +--- +--- +---.. versionadded:: 1.1.0 +--- +--- scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0 +---@field vectorupdater UserDelegate +---Settable. +---:type: :ref:`Color ` +--- +---Optional, defaults to white. This is the color to draw the vector. +---If you leave the :attr:`VecDraw:WIPING` suffix at its default value +---of True, then there will be a wipe effect such that the line will +---fade-in as it goes, only becoming this color at the endpoint tip. +--- +---(You can pass in an RGBA with an alpha value less than 1.0 if you +---would like the line to never be fully opaque even at the tip.) +---@field color RGBA +---Settable. +---:type: :ref:`Color ` +--- +---Optional, defaults to white. This is the color to draw the vector. +---If you leave the :attr:`VecDraw:WIPING` suffix at its default value +---of True, then there will be a wipe effect such that the line will +---fade-in as it goes, only becoming this color at the endpoint tip. +--- +---(You can pass in an RGBA with an alpha value less than 1.0 if you +---would like the line to never be fully opaque even at the tip.) +---@field colour RGBA +---Settable. +---:type: `KosDelegate` with no parameters, returning a `Color` +--- +---This allows you to tell the VecDraw that you'd like it to update the ``COLOR``/``COLOUR`` +---suffix of the vector regularly every update, according to your own scripted code. +--- +---You create a `KosDelegate` that takes no parameters, and returns a Color, +---which the system will automatically assign to the :attr:`COLOR` suffix every update. +---Be aware that this system does eat into the instructions available per update, so if +---you make this delegate do too much work, it will slow down your script's performance. +--- +---To make the system stop calling your delegate, set this suffix to the magic +---keyword :global:`DONOTHING`. +--- +---Example:: +--- +--- // This example will change how opaque the arrow is over time by changing +--- // the 'alpha' of its color: +--- set vd to vecdraw(v(0,0,0), ship:north:vector*5, green, "fading arrow", 1.0, true, 0.2). +--- print "Fading the arrow in and out for a few seconds.". +--- set vd:colorupdater to { return RGBA(0,1,0,sin(time:seconds*180)). }. +--- wait 5. +--- print "Stopping the color change.". +--- set vd:colorupdater to DONOTHING. +--- wait 3. +--- print "Removing the arrow.". +--- set vd to 0. +--- +--- +---.. versionadded:: 1.1.0 +--- +--- scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0 +---@field colorupdater UserDelegate +---Settable. +---:type: `KosDelegate` with no parameters, returning a `Color` +--- +---This allows you to tell the VecDraw that you'd like it to update the ``COLOR``/``COLOUR`` +---suffix of the vector regularly every update, according to your own scripted code. +--- +---You create a `KosDelegate` that takes no parameters, and returns a Color, +---which the system will automatically assign to the :attr:`COLOR` suffix every update. +---Be aware that this system does eat into the instructions available per update, so if +---you make this delegate do too much work, it will slow down your script's performance. +--- +---To make the system stop calling your delegate, set this suffix to the magic +---keyword :global:`DONOTHING`. +--- +---Example:: +--- +--- // This example will change how opaque the arrow is over time by changing +--- // the 'alpha' of its color: +--- set vd to vecdraw(v(0,0,0), ship:north:vector*5, green, "fading arrow", 1.0, true, 0.2). +--- print "Fading the arrow in and out for a few seconds.". +--- set vd:colorupdater to { return RGBA(0,1,0,sin(time:seconds*180)). }. +--- wait 5. +--- print "Stopping the color change.". +--- set vd:colorupdater to DONOTHING. +--- wait 3. +--- print "Removing the arrow.". +--- set vd to 0. +--- +--- +---.. versionadded:: 1.1.0 +--- +--- scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0 +---@field colourupdater UserDelegate +---Settable. +---Set to true to make the arrow appear, false to hide it. Defaults to false until you're ready to set it to true. +---@field show boolean +---Settable. +---Optional, defaults to V(0,0,0) - position of the tail of the vector to draw in SHIP-RAW coords. V(0,0,0) means the ship Center of Mass. +---@field start Vector +---Settable. +---:type: `KosDelegate` with no parameters, returning a `Vector` +--- +---This allows you to tell the VecDraw that you'd like it to update the START position +---of the vector regularly every update, according to your own scripted code. +--- +---You create a `KosDelegate` that takes no parameters, and returns a vector, +---which the system will automatically assign to the :attr:`START` suffix every update. +---Be aware that this system does eat into the instructions available per update, so if +---you make this delegate do too much work, it will slow down your script's performance. +--- +---To make the system stop calling your delegate, set this suffix to the magic +---keyword :global:`DONOTHING`. +--- +---Example:: +--- +--- // This example will bounce the arrow up and down over time for a few seconds, +--- // moving the location of the vector's start according to a sine wave over time: +--- set vd to vecdraw(v(0,0,0), ship:north:vector*5, green, "bouncing arrow", 1.0, true, 0.2). +--- print "Moving the arrow up and down for a few seconds.". +--- set vd:startupdater to { return ship:up:vector*3*sin(time:seconds*180). }. +--- wait 5. +--- print "Stopping the arrow movement.". +--- set vd:startupdater to DONOTHING. +--- wait 3. +--- print "Removing the arrow.". +--- set vd to 0. +--- +---.. versionadded:: 1.1.0 +--- +--- scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0 +---@field startupdater UserDelegate +---Settable. +---Optional, defaults to 1.0. Scalar to multiply the VEC by, and the WIDTH, +---but not the START. +---@field scale number +---Settable. +---Optional, defaults to "". Text to show on-screen at the midpoint of the vector. +---Note the font size the label is displayed in gets stretched when you +---change the :attr:`SCALE` or the :attr:`WIDTH` values. +---@field label string +---Settable. +---Define the width of the drawn line, in meters. The deafult is 0.2 if +---left off. Note, this also causes the font of the label to be enlarged +---to match if set to a value larger than 0.2. +---@field width number +---Settable. +---(Defaults to True if left off.) Will this line be drawn with +---a pointy arrowhead "hat" on the tip to show which end is the +---start point and which is the end point? If this is false, +---then Vecdraw draws just a thick line, instead of an arrow. +---@field pointy boolean +---Settable. +---(Defaults to True if left off.) If true, this line will be drawn +---with a "wipe" effect that varies how transparent it is. At the +---start point it will be a more transparent version of the color +---you specified in :attr:`VecDraw:COLOR`. It will only become the +---full opacity you requested when it reaches the endpoint of the line. +---This effect is to help show the direction the arrow is going as it +---"fades in" to full opacity as it goes along. +--- +---If false, then the opacity of the line will not vary. It will draw +---the whole line at the exact color you specified in the in the +---:attr:`VecDraw:COLOR` SUFFIX. (Which can still be transparent if +---you use an RGBA() and provide the alpha value.) +---@field wiping boolean + +---@class ConsumedResource : Structure +---The name of the resource, i.e. "LIQUIDFUEL", "ELECTRICCHARGE", "MONOPROP". +---@field name string +---The density value of this resource, expressed in Megagrams f mass +---per Unit of resource. (i.e. a value of 0.005 would mean that each +---unit of this resource is 5 kilograms. Megagrams [metric tonnes] is +---the usual unit that most mass in the game is represented in.) +---@field density number +---How much volume of this fuel is this engine consuming at this very moment. +---@field fuelflow number +---How much volume of this fuel would this engine consume at standard pressure and velocity if the throttle was max at 1.0, and the thrust limiter was max at 100%. +---@field maxfuelflow number +---How much volume of this fuel does this require at this very moment for the current throttle setting. +---This will usually equal FUELFLOW but may be higher for INTAKEAIR for instance. +---@field requiredflow number +---How much mass of this fuel is this engine consuming at this very moment. +---@field massflow number +---How much mass of this fuel would this engine consume at standard pressure and velocity if the throttle was max at 1.0, and the thrust limiter was max at 100%. +---@field maxmassflow number +---What is the volumetric ratio of this fuel as a proportion of the overall fuel mixture, e.g. if this is 0.5 then this fuel is half the required fuel for the engine. +---@field ratio number +---The value of how much resource is left and accessible to this engine. Only valid while the engine is running. +---@field amount number +---What AMOUNT would be if the resource was filled to the top. Only valid while the engine is running. +---@field capacity number + +---@class OrbitableVelocity : Structure +---@field obt Vector +---@field orbit Vector +---Returns the surface-frame velocity. Note that this is the surface velocity relative to the surface of the SOI body, not the orbiting object itself. (i.e. Mun:VELOCITY:SURFACE returns the Mun's velocity relative to the surface of its SOI body, Kerbin). +--- +---.. note:: +--- **Special case instance, the Sun:** Because the Sun has no parent +--- SoI body that it orbits around (Kerbal Space Program does not +--- simulate the existence of anything outside the one solar system), +--- that means that the Sun's surface velocity is just hardcoded to +--- be the same thing as its orbital velocity. This may or may not be +--- entirely correct, but the "correct" answer to the question, "What is +--- sun:velocity:surface?" would technically be "I refuse to answer. +--- That's an invalid question." Rather than crash or throw an exception, +--- kOS just returns the same as the orbital velocity in this case. +---@field surface Vector + +---@class DeltaV : Structure +---:type: `number` in m/s +--- +---Returns stock KSP's notion of how much deltaV there is. The stock deltaV +---calculation assumes all engine burns take place at the current atmospheric +---pressure the ship is in. This should match the value seen in the staging +---list during flight, (Including the sometimes incorrect values that stock +---KSP gives in its DeltaV calculations, unfortunately.) +--- +---Heed the warning above: Stock delta-V values might not be getting +---calculated if this vessel is not the currently active vessel. +---@field current number +---:type: `number` in m/s +--- +---Returns stock KSP's notion of how much deltaV there *would be* if all the +---burns took place at 1 ATM (sea level atmosphere). This should match +---the value seen in the staging list during construction if you had the +---delta-V readouts in sea level mode., (Including the sometimes +---incorrect values that stock KSP gives in its DeltaV calculations, +---unfortunately.) +--- +---Heed the warning above: Stock delta-V values might not be getting +---calculated if this vessel is not the currently active vessel. +---@field asl number +---:type: `number` in m/s +--- +---Returns stock KSP's notion of how much deltaV there *would be* if all the +---burns took place in a vacuum. This should match +---the value seen in the staging list during construction if you had the +---delta-V readouts in vacuum mode., (Including the sometimes +---incorrect values that stock KSP gives in its DeltaV calculations, +---unfortunately.) +--- +---Heed the warning above: Stock delta-V values might not be getting +---calculated if this vessel is not the currently active vessel. +---@field vacuum number +---:type: `number` in seconds +--- +---Returns stock KSP's notion of how long it will take to cause this deltaV. +---(How much time it takes for the engine(s) to burn up the fuel if they +---are at MAXTHRUST). This should match the value seen in the staging list. +---(Including the sometimes incorrect values that stock KSP gives in its +---DeltaV calculations, unfortunately.) +--- +---Heed the warning above: Stock delta-V values might not be getting +---calculated if this vessel is not the currently active vessel. +---@field duration number +---:type: none (void) +--- +---The stock delta-V calculations are made by constantly running a small +---simulation in its head during flight that takes a few update ticks +---to arrive at the answer. Calling this method will force the stock game +---to mark its current delta-V values "dirty" and make it want to re-run +---the calculation. **DO NOT CALL THIS REPEATEDLY IN A LOOP OR A +---TRIGGER**. Calling this causes the game to lag if you keep doing it +---all the time (it generates "garbage" for the game to "collect".) +--- +---After calling FORCECALC(), the deltaV values you see will be quite +---wrong for a few update ticks, while the game calculates the new values. +---Unfortunately, there isn't a good way for a kerobscript to find out +---when the answer is final and the recalculation is over. (Sorry, we +---tried, but couldn't find the API call in KSP that would tell us this.) +--- +---The only real way to decide the recalculation is over is to examine +---the output of the :ASL or :VACUUM suffixes and see when they seem +---to have settled on a number and stayed there a while. (don't use +---:CURRENT for this, as it will naturally change if you are ascending +---or descending in atmosphere.) Be aware that a *small* fluctuation +---in the value is in fact expected, as the simulation KSP runs in its +---head is subject to floating point errors (i.e. while you see a value +---like "2345 m/s" in the User display, under the hood that value could +---actually be varying between 2345.11112 to 2345.11135 to 2345.11102, +---etc.) +---@field forcecalc fun() + +---@class Career : Structure +---:type: :ref:`Boolean ` +--- +---If your tracking center allows the tracking of unnamed objects (asteroids, mainly) then this will return true. +---@field cantrackobjects boolean +---:type: :ref:`scalar ` +--- +---If your tracking center allows some patched conics predictions, then this number will be greater than zero. +---The number represents how many patches beyond the current one that you are allowed to see. It influences +---attempts to call SHIP:PATCHES and SHIP:OBT:NEXTPATCH. +---@field patchlimit number +---:type: :ref:`Boolean ` +--- +---If your tracking center and mission control buildings are both upgraded enough, then the game allows +---you to make manuever nodes (which the game calls "flight planning"). This will return true if you +---can make maneuver nodes. +---@field canmakenodes boolean +---:type: :ref:`Boolean ` +--- +---If your VAB or SPH are upgraded enough to allow custom action groups, then you will also be allowed +---to execute the :DOACTION method of PartModules. Otherwise you can't. This will return a boolean +---letting you know if the condition has been met. +---@field candoactions boolean + +---@class HSVA : RGBA +---Settable. +---@field h number +---Settable. +---@field hue number +---Settable. +---@field s number +---Settable. +---@field saturation number +---Settable. +---@field v number +---Settable. +---@field value number + +---@class Body : Orbitable +---The name of the body. Example: "Mun". +---@field name string +---Longer description of the body, often just a duplicate of the name. +---@field description string +---The mass of the body in kilograms. +---@field mass number +---True if this body has an ocean. Example: In the stock solar system, +---this is True for Kerbin and False for Mun. +---@field hasocean boolean +---True if this body has a solid surface. Example: In the stock solar system, +---this is True for Kerbin and False for Jool. +---@field hassolidsurface boolean +---A list of the bodies orbiting this body. Example: In the stock solar system, +---Kerbin:orbitingchildren is a list two things: Mun and Minmus. +---@field orbitingchildren List +---The altitude of this body above the sea level surface of its parent body. I.e. the altitude of Mun above Kerbin. +---@field altitude number +---The radius from the body's center to its sea level. +---@field radius number +---The `Gravitational Parameter`_ of the body. +---@field mu number +---The number of seconds it takes the body to rotate around its own axis. +---This is the sidereal rotation period which can differ from the length +---of a day due to the fact that the body moves a bit further around the +---Sun while it's rotating around its own axis. +---@field rotationperiod number +---A variable that describes the atmosphere of this body. +---@field atm Atmosphere +---Angular velocity of the body's rotation about its axis (its +---sidereal day) expressed as a vector. +--- +---The direction the angular velocity points is in Ship-Raw orientation, +---and represents the axis of rotation. Remember that everything in +---Kerbal Space Program uses a *left-handed coordinate system*, which +---affects which way the angular velocity vector will point. If you +---curl the fingers of your **left** hand in the direction of the rotation, +---and stick out your thumb, the thumb's direction is the way the +---angular velocity vector will point. +--- +---The magnitude of the vector is the speed of the rotation, *in radians*. +--- +---Note, unlike many of the other parts of kOS, the rotation speed is +---expressed in radians rather than degrees. This is to make it +---congruent with how VESSEL:ANGULARMOMENTUM is expressed, and for +---backward compatibility with older kOS scripts. +---@field angularvel Vector +---The radius of the body's sphere of influence. Measured from the body's center. +---@field soiradius number +---The rotation angle is the number of degrees between the +---:ref:`Solar Prime Vector ` and the +---current position of the body's prime meridian (body longitude +---of zero). +--- +---The value is in constant motion, and once per body's rotation +---period ("sidereal day"), its ``:rotationangle`` will wrap +---around through a full 360 degrees. +---@field rotationangle number +---:parameter vectorPos: `Vector` input position in XYZ space. +---The geoposition underneath the given vector position. SHIP:BODY:GEOPOSITIONOF(SHIP:POSITION) should, in principle, give the same thing as SHIP:GEOPOSITION, while SHIP:BODY:GEOPOSITIONOF(SHIP:POSITION + 1000*SHIP:NORTH) would give you the lat/lng of the position 1 kilometer north of you. Be careful not to confuse this with :GEOPOSITION (no "OF" in the name), which is also a suffix of Body by virtue of the fact that Body is an Orbitable, but it doesn't mean the same thing. +--- +---(Not to be confused with the :attr:`Orbitable:GEOPOSITION` suffix, which ``Body`` inherits +---from `Orbitable`, and which gives the position that this body is directly above +---on the surface *of its parent body*.) +---@field geopositionof fun(vectorPos: Vector): GeoCoordinates +---The altitude of the given vector position, above this body's 'sea level'. SHIP:BODY:ALTITUDEOF(SHIP:POSITION) should, in principle, give the same thing as SHIP:ALTITUDE. Example: Eve:ALTITUDEOF(GILLY:POSITION) gives the altitude of gilly's current position above Eve, even if you're not actually anywhere near the SOI of Eve at the time. Be careful not to confuse this with :ALTITUDE (no "OF" in the name), which is also a suffix of Body by virtue of the fact that Body is an Orbitable, but it doesn't mean the same thing. +---@field altitudeof fun(param1: Vector): number +---:parameter latitude: `number` input latitude +---:parameter longitude: `number` input longitude +---Given a latitude and longitude, this returns a `GeoCoordinates` structure +---for that position on this body. +--- +---(Not to be confused with the :attr:`Orbitable:GEOPOSITION` suffix, which ``Body`` inherits +---from `Orbitable`, and which gives the position that this body is directly above +---on the surface *of its parent body*.) +---@field geopositionlatlng fun(latitude: number, longitude: number): GeoCoordinates + +---@class GeoCoordinates : Structure +---The latitude of this position on the surface. +---@field lat number +---The longitude of this position on the surface. +---@field lng number +---The :ref:`Celestial Body ` this position is attached to. +---@field body Body +---Distance of the terrain above "sea level" at this geographical position. Negative numbers are below "sea level." +---@field terrainheight number +---Distance from the :ref:`CPU_Vessel ` to this point on the surface. +---@field distance number +---The *absolute* compass direction from the :ref:`CPU_Vessel ` to this point on the surface. +---@field heading number +---The *relative* compass direction from the :ref:`CPU_Vessel ` to this point on the surface. For example, if the vessel is heading at compass heading 45, and the geo-coordinates location is at heading 30, then :attr:`GeoCoordinates:BEARING` will return -15. +---@field bearing number +---The ship-raw 3D position on the surface of the body, relative to the current ship's Center of mass. +---@field position Vector +---The (linear) velocity of this spot on the surface of the planet/moon, due to the rotation of the +---body causing that spot to move though space. +---(For example, on Kerbin at a sea level location, it would be 174.95 m/s eastward, and slightly +---more at higher terrain spots above sea level.) +---Note that this is returned as an `OrbitableVelocity`, meaning it isn't a vector but a +---pair of vectors, one called ``:orbit`` and one called ``:surface``. Note that the +---surface-relative velocity you get from the ``:surface`` suffix isn't always zero like you might +---intuit because ``:surface`` gives you the velocity relative to the surface reference frame +---where ``SHIP`` is, which might not be the same latitude/longitude/altitude as where this +---Geocoordinates is. +---@field velocity OrbitableVelocity +---The ship-raw 3D position above or below the surface of the body, relative to the current ship's Center of mass. You pass in an altitude number for the altitude above "sea" level of the desired location. +---@field altitudeposition fun(altitude: number): Vector +---This is the same as :attr:`GeoCoordinates:VELOCITY`, except that it lets you specify some +---altitude other than the surface terrain height. You specify a (sea-level) altitude, +---and it will calculate based on a point at that altitude which may be above or below +---the actual surface at this latitude and longitude. It will calculate as if you had some +---point fixed to the ground, like an imaginary tower bolted to the surface, but not at the +---ground's altitude. (The body's rotation will impart a larger magnitude linear velocity +---on a locaton affixed to the body the farther that location is from the body's center). +---@field altitudevelocity fun(altitude: number): OrbitableVelocity + +---@class ConsumedResourceRCS : Structure +---The name of the resource, i.e. "LIQUIDFUEL", "ELECTRICCHARGE", "MONOPROP". +---@field name string +---The density value of this resource, expressed in Megagrams f mass +---per Unit of resource. (i.e. a value of 0.005 would mean that each +---unit of this resource is 5 kilograms. Megagrams [metric tonnes] is +---the usual unit that most mass in the game is represented in.) +---@field density number +---What is the volumetric ratio of this fuel as a proportion of the overall fuel mixture, e.g. if this is 0.5 then this fuel is half the required fuel for the thruster. +---@field ratio number +---The value of how much resource is left and accessible to this thruster. Only valid while the thruster is running. +---@field amount number +---What AMOUNT would be if the resource was filled to the top. Only valid while the thruster is running. +---@field capacity number + +---@class AggregateResource : Structure +---The name of the resource, i.e. "LIQUIDFUEL", "ELECTRICCHARGE", "MONOPROP". +---@field name string +---The density value of this resource, expressed in Megagrams f mass +---per Unit of resource. (i.e. a value of 0.005 would mean that each +---unit of this resource is 5 kilograms. Megagrams [metric tonnes] is +---the usual unit that most mass in the game is represented in.) +---@field density number +---The value of how much resource is left. +---@field amount number +---What AMOUNT would be if the resource was filled to the top. +---@field capacity number +---Because this is a summation of the resources from many parts. kOS gives you the list of all parts that do or could contain the resource. +---@field parts List + +---@class TimeBase : Structure +---@field year number +---@field day number +---@field hour number +---@field minute number +---@field second number +---@field seconds number +---@field clock string +---@field calendar string + +---@class Node : Structure +---@field deltav Vector +---@field burnvector Vector +---Settable. +---@field eta number +---Settable. +---@field time number +---Settable. +---@field prograde number +---Settable. +---@field radialout number +---Settable. +---@field normal number +---@field obt Orbit +---@field orbit Orbit + +---@class Transfer : Structure +---@field goal number +---@field transferred number +---@field status string +---@field message string +---@field resource string +---Settable. +---@field active boolean + +---@class Orbit : Structure +---a name for this orbit. +---@field name string +---:type: `number` (m) +--- +---The max altitude expected to be reached. +---@field apoapsis number +---:type: `number` (m) +--- +---The min altitude expected to be reached. +---@field periapsis number +---The celestial body this orbit is orbiting. +---@field body Body +---:type: `number` (seconds) +--- +---`orbital period`_ +---@field period number +---:type: `number` (degree) +--- +---`orbital inclination`_ +---@field inclination number +---`orbital eccentricity`_ +---@field eccentricity number +---:type: `number` (m) +--- +---`semi-major axis`_ +---@field semimajoraxis number +---:type: `number` (m) +--- +---`semi-minor axis`_ +---@field semiminoraxis number +---Same as :attr:`Orbit:LONGITUDEOFASCENDINGNODE`. +---@field lan number +---Same as :attr:`Orbit:LONGITUDEOFASCENDINGNODE`. +---@field longitudeofascendingnode number +---`argument of periapsis`_ +---@field argumentofperiapsis number +---`true anomaly`_ in degrees. Even though orbital parameters are +---traditionally done in radians, in keeping with the kOS standard +---of making everything into degrees, they are given as degrees by +---kOS. +--- +---**Closed versus Open orbits clamp this differently:** The range of +---possible values this can have differs depending on if the orbit +---is "closed" (elliptical, eccentricity < 1.0) versus "open" (parabolic +---or hyperbolic, eccentricity >= 1.0). If the orbit is closed, then +---this value will be in the range [0..360), where values larger than +---180 represent positions in the orbit where it is "coming down" +---from apoapsis to periapsis. But if the orbit is open, then this +---value will be in the range (-180..180), where negative values are +---used to represent the positions in the orbit where it is "coming down" +---to the periapsis. The difference is because it does not make sense +---to speak of the orbit looping all the way around 360 degrees in +---the case of an open orbit where it does not come back down. +--- +---Note that the above switch between 0..360 versus -180..180 happens +---when the orbit is *mathematically* shown to be an escaping orbit, +---NOT when it's still an ellipse but the apoapsis happens to be higher +---than the body's sphere of influence so the game will let it escape +---anyway. Both conditions look similar on the game map so it may +---be hard to tell them apart without actually querying the eccentricity +---to find out which it is. +---@field trueanomaly number +---:type: `number` degrees +--- +---`mean anomaly`_ in degrees. Even though orbital parameters are +---traditionally done in radians, in keeping with the kOS standard +---of making everything into degrees, they are given as degrees by +---kOS. +--- +---Internally, KSP tracks orbit position using :attr:`MEANANOMALYATEPOCH` +---and :attr:`EPOCH`. "Epoch" is an arbitrary timestamp expressed in +---universal time (gameworld seconds from game start, same as ``TIME:SECONDS`` +---uses) at which the mean anomaly of the orbit would be :attr:`MEANANOMALYATEPOCH`. +--- +---Given the mean anomaly at epoch, and the epoch time, and the current time, +---and the orbital period, it's possible to find out the current mean anomaly. +---Kerbal Space Program uses this internally to track orbit positions while under +---time warp without using the full physics system. +--- +---**Closed versus Open orbits clamp this differently:** The range of +---possible values this can have differs depending on if the orbit +---is "closed" (elliptical, eccentricity < 1.0) versus "open" (parabolic +---or hyperbolic, eccentricity >= 1.0). If the orbit is closed, then +---this value will be in the range [0..360), where values larger than +---180 represent positions in the orbit where it is "coming back down" +---from apoapsis to periapsis. But if the orbit is open, then this value +---doesn't have any limits, and furthermore negative values are +---used to represent the portion of the orbit that is "coming down" +---to the periapsis, rather than using values > 180 for this. +--- +---Note that the above switch between MEANANOMALY behaving in the "closed" +---versus "open" way depends on the orbit being *mathematically* shown +---to be an escaping orbit, NOT merely "escaping" because it has an +---apoapsis higher than the body's sphere of influence. If the orbit's +---mathematical parameters show it to be an ellipse, but its apoapsis is +---higher than the body's sphere of influence, then the game will let it +---escape anyway despite it still being an elliptical orbit. (It's just +---an elliptical orbit with the top "cut off".) The MEANANOMALY +---measurement will treat such elliptical-but-escaping-anyway scenarios +---as "closed" even though they don't look like it on the map. +---@field meananomalyatepoch number +---:type: `number` universal timestamp (seconds) +--- +---Internally, KSP tracks orbit position using :attr:`MEANANOMALYATEPOCH` +---and :attr:`EPOCH`. "Epoch" is an arbitrary timestamp expressed in +---universal time (gameworld seconds from game start, same as ``TIME:SECONDS`` +---uses) at which the mean anomaly of the orbit would be :attr:`MEANANOMALYATEPOCH`. +--- +---Beware, if you are an experienced programmer, you may be aware of the +---word "Epoch" being used to mean a fixed point in time that never +---ever changes throughout an entire system. For example, the Unix +---timestamp system refers to Jan 1, 1970 as the "epoch". This is *NOT* +---how the word is used in KSP's orbit system. In Kerbal Space Program, +---the "epoch" is not a true "epoch", in that it often moves and you have to +---re-check what it is. It's not a hardcoded constant. +--- +---(The epoch timestamp seems to change when you go on or off from time warp.) +---@field epoch number +---Describes the way in which this orbit will end and become a different orbit, with a value taken :ref:`from this list `. +---@field transition string +---The current position of whatever the object is that is in this orbit. +---@field position Vector +---The current velocity of whatever the object is that is in this orbit. Be aware +---that this is not just a velocity vector, but a structure containing both the +---orbital and surface velocity vectors as a pair. (See `OrbitableVelocity`). +---@field velocity OrbitableVelocity +---*In career this requires a building upgrade* - In career mode where +---buildings are not upgraded at the start, this suffix won't be allowed +---until your tracking station is upgraded a level. +--- +---When this orbit has a transition to another orbit coming up, this suffix returns the next Orbit patch after this one. For example, when escaping from a Mun orbit into a Kerbin orbit from which you will escape and hit a Solar orbit, then the current orbit's :attr:`:NEXTPATCH ` will show the Kerbin orbit, and ``:NEXTPATCH:NEXTPATCH`` will show the solar orbit. The number of patches into the future that you can peek depends on your conic patches setting in your **Kerbal Space Program** Settings.cfg file. +---@field nextpatch Orbit +---*In career this requires a building upgrade* - In career mode where +---buildings are not upgraded at the start, this suffix won't be allowed +---until your tracking station is upgraded a level. +--- +---If :attr:`:NEXTPATCH ` will return a valid patch, this is true. If :attr:`:NEXTPATCH ` will not return a valid patch because there are no transitions occurring in the future, then :attr:`HASNEXTPATCH ` (see +--- :ref:`note in the Directions documentation ` +--- about information loss when doing this). +--- +---SET: +--- Tells the vector to keep its magnitude as it is but point in a new direction, adjusting its :math:`(x,y,z)` numbers accordingly. +---@field direction Direction + +---@class Timewarp : Structure +---Settable. +---The current multiplier timescale rate (i.e. 1000 if current rate +---is 1000x as much as normal, etc). +--- +---If you have just changed the time warp, it takes a few moments for +---the game to "catch up" and achieve the desired warp rate. You can +---query this value to find out what the current rate is the game is +---operating under during this physics tick. It often takes almost +---a whole second of game time to finally arrive at the destination rate. +--- +---When you ``set`` the ``:RATE`` equal to a new value, then +---instead of directly setting the rate to that value, kOS will +---set the :attr:`WARP` to whatever value it would need to have +---to end up with that rate. The rate itself won't change right +---away. For example, the following two commands are equivalent:: +--- +--- // This will eventually give you a rate of 100, after several +--- // update ticks have passed, but not right away: +--- set kuniverse:timewarp:warp to 4. +--- +--- // This will *also* do the same thing, and not set the rate +--- // to 100 right away, but instead tells kOS indirectly +--- // to set the WARP to 4, so as to target a destination +--- // rate of 100. +--- set kuniverse:timewarp:rate to 100. +--- +---If you set the rate to a value that isn't on the allowed list +---that the KSP game interface normally lets you pick, then kOS +---will pick whichever :attr:`WARP` value will get you closest +---to the requested rate. For example:: +--- +--- // If you do any of these, then the effect is the same: +--- set kuniverse:timewarp:rate to 89. +--- set kuniverse:timewarp:rate to 145. +--- set kuniverse:timewarp:rate to 100. +--- // Because the game only allows permanent rates of 1,5,10,50,100,1000, etc. +--- // A rate of 100 was the closest match it could allow. +--- +---Note, the game is actually capable of warping at arbitrary rates +---in between these values, and it does so temporarily when transitioning +---to a new warp rate, but it doesn't allow you to hold the rate at those +---in-between values indefintiely. +---@field rate number +---If :attr:`MODE` is "PHYSICS", this will return :attr:`PHYSICSRATELIST`. +---If :attr:`MODE` is "RAILS", this will return :attr:`RAILSRATELIST`. +--- +---It's always the list that goes with the current warping mode. +---@field ratelist List +---:type: `List` of `number` values +--- +---The list of the legal values that the game lets you set the warp rate +---to when in "on rails" warping mode ("normal" time warp). +--- +---(As of this writing of the documents, the values come out like the table +---below, but the base KSP game could change these at any time. The +---following table is not a guarantee.) +--- +---.. table:: RAILS WARP RATE LIST +--- +--- ==== ==== +--- WARP RATE +--- ==== ==== +--- 0 1x +--- 1 5x +--- 2 10x +--- 3 50x +--- 4 100x +--- 5 1000x +--- 6 10000x +--- 7 100000x +--- ==== ==== +---@field railsratelist List +---:type: `List` of `number` values +--- +---The list of the legal values that the game lets you set the warp rate +---to when in "physics warp" warping mode. +--- +---(As of this writing of the documents, the values come out like the table +---below, but the base KSP game could change these at any time. The +---following table is not a guarantee.) +--- +---.. table:: PHYSICS WARP RATE LIST +--- +--- ==== ==== +--- WARP RATE +--- ==== ==== +--- 0 1x +--- 1 2x +--- 2 3x +--- 3 4x +--- ==== ==== +---@field physicsratelist List +---Settable. +---The string value indicating whether we are in "PHYSICS" or "RAILS" +---warping mode right now. You can set this value to change which +---warp mode the game will perform. +--- +---(Any experienced player of KSP should be aware of what the difference +---between physics warp and "time warp" (rails) is. In "physics" warp, +---all the normal things work, and the game simulates the entire physics +---engine with longer coarser delta-T time steps to achieve a faster +---simulation rate. In "rails" warp, many of the calculations are not +---working, the vessel is not controllable, and the game calculates +---positions of objects based on the Keplerian elliptical parameters only.) +---@field mode string +---Settable. +---Time warp as an integer index. In the tables listed above for +---:attr:`RAILSRATELIST` and :attr:`PHYSICSRATELIST`, this is the number +---on the lefthand side of those tables. (i.e. if +---:attr:`MODE` is "RAILS" and :attr:`RATE` is 50, then that means +---:attr:`WARP` is 3.) +--- +---If you set either :attr:`WARP` or :attr:`RATE`, the other will change +---along with it to agree with it. (See the full explanation in +---:attr:`RATE` below). +---@field warp number +---:parameter timestamp: `number` +---:return: None +--- +---Call this method to warp time forward to a universal time stamp. +---The argument you pass in should be a universal timestamp in seconds. +---Example: To warp 120 seconds into the future: +---``kuniverse:timewarp:warpto(time:seconds + 120)``. +--- +---Obviously this alters the values of :attr:`WARP` and :attr:`RATE` while +---the warping is happening. +---@field warpto fun(timestamp: number) +---:return: None +--- +---Call this method to cancel any active warp. This will both interupt any +---current automated warp (such as one using :meth:`WARPTO` +---or the "Warp Here" user interface) and a manual warp setting (as if you had +---used the ``SET WARP TO 0.`` command). +---@field cancelwarp fun() +---Physics Delta-T. How much time *should* pass between ticks. Note this is +---not the *actual* time that has passed. For that you should query +---:attr:`time:seconds ` regularly and store the timestamps it +---returns, and compare those timestamps. This value is just the "expected" +---physics delta-T that you *should* get if everything is running smoothly +---and your computer can keep up with everything the game is doing. +--- +---This value changes depending on your physics warp. Note, if you query it +---during on-rails warping, it can return some very strange values you +---shouldn't trust. +---@field physicsdeltat number +---When you have just changed the warp speed, the game takes time to +---"catch up" and achieve the new desired speed. (i.e. if you change your +---rate from 100x up to 1000x, and you look at the screen, you will see +---the numbers in the display saying things like "Warp 123x" then "Warp 344x" +---then "Warp 432x", etc. There are several "ticks" during which the warp +---hasn't yet achieved the desired 1000x level.) This can take a "long" +---time in computer terms to happen. +--- +---You can query this value to find out whether or not the actual warp +---rate has finally settled on the desired amount yet. +--- +---For example:: +--- +--- set kuniverse:timewarp:mode to "RAILS". +--- set kuniverse:timewarp:rate to 1000. +--- print "starting to change warp". +--- until kuniverse:timewarp:issettled { +--- print "rate = " + round(rate,1). +--- wait 0. +--- } +--- print "warp is now 1000x". +--- +--- // The above would output something like this to the screen: +--- starting to change warp. +--- rate = 113.5 +--- rate = 143.2 +--- rate = 213.1 +--- rate = 233.2 +--- rate = 250.0 +--- rate = 264.1 +--- rate = 301.5 +--- rate = 320.5 +--- rate = 361.5 +--- rate = 391.3 +--- rate = 421.5 +--- rate = 430.0 +--- rate = 450.5 +--- rate = 471.5 +--- rate = 490.1 +--- rate = 501.5 +--- rate = 613.5 +--- rate = 643.2 +--- rate = 713.1 +--- rate = 733.2 +--- rate = 750.0 +--- rate = 764.1 +--- rate = 801.5 +--- rate = 820.5 +--- rate = 861.5 +--- rate = 891.3 +--- rate = 921.5 +--- rate = 930.0 +--- rate = 950.5 +--- rate = 971.5 +--- rate = 990.1 +--- rate = 1000 +--- warp is now 1000x. +---@field issettled boolean + +---@class VesselSensors : Structure +---Accelleration the vessel is undergoing. A combination of both the gravitational pull and the engine thrust. +---@field acc Vector +---The current pressure of this ship. +---@field pres number +---The current temperature. +---@field temp number +---Magnitude and direction of gravity acceleration where the vessel currently is. Magnitude is expressed in "G"'s (multiples of 9.802 m/s^2). +---@field grav Vector +---The total amount of sun exposure that exists here - only readable if there are solar panels on the vessel. +---@field light number + +---@class CrewMember : Structure +---crew member's name +---@field name string +---true if this crew member is a tourist +---@field tourist boolean +---"Male" or "Female" +---@field gender string +---crew member's trait (specialization), for example "Pilot" +---@field trait string +---experience level (number of stars) +---@field experience number +---`Part` this crew member is located in +---@field part Part +---@field status string +---@field experiencevalue number +---@field kerbaltype string + +---@class Resource : Structure +---The name of the resource, i.e. "LIQUIDFUEL", "ELECTRICCHARGE", "MONOPROP". +---@field name string +---The value of how much resource is left. +---@field amount number +---The density value of this resource, expressed in Megagrams f mass +---per Unit of resource. (i.e. a value of 0.005 would mean that each +---unit of this resource is 5 kilograms. Megagrams [metric tonnes] is +---the usual unit that most mass in the game is represented in.) +---@field density number +---What AMOUNT would be if the resource was filled to the top. +---@field capacity number +---Many, but not all, resources can be turned on and off, this removes them from the fuel flow. +---@field toggleable boolean +---Settable. +---If the resource is TOGGLEABLE, setting this to false will prevent the resource from being taken out normally. +---@field enabled boolean + +---@class Control : Structure +---@field pilotyaw number +---Settable. +---@field pilotyawtrim number +---@field pilotroll number +---Settable. +---@field pilotrolltrim number +---@field pilotpitch number +---Settable. +---@field pilotpitchtrim number +---@field pilotfore number +---@field pilotstarboard number +---@field pilottop number +---Settable. +---@field pilotwheelthrottle number +---Settable. +---@field pilotwheelthrottletrim number +---@field pilotwheelsteer number +---Settable. +---@field pilotwheelsteertrim number +---@field pilotneutral boolean +---@field pilotrotation Vector +---@field pilottranslation Vector +---Settable. +---@field pilotmainthrottle number +---Settable. +---@field yaw number +---Settable. +---@field yawtrim number +---Settable. +---@field roll number +---Settable. +---@field rolltrim number +---Settable. +---@field pitch number +---Settable. +---@field pitchtrim number +---Settable. +---@field rotation Vector +---Settable. +---@field fore number +---Settable. +---@field starboard number +---Settable. +---@field top number +---Settable. +---@field translation Vector +---Settable. +---@field wheelsteer number +---Settable. +---@field wheelsteertrim number +---Settable. +---@field mainthrottle number +---Settable. +---@field wheelthrottle number +---Settable. +---@field wheelthrottletrim number +---Settable. +---@field bound boolean +---Settable. +---@field neutral boolean +---Settable. +---@field neutralize boolean + +---@class LoadDistances : Structure +---@field escaping LoadDistance +---@field flying LoadDistance +---@field landed LoadDistance +---@field orbit LoadDistance +---@field prelaunch LoadDistance +---@field splashed LoadDistance +---@field suborbital LoadDistance + +---@class LoadDistance : Structure +---Settable. +---@field load number +---Settable. +---@field unload number +---Settable. +---@field pack number +---Settable. +---@field unpack number + +---@class Direction : Structure +---@operator add(Direction):Direction +---@operator sub(Direction):Direction +---@operator mul(Direction):Direction +---@operator unm:Direction +---:type: `number` (deg) +--- +--- +---Rotation around the :math:`x` axis. +---@field pitch number +---:type: `number` (deg) +--- +---Rotation around the :math:`y` axis. +---@field yaw number +---:type: `number` (deg) +--- +--- +---Rotation around the :math:`z` axis. +---@field roll number +---`Vector` of length 1 that is in the same direction as the "look-at" of this Direction. Note that it is the same meaning as "what the Z axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe". When you LOCK STEERING to a direction, that direction's FOREVECTOR is the vector the nose of the ship will orient to. SHIP:FACING:FOREVECTOR is the way the ship's nose is aimed right now. +---@field forevector Vector +---`Vector` of length 1 that is in the same direction as the "look-at" of this Direction. Note that it is the same meaning as "what the Z axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe". When you LOCK STEERING to a direction, that direction's FOREVECTOR is the vector the nose of the ship will orient to. SHIP:FACING:FOREVECTOR is the way the ship's nose is aimed right now. +---@field vector Vector +---`Vector` of length 1 that is in the same direction as the "look-up" of this Direction. Note that it is the same meaning as "what the Y axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe". When you LOCK STEERING to a direction, that direction's TOPVECTOR is the vector the roof of the ship will orient to. SHIP:FACING:TOPVECTOR is the way the ship's roof is aimed right now. +---@field topvector Vector +---`Vector` of length 1 that is in the same direction as the "look-up" of this Direction. Note that it is the same meaning as "what the Y axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe". When you LOCK STEERING to a direction, that direction's TOPVECTOR is the vector the roof of the ship will orient to. SHIP:FACING:TOPVECTOR is the way the ship's roof is aimed right now. +---@field upvector Vector +---`Vector` of length 1 that is in the same direction as the "starboard side" of this Direction. Note that it is the same meaning as "what the X axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe". When you LOCK STEERING to a direction, that direction's STARVECTOR is the vector the right wing of the ship will orient to. SHIP:FACING:STARVECTOR is the way the ship's right wing is aimed right now. +---@field starvector Vector +---`Vector` of length 1 that is in the same direction as the "starboard side" of this Direction. Note that it is the same meaning as "what the X axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe". When you LOCK STEERING to a direction, that direction's STARVECTOR is the vector the right wing of the ship will orient to. SHIP:FACING:STARVECTOR is the way the ship's right wing is aimed right now. +---@field rightvector Vector +---:struct: Gives a `Direction` with the opposite rotation around its axes. +---@field inverse Direction + +---@class TimeSpan : TimeBase +---@operator add(TimeSpan):TimeSpan +---@operator add(number):TimeSpan +---@operator sub(TimeSpan):TimeSpan +---@operator sub(number):TimeSpan +---@operator mul(TimeSpan):TimeSpan +---@operator mul(number):TimeSpan +---@operator div(TimeSpan):TimeSpan +---@operator div(number):TimeSpan +---Settable. +---Whole number of Years in the span. Note that TimeSpan starts +---counting years at 0 not at 1. This is a difference from how it +---works for `TimeStamp` +---@field year number +---Settable. +---*TOTAL* time in the span, expressed in units of years. This is not +---the same as :attr:`TimeSpan:YEAR` because it includes a fractional +---part and is the *entire* span, not just the whole number of years. +---Example: If there are 426 days in a Year, and the Timespan is +---1 year and 213 days long, then this will return ``1.5`` rather than ``1``, +---as the *entire* span is one and a half years. You can think of this +---as being :attr:`TimeSpan:SECONDS` divided by seconds per year. +---@field years number +---Settable. +---:type: `number` (range varies by universe) +--- +---Whole number of days remaining after the lst full year within the span. +---Kerbin has 426 days in a year if using Kerbin's +---6 hour day (one fourth as much if if :attr:`Kuniverse:HOURSPERDAY` +---is 24 meaning the game is configured to show Earthlike days not +---Kerbin days. +--- +---The range of possible values could be different if you have mods +---installed that replace the stock solar system with a different +---solar system and thus alter how long your homeworld's year is. +--- +---Note that for spans the first day of the year is the zero-th +---day, not the 1-th day. This is a difference from how it +---works for `TimeStamp` +---@field day number +---Settable. +---*TOTAL* time in the span, expressed in units of days. This is not +---the same as :attr:`TimeSpan:DAY` because it includes a fractional +---part and is the *entire* span, not just the whole number of days leftover +---in the last partial year. +---Example: If there are 426 days in a Year, and the Timespan is +---1 year and 213 days and 12 hours long, then this will return ``639.5`` +---rather than ``213``, as the *entire* span is 639 and a half days. +---@field days number +---Settable. +---:type: `number` (0-5) or (0-23) +--- +---Whole number of hours remaining after the last full day in the span. +---Note the setting :attr:`Kuniverse:HOURSPERDAY` affects +---whether this will be a number from 0 to 5 (6 hour day) or a number +---from 0 to 23 (24 hour day). +---@field hour number +---Settable. +---*TOTAL* time in the span, expressed in units of hours. This is not +---the same as :attr:`TimeSpan:HOUR` because it includes a fractional +---part and is the *entire* span, not just the whole number of hours +---leftover in the last partial day. +---Example: If the Timespan is 0 years, 2 days, 3 hours, and 20 minutes, +---and days are 6 hours long, then this will return 15.3333333 since +---the *entire* span is 2 days of 6 hours each, plus 3 more hours, plus +---20 minutes which is one third of an hour. +---@field hours number +---Settable. +---:type: `number` (0-59) +--- +---Whole number of minutes remaining after the last full hour in the span. +---@field minute number +---Settable. +---*TOTAL* time in the span, expressed in units of minutes. This is not +---the same as :attr:`TimeSpan:MINUTE` because it includes a fractional +---part and is the *entire* span, not just the whole number of minutes +---leftover in the last partial hour. +---Example: If the Timespan is 0 years, 0 days, 3 hours, 20 minutes, and +---30 seconds, then this will return ``200.5`` as that is the *entire* +---span: 3 60-minute hours is 180, plus 20 more minutes is 200, plus 30 +---seconds which is half a minute gives 200.5. +---@field minutes number +---Settable. +---:type: `number` (0-59) +--- +---Whole number of seconds remaining after the last full minute in the span. +---Please note the difference between this and :attr:`TimeSpan:SECONDS`. +---@field second number +---Settable. +---:type: `number` (float) +--- +---*TOTAL* Seconds in the TimeSpan, including fractonal part. Note +---this is NOT the same as :attr:`TimeSpan:SECOND` (singular), +---because this is the total span of time expressed in seconds, +---and not just the leftover seconds in the last minute of the span. +---@field seconds number +---The full string for the TimeSpan. (Down to the second anyway. Fractions of +---seconds not shown), including year, day, hour, minute, and second. +---The format is: +--- +---``_y_d_h_m_s`` (where the underscores are numbers). +---@field full string + +---@class Stage : Structure +---Every craft has a current stage, and that stage is represented by a number, this is it! +---@field number number +---Kerbal Space Program enforces a small delay between staging commands, this is to allow the last staging command to complete. This bool value will let you know if kOS can activate the next stage. +---@field ready boolean +---This is a collection of the available `AggregateResource` for the current stage. +---@field resources List +---This is a dictionary style collection of the available `Resource` +---for the current stage. The `string` key in the lexicon will match +---the name suffix on the `AggregateResource`. This suffix walks the parts +---list entirely on every call, so it is recommended that you cache the value +---if it will be reference repeatedly. +---@field resourceslex Lexicon +---One of the nearest `Decoupler` parts that is going to be activated by staging +---(not necessarily in next stage, if that stage does not contain any decoupler, separator, +---launch clamp or docking port with staging enabled). `None` if there is no decoupler. +--- +---This is particularly helpful for advanced staging logic, e.g.: +---:: +--- +--- STAGE. +--- IF stage:nextDecoupler:isType("LaunchClamp") +--- STAGE. +--- IF stage:nextDecoupler <> "None" { +--- WHEN availableThrust = 0 or ( +--- stage:resourcesLex["LiquidFuel"]:amount = 0 and +--- stage:resourcesLex["SolidFuel"]:amount = 0) +--- THEN { +--- STAGE. +--- return stage:nextDecoupler <> "None". +--- } +--- } +---@field nextdecoupler any +---One of the nearest `Decoupler` parts that is going to be activated by staging +---(not necessarily in next stage, if that stage does not contain any decoupler, separator, +---launch clamp or docking port with staging enabled). `None` if there is no decoupler. +--- +---This is particularly helpful for advanced staging logic, e.g.: +---:: +--- +--- STAGE. +--- IF stage:nextDecoupler:isType("LaunchClamp") +--- STAGE. +--- IF stage:nextDecoupler <> "None" { +--- WHEN availableThrust = 0 or ( +--- stage:resourcesLex["LiquidFuel"]:amount = 0 and +--- stage:resourcesLex["SolidFuel"]:amount = 0) +--- THEN { +--- STAGE. +--- return stage:nextDecoupler <> "None". +--- } +--- } +---@field nextseparator any +---Returns delta-V information (see `DeltaV`) about the current stage.:: +--- +--- // These two lines would do the same thing: +--- SET DV TO STAGE:DELTAV. +--- SET DV TO SHIP:STAGEDELTAV(SHIP:STAGRENUM). +---@field deltav DeltaV + +---@class Kuniverse : Structure +---Returns true if either revert to launch or revert to editor is available. Note: either option may still be unavailable, use the specific methods below to check the exact option you are looking for. +---@field canrevert boolean +---Returns true if either revert to launch is available. +---@field canreverttolaunch boolean +---Returns true if either revert to the editor is available. This tends +---to be false after reloading from a saved game where the vessel was +---already in existence in the saved file when you loaded the game. +---@field canreverttoeditor boolean +---Initiate the KSP game's revert to launch function. All progress so far will be lost, and the vessel will be returned to the launch pad or runway at the time it was initially launched. +---@field reverttolaunch fun() +---Initiate the KSP game's revert to editor function. The game will revert to the editor, as selected based on the vessel type. +---@field reverttoeditor fun() +---:parameter editor: The editor identifier +---:return: None +--- +---Revert to the provided editor. Valid inputs are `"VAB"` and `"SPH"`. +---@field revertto fun(editor: string) +---Returns true if KSP's quicksave feature is enabled and available. +---@field canquicksave boolean +---Pauses Kerbal Space Program, bringing up the same pause menu that would +---normally appear when you hit the "Escape" key. +--- +---**Warning:** *NO lines of Kerboscript code can run while the game is +---paused!!! If you call this, you will be stopping your script there +---until a human being clicks "resume" on the pause menu.* +--- +---kOS is designed to thematically act like a computer that lives *inside* +---the game universe. That means it stops when the game clock stops, for +---the same reason a bouncing ball stops when the game clock stops. +--- +---Until a human being resumes the game by clicking the Resume button +---in the menu, your script will be stuck. This makes it impossible +---to have the program run code that decides when to un-pause the game. +---Once the Resume button is clicked, then the program will +---continue where it left off, just after the point where it called +---``KUniverse:PAUSE().``. +--- +---Note, if you use Control-C in the terminal to kill the program, +---that *will* work while the game is paused like this. If you make +---the mistake of having your script keep re-pausing the game every +---time the game resumes (i.e. you call ``Kuniverse:PAUSE()`` +---again and again in a loop), then using Control-C in the terminal +---can be a way to break out of this problem. +---@field pause fun() +---Initiate the KSP game's quicksave function. The game will save the current +---state to the default quicksave file. +---@field quicksave fun() +---Initiate the KSP game's quickload function. The game will load the game +---state from the default quickload file. +---@field quickload fun() +---:parameter name: The name of the save file +---:return: None +--- +---Initiate the KSP game's quicksave function. The game will save the current +---state to a quicksave file matching the name parameter. +---@field quicksaveto fun(name: string) +---:parameter name: The name of the save file +---:return: None +--- +---Initiate the KSP game's quickload function. The game will load the game +---state from the quicksave file matching the name parameter. +---@field quickloadfrom fun(name: string) +---:type: `List` of `string` +--- +---Returns a list of names of all quicksave file in this KSP game. +---@field quicksavelist List +---Returns the name of the originating editor based on the vessel type. +---The value is one of: +--- +---- "SPH" for things built in the space plane hangar, +---- "VAB" for things built in the vehicle assembly building. +---- "" (empty `string`) for cases where the vehicle cannot remember its editor (when KUniverse:CANREVERTTOEDITOR is false.) +---@field origineditor string +---Get or set the default loading distances for vessels loaded in the future. +---Note: this setting will not affect any vessel currently in the universe for +---the current flight session. It will take effect the next time you enter a +---flight scene from the editor or tracking station, even on vessels that have +---already existed beforehand. The act of loading a new scene causes all the +---vessels in that scene to inherit these new default values, forgetting the +---values they may have had before. +--- +---(To affect the value on a vessel already existing in the current scene +---you have to use the :LOADDISTANCE suffix of the Vessel structure.) +---@field defaultloaddistance LoadDistances +---Settable. +---Returns the active vessel object and allows you to set the active vessel. Note: KSP will not allow you to change vessels by default when the current active vessel is in the atmosphere or under acceleration. Use :meth:`FORCEACTIVE` under those circumstances. +---@field activevessel Vessel +---:parameter vessel: `Vessel` to switch to. +---:return: None +--- +---Force KSP to change the active vessel to the one specified. Note: Switching the active vessel under conditions that KSP normally disallows may cause unexpected results on the initial vessel. It is possible that the vessel will be treated as if it is re-entering the atmosphere and deleted. +---@field forcesetactivevessel fun(vessel: Vessel) +---:parameter vessel: `Vessel` to switch to. +---:return: None +--- +---Force KSP to change the active vessel to the one specified. Note: Switching the active vessel under conditions that KSP normally disallows may cause unexpected results on the initial vessel. It is possible that the vessel will be treated as if it is re-entering the atmosphere and deleted. +---@field forceactive fun(vessel: Vessel) +---:type: `number` (integer) +--- +---Has the value of either 6 or 24, depending on what setting you used +---on Kerbal Space Program's main settings screen for whether you wanted +---to think in terms of Kerbal days (6 hours) or Kerbin days (24 hours). +---This only affects what the clock format looks like and doesn't +---change the actual time in game, which is stored purely as a number of +---seconds since epoch anyway and is unaffected by how the time is presented +---to the human being watching the game. (i.e. if you allow +---25 hours to pass in the game, the game merely tracks that 39000 seconds +---have passed (25 x 60 x 60). It doesn't care how that translates into +---minutes, hours, days, and years until showing it on screen to the player.) +--- +---This setting also affects how values from `TimeSpan` and +---`TimeStamp` calculate the ``:hours``, ``:days``, and ``:years`` +---suffixes. +--- +---Note that this setting is not settable. This decision was made because +---the main stock KSP game only ever changes the setting on the main +---settings menu, which isn't accessible during play. It's entirely +---possible for kOS to support changing the value mid-game, but we've +---decided to deliberately avoid doing so because there may be other mods +---with code that only reads the setting once up front and then assumes +---it never changes after that. Because in the stock game, that +---assumption would be true. +---@field hoursperday number +---:parameter message: `string` message to append to the log. +---:return: None +--- +---All Unity games (Kerbal Space Program included) have a standard +---"log" file where they can store a lot of verbose messages that +---help developers trying to debug their games. Sometimes it may +---be useful to make your script log a message to *THAT* debug file, +---instead of using kOS's normal ``Log`` function to append a +---message to some file of your own making. +--- +---This is useful for cases where you are trying to work with a kOS +---developer to trace the cause of a problem and you want your script +---to mark the moments when it hit different parts of the program, and +---have those messages get embedded in the log interleaved with the +---game's own diagnostic messages. +--- +---Here is an example. Say you suspected the game was throwing an error +---every time you tried to lock steering to up. So you experiment with +---this bit of code:: +--- +--- kuniverse:debuglog("=== Now starting test ==="). +--- kuniverse:debuglog("--- Locking steering to up----"). +--- lock steering to up. +--- kuniverse:debuglog("--- Now forcing a physics tick ----"). +--- wait 0.001. +--- kuniverse:debuglog("--- Now unlocking steering again ----"). +--- unlock steering. +--- wait 0.001. +--- kuniverse:debuglog("=== Now done with test ==="). +--- +---This would cause the messages you wrote to appear in the debug log, +---interleaved with any error messages kOS, and any other parts of the +---entire Kerbal Space Program game, dump into the same log. +--- +---The location of this log varies depending on your platform. For +---some reason, Unity chooses a different filename convention for +---each OS. Consult the list below to see where it is on your platform. +--- +---- Windows 32-bit: [install_dir]\KSP_Data\output_log.txt +---- Windows 64-bit: [install_dir]\KSP_x64_Data\output_log.txt (not officially supported) +---- Mac OS X: ~/Library/Logs/Unity/Player.log +---- Linux: ~/.config/unity3d/Squad/"Kerbal Space Program"/Player.log +--- +---For an example of what it looks like in the log, this:: +--- +--- kuniverse:debuglog("this is my message"). +--- +---ends up resulting in this in the KSP output log:: +--- +--- kOS: (KUNIVERSE:DEBUGLOG) this is my message +---@field debuglog fun(message: string) +---:parameter name: `string` craft name. +---:parameter facility: `string` editor name. +---:return: `CraftTemplate` +--- +---Returns the `CraftTemplate` matching the given craft name saved from +---the given editor. Valid values for editor include ``"VAB"`` and ``"SPH"``. +---@field getcraft fun(name: string, editor: string): CraftTemplate +---:parameter template: `CraftTemplate` craft template object. +--- +---Launch a new instance of the given `CraftTemplate` from the +---template's default launch site. +--- +---**NOTE:** The craft will be launched with the KSP default crew assignment, +---as if you had clicked launch from the editor without manually adjusting the +---crew. +--- +---**NOTE:** Due to how KSP handles launching a new craft, this will end the +---current program even if the currently active vessel is located within +---physics range of the launch site. +---@field launchcraft fun(template: CraftTemplate) +---:parameter template: `CraftTemplate` craft template object. +---:parameter site: `string` launch site name. +--- +---Launch a new instance of the given `CraftTemplate` from the given +---launch site. Valid values for site include ``"RUNWAY"`` and ``"LAUNCHPAD"``. +--- +---**NOTE:** The craft will be launched with the KSP default crew assignment, +---as if you had clicked launch from the editor without manually adjusting the +---crew. To pick which crew are on the craft use +---:meth:`Kuniverse:LAUNCHCRAFTWITHCREWFROM()` instead. +--- +---**NOTE:** Due to how KSP handles launching a new craft, this will end the +---current program even if the currently active vessel is located within +---physics range of the launch site. +---@field launchcraftfrom fun(template: CraftTemplate, site: string) +---:parameter template: `CraftTemplate` craft template object. +---:parameter crewlist: `List` of `string` kerbal names. +---:parameter site: `string` launch site name. +--- +---Launch a new instance of the given `CraftTemplate` with the given crew +---manifest from the given launch site. +---Valid values for site include ``"RUNWAY"`` and ``"LAUNCHPAD"``. +--- +---If any of the kerbal names you use in the ``crewlist`` parameter don't +---exist in the game, there will be no error. Instead that name just +---gets ignored in the list. +--- +---**NOTE:** Due to how KSP handles launching a new craft, this will end the +---current program even if the currently active vessel is located within +---physics range of the launch site. +---@field launchcraftwithcrewfrom fun(template: CraftTemplate, crewlist: List, site: string) +---:return: `List` of `CraftTemplate` +--- +---Returns a list of all `CraftTemplate` templates stored in the VAB +---and SPH folders of the stock Ships folder and the save specific Ships folder. +---@field craftlist List +---@field switchvesselwatchers UniqueSet +---Returns the `TimeWarp` structure that you can use to manipulate +---Kerbal Space Program's time warping features. See the documentation +---on `TimeWarp` for more details. +--- +---example: ``set kuniverse:timewarp:rate to 50.`` +---@field timewarp Timewarp +---An alias for :struct:`KUniverse:REALTIME`. +---@field realworldtime number +---An alias for :struct:`KUniverse:REALTIME`. +---@field realtime number + +---@class Config : Structure +---Settable. +---:type: `number` integer. range = [50,2000] +--- +---Configures the ``InstructionsPerUpdate`` setting. +--- +---This is the number of kRISC psuedo-machine-language instructions that each kOS CPU will attempt to execute from the main program per :ref:`physics update tick `. +--- +---This value is constrained to stay within the range [50..2000]. If you set it to a value outside that range, it will reset itself to remain in that range. +---@field ipu number +---Settable. +---@field luaipu number +---Settable. +---Configures the ``useCompressedPersistence`` setting. +--- +---If true, then the contents of the kOS local volume 'files' stored inside the campaign save's persistence file will be stored using a compression algorithm that has the advantage of making them take less space, but at the cost of making the data impossible to decipher with the naked human eye when looking at the persistence file. +---@field ucp boolean +---Settable. +---Configures the ``showStatistics`` setting. +--- +---If true, then executing a program will log numbers to the screen showing execution speed statistics. +--- +---When this is set to true, it also makes the use of the +---:ref:`ProfileResult() ` function available, for +---deep analysis of your program run, if you are so inclined. +---@field stat boolean +---Settable. +---Configures the ``startOnArchive`` setting. +--- +---If true, then when a vessel is first loaded onto the launchpad or runway, the initial default volume will be set to volume 0, the archive, instead of volume 1, the local drive. +---@field arch boolean +---Settable. +---Configures the ``obeyHideUI`` setting. +--- +---If true, then the kOS terminals will all hide when you toggle the user +---interface widgets with Kerbal Space Program's Hide UI key (it is +---set to F2 by default key bindings). +---@field obeyhideui boolean +---Settable. +---Configures the ``enableSafeMode`` setting. +---If true, then it enables the following error messages:: +--- +--- Tried to push NaN into the stack. +--- Tried to push Infinity into the stack. +--- +---They will be triggered any time any mathematical operation would result in something that is not a real number, such as dividing by zero, or trying to take the square root of a negative number, or the arccos of a number larger than 1. Performing such an operation will immediately terminate the program with one of the error messages shown above. +--- +---If false, then these operations are permitted, but the result may lead to code that does not function correctly if you are not careful about how you use it. Using a value that is not a real number may result in freezing Kerbal Space Program itself if that value is used in a variable that is passed into Kerbal Space Program's API routines. KSP's own API interface does not seem to have any protective checks in place and will faithfully try to use whatever values its given. +---@field safe boolean +---Settable. +---Configures the ``audibleExceptions`` setting. +--- +---If true, then it enables a mode in which errors coming from kOS will +---generte a sound effect of a short little warning bleep to remind you that +---an exception occurred. This can be useful when you are flying +---hands-off and need to realize your autopilot script just died so +---@field audioerr boolean +---Settable. +---Configures the ``verboseExceptions`` setting. +--- +---If true, then it enables a mode in which errors coming from kOS are very long and verbose, trying to explain every detail of the problem. +---@field verbose boolean +---Settable. +---Setting this config option to TRUE will allow scripts to clobber +---built-in idenifier names, re-enabling older behavior for backward +---compatibility and disabling the compiler enforcement that was +---introduced in kOS v 1.4.0.0 to stop this practice. +--- +---In kOS v1.4.0.0, the compiler started enforcing the rule that kerboscript +---programs must never create a user variable, lock, or function with a +---name that clashes with one of kOS's own built-in variable, lock, or +---function names. This rule was introduced to prevent common bugs where +---a program masked over some vital kOS variable, rendering it inaccessible, +---like for example ``SHIP``, or ``VELOCITY``. +--- +---Older scripts written before kOS 1.4.0.0 might need this config option +---enabled to make the compiler accept them and not throw errors. +--- +---Before enabling this to make the error messages go away, first consider +---going through the offeding script and editing it to rename the variable, +---lock, or function that is causing the message. That would be the better +---solution. This config option is only being presented as a dirty way +---to make old scripts that are no longer being edited keep working on +---newer versions of kOS. In the long run, it's better to edit the scripts. +--- +---**Note: This can be over-ridden by @CLOBBERBUILTINS directive:** +--- +---Note that this config option can be over-ridden on a per-file basis by +---using the compiler directive called :ref:`@CLOBBERBUILTINS `. +---The Config value here is merely the default you get for files that lack a +---:ref:`@CLOBBERBUILTINS ` compiler directive. +---@field clobberbuiltins boolean +---Settable. +---Configures the ``debugEachOpcode`` setting. +--- +---NOTE: This makes the game VERY slow, use with caution. +--- +---If true, each opcode that is executed by the CPU will be accompanied by +---an entry in the KSP log. This is a debugging tool for those who are very +---familiar with the inner workings of kOS and should rarely be used outside +---the kOS dev team. +--- +---This change takes effect immediately. +---@field debugeachopcode boolean +---Settable. +---@field blizzy boolean +---Settable. +---:type: `number`. range = [0,1] +--- +---Configures the ``Brightness`` setting. +--- +---This is the default starting brightness setting a new +---kOS in-game terminal will have when it is invoked. This +---is just the default for new terminals. Individual terminals +---can have different settings, either by setting the value +---:attr:`Terminal:BRIGHTNESS` in a script, or by manually moving the +---brightness slider widget on that terminal. +--- +---The value here must be between 0 (invisible) and 1 (Max brightness). +---@field brightness number +---Settable. +---:type: `number` integer-only. range = [6,20] +--- +---Configures the ``TerminalFontDefaultSize`` setting. +--- +---This is the default starting font height (in pixels. not "points") +---for all newly created kOS in-game terminals. This +---is just the default for new terminals. Individual terminals +---can have different settings, either by setting the value +---:attr:`Terminal:CHARHEIGHT` in a script, or by manually clicking +---the font adjustment buttons on that terminal. +--- +---The value here must be at least 6 (nearly impossible to read) +---and no greater than 30 (very big). It will be rounded to the +---nearest integer when setting the value. +---@field defaultfontsize number +---Settable. +---:type: `number` integer-only. range = [15,255] +--- +---Configures the ``TerminalDefaultWidth`` setting. +--- +---This is the default starting width (in number of character cells, +---not number of pixels) for all newly created kOS in-game terminals. +---This is just the default for new terminals. Individual terminals +---can have different settings, either by setting the value +---:attr:`Terminal:WIDTH` in a script, or by manually dragging the +---resize corner of the terminal with the mouse. +---@field defaultwidth number +---Settable. +---:type: `number` integer-only. range = [3,160] +--- +---Configures the ``TerminalDefaultHeight`` setting. +--- +---This is the default starting height (in number of character cells, +---not number of pixels) for all newly created kOS in-game terminals. +---This is just the default for new terminals. Individual terminals +---can have different settings, either by setting the value +---:attr:`Terminal:HEIGHT` in a script, or by manually dragging the +---resize corner of the terminal with the mouse. +---@field defaultheight number +---Settable. +---*This is settable by use of the "Toggle Autopilot" Action Group too.* +--- +---When this is set to True, it suppresses all of kOS's attempts to +---override the steering, throttle, or translation controls, leaving +---them entirely under manual control. It is intended to be a way +---to let you take manual control in an emergency quickly (through +---the toolbar window where this setting appears) without having to +---quit the running program or figure out which terminal window has +---the program causing the control lock. +--- +---You can also bind this setting to an action group for a kOS core part +---in the VAB or SPH. The action is called "Toggle Suppress". +---(Or "Suppress On" and "Suppress Off" for one-way action groups +---that don't toggle.) +--- +---While it does suppress steering, throttle, and translation, it cannot +---suppress action groups or staging. +---@field suppressautopilot boolean + +---@class CraftTemplate : Structure +---Returns the name of the craft. It may differ from the file name. +---@field name string +---Returns the description field of the craft, which may be edited from the +---drop down window below the craft name in the editor. +---@field description string +---Name of the editor from which the craft file was saved. Valid values are +---``"VAB"`` and ``"SPH"``. +---@field editor string +---Returns the name of the default launch site of the craft. Valid values are +---``"LAUNCHPAD"`` and ``"RUNWAY"``. +---@field launchsite string +---Returns the total default mass of the craft. This includes the dry mass and the +---mass of any resources loaded onto the craft by default. +---@field mass number +---Returns the total default cost of the craft. This includes the cost of the +---vessel itself as well as any resources loaded onto the craft by default. +---@field cost number +---Returns the total number of parts on the craft. +---@field partcount number + +---@class Sensor : Part +---Settable. +---True of the sensor is enabled. Can SET to cause the sensor to activate or de-activate. +---@field active boolean +---:access: Get only +---@field type string +---The value of the sensor's readout, usualy including the units. +---@field display string +---The rate at which this sensor drains ElectricCharge. +---@field powerconsumption number +---Call this method to cause the sensor to switch between active and deactivated or visa versa. +---@field toggle fun() + +---@class Decoupler : Part + +---@class RCS : Part +---Settable. +---Is this rcs thruster enabled. +---@field enabled boolean +---Settable. +---Is yaw control enabled for this rcs thruster. +---@field yawenabled boolean +---Settable. +---Is pitch control enabled for this rcs thruster. +---@field pitchenabled boolean +---Settable. +---Is roll control enabled for this rcs thruster. +---@field rollenabled boolean +---Settable. +---Is port/starboard control enabled for this rcs thruster. +---@field starboardenabled boolean +---Settable. +---Is dorsal/ventral control enabled for this rcs thruster. +---@field topenabled boolean +---Settable. +---Is fore/aft control enabled for this rcs thruster. +---@field foreenabled boolean +---Settable. +---Does this thruster apply fore thrust when the ship throttled up. +---@field forebythrottle boolean +---Settable. +---Does this thruster always apply full thrust. +---@field fullthrust boolean +---Settable. +---:type: `number` (%) +--- +---If this is a thruster with a thrust limiter (tweakable) enabled, what +---percentage is it limited to? Note that this is expressed as a +---percentage, not a simple 0..1 coefficient. e.g. To set thrustlimit +---to half, you use a value of 50.0, not 0.5. +--- +---This value is not allowed to go outside the range [0..100]. If you +---attempt to do so, it will be clamped down into the allowed range. +--- +---Note that although a kerboscript is allowed to set the value to a +---very precise number (for example 10.5123), the stock in-game display +---widget that pops up when you right-click the rcs will automatically +---round it to the nearest 0.5 whenever you open the panel. So if you +---do something like ``set ship:part[20]:thrustlimit to 10.5123.`` in +---your script, then look at the rightclick menu for the rcs, the very +---act of just looking at the menu will cause it to become 10.5 instead +---of 10.5123. There isn't much that kOS can do to change this. It's a +---user interface decision baked into the stock game. +---@field thrustlimit number +---Settable. +---Default: 0.05. +--- +---**Please note the warning below before you try to SET this.** +--- +---The stock game imposes a large dead zone on RCS thrusters. By +---default they will not respond to any inputs less than this value. +---For example, at the default value of 0.05, the RCS thruster +---will ignore this statement:: +--- +--- set ship:control:yaw to 0.049. +--- +---but it will respond to this statement:: +--- +--- set ship:control:yaw to 0.051. +--- +---The reason this limit exists is apparently (this is speculation, +---warning) that it's how the stock game prevents SAS from spending +---a lot of monopropellant when it wiggles the controls small amounts. +---When control inputs are smaller than this value, then the RCS +---thrusters ignore them and only the reaction wheels and engine +---gimbals respond. Despite the fact that this is really only a +---problem with SAS, the game appears to have solved the problem by +---imposing this null zone physically on the RCS parts themselves so +---the limit affects everything that uses them, including kOS +---autopiloting and user manual control. +--- +---The best way to deal with this, if you have a script that wants +---the RCS thrusters to operate at a value less than this, is +---to pulse the input intermittently on and off at 0.05 to achieve +---amounts smaller than 0.05, rather than trying to solve it by +---setting this value. (Remember that in the real world, thrusters +---have a minimum thrust they can't go below so it's not entirely +---unrealistic for this deadband to exist in the game.) +---@field deadband number +---:type: `number` (kN) +--- +---Taking into account the thrust limiter tweakable setting, how much thrust would this rcs thruster give at its current thrust limit setting and atmospheric pressure conditions, if one of the control axes that activated it (yaw, pitch, roll, fore, aft, or top) was maxxed . +---@field availablethrust number +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---:type: `number` (kN) +--- +---Taking into account the thrust limiter tweakable setting, how much thrust at the given atmospheric pressure would this rcs thruster give at its current thrust limit setting if one of the control axes that activated it (yaw, pitch, roll, fore, aft, or top) was maxxed. The pressure is measured in ATMs, meaning 0.0 is a vacuum, 1.0 is sea level at Kerbin. +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field availablethrustat fun(pressure: number): number +---:type: `number` (kN) +--- +---How much thrust would this rcs thruster give at its current atmospheric pressure if one of the control axes that activates it (yaw, pitch, roll, fore, aft, or top) was maxxed, and the thrust limiter was max at 100%. Note this might not be the thruster's actual max thrust it could have under other air pressure conditions. Some thrusters have a very different value for MAXTHRUST in vacuum as opposed to at sea level pressure. +---@field maxthrust number +---:type: `number` (units/s) +--- +---How much fuel volume would this rcs thruster consume at standard pressure and velocity if one of the control axes that activated it (yaw, pitch, roll, fore, aft, or top) was maxxed, and the thrust limiter was max at 100%. Note this might not be the engine's actual max fuel flow it could have under other air pressure conditions. +---@field maxfuelflow number +---:type: `number` (Mg/s) +--- +---How much fuel mass would this rcs thruster consume at standard pressure and velocity if one of the control axes that activated it (yaw, pitch, roll, fore, aft, or top) was maxxed, and the thrust limiter was max at 100%. Note this might not be the engine's actual max fuel flow it could have under other air pressure conditions. +---@field maxmassflow number +---`Specific impulse `_ +---@field isp number +---Synonym for :VACUUMISP +---@field visp number +---Synonym for :VACUUMISP +---@field vacuumisp number +---Synonym for :SEALEVELISP +---@field slisp number +---Synonym for :SEALEVELISP +---@field sealevelisp number +---Is this rcs thruster failed because it is starved of a resource (monopropellant)? +---@field flameout boolean +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---`Specific impulse `_ at the given atmospheric pressure. Use a pressure of 0 for vacuum, and 1 for sea level (on Kerbin). +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field ispat fun(pressure: number): number +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---:type: `number` (kN) +--- +---How much thrust would this rcs thruster give if one of the control axes that activated it (yaw, pitch, roll, fore, aft, or top) was maxxed and thrust limiter was max at the given atmospheric pressure. Use a pressure of 0.0 for vacuum, and 1.0 for sea level (on Kerbin) (or more than 1 for thicker atmospheres like on Eve). +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field maxthrustat fun(pressure: number): number +---:type: `List` of :struct:`Vectors ` +--- +---This gives a list of all the vectors that this RCS module can thrust along. Vectors returned are of unit length. The vectors are returned in Ship-Raw coordinates, rather than relative to the ship. (i.e. if it thrusts along the ship's fore axis, and the ship's current ``ship:facing:forevector`` is ``V(0.7071, 0.7071, 0)``, then the value this returns would be ``V(0.7071, 0.7071, 0)``, not ``V(0,0,1)``). +---@field thrustvectors List +---:type: `Lexicon` of `CONSUMEDRESOURCERCS` +--- +---This gives a lexicon of all the resources this rcs thruster consumes, keyed by resource name. +---@field consumedresources Lexicon + +---@class Separator : Decoupler +---Force of the push that happens when this decoupler is fired. +---@field ejectionforce number +---True if this part has already had its decoupling event triggered. +---@field isdecoupled boolean +---True if this part's decoupling event is in the vessel's staging list. +---@field staged boolean + +---@class DockingPort : Decoupler +---@field aquirerange number +---@field aquireforce number +---@field aquiretorque number +---gets the range at which the port will "notice" another port and pull on it. +---@field acquirerange number +---gets the force with which the port pulls on another port. +---@field acquireforce number +---gets the rotational force with which the port pulls on another port. +---@field acquiretorque number +---@field reengagedistance number +---name of vessel on the other side of the docking port. +---@field dockedshipname string +---One of the following string values: +--- +---``Ready`` +--- Docking port is not yet attached and will attach if it touches another. +---``Docked (docker)`` +--- One port in the joined pair is called the docker, and has this state +---``Docked (dockee)`` +--- One port in the joined pair is called the dockee, and has this state +---``Docked (same vessel)`` +--- Sometimes KSP says this instead. It's unclear what it means. +---``Disabled`` +--- Docking port will refuse to dock if it bumps another docking port. +---``PreAttached`` +--- Temporary state during the "wobbling" while two ports are magnetically touching but not yet docked solidly. During this state the two vessels are still tracked as separate vessels and haven't become one yet. +---@field state string +---True if this part can be picked with ``SET TARGET TO``. +---@field targetable boolean +---Call this to cause the docking port to detach. +---@field undock fun() +---@field target fun() +---Gets the facing of this docking port which may differ from the facing of the part itself if the docking port is aimed out the side of the part, as in the case of the inline shielded docking port. +---@field portfacing Direction +---The coordinates of the point on the docking port part where the +---port attachment spot is located. This is different from the +---part's position itself because that's the position of the center +---of the whole part. This is the position of the face of the +---docking port. Coordinates are in SHIP-RAW xyz coords. +---@field nodeposition Vector +---Each docking port has a node type string that specifies its +---compatibility with other docking ports. In order for two docking +---ports to be able to attach to each other, the values for their +---NODETYPEs must be the same. +--- +---The base KSP stock docking port parts all use one of the following +---three values: +--- +--- - "size0" for all Junior-sized docking ports. +--- - "size1" for all Normal-sized docking ports. +--- - "size2" for all Senior-sized docking ports. +--- +---Mods that provide their own new kinds of docking port might use +---any other value they feel like here, but only if they are trying +---to declare that the new part isn't supposed to be able to connect +---to stock docking ports. Any docking port that is meant to connect +---to stock ports will have to adhere to the above scheme. +---@field nodetype string +---@field dockwatchers UniqueSet +---@field undockwatchers UniqueSet +---:type: `DockingPort`, or the `string` "None" if no such port. +--- +---The docking port this docking port is attached to. +---If this docking port is not actually attached to another port, attempting +---to call this will return a String instead of a DockingPort, and that String +---will have the value "None". (Alternatively, you can test if this +---docking port has a partner port attached by calling +---:meth:`DockingPort:HASPARTER`.) +---@field partner any +---Whether or not this docking port is attached to another docking port. +---@field haspartner boolean + +---@class Engine : Part +---Call to make the engine turn on. +---@field activate fun() +---Call to make the engine turn off. +---@field shutdown fun() +---Settable. +---:type: `number` (%) +--- +---If this an engine with a thrust limiter (tweakable) enabled, what +---percentage is it limited to? Note that this is expressed as a +---percentage, not a simple 0..1 coefficient. e.g. To set thrustlimit +---to half, you use a value of 50.0, not 0.5. +--- +---This value is not allowed to go outside the range [0..100]. If you +---attempt to do so, it will be clamped down into the allowed range. +--- +---Note that although a kerboscript is allowed to set the value to a +---very precise number (for example 10.5123), the stock in-game display +---widget that pops up when you right-click the engine will automatically +---round it to the nearest 0.5 whenever you open the panel. So if you +---do something like ``set ship:part[20]:thrustlimit to 10.5123.`` in +---your script, then look at the rightclick menu for the engine, the very +---act of just looking at the menu will cause it to become 10.5 instead +---of 10.5123. There isn't much that kOS can do to change this. It's a +---user interface decision baked into the stock game. +---@field thrustlimit number +---:type: `number` (kN) +--- +---How much thrust would this engine give at its current atmospheric pressure and velocity if the throttle was max at 1.0, and the thrust limiter was max at 100%. Note this might not be the engine's actual max thrust it could have under other air pressure conditions. Some engines have a very different value for MAXTHRUST in vacuum as opposed to at sea level pressure. Also, some jet engines have a very different value for MAXTHRUST depending on how fast they are currently being rammed through the air. Also note that this will read zero if the engine is currently disabled. +---@field maxthrust number +---:type: `number` (kN) +--- +---How much thrust is this engine giving at this very moment. +---@field thrust number +---:type: `number` (units/s) +--- +---How much fuel volume is this engine consuming at this very moment. +---@field fuelflow number +---:type: `number` (units/s) +--- +---How much fuel volume would this engine consume at standard pressure and velocity if the throttle was max at 1.0, and the thrust limiter was max at 100%. Note this might not be the engine's actual max fuel flow it could have under other air pressure conditions. Some jet engines have a very different fuel consumption depending on how fast they are currently being rammed through the air. +---@field maxfuelflow number +---:type: `number` (Mg/s) +--- +---How much fuel mass is this engine consuming at this very moment. +---@field massflow number +---:type: `number` (Mg/s) +--- +---How much fuel mass would this engine consume at standard pressure and velocity if the throttle was max at 1.0, and the thrust limiter was max at 100%. Note this might not be the engine's actual max fuel flow it could have under other air pressure conditions. Some jet engines have a very different fuel consumption depending on how fast they are currently being rammed through the air. +---@field maxmassflow number +---`Specific impulse `_ +---@field isp number +---Synonym for :VACUUMISP +---@field visp number +---Synonym for :VACUUMISP +---@field vacuumisp number +---Synonym for :SEALEVELISP +---@field slisp number +---Synonym for :SEALEVELISP +---@field sealevelisp number +---Is this engine failed because it is starved of a resource (liquidfuel, oxidizer, oxygen)? +---@field flameout boolean +---Has this engine been ignited? If both :attr:`Engine:IGNITION` and :attr:`Engine:FLAMEOUT` are true, that means the engine could start up again immediately if more resources were made available to it. +---@field ignition boolean +---Is this an engine that can be started again? Usually True, but false for solid boosters. +---@field allowrestart boolean +---Is this an engine that can be shut off once started? Usually True, but false for solid boosters. +---@field allowshutdown boolean +---Is this an engine that is stuck at a fixed throttle? (i.e. solid boosters) +---@field throttlelock boolean +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---`Specific impulse `_ at the given atmospheric pressure. Use a pressure of 0 for vacuum, and 1 for sea level (on Kerbin). +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field ispat fun(pressure: number): number +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---:type: `number` (kN) +--- +---How much thrust would this engine give if both the throttle and thrust limtier was max at the current velocity, and at the given atmospheric pressure. Use a pressure of 0.0 for vacuum, and 1.0 for sea level (on Kerbin) (or more than 1 for thicker atmospheres like on Eve). Note that this will read zero if the engine is currently disabled. +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field maxthrustat fun(pressure: number): number +---:type: `number` (kN) +--- +---Taking into account the thrust limiter tweakable setting, how much thrust would this engine give if the throttle was max at its current thrust limit setting and atmospheric pressure and velocity conditions. Note that this will read zero if the engine is currently disabled. +---@field availablethrust number +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---:type: `number` (kN) +--- +---Taking into account the thrust limiter tweakable setting, how much thrust would this engine give if the throttle was max at its current thrust limit setting and velocity, but at a different atmospheric pressure you pass into it. The pressure is measured in ATM's, meaning 0.0 is a vacuum, 1.0 is sea level at Kerbin. Note that this will read zero if the engine is currently disabled. +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field availablethrustat fun(pressure: number): number +---:type: `number` (kN) +--- +---Taking into account the thrust limiter tweakable setting, how much thrust would this engine give if the throttle was max at its current thrust limit setting and atmospheric pressure and velocity conditions. This will give the correct value even if the engine is currently disabled. +---@field possiblethrust number +---:parameter pressure: atmospheric pressure (in standard Kerbin atmospheres) +---:type: `number` (kN) +--- +---Taking into account the thrust limiter tweakable setting, how much thrust would this engine give if the throttle was max at its current thrust limit setting and velocity, but at a different atmospheric pressure you pass into it. The pressure is measured in ATM's, meaning 0.0 is a vacuum, 1.0 is sea level at Kerbin. This will give the correct value even if the engine is currently disabled. +---(Pressure must be greater than or equal to zero. If you pass in a +---negative value, it will be treated as if you had given a zero instead.) +---@field possiblethrustat fun(pressure: number): number +---@field maxpossiblethrust number +---@field maxpossiblethrustat fun(param1: number): number +---:type: `Lexicon` of `ConsumedResource` +--- +---The fuel resources this engine consumes, and in what ratios. +---@field consumedresources Lexicon +---Does this engine have multiple modes (i.e. RAPIER)? Check this before calling multi-mode specific suffixes. +---@field multimode boolean +---:type: `List` of strings +--- +---Lists names of modes of this engine if multimode, returns a list of 1 string "Single mode" otherwise. +---@field modes List +---Name of the current mode. Only assessible for multi-mode engines. +---@field mode string +---Call to switch to another mode. Only assessible for multi-mode engines. +---@field togglemode fun() +---Settable. +---True for primary mode, false for secondary. Setting to other value equals toggling the mode. Only assessible for multi-mode engines. +---@field primarymode boolean +---Settable. +---Is automatic switching enabled? Can set to switch between manual and automatic switching. Only assessible for multi-mode engines. +---@field autoswitch boolean +---Does this engine have a gimbal enabled? +---@field hasgimbal boolean +---Returns the `Gimbal` attached to this engine. Only accessible if the gimbal is present (Use :attr:`Engine:HASGIMBAL` to check if available). +---@field gimbal Gimbal +---If RealFuels is installed, returns true if this engine is a type of engine that requires ullage, otherwise returns false. +---Note: this is a static property of the engine, for current fuel status, check `FUELSTABILITY`. +---@field ullage boolean +---If RealFuels is installed, returns the fuel stability of this engine as a value between 0 and 1 (where 1 is fullly stable), otherwise returns 1. +---Engines that don't require ullage will always return 1, unless they are pressure fed and the feed pressure is too low. +---@field fuelstability number +---If RealFuels is installed, returns true if this engine is pressure fed, otherwise returns false. +---@field pressurefed boolean +---If RealFuels is installed, returns the number of ignitions remaining, or -1 if it is unlimited, otherwise returns -1. +---@field ignitions number +---If RealFuels is installed, returns the minimum throttle setting as a value between 0 and 1, otherwise returns 0. +---@field minthrottle number +---If RealFuels is installed, returns the configuration name of this engine if applicable, otherwise returns the part title. +---@field config string + +---@class LaunchClamp : Decoupler + +---@class Part : Structure +---Call this function to cause the game to do the same thing as when you right-click a part on a vessel and select "control from here" on the menu. It rotates the control orientation so that fore/aft/left/right/up/down now match the orientation of this part. NOTE that this will not work for every type of part. It only works for those parts that KSP itself allows this for (control cores and docking ports). It accepts no arguments, and returns no value. +---All vessels must have at least one "control from" +---part on them somewhere, which is why there's no mechanism for un-setting +---the "control from" setting other than to pick another part and set it +---to that part instead. +--- +---.. warning:: +--- This suffix is only callable for parts attached to the :ref:`CPU Vessel ` +---@field controlfrom fun() +---Name of part as it is used behind the scenes in the game's API code. +--- +---A part's *name* is the name it is given behind the scenes in KSP. It never appears in the normal GUI for the user to see, but it is used in places like Part.cfg files, the saved game persistence file, the ModuleManager mod, and so on. +---@field name string +---@field fuelcrossfeed boolean +---The title of the part as it appears on-screen in the gui. +--- +---A part's *title* is the name it has inside the GUI interface on the screen that you see as the user. +---@field title string +---the stage this part is part of. +---@field stage number +---Part Craft ID. This is similar to :attr:`Part:UID`, except that this +---ID is only unique per craft design. In other words if you launch two +---copies of the same design without editing the design at all, then the +---same part in both copies of the design will have the same ``Part:CID`` +---as each other. (This value is kept in the *craft file* and repeated +---in each instance of the vessel that you launch). +---@field cid string +---Part Universal ID. All parts have a unique ID number. Part's uid never changes because it is the same value as stored in persistent.sfs. Although you can compare parts by comparing their uid it is recommended to compare parts directly if possible. +---@field uid string +---The rotation of this part's X-axis, which points out of its side and is probably not what you want. You probably want the :attr:`Part:FACING` suffix instead. +---@field rotation Direction +---The location of this part in the universe. It is expressed in the same frame of reference as all the other positions in kOS, and thus can be used to help do things like navigate toward the position of a docking port. +---@field position Vector +---@field com Vector +---Settable. +---The name tag value that may exist on this part if you have given the part a name via the :ref:`name-tag system `. +--- +---A part's *tag* is whatever custom name you have given it using the :ref:`name-tag system described here `. This is probably the best naming convention to use because it lets you make up whatever name you like for the part and use it to pick the parts you want to deal with in your script. +--- +---WARNING: This suffix is only settable for parts attached to the :ref:`CPU Vessel ` +---@field tag string +---The direction that this part is facing, which is also the rotation +---that would transform a vector from a coordinate space where the +---axes were oriented to match the part, to one where they're +---oriented to match the world's ship-raw coordinates. +---@field facing Direction +---Constructs a "bounding box" structure that can be used to +---give your script some idea of the extents of the part's shape - how +---wide, long, and tall it is. +--- +---It can be slightly expensive in terms of CPU time to keep calling +---this suffix over and over, as kOS has to perform some work to build +---this structure. If you need to keep looking at a part's bounds again +---and again in a loop, and you know that part's shape isn't going to be +---changing (i.e. you're not going to extend a solar panel or something +---like that), then it's better for you to call this ``:BOUNDS`` suffix +---just once at the top, storing the result in a variable that you use in +---the loop. +--- +---More detailed information is found on the documentation page for +---`Bounds`. +---@field bounds Bounds +---list of the `Resource` in this part. +---@field resources List +---True if this part can be selected by KSP as a target. +--- +---This example assumes you have a target vessel picked, and that the target vessel is loaded into full-physics range and not "on rails". vessels that are "on rails" do not have their full list of parts entirely populated at the moment:: +--- +--- LIST PARTS FROM TARGET IN tParts. +--- +--- PRINT "The target vessel has a". +--- PRINT "partcount of " + tParts:LENGTH. +--- +--- SET totTargetable to 0. +--- FOR part in tParts { +--- IF part:TARGETABLE { +--- SET totTargetable TO totTargetable + 1. +--- } +--- } +--- +--- PRINT "...and " + totTargetable. +--- PRINT " of them are targetable parts.". +---@field targetable boolean +---the vessel that contains this part. +---@field ship Vessel +---:parameter name: (`string`) The name of the module to check for +---:returns: `boolean` +--- +---Checks to see if this part contains the `PartModule` with the name +---given. If it does, this returns true, else it returns false. (If +---``HASMODULE(name)`` returns false, then this means an attempt to use +---``GETMODULE(name)`` would fail with an error.) +---@field hasmodule fun(name: string): boolean +---:parameter name: (`string`) Name of the part module +---:returns: `PartModule` +--- +---Get one of the :struct:`PartModules ` attached to this part, given the name of the module. (See :attr:`Part:MODULES` for a list of all the names available). +---@field getmodule fun(name: string): PartModule +---:parameter index: (`number`) Index number of the part module +---:returns: `PartModule` +--- +---Get one of the :struct:`PartModules ` attached to this part, +---given the index number of the module. You can use :attr:`Part:MODULES` for a +---list of names of all modules on the part. The indexes are not guaranteed to +---always be in the same order. It is recommended to iterate over the indexes +---with a loop and verify the module name:: +--- +--- local moduleNames is part:modules. +--- for idx in range(0, moduleNames:length) { +--- if moduleNames[idx] = "test module" { +--- local pm is part:getmodulebyindex(idx). +--- DoSomething(pm). +--- } +--- } +---@field getmodulebyindex fun(index: number): PartModule +---:type: `List` of strings +--- +---list of the names of :struct:`PartModules ` enabled for this part. +---@field modules List +---:type: `List` of strings +--- +---list of the names of :struct:`PartModules ` enabled for this part. +---@field allmodules List +---When walking the :ref:`tree of parts `, this is the part that this part is attached to on the way "up" toward the root part. +---@field parent any +---:type: `Decoupler` or `string` +--- +---The decoupler/separator that will decouple this part when activated. `None` if no such exists. +---@field decoupler any +---:type: `Decoupler` or `string` +--- +---The decoupler/separator that will decouple this part when activated. `None` if no such exists. +---@field separator any +---The stage number where this part will get decoupled. -1 if cannot be decoupled. +---@field decoupledin number +---The stage number where this part will get decoupled. -1 if cannot be decoupled. +---@field separatedin number +---When walking the :ref:`tree of parts `, this is true as long as there is a parent part to this part, and is false if this part has no parent (which can only happen on the root part). +---@field hasparent boolean +---:type: `List` of :struct:`Parts ` +--- +---When walking the :ref:`tree of parts `, this is all the parts that are attached as children of this part. It returns a list of zero length when this part is a "leaf" of the parts tree. +---@field children List +---The mass of the part if all of its resources were empty. If the part has no physics this will always be 0. +---@field drymass number +---The current mass or the part and its resources. If the part has no physics this will always be 0. +---@field mass number +---The mass of the part if all of its resources were full. If the part has no physics this will always be 0. +---@field wetmass number +---This comes from a part's configuration and is an artifact of the KSP simulation. +--- +---For a list of stock parts that have this attribute and a fuller explanation see `the KSP wiki page about massless parts `_. +---@field hasphysics boolean +---Returns how many parts are in the same symmetry set as this part. +--- +---Note that all parts should at least return a minimum value of 1, since +---even a part placed without symmetry is technically in a group of 1 part, +---itself. +---@field symmetrycount number +---Tells you the type of symmetry this part has by returning a number +---as follows: +--- +---0 = This part has radial symmetry +--- +---1 = This part has mirror symmetry +--- +---It's unclear if this means anything when the part's symmetry is 1x. +---@field symmetrytype number +---:returns: nothing +--- +---Call this method to remove this part from its symmetry group, reverting +---it back to a symmetry group of 1x (just itself). This has the same +---effect as pressing the "Remove From Symmetry" button in the part's +---action window. +--- +---Note that just like when you press the "Remove from Symmetry" button, +---once a part has been removed from symmetry you don't have a way to +---put it back into the symmetry group again. +---@field removesymmetry fun() +---:parameter name: (`number`) Index of which part in the symmetry group +---:returns: `Part` +--- +---When a set of parts has been placed with symmetry in the Vehicle +---Assembly Building or Space Plane Hangar, this method can be used +---to find all the parts that are in the same symmetrical group. +--- +---The index is numbered from zero to :attr:``SYMMETRYCOUNT`` minus one. +--- +---The zero-th symmetry partner is this part itself. Even parts placed +---without symmetry still are technically in a symmetry group of 1 part. +--- +---The index also wraps around in a cycle, such that if there are 4 parts in +---symmetry, then ``SYMMETRYPARTNER(0)`` and ``SYMMETRYPARTNER(4)`` and +---``SYMMETRYPARTNER(8)`` would all actually be the same part. +--- +---Example:: +--- +--- // Print the symmetry group a part is inside: +--- function print_sym { +--- parameter a_part. +--- +--- print a_part + " is in a " + a_part:SYMMETRYCOUNT + "x symmetry set.". +--- +--- if a_part:SYMMETRAYCOUNT = 1 { +--- return. // no point in printing the list when its not in a group. +--- } +--- +--- if a_part:SYMMETRYTYPE = 0 { +--- print " The symmetry is radial.". +--- } else if a_part:SYMMETRYTYPE = 1 { +--- print " The symmetry is mirror.". +--- } else { +--- print " The symmetry is some other weird kind that". +--- print " didn't exist back when this example was written.". +--- } +--- +--- print " The Symmetry Group is: ". +--- for i in range (0, a_part:SYMMETRYCOUNT) { +--- print " [" + i + "] " + a_part:SYMMETRYPARTNER(i). +--- } +--- } +---@field symmetrypartner fun(index: number): Part +---:parameter name: (`string`) Name of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSNAMED(name)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partsnamed fun(name: string): List +---:parameter namePattern: (`string`) Pattern of the name of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSNAMEDPATTERN(namePattern)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partsnamedpattern fun(namePattern: string): List +---:parameter title: (`string`) Title of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSTITLED(title)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partstitled fun(title: string): List +---:parameter titlePattern: (`string`) Patern of the title of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSTITLEDPATTERN(titlePattern)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partstitledpattern fun(titlePattern: string): List +---:parameter name: (`string`) name, title or tag of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSDUBBED(name)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partsdubbed fun(name: string): List +---:parameter namePattern: (`string`) Pattern of the name, title or tag of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSDUBBEDPATERN(namePattern)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partsdubbedpattern fun(namePattern: string): List +---:parameter name: (`string`) Name of the part modules +---:return: `List` of `PartModule` objects +--- +---Same as :meth:`Vessel:MODULESNAMED(name)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field modulesnamed fun(name: string): List +---:parameter tag: (`string`) Tag of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSTAGGED(tag)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partstagged fun(tag: string): List +---:parameter tagPattern: (`string`) Pattern of the tag of the parts +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:PARTSTAGGEDPATTERN(tagPattern)` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field partstaggedpattern fun(tagPattern: string): List +---:return: `List` of `Part` objects +--- +---Same as :meth:`Vessel:ALLTAGGEDPARTS()` except that this version +---doesn't search the entire vessel tree and instead it only searches the +---branch of the vessel's part tree from the current part down through +---its children and its children's children and so on. +---@field alltaggedparts List + +---@class ActiveResource : AggregateResource + +---@class Element : Structure +---Settable. +---The name of the Element element, is an artifact from the vessel the element belonged to before docking. Cannot be set to an empty `string`. +---@field name string +---A unique id +---@field uid string +---The parent vessel containing the element. +---@field vessel Vessel +---:type: `List` of `Part` objects +--- +---A List of all the :struct:`parts ` on the Element. ``SET FOO TO SHIP:PARTS.`` has exactly the same effect as ``LIST PARTS IN FOO.``. For more information, see :ref:`ship parts and modules `. +---@field parts List +---:type: `List` of `DockingPort` objects +--- +---A List of all the :struct:`docking ports ` on the Element. +---@field dockingports List +---@field decouplers List +---@field separators List +---:type: `List` of `AggregateResource` objects +--- +---A List of all the :struct:`AggregateResources ` on the element. +---@field resources List + +---@class Atmosphere : Structure +---The Body that this atmosphere is around - as a STRING NAME, not a Body object. +---@field body string +---True if this atmosphere is "real" and not just a dummy placeholder. +---@field exists boolean +---True if the air has oxygen and could therefore be used by a jet engine's intake. +---@field oxygen boolean +---:type: `number` (atm) +--- +---Pressure at the body's sea level. +--- +---Result is returned in Atmospheres. 1.0 Atmosphere = same as Kerbin or Earth. +---If you prefer to see the answer in KiloPascals, multiply the answer by +---:global:`Constant:AtmToKPa`. +--- +---.. warning:: +--- .. versionchanged:: 1.1.0 +--- Previous versions returned this value in KiloPascals by mistake, +--- which has now been changed to Atmospheres. +---@field sealevelpressure number +---:type: `number` (m) +--- +---The altitude at which the atmosphere is "officially" advertised as ending. (actual ending value differs, see below). +---@field height number +---:parameter altitude: The altitude above sea level (in meters) you want to know the pressure for. +---:type: `number` (atm) +--- +---Number of Atm's of atmospheric pressure at the given altitude. +---If you pass in zero, you should get the sea level pressure. +---If you pass in 10000, you get the pressure at altitude=10,000m. +---This will return zero if the body has no atmosphere, or if the altitude you +---pass in is above the max atmosphere altitude for the body. +--- +---Result is returned in Atmospheres. 1.0 Atmosphere = same as Kerbin or Earth. +---If you prefer to see the answer in KiloPascals, multiply the answer by +---:global:`Constant:AtmToKPa`. +---@field altitudepressure fun(altitude: number): number +---:acces: Get only +--- +---The Molecular Mass of the gas the atmosphere is composed of. +---Units are in kg/mol. +---`Wikipedia Molar Mass Explanation `_. +---@field molarmass number +---The Adiabatic index of the gas the atmosphere is composed of. +---`Wikipedia Adiabatic Index Explanation `_. +---@field adiabaticindex number +---The Adiabatic index of the gas the atmosphere is composed of. +---`Wikipedia Adiabatic Index Explanation `_. +---@field adbidx number +---:parameter: altitude (`number`) the altitude to query temperature at. +--- +---Returns an approximate atmosphere temperature on this world at the given altitude. +---Note that this is only approximate because the temperature will vary depending +---on the sun position in the sky (i.e. your latitude and what time of day it is). +---@field altitudetemperature fun(altitude: number): number +---:parameter: altitude (`number`) the altitude to query temperature at. +--- +---Returns an approximate atmosphere temperature on this world at the given altitude. +---Note that this is only approximate because the temperature will vary depending +---on the sun position in the sky (i.e. your latitude and what time of day it is). +---@field alttemp fun(altitude: number): number + +---@class RGBA : Structure +---Settable. +---@field r number +---Settable. +---@field red number +---Settable. +---@field g number +---Settable. +---@field green number +---Settable. +---@field b number +---Settable. +---@field blue number +---Settable. +---@field a number +---Settable. +---@field alpha number +---@field html string +---@field hex string + +---@class Orbitable : Structure +---Name of this vessel or body. +---@field name string +---:type: `number` (deg) +--- +---.. deprecated:: 0.15 +--- +--- This is only kept here for backward compatibility. +--- in new scripts you write, use :attr:`OBT:APOAPSIS `. +--- (i.e. use ``SHIP:OBT:APOAPSIS`` instead of ``SHIP:APOAPSIS``, +--- or use ``MUN:OBT:APOAPSIS`` instead of ``MUN:APOAPSIS``, etc). +---@field apoapsis number +---:type: `number` (deg) +--- +---.. deprecated:: 0.15 +--- +--- This is only kept here for backward compatibility. +--- in new scripts you write, use :attr:`OBT:PERIAPSIS `. +--- (i.e. use ``SHIP:OBT:PERIAPSIS`` instead of ``SHIP:PERIAPSIS``). +--- or use ``MUN:OBT:PERIAPSIS`` instead of ``MUN:PERIAPSIS``, etc). +---@field periapsis number +---The `Body` that this object is orbiting. I.e. ``Mun:BODY`` returns ``Kerbin``. +---@field body Body +---True if this object has a body it orbits (false only when this object is the Sun, pretty much). +---@field hasbody boolean +---True if this object has a body it orbits (false only when this object is the Sun, pretty much). +---@field hasobt boolean +---True if this object has a body it orbits (false only when this object is the Sun, pretty much). +---@field hasorbit boolean +---pointing straight up away from the SOI body. +---@field up Direction +---pointing straight north on the SOI body, parallel to the surface of the SOI body. +---@field north Direction +---pointing in the direction of this object's **orbitable-frame** velocity +---@field prograde Direction +---pointing in the opposite of the direction of this object's **orbitable-frame** velocity +---@field retrograde Direction +---pointing in the direction of this object's **surface-frame** velocity. Note that if this Orbitable is itself a body, remember that this is relative to the surface of the SOI body, not this body. +---@field srfprograde Direction +---pointing in the opposite of the direction of this object's **surface-frame** velocity. Note that this is relative to the surface of the SOI body. +---@field srfretrograde Direction +---The current single orbit "patch" that this object is on (not the future orbits it might be expected to achieve after maneuver nodes or encounter transitions, but what the current orbit would be if nothing changed and no encounters perturbed the orbit. +---@field obt Orbit +---The current single orbit "patch" that this object is on (not the future orbits it might be expected to achieve after maneuver nodes or encounter transitions, but what the current orbit would be if nothing changed and no encounters perturbed the orbit. +---@field orbit Orbit +---The position of this object in the :ref:`SHIP-RAW reference frame ` +---@field position Vector +---The :struct:`orbitable velocity ` of this object in the :ref:`SHIP-RAW reference frame ` +---@field velocity OrbitableVelocity +---:type: `number` (m) +--- +---The `number` distance between this object and the center of `SHIP`. +---@field distance number +---pointing in the direction of this object from `SHIP`. +---@field direction Direction +---:type: `number` (deg) +--- +---The latitude in degrees of the spot on the surface of the SOI body directly under this object. +---@field latitude number +---:type: `number` (deg) +--- +---The longitude in degrees of the spot on the surface of the SOI body directly under this object. Longitude returned will always be normalized to be in the range [-180,180]. +---@field longitude number +---:type: `number` (m) +--- +---The altitude in meters above the *sea level* surface of the SOI body (not the center of the SOI body. To get the true radius of the orbit for proper math calculations remember to add altitude to the SOI body's radius.) +---@field altitude number +---A combined structure of the latitude and longitude numbers. +---@field geoposition GeoCoordinates +---:type: `List` of `Orbit` "patches" +--- +---The list of all the orbit patches that this object will transition to, not taking into account maneuver nodes. The zero-th patch of the list is the current orbit. +---@field patches List + +---@class VesselAltitude : Structure +---@field apoapsis number +---@field periapsis number +---@field radar number + +---@class AGXAddon : RTAddon + +---@class TRAddon : RTAddon +---**Only gives the correct answer for Trajectries version >= 2.2.0** +--- +---*For earlier versions, it gives a hardcoded fixed answer, as follows:* +--- +---- For any Trajectories version earlier than 2.0.0, +--- this returns the empty string "". +---- For any Trajectories version at least 2.0.0 but +--- below 2.2.0, this returns the 'rounded off' answer "2.0.0" +--- regardless of the precise version number within that range. +---- If your Trajectories version is at least 2.2.0 or above, +--- this returns the specific version string correctly. +--- +---For cases where you need to check for a known minimum Trajectories +---version, it is probably better to use the specific boolean suffix +---for that version (for example, :attr:`TRAddon:ISVERTWO`, or +---:attr:`TRAddon:ISVERTWOTWO` etc.) +---@field getversion string +---**Only gives the correct answer for Trajectries version >= 2.0.0** +--- +---*For earlier versions, it gives a hardcoded fixed answer, as follows:* +--- +---- For any Trajectories version earlier than 2.0.0, +--- this returns "0". +---- If your Trajectories version is at least 2.0.0 or above, +--- this returns the specific version major value correctly. +--- +---For cases where you need to check for a known minimum Trajectories +---version, it is probably better to use the specific boolean suffix +---for that version (for example, :attr:`TRAddon:ISVERTWO`, or +---:attr:`TRAddon:ISVERTWOTWO` etc.) +---@field getversionmajor number +---**Only gives the correct answer for Trajectries version >= 2.2.0** +--- +---*For earlier versions, it gives a hardcoded fixed answer, as follows:* +--- +---- For any Trajectories version below 2.2.0, this returns +--- "0" regardless of the precise version number within that range. +---- If your Trajectories version is at least 2.2.0 or above, +--- this returns the specific version minor value correctly. +--- +---For cases where you need to check for a known minimum Trajectories +---version, it is probably better to use the specific boolean suffix +---for that version (for example, :attr:`TRAddon:ISVERTWO`, or +---:attr:`TRAddon:ISVERTWOTWO` etc.) +---@field getversionminor number +---**Only gives the correct answer for Trajectries version >= 2.2.0** +--- +---*For earlier versions, it gives a hardcoded fixed answer, as follows:* +--- +---- For any Trajectories version below 2.2.0, this returns +--- "0" regardless of the precise version number within that range. +---- If your Trajectories version is at least 2.2.0 or above, +--- this returns the specific version patch value correctly. +--- +---For cases where you need to check for a known minimum Trajectories +---version, it is probably better to use the specific boolean suffix +---for that version (for example, :attr:`TRAddon:ISVERTWO`, or +---:attr:`TRAddon:ISVERTWOTWO` etc.) +---@field getversionpatch number +---True if the Trajectories mod is at least version 2.0.0 or above. +---@field isvertwo boolean +---True if the Trajectories mod is at least version 2.2.0 or above. +---@field isvertwotwo boolean +---True if the Trajectories mod is at least version 2.4.0 or above. +---@field isvertwofour boolean +---Estimated impact position. +---@field impactpos GeoCoordinates +---True if Trajectories has calculated an impact position for the current `Vessel`. You should always check this before using :attr:`impactPos`, :attr:`plannedVect`, :meth:`setTarget`, or :attr:`correctedVect` to avoid exceptions. +---@field hasimpact boolean +---A vector that applies an offset to :attr:`PLANNEDVEC` +---intended to correct the predicted trajectory to impact at the selected +---target position. This vector does not use any aerodynamic prediction and +---is a very simplistic representation. It is also just a unit vector. It +---contains no magnitude information about how far off the selected target is +---from the predicted impact - just the way the offset points. Accuracy is +---not guaranteed, but it should at least help determine if you need to +---pitch the nose up or down. +---@field correctedvec Vector +---A vector that applies an offset to :attr:`PLANNEDVEC` +---intended to correct the predicted trajectory to impact at the selected +---target position. This vector does not use any aerodynamic prediction and +---is a very simplistic representation. It is also just a unit vector. It +---contains no magnitude information about how far off the selected target is +---from the predicted impact - just the way the offset points. Accuracy is +---not guaranteed, but it should at least help determine if you need to +---pitch the nose up or down. +---@field correctedvector Vector +---Vector pointing the direction your vessel should face to follow the +---predicted trajectory, based on the angle of attack selected in the +---Trajectories descent profile. +---@field plannedvec Vector +---Vector pointing the direction your vessel should face to follow the +---predicted trajectory, based on the angle of attack selected in the +---Trajectories descent profile. +---@field plannedvector Vector +---:parameter position: `GeoCoordinates` +---:return: None +--- +---Sets the Trajectories target landing position to the given position. +---@field settarget fun(position: GeoCoordinates) +---**Did Not Exist in Trajectories before 2.0.0!** +--- +---*If :attr:`TRAddons:ISVERTWO` is false, using this suffix will cause +---a runtime error.* +--- +---The Trajectories Addon can be given a target position. +---This is true if such a position is set, or false if it is not. +---@field hastarget boolean +---**Did Not Exist in Trajectories before 2.2.0!** +--- +---*If :attr:`TRAddons:ISVERTWOTWO` is false, using this suffix will cause +---a runtime error.* +--- +---Gives you Trajectories prediction of how many seconds until impact +---on ground or water. +---@field timetillimpact number +---Settable. +---**Did Not Exist in Trajectories before 2.2.0!** +--- +---*If :attr:`TRAddons:ISVERTWOTWO` is false, using this suffix will cause +---a runtime error.* +--- +---For Trajectories 2.2.0 True if all the descent profile AoA values are 180. +---For Trajectories 2.4.0 True if all the descent profile nodes are 'retrograde' +--- +---You can set this to have the same effect as clicking on retrograde mode +---in the trajectories GUI. Setting this value to true causes +---:attr:`TRAddon:PROGRADE` to become false. (They cannot both be +---true at the same time.) +--- +---Setting this causes all Trajectories descent profile nodes +---to be set to 'retrograde' mode if True or 'prograde' mode if False. +---Also resets all AoA values to 0. +---@field retrograde boolean +---Settable. +---**Did Not Exist in Trajectories before 2.2.0!** +--- +---*If :attr:`TRAddons:ISVERTWOTWO` is false, using this suffix will cause +---a runtime error.* +--- +---For Trajectories 2.2.0 True if all the descent profile AoA values are 0. +---For Trajectories 2.4.0 True if all the descent profile nodes are 'prograde' +--- +---You can set this to have the same effect as clicking on prograde mode +---in the trajectories GUI. Setting this value to true causes +---:attr:`TRAddon:RETROGRADE` to become false. (They cannot both be +---true at the same time.) +--- +---Setting this causes all Trajectories descent profile nodes +---to be set to 'prograde' mode if True or 'retrograde' mode if False. +---Also resets all AoA values to 0. +---@field prograde boolean +---**Did Not Exist in Trajectories before 2.4.0!** +--- +---*If :attr:`TRAddons:ISVERTWOFOUR` is false, using this suffix will cause +---a runtime error.* +--- +---Returns the Trajectories target position if one is set. +---@field gettarget GeoCoordinates +---:parameter None +---:return: None +--- +---**Did Not Exist in Trajectories before 2.4.0!** +--- +---*If :attr:`TRAddons:ISVERTWOFOUR` is false, using this suffix will cause +---a runtime error.* +--- +---Clears the Trajectories target position. +---@field cleartarget fun() +---:parameter AoA: `number` +---:return: None +--- +---**Did Not Exist in Trajectories before 2.4.0!** +--- +---*If :attr:`TRAddons:ISVERTWOFOUR` is false, using this suffix will cause +---a runtime error.* +--- +---Resets all the Trajectories descent profile nodes to the passed AoA value (in Degrees), +---also sets Retrograde if AoA value is greater than 90 degrees (PI/2 radians) +---otherwise sets to Prograde. +---@field resetdescentprofile fun(AoA: number) +---Settable. +---:type: :struct:`List` +--- +---**Did Not Exist in Trajectories before 2.4.0!** +--- +---*If :attr:`TRAddons:ISVERTWOFOUR` is false, using this suffix will cause +---a runtime error.* +--- +---Returns or sets all the Trajectories descent profile AoA values (in Degrees), +---also sets a node to Retrograde if it's passed AoA is greater than 90 degrees +---(PI/2 radians) +---Note. also use with :attr:`TRAddons:DESCENTGRADES` to set a nodes grade +---if needed and passing AoA values as displayed in the gui with max 90 degrees +---(PI/2 radians). +--- +---List(atmospheric entry, high altitude, low altitude, final approach). +---@field descentangles List +---Settable. +---:type: :struct:`List` +--- +---**Did Not Exist in Trajectories before 2.4.0!** +--- +---*If :attr:`TRAddons:ISVERTWOFOUR` is false, using this suffix will cause +---a runtime error.* +--- +---Returns or sets all the Trajectories descent profile modes, +---True = AoA, False = Horizon. +--- +---List(atmospheric entry, high altitude, low altitude, final approach). +---@field descentmodes List +---Settable. +---:type: :struct:`List` +--- +---**Did Not Exist in Trajectories before 2.4.0!** +--- +---*If :attr:`TRAddons:ISVERTWOFOUR` is false, using this suffix will cause +---a runtime error.* +--- +---Returns or sets all the Trajectories descent profile grades, +---True = Retrograde, False = Prograde. +--- +---List(atmospheric entry, high altitude, low altitude, final approach). +---@field descentgrades List + +---@class IRAddon : RTAddon +---:type: `List` of `IRControlGroup` objects +--- +---Lists all Servo Groups for the Vessel on which the script is being executed. +---Example of use:: +--- +--- for g in ADDONS:IR:GROUPS +--- { +--- Print g:NAME + " contains " + g:SERVOS:LENGTH + " servos". +--- } +---@field groups List +---:type: `List` of `IRServo` objects +--- +---Lists all Servos for the Vessel on which the script is being executed. +---Example of use:: +--- +--- for s in ADDONS:IR:ALLSERVOS +--- { +--- print "Name: " + s:NAME + ", position: " + s:POSITION. +--- } +---@field allservos List +---:parameter part: `Part` for which to return servos +---:type: `List` of `IRServo` objects +--- +---Lists all Servos found on the given `Part`. +---@field partservos fun(part: Part): List + +---@class IRControlGroup : Structure +---Settable. +---:type: :ref:`string ` +--- +---Name of the Control Group (cannot be empty). +---@field name string +---Settable. +---:type: :ref:`scalar ` +--- +---Speed multiplier as set in the IR user interface. Avoid setting it to 0. +---@field speed number +---Settable. +---:type: :ref:`Boolean ` +--- +---True if Group is expanded in IR UI +---@field expanded boolean +---Settable. +---:type: :ref:`string ` +--- +---Key assigned to forward movement. Can be empty. +---@field forwardkey string +---Settable. +---:type: :ref:`string ` +--- +---Key assigned to reverse movement. Can be empty. +---@field reversekey string +---:type: List of `IRServo` objects +--- +---Lists Servos in the Group. Example of use:: +--- +--- for g in ADDONS:IR:GROUPS +--- { +--- Print g:NAME + " contains " + g:SERVOS:LENGTH + " servos:". +--- for s in g:servos +--- { +--- print " " + s:NAME + ", position: " + s:POSITION. +--- } +--- } +---@field servos List +---:return: void +--- +---Commands servos in the group to move in positive direction. +---@field moveright fun() +---:return: void +--- +---Commands servos in the group to move in negative direction. +---@field moveleft fun() +---:return: void +--- +---Commands servos in the group to move to default position. +---@field movecenter fun() +---:return: void +--- +---Commands servos in the group to move to next preset +---@field movenextpreset fun() +---:return: void +--- +---Commands servos in the group to move to previous preset +---@field moveprevpreset fun() +---:return: void +--- +---Commands servos in the group to stop +---@field stop fun() +---Returns a Vessel that owns this ServoGroup +---@field vessel Vessel + +---@class IRServo : Structure +---Settable. +---:type: :ref:`string ` +--- +---Name of the Control Group (cannot be empty). +---@field name string +---:type: :ref:`scalar ` +--- +---Unique ID of the servo part (part.flightID). +---@field uid number +---Settable. +---:type: :ref:`Boolean ` +--- +---Set Hightlight status of the part. +---@field highlight boolean +---:type: :ref:`scalar ` +--- +---Current position of the servo. +---@field position number +---:type: :ref:`scalar ` +--- +---Minimum position for servo as defined by part creator in part.cfg +---@field mincfgposition number +---:type: :ref:`scalar ` +--- +---Maximum position for servo as defined by part creator in part.cfg +---@field maxcfgposition number +---Settable. +---:type: :ref:`scalar ` +--- +---Minimum position for servo, from tweakable. +---@field minposition number +---Settable. +---:type: :ref:`scalar ` +--- +---Maximum position for servo, from tweakable. +---@field maxposition number +---:type: :ref:`scalar ` +--- +---Servo movement speed as defined by part creator in part.cfg +---@field configspeed number +---Settable. +---:type: :ref:`scalar ` +--- +---Current Servo speed. +---@field currentspeed number +---Settable. +---:type: :ref:`scalar ` +--- +---Servo speed multiplier, from tweakable. +---@field speed number +---Settable. +---:type: :ref:`scalar ` +--- +---Servo acceleration multiplier, from tweakable. +---@field acceleration number +---:type: :ref:`Boolean ` +--- +---True if Servo is moving +---@field ismoving boolean +---:type: :ref:`Boolean ` +--- +---True if Servo is uncontrollable (ex. docking washer) +---@field isfreemoving boolean +---Settable. +---:type: :ref:`Boolean ` +--- +---Servo's locked status, set true to lock servo. +---@field locked boolean +---Settable. +---:type: :ref:`Boolean ` +--- +---Servo's inverted status, set true to invert servo's axis. +---@field inverted boolean +---:return: void +--- +---Commands servo to move in positive direction +---@field moveright fun() +---:return: void +--- +---Commands servo to move in negative direction +---@field moveleft fun() +---:return: void +--- +---Commands servo to move to default position +---@field movecenter fun() +---:return: void +--- +---Commands servo to move to next preset +---@field movenextpreset fun() +---:return: void +--- +---Commands servo to move to previous preset +---@field moveprevpreset fun() +---:return: void +--- +---Commands servo to stop +---@field stop fun() +---:parameter position: (float) Position to move to +---:parameter speedMult: (float) Speed multiplier +---:return: void +--- +---Commands servo to move to `position` with `speedMult` multiplier. +---@field moveto fun(position: number, speedMult: number) +---Returns reference to the `Part` containing servo module. Please note that Part:UID does not equal IRServo:UID. +---@field part Part + +---@class KACAddon : RTAddon +---:return: List of `KACAlarm` objects +--- +---List **all** the alarms set up in Kerbal Alarm Clock. Example of use:: +--- +--- for i in ADDONS:KAC:ALARMS +--- { +--- print i:NAME + " - " + i:REMAINING + " - " + i:TYPE+ " - " + i:ACTION. +--- } +---@field alarms List + +---@class KACAlarm : Structure +---:type: :ref:`string ` +--- +---Unique identifier of the alarm. +---@field id string +---Settable. +---:type: :ref:`string ` +--- +---Name of the alarm. Displayed in main KAC window. +---@field name string +---Settable. +---:type: :ref:`string ` +--- +---Long description of the alarm. Can be seen when alarm pops or by double-clicking alarm in UI. +--- +---**Warning**: This field may be reserved in the future version of KAC-KOS integration for automated script execution upon triggering of the alarm. +---@field notes string +---Settable. +---:type: :ref:`string ` +--- +---Should be one of the following +--- +--- * `MessageOnly` - Message Only-No Affect on warp +--- * `KillWarpOnly` - Kill Warp Only-No Message +--- * `KillWarp` - Kill Warp and Message +--- * `PauseGame` - Pause Game and Message +--- +---If set incorrectly will log a warning in Debug log and revert to previous or default value. +---@field action string +---:type: :ref:`string ` +--- +---Can only be set at Alarm creation. +---Could be one of the following as per API +--- +--- * Raw (default) +--- * Maneuver +--- * ManeuverAuto +--- * Apoapsis +--- * Periapsis +--- * AscendingNode +--- * DescendingNode +--- * LaunchRendevous +--- * Closest +--- * SOIChange +--- * SOIChangeAuto +--- * Transfer +--- * TransferModelled +--- * Distance +--- * Crew +--- * EarthTime +--- +---**Warning**: Unless you are 100% certain you know what you're doing, create only "Raw" AlarmTypes to avoid unnecessary complications. +---@field type string +---:type: :ref:`scalar ` +--- +---Time remaining until alarm is triggered. +---@field remaining number +---Settable. +---@field time number +---Settable. +---@field margin number +---Settable. +---:type: :ref:`boolean ` +--- +---Should the alarm be repeated once it fires. +---@field repeat boolean +---Settable. +---:type: :ref:`scalar ` +--- +---How long after the alarm fires should the next alarm be set up. +---@field repeatperiod number +---Settable. +---:type: :ref:`string ` +--- +---Name of the body the vessel is departing from. +---@field originbody string +---Settable. +---:type: :ref:`string ` +--- +---Name of the body the vessel is arriving to. +---@field targetbody string + +---@class RTAddon : RTAddon +---:parameter vessel: `Vessel` +---:return: (`number`) seconds +--- +---Returns shortest possible delay for `vessel` (Will be less than KSC delay if you have a local command post). +---@field delay fun(vessel: Vessel): number +---:parameter vessel: `Vessel` +---:return: (`number`) seconds +--- +---Returns delay in seconds from KSC to `vessel`. +---@field kscdelay fun(vessel: Vessel): number +---:parameter vessel: `Vessel` +---:return: `boolean` +--- +---Returns True if `vessel` has any connection (including to local command posts). +---@field hasconnection fun(vessel: Vessel): boolean +---:parameter vessel: `Vessel` +---:return: `boolean` +--- +---Returns True if `vessel` has connection to KSC. +---@field haskscconnection fun(vessel: Vessel): boolean +---:parameter part: `Part` +---:return: `boolean` +--- +---Returns True if `part` has any connection (including to local command posts). +---@field antennahasconnection fun(part: Part): boolean +---:parameter vessel: `Vessel` +---:return: `boolean` +--- +---Returns True if `vessel` has local control (and thus not requiring a RemoteTech connection). +---@field haslocalcontrol fun(vessel: Vessel): boolean +---:return: `List` of `string` +--- +---Returns names of all RT ground stations +---@field groundstations List + +---@class RTAddonAntennaModule : PartModule +---@field allfields List +---@field allfieldnames List +---@field hasfield fun(param1: string): boolean +---@field getfield fun(param1: string): any +---@field setfield fun(param1: string, param2: any) + +---@class HIGHLIGHT : Structure +---Settable. +---@field color RGBA +---Settable. +---@field enabled boolean + +---@class SteeringManager : Structure +---Returns the PIDLoop object responsible for calculating the :ref:`target angular velocity ` in the pitch direction. This allows direct manipulation of the gain parameters, and other components of the `PIDLoop` structure. Changing the loop's `MAXOUTPUT` or `MINOUTPUT` values will have no effect as they are overwritten every physics frame. They are set to limit the maximum turning rate to that which can be stopped in a :attr:`MAXSTOPPINGTIME` seconds (calculated based on available torque, and the ship's moment of inertia). +---@field pitchpid PIDLoop +---Returns the PIDLoop object responsible for calculating the :ref:`target angular velocity ` in the yaw direction. This allows direct manipulation of the gain parameters, and other components of the `PIDLoop` structure. Changing the loop's `MAXOUTPUT` or `MINOUTPUT` values will have no effect as they are overwritten every physics frame. They are set to limit the maximum turning rate to that which can be stopped in a :attr:`MAXSTOPPINGTIME` seconds (calculated based on available torque, and the ship's moment of inertia). +---@field yawpid PIDLoop +---Returns the PIDLoop object responsible for calculating the :ref:`target angular velocity ` in the roll direction. This allows direct manipulation of the gain parameters, and other components of the `PIDLoop` structure. Changing the loop's `MAXOUTPUT` or `MINOUTPUT` values will have no effect as they are overwritten every physics frame. They are set to limit the maximum turning rate to that which can be stopped in a :attr:`MAXSTOPPINGTIME` seconds (calculated based on available torque, and the ship's moment of inertia). +--- +---.. note:: +--- +--- The SteeringManager will ignore the roll component of steering +--- until after both the pitch and yaw components are close to being +--- correct. In other words it will try to point the nose of the +--- craft in the right direction first, before it makes any attempt +--- to roll the craft into the right orientation. As long as the +--- pitch or yaw is still far off from the target aim, this PIDloop +--- won't be getting used at all. +---@field rollpid PIDLoop +---Returns true if the SteeringManager is currently controlling the vessel steering. +---@field enabled boolean +---Returns direction that the is currently being targeted. If steering is locked to a vector, this will return the calculated direction in which kOS chose an arbitrary roll to go with the vector. If steering is locked to "kill", this will return the vessel's last facing direction. +---@field target Direction +---:return: none +--- +---Resets the integral sum to zero for all six steering PID Loops. +---@field resetpids fun() +---:return: none +--- +---Resets the various tuning parameters of the `SteeringManager` to +---their default values as if the ship had just been loaded. This internally +---will also call :meth:`SteeringManager:RESETPIDS`. +---@field resettodefault fun() +---Settable. +---Setting this suffix to true will cause the steering manager to display graphical vectors (see `VecDraw`) representing the forward, top, and starboard of the facing direction, as well as the world x, y, and z axis orientation (centered on the vessel). Setting to false will hide the vectors, as will disabling locked steering. +---@field showfacingvectors boolean +---Settable. +---Setting this suffix to true will cause the steering manager to display graphical vectors (see `VecDraw`) representing the current and target angular velocities in the pitch, yaw, and roll directions. Setting to false will hide the vectors, as will disabling locked steering. +---@field showangularvectors boolean +---Settable. +---Setting this suffix to true will cause the steering manager to clear the terminal screen and print steering data each update. +---@field showsteeringstats boolean +---Settable. +---Setting this suffix to true will cause the steering manager log the data from all 6 PIDLoops calculating target angular velocity and target torque. The files are stored in the `[KSP Root]\GameData\kOS\Plugins\PluginData\kOS` folder, with one file per loop and a new file created for each new manager instance (i.e. every launch, every revert, and every vessel load). These files can grow quite large, and add up quickly, so it is recommended to only set this value to true for testing or debugging and not normal operation. +---@field writecsvfiles boolean +---Settable. +---Represents the settling time for the :ref:`PID calculating pitch torque based on target angular velocity `. The proportional and integral gain is calculated based on the settling time and the moment of inertia in the pitch direction. Ki = (moment of inertia) * (4 / (settling time)) ^ 2. Kp = 2 * sqrt((moment of inertia) * Ki). +---@field pitchts number +---Settable. +---Represents the settling time for the :ref:`PID calculating yaw torque based on target angular velocity `. The proportional and integral gain is calculated based on the settling time and the moment of inertia in the yaw direction. Ki = (moment of inertia) * (4 / (settling time)) ^ 2. Kp = 2 * sqrt((moment of inertia) * Ki). +---@field yawts number +---Settable. +---Represents the settling time for the :ref:`PID calculating roll torque based on target angular velocity `. The proportional and integral gain is calculated based on the settling time and the moment of inertia in the roll direction. Ki = (moment of inertia) * (4 / (settling time)) ^ 2. Kp = 2 * sqrt((moment of inertia) * Ki). +---@field rollts number +---Settable. +---DEFAULT VALUE: 0.0002 +--- +---Tweaking this value can help make the controls stop wiggling so fast. +--- +---You cannot set this value higher than +---:attr:`SteeringManager:TORQUEEPSILONMAX`. +---If you attempt to do so, then +---:attr:`SteeringManager:TORQUEEPSILONMAX` will be increased to match +---the value just set :attr:`SteeringManager:TORQUEEPSILONMIN` to. +--- +---To see how to use this value, look at the description of +---:attr:`SteeringManager:TORQUEEPSILONMAX` below, which +---has the full documentation about how these two values, Min and Max, +---work together. +---@field torqueepsilonmin number +---Settable. +---DEFAULT VALUE: 0.001 +--- +---Tweaking this value can help make the controls stop wiggling so fast. +---If you have problems wasting too much RCS propellant because kOS +---"cares too much" about getting the rotation rate exactly right and is +---wiggling the controls unnecessarily when rotating toward a new direction, +---setting thie value a bit higher can help. +--- +---You cannot set this value lower than +---:attr:`SteeringManager:TORQUEEPSILONMIN`. +---If you attempt to do so, then +---:attr:`SteeringManager:TORQUEEPSILONMIN` will be decreased to match +---the value just set :attr:`SteeringManager:TORQUEEPSILONMAX` to. +--- +---**HOW IT WORKS:** +--- +---If the error in the desired rotation rate is smaller than the current epsilon, +---then the PID that calculates desired torque will ignore that error and not +---bother correcting it until it gets bigger. The actual epsilon value used +---in the steering manager's internal PID controller is always something between +---:attr:`SteeringManager:TORQUEEPSILONMIN`. +---and +---:attr:`SteeringManager:TORQUEEPSILONMAX`. +---It varies between these two values depending on whether the +---vessel is currently rotating at near the maximum rotation rate +---the SteeringManager allows (as determined by +---:attr:`SteeringManager:MAXSTOPPINGTIME`) or whether it's quite far +---from its maximum rotation rate. +---:attr:`SteeringManager:TORQUEEPSILONMAX` is used when the vessel is +---at it's maximum rotation rate (i.e. it's coasting around to a new +---orientation and shouldn't pointlessly spend RCS fuel trying to hold +---that angular velocity precisely). +---:attr:`SteeringManager:TORQUEEPSILONMIN` is used when the vessel is +---not trying to rotate at all and is supposed to be using the steering +---just to hold the aim at a standstill. In between these two states, +---it uses a value partway between the two, linearly interpolated between +---them. +--- +---If you desire a constant epsilon, set both the min and max values to the +---same value. +---@field torqueepsilonmax number +---Settable. +---:type: `number` (s) +--- +---This value is used to limit the turning rate when :ref:`calculating target angular velocity `. The ship will not turn faster than what it can stop in this amount of time. The maximum angular velocity about each axis is calculated as: (max angular velocity) = MAXSTOPPINGTIME * (available torque) / (moment of inertia). +--- +---.. note:: +--- +--- This setting affects all three of the :ref:`rotational velocity PID's ` at once (pitch, yaw, and roll), rather than affecting the three axes individually one at a time. +---@field maxstoppingtime number +---:type: `number` (deg) +--- +---The angle between the ship's facing direction forward vector and the target direction's forward. This is the combined pitch and yaw error. +---@field angleerror number +---:type: `number` (deg) +--- +---The pitch angle between the ship's facing direction and the target direction. +---@field pitcherror number +---:type: `number` (deg) +--- +---The yaw angle between the ship's facing direction and the target direction. +---@field yawerror number +---:type: `number` (deg) +--- +---The roll angle between the ship's facing direction and the target direction. +---@field rollerror number +---Settable. +---:type: `number` (kNm) +--- +---You can set this value to provide an additive bias to the calculated available pitch torque used in the pitch :ref:`torque PID `. (available torque) = ((calculated torque) + PITCHTORQUEADJUST) * PITCHTORQUEFACTOR. +---@field pitchtorqueadjust number +---Settable. +---:type: `number` (kNm) +--- +---You can set this value to provide an additive bias to the calculated available yaw torque used in the yaw :ref:`torque PID `. (available torque) = ((calculated torque) + YAWTORQUEADJUST) * YAWTORQUEFACTOR. +---@field yawtorqueadjust number +---Settable. +---:type: `number` (kNm) +--- +---You can set this value to provide an additive bias to the calculated available roll torque used in the roll :ref:`torque PID `. (available torque) = ((calculated torque) + ROLLTORQUEADJUST) * ROLLTORQUEFACTOR. +---@field rolltorqueadjust number +---Settable. +---:type: `number` (kNm) +--- +---You can set this value to provide an multiplicative factor bias to the calculated available pitch torque used in the :ref:`torque PID `. (available torque) = ((calculated torque) + PITCHTORQUEADJUST) * PITCHTORQUEFACTOR. +---@field pitchtorquefactor number +---Settable. +---:type: `number` (kNm) +--- +---You can set this value to provide an multiplicative factor bias to the calculated available yaw torque used in the :ref:`torque PID `. (available torque) = ((calculated torque) + YAWTORQUEADJUST) * YAWTORQUEFACTOR. +---@field yawtorquefactor number +---Settable. +---:type: `number` (kNm) +--- +---You can set this value to provide an multiplicative factor bias to the calculated available roll torque used in the :ref:`torque PID `. (available torque) = ((calculated torque) + ROLLTORQUEADJUST) * ROLLTORQUEFACTOR. +---@field rolltorquefactor number +---@field averageduration number +---Settable. +---:type: `number` (deg) +--- +---The maximum value of :attr:`ANGLEERROR` for +---which kOS will attempt to respond to error along the roll axis. If this +---is set to 5 (the default value), the facing direction will need to be within +---5 degrees of the target direction before it actually attempts to roll the +---ship. Setting the value to 180 will effectivelly allow roll control at any +---error amount. When :attr:`ANGLEERROR` is +---greater than this value, kOS will only attempt to kill all roll angular +---velocity. The value is clamped between 180 and 1e-16. +---@field rollcontrolanglerange number + +---@class Archive : Volume + +---@class FileContent : Structure +---Length of the file. +---@field length number +---True if the file is empty +---@field empty boolean +---Type of the content as a string. Can be one of the following:\ +--- +---TOOSHORT +--- Content too short to establish a type +--- +---ASCII +--- A file containing ASCII text, like the result of a LOG command. +--- +---KSM +--- A type of file containing KerboMachineLanguage compiled code, that was created from the :ref:`COMPILE command `. +--- +---BINARY +--- Any other type of file. +---@field type string +---Contents of the file decoded using UTF-8 encoding +---@field string string +---Contents of the file as a list of bytes. Each item in the list is a number between 0 and 255 representing a single byte from the file. +---@field binary List +---Iterates over the lines of a file +---@field iterator Iterator + +---@class LocalVolume : Volume + +---@class VolumeDirectory : VolumeItem +---@field iterator Iterator +---An alias for :meth:`LEXICON`. It's slightly wrong that a method called "List" +---returns a Lexicon instead of a List, but it has been that way long enough that +---now for backward compatibility, the name "List" had to remain as an alias +---for this method. +---@field list Lexicon +---An alias for :meth:`LEXICON`. It's slightly wrong that a method called "List" +---returns a Lexicon instead of a List, but it has been that way long enough that +---now for backward compatibility, the name "List" had to remain as an alias +---for this method. +---@field lexicon Lexicon +---An alias for :meth:`LEXICON`. It's slightly wrong that a method called "List" +---returns a Lexicon instead of a List, but it has been that way long enough that +---now for backward compatibility, the name "List" had to remain as an alias +---for this method. +---@field lex Lexicon + +---@class Volume : Structure +---Free space left on the volume +---@field freespace number +---Total space on the volume +---@field capacity number +---Settable. +---Gets or sets volume name. This name can be used instead of the volumeId with some :ref:`file and volume-related commands` +---@field name string +---True if the name of this volume can be changed. Currently only the name of the archive can't be changed. +---@field renameable boolean +---Amount of power consumed when this volume is set as the current volume +---@field powerrequirement number +---Returns volume's root directory +---@field root VolumeDirectory +---:return: `boolean` +--- +---Returns true if the given file or directory exists. This will also return true when the given file does not exist, but there is a file with the same name and `.ks` or `.ksm` extension added. +---Use ``Volume:FILES:HASKEY(name)`` to perform a strict check. +--- +---Paths passed as the argument to this command should not contain a volume id or name and should not be relative. +---@field exists fun(path: string): boolean +---:type: `Lexicon` of `VolumeItem` +--- +---List of files and directories on this volume. Keys are the names of all items on this volume and values are the associated `VolumeItem` structures. +---@field files Lexicon +---:return: `VolumeFile` +--- +---Creates a file under the given path and returns `VolumeFile`. It will fail if the file already exists. +--- +---Paths passed as the argument to this command should not contain a volume id or name and should not be relative. +---@field create fun(path: string): VolumeFile +---:return: `VolumeDirectory` +--- +---Creates a directory under the given path and returns `VolumeDirectory`. It will fail if the directory already exists. +--- +---Paths passed as the argument to this command should not contain a volume id or name and should not be relative. +---@field createdir fun(path: string): VolumeDirectory +---:return: `VolumeItem` or `boolean` false +--- +---Opens the file or directory pointed to by the given path and returns `VolumeItem`. It will return a boolean false if the given file or directory does not exist. +--- +---Paths passed as the argument to this command should not contain a volume id or name and should not be relative. +---@field open fun(path: string): any +---:return: boolean +--- +---Deletes the given file or directory (recursively). It will return true if the given item was successfully deleted and false otherwise. +--- +---Paths passed as the argument to this command should not contain a volume id or name and should not be relative. +---@field delete fun(path: string): boolean + +---@class VolumeItem : Structure +---Name of the item, including the extension. +---@field name string +---Size of the item, in bytes. +---@field size number +---Item extension (part of the name after the last dot). +---@field extension string +---True if this item is a file +---@field isfile boolean + +---@class Path : Structure +---Volume this path belongs to. +---@field volume Volume +---:type: `List` of `string` +--- +---List of segments this path contains. Segments are parts of the path separated by `/`. For example path `0:/directory/subdirectory/script.ks` contains the following segments: +---`directory`, `subdirectory` and `script.ks`. +---@field segments List +---Number of this path's segments. +---@field length number +---Name of file or directory this path points to (same as the last segment). +---@field name string +---True if the last segment of this path has an extension. +---@field hasextension boolean +---Extension of the last segment of this path. +---@field extension string +---Returns a new path that points to the root directory of this path's volume. +---@field root Path +---Returns a new path that points to this path's parent. This method will throw an exception if this path does not have a parent (its length is 0). +---@field parent Path +---:parameter path: `Path` path to check +---:return: `boolean` +--- +---Returns true if `path` is the parent of this path. +---@field isparent fun(path: Path): boolean +---:parameter name: `string` new path name +---:return: `Path` +--- +---Will return a new path with the value of the last segment of this path replaced (or added if there's none). +---@field changename fun(name: string): Path +---:parameter extension: `string` new path extension +---:return: `Path` +--- +---Will return a new path with the extension of the last segment of this path replaced (or added if there's none). +---@field changeextension fun(extension: string): Path +---:parameter name: `string` segments to add +---:return: `Path` +--- +---Returns a new path that represents the file or directory +---that would be reached by starting from this path and then +---appending the path elements given in the list. +--- +---e.g:: +--- +--- set p to path("0:/home"). +--- set p2 to p:combine("d1", "d2", "file.ks"). +--- print p2 +--- 0:/home/d1/d2/file.ks +---@field combine fun(...: any): Path + +---@class VolumeFile : VolumeItem +---:return: `FileContent` +--- +---Reads the content of the file. +---@field readall FileContent +---:return: `boolean` +--- +---Writes the given string or a `FileContent` to the file. Returns true if successful (lack of space on the `Volume` can cause a failure). +---@field write fun(String: any): boolean +---:return: `boolean` +--- +---Writes the given string followed by a newline to the file. Returns true if successful. +---@field writeln fun(string: string): boolean +---:return: None +--- +---Clears this file +---@field clear fun() + +---@class Connection : Structure +---True if the connection is opened and messages can be sent. +--- +---- For CPU connections: +--- - This will be true if the destination CPU belongs to the same vessel +--- as the current CPU, and will be false otherwise. +---- For Vessel connections: +--- - If you are using Stock KSP and chose the PermitAll connectivity +--- manager, then this will aways return true. +--- - If you are using Stock KSP and chose the CommNet connectivity +--- manager, then this will obey the rules of the stock CommNet system +--- for whether a connection path exists between the source and +--- destination vessel. +--- - If you are using the RemoteTech and chose the RemoteTech +--- connectivity manager, then this will obey the rules of the +--- RemoteTech mod for whether a connection path exists between the +--- source and destination vessel. +--- +---The connection has to be opened only in the moment of sending the message in order for it to arrive. If connection is lost after the message was sent, +---but before it arrives at its destination, this will have no effect on whether the message will reach its destination or not. +--- +---.. note:: +--- **Debris Vessels**: If you are using the KSP Stock CommNet system, +--- be aware that it never includes "debris" type vessels in the +--- communications network. ``ISCONNECTED`` will always be false +--- for any vessel of type "debris", no matter what antennas it +--- may have on it. +--- +--- .. note:: +--- **ISCONNECTION fails just after scene load**: If you have just loaded +--- the scene, such as after a vessel switch, then both the Stock CommNet +--- system and the RemoteTech mod often have a slight delay before they +--- "find" all the communication paths that exist. This means that +--- ISCONNECTION will often return ``False`` for the first second or two +--- after a scene load, even when the correct answer should be ``True``. +--- It will be unable to report the correct answer until a second or so +--- later after the communications paths have all been discovered by the +--- game. Because of this, if you have a boot script that depends on an +--- accurate answer for ISCONNECTED, it's a good idea for that boot +--- script to start with a short wait of a second or two at the top of +--- the script. +---@field isconnected boolean +---The number of seconds that it will take for messages sent using this connection to arrive at their destination. This value will be equal to -1 if connection is not opened. +--- +---- For CPU connections: +--- - This will be always equal to 0 if the destination CPU belongs +--- to the same vessel as the current CPU. Otherwise it will be +--- equal to -1 as no such connection is allowed. +---- For vessel connections: +--- - If you are using the PermitAll Connectivity Manager, then this +--- will always be zero, as messages arrive instantly. +--- - If you are using the stock CommNet Connectivirty Manager, then this +--- will always be zero, as stock CommNet does not impose any delay +--- from radio signals. +--- - If you are using the RemoteTech Connectivity Manager, then this +--- will report RemoteTech's signal delay along the path being used +--- to form the connection. RemoteTech calculates the number of +--- seconds of delay due radio signals traveling at the speed of light, +--- which can be quite significant when dealing with interplanetary +--- distances. +---@field delay number +---:parameter message: `any` +---:return: (`boolean`) true if the message was successfully sent. +--- +---Send a message using this connection. Any serializable structure or a primitive (`string`, `number` or `boolean`) can be given as an argument. +---It is always worth checking the return value of this function. A returned false value would indicate that the message was not sent for some reason. +---This method will fail to send the message and return false if :attr:`Connection:ISCONNECTED` is false. +---@field sendmessage fun(message: any): boolean +---:type: `Vessel` or `kOSProcessor` +--- +---Destination of this connection. Will be either a vessel or a processor. +---@field destination any + +---@class BuiltinDelegate : Delegate + +---@class List : Collection +---Returns a new list that contains the same thing as the old list. +---@field copy List +---:parameter item: (any type) item to be added +--- +---Appends the new value given to the end of the list. +---@field add fun(item: any) +---:parameter index: (integer) position in list (starting from zero) +---:parameter item: (any type) item to be added +--- +---Inserts a new value at the position given, pushing all the other values in the list (if any) one spot to the right. +---@field insert fun(index: number, item: any) +---:parameter index: (integer) position in list (starting from zero) +--- +---Remove the item from the list at the numeric index given, with counting starting at the first item being item zero +---@field remove fun(index: number) +---:parameter index: (integer) starting index (from zero) +---:parameter length: (integer) resulting length of returned `List` +---:return: `List` +--- +---Returns a new list that contains a subset of this list starting at the given index number, and running for the given length of items. +---@field sublist fun(index: number, length: number): List +---:parameter separator: (string) separator that will be inserted between the list items +---:return: `string` +--- +---Returns a string created by converting each element of the array to a string, separated by the given separator. +---@field join fun(separator: string): string +---This is just an alias for :meth:`FIND(item)`. +---@field indexof fun(item: any): number +---This is just an alias for :meth:`FIND(item)`. +---@field find fun(item: any): number +---This is just an alias for :meth:`FINDLAST(item)`. +---@field lastindexof fun(item: any): number +---This is just an alias for :meth:`FINDLAST(item)`. +---@field findlast fun(item: any): number + +---@class Boolean : Structure + +---@class NoDelegate : UserDelegate + +---@class Structure +---When issuing the command ``PRINT aaa.``, the variable ``aaa`` gets +---converted to a string and then the string is shown on the screen. +---This suffix universally lets you get that string version of any item, +---rather than showing it on the screen. +---@field tostring string +---:parameter name: `string` name of the suffix being tested for +---Given the name of a suffix, returns true if the object has a suffix +---by that name. For example, if you have a variable that might be a +---:struct:`vessel `, or might be a :struct:`Body `, +---then this example:: +--- +--- print thingy:hassuffix("maxthrust"). +--- +---would print ``True`` if ``thingy`` was a vessel of some sort, but +---``False`` if ``thingy`` was a body, because there exists a maxthrust +---suffix for vessels but not for bodies. +--- +---When searching for suffix names, the search is performed in a +---case-insensitive way. Kerboscript cannot distinguish ":AAA" +---and ":aaa" as being two different suffixes. In kerboscript, +---they'd be the same suffix. +--- +---(Note that because a `Lexicon` can use a special +---:ref:`Lexicon suffix syntax `, it will also +---return true for suffix-usable keys when you call its +---HASSUFFIX method.) +---@field hassuffix fun(name: string): boolean +---:type: :struct:`List ` of :struct:`strings ` +--- +---Returns a list of all the string names of the suffixes that can +---be used by the thing you call it on. As of this release, no +---information is shown about the parameters the suffix expects, or +---about the return value it gives. All you see is the suffix names. +--- +---If this object's type is inherited from other types (for example, a +---:struct:`Body ` is also a kind of :struct:`Orbitable `.) +---then what you see here contains the list of all the suffixes from the base +---type as well. (Therefore the suffixes described here on this very page +---always appear in the list for any type.) +--- +---Note, for some objects, like Vessels, this can be a rather long list. +--- +---The list is returned sorted in alphabetical order. +--- +---Example:: +--- +--- set v1 to V(12,41,0.1). // v1 is a vector +--- print v1:suffixnames. +--- List of 14 items: +--- [0] = DIRECTION +--- [1] = HASSUFFIX +--- [2] = ISSERIALIZABLE +--- [3] = ISTYPE +--- [4] = MAG +--- [5] = NORMALIZED +--- [6] = SQRMAGNITUDE +--- [7] = SUFFIXNAMES +--- [8] = TOSTRING +--- [9] = TYPENAME +--- [10] = VEC +--- [11] = X +--- [12] = Y +--- [13] = Z +--- +---(Note that because a `Lexicon` can use a special +---:ref:`Lexicon suffix syntax `, it will also +---include all of its suffix-usable keys when you call its +---SUFFIXNAMES method.) +---@field suffixnames List +---Not all types can be saved using the built-in serialization function +---:ref:`WRITEJSON `. For those that can, values of that +---type will return ``True`` for this suffix, otherwise it returns ``False``. +---@field isserializable boolean +---Gives the name of the type of the object, in kOS terminology. +--- +---Type names correspond to the types mentioned throughout these +---documentation pages, at the tops of the tables that list +---suffixes. +--- +---Examples:: +--- +--- set x to 1. +--- print x:typename +--- Scalar +--- +--- set x to 1.1. +--- print x:typename +--- Scalar +--- +--- set x to ship:parts[2]. +--- print x:typename +--- Part +--- +--- set x to Mun. +--- print x:typename +--- Body +--- +---The kOS types described in these documentaion pages correspond +---one-to-one with underlying types in the C# code the implements +---them. However they don't have the same name as the underlying +---C# names. This returns an abstraction of the C# name. There +---are a few places in the C# code where an error message will +---mention the C# type name instead of the kOS type name. This is +---an issue that might be resolved in a later release. +---@field typename string +---:Parameter name: string name of the type being checked for +---This is ``True`` if the value is of the type mentioned in the name, or +---if it is a type that is derived from the type mentioned in the name. +---Otherwise it is ``False``. +--- +---Example:: +--- +--- set x to SHIP. +--- print x:istype("Vessel"). +--- True +--- print x:istype("Orbitable"). +--- True +--- print x:istype("Structure"). +--- True. +--- print x:istype("Body"). +--- False +--- print x:istype("Vector"). +--- False +--- print x:istype("Some bogus type name that doesn't exist"). +--- False +--- +---The type name is searched in a case-insensitive way. +---@field istype fun(name: string): boolean +---Gives a string describing the typename of this value, and the +---typename of the type this value is inherited from, and the typename +---of the type that type is inherited from, etc all the way to +---this root type of ``Structure`` that all values share. +--- +---Example:: +--- +--- set x to SHIP. +--- print x:inheritance. +--- Vessel derived from Orbitable derived from Structure +--- +---(The kOS types described in that string are an abstraction of the +---underlying C# names in the mod's implementation, and a few of the +---C# types the mod uses to abstract a few things are skipped along +---the way, as they are types the script code can't see directly.) +---@field inheritance string + +---@class Iterator : Structure +---:returns: `boolean` +--- +---Call this to move the iterator to the next item in the list. Returns true if there is such an item, or false if no such item exists because it's already at the end of the list. +---@field next boolean +---Returns true if the iterator is at the end of the list and therefore cannot be "NEXTed", false otherwise. +---@field atend boolean +---:type: `number` (integer) +--- +---Returns the numerical index of how far you are into the list, starting the counting at 0 for the first item in the list. The last item in the list is numbered N-1, where N is the number of items in the list. +--- +---.. note:: +--- +--- If you have just created the ITERATOR, then the value of :attr:`Iterator:INDEX` is -1. It only becomes 0 after the first call to :meth:`Iterator:NEXT`. +---@field index number +---Returns the thing stored at the current position in the list. +---@field value any + +---@class Version : Structure +---@field major number +---@field minor number +---@field patch number +---@field build number + +---@class ConfigKey : Structure + +---@class Lexicon : Structure +---Removes all of the pairs from the lexicon. Making it empty. +---@field clear fun() +---Returns a List of the keys stored in this lexicon. +---@field keys List +---:parameter key: (any type) +---:return: `boolean` +--- +---Returns true if the lexicon contains the provided key +---@field haskey fun(key: any): boolean +---:parameter key: (any type) +---:return: `boolean` +--- +---Returns true if the lexicon contains the provided value +---@field hasvalue fun(key: any): boolean +---Returns a List of the values stored in this lexicon. +---@field values List +---Returns a new lexicon that contains the same set of pairs as this lexicon. +---Note that this is a "shallow" copy, meaning that if there is a value in +---the list that refers to, for example, another Lexicon, or a Vessel, or +---a Part, the new copy will still be referring to the same object as the +---original copy in that value. +---@field copy Lexicon +---Returns the number of pairs in the lexicon. +---@field length number +---:parameter key: the keyvalue of the pair to be removed +--- +---Remove the pair with the given key from the lexicon. +---@field remove fun(key: any): boolean +---:parameter key: (any type) a unique key +---:parameter value: (any type) a value that is to be associated to the key +--- +---Adds an additional pair to the lexicon. +---@field add fun(key: any, value: any) +---Returns a string containing a verbose dump of the lexicon's contents. +--- +---The difference between a DUMP and just the normal printing of a +---Lexicon is in whether or not it recursively shows you the contents +---of every complex object inside the Lexicon. +--- +---i.e:: +--- +--- // Just gives a shallow list: +--- print mylexicon. +--- +--- // Walks the entire tree of contents, descending down into +--- // any Lists or Lexicons that are stored inside this Lexicon: +--- print mylexicon:dump. +---@field dump string +---Settable. +---The case sensitivity behaviour of the lexicon when the keys are strings. +---By default, all kerboscript lexicons use case-insensitive keys, at +---least for those keys that are string types, meaning that +---mylexicon["AAA"] means the same exact thing as mylexicon["aaa"]. If +---you do not want this behaviour, and instead want the key "AAA" to be +---different from the key "aaa", you can set this value to true. +--- +---Be aware, however, that if you change this, it has the side effect +---of *clearing out* the entire contents of the lexicon. This is done so +---as to avoid any potential clashes when the rules about what constitutes +---a duplicate key changed after the lexicon was already populated. +---Therefore you should probably only set this on a brand new lexicon, +---right after you've created it, and never change it after that. +---@field casesensitive boolean +---Settable. +---The case sensitivity behaviour of the lexicon when the keys are strings. +---By default, all kerboscript lexicons use case-insensitive keys, at +---least for those keys that are string types, meaning that +---mylexicon["AAA"] means the same exact thing as mylexicon["aaa"]. If +---you do not want this behaviour, and instead want the key "AAA" to be +---different from the key "aaa", you can set this value to true. +--- +---Be aware, however, that if you change this, it has the side effect +---of *clearing out* the entire contents of the lexicon. This is done so +---as to avoid any potential clashes when the rules about what constitutes +---a duplicate key changed after the lexicon was already populated. +---Therefore you should probably only set this on a brand new lexicon, +---right after you've created it, and never change it after that. +---@field case boolean + +---@class Terminal : Structure +---Settable. +---If you read the height it will return a number of character cells tall the terminal +---is. If you set this value, it will cause the terminal to resize. +---If there's multiple terminals connected to the same CPU part via telnet clients, +---then kOS will attempt to keep them all the same size, and one terminal being resized +---will resize them all. (caveat: Some terminal types cannot be resized from the +---server side, and therefore this doesn't always work in both directions). +--- +---This setting is different per kOS CPU part. Different terminal +---windows can have different settings for this value. +---@field height number +---Settable. +---If you read the width it will return a number of character cells wide the terminal +---is. If you set this value, it will cause the terminal to resize. +---If there's multiple terminals connected to the same CPU part via telnet clients, +---then kOS will attempt to keep them all the same size, and one terminal being resized +---will resize them all. (caveat: Some terminal types cannot be resized from the +---server side, and therefore this doesn't always work in both directions). +--- +---This setting is different per kOS CPU part. Different terminal +---windows can have different settings for this value. +---@field width number +---Settable. +---If true, then the terminal window is currently set to show +---the whole screen in reversed color - swapping the background +---and foreground colors. Both the telnet terminals and the in-game +---GUI terminal respond to this setting equally. +--- +---Note, this setting can also be toggled with a radio-button on the +---in-game GUI terminal window. +--- +---This setting is different per kOS CPU part. Different terminal +---windows can have different settings for this value. +---@field reverse boolean +---Settable. +---If true, then the terminal window is currently set to show any +---BEEP characters by silently flashing the screen for a moment +---(inverting the background/foreground for a fraction of a second), +---instead of making a sound. +--- +---Note, this setting can also be toggled with a radio-button on the +---in-game GUI terminal window. +--- +---This will only typically affect the in-game GUI terminal window, +---and **not a telnet client's** terminal window. +--- +---To affect the window you are using in a telnet session, you will +---have to use whatever your terminal or terminal emulator's local +---settings panel has for it. Most do have some sort of visual +---beep setting, but it is usually not settable via a control character +---sequence sent across the connection. The terminals are designed to +---assume it's a local user preference that isn't overridable +---by the software you are running. +--- +---This setting is different per kOS CPU part. Different terminal +---windows can have different settings for this value. +---@field visualbeep boolean +---Settable. +---The same thing as the brightness slider on the terminal GUI. +---The values range from 0.0 (minimum) to 1.0 (maximum). At +---zero, the effect is to entirely hide the letters altogether. +---@field brightness number +---Settable. +---Width of a character cell in the display terminal, in pixels. +--- +---Please note that this value is not settable anymore. It +---can only be changed as a side-effect of changing the +---:attr:`CHARHEIGHT`. This is because the font is in +---charge of choosing the ratio between a letter's height and +---its width. You can't force the font to render a letter +---at a different aspect ratio than it wants to. +---@field charwidth number +---Settable. +---Height of a character cell in the display terminal, in pixels. +---The value is forced to remain in the range [4..24] and be +---divisible by 2. If you try to set it to any other value, it +---will snap to the allowed range and increment. +---@field charheight number +---@field resizewatchers UniqueSet +---This gives you a `TerminalInput` structure, which can be +---used to read user's input into the kOS terminal. +---@field input TerminalInput + +---@class Delegate : Structure +---@field call fun(...: any): any +---@field bind fun(...: any): Delegate +---@field isdead boolean + +---@class Constant : Structure +---Newton's Gravitational Constant that the game's planetary +---bodies are implying in their configuration data. +---(6.67384E-11 as of the last update to these documents). +--- +---Note, the stock KSP game never technically records a value +---for G in its data. kOS derives this value by calculating it +---based on the Sun's Mass and its Gravitational Parameter. It +---is possible for a mod (or perhaps a future release of KSP, if +---mistakes were made) to define a universe in which Newton's +---Gravitational Constant, G, isn't actually constant at all +---within that game universe, and instead varies from one sphere +---of influence to the next. Such a universe would be breaking +---some laws of physics by a lot, but it is technically possible +---in the game's data model. Due to this strange feature in +---the game's data model, it is probably safer to always have +---your scripts use the body's Mu in your formulas instead of +---explicitly doing mass*G to derive it. +--- +---Do NOT confuse this with ``Constant:g0`` below. +--- +---Example:: +--- +--- PRINT "Gravitational parameter of Kerbin, calculated:". +--- PRINT constant:G * Kerbin:Mass. +--- PRINT "Gravitational parameter of Kerbin, hardcoded:". +--- PRINT Kerbin:Mu. +--- PRINT "The above two numbers had *better* agree.". +--- PRINT "If they do not, then your solar system is badly configured.". +---@field g number +---Standard value the game uses for acceleration due to +---gravity at sea level on Earth. (9.80655 m/s^2 as +---of the last update to these documents). +--- +---Do NOT confuse this with ``Constant:G`` above. +--- +---The place where this matters the most is in ISP +---calculations. The rocket equation using ISP +---contains an inherent conversion from mass to weight +---that basically means, "what would this mass of fuel +---have weighed at g0?". Some kind of official standard +---value of g0 is needed to use ISP properly to predict +---how much fuel will be burned in a scenario. +--- +---In pretty much any other calculation you do in your kOS +---scripts, other than when using ISP in the Rocketry Equation, +---you should probably not use g0 and instead calculate your +---local gravity more precisely based on your actual radius to +---the body center. Not only because this is more accurate, but +---because the g0 you see here is NOT the g0 you would actually +---have on Kerbin's sea level. It's the g0 on Earth, which is +---what the game's ISP numbers are using. Kerbin's sea level +---g0 is ever so slightly different from Earth's g0 (but not +---by much.) +--- +---:: +--- +--- PRINT "Gravitational parameter of Kerbin is:". +--- PRINT constant:G * Kerbin:Mass. +---@field g0 number +---Natural Log base "e":: +--- +--- PRINT "e^2 is:". +--- PRINT constant:e ^ 2. +---@field e number +---Ratio of circumference of a circle to its diameter, 3.14159265... +--- +---:: +--- +--- SET diameter to 10. +--- PRINT "circumference is:". +--- PRINT constant:pi * diameter. +---@field pi number +---Speed of light in a vacuum, in meters per second. +--- +---:: +--- +--- SET speed to SHIP:VELOCITY:ORBIT:MAG. +--- SET percentOfLight to (speed / constant:c) * 100. +--- PRINT "We're going " + percentOfLight + "% of lightspeed!". +--- +---.. note:: +--- In Kerbal Space Program, all physics motion is purely Newtonian. +--- You can go faster than the speed of light provided you have enough +--- delta-V, and no time dilation effects will occur. The universe +--- will behave entirely linearly even at speeds near *c*. +--- +---This constant is provided mainly for the benefit of people who are +---playing with the mod "RemoteTech" installed, who may want to perform +---calculations about signal delays to hypothetical probes. (Note that +---if the probe already has a connection, you can +---:ref:`ask Remotetech directly ` what the signal delay is. +---@field c number +---A conversion constant. +--- +---If you have a pressure measurement expressed in atmospheres of pressure, +---you can multiply it by this to get the equivalent in kiloPascals +---(kiloNewtons per square meter). +--- +---:: +--- +--- PRINT "1 atm is:". +--- PRINT 1 * constant:AtmToKPa + " kPa.". +---@field atmtokpa number +---A conversion constant. +--- +---If you have a pressure measurement expressed in kiloPascals (kiloNewtons +---per square meter), you can multiply it by this to get the equivalent +---in atmospheres. +--- +---:: +--- +--- PRINT "100 kPa is:". +--- PRINT 100 * constant:KPaToATM + " atmospheres". +---@field kpatoatm number +---A conversion constant. +--- +---If you have an angle measured in degrees, you can multiply it by +---this to get the equivalent measure in radians. It is exactly +---the same thing as saying ``constant:pi / 180``, except the result is +---pre-recorded as a constant number and thus no division is performed +---at runtime. +--- +---:: +--- +--- PRINT "A right angle is:". +--- PRINT 90 * constant:DegToRad + " radians". +---@field degtorad number +---A conversion constant. +--- +---If you have an angle measured in radians, you can multiply it by +---this to get the equivalent measure in degrees. It is exactly +---the same thing as saying ``180 / constant:pi``, except the result is +---pre-recorded as a constant number and thus no division is performed +---at runtime. +--- +---:: +--- +--- PRINT "A radian is:". +--- PRINT 1 * constant:RadToDeg + " degrees". +---@field radtodeg number +---Avogadro's Constant. +--- +---This value can be used in calculating atmospheric properties for drag purposes, +---which can be a rather advanced topic. +---`(Avogadro's constant Wikipedia Page) `_. +---@field avogadro number +---Boltzmann Constant. +--- +---This value can be used in calculating atmospheric properties for drag purposes, +---which can be a rather advanced topic. +---`(Boltzmann constant Wikipedia Page) `_. +---@field boltzmann number +---Ideal Gas Constant. +--- +---This value can be used in calculating atmospheric properties for drag purposes, +---which can be a rather advanced topic. +---`(Ideal Gas Constant Wikipedia Page) `_. +---@field idealgas number + +---@class TerminalInput : Structure +---:return: `string` +--- +---Read the next character of terminal input. If the user hasn't typed +---anything in that is still waiting to be read, then this will "block" +---(meaning it will pause the execution of the program) until there +---is a character that has been typed that can be processed. +--- +---The character will be expressed in a string containing 1 char. +--- +---If you need to check against "unprintable" characters such as +---backspace (control-H) and so on, you can do so with the +---:func:`unchar` function, or by using the aliases described elsewhere +---in this structure. +---@field getchar string +---True if there is at least 1 character of input waiting. If this is +---false then that would mean that an attempt to call :meth:`GETCHAR` +---would block and wait for user input. If this is true then an attempt +---to call :meth:`GETCHAR` would return immediately with an answer. +--- +---You can simulate non-blocking I/O like so:: +--- +--- // Read a char if it exists, else just keep going: +--- if terminal:input:haschar { +--- process_one_char(terminal:input:getchar()). +--- } +---@field haschar boolean +---:return: None +--- +---Call this method to throw away all waiting input characters, flushing +---the input queue. +---@field clear fun() +---@field code string +---@field tostring string +---A string for testing if the character read is a backspace. +---@field backspace string +---A string for testing if the character read is the return key. +---@field return string +---A string for testing if the character read is the return key. +---@field enter string + +---@class Queue : Enumerable +---Returns a new queue that contains the same thing as the old one. +---@field copy Queue +---:parameter item: (any type) item to be added +--- +---Adds the item to the end of the queue. +---@field push fun(item: any) +---Returns the item in the front of the queue and removes it. +---@field pop fun(): any +---Returns the item in the front of the queue without removing it. +---@field peek fun(): any +---Removes all elements from the queue. +---@field clear fun() + +---@class Enumerable : Structure +---An alternate means of iterating over an enumerable. See: `Iterator`. +---@field iterator Iterator +---An alternate means of iterating over an enumerable. Order of items is reversed. See: `Iterator`. +---@field reverseiterator Iterator +---Returns the number of elements in the enumerable. +---@field length number +---:parameter item: element whose presence in the enumerable should be checked +---:return: `boolean` +--- +---Returns true if the enumerable contains an item equal to the one passed as an argument +---@field contains fun(item: any): boolean +---Returns true if the enumerable has zero items in it. +---@field empty boolean +---Returns a string containing a verbose dump of the enumerable's contents. +---@field dump string + +---@class UniqueSet : Collection +---Returns a new set that contains the same thing as the old set. +---@field copy UniqueSet +---:parameter item: (any type) item to be added +--- +---Appends the new value given. +---@field add fun(item: any) +---:parameter item: (any type) item to be removed +--- +---Remove the item from the set. +---@field remove fun(item: any): boolean + +---@class UserDelegate : Delegate + +---@class Scalar : Structure + +---@class Range : Enumerable +---Returns the initial element of the range. Must be a round number. +---@field start number +---Returns the range limit. Must be a round number. +---@field stop number +---Returns the step size. Must be a round number. +---@field step number + +---@class PIDLoop : Structure +---The value representing the time of the last sample. This value is equal to the time parameter of the :meth:`UPDATE` method. +---@field lastsampletime number +---Settable. +---The proportional gain factor. +---@field kp number +---Settable. +---The integral gain factor. +---@field ki number +---Settable. +---The derivative gain factor. +---@field kd number +---The value representing the input of the last sample. This value is equal to the input parameter of the :meth:`UPDATE` method. +---@field input number +---Settable. +---The current setpoint. This is the value to which input is compared when :meth:`UPDATE` is called. It may not be synced with the last sample. +--- +---It is desirable to use :attr:`SETPOINT` for the +---:ref:`reasons described above `. +---@field setpoint number +---The calculated error from the last sample (setpoint - input). +---@field error number +---The calculated output from the last sample. +---@field output number +---Settable. +---The current maximum output value. This value also helps with regulating integral wind up mitigation. +---@field maxoutput number +---Settable. +---The current minimum output value. This value also helps with regulating integral wind up mitigation. +---@field minoutput number +---Settable. +---This is just an alias that is the same thing as :attr:`EPSILON`. +---@field ignoreerror number +---Settable. +---This is just an alias that is the same thing as :attr:`EPSILON`. +---@field epsilon number +---The value representing the time weighted sum of all errrors. It will be equal to :attr:`ITERM` / :attr:`KI`. This value is adjusted by the integral windup mitigation logic. +---@field errorsum number +---The value representing the proportional component of :attr:`OUTPUT`. +---@field pterm number +---The value representing the integral component of :attr:`OUTPUT`. This value is adjusted by the integral windup mitigation logic. +---@field iterm number +---The value representing the derivative component of :attr:`OUTPUT`. +---@field dterm number +---The rate of change of the :attr:`INPUT` during the last sample. It will be equal to (input - last input) / (change in time). +---@field changerate number +---:return: none +--- +---Call this method to clear the :attr:`ERRORSUM`, :attr:`ITERM`, and :attr:`LASTSAMPLETIME` components of the PID calculation. +---@field reset fun() +---:parameter time: (`number`) the decimal time in seconds +---:parameter input: (`number`) the input variable to compare to the setpoint +---:return: `number` representing the calculated output +--- +---Upon calling this method, the PIDLoop will calculate the output based on this this basic framework (see below for detailed derivation): output = error * kp + errorsum * ki + (change in input) / (change in time) * kd. This method is usually called with the current time in seconds (`TIME:SECONDS`), however it may be called using whatever rate measurement you prefer. Windup mitigation is included, based on :attr:`MAXOUTPUT` and :attr:`MINOUTPUT`. Both integral components and derivative components are guarded against a change in time greater than 1s, and will not be calculated on the first iteration. +---@field update fun(time: number, input: number): number + +---@class Stack : Enumerable +---Returns a new stack that contains the same thing as the old one. +---@field copy Stack +---:parameter item: (any type) item to be added +--- +---Adds the item to the top of the stack. +---@field push fun(item: any) +---Returns the item on top of the stack and removes it. +---@field pop fun(): any +---Returns the item on top of the stack without removing it. +---@field peek fun(): any +---Removes all elements from the stack. +---@field clear fun() + +---@class String : Structure +---:type: `number` (integer) +--- +---Number of characters in the string +---@field length number +---:parameter start: `number` (integer) starting index (from zero) +---:parameter count: `number` (integer) resulting length of returned `string` +---:return: `string` +--- +---Returns a new string with the given count of characters from this string starting from the given start position. +---@field substring fun(start: number, count: number): string +---:parameter string: `string` to look for +---True if the given string is contained within this string. +---@field contains fun(string: string): boolean +---:parameter string: `string` to look for +---True if this string ends with the given string. +---@field endswith fun(string: string): boolean +---:parameter string: `string` to look for +---:parameter startAt: `number` (integer) index to start searching at +---Returns the index of the first occurrence of the given string in this string (starting from startAt). +--- +---If the ``string`` passed in is not found, this returns -1. +--- +---If the ``string`` passed in is the empty string ``""``, this always claims to have +---successfully "found" that empty string at the start of the search. +---@field findat fun(string: string, startAt: number): number +---:parameter index: `number` (integer) index to add the string at +---:parameter string: `string` to insert +---Returns a new string with the given string inserted at the given index into this string +---@field insert fun(index: number, string: string): string +---:parameter string: `string` to look for +---:parameter startAt: `number` (integer) index to start searching at +---Returns the index of the last occurrence of the given string in this string (starting from startAt) +--- +---If the ``string`` passed in is not found, this returns -1. +--- +---If the ``string`` passed in is the empty string ``""``, this always claims to have +---successfully "found" that empty string at the beginning of the search. +---@field findlastat fun(string: string, startAt: number): number +---:parameter width: `number` (integer) number of characters the resulting string will contain +---Returns a new right-aligned version of this string padded to the given width by spaces. +---@field padleft fun(width: number): string +---:parameter width: `number` (integer) number of characters the resulting string will contain +---Returns a new left-aligned version of this string padded to the given width by spaces. +---@field padright fun(width: number): string +---:parameter index: `number` (integer) position of the string from which characters will be removed from the resulting string +---:parameter count: `number` (integer) number of characters that will be removing from the resulting string +---Returns a new string out of this string with the given count of characters removed starting at the given index. +---@field remove fun(index: number, count: number): string +---:parameter oldString: `string` to search for +---:parameter newString: `string` that all occurances of oldString will be replaced with +---Returns a new string out of this string with any occurrences of oldString replaced with newString. +---@field replace fun(oldString: string, newString: string): string +---:parameter separator: `string` delimiter on which this string will be split +---:return: `List` +--- +---Breaks this string up into a list of smaller strings on each occurrence of the given separator. This will return a +---list of strings, none of which will contain the separator character(s). +---@field split fun(separator: string): List +---:parameter string: `string` to look for +---True if this string starts with the given string . +---@field startswith fun(string: string): boolean +---Returns a new string with all characters in this string replaced with their lower case versions +---@field tolower string +---Returns a new string with all characters in this string replaced with their upper case versions +---@field toupper string +---returns a new string with no leading or trailing whitespace +---@field trim string +---returns a new string with no trailing whitespace +---@field trimend string +---returns a new string with no leading whitespace +---@field trimstart string +---:parameter pattern: `string` pattern to be matched against the string +---True if the string matches the given pattern (regular expression). The match is not anchored to neither the start nor the end of the string. +---That means that pattern ``"foo"`` will match ``"foobar"``, ``"barfoo"`` and ``"barfoobar"`` too. If you want to match from the start, +---you have to explicitly specify the start of the string in the pattern, i.e. for example to match strings starting with ``"foo"`` you need to +---use the pattern ``"^foo"`` (or equivalently ``"^foo.*"`` or even ``"^foo.*$"``). +--- +---Regular expressions are beyond the scope of this documentation. For reference see `Regular Expression Language - Quick Reference `__\ . +---@field matchespattern fun(pattern: string): boolean +---:parameter defaultIfError: (optional argument) `number` to return as a default value if the string format is in error. +---:return: `number` +--- +---Returns the numeric version of the string, as a number that can be used +---for mathematics or anywhere a `number` is expected. If the +---string is not in a format that kOS is able to convert into a number, then +---the value ``defaultIfError`` is returned instead. You can use this to +---either select a sane default, or to deliberately select a value you +---never expect to get in normal circumstances so you can use it as a +---test to see if the string was formatted well. +--- +---The argument ``defaultIfError`` is optional. If it is left off, then +---when there is a problem in the format of the string, you will get +---an error that stops the script instead of returning a value. +--- +---The valid understood format allows an optional leading sign, +---a decimal point with fractional part, and scientific notation +---using "e" as in "1.23e3" for "1230" or "1.23e-3" for "0.00123". +--- +---You may also include optional underscores in the string to +---help space groups of digits, and they will be ignored. +---(For example you may write "one thousand" as "1_000" instead +---of as "1000" if you like".) +--- +---Example - using with math:: +--- +--- set str to "16.8". +--- print "half of " + str + " is " + str:tonumber() / 2. +--- half of 16.8 is 8.4 +--- +---Example - checking for bad values by using defaultIfError:: +--- +--- set str to "Garbage 123 that is not a proper number". +--- set val to str:tonumber(-9999). +--- if val = -9999 { +--- print "that string isn't a number". +--- } else { +--- print "the string is a number: " + val. +--- } +--- +---Example - not setting a default value can throw an error:: +--- +--- set str to "Garbage". +--- set val to str:tonumber(). // the script dies with error here. +--- print "value is " + val. // the script never gets this far. +---@field tonumber fun(...: any): number +---:parameter defaultIfError: (optional argument) `number` to return as a default value if the string format is in error. +---:return: `number` +--- +---Returns the numeric version of the string, as a number that can be used +---for mathematics or anywhere a `number` is expected. If the +---string is not in a format that kOS is able to convert into a number, then +---the value ``defaultIfError`` is returned instead. You can use this to +---either select a sane default, or to deliberately select a value you +---never expect to get in normal circumstances so you can use it as a +---test to see if the string was formatted well. +--- +---The argument ``defaultIfError`` is optional. If it is left off, then +---when there is a problem in the format of the string, you will get +---an error that stops the script instead of returning a value. +--- +---The valid understood format allows an optional leading sign, +---a decimal point with fractional part, and scientific notation +---using "e" as in "1.23e3" for "1230" or "1.23e-3" for "0.00123". +--- +---You may also include optional underscores in the string to +---help space groups of digits, and they will be ignored. +---(For example you may write "one thousand" as "1_000" instead +---of as "1000" if you like".) +--- +---Example - using with math:: +--- +--- set str to "16.8". +--- print "half of " + str + " is " + str:tonumber() / 2. +--- half of 16.8 is 8.4 +--- +---Example - checking for bad values by using defaultIfError:: +--- +--- set str to "Garbage 123 that is not a proper number". +--- set val to str:tonumber(-9999). +--- if val = -9999 { +--- print "that string isn't a number". +--- } else { +--- print "the string is a number: " + val. +--- } +--- +---Example - not setting a default value can throw an error:: +--- +--- set str to "Garbage". +--- set val to str:tonumber(). // the script dies with error here. +--- print "value is " + val. // the script never gets this far. +---@field toscalar fun(...: any): number +---@field format fun(...: any): string +---Alias for FIND(string) +---@field indexof fun(string: string): number +---Alias for FIND(string) +---@field find fun(string: string): number +---Alias for FINDLAST(string) +---@field lastindexof fun(string: string): number +---Alias for FINDLAST(string) +---@field findlast fun(string: string): number +---An alternate means of iterating over a string's characters +---(See: `Iterator`). +--- +---For most programs you won't have to use this directly. It's just +---what enables you to use a string with a FOR loop to get access +---to its characters one at a time. +---@field iterator Iterator + +---@class Collection : Enumerable +---@field clear fun() + diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/library/variables.lua b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/variables.lua new file mode 100644 index 0000000000..3a35a8e600 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/library/variables.lua @@ -0,0 +1,1010 @@ +---Get/Set. +---:type: Action Group, `boolean` +--- +---Turns the SAS **on** or **off**, like using ``T`` at the keybaord:: +--- +--- SAS ON. // same as SET SAS TO TRUE. +--- SAS OFF. // same as SET SAS TO FALSE. +--- PRINT SAS. // prints either "True" or "False". +--- +---.. warning:: +--- +--- Be aware that having KSP's ``SAS`` turned on *will* conflict +--- with using "cooked control" (the ``lock steering`` command). You +--- should not use these two modes of steering control at the same time. +--- For further information see the +--- :ref:`warning in lock steering documentation`. +---@type boolean +sas = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Deploys or retracts the landing gear, like using the ``G`` key at the keyboard:: +--- +--- GEAR ON. +---@type boolean +gear = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Deploys or retracts all the landing legs (but not wheeled landing gear):: +--- +--- LEGS ON. +--- +---Returns true if all the legs are deployed. +---@type boolean +legs = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Deploys all the parachutes (only `ON` command has effect):: +--- +--- CHUTES ON. +--- +---Returns true if all the chutes are deployed. +---@type boolean +chutes = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Deploys all the parachutes than can be safely deployed in the current conditions (only `ON` command has effect):: +--- +--- CHUTESSAFE ON. +--- +---Returns false only if there are disarmed parachutes chutes which may be safely +---deployed, and true if all safe parachutes are already deployed including +---any time where there are no safe parachutes. +--- +---The following code will gradually deploy all the chutes as the speed drops:: +--- +--- WHEN (NOT CHUTESSAFE) THEN { +--- CHUTESSAFE ON. +--- RETURN (NOT CHUTES). +--- } +---@type boolean +chutessafe = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Turns the lights **on** or **off**, like using the ``U`` key at the keyboard:: +--- +--- LIGHTS ON. +---@type boolean +lights = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Extends or retracts all the deployable solar panels:: +--- +--- PANELS ON. +--- +---Returns true if all the panels are extended, including those inside of +---fairings or cargo bays. +--- +---.. note:: +--- Some solar panels can't be retracted once deployed. Consult the part's +--- description for details. +---@type boolean +panels = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Extends or retracts all the deployable radiators and activates or deactivates all the fixed ones:: +--- +--- RADIATORS ON. +--- +---Returns true if all the radiators are extended (if deployable) and active. +---@type boolean +radiators = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Extends or retracts all the extendable ladders:: +--- +--- LADDERS ON. +--- +---Returns true if all the ladders are extended. +---@type boolean +ladders = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Opens or closes all the payload and service bays (including the cargo ramp):: +--- +--- BAYS ON. +--- +---Returns true if at least one bay is open. +---@type boolean +bays = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Deploys or retracts all the mining drills:: +--- +--- DEPLOYDRILLS ON. +--- +---Returns true if all the drills are deployed. +---@type boolean +deploydrills = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Activates (has effect only on drills that are deployed and in contact with minable surface) or stops all the mining drills:: +--- +--- DRILLS ON. +--- +---Returns true if at least one drill is actually mining. +---@type boolean +drills = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Activates or deactivates all the fuel cells (distingushed from other conveters by converter/action names):: +--- +--- FUELCELLS ON. +--- +---Returns true if at least one fuel cell is activated. +---@type boolean +fuelcells = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Activates or deactivates all the ISRU converters (distingushed from other conveters by converter/action names):: +--- +--- ISRU ON. +--- +---Returns true if at least one ISRU converter is activated. +---@type boolean +isru = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Opens or closes all the air intakes:: +--- +--- INTAKES ON. +--- +---Returns true if all the intakes are open. +---@type boolean +intakes = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Turns the brakes **on** or **off**, like clicking the brakes button, though *not* like using the ``B`` key, because they stay on:: +--- +--- BRAKES ON. +---@type boolean +brakes = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Turns the RCS **on** or **off**, like using ``R`` at the keyboard:: +--- +--- RCS ON. // same as SET RCS TO TRUE. +--- RCS OFF. // same as SET RCS TO FALSE. +--- PRINT RCS. // prints either "True" or "False". +---@type boolean +rcs = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---Abort action group (no actions are automatically assigned, configurable in the editor), like using the ``Backspace`` key at the keyboard:: +--- +--- ABORT ON. +---@type boolean +abort = nil + +---Get/Set. +---:type: Action Group, `boolean` +--- +---10 custom action groups (no actions are automatically assigned, configurable in the editor), like using the numeric keys at the keyboard:: +--- +--- AG1 ON. +--- AG4 OFF. +--- SET AG10 to AG3. +---@type boolean +ag1 = nil + +---Get/Set. +---@type boolean +ag2 = nil + +---Get/Set. +---@type boolean +ag3 = nil + +---Get/Set. +---@type boolean +ag4 = nil + +---Get/Set. +---@type boolean +ag5 = nil + +---Get/Set. +---@type boolean +ag6 = nil + +---Get/Set. +---@type boolean +ag7 = nil + +---Get/Set. +---@type boolean +ag8 = nil + +---Get/Set. +---@type boolean +ag9 = nil + +---Get/Set. +---@type boolean +ag10 = nil + +---Get only. +---@type RGBA +white = nil + +---Get only. +---@type RGBA +black = nil + +---Get only. +---@type RGBA +red = nil + +---Get only. +---@type RGBA +green = nil + +---Get only. +---@type RGBA +blue = nil + +---Get only. +---@type RGBA +yellow = nil + +---Get only. +---@type RGBA +cyan = nil + +---Get only. +---@type RGBA +magenta = nil + +---Get only. +---@type RGBA +purple = nil + +---Get only. +---@type RGBA +grey = nil + +---Get only. +---@type RGBA +gray = nil + +---Get only. +---Returns amount of time, in seconds, from vessel load +---@type number +sessiontime = nil + +---Get only. +---@type Terminal +terminal = nil + +---Get only. +---@type Kuniverse +kuniverse = nil + +---Get only. +---Returns a `Connection` representing the :ref:`CPU Vessel's` +---communication line to a network "home" node. This home node may be the KSC +---ground station, or other ground stations added by the CommNet settings or +---RemoteTech. Functionally, this connection may be used to determine if the +---archive volume is accessible. +--- +---.. warning:: +--- +--- Attempting to send a message to the "home" connection will result in an +--- error message. While this connection uses the same structure as when +--- sending inter-vessel and inter-processor messages, message support is +--- not included. +---@type Connection +homeconnection = nil + +---Get only. +---Returns a `Connection` representing the :ref:`CPU Vessel's` +---communication line to a control source. This may be the same as the +---:global:`HOMECONNECTION`, or it may represent a local crewed command pod, +---or it may represent a connection to a control station. When using the +---``CommNetConnectivityManager`` this should show as connected whenever a vessel +---has partial manned control, or full control. Functionally this may be used +---to determine if terminal input is available, and what the potential signal +---delay may be for this input. +--- +---.. warning:: +--- +--- Attempting to send a message to the "control" connection will result in +--- an error message. While this connection uses the same structure as when +--- sending inter-vessel and inter-processor messages, message support is +--- not included. +---@type Connection +controlconnection = nil + +---Get/Set. +---This is identical to :attr:`MODE` above. +---:: +--- +--- // These two do the same thing: +--- SET WARPMODE TO "PHYSICS". +--- SET KUNIVERSE:TIMEWARP:MODE TO "PHYSICS". +--- +--- // These two do the same thing: +--- SET WARPMODE TO "RAILS". +--- SET KUNIVERSE:TIMEWARP:MODE TO "RAILS". +---@type string | "physics" | "rails" +warpmode = nil + +---Get/Set. +---This is identical to :attr:`WARP` above. +---:: +--- +--- // These do the same thing: +--- SET WARP TO 3. +--- SET KUNIVERSE:TIMEWARP:WARP to 3. +---@type number +warp = nil + +---Get/Set. +---A variable that controls or queries whether or not the game is in map view:: +--- +--- IF MAPVIEW { +--- PRINT "You are looking at the map.". +--- } ELSE { +--- PRINT "You are looking at the flight view.". +--- }. +--- +---You can switch between map and flight views by setting this variable:: +--- +--- SET MAPVIEW TO TRUE. // to map view +--- SET MAPVIEW TO FALSE. // to flight view +---@type boolean +mapview = nil + +---Get only. +---@type Constant +constant = nil + +---Get only. +---Returns operating system version number. e.g. 0.1.2.3 +---@type string +version = nil + +---Get only. +---Gives the Prime Meridian `Vector` for the Solar System itself, in +---current Ship-Raw XYZ coordinates. +--- +---Both the :attr:`Orbit:LONGITUDEOFASCENDINGNODE` orbit suffix and the +---:attr:`Body:ROTATIONANGLE` body suffix are expressed in terms of +---degree offsets from this *Prime Meridian Reference Vector*. +---@type Vector +solarprimevector = nil + +---Get only. +---Get-only. ``archive`` returns a `Volume` structure referring to the archive. +---You can read more about what archive is on the :ref:`File & volumes ` page. +---@type Volume +archive = nil + +---Get/Set. +---@type number +THROTTLE = nil + +---Get/Set. +---@type Vector | Direction | Node | string | "kill" +STEERING = nil + +---Get/Set. +---@type GeoCoordinates | Vessel | number +WHEELSTEERING = nil + +---Get/Set. +---@type number +WHEELTHROTTLE = nil + +---Get/Set. +---.. object:: SASMODE +--- +---Getting this variable will return the currently selected SAS mode. Where ``value`` is one of the valid strings listed below, this will set the stock SAS mode for the cpu vessel:: +--- +--- SET SASMODE TO value. +--- +---It is the equivalent to clicking on the buttons next to the nav ball while manually piloting the craft, and will respect the current mode of the nav ball (orbital, surface, or target velocity - use NAVMODE to read or set it). Valid strings for ``value`` are ``"PROGRADE"``, ``"RETROGRADE"``, ``"NORMAL"``, ``"ANTINORMAL"``, ``"RADIALOUT"``, ``"RADIALIN"``, ``"TARGET"``, ``"ANTITARGET"``, ``"MANEUVER"``, ``"STABILITYASSIST"``, and ``"STABILITY"``. A null or empty string will default to stability assist mode, however any other invalid string will throw an exception. This feature will respect career mode limitations, and will throw an exception if the current vessel is not able to use the mode passed to the command. An exception is also thrown if ``"TARGET"`` or ``"ANTITARGET"`` are used when no target is set. +--- +---.. note:: +--- SAS mode is reset to stability assist when toggling SAS on, however it doesn't happen immediately. +--- Therefore, after activating SAS, you'll have to skip a frame before setting the SAS mode. +--- Velocity-related modes also reset back to stability assist when the velocity gets too low. +--- +---.. warning:: SASMODE does not work with RemoteTech +--- +--- Due to the way that RemoteTech disables flight control input, the built in SAS modes do not function properly when there is no connection to the KSC or a Command Center. If you are writing scripts for use with RemoteTech, make sure to take this into account. +--- +---.. warning:: SASMODE should not be used with LOCK STEERING +--- +--- Be aware that having KSP's ``SAS`` turned on *will* conflict +--- with using "cooked control" (the ``lock steering`` command). You +--- should not use these two modes of steering control at the same time. +--- For further information see the +--- :ref:`warning in lock steering documentation`. +---@type string | "maneuver" | "prograde" | "retrograde" | "normal" | "antinormal" | "radialin" | "radialout" | "target" | "antitarget" | "stability" | "stabilityassist" +sasmode = nil + +---Get/Set. +---.. object:: NAVMODE +--- +---Getting this variable will return the currently selected nav ball speed display mode. Where ``value`` is one of the valid strings listed below, this will set the nav ball mode for the cpu vessel:: +--- +--- SET NAVMODE TO value. +--- +---It is the equivalent to changing the nav ball mode by clicking on speed display on the nav ball while manually piloting the craft, and will change the current mode of the nav ball, affecting behavior of most SAS modes. Valid strings for ``value`` are ``"ORBIT"``, ``"SURFACE"`` and ``"TARGET"``. A null or empty string will default to orbit mode, however any other invalid string will throw an exception. This feature is accessible only for the active vessel, and will throw an exception if the current vessel is not active. An exception is also thrown if ``"TARGET"`` is used, but no target is selected. +---@type string | "orbit" | "surface" | "target" +navmode = nil + +---Get/Set. +---The PIDLoop used to control wheelsteering. Can be used to optimize +--- steering performance and eliminate steering oscillations on some vessels. +---@type PIDLoop +wheelsteeringpid = nil + +---Get only. +---@type VesselAltitude +alt = nil + +---Get only. +---@type Vector +angularvelocity = nil + +---Get only. +---The orbit patch describing the next encounter with a body the current +---vessel will enter. If there is no such encounter coming, it will return +---the special string "None". If there is an encounter coming, it will +---return an object :ref:`of type Orbit `. (i.e. to obtain the name +---of the planet the encounter is with, you can do: +---``print ENCOUNTER:BODY:NAME.``, for example.). +---@type Orbit | string +encounter = nil + +---Get only. +---@type OrbitEta +eta = nil + +---Get only. +---You can obtain the number of seconds it has been since the current +---CPU vessel has been launched with the bound global variable +---``MISSIONTIME``. In real space programs this is referred to usually +---as "MET" - Mission Elapsed Time, and it's what's being measured when +---you hear that familiar voice saying "T minus 10 seconds..." Point "T" +---is the zero point of the mission elapsed time, and everything before that +---is a negative number and everything after it is a positive number. +---kOS is only capable of returning the "T+" times, not the "T-" times, +---because it doesn't read your mind to know ahead of time when you plan +---to launch. +---@type Struct +missiontime = nil + +---Get only. +---The special variable :global:`TIME` is used to get the current time +---in the gameworld (not the real world where you're sitting in a chair +---playing Kerbal Space Program.) It is the same thing as calling +---:func:`TIME` with empty parentheses. +---@type TimeStamp +time = nil + +---Get only. +---@type Vessel +activeship = nil + +---Get only. +---Vessel situation +---@type string +status = nil + +---Get only. +---@type Stage +stageinfo = nil + +---Get/Set. +---.. attribute:: Vessel:SHIPNAME +--- +---The name of the vessel as it appears in the tracking station. When you set this, it cannot be empty. +---@type string +shipname = nil + +---Get only. +---@type SteeringManager +steeringmanager = nil + +---Get only. +---:global:`NEXTNODE` is a built-in variable that always refers to the next upcoming node that has been added to your flight plan:: +--- +--- SET MyNode to NEXTNODE. +--- PRINT NEXTNODE:PROGRADE. +--- REMOVE NEXTNODE. +--- +---Currently, if you attempt to query :global:`NEXTNODE` and there is no node on your flight plan, it produces a run-time error. (This needs to be fixed in a future release so it is possible to query whether or not you have a next node). +--- +---.. warning:: +--- As per the warning above at the top of the section, NEXTNODE won't work on vessels that are not the active vessel. +--- +---The special identifier :global:`NEXTNODE` is a euphemism for "whichever node is coming up soonest on my flight path". Therefore you can remove a node even if you no longer have the maneuver node variable around, by doing this:: +--- +--- REMOVE NEXTNODE. +---@type Node +nextnode = nil + +---Get only. +---Returns true if there is a planned maneuver `ManeuverNode` in the +---:ref:`CPU vessel's ` flight plan. This will always return +---false for the non-active vessel, as access to maneuver nodes is limited to the active vessel. +---@type boolean +hasnode = nil + +---Get only. +---:type: `List` of `ManeuverNode` elements +--- +---Returns a list of all `ManeuverNode` objects currently on the +---:ref:`CPU vessel's ` flight plan. This list will be empty if +---no nodes are planned, or if the :ref:`CPU vessel ` is currently +---unable to use maneuver nodes. +--- +---.. note:: +--- If you store a reference to this list in a variable, the variable's +--- instance will not be automatically updated if you :global:`ADD` or +--- :global:`REMOVE` maneuver nodes to the flight plan. +--- +---.. note:: +--- Adding a `ManeuverNode` to this list, or a reference to this +--- list **will not** add it to the flight plan. Use the :global:`ADD` +--- command instead. +---@type List +allnodes = nil + +---Get only. +---@type Orbit +obt = nil + +---Get only. +---@type Orbit +orbit = nil + +---Get only. +---This returns the amount of IPU (instructions per update) that are +---left in this physics tick. For example, if this gives you the value +---20, you can run 20 more instructions within this physics update +---before the game will let the rest of the game run and advance time. +---After this amount of instructions, other CPUs will run their +---instructions, and the game will do the rest of its work, and then +---`TIME:SECONDS` will increase and you'll get another physics update +---in which to run more of the program. +--- +---Another way to think of this is "For the next ``OPCODESLEFT`` +---instructions, the universe is still physically frozen, giving +---frozen values for time, position, velocity, etc. After that +---it will be the next physics tick and those things will have moved +---ahead to the next physics tick." +--- +---OPCODESLEFT can be used to try to make sure you run a block of code in one +---physics tick. This is useful when working with vectors or when interacting +---with shared message queues. +--- +---To use:: +--- +--- // Will always wait the first time, becomes more accurate the second time. +--- GLOBAL OPCODESNEEDED TO 1000. +--- IF OPCODESLEFT < OPCODESNEEDED +--- WAIT 0. +--- LOCAL STARTIPU TO OPCODESLEFT. +--- LOCAL STARTTIME TO TIME:SECONDS. +--- +--- // your code here, make sure to keep the instruction count lower than your CONFIG:IPU +--- +--- IF STARTTIME = TIME:SECONDS { +--- SET OPCODESNEEDED TO STARTIPU - OPCODESLEFT. +--- } ELSE { +--- PRINT "Code is taking too long to execute. Please make the code shorter or raise the IPU.". +--- } +---@type number +opcodesleft = nil + +---Get only. +---There is a special keyword ``DONOTHING`` that refers to a special +---kind of `KosDelegate` called a `NoDelegate`. +--- +---The type string returned by ``DONOTHING:TYPENAME`` is ``"NoDelegate"``. +---Otherwise an instance of `NoDelegate` has the same suffixes as one +---of `KOSDelegate`, although you're not usually +---expected to ever use them, except maybe ``TYPENAME`` to discover +---that it is a `NoDelegate`. +--- +---``DONOTHING`` is used when you're in a situation where you had +---previously assigned a `KosDelegate` to some callback hook +---the kOS system provides, but now you want the kOS system to stop +---calling it. To do so, you assign that callback hook to the value +---``DONOTHING``. +--- +---``DONOTHING`` is similar to making a `KosDelegate` that +---consists of just ``{return.}``. If you attempt to call it from +---your own code, that's how it will behave. But the one extra +---feature it has is that it allows kOS to understand your intent +---that you wish to disable a callback hook. kOS can detect when +---the ``KosDelegate`` you assign to something happens to be the +---``DONOTHING`` delegate. When it is, kOS knows to not even +---bother calling the delegate at all anymore. +---@type NoDelegate +donothing = nil + +---Get only. +---@type Config +config = nil + +---Get only. +---@type Addons +addons = nil + +---Get only. +---@type Core +core = nil + +---Get only. +---@type Vessel +ship = nil + +---Get/Set. +---:type: `string` (set); `Vessel` or `Body` or `Part` (get/set) +--- +---Where ``name`` is the name of a target vessel or planet, this will set the current target:: +--- +--- SET TARGET TO name. +--- +---For more information see :ref:`bindings`. +--- +---NOTE, the way to de-select the target is to set it to an empty +---string like this:: +--- +--- SET TARGET TO "". // de-selects the target, setting it to nothing. +--- +---(Trying to use :ref:`UNSET TARGET.` will have no effect because +---``UNSET`` means "get rid of the variable itself" which you're not +---allowed to do with built-in bound variables like ``TARGET``.) +---@type string | Vessel | Body | Part +target = nil + +---Get only. +---@type boolean +hastarget = nil + +---Get only. +---.. attribute:: Vessel:HEADING +--- +---*absolute* compass heading (degrees) to this vessel from the :ref:`CPU Vessel ` +---@type number +shipheading = nil + +---Get only. +---.. attribute:: Vessel:AVAILABLETHRUST +--- +---Sum of all the :ref:`engines' AVAILABLETHRUSTs ` of all the currently active engines taking into account their throttlelimits. Result is in Kilonewtons. +---@type number +availablethrust = nil + +---Get only. +---.. attribute:: Vessel:MAXTHRUST +--- +---Sum of all the :ref:`engines' MAXTHRUSTs ` of all the currently active engines In Kilonewtons. +---@type number +maxthrust = nil + +---Get only. +---.. attribute:: Vessel:FACING +--- +---The way the vessel is pointed, which is also the rotation +---that would transform a vector from a coordinate space where the +---axes were oriented to match the vessel's orientation, to one +---where they're oriented to match the world's ship-raw coordinates. +--- +---i.e. ``SHIP:FACING * V(0,0,1)`` gives the direction the +---ship is pointed (it's Z-axis) in absolute ship-raw coordinates +---@type Direction +facing = nil + +---Get only. +---.. attribute:: Vessel:ANGULARMOMENTUM +--- +---Given in :ref:`SHIP_RAW ` reference frame. The vector +---represents the axis of the rotation (in left-handed convention, +---not right handed as most physics textbooks show it), and its +---magnitude is the angular momentum of the rotation, which varies +---not only with the speed of the rotation, but also with the angular +---inertia of the vessel. +--- +---Units are expressed in: (Megagrams * meters^2) / (seconds * radians) +--- +---(Normal SI units would use kilograms, but in KSP all masses use a +---1000x scaling factor.) +--- +---**Justification for radians here:** +---Unlike the trigonometry functions in kOS, this value uses radians +---rather than degrees. The convention of always expressing angular +---momentum using a formula that assumes you're using radians is a very +---strongly adhered to universal convention, for... reasons. +---It's so common that it's often not even explicitly +---mentioned in information you may find when doing a web search on +---helpful formulae about angular momentum. This is why kOS doesn't +---use degrees here. (That an backward compatibility for old scripts. +---It's been like this for quite a while.). +---@type Vector +angularmomentum = nil + +---Get only. +---.. attribute:: Vessel:ANGULARVEL +--- +---Angular velocity of the body's rotation about its axis (its +---day) expressed as a vector. +--- +---The direction the angular velocity points is in Ship-Raw orientation, +---and represents the axis of rotation. Remember that everything in +---Kerbal Space Program uses a *left-handed coordinate system*, which +---affects which way the angular velocity vector will point. If you +---curl the fingers of your **left** hand in the direction of the rotation, +---and stick out your thumb, the thumb's direction is the way the +---angular velocity vector will point. +--- +---The magnitude of the vector is the speed of the rotation. +--- +---Note, unlike many of the other parts of kOS, the rotation speed is +---expressed in radians rather than degrees. This is to make it +---congruent with how VESSEL:ANGULARMOMENTUM is expressed, and for +---backward compatibility with older kOS scripts. +---@type Vector +angularvel = nil + +---Get only. +---.. attribute:: Vessel:MASS +--- +---:type: `number` (metric tons) +--- +---The mass of the ship +---@type number +mass = nil + +---Get only. +---.. attribute:: Vessel:VERTICALSPEED +--- +---:type: `number` (m/s) +--- +---How fast the ship is moving. in the "up" direction relative to the SOI Body's sea level surface. +---@type number +verticalspeed = nil + +---Get only. +---.. attribute:: Vessel:GROUNDSPEED +--- +---:type: `number` (m/s) +--- +---How fast the ship is moving in the two dimensional plane horizontal +---to the SOI body's sea level surface. The vertical component of the +---ship's velocity is ignored when calculating this. +--- +---.. note:: +--- +--- .. versionadded:: 0.18 +--- The old name for this value was SURFACESPEED. The name was changed +--- because it was confusing before. "surface speed" implied it's the +--- `number` magnitude of "surface velocity", but it wasn't, because of how +--- it ignores the vertical component. +---@type number +groundspeed = nil + +---Get only. +---@type number +surfacespeed = nil + +---Get only. +---.. attribute:: Vessel:AIRSPEED +--- +---:type: `number` (m/s) +--- +---How fast the ship is moving relative to the air. KSP models atmosphere as simply a solid block of air "glued" to the planet surface (the weather on Kerbin is boring and there's no wind). Therefore airspeed is generally the same thing as as the magnitude of the surface velocity. +---@type number +airspeed = nil + +---Get only. +---@type number +latitude = nil + +---Get only. +---@type number +longitude = nil + +---Get only. +---@type number +altitude = nil + +---Get only. +---.. attribute:: Orbitable:APOAPSIS +--- +---:type: `number` (deg) +--- +---.. deprecated:: 0.15 +--- +--- This is only kept here for backward compatibility. +--- in new scripts you write, use :attr:`OBT:APOAPSIS `. +--- (i.e. use ``SHIP:OBT:APOAPSIS`` instead of ``SHIP:APOAPSIS``, +--- or use ``MUN:OBT:APOAPSIS`` instead of ``MUN:APOAPSIS``, etc). +---@type number +apoapsis = nil + +---Get only. +---.. attribute:: Orbitable:PERIAPSIS +--- +---:type: `number` (deg) +--- +---.. deprecated:: 0.15 +--- +--- This is only kept here for backward compatibility. +--- in new scripts you write, use :attr:`OBT:PERIAPSIS `. +--- (i.e. use ``SHIP:OBT:PERIAPSIS`` instead of ``SHIP:PERIAPSIS``). +--- or use ``MUN:OBT:PERIAPSIS`` instead of ``MUN:PERIAPSIS``, etc). +---@type number +periapsis = nil + +---Get only. +---.. attribute:: Orbitable:BODY +--- +---The `Body` that this object is orbiting. I.e. ``Mun:BODY`` returns ``Kerbin``. +---@type Body +body = nil + +---Get only. +---.. attribute:: Orbitable:UP +--- +---pointing straight up away from the SOI body. +---@type Direction +up = nil + +---Get only. +---.. attribute:: Orbitable:NORTH +--- +---pointing straight north on the SOI body, parallel to the surface of the SOI body. +---@type Direction +north = nil + +---Get only. +---.. attribute:: Orbitable:PROGRADE +--- +---pointing in the direction of this object's **orbitable-frame** velocity +---@type Direction +prograde = nil + +---Get only. +---.. attribute:: Orbitable:RETROGRADE +--- +---pointing in the opposite of the direction of this object's **orbitable-frame** velocity +---@type Direction +retrograde = nil + +---Get only. +---.. attribute:: Orbitable:SRFPROGRADE +--- +---pointing in the direction of this object's **surface-frame** velocity. Note that if this Orbitable is itself a body, remember that this is relative to the surface of the SOI body, not this body. +---@type Direction +srfprograde = nil + +---Get only. +---.. attribute:: Orbitable:SRFRETROGRADE +--- +---pointing in the opposite of the direction of this object's **surface-frame** velocity. Note that this is relative to the surface of the SOI body. +---@type Direction +srfretrograde = nil + +---Get only. +---.. attribute:: Orbitable:VELOCITY +--- +---The :struct:`orbitable velocity ` of this object in the :ref:`SHIP-RAW reference frame ` +---@type OrbitableVelocity +velocity = nil + +---Get only. +---.. attribute:: Orbitable:GEOPOSITION +--- +---A combined structure of the latitude and longitude numbers. +---@type GeoCoordinates +geoposition = nil + +---Get only. +---@type Body +sun = nil + +---Get only. +---@type Body +kerbin = nil + +---Get only. +---@type Body +mun = nil + +---Get only. +---@type Body +minmus = nil + +---Get only. +---@type Body +moho = nil + +---Get only. +---@type Body +eve = nil + +---Get only. +---@type Body +duna = nil + +---Get only. +---@type Body +ike = nil + +---Get only. +---@type Body +jool = nil + +---Get only. +---@type Body +laythe = nil + +---Get only. +---@type Body +vall = nil + +---Get only. +---@type Body +bop = nil + +---Get only. +---@type Body +tylo = nil + +---Get only. +---@type Body +gilly = nil + +---Get only. +---@type Body +pol = nil + +---Get only. +---@type Body +dres = nil + +---Get only. +---@type Body +eeloo = nil + diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/plugin/patcher.lua b/Resources/GameData/kOS/PluginData/LuaLSAddon/plugin/patcher.lua new file mode 100644 index 0000000000..853b94d799 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/plugin/patcher.lua @@ -0,0 +1,147 @@ +local patchMessage = [[ +Patcher plugin will patch lua language server to add the arrow syntax support. +You may may also see this message after lua language server was updated and needs to get patched again. +If you do not wish to see this message remove the plugin from your lua language server settings. +To uninstall the patch replace the "patcher.lua" plugin with the "unpatcher.lua" plugin or reinstall lua language server. +The arrow syntax won't be enabled unless you have '"Lua.runtime.special": { "_G": "_G" }' in your settings("runtime.special" in case you use .luarc.json file). +For the syntax support to apply lua language server needs a restart. +If the patch fails, the added syntax doesn't work or there are errors popping up after the patch then most likely the installed version of lua language server is not supported. +The latest version of lua language server that is guaranteed to work is 3.13.2. +]] + +local parseArrowFn = [[ + +--patch_id:0 +local function parseArrow() + local funcLeft = getPosition(Tokens[Index], 'left') + local funcRight = getPosition(Tokens[Index], 'right') + local func = { + type = 'function', + start = funcLeft, + finish = funcRight, + bstart = funcRight, + keyword = { + [1] = funcLeft, + [2] = funcRight, + }, + } + Index = Index + 2 + skipSpace(true) + local LastLocalCount = LocalCount + LocalCount = 0 + pushChunk(func) + func.args = { + type = 'funcargs', + start = funcLeft, + finish = funcRight, + parent = func, + } + local returnExp = parseExp() + popChunk() + func.bfinish = getPosition(Tokens[Index], 'left') + if returnExp then + returnExp.parent = func + func[1] = { + returnExp, + type = 'return', + parent = func, + finish = returnExp.finish, + start = func.finish + } + func.bfinish = func[1].finish + func.hasReturn = true + func.returns = { func[1] } + func.keyword[3] = returnExp.finish-1 + func.keyword[4] = returnExp.finish + func.finish = returnExp.finish + else + func.finish = lastRightPosition() + missExp() + end + LocalCount = LastLocalCount + return func +end +]] + +local parseTokenCheck = [[ + + if State.options.special._G == "_G" and token == '>' then + return parseArrow() + end +]] + +local client = require 'client' + +local function insert(str1, str2, index) return str1:sub(1, index-1)..str2..str1:sub(index, -1) end + +local function patcherError(err) client.showMessage("Error", "Patcher plugin: "..err) end +local function openFileFailedError(path, err) patcherError("Failed to open the file at \""..path.."\": \n"..err) end +local function readFileFailedError(path, err) patcherError("Failed to read the file at \""..path.."\": \n"..err) end +local function writeFileFailedError(path, err) patcherError("Failed to write to the file at \""..path.."\": \n"..err) end +local function patchFailedError() patcherError("Failed to apply the patch. See the patch confirmation message") end + +local patchFilePath = package.path:match("([^?]+)").."parser/compile.lua" +local originalFilePath = package.path:match("([^?]+)").."parser/original.compile.lua" + +-- get contents of the patch file +local patchFile, patchFileOpenErr = io.open(patchFilePath, "r") +if not patchFile then + openFileFailedError(patchFilePath, patchFileOpenErr) + return +end +local patchFileText, patchFileReadErr = patchFile:read("a") +if patchFileReadErr then + readFileFailedError(patchFilePath, patchFileReadErr) + patchFile:close() + return +end +patchFile:close() + +-- check if the file is already patched +local filePatched = patchFileText:match("--patch_id:(%d+)") +if filePatched then return end + +-- ask the user if they want to patch +local result = client.awaitRequestMessage("Warning", patchMessage, { "Ok", "Don't patch" }) +if result ~= "Ok" then return end + +-- save original file before patching +local originalFile, originalFileOpenErr = io.open(originalFilePath, "w") +if not originalFile then + openFileFailedError(originalFilePath, (originalFileOpenErr or "")) + return +end +local _, originalFileWriteErr = originalFile:write(patchFileText) +if originalFileWriteErr then + writeFileFailedError(originalFilePath, originalFileWriteErr) + return +end +originalFile:close() + +-- patch the text +local parseArrowFnInsertPos = patchFileText:match("()\nlocal function parseFunction") +if not parseArrowFnInsertPos then + patchFailedError() + return +end +patchFileText = insert(patchFileText, parseArrowFn, parseArrowFnInsertPos) +local tokenCheckInsertPos = patchFileText:match("return parseFunction%(%)\n end\n()") +if not tokenCheckInsertPos then + patchFailedError() + return +end +patchFileText = insert(patchFileText, parseTokenCheck, tokenCheckInsertPos) + +-- save patched text to the patch file +local patchFile, patchFileOpenErr = io.open(patchFilePath, "w") +if not patchFile then + openFileFailedError(patchFilePath, patchFileOpenErr) + return +end +local _, patchFileWriteErr = patchFile:write(patchFileText) +if patchFileWriteErr then + writeFileFailedError(patchFilePath, patchFileWriteErr) + patchFile:close() + return +end +patchFile:close() diff --git a/Resources/GameData/kOS/PluginData/LuaLSAddon/plugin/unpatcher.lua b/Resources/GameData/kOS/PluginData/LuaLSAddon/plugin/unpatcher.lua new file mode 100644 index 0000000000..2adafe7320 --- /dev/null +++ b/Resources/GameData/kOS/PluginData/LuaLSAddon/plugin/unpatcher.lua @@ -0,0 +1,28 @@ +local client = require 'client' + +local patchFilePath = package.path:match("([^?]+)").."parser/compile.lua" +local originalFilePath = package.path:match("([^?]+)").."parser/original.compile.lua" + +local originalFile, originalErr = io.open(originalFilePath, "r") +if not originalFile then + client.showMessage("Error", "Unpatcher plugin: Failed to open original file at \""..originalFilePath.."\". \n"..originalErr) + return +end +local originalFileText, originalFileReadErr = originalFile:read("a") +if not originalFileText then + client.showMessage("Error", "Unpatcher plugin: Failed to read original file at \""..originalFilePath.."\". \n"..originalFileReadErr) +end +originalFile:close() + +local patchFile, patchErr = io.open(patchFilePath, "w") +if not patchFile then + client.showMessage("Error", "Unpatcher plugin: Failed to open patch file at \""..patchFilePath.."\". \n"..patchErr) + return +end +local _, patchFileWriteErr = patchFile:write(originalFileText) +if patchFileWriteErr then + client.showMessage("Error", "Unpatcher plugin: Failed to write to patch file at \""..patchFilePath.."\". \n"..patchFileWriteErr) +end +patchFile:close() + +client.showMessage("Info", "Unpatcher plugin: Successfully unpatched lua language server. It is highly recommended to remove this plugin after use") diff --git a/doc/source/contents.rst b/doc/source/contents.rst index 967dafacda..a4cbc12d54 100644 --- a/doc/source/contents.rst +++ b/doc/source/contents.rst @@ -23,6 +23,7 @@ Documentation Table of Contents Getting Help Changes About + Lua Interpreter Indices and tables ================== diff --git a/doc/source/lua_interpreter.rst b/doc/source/lua_interpreter.rst new file mode 100644 index 0000000000..7fa02a57a5 --- /dev/null +++ b/doc/source/lua_interpreter.rst @@ -0,0 +1,240 @@ +.. _lua_interpreter: + +Lua Interpreter +=============== + +Lua interpreter is one of the options for the "interpreter" field on kOSProcessors. +You can enable it with the PAW menu activated by right clicking on the kOSProcessor part or with the ``core:setfield("interpreter", "lua").`` terminal command. +Lua interpreter uses all the same structures, functions and bound variables that are available in kOS. +This documentation will walk you through how to use it if you are already comfortable using kOS with kerboscript, documentation for people new to kOS may come in the future. +Basic knowledge of lua is required. + +- `Main differences from kerboscript`_ +- `Main lua changes`_ +- `Development environment`_ +- `Name changes to bound variables and functions`_ +- `Default modules`_ +- `Callbacks module`_ +- `Misc module`_ +- `Design patterns`_ +- `Example scripts`_ +- `System variables`_ + +Main differences from kerboscript +--------------------------------- +For the most part you can directly apply your kerboscript knowledge when using the lua interpreter. +Just write the lua scripts using its syntax as you would kerboscript, but there are a few differences you need to be aware of. + +- The difference between kerboscript ``RUN`` command and ``dofile`` and ``loadfile`` functions. The main difference being that the context is shared across the whole core. +- Triggers are implemented using the `callback system`_. While you can use them exactly like you would use kerboscript triggers, that is not the most efficient way. +- Some `name changes to bound variables and functions`_. + +Main lua changes +---------------- +- ``io``, ``os``, ``debug`` libraries are not loaded. +- Basic library functions accessing the file system are made to access the internal kOS file system. +- The environment table has ``__index`` and ``__newindex`` metamethods that act as a binding layer for kOS functions and bound variables. +- `Default modules`_ are loaded. +- There is a small syntactic sugar to define functions that return expressions. ``> *expression*`` gets interpreted as ``function() return *expression* end``. + +Development environment +----------------------- +The best way to use the lua interpreter is with a text editor that assists you. +To get annotations for all structures, functions and bound variables with their descriptions, +as well as support for the added syntactic sugar see the README file at ``*KSP_Folder*/GameData/kOS/PluginData/LuaLSAddon/README.md``. + +Name changes to bound variables and functions +--------------------------------------------- +Some name changes were neccessary to resolve name conflicts: + | ``STAGE`` bound variable → ``stageinfo``. + | ``HEADING`` bound variable → ``shipheading``. + | ``BODY`` function → ``getbody``. + +kOS variables and functions that are accessable only by capital names: + | ``STEERING`` + | ``THROTTLE`` + | ``WHEELSTEERING`` + | ``WHEELTHROTTLE`` + | ``VECDRAW`` + | ``CLEARVECDRAWS`` + + This was done so there is no confusion between kOS variables and lua variables used by default modules. + +Kerboscript command alternatives: + | ``WHEN condition THEN *body*.`` → `when(condition, body) <#when-condition-body-priority-callbacks>`_ + | ``ON state *body*.`` → `on(state, body) <#on-state-body-priority-callbacks>`_ + | ``WAIT seconds.`` → `wait(seconds)`_ + | ``WAIT UNTIL condition.`` → `waituntil(condition)`_ + | ``PRINT item AT(column, row).`` → ``printat(item, column, row)`` + | ``STAGE.`` → ``stage()`` + | ``CLEARSCREEN.`` → ``clearscreen()`` + | ``ADD node.`` → ``add(node)`` + | ``REMOVE node.`` → ``remove(node)`` + | ``LOG text TO path.`` → ``logfile(text, path)`` + | ``SWITCH TO volumeId.`` → ``switch(volumeId)`` + | ``EDIT path.`` → ``edit(path)`` + | ``REBOOT.`` → ``reboot()`` + | ``SHUTDOWN.`` → ``shutdown()`` + | ``LIST listType IN variable.`` → ``variable = buildlist(listType)`` + | ``LIST listType.`` → ``printlist(listType)`` + | ``LIST.`` → ``printlist("files")`` + + All these functions, except the first 4, are available in kerboscript but are documented as commands. + +Default modules +--------------- +You can look at the code for the default modules at the ``*KSP_Folder*/GameData/kOS/PluginData/LuaModules`` folder. +You can also create your own modules, place them in this folder and kOS will automatically require them during core booting. + +Callbacks module +---------------- + +Ship control +------------ +There are 4 variables that are used to access ship control: ``steering``, ``throttle``, ``wheelsteering``, ``wheelthrottle``. +If a function is assigned to those variables the return value of the function will be calculated at the start of each physics tick and used as the value to control the ship. + +:: + + steering = function() return prograde end -- kOS will continuously point the ship prograde + + steering => prograde -- equivalent to the previous command using the added syntactic sugar + + steering = prograde -- value will be updated once and stay, even if prograde changes in the future + + +Callback system +--------------- +Callbacks is a list of lua `coroutines `_ that get called each physics tick(or each frame). +The main interface to the callback system is the ``addcallback`` function. + +``addcallback(body, priority?, callbacks?)`` +```````````````````````````````````````````` + body: + Callback function body to get executed on the next physics tick(or the next frame, see the third parameter). + If returns a number or ``true`` the callback doesn't get cleared. + If returns a number this number will be used as the callback priority, see the second parameter. + priority?: + Callback priority. Callbacks with highest priorities get executed first. Default is 0. + callbacks?: + Callbacks table where to add the callback to. + Options: + + - ``callbacks.fixedupdatecallbacks``: gets executed each physics tick(Default). + - ``callbacks.updatecallbacks``: gets executed each frame. + +``when(condition, body, priority?, callbacks?)`` +```````````````````````````````````````````````` + | **condition:** The callback executes only if this function returns a true value + | **body:** Same as in ``addcallback`` function + | **priority?:** Same as in ``addcallback`` function + | **callbacks?:** Same as in ``addcallback`` function + + "When" trigger implemented as a wrapper around the ``addcallback`` function. + +``on(state, body, priority?, callbacks?)`` +`````````````````````````````````````````` + | **state:** The callback executes only if this function returns a value that is not equal to the value it returned previously + | **body:** Same as in ``addcallback`` function + | **priority?:** Same as in ``addcallback`` function + | **callbacks?:** Same as in ``addcallback`` function + + "On" trigger implemented as a wrapper around the ``addcallback`` function. + +.. note:: + Callbacks are coroutines, and `wait <#wait-seconds>`_/`waituntil <#waituntil-condition>`_ functions are simple abstractions using the ``coroutine.yield`` function. + Because of this using ``wait`` in a callback will not block any other code. + You can think of it as a callback saying "Ok, I am done for now, I will let other code execute until its my turn again". + This unlocks some helpful design patterns: + + Timed actions in callbacks without nesting:: + + when(> alt.radar < 100, function() + gear = true + wait(7) + print("Gear deployed.") + end) + + The ``wait(7)`` being inside the callback body means the following code(or the terminal) is not blocked. + + Running programs without blocking the terminal:: + + addcallback(> dofile("launch.lua")) + + As long as the running program is not using all available instructions the terminal won't be blocked. + This also allows running multiple blocking functions/programs at the same time. + In that case the programs would "pass" the execution between each other using the "wait" functions. + +Misc module +------------ + +``wait(seconds)`` +````````````````` + Suspends the execution for the specified amount of time. + Any call to this function will suspend execution for at least one physics tick. + This function is a simple abstraction made to achieve the same effect as the kerboscript ``wait *number*.`` command. + + implementation:: + + function wait(seconds) + local waitEnd = time.seconds + seconds + coroutine.yield() + while time.seconds < waitEnd do coroutine.yield() end + end + +``waituntil(condition)`` +```````````````````````` + **condition:** function + + Suspends the execution until the ``condition`` function returns a true value. + This function is a simple abstraction made to achieve the same effect as the kerboscript ``wait until *condition*.`` command. + + implementation:: + + function waituntil(condition) + while not condition() do coroutine.yield() end + end + +``vecdraw(start?, vector?, color?, label?, scale?, show?, width?, pointy?, wiping?): VecdrawTable`` +``````````````````````````````````````````````````````````````````````````````````````````````````` + Wrapper around kOS :func:`VECDRAW` function that uses the `Callback system`_ to automatically update the "start" and "vector". + Those parameters can accept functions, in which case their values will be changed each frame with the return value of the functions. + This function returns a table representing a Vecdraw structure, and when this table gets garbage collected the vecdraw is removed. + + :: + + vd = vecdraw(nil, mun.position) -- assign the return value to a variable to keep it from being collected + vd.show = true + vd = nil -- this will remove the vecdraw by garbage collection + +``clearvecdraws()`` +``````````````````` + A wrapper around kOS :func:`CLEARVECDRAWS` function that also clears vecdraws created with the ``vecdraw`` function + +``json`` +```````` + `openrestry/lua-cjson module `_ used for encoding and decoding json. + +Design patterns +--------------- + +Interactive scripts +``````````````````` + You can run programs defining functions/callbacks etc. made to be interacted with from the terminal. + For example you can have a program defining basic utility functions and then using them how you please from the terminal. + Or create an interactive script for a specific craft that runs as its firmware from the bootfile. + It was possible with kerboscript using telnet to paste programs into the terminal, but in lua the context between programs and the terminal is shared, making it much easier to do. + +Example scripts +--------------- + You can take a look at some examples of using lua at `sug44/kOSLuaScripts `_ + +System variables +---------------- + + There are 3 lua variables that are used by kOS: + - ``fixedupdate``. Function called at the start of each physics tick. + - ``update``. Function called at the start of each frame. + - ``breakexecution``. Function called when the terminal receives the Ctrl+C code. If Ctrl+C was pressed 3 times while the command code was deprived of instructions by the ``fixedupdate`` function it will be set to ``nil`` to prevent the core from geting stuck. To prevent it from happening this function must ensure the terminal is not deprived of instructions. + + Those variables are used by the default `Callbacks module`_. diff --git a/src/kOS.Safe.Test/Execution/Config.cs b/src/kOS.Safe.Test/Execution/Config.cs index 980b9adbe2..c25f8b72fe 100644 --- a/src/kOS.Safe.Test/Execution/Config.cs +++ b/src/kOS.Safe.Test/Execution/Config.cs @@ -66,6 +66,18 @@ public int InstructionsPerUpdate { } } + + public int LuaInstructionsPerUpdate + { + get + { + return 10000; // high enough to run everything in one pass (unless it waits) + } + + set + { + } + } public bool ObeyHideUI { diff --git a/src/kOS.Safe/Encapsulation/IConfig.cs b/src/kOS.Safe/Encapsulation/IConfig.cs index 22bca80619..e9088ca38f 100644 --- a/src/kOS.Safe/Encapsulation/IConfig.cs +++ b/src/kOS.Safe/Encapsulation/IConfig.cs @@ -7,6 +7,7 @@ namespace kOS.Safe.Encapsulation public interface IConfig: ISuffixed { int InstructionsPerUpdate { get; set; } + int LuaInstructionsPerUpdate { get; set; } bool UseCompressedPersistence { get; set; } bool ShowStatistics { get; set; } bool StartOnArchive { get; set; } diff --git a/src/kOS.Safe/Encapsulation/IIndexable.cs b/src/kOS.Safe/Encapsulation/IIndexable.cs index 715e3c81c0..f6fa01c6a7 100644 --- a/src/kOS.Safe/Encapsulation/IIndexable.cs +++ b/src/kOS.Safe/Encapsulation/IIndexable.cs @@ -12,8 +12,9 @@ public interface IIndexable /// assume integer indeces: /// /// + /// if failed to find a value return null instead of throwing an exception /// - Structure GetIndex(int index); + Structure GetIndex(int index, bool failOkay = false); /// /// This should redirect to SetIndex(Structure index, Structure value), and is provided as diff --git a/src/kOS.Safe/Encapsulation/Lexicon.cs b/src/kOS.Safe/Encapsulation/Lexicon.cs index e616035670..c430ebcc23 100644 --- a/src/kOS.Safe/Encapsulation/Lexicon.cs +++ b/src/kOS.Safe/Encapsulation/Lexicon.cs @@ -280,9 +280,10 @@ public Structure GetIndex(Structure key) // Only needed because IIndexable demands it. For a lexicon, none of the code is // actually trying to call this: - public Structure GetIndex(int index) + public Structure GetIndex(int index, bool failOkay = false) { - return internalDictionary[FromPrimitiveWithAssert(index)]; + try { return internalDictionary[FromPrimitiveWithAssert(index)]; } + catch { if (failOkay) return null; throw; } } public void SetIndex(Structure index, Structure value) diff --git a/src/kOS.Safe/Encapsulation/ListValue.cs b/src/kOS.Safe/Encapsulation/ListValue.cs index 5650380282..4075ed8685 100644 --- a/src/kOS.Safe/Encapsulation/ListValue.cs +++ b/src/kOS.Safe/Encapsulation/ListValue.cs @@ -100,8 +100,11 @@ private ListValue SubListMethod(ScalarValue start, ScalarValue runLength) public static ListValue CreateList(IEnumerable list) => new ListValue(list.Cast()); - public Structure GetIndex(int index) => - Collection[index]; + public Structure GetIndex(int index, bool failOkay = false) + { + try { return Collection[index]; } + catch { if (failOkay) return null; throw; } + } public Structure GetIndex(Structure index) { diff --git a/src/kOS.Safe/Encapsulation/StringValue.cs b/src/kOS.Safe/Encapsulation/StringValue.cs index ecbe90ac3e..34dc3d9dfe 100644 --- a/src/kOS.Safe/Encapsulation/StringValue.cs +++ b/src/kOS.Safe/Encapsulation/StringValue.cs @@ -198,9 +198,10 @@ public ScalarValue ToScalar(ScalarValue defaultIfError = null) throw new KOSNumberParseException(internalString); } - public Structure GetIndex(int index) + public Structure GetIndex(int index, bool failOkay = false) { - return new StringValue(internalString[index]); + try { return new StringValue(internalString[index]); } + catch { if (failOkay) return null; throw; } } public Structure GetIndex(Structure index) diff --git a/src/kOS.Safe/Encapsulation/Structure.cs b/src/kOS.Safe/Encapsulation/Structure.cs index 4d51fc18e4..0ae9be0d16 100644 --- a/src/kOS.Safe/Encapsulation/Structure.cs +++ b/src/kOS.Safe/Encapsulation/Structure.cs @@ -132,13 +132,12 @@ private static IDictionary GetStaticSuffixesForType(Type curren /// /// Set a suffix of this structure that has suffixName to the given value. - /// If failOkay is false then it will throw exception if it fails to find the suffix. - /// If failOkay is true then it will continue happily if it fails to find the suffix. + /// Throws an exception if the structure suffix doesn't have a setter, unless failOkay /// /// /// - /// - /// false if failOkay was true and it failed to find the suffix + /// If true and structure suffix doesn't have a setter return false instead of throwing an exception + /// false if failed to find the suffix, true otherwise public virtual bool SetSuffix(string suffixName, object value, bool failOkay = false) { callInitializeSuffixes(); diff --git a/src/kOS.Safe/Encapsulation/Suffixes/DelegateSuffixResult.cs b/src/kOS.Safe/Encapsulation/Suffixes/DelegateSuffixResult.cs index cd9f6f146b..0f23a341b0 100644 --- a/src/kOS.Safe/Encapsulation/Suffixes/DelegateSuffixResult.cs +++ b/src/kOS.Safe/Encapsulation/Suffixes/DelegateSuffixResult.cs @@ -29,6 +29,10 @@ public class DelegateSuffixResult : ISuffixResult private readonly CallDel call; private Structure value; + public CallDel RawCall => call; + public DelegateInfo RawDelInfo => delInfo; + public void RawSetValue(Structure newValue) { value = newValue; } + public Structure Value { get { return value; } diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index d6d35cd1dd..06972b4770 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -49,8 +49,8 @@ public bool Equals(PopContextNotifyeeContainer other) } } - private readonly IStack stack; - private readonly VariableScope globalVariables; + protected readonly IStack stack; + protected readonly VariableScope globalVariables; private class YieldFinishedWithPriority @@ -60,10 +60,10 @@ private class YieldFinishedWithPriority } private List yields; - private double currentTime; - private readonly SafeSharedObjects shared; - private readonly List contexts; - private ProgramContext currentContext; + protected double currentTime; + protected readonly SafeSharedObjects shared; + protected readonly List contexts; + protected ProgramContext currentContext; private VariableScope savedPointers; private int instructionsPerUpdate; @@ -121,12 +121,12 @@ public CPU(SafeSharedObjects shared) globalVariables = new VariableScope(0, null); contexts = new List(); yields = new List(); - if (this.shared.UpdateHandler != null) this.shared.UpdateHandler.AddFixedObserver(this); popContextNotifyees = new HashSet(); } - public void Boot() + public virtual void Boot() { + if (shared.UpdateHandler != null) shared.UpdateHandler.AddFixedObserver(this); // break all running programs currentContext = null; contexts.Clear(); @@ -139,7 +139,7 @@ public void Boot() // clear global variables globalVariables.Clear(); // clear interpreter - if (shared.Interpreter != null) shared.Interpreter.Reset(); + if (shared.Terminal != null) shared.Terminal.Reset(); // load functions if (shared.FunctionManager != null) shared.FunctionManager.Load(); // load bindings @@ -214,7 +214,7 @@ public void Boot() } } - private void PushInterpreterContext() + protected void PushInterpreterContext() { var interpreterContext = new ProgramContext(true); // initialize the context with an empty program @@ -233,7 +233,7 @@ private void PushContext(ProgramContext context) if (contexts.Count > 1) { - shared.Interpreter.SetInputLock(true); + shared.Terminal.SetInputLock(true); } } @@ -268,7 +268,7 @@ private void PopContext() if (contexts.Count == 1) { - shared.Interpreter.SetInputLock(false); + shared.Terminal.SetInputLock(false); } } IsPoppingContext = false; @@ -450,7 +450,7 @@ public IProgramContext SwitchToProgramContext() return currentContext; } - public Opcode GetCurrentOpcode() + public virtual Opcode GetCurrentOpcode() { return currentContext.Program[currentContext.InstructionPointer]; } @@ -582,14 +582,14 @@ public void BreakExecution(bool manual) /// this purpose. Waiting in mainline code will still allow triggers to run. /// /// - public void YieldProgram(YieldFinishedDetector yieldTracker) + public virtual void YieldProgram(YieldFinishedDetector yieldTracker) { yields.Add(new YieldFinishedWithPriority() { detector = yieldTracker, priority = CurrentPriority }); yieldTracker.creationTimeStamp = currentTime; yieldTracker.Begin(shared); } - private bool IsYielding() + protected bool IsYielding() { int numStillBlocking = 0; @@ -1178,7 +1178,7 @@ public int GetArgumentStackSize() /// more than one instance of a trigger to exist for this same triggerFunctionPointer. Pass /// a zero to indicate you want to prevent multiple instances of triggers from this same /// entry point to be invokable. - /// Trigger should happen immediately on next opcode instead of waiting till next fixeupdate + /// Trigger should happen immediately on next opcode instead of waiting till next fixedupdate /// The closure the trigger should be called with. If this is /// null, then the trigger will only be able to see global variables reliably. /// A TriggerInfo structure describing this new trigger, which probably isn't very useful @@ -1218,7 +1218,7 @@ public TriggerInfo AddTrigger(int triggerFunctionPointer, InterruptPriority prio /// more than one instance of a trigger to exist for this same triggerFunctionPointer. Pass /// a zero to indicate you want to prevent multiple instances of triggers from this same /// entry point to be invokable. - /// Trigger should happen immediately on next opcode instead of waiting till next fixeupdate + /// Trigger should happen immediately on next opcode instead of waiting till next fixedupdate /// The list of arguments to pass to the UserDelegate when it gets called. /// A TriggerInfo structure describing this new trigger. It can be used to monitor /// the progress of the function call: To see if it has had a chance to finish executing yet, @@ -1263,7 +1263,7 @@ public TriggerInfo AddTrigger(UserDelegate del, InterruptPriority priority, int /// more than one instance of a trigger to exist for this same UserDelegate. Pass /// a zero to indicate you want to prevent multiple instances of triggers from this same /// Delegate to be invokable. - /// Trigger should happen immediately on next opcode instead of waiting till next fixeupdate + /// Trigger should happen immediately on next opcode instead of waiting till next fixedupdate /// A parms list of arguments to pass to the UserDelegate when it gets called. /// A TriggerInfo structure describing this new trigger. It can be used to monitor /// the progress of the function call: To see if it has had a chance to finish executing yet, @@ -1293,7 +1293,7 @@ public TriggerInfo AddTrigger(UserDelegate del, InterruptPriority priority, int /// If the TriggerInfo you pass in was built for a different ProgramContext than the one that /// is currently running, then this will return null and refuse to do anything. /// - /// Trigger should happen immediately on next opcode instead of waiting till next fixeupdate + /// Trigger should happen immediately on next opcode instead of waiting till next fixedupdate /// To be in agreement with how the other AddTrigger() methods work, this returns /// a TriggerInfo which is just the same one you passed in. It will return a null, however, /// in cases where the TriggerInfo you passed in is for a different ProgramContext. @@ -1771,7 +1771,7 @@ public void StopCompileStopwatch() compileWatch.Stop(); } - private class BootGlobalPath : InternalPath + protected class BootGlobalPath : InternalPath { private string command; diff --git a/src/kOS.Safe/Function/FunctionManager.cs b/src/kOS.Safe/Function/FunctionManager.cs index 2c0043fb6d..9c178acb3a 100644 --- a/src/kOS.Safe/Function/FunctionManager.cs +++ b/src/kOS.Safe/Function/FunctionManager.cs @@ -13,6 +13,8 @@ public class FunctionManager : IFunctionManager private Dictionary functions; private static readonly Dictionary rawAttributes = new Dictionary(); + public Dictionary RawFunctions => functions; + public FunctionManager(SafeSharedObjects shared) { this.shared = shared; @@ -27,11 +29,14 @@ public void Load() var type = rawAttributes[attr]; if (attr == null || type == null) continue; object functionObject = Activator.CreateInstance(type); + string longestName = attr.Names.Aggregate(string.Empty, (max, cur) => cur.Length > max.Length? cur : max); foreach (string functionName in attr.Names) { if (functionName != string.Empty) { - functions.Add(functionName, (SafeFunctionBase)functionObject); + var function = (SafeFunctionBase)functionObject; + function.FunctionName = longestName; + functions.Add(functionName, function); } } } diff --git a/src/kOS.Safe/Function/SafeFunctionBase.cs b/src/kOS.Safe/Function/SafeFunctionBase.cs index 60a8388371..39a407cddd 100644 --- a/src/kOS.Safe/Function/SafeFunctionBase.cs +++ b/src/kOS.Safe/Function/SafeFunctionBase.cs @@ -1,4 +1,4 @@ -using kOS.Safe.Compilation; +using kOS.Safe.Compilation; using kOS.Safe.Encapsulation; using kOS.Safe.Exceptions; using System; @@ -8,6 +8,8 @@ namespace kOS.Safe.Function { public abstract class SafeFunctionBase { + public string FunctionName; + /// /// ALL FUNCTIONS in kOS will always have exactly one return value. We have no /// "void" functions, to keep the execution logic consistent and simple. Therefore @@ -107,7 +109,7 @@ protected void AssertArgBottomAndConsume(SafeSharedObjects shared) if (shouldBeBottom != null && shouldBeBottom.GetType() == OpcodeCall.ArgMarkerType) return; // Assert passed. - throw new KOSArgumentMismatchException("Too many arguments were passed to " + GetFuncName()); + throw new KOSArgumentMismatchException("Too many arguments were passed to " + FunctionName); } /// @@ -145,7 +147,7 @@ protected object PopValueAssert(SafeSharedObjects shared, bool barewordOkay = fa { object returnValue = shared.Cpu.PopValueArgument(barewordOkay); if (returnValue != null && returnValue.GetType() == OpcodeCall.ArgMarkerType) - throw new KOSArgumentMismatchException("Too few arguments were passed to " + GetFuncName()); + throw new KOSArgumentMismatchException("Too few arguments were passed to " + FunctionName); return returnValue; } @@ -159,7 +161,7 @@ protected object PopStackAssert(SafeSharedObjects shared) { object returnValue = shared.Cpu.PopArgumentStack(); if (returnValue != null && returnValue.GetType() == OpcodeCall.ArgMarkerType) - throw new KOSArgumentMismatchException("Too few arguments were passed to " + GetFuncName()); + throw new KOSArgumentMismatchException("Too few arguments were passed to " + FunctionName); return returnValue; } @@ -174,22 +176,6 @@ protected Structure PopStructureAssertEncapsulated(SafeSharedObjects shared, boo return Structure.FromPrimitiveWithAssert(returnValue); } - protected string GetFuncName() - { - // The following is all just to extract the function name from the attribute. - // That really should be easier - string funcName = ""; // hopefully this cannot ever get seen by the user because of the next lines. - FunctionAttribute attr = (FunctionAttribute)GetType().GetCustomAttributes(typeof(FunctionAttribute), true).FirstOrDefault(); - if (attr != null) - { - // Of all the possible alias names, lets pick the longest one, as the most verbose description: - string longestOne = ""; - foreach (string name in attr.Names) - if (name.Length > longestOne.Length) - longestOne = name; - funcName = longestOne; - } - return funcName; - } + protected string GetFuncName() => FunctionName; } } \ No newline at end of file diff --git a/src/kOS.Safe/SafeSharedObjects.cs b/src/kOS.Safe/SafeSharedObjects.cs index a66ac36eb2..8a5c48d6de 100644 --- a/src/kOS.Safe/SafeSharedObjects.cs +++ b/src/kOS.Safe/SafeSharedObjects.cs @@ -13,8 +13,9 @@ namespace kOS.Safe public class SafeSharedObjects { public ICpu Cpu { get; set; } - public IScreenBuffer Screen { get; set; } public IInterpreter Interpreter { get; set; } + public IScreenBuffer Screen { get; set; } + public ITerminal Terminal { get; set; } public IBindingManager BindingMgr { get; set; } public Script ScriptHandler { get; set; } public ILogger Logger { get; set; } diff --git a/src/kOS.Safe/Screen/IInterpreter.cs b/src/kOS.Safe/Screen/IInterpreter.cs index 1b03597dd5..d5700d4269 100644 --- a/src/kOS.Safe/Screen/IInterpreter.cs +++ b/src/kOS.Safe/Screen/IInterpreter.cs @@ -1,15 +1,20 @@ -using kOS.Safe.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace kOS.Safe.Screen { - public interface IInterpreter : IScreenBuffer + public interface IInterpreter : IDisposable { - void Type(char ch); - bool SpecialKey(char key); - string GetCommandHistoryAbsolute(int absoluteIndex); - void SetInputLock(bool isLocked); - bool IsAtStartOfCommand(); + string Name { get; } + void Boot(); + void ProcessCommand(string commandText); + bool IsCommandComplete(string commandText); bool IsWaitingForCommand(); - void Reset(); + void BreakExecution(); + int InstructionsThisUpdate(); + int ECInstructionsThisUpdate(); } -} \ No newline at end of file +} diff --git a/src/kOS.Safe/Screen/ITerminal.cs b/src/kOS.Safe/Screen/ITerminal.cs new file mode 100644 index 0000000000..b9a8e775a7 --- /dev/null +++ b/src/kOS.Safe/Screen/ITerminal.cs @@ -0,0 +1,16 @@ +using kOS.Safe.Utilities; + +namespace kOS.Safe.Screen +{ + public interface ITerminal : IScreenBuffer + { + void Type(char ch); + bool SpecialKey(char key); + string GetCommandHistoryAbsolute(int absoluteIndex); + int GetCommandHistoryIndex(); + void SetInputLock(bool isLocked); + bool IsAtStartOfCommand(); + bool IsWaitingForCommand(); + void Reset(); + } +} \ No newline at end of file diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index 75ac6219ca..477f239b3c 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -205,6 +205,7 @@ True Resources.resx + diff --git a/src/kOS/Binding/BindingManager.cs b/src/kOS/Binding/BindingManager.cs index fab145bedd..fdda2daded 100644 --- a/src/kOS/Binding/BindingManager.cs +++ b/src/kOS/Binding/BindingManager.cs @@ -1,5 +1,6 @@ using kOS.Safe.Binding; using kOS.Safe.Utilities; +using kOS.Screen; using System; using System.Collections.Generic; using System.Linq; @@ -25,6 +26,7 @@ public class BindingManager : IBindingManager private static readonly HashSet> rawAttributes = new HashSet>(); private FlightControlManager flightControl; + public Dictionary RawVariables => variables; public BindingManager(SharedObjects shared) { @@ -81,7 +83,10 @@ public void AddBoundVariable(string name, BindingGetDlg getDelegate, BindingSetD Name = name, }; variables.Add(name, variable); - shared.Cpu.AddVariable(variable, name, false); + if (shared.Interpreter is KSInterpreter) + { + shared.Cpu.AddVariable(variable, name, false); + } } if (getDelegate != null) diff --git a/src/kOS/Binding/CPUBinding.cs b/src/kOS/Binding/CPUBinding.cs index cf5f17f1c6..9b0e72617d 100644 --- a/src/kOS/Binding/CPUBinding.cs +++ b/src/kOS/Binding/CPUBinding.cs @@ -1,3 +1,4 @@ +using kOS.Lua; using kOS.Safe.Binding; using kOS.Module; @@ -8,7 +9,14 @@ public class CPUBinding : Binding { public override void AddTo(SharedObjects shared) { - shared.BindingMgr.AddGetter("OPCODESLEFT", delegate { return kOSCustomParameters.Instance.InstructionsPerUpdate - shared.Cpu.InstructionsThisUpdate; }); + shared.BindingMgr.AddGetter("OPCODESLEFT", () => + { + if (shared.Interpreter is LuaInterpreter) + { + return kOSCustomParameters.Instance.LuaInstructionsPerUpdate - shared.Interpreter.InstructionsThisUpdate(); + } + return kOSCustomParameters.Instance.InstructionsPerUpdate - shared.Interpreter.InstructionsThisUpdate(); + }); shared.BindingMgr.MarkVolatile("OPCODESLEFT"); } } diff --git a/src/kOS/Function/Misc.cs b/src/kOS/Function/Misc.cs index eb32c6619f..422e697544 100644 --- a/src/kOS/Function/Misc.cs +++ b/src/kOS/Function/Misc.cs @@ -168,4 +168,85 @@ public override void Execute(SharedObjects shared) ReturnValue = PartModuleFieldsFactory.Construct(processor, shared); } } + + [Function("runfileon")] + public class RunFileOn : FunctionBase + { + public override void Execute(SharedObjects shared) + { + var processorTagOrVolume = PopValueAssert(shared); + var pathObject = PopValueAssert(shared); + + AssertArgBottomAndConsume(shared); + + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + VolumeFile volumeFile = volume.Open(path) as VolumeFile; + FileContent content = volumeFile != null ? volumeFile.ReadAll() : null; + + if (content == null) throw new Exception(string.Format("File '{0}' not found", path)); + + kOSProcessor processor; + + if (processorTagOrVolume is Volume) + { + processor = shared.ProcessorMgr.GetProcessor(processorTagOrVolume as Volume); + } + else if (processorTagOrVolume is string || processorTagOrVolume is StringValue) + { + processor = shared.ProcessorMgr.GetProcessor(processorTagOrVolume.ToString()); + } + else + { + throw new KOSInvalidArgumentException(FunctionName, "processorId", "String or Volume expected"); + } + + if (processor == null) + { + throw new KOSInvalidArgumentException(FunctionName, "processorId", "Processor with that volume or name was not found"); + } + + processor.RunBootFile(volumeFile); + } + } + + [Function("runcommandon")] + public class RunCommandOn : FunctionBase + { + public override void Execute(SharedObjects shared) + { + var processorTagOrVolume = PopValueAssert(shared); + var command = Structure.ToPrimitive(PopValueAssert(shared)) as string; + + AssertArgBottomAndConsume(shared); + + if (command == null) + { + throw new KOSInvalidArgumentException(FunctionName, "command", "String expected"); + } + + kOSProcessor processor; + + if (processorTagOrVolume is Volume) + { + processor = shared.ProcessorMgr.GetProcessor(processorTagOrVolume as Volume); + } + else if (processorTagOrVolume is string || processorTagOrVolume is StringValue) + { + processor = shared.ProcessorMgr.GetProcessor(processorTagOrVolume.ToString()); + } + else + { + throw new KOSInvalidArgumentException(FunctionName, "processorId", "String or Volume expected"); + } + + if (processor == null) + { + throw new KOSInvalidArgumentException(FunctionName, "processorId", + "Processor with that volume or name was not found"); + } + + processor.RunCommand(command); + } + } } diff --git a/src/kOS/KSPLogger.cs b/src/kOS/KSPLogger.cs index 9261c51d51..376b5fbc81 100644 --- a/src/kOS/KSPLogger.cs +++ b/src/kOS/KSPLogger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -130,7 +130,7 @@ private string TraceLog() else msg += "Called from "; - msg += (thisOpcode is OpcodeEOF) ? Interpreter.InterpreterName + msg += (thisOpcode is OpcodeEOF) ? Terminal.InterpreterName : BuildLocationString(thisOpcode.SourcePath, thisOpcode.SourceLine); msg += "\n" + textLine + "\n"; diff --git a/src/kOS/Lua/Binding.cs b/src/kOS/Lua/Binding.cs new file mode 100644 index 0000000000..4991ec75f2 --- /dev/null +++ b/src/kOS/Lua/Binding.cs @@ -0,0 +1,316 @@ +using KeraLua; +using kOS.Binding; +using kOS.Safe.Encapsulation; +using kOS.Safe.Function; +using kOS.Safe.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using kOS.Lua.Types; +using kOS.Suffixed; +using Debug = UnityEngine.Debug; +using TimeSpan = kOS.Suffixed.TimeSpan; + +namespace kOS.Lua +{ + public static class Binding + { + public static readonly Dictionary Bindings = new Dictionary(); + private static readonly LuaTypeBase[] luaTypes = { new KSStructure(), new KSFunction() }; + private static readonly HashSet uniqueTypes = new HashSet() + { // Types of objects that you want to create new lua instances of when pushing onto the stack, + // but that have overridden Equals and GetHashCode methods so BindingData.Objects dictionary + // picks "equal" already created object. For example "v1 = v(0,0,0); v2 = v(0,0,0); v1.x = 1; print(v2.x);" + // would print 1 because both v1 and v2 would be the same lua object, which is what we are avoiding here. + typeof(Vector), typeof(Direction), typeof(TimeSpan), typeof(TimeStamp) + }; + + // the CSharp object to userdata binding model was adapted from NLua model + // with some simplifications and changes to make it work on Structures + public class BindingData + { + public readonly Dictionary Objects = new Dictionary(); + public readonly Dictionary UserdataPtrs = new Dictionary(); + public readonly SharedObjects Shared; + + public BindingData(SharedObjects shared) + { + Shared = shared; + } + } + + public static void BindToState(KeraLua.Lua state, SharedObjects shared) + { + state = state.MainThread; + BindingChanges.Apply(shared.BindingMgr as BindingManager, shared.FunctionManager as FunctionManager); + Bindings[state.Handle] = new BindingData(shared); + + foreach (var type in luaTypes) + type.CreateMetatable(state); + + // set index and newindex metamethods on the environment table + state.PushGlobalTable(); + state.NewTable(); + state.PushCFunction(EnvIndex); + state.SetField(-2, "__index"); + state.PushCFunction(EnvNewIndex); + state.SetField(-2, "__newindex"); + state.SetMetaTable(-2); + state.Pop(1); + + // add userdataAddressToUserdata table to the registry + state.NewTable(); + state.NewTable(); + state.PushString("v"); + state.SetField(-2, "__mode"); + state.SetMetaTable(-2); + state.SetField((int)LuaRegistry.Index, "userdataAddressToUserdata"); + } + + public static int CollectObject(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + Bindings.TryGetValue(state.MainThread.Handle, out var binding); + if (binding == null) return 0; // happens after DisposeStateBinding() was called + var userdataAddress = state.ToUserData(1); + binding.Objects.TryGetValue(userdataAddress, out var obj); + if (obj == null) return 0; // read the note in PushObject() to know when this happens + binding.UserdataPtrs.Remove(obj); + binding.Objects.Remove(userdataAddress); + return 0; + } + + public static int ObjectToString(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var obj = Binding.Bindings[state.MainThread.Handle].Objects[state.ToUserData(1)]; + state.PushString(obj.ToString()); + return 1; + } + + private static int EnvIndex(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + + state.CheckType(1, LuaType.Table); + if (state.Type(2) != LuaType.String) + return 0; + + var index = state.ToString(2); + var binding = Bindings[state.MainThread.Handle]; + var isCapitalNameOnlyVariableNotCapital = BindingChanges.CapitalNameOnlyVariables.Contains(index.ToUpper()) + && index.Any(char.IsLower); + if (isCapitalNameOnlyVariableNotCapital) + return 0; + var variables = (binding.Shared.BindingMgr as BindingManager).RawVariables; + var functions = (binding.Shared.FunctionManager as FunctionManager).RawFunctions; + if (variables.TryGetValue(index, out var boundVar)) + { + return (int)Util.LuaExceptionCatch(() => + PushLuaType(state, Structure.ToPrimitive(boundVar.Value), binding), state); + } + if (functions.TryGetValue(index, out var function)) + { + return PushLuaType(state, function, binding); + } + return 0; + } + + private const string ClobberBuiltInsMessage = + "If you really want to be able to \"hide\" a built-in variable/function behind a user variable you can "+ + "\"rawset(_ENV, *variableName*, *value*)\" or allow it globally with \"config.clobberBuiltIns = true\""; + + private static int EnvNewIndex(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + + state.CheckType(1, LuaType.Table); + state.CheckAny(3); + if (state.Type(2) != LuaType.String) + { + state.RawSet(1); + return 0; + } + + var index = state.ToString(2); + + var isCapitalNameOnlyVariableNotCapital = BindingChanges.CapitalNameOnlyVariables.Contains(index.ToUpper()) + && index.Any(char.IsLower); + if (isCapitalNameOnlyVariableNotCapital) + { + state.RawSet(1); + return 0; + } + + var binding = Bindings[state.MainThread.Handle]; + var variables = (binding.Shared.BindingMgr as BindingManager).RawVariables; + if (variables.TryGetValue(index, out var boundVar)) + { + if (boundVar.Set == null) + { + if (SafeHouse.Config.AllowClobberBuiltIns) + { + state.RawSet(1); + return 0; + } + return state.Error($"Attempt to clobber a built-in kOS bound variable without a setter: {boundVar.Name}.\n"+ClobberBuiltInsMessage); + } + var newValue = ToCSharpObject(state, 3, binding); + if (newValue == null) return 0; + Util.LuaExceptionCatch(() => boundVar.Value = newValue, state); + return 0; + } + + var functions = (binding.Shared.FunctionManager as FunctionManager).RawFunctions; + if (functions.TryGetValue(index, out var function) && !SafeHouse.Config.AllowClobberBuiltIns) + { + return state.Error($"Attempt to clobber a built-in kOS function: {function.FunctionName}.\n"+ClobberBuiltInsMessage); + } + + state.RawSet(1); + return 0; + } + + public static object ToCSharpObject(KeraLua.Lua state, int index, BindingData binding = null) + { + switch (state.Type(index)) + { + case LuaType.Number: + return state.ToNumber(index); + case LuaType.String: + return state.ToString(index); + case LuaType.Boolean: + return state.ToBoolean(index); + case LuaType.UserData: + return binding?.Objects.GetValueOrDefault(state.ToUserData(index), null); + default: + return null; + } + } + + public static int PushLuaType(KeraLua.Lua state, object obj, BindingData binding) + { + switch (obj) + { + case null: + state.PushNil(); + break; + case double db: + state.PushNumber(db); + break; + case int i: + state.PushInteger(i); + break; + case string str: + state.PushString(str); + break; + case bool b: + state.PushBoolean(b); + break; + default: + { + foreach (var type in luaTypes) + if (type.BindingTypes.Any(t => t.IsInstanceOfType(obj))) + return PushObject(state, obj, binding, type.MetatableName); + state.PushNil(); + break; + } + } + return 1; + } + + private static int PushObject(KeraLua.Lua state, object obj, BindingData binding, string metatable) + { + var isUnique = uniqueTypes.Contains(obj.GetType()); + state.GetMetaTable("userdataAddressToUserdata"); + if (!isUnique && binding.UserdataPtrs.TryGetValue(obj, out IntPtr userdataAddress)) // Object already in the list of object userdata? Push the userdata + { + // Note: starting with lua5.1 the garbage collector may remove weak reference items (such as our userdataAddressToUserdata values) when the initial GC sweep + // occurs, but the actual call of the __gc finalizer for that object may not happen until a little while later. During that window we might call + // this routine and find the element missing from userdataAddressToUserdata, but CollectObject() has not yet been called. In that case, we go ahead and + // do the same thing CollectObject() does and remove from out object maps + state.PushLightUserData(userdataAddress); + if (state.RawGet(-2) != LuaType.Nil) + { // if found the objects userdata return it + state.Remove(-2); + return 1; + } + state.Remove(-1); // remove the nil value + binding.Objects.Remove(userdataAddress); // Remove from both our maps and fall out to create a new userdata + binding.UserdataPtrs.Remove(obj); + } + + userdataAddress = state.NewUserData(0); + state.GetMetaTable(metatable); + state.SetMetaTable(-2); + state.PushLightUserData(userdataAddress); + state.PushCopy(-2); + state.RawSet(-4); // add userdata on top of the stack to the userdataAddressToUserdata metatable at userdataAddress key + state.Remove(-2); + + binding.Objects[userdataAddress] = obj; + if (!isUnique) + binding.UserdataPtrs[obj] = userdataAddress; + + return 1; + } + + private static class BindingChanges + { + public static readonly HashSet CapitalNameOnlyVariables = new HashSet + { + "STEERING", + "THROTTLE", + "WHEELSTEERING", + "WHEELTHROTTLE", + "VECDRAW", + "CLEARVECDRAWS" + }; + private static readonly Renames variableRenames = new Renames() + { + {"STAGE", "STAGEINFO"}, + {"HEADING", "SHIPHEADING"} + }; + private static readonly Renames functionRenames = new Renames() + { + {"BODY", "GETBODY"} + }; + private static readonly List removeFunctions = new List() + { + "run", + "load", + "debugdump", + "profileresult", + "makebuiltindelegate", + "droppriority", + "scriptpath" + }; + + public static void Apply(BindingManager bindingManager, FunctionManager functionManager) + { + foreach (var rename in variableRenames) + { + if (!bindingManager.RawVariables.TryGetValue(rename.Key, out var variable)) continue; + bindingManager.RawVariables.Add(rename.Value, variable); + bindingManager.RawVariables.Remove(rename.Key); + } + foreach (var rename in functionRenames) + { + if (!functionManager.RawFunctions.TryGetValue(rename.Key, out var variable)) continue; + functionManager.RawFunctions.Add(rename.Value, variable); + functionManager.RawFunctions.Remove(rename.Key); + } + + foreach (var functionName in removeFunctions) + { + functionManager.RawFunctions.Remove(functionName); + } + } + + private class Renames : List> + { + public void Add(string originalName, string newName) => Add(new KeyValuePair(originalName, newName)); + } + } + } +} diff --git a/src/kOS/Lua/Libraries.cs b/src/kOS/Lua/Libraries.cs new file mode 100644 index 0000000000..11f9597bdf --- /dev/null +++ b/src/kOS/Lua/Libraries.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using KeraLua; +using Debug = UnityEngine.Debug; + +namespace kOS.Lua +{ + public static class Libraries + { + private static readonly RegList whitelistedLibraries = new RegList + { + {"_G", Libs.Base.Open}, + {"package", Libs.Package.Open}, + {"coroutine", NativeMethods.luaopen_coroutine}, + {"string", NativeMethods.luaopen_string}, + {"utf8", NativeMethods.luaopen_utf8}, + {"table", NativeMethods.luaopen_table}, + {"math", NativeMethods.luaopen_math}, + }; + + private static readonly RegList devLibraries = new RegList + { + {"debug", NativeMethods.luaopen_debug}, + {"dev", Libs.Dev.Open}, + }; + + public static void Open(KeraLua.Lua state, SharedObjects shared) + { + /* + the goal here is to remove the ability of lua scripts to be malicious and make it simpler to keep it that way + it is achieved by: + 1. Opening only whitelisted libraries + 2. Recursively removing every field in the registry table + 3. Adding whitelisted fields to the registry table + + this means that even if some potentially dangerous lua library/function gets added it would need + to be explicitly added in the whitelist to be accessible by lua scripts + */ + + // open whitelisted libraries + foreach (var library in whitelistedLibraries) + { + state.PushCFunction(library.function); + state.PushString(library.name); + state.Call(1, 1); + state.SetGlobal(library.name); + } + + // whitelist the registry table + using (var streamReader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("kOS.Lua.whitelist.lua"))) + { + state.LoadString(streamReader.ReadToEnd()); + } + state.PushCopy((int)LuaRegistry.Index); + state.PushCFunction(SetUpvalue); + state.Call(2, 0); + + state.GarbageCollector(LuaGC.Collect, 0); + + Binding.BindToState(state, shared); + + // open lua libraries + var modulesDir = + Path.Combine(new DirectoryInfo(Assembly.GetExecutingAssembly().Location).Parent.Parent.FullName, + "PluginData", "LuaModules"); + var luaModules = Directory.GetFiles(modulesDir, "*.lua", SearchOption.AllDirectories); + foreach (var luaModule in luaModules) + { + using (var streamReader = new StreamReader(luaModule)) + { + var moduleName = Path.GetFileNameWithoutExtension(luaModule); + + // call the module file + if (state.LoadString(streamReader.ReadToEnd()) != LuaStatus.OK || + state.PCall(0, 1, 0) != LuaStatus.OK) + { + shared.SoundMaker.BeginFileSound("error"); + shared.Screen.Print($"error loading module '{moduleName}':\n" + state.ToString(-1)); + state.Pop(1); + continue; + } + + // call "init" field on the module if it exists + if (state.Type(-1) == LuaType.Table) + { + if (state.GetField(-1, "init") == LuaType.Function) + { + if (state.PCall(0, 0, 0) != LuaStatus.OK) + { + shared.SoundMaker.BeginFileSound("error"); + shared.Screen.Print($"error in 'init' function of module '{moduleName}':\n" + state.ToString(-1)); + state.Pop(1); + } + } + else + { + state.Pop(1); + } + } + + // add the module to the global table and the loaded table + state.PushCopy(-1); + state.SetGlobal(moduleName); + state.GetField(LuaRegistry.Index, "_LOADED"); + state.Rotate(-2, 1); + state.SetField(-2, moduleName); + state.Pop(1); + } + } + + // open dev libraries past the whitelist if built in debug configuration + #if DEBUG + Debug.LogWarning("LUA DEV LIBRARIES OPENED"); + foreach (var library in devLibraries) + { + state.RequireF(library.name, library.function,true); + state.Pop(1); + } + #endif + } + + private static int SetUpvalue(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + state.CheckType(1, LuaType.Function); + var n = (int)state.CheckInteger(2); + state.CheckAny(3); + state.SetUpValue(1, n); + return 0; + } + } + + public class RegList : List + { + public void Add(string key, LuaFunction value) => Add(new LuaRegister { name = key, function = value }); + } +} \ No newline at end of file diff --git a/src/kOS/Lua/Libs/Base.cs b/src/kOS/Lua/Libs/Base.cs new file mode 100644 index 0000000000..8d4436529c --- /dev/null +++ b/src/kOS/Lua/Libs/Base.cs @@ -0,0 +1,233 @@ +using KeraLua; +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Execution; +using kOS.Safe.Persistence; + +namespace kOS.Lua.Libs +{ + public static class Base + { + private static readonly RegList baseLib = new RegList + { + {"dofile", DoFile}, + {"load", Load}, + {"loadfile", LoadFile}, + {"print", Print}, + {"type", Type}, + {"warn", Warn}, + {null, null} + }; + + public static int Open(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + + // temporarily store the global table on the call stack + state.PushGlobalTable(); + + // temporarily replace global table for the native open method to dump its values there + state.NewTable(); + state.RawSetInteger((int)LuaRegistry.Index, (int)LuaRegistryIndex.Globals); + + state.PushCFunction(NativeMethods.luaopen_base); + /* + LUAMOD_API int luaopen_base (lua_State *L) { + /* open lib into global table * / + lua_pushglobaltable(L); + luaL_setfuncs(L, base_funcs, 0); + /* set global _G * / + lua_pushvalue(L, -1); + lua_setfield(L, -2, LUA_GNAME); + /* set global _VERSION * / + lua_pushliteral(L, LUA_VERSION); + lua_setfield(L, -2, "_VERSION"); + return 1; + } + */ + state.Call(0, 1); + + // restore the global table + state.Rotate(-2, 1); // global table <-> luaopen_base table + state.RawSetInteger((int)LuaRegistry.Index, (int)LuaRegistryIndex.Globals); + + // create LuaRegister list with whitelisted fields from luaopen_base table + var nativeFuncs = new RegList + { + {"assert", null}, + {"collectgarbage", null}, + // {"dofile", luaB_dofile}, access to file system + {"error", null}, + {"getmetatable", null}, + {"ipairs", null}, + // {"loadfile", luaB_loadfile}, access to file system, loading binary chunks + // {"load", luaB_load}, access to file system, loading binary chunks + {"next", null}, + {"pairs", null}, + {"pcall", null}, + // {"print", luaB_print}, not used + // {"warn", luaB_warn}, not used + {"rawequal", null}, + {"rawlen", null}, + {"rawget", null}, + {"rawset", null}, + {"select", null}, + {"setmetatable", null}, + {"tonumber", null}, + {"tostring", null}, + {"type", null}, + {"xpcall", null}, + /* placeholders */ + {"_G", null}, + {"_VERSION", null}, + {null, null} + }; + for (int i = 0; i < nativeFuncs.Count - 1; i++) + { + var func = nativeFuncs[i]; + if (state.GetField(-1, func.name) == LuaType.Function) + { + nativeFuncs[i] = new LuaRegister { name = func.name, function = state.ToCFunction(-1) }; + } + state.Pop(1); + } + + // add whitelisted fields to the global table + state.PushGlobalTable(); + state.SetFuncs(nativeFuncs.ToArray(), 0); + + // add _G table + state.PushGlobalTable(); + state.SetField(-2, "_G"); + + // add _VERSION from the luaopen_base table + state.GetField(-2, "_VERSION"); + state.SetField(-2, "_VERSION"); + + // remove the luaopen_base table from the stack + state.Remove(-2); + + // save the default type function at "_type" index + state.GetField(-1, "type"); + state.SetField(-2, "_type"); + + // add new functions + state.SetFuncs(baseLib.ToArray(), 0); + + return 1; + } + + private static int Type(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + if (state.Type(1) == LuaType.UserData) + { + var obj = Binding.Bindings[state.MainThread.Handle].Objects[state.ToUserData(1)]; + if (obj is Structure structure) + { + state.PushString(structure.KOSName); + return 1; + } + } + if (state.GetMetaField(1, "__type") == LuaType.String) + return 1; + state.PushString(state.TypeName(1)); + return 1; + } + + private static int Print(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var argCount = state.GetTop(); + var prints = new string[argCount]; + for (int i = 0; i < argCount; i++) + prints[i] = state.ToString(i + 1); + Binding.Bindings[state.MainThread.Handle].Shared.Screen.Print(string.Join(" ", prints)); + return 0; + } + + private static int Warn(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var errorMessage = state.CheckString(1); + var tracebackLevel = state.OptInteger(2, 0); + if (tracebackLevel > 0) + { + state.Traceback(state, (int)tracebackLevel); + errorMessage += "\n" + state.ToString(-1); + } + var shared = Binding.Bindings[state.MainThread.Handle].Shared; + shared.SoundMaker.BeginFileSound("error"); + shared.Screen.Print(errorMessage); + return 0; + } + + private static int Load(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var chunk = state.CheckString(1); + var chunkName = state.OptString(2, "chunk"); + if (state.LoadString(chunk, chunkName) != LuaStatus.OK) + { + state.PushNil(); + state.PushCopy(-2); + return 2; + } + if (!state.IsNoneOrNil(4)) + { + state.PushCopy(4); + if (state.SetUpValue(-2, 1) == null) + state.Pop(1); + } + return 1; + } + + private static int LoadFile(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + state.CheckString(1); + var filePath = state.ToString(1); + var shared = Binding.Bindings[state.MainThread.Handle].Shared; + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(filePath); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + VolumeFile file = volume.Open(path) as VolumeFile; + if (file == null) + { + state.PushNil(); + state.PushString($"File '{filePath}' not found"); + return 2; + } + if (state.LoadString(file.ReadAll().String, file.Path.ToString()) != LuaStatus.OK) + { + state.PushNil(); + state.PushCopy(-2); + return 2; + } + if (state.GetTop() >= 4) + { // if there was a third argument set it as the _ENV value for the loaded function + state.PushCopy(3); + if (state.SetUpValue(-2, 1) == null) + state.Pop(1); + } + return 1; + } + + private static int DoFile(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var argCount = state.GetTop(); + LoadFile(L); + if (state.IsString(-1)) + return state.Error(state.ToString(-1)); + state.CallK(0, -1, argCount, DoFileContinuation); + return DoFileContinuation(L, (int)LuaStatus.OK, (IntPtr)argCount); + } + + private static int DoFileContinuation(IntPtr L, int status, IntPtr ctx) + { + var state = KeraLua.Lua.FromIntPtr(L); + var argCount = (int)ctx; + return state.GetTop()-argCount; + } + } +} \ No newline at end of file diff --git a/src/kOS/Lua/Libs/Dev.cs b/src/kOS/Lua/Libs/Dev.cs new file mode 100644 index 0000000000..f40d71b210 --- /dev/null +++ b/src/kOS/Lua/Libs/Dev.cs @@ -0,0 +1,76 @@ +using KeraLua; +using System; + +namespace kOS.Lua.Libs +{ + public static class Dev + { + private static readonly RegList devLib = new RegList + { + {"log", Log}, + {"getregistry", GetRegistry}, + {"getupvalues", GetUpvalues}, + {null, null} + }; + + public static int Open(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + state.PushGlobalTable(); + state.SetFuncs(devLib.ToArray(), 0); + return 1; + } + + private static int Log(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var logMessage = state.CheckString(1); + UnityEngine.Debug.Log(logMessage); + return 0; + } + + private static int GetRegistry(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + state.PushCopy((int)LuaRegistry.Index); + return 1; + } + + // gets a table with all upvalues on the first function argument + // that are not a key in the second optional table argument + // returns sequence table of tables with name, upvalue, index fields + private static int GetUpvalues(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + state.CheckType(1, LuaType.Function); + if (state.Type(2) == LuaType.None) + state.NewTable(); + else + state.CheckType(2, LuaType.Table); + state.NewTable(); + for (int i = 1; ; i++) + { + var uvName = state.GetUpValue(1, i); + if (uvName == null) break; + state.PushCopy(-1); + if (state.RawGet(2) == LuaType.Nil) + { + state.Pop(1); + state.NewTable(); + state.Rotate(-2, 1); + state.SetField(-2, "upvalue"); + state.PushString(uvName); + state.SetField(-2, "name"); + state.PushInteger(i); + state.SetField(-2, "index"); + state.SetInteger(3, state.Length(3)+1); + } + else + { + state.Pop(2); + } + } + return 1; + } + } +} diff --git a/src/kOS/Lua/Libs/Package.cs b/src/kOS/Lua/Libs/Package.cs new file mode 100644 index 0000000000..08de489d28 --- /dev/null +++ b/src/kOS/Lua/Libs/Package.cs @@ -0,0 +1,280 @@ +using KeraLua; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using kOS.Safe.Persistence; +using Debug = UnityEngine.Debug; + +namespace kOS.Lua.Libs +{ + public static class Package + { + private static readonly LuaFunction[] searchers = + { + PreloadSearcher, + LuaSearcher, + CSearcher, + CRootSearcher + }; + + private static readonly string osName = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "macOS" : + "Linux"; + private static readonly string modulesDirectory = + Path.Combine(new DirectoryInfo(Assembly.GetExecutingAssembly().Location).Parent.Parent.FullName, + "PluginData", "LuaModules", osName); + private static readonly string[] libraryExtensions = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new [] { "dll" } : + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? new [] { "so", "dylib", "framework" } : + new [] { "so" }; + private static readonly PackageConfig config = new PackageConfig + { + DIRSEP = VolumePath.PathSeparator, + PATHSEP = ';', + PATHMARK = '?', + EXECDIR = '!', + IGMARK = '-', + }; + // "0:/?.lua;0:/?/init.lua;/?.lua;/?/init.lua;?.lua;?/init.lua" + private static readonly string luaSearchPath = + $"0:{config.DIRSEP}{config.PATHMARK}.lua{config.PATHSEP}"+ + $"0:{config.DIRSEP}{config.PATHMARK}{config.DIRSEP}init.lua{config.PATHSEP}"+ + $"{config.DIRSEP}{config.PATHMARK}.lua{config.PATHSEP}"+ + $"{config.DIRSEP}{config.PATHMARK}{config.DIRSEP}init.lua{config.PATHSEP}"+ + $"{config.PATHMARK}.lua{config.PATHSEP}{config.PATHMARK}{config.DIRSEP}init.lua"; + private static LuaFunction _loadlib; + + public static int Open(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + + /* + Rewritten package library with limited functionality to make library safe. + Only 'require' and 'loadlib' functions are taken from the native package library. + The unsafe loadlib function is only used internally to look for C functions in a library. + functionality removed: + changing cpath, C searchers will only look in modulesDirectory + loadlib, searchpath function + */ + + // swap the global table and call luaopen_package + state.PushGlobalTable(); + state.NewTable(); + state.PushCopy(-1); + state.RawSetInteger((int)LuaRegistry.Index, (int)LuaRegistryIndex.Globals); + state.PushCFunction(NativeMethods.luaopen_package); + /* + LUAMOD_API int luaopen_package (lua_State *L) { + createclibstable(L); + luaL_newlib(L, pk_funcs); /* create 'package' table * / + createsearcherstable(L); + /* set paths * / + setpath(L, "path", LUA_PATH_VAR, LUA_PATH_DEFAULT); + setpath(L, "cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT); + /* store config information * / + lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" + LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); + lua_setfield(L, -2, "config"); + /* set field 'loaded' * / + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_setfield(L, -2, "loaded"); + /* set field 'preload' * / + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); + lua_setfield(L, -2, "preload"); + lua_pushglobaltable(L); + lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib * / + luaL_setfuncs(L, ll_funcs, 1); /* open lib into global table * / + lua_pop(L, 1); /* pop global table * / + return 1; /* return 'package' table * / + } + */ + state.Call(0, 1); + // global table, fake global table, package table + state.Insert(-3); + state.Insert(-2); + // package table, fake global table, global table + state.RawSetInteger((int)LuaRegistry.Index, (int)LuaRegistryIndex.Globals); // restore the real global table + + state.GetField(-1, "require"); + var require = state.ToCFunction(-1); + state.Pop(2); // pop require and the fake global table + + state.GetField(-1, "loadlib"); + _loadlib = state.ToCFunction(-1); + state.Pop(2); // pop loadlib and package table + + // new package table + state.NewTable(); + + // add require to the global table with a nil upvalue that later will be set to the whitelisted package table + state.PushNil(); + state.PushCClosure(require, 1); + state.SetGlobal("require"); + + // add searchers table + state.NewTable(); + var i = 1; + foreach (var searcher in searchers) + { + state.PushNil(); + state.PushCClosure(searcher, 1); + state.RawSetInteger(-2, i++); + } + state.SetField(-2, "searchers"); + + // add config + state.PushString(config.DIRSEP+"\n"+config.PATHSEP+"\n"+config.PATHMARK+"\n"+config.EXECDIR+"\n"+config.IGMARK); + state.SetField(-2, "config"); + + // add path string + state.PushString(luaSearchPath); + state.SetField(-2, "path"); + + // loaded and preload tables are getting added in whitelist.lua + + return 1; + } + + private static int PreloadSearcher(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var name = state.CheckString(1); + state.GetField(LuaRegistry.Index, "_PRELOAD"); + if (state.GetField(-1, name) == LuaType.Nil) + { + state.PushString($"no field package.preload['{name}']"); + return 1; + } + state.PushString(":preload:"); + return 2; + } + + private static int LuaSearcher(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var name = state.CheckString(1); + if (state.GetField((int)LuaRegistry.Index-1, "path") != LuaType.String) + return state.Error("'package.path' must be a string"); + var paths = state.ToString(-1); + var volumeManager = Binding.Bindings[state.MainThread.Handle].Shared.VolumeMgr; + var errorMessage = ""; + foreach(var pathTemplate in paths.Split(config.PATHSEP)) + { + var path = pathTemplate.Replace(config.PATHMARK.ToString(), name); + var globalPath = Util.LuaExceptionCatch(() => volumeManager.GlobalPathFromObject(path), state) as GlobalPath; + var file = Util.LuaExceptionCatch(() => volumeManager.GetVolumeFromPath(globalPath).Open(globalPath), state) as VolumeFile; + if (file == null) + { + errorMessage += $"no file '{path}'\n"; + continue; + } + if (state.LoadString(file.ReadAll().String, file.Path.ToString()) != LuaStatus.OK) + return state.Error($"error loading module '{name}' from file '{globalPath}':\n\t{state.ToString(-1)}"); + state.PushString(globalPath.ToString()); + return 2; + } + state.PushString(errorMessage); + return 1; + } + + private static string FindPackage(KeraLua.Lua state, string[] names) + { + var packages = new Dictionary(); + if (Directory.Exists(modulesDirectory)) + { + foreach (var packagePath in Directory.GetFiles(modulesDirectory)) + { + packages.Add(Path.GetFileName(packagePath), packagePath); + } + } + var errorMessage = ""; + foreach (var name in names) + { + foreach (var extension in libraryExtensions) + { + var fileName = name + "." + extension; + if (packages.TryGetValue(fileName, out var packagePath)) + { + return packagePath; + } + errorMessage += $"no file '{Path.Combine(modulesDirectory, fileName)}'\n"; + } + } + state.PushString(errorMessage); + return null; + } + + private static int LoadFunc(KeraLua.Lua state, string filename, string name) + { + var modname = name.Replace('.', '_'); + if (modname.Contains(config.IGMARK.ToString())) + { + var stat = LookForFunc(state, filename, "luaopen_" + modname.Split(config.IGMARK)[0]); + if (stat != 2) return stat; + return LookForFunc(state, filename, "luaopen_" + modname.Split(config.IGMARK)[1]); + } + return LookForFunc(state, filename, "luaopen_" + modname); + } + + private static int LookForFunc(KeraLua.Lua state, string filename, string sym) + { + var precallTop = state.GetTop(); + state.PushCFunction(_loadlib); + state.PushString(filename); + state.PushString(sym); + state.Call(2, -1); + var stat = 0; + if (state.GetTop() - precallTop == 3) // if error + { + stat = state.ToString(-1) == "open" ? 1 : 2; + state.Pop(1); + state.Remove(-2); + } + return stat; + } + + private static int CSearcher(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var name = state.CheckString(1); + var filename = FindPackage(state, new [] { name, "loadall" }); + if (filename == null) return 1; + var success = 0 == LoadFunc(state, filename, name); + if (!success) + return state.Error($"error loading module '{name}' from file '{filename}':\n\t{state.ToString(-1)}"); + state.PushString(filename); + return 2; + } + + private static int CRootSearcher(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var name = state.CheckString(1); + if (!name.Contains(".")) return 0; + var filename = FindPackage(state, new [] { name.Split('.')[0], "loadall" }); + if (filename == null) return 1; + var status = LoadFunc(state, filename, name); + if (status == 1) + return state.Error($"error loading module '{name}' from file '{filename}':\n\t{state.ToString(-1)}"); + if (status == 2) + { + state.PushString($"no module '{name}' in file '{name.Split('.')[0]}'"); + return 1; + } + state.PushString(filename); + return 2; + } + + private struct PackageConfig + { + public char DIRSEP; + public char PATHSEP; + public char PATHMARK; + public char EXECDIR; + public char IGMARK; + } + } +} \ No newline at end of file diff --git a/src/kOS/Lua/Libs/callbacks.lua b/src/kOS/Lua/Libs/callbacks.lua new file mode 100644 index 0000000000..d0a2dc6450 --- /dev/null +++ b/src/kOS/Lua/Libs/callbacks.lua @@ -0,0 +1,233 @@ +local M = {} + +function M.init() + ---Controls how kOS should steer the ship. + ---Options: + ---- `function` that returns one of the options listed below. + ---- `Direction` the ship should point towards. + ---- `Vector` the ship should point towards. Like `Direction`, but without the roll component + ---- `string` "kill". The ship will try to kill all its angular velocity + ---@type function | Direction | Vector | Node | "kill" + steering = nil + ---Controls the throttle value of the ship. Can be a number or a function returning a number. + ---The number is expected to be between 0 and 1. + ---@type function | number + throttle = nil + ---Options: + ---- `function` that returns one of the options listed below. + ---- `number`. Compass heading the ship should steer towards. + ---- `GeoCoordinates` the ship should steer towards. + ---- `Vessel` the ship should steer towards. + ---@type function | number | GeoCoordinates | Vessel + wheelsteering = nil + ---Controls the wheel throttle value of the ship. Can be a number or a function returning a number. + ---The number is expected to be between 0 and 1. + ---@type function | number + wheelthrottle = nil + + M.breakcontrol() + M.fixedupdatecallbacks = {} + M.updatecallbacks = {} + + fixedupdate = M.fixedupdate + update = M.update + breakexecution = M.breakexecution + addcallback = M.addcallback + when = M.when + on = M.on +end + +function M.kill() + fixedupdate = nil + update = nil + M.breakcontrol() + M.fixedupdatecallbacks = {} + M.updatecallbacks = {} +end + +function M.fixedupdate() + M.runcontrol() + M.runcallbacks(M.fixedupdatecallbacks) +end + +function M.update() + M.runcallbacks(M.updatecallbacks) +end + +function M.breakexecution() + M.breakcontrol() + M.fixedupdatecallbacks = {} + M.updatecallbacks = {} +end + +---A table with a finalizer that gets called when the lua state is disposed(on shutdown, reboot). +---Makes the core automatically "let go" of the controls. +M.finalizer = setmetatable({}, { __gc = function() + toggleflybywire("steering", false) + toggleflybywire("throttle", false) + toggleflybywire("wheelsteering", false) + toggleflybywire("wheelthrottle", false) +end}) + +function M.breakcontrol() + steering, throttle, wheelsteering, wheelthrottle = nil, nil, nil, nil + M.controlled = {} +end + +function M.runcontrol() + if M.controlcoroutine then coroutine.resume(M.controlcoroutine) end + M.controlcoroutine = coroutine.create(M.processcontrol) + coroutine.resume(M.controlcoroutine) +end + +function M.processcontrol() + if steering then + M.controlled.steering = true + local success, error = pcall(function() STEERING = type(steering) == "function" and steering() or steering end) + if not success then + warn(error, 1) + steering = nil + end + elseif M.controlled.steering then + M.controlled.steering = false + toggleflybywire("steering", false) + end + if throttle then + M.controlled.throttle = true + local success, error = pcall(function() THROTTLE = type(throttle) == "function" and throttle() or throttle end) + if not success then + warn(error, 1) + throttle = nil + end + elseif M.controlled.throttle then + M.controlled.throttle = false + toggleflybywire("throttle", false) + end + if wheelsteering then + M.controlled.wheelsteering = true + local success, error = pcall(function() WHEELSTEERING = type(wheelsteering) == "function" and wheelsteering() or wheelsteering end) + if not success then + warn(error, 1) + wheelsteering = nil + end + elseif M.controlled.wheelsteering then + M.controlled.wheelsteering = false + toggleflybywire("wheelsteering", false) + end + if wheelthrottle then + M.controlled.wheelthrottle = true + local success, error = pcall(function() WHEELTHROTTLE = type(wheelthrottle) == "function" and wheelthrottle() or wheelthrottle end) + if not success then + warn(error, 1) + wheelthrottle = nil + end + elseif M.controlled.wheelthrottle then + M.controlled.wheelthrottle = false + toggleflybywire("wheelthrottle", false) + end + M.controlcoroutine = nil +end + +function M.runcallbacks(callbacks) + if callbacks.continuation then + coroutine.resume(callbacks.continuation) + end + if callbacks.unsorted then + callbacks.continuation = coroutine.create(function() + table.sort(callbacks, function(a, b) + return a.priority == b.priority and a.creationTime < b.creationTime or a.priority < b.priority + end) + callbacks.unsorted = false + callbacks.continuation = nil + end) + coroutine.resume(callbacks.continuation) + end + for i=#callbacks,1,-1 do + local callback = callbacks[i] + if callback.coroutine then + coroutine.resume(callback.coroutine, callback) + else + if callback.body then + callback.coroutine = coroutine.create(callback.body) + coroutine.resume(callback.coroutine, callback) + else + table.remove(callbacks, i) + end + end + end +end + +---@param body function : +---Callback function body to get executed on the next physics tick(or the next frame, see the third parameter). +---If returns a number or `true` the callback doesn't get cleared. +---If returns a number this number will be used as the callback priority, see the second parameter. +---@param priority? number : +---Callback priority. Callbacks with highest priorities get executed first. Default is 0. +---@param callbacks? table : +---Callbacks table where to add the callback to. +---Options: +---- `callbacks.fixedupdatecallbacks`: gets executed each physics tick. Default. +---- `callbacks.updatecallbacks`: gets executed each frame. +function M.addcallback(body, priority, callbacks) + callbacks = callbacks or M.fixedupdatecallbacks + local callback = { + body = function(callback) + local success, newPriority = pcall(body, callback) + if not(newPriority == true or newPriority == callback.priority) then + if success then + callback.priority = tonumber(newPriority) + if callback.priority then + callbacks.unsorted = true + else + callback.body = nil + end + else + warn("error in callback:\n" .. (newPriority == nil and "" or tostring(newPriority))) + callback.body = nil + end + end + callback.coroutine = nil + end, + priority = priority or 0, + creationTime = time.seconds + kuniverse.timewarp.physicsdeltat * (config.luaipu - opcodesleft) / config.luaipu + } + table.insert(callbacks, callback) + callbacks.unsorted = true + return callback +end + +---"When" trigger implemented as a wrapper around the `addcallback` function. +---@param condition function The callback executes only if this function returns a true value +---@param body function The same as in `addcallback` function +---@param priority? number The same as in `addcallback` function +---@param callbacks? table The same as in `addcallback` function +function M.when(condition, body, priority, callbacks) + return M.addcallback(function (callback) + if condition() then + return body() + else + return callback.priority + end + end, priority, callbacks) +end + +---"On" trigger implemented as a wrapper around the `addcallback` function. +---@param state function The callback executes only if this function returns a value that is not equal to the value it returned previously +---@param body function The same as in `addcallback` function +---@param priority? number The same as in `addcallback` function +---@param callbacks? table The same as in `addcallback` function +function M.on(state, body, priority, callbacks) + local previousState = state() + return M.addcallback(function (callback) + local currentState = state() + if currentState ~= previousState then + local newPriority = body() + previousState = currentState + return newPriority + else + return callback.priority + end + end, priority, callbacks) +end + +return M \ No newline at end of file diff --git a/src/kOS/Lua/Libs/misc.lua b/src/kOS/Lua/Libs/misc.lua new file mode 100644 index 0000000000..4fa1cc7d5e --- /dev/null +++ b/src/kOS/Lua/Libs/misc.lua @@ -0,0 +1,135 @@ +local M = {} + +function M.init() + wait = M.wait + waituntil = M.waituntil + vecdraw = M.vecdraw + clearvecdraws = M.clearvecdraws + + M.vecdraws = setmetatable({}, { __mode = "v" }) + M.updatingvecdraws = setmetatable({}, { __mode = "v" }) + + ---@class CJson + ---@field encode fun(table): string encodes a `table` into a json `string` + ---@field decode fun(string): table decodes a json `string` into a `table` + + ---[openrestry/lua-cjson module](https://github.com/openresty/lua-cjson/tree/2.1.0.10rc1). + ---Not all keys are annotated. Complete documentation is available at the link. + ---@type CJson + json = select(2, pcall(require, "cjson")) +end + +---Suspends the execution for the specified amount of time. +---Any call to this function will suspend execution for at least one physics tick. +---This function is a simple abstraction made to achieve the same effect as the kerboscript `wait *number*.` command. +---One difference is it only suspends the execution of the coroutine it was called from, making it totally fine to use inside callbacks. +function M.wait(seconds) + local waitEnd = time.seconds + seconds + coroutine.yield() + while time.seconds < waitEnd do coroutine.yield() end +end + +---Suspends the execution until the `condition` function returns a true value. +---This function is a simple abstraction made to achieve the same effect as the kerboscript `wait until *condition*.` command. +---One difference is it only suspends the execution of the coroutine it was called from, making it totally fine to use inside callbacks. +---@param condition function +function M.waituntil(condition) + while not condition() do coroutine.yield() end +end + +---A wrapper around kOS `CLEARVECDRAWS` function that also clears vecdraws created with the `vecdraw` function +function M.clearvecdraws() + CLEARVECDRAWS() + for _,vd in ipairs(M.vecdraws) do + vd.parameters.show = false + end + M.updatingvecdraws = setmetatable({}, { __mode = "v" }) +end + +local vecdrawmt = { + __index = function(vd, index) + return vd.parameters[index] == nil and vd.structure[index] or vd.parameters[index] + end, + __newindex = function(vd, index, value) + local parameters = vd.parameters + if type(value) ~= "function" then vd.structure[index] = value end + if parameters[index] == nil then return end + parameters[index] = value + + if index == "start" then vd.isStartFunction = type(value) == "function" + elseif index == "vector" then vd.isVectorFunction = type(value) == "function" end + + local vdShouldBeUpdating = parameters.show and (vd.isStartFunction or vd.isVectorFunction) + + if not vdShouldBeUpdating and vd.updating then + vd.updating = false + for i,v in ipairs(M.updatingvecdraws) do + if v == vd then + table.remove(M.updatingvecdraws, i) + if #M.updatingvecdraws == 0 then + M.updatingvecdraws.callback.body = nil + end + break + end + end + elseif vdShouldBeUpdating and not vd.updating then + vd.updating = true + table.insert(M.updatingvecdraws, vd) + if #M.updatingvecdraws == 1 then + M.updatingvecdraws.callback = addcallback(function() + for _, vd in ipairs(M.updatingvecdraws) do + if vd.isStartFunction then vd.structure.start = vd.parameters.start() end + if vd.isVectorFunction then vd.structure.vector = vd.parameters.vector() end + end + return true + end, 0, callbacks.updatecallbacks) + end + end + end, + __gc = function(vd) vd.show = false end, +} + +---@class VecdrawTable : Vecdraw +---@field structure Vecdraw +---@field parameters table + +---Wrapper around kOS `VECDRAW` function that uses the callbacks library to automatically update the "start" and "vector". +---Those parameters can accept functions, in which case their values will be changed each frame with the return value of the functions. +---This function returns a table representing a Vecdraw structure, and when this table gets garbage collected the vecdraw is removed. +---``` +---vd = vecdraw(nil, mun.position) -- assign the return value to a variable to keep it from being collected +---vd.show = true +---vd = nil -- this will remove the vecdraw by garbage collection +---``` +---@param start? Vector | function `Vector` in ship-raw reference frame where the `Vecdraw` will be drawn from. +---@param vector? Vector | function absolute `Vector` position where the `Vecdraw` should end. +---@param color? RGBA +---@param label? string +---@param scale? number +---@param show? boolean +---@param width? number +---@param pointy? boolean +---@param wiping? boolean +---@return VecdrawTable +---@nodiscard +function M.vecdraw(start, vector, color, label, scale, show, width, pointy, wiping) + local vd = { + structure = VECDRAW(v(0,0,0), v(0,0,0), color or white, label or "", scale or 1, show ~= nil and show, width or 0.2, pointy == nil or pointy, wiping == nil or wiping), + isStartFunction = false, + isVectorFunction = false, + updating = false, + parameters = { + start = v(0,0,0), + vector = v(0,0,0), + show = false, + } + } + setmetatable(vd, vecdrawmt) + table.insert(M.vecdraws, vd) + if start then vd.start = start end + if vector then vd.vector = vector end + if show then vd.show = show end + return vd +end + +return M diff --git a/src/kOS/Lua/LuaCPU.cs b/src/kOS/Lua/LuaCPU.cs new file mode 100644 index 0000000000..83993a8871 --- /dev/null +++ b/src/kOS/Lua/LuaCPU.cs @@ -0,0 +1,71 @@ +using kOS.Safe; +using kOS.Safe.Compilation; +using kOS.Safe.Execution; +using kOS.Safe.Utilities; +using kOS.Safe.Binding; +using kOS.Safe.Callback; +using kOS.Safe.Encapsulation; +using kOS.Safe.Exceptions; +using kOS.Safe.Persistence; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using kOS.Module; + +namespace kOS.Lua +{ + public class LuaCPU : CPU + { + public IStack Stack => stack; + + public new bool IsYielding() => base.IsYielding(); + + public LuaCPU(SafeSharedObjects shared) : base(shared) { } + + // difference from base Boot method: + // not adding a fixed observer + // changed boot message + // not running a boot script. its done in LuaInterpreter + public override void Boot() + { + // break all running programs + currentContext = null; + contexts.Clear(); + if (shared.GameEventDispatchManager != null) shared.GameEventDispatchManager.Clear(); + PushInterpreterContext(); + CurrentPriority = InterruptPriority.Normal; + currentTime = 0; + // clear stack (which also orphans all local variables so they can get garbage collected) + stack.Clear(); + // clear global variables + globalVariables.Clear(); + // clear interpreter + if (shared.Terminal != null) shared.Terminal.Reset(); + // load functions + if (shared.FunctionManager != null) shared.FunctionManager.Load(); + // load bindings + if (shared.BindingMgr != null) shared.BindingMgr.Load(); + + // Booting message + if (shared.Screen != null) + { + shared.Screen.ClearScreen(); + string bootMessage = string.Format("kOS Operating System\nLua v{0}\n(manual at {1})\n \nProceed.\n", LuaInterpreter.LuaVersion, SafeHouse.DocumentationURL); + shared.Screen.Print(bootMessage); + } + } + + // this is called from LuaInterpreter KOSFixedUpdate to keep one FixedUpdate function per interpreter and to keep order consistency + public void FixedUpdate() + { + currentTime = shared.UpdateHandler.CurrentFixedTime; + } + + public override Opcode GetCurrentOpcode() + { + return new OpcodeBogus(); + } + } +} diff --git a/src/kOS/Lua/LuaInterpreter.cs b/src/kOS/Lua/LuaInterpreter.cs new file mode 100644 index 0000000000..6178404cf2 --- /dev/null +++ b/src/kOS/Lua/LuaInterpreter.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Text; +using KeraLua; +using kOS.Safe; +using kOS.Safe.Utilities; +using kOS.Safe.Screen; +using kOS.Safe.Persistence; + +namespace kOS.Lua +{ + public class LuaInterpreter : IInterpreter, IFixedUpdateObserver, IUpdateObserver + { + public const string LuaVersion = "5.4"; + public static readonly string[] FilenameExtensions = { "lua" }; + public string Name => "lua"; + private SharedObjects Shared { get; } + private KeraLua.Lua state; + private KeraLua.Lua commandCoroutine; + private KeraLua.Lua fixedUpdateCoroutine; + private KeraLua.Lua updateCoroutine; + /// Relevant information for the instruction hook meant to be accessed with the lua state handle in a static context + private static readonly Dictionary stateHookInfo = new Dictionary(); + private readonly Queue commandsQueue = new Queue(); + private int breakExecutionCount; + private int? idleInstructions; + private int fixedupdateInstructions; + + private class CommandInfo + { + public string Command; + public string ChunkName; + public CommandInfo(string command, string chunkName) + { + Command = command; + ChunkName = chunkName; + } + } + + private class InstructionHookInfo + { + public int InstructionsPerUpdate; + public int InstructionsThisUpdate; + public bool BreakExecution; + public readonly SharedObjects Shared; + public InstructionHookInfo(SharedObjects shared) + { + Shared = shared; + } + } + + public LuaInterpreter(SharedObjects shared) + { + Shared = shared; + } + + public void Boot() + { + Dispose(); + Shared.UpdateHandler.AddFixedObserver(this); + Shared.UpdateHandler.AddObserver(this); + state = new KeraLua.Lua(false); + state.Encoding = Encoding.UTF8; + + commandCoroutine = state.NewThread(); + fixedUpdateCoroutine = state.NewThread(); + updateCoroutine = state.NewThread(); + commandCoroutine.SetHook(AfterEveryInstructionHook, LuaHookMask.Count, 1); + fixedUpdateCoroutine.SetHook(AfterEveryInstructionHook, LuaHookMask.Count, 1); + updateCoroutine.SetHook(AfterEveryInstructionHook, LuaHookMask.Count, 1); + stateHookInfo.Add(state.MainThread.Handle, new InstructionHookInfo(Shared)); + + Libraries.Open(state, Shared); + + if (!Shared.Processor.CheckCanBoot()) return; + + VolumePath path = Shared.Processor.BootFilePath; + // Check to make sure the boot file name is valid, and then that the boot file exists. + if (path == null) + { + SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); + } + else + { + // Boot is only called once right after turning the processor on, + // the volume cannot yet have been changed from that set based on + // Config.StartOnArchive, and Processor.CheckCanBoot() has already + // handled the range check for the archive. + var file = Shared.VolumeMgr.CurrentVolume.Open(path) as VolumeFile; + if (file == null) + { + SafeHouse.Logger.Log(string.Format("Boot file \"{0}\" is missing, skipping boot script", path)); + } + else + { + ProcessCommand($"dofile(\"{file.Path}\")", "boot"); + } + } + } + + private static void AfterEveryInstructionHook(IntPtr L, IntPtr ar) + { + var state = KeraLua.Lua.FromIntPtr(L); + var hookInfo = stateHookInfo[state.MainThread.Handle]; + if (++hookInfo.InstructionsThisUpdate >= hookInfo.InstructionsPerUpdate || hookInfo.BreakExecution + || (hookInfo.Shared.Cpu as LuaCPU).IsYielding()) + { + // it's possible for a C/CSharp function to call lua making a coroutine unable to yield because + // of the "C-call boundary". + if (state.IsYieldable) + state.Yield(0); + } + } + + public void ProcessCommand(string commandText) => ProcessCommand(commandText, "command"); + + private void ProcessCommand(string commandText, string commandName) + { + if (state == null) return; + commandsQueue.Enqueue(new CommandInfo(commandText, commandName)); + } + + private bool LoadCommand(CommandInfo commandInfo) + { + if ((LuaStatus)commandCoroutine.ResetThread() != LuaStatus.OK || commandCoroutine.LoadString(commandInfo.Command, commandInfo.ChunkName) != LuaStatus.OK) + { + var err = commandCoroutine.ToString(-1); + commandCoroutine.Pop(1); + DisplayError(err); + return false; + } + return true; + } + + public bool IsCommandComplete(string commandText) + { + if (commandCoroutine.LoadString(commandText) == LuaStatus.ErrSyntax) + { + var err = commandCoroutine.ToString(-1); + commandCoroutine.Pop(1); + // if the error is not caused by leaving stuff like do('"{ open let it go to ProcessCommand to be displayed to the user + return !err.EndsWith(""); + } + commandCoroutine.Pop(1); + return true; + } + + public bool IsWaitingForCommand() + { + return !(Shared.Cpu as LuaCPU).IsYielding() && commandCoroutine.Status != LuaStatus.Yield + && fixedupdateInstructions < stateHookInfo[state.MainThread.Handle].InstructionsPerUpdate; + } + + public void BreakExecution() + { + if (state == null) return; + var hookInfo = stateHookInfo[state.MainThread.Handle]; + hookInfo.BreakExecution = true; + breakExecutionCount++; + } + + public int InstructionsThisUpdate() + { + if (state != null && stateHookInfo.TryGetValue(state.MainThread.Handle, out var hookInfo)) + return hookInfo.InstructionsThisUpdate; + return 0; + } + + public int ECInstructionsThisUpdate() + { + if (state != null && stateHookInfo.TryGetValue(state.MainThread.Handle, out var hookInfo)) + return Math.Max(hookInfo.InstructionsThisUpdate - idleInstructions ?? 0, 0); + return 0; + } + + public void KOSFixedUpdate(double dt) + { + (Shared.Cpu as LuaCPU).FixedUpdate(); + Shared.BindingMgr?.PreUpdate(); + + var hookInfo = stateHookInfo[commandCoroutine.MainThread.Handle]; + hookInfo.InstructionsPerUpdate = SafeHouse.Config.LuaInstructionsPerUpdate; + hookInfo.InstructionsThisUpdate = 0; + + if (hookInfo.BreakExecution) + { // true after BreakExecution was called, reset thread to prevent execution of the same program + hookInfo.BreakExecution = false; + commandsQueue.Clear(); + commandCoroutine.ResetThread(); + fixedUpdateCoroutine.ResetThread(); + updateCoroutine.ResetThread(); + if (Util.RawGetGlobal(fixedUpdateCoroutine, "breakexecution") == LuaType.Function) + { + var status = fixedUpdateCoroutine.Resume(state, 0); + if (status != LuaStatus.OK && status != LuaStatus.Yield) + { + DisplayError(fixedUpdateCoroutine.ToString(-1), fixedUpdateCoroutine); + fixedUpdateCoroutine.Pop(1); + } + } + else fixedUpdateCoroutine.Pop(1); + } + + fixedUpdateCoroutine.ResetThread(); + if (Util.RawGetGlobal(fixedUpdateCoroutine, "fixedupdate") == LuaType.Function && !(Shared.Cpu as LuaCPU).IsYielding()) + { + fixedUpdateCoroutine.PushNumber(dt); + var status = fixedUpdateCoroutine.Resume(state, 1); + if (status != LuaStatus.OK && status != LuaStatus.Yield) + { + DisplayError(fixedUpdateCoroutine.ToString(-1) + +"\nfixedupdate function errored and was set to nil." + +"\nTo reset fixedupdate do 'fixedupdate = callbacks.fixedupdate'.", fixedUpdateCoroutine); + fixedUpdateCoroutine.Pop(1); + fixedUpdateCoroutine.PushNil(); + fixedUpdateCoroutine.SetGlobal("fixedupdate"); + } + } + else fixedUpdateCoroutine.Pop(1); + + fixedupdateInstructions = hookInfo.InstructionsThisUpdate; + + if (idleInstructions == null) + idleInstructions = hookInfo.InstructionsThisUpdate; + + if (hookInfo.InstructionsThisUpdate < hookInfo.InstructionsPerUpdate) + breakExecutionCount = 0; + + if (breakExecutionCount >= 3) + { + Shared.SoundMaker.BeginFileSound("beep"); + Shared.Screen.Print("Ctrl+C was pressed 3 times while the processor was using all of the available instructions so "+ + "fixedupdate function was set to nil. To reset the default function do:\n\"callbacks.init()\"."); + state.PushNil(); + state.SetGlobal("fixedupdate"); + breakExecutionCount = 0; + } + + if (hookInfo.InstructionsThisUpdate < hookInfo.InstructionsPerUpdate && !(Shared.Cpu as LuaCPU).IsYielding()) + { + // resumes the coroutine after it yielded due to running out of instructions + // and/or executes queued commands until they run out or the coroutine yields + while (commandCoroutine.Status == LuaStatus.Yield || commandsQueue.Count > 0) + { + if (commandCoroutine.Status == LuaStatus.Yield || LoadCommand(commandsQueue.Dequeue())) + { + var status = commandCoroutine.Resume(state, 0); + if (status == LuaStatus.Yield) break; + if (status != LuaStatus.OK) + { + DisplayError(commandCoroutine.ToString(-1), commandCoroutine); + commandCoroutine.ResetThread(); + } + } + } + } + } + + public void KOSUpdate(double dt) + { + if (dt == 0) return; // don't run when the game is paused + var hookInfo = stateHookInfo[commandCoroutine.MainThread.Handle]; + + if (hookInfo.InstructionsThisUpdate >= hookInfo.InstructionsPerUpdate) return; + + updateCoroutine.ResetThread(); + if (Util.RawGetGlobal(updateCoroutine, "update") == LuaType.Function && !(Shared.Cpu as LuaCPU).IsYielding()) + { + updateCoroutine.PushNumber(dt); + var status = updateCoroutine.Resume(state, 1); + if (status != LuaStatus.OK && status != LuaStatus.Yield) + { + DisplayError(updateCoroutine.ToString(-1) + +"\nupdate function errored and was set to nil." + +"\nTo reset update do 'update = callbacks.update'.", updateCoroutine); + updateCoroutine.Pop(1); + updateCoroutine.PushNil(); + updateCoroutine.SetGlobal("update"); + } + } + else updateCoroutine.Pop(1); + } + + public void Dispose() + { + if (state == null) return; + Shared.UpdateHandler.RemoveFixedObserver(this); + Shared.UpdateHandler.RemoveObserver(this); + var stateHandle = state.MainThread.Handle; + state.Dispose(); + stateHookInfo.Remove(stateHandle); + Binding.Bindings.Remove(stateHandle); + state = null; + commandsQueue.Clear(); + breakExecutionCount = 0; + idleInstructions = null; + } + + private void DisplayError(string errorMessage, KeraLua.Lua state = null) + { + if (state != null) + { + state.Traceback(state); + errorMessage += "\n" + state.ToString(-1); + } + Shared.Logger.Log("lua error: "+errorMessage); + Shared.SoundMaker.BeginFileSound("error"); + Shared.Screen.Print(errorMessage); + } + } +} diff --git a/src/kOS/Lua/NativeMethods.cs b/src/kOS/Lua/NativeMethods.cs new file mode 100644 index 0000000000..ed25312f43 --- /dev/null +++ b/src/kOS/Lua/NativeMethods.cs @@ -0,0 +1,50 @@ +using System.Runtime.InteropServices; +using lua_State = System.IntPtr; + +namespace kOS.Lua +{ + internal static class NativeMethods + { + #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__ + private const string LuaLibraryName = "@rpath/liblua54.framework/liblua54"; + #elif __ANDROID__ + private const string LuaLibraryName = "liblua54.so"; + #elif __MACOS__ + private const string LuaLibraryName = "liblua54.dylib"; + #elif WINDOWS_UWP + private const string LuaLibraryName = "lua54.dll"; + #else + private const string LuaLibraryName = "lua54"; + #endif + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_base(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_package(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_coroutine(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_table(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_io(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_os(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_string(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_math(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_utf8(lua_State luaState); + + [DllImport(LuaLibraryName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int luaopen_debug(lua_State luaState); + } +} \ No newline at end of file diff --git a/src/kOS/Lua/Types/KSFunction.cs b/src/kOS/Lua/Types/KSFunction.cs new file mode 100644 index 0000000000..7d4ad79f16 --- /dev/null +++ b/src/kOS/Lua/Types/KSFunction.cs @@ -0,0 +1,60 @@ +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Execution; +using kOS.Safe.Function; +using Debug = UnityEngine.Debug; + +namespace kOS.Lua.Types +{ + public class KSFunction : LuaTypeBase + { + private static readonly Type[] bindingTypes = { typeof(SafeFunctionBase), typeof(DelegateSuffixResult) }; + private static readonly string metatableName = "KerboscriptFunction"; + public override string MetatableName => metatableName; + public override Type[] BindingTypes => bindingTypes; + + public override void CreateMetatable(KeraLua.Lua state) + { + state.NewMetaTable(metatableName); + state.PushString(metatableName); + state.SetField(-2, "__type"); + AddMethod(state, "__call", KSFunctionCall); + AddMethod(state, "__gc", Binding.CollectObject); + AddMethod(state, "__tostring", Binding.ObjectToString); + state.Pop(1); + } + + private static int KSFunctionCall(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + state.CheckUserData(1, metatableName); + var ksFunction = binding.Objects[state.ToUserData(1)]; + + var stack = (binding.Shared.Cpu as LuaCPU).Stack; + stack.Clear(); + stack.PushArgument(new KOSArgMarkerType()); + for (int i = 2; i <= state.GetTop(); i++) + { + var arg = Binding.ToCSharpObject(state, i, binding); + var structure = Structure.FromPrimitive(arg) as Structure; + if (structure == null) + state.Error($"Cannot cast argument #{i-1} of type {(arg == null? "null" : arg.GetType().ToString())} to Structure."); + Util.LuaExceptionCatch(() => stack.PushArgument(structure), state); + } + + if (ksFunction is SafeFunctionBase function) + { + Util.LuaExceptionCatch(() => function.Execute(binding.Shared), state); + return Binding.PushLuaType(state, Structure.ToPrimitive(function.ReturnValue), binding); + } + if (ksFunction is DelegateSuffixResult delegateResult) + { + Util.LuaExceptionCatch(() => delegateResult.Invoke(binding.Shared.Cpu), state); + return Binding.PushLuaType(state, Structure.ToPrimitive(delegateResult.Value), binding); + } + return state.Error(string.Format("attempt to call a non function {0} value", ksFunction.GetType().Name)); + } + } +} diff --git a/src/kOS/Lua/Types/KSStructure.cs b/src/kOS/Lua/Types/KSStructure.cs new file mode 100644 index 0000000000..3c70a47419 --- /dev/null +++ b/src/kOS/Lua/Types/KSStructure.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using KeraLua; +using kOS.Safe.Compilation; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Exceptions; +using kOS.Safe.Execution; +using kOS.Safe.Function; +using Debug = UnityEngine.Debug; + +namespace kOS.Lua.Types +{ + public class KSStructure : LuaTypeBase + { + private static readonly CalculatorStructure structureCalculator = new CalculatorStructure(); + private static readonly string metatableName = "Structure"; + private static readonly Type[] bindingTypes = { typeof(Structure) }; + public override string MetatableName => metatableName; + public override Type[] BindingTypes => bindingTypes; + + public override void CreateMetatable(KeraLua.Lua state) + { + state.NewMetaTable(metatableName); + state.PushString(metatableName); + state.SetField(-2, "__type"); + AddMethod(state, "__index", StructureIndex); + AddMethod(state, "__newindex", StructureNewIndex); + AddMethod(state, "__pairs", StructurePairs); + AddMethod(state, "__gc", Binding.CollectObject); + AddMethod(state, "__len", StructureLength); + AddMethod(state, "__tostring", StructureToString); + AddMethod(state, "__add", StructureAdd); + AddMethod(state, "__sub", StructureSubtract); + AddMethod(state, "__mul", StructureMultiply); + AddMethod(state, "__div", StructureDivide); + AddMethod(state, "__pow", StructurePower); + AddMethod(state, "__unm", StructureUnary); + AddMethod(state, "__eq", StructureEqual); + AddMethod(state, "__lt", StructureLessThan); + AddMethod(state, "__le", StructureLessEqualThan); + state.Pop(1); + } + + private static int StructureAdd(IntPtr L) => StructureOperator(L, structureCalculator.Add); + private static int StructureSubtract(IntPtr L) => StructureOperator(L, structureCalculator.Subtract); + private static int StructureMultiply(IntPtr L) => StructureOperator(L, structureCalculator.Multiply); + private static int StructureDivide(IntPtr L) => StructureOperator(L, structureCalculator.Divide); + private static int StructurePower(IntPtr L) => StructureOperator(L, structureCalculator.Power); + private static int StructureEqual(IntPtr L) => StructureOperator(L, structureCalculator.Equal); + private static int StructureLessThan(IntPtr L) => StructureOperator(L, structureCalculator.LessThan); + private static int StructureLessEqualThan(IntPtr L) => StructureOperator(L, structureCalculator.LessThanEqual); + + private static int StructureOperator(IntPtr L, Func operatorMethod) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + var pair = new OperandPair(Binding.ToCSharpObject(state, 1, binding), Binding.ToCSharpObject(state, 2, binding)); + return (int)Util.LuaExceptionCatch(() => Binding.PushLuaType(state, operatorMethod(pair), binding), state); + } + + private static int StructureUnary(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + var obj = Binding.ToCSharpObject(state, 1, binding); + if (obj == null) return 0; + MethodInfo unaryMethod = obj.GetType().GetMethod("op_UnaryNegation", BindingFlags.FlattenHierarchy |BindingFlags.Static | BindingFlags.Public); + if (unaryMethod != null) + return (int)Util.LuaExceptionCatch(() => Binding.PushLuaType(state, unaryMethod.Invoke(null, new[]{obj}), binding), state); + Util.LuaExceptionCatch(() => throw new KOSUnaryOperandTypeException("negate", obj), state); + return 0; + } + + private static int StructureLength(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + var structure = binding.Objects[state.ToUserData(1)] as Structure; + if (!structure.HasSuffix("LENGTH")) + return state.Error("attempt to get length of a Structure with no length suffix"); + state.PushString("LENGTH"); + return (int)Util.LuaExceptionCatch(() => PushSuffixResult(state, binding, structure, -1), state); + } + + private static int StructureToString(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var structure = Binding.Bindings[state.MainThread.Handle].Objects[state.ToUserData(1)]; + var structureString = (string)Util.LuaExceptionCatch(() => structure.ToString(), state); + if (structure is IEnumerable) + { // make enum structures ToString() method show 1 base indexed values in lua + // replaces "\n [*number*]" with "\n [*number+1*]" + state.PushString(Regex.Replace(structureString, @"\n\s*\[([0-9]+)\]", (match) => + Regex.Replace(match.Groups[0].Value, match.Groups[1].Value, (int.Parse(match.Groups[1].Value) + 1).ToString()) + )); + } + else + state.PushString(structureString); + return 1; + } + + private static int StructureIndex(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + object obj = binding.Objects[state.ToUserData(1)]; + var structure = obj as Structure; + if (structure == null) + return state.Error(string.Format("attempt to index a {0} value", obj.GetType().Name)); + + return (int)Util.LuaExceptionCatch(() => PushSuffixResult(state, binding, structure, 2), state); + } + + private static int PushSuffixResult(KeraLua.Lua state, Binding.BindingData binding, Structure structure, int index) + { + object pushValue = null; + if (state.Type(index) == LuaType.Number && structure is IIndexable indexable) + { + pushValue = Structure.ToPrimitive(indexable.GetIndex((int)state.ToInteger(index)-(structure is Lexicon? 0 : 1), true)); + return Binding.PushLuaType(state, pushValue, binding); + } + + var suffixName = state.ToString(index).ToLower(); + + if (structure is TerminalInput && suffixName == "getchar") + { + state.PushCFunction(GetChar); + return 1; + } + + var result = structure.GetSuffix(suffixName, true); + if (result == null) + return Binding.PushLuaType(state, null, binding); + + if (result.HasValue) + { + pushValue = Structure.ToPrimitive(result.Value); + } + else if (result is DelegateSuffixResult delegateResult && delegateResult.RawDelInfo.ReturnType != typeof(void) + && delegateResult.RawDelInfo.Parameters.Length == 0 + && suffixName != "pop" && suffixName != "peek") + { + var callResult = delegateResult.RawCall(null); + delegateResult.RawSetValue(Structure.FromPrimitiveWithAssert(callResult)); + pushValue = Structure.ToPrimitive(delegateResult.Value); + } else + { + pushValue = result as DelegateSuffixResult; // if its somehow not DelegateSuffixResult push null + } + return Binding.PushLuaType(state, pushValue, binding); + } + + private static int StructureNewIndex(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + var obj = binding.Objects[state.ToUserData(1)]; + var structure = obj as Structure; + if (structure == null) + return state.Error(string.Format("attempt to index a {0} value", obj.GetType().Name)); + var newValue = Binding.ToCSharpObject(state, 3, binding); + if (newValue == null) return 0; + if (structure is IIndexable && state.Type(2) == LuaType.Number) + { + var index = (int)state.ToInteger(2) - (structure is Lexicon? 0 : 1); + Util.LuaExceptionCatch(() => + (structure as IIndexable).SetIndex(index, Structure.FromPrimitive(newValue) as Structure), state); + } + else + { + var index = state.ToString(2); + Util.LuaExceptionCatch(() => + { + if (!structure.SetSuffix(index, Structure.FromPrimitive(newValue))) + throw new Exception($"Suffix \"{index}\" not found on Structure {structure.KOSName}"); + }, state); + } + return 0; + } + + private static int StructurePairs(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + var structure = binding.Objects[state.ToUserData(1)] as Structure; + if (structure == null) + return state.Error("pairs metamethod can only be called with a Structure type"); + + state.PushInteger(1); + var enumCount = (structure is IIndexable && structure is IEnumerable enumerable) + ? enumerable.Count() + : 0; + state.NewTable(); + var index = 1; + for (; index <= enumCount; index++) + { + state.PushInteger(index); + state.PushInteger(index); + state.SetTable(-3); + } + foreach (StringValue name in structure.GetSuffixNames()) + { + state.PushInteger(index++); + state.PushString(name); + state.SetTable(-3); + } + + state.PushCClosure(StructureNext, 2); // pass the starting index and table with index-suffix pairs + state.PushCopy(1); + return 2; + } + + private static int StructureNext(IntPtr L) + { + var state = KeraLua.Lua.FromIntPtr(L); + var binding = Binding.Bindings[state.MainThread.Handle]; + var structure = binding.Objects[state.ToUserData(1)] as Structure; + if (structure == null) + return state.Error("iterator can only be called with a Structure type"); + // ignore the second argument + var currentIndex = state.ToInteger(KeraLua.Lua.UpValueIndex(1)); + state.PushCopy(KeraLua.Lua.UpValueIndex(2)); + state.PushInteger(currentIndex); + state.GetTable(-2); + + Util.LuaExceptionCatch(() => PushSuffixResult(state, binding, structure, -1), state); + + state.PushInteger(currentIndex+1); + state.Copy(-1, KeraLua.Lua.UpValueIndex(1)); + state.Remove(-1); + return 2; + } + + private static int GetChar(IntPtr L) + { + return GetCharContinuation(L, 0, IntPtr.Zero); + } + + private static int GetCharContinuation(IntPtr L, int status, IntPtr ctx) + { + var state = KeraLua.Lua.FromIntPtr(L); + var shared = Binding.Bindings[state.MainThread.Handle].Shared; + var q = shared.Screen.CharInputQueue; + if (q.Count == 0) + state.YieldK(0, 0, GetCharContinuation); + state.PushString(q.Dequeue().ToString()); + return 1; + } + } +} diff --git a/src/kOS/Lua/Types/LuaTypeBase.cs b/src/kOS/Lua/Types/LuaTypeBase.cs new file mode 100644 index 0000000000..36cd1636d8 --- /dev/null +++ b/src/kOS/Lua/Types/LuaTypeBase.cs @@ -0,0 +1,18 @@ +using System; + +namespace kOS.Lua.Types +{ + public abstract class LuaTypeBase + { + public abstract Type[] BindingTypes { get; } + public abstract string MetatableName { get; } + public abstract void CreateMetatable(KeraLua.Lua state); + + private protected static void AddMethod(KeraLua.Lua state, string name, KeraLua.LuaFunction metaMethod) + { + state.PushString(name); + state.PushCFunction(metaMethod); + state.RawSet(-3); + } + } +} \ No newline at end of file diff --git a/src/kOS/Lua/Util.cs b/src/kOS/Lua/Util.cs new file mode 100644 index 0000000000..092acd4767 --- /dev/null +++ b/src/kOS/Lua/Util.cs @@ -0,0 +1,40 @@ +using System; +using KeraLua; +using UnityEngine; + +namespace kOS.Lua +{ + public static class Util + { + public static LuaType RawGetGlobal(KeraLua.Lua lua, string name) + { + lua.PushGlobalTable(); + lua.PushString(name); + var type = lua.RawGet(-2); + lua.Remove(-2); + return type; + } + + public static void LuaExceptionCatch(Action tryBody, KeraLua.Lua state) => + LuaExceptionCatch(() => { tryBody(); return null; }, state); + + public static object LuaExceptionCatch(Func tryBody, KeraLua.Lua state) + { + try { return tryBody(); } + catch (Exception e) + { + Debug.Log(e); + return state.Error(e.Message == ""? e.GetType().FullName : e.Message); + } + } + + public static void DumpStack(KeraLua.Lua state, string debugName = "", Binding.BindingData binding = null) + { + binding = binding ?? Binding.Bindings[state.MainThread.Handle]; + Debug.Log(debugName+"_________"); + for (int i = 0; i <= state.GetTop(); i++) + Debug.Log(i+" "+state.TypeName(i)+" "+Binding.ToCSharpObject(state, i, binding)); + Debug.Log("____________________"); + } + } +} \ No newline at end of file diff --git a/src/kOS/Lua/include/LuaModules/Linux/cjson.so b/src/kOS/Lua/include/LuaModules/Linux/cjson.so new file mode 100644 index 0000000000..088d648da6 Binary files /dev/null and b/src/kOS/Lua/include/LuaModules/Linux/cjson.so differ diff --git a/src/kOS/Lua/include/LuaModules/Windows/cjson.dll b/src/kOS/Lua/include/LuaModules/Windows/cjson.dll new file mode 100644 index 0000000000..50eafc23bb Binary files /dev/null and b/src/kOS/Lua/include/LuaModules/Windows/cjson.dll differ diff --git a/src/kOS/Lua/include/LuaModules/macOS/cjson.so b/src/kOS/Lua/include/LuaModules/macOS/cjson.so new file mode 100644 index 0000000000..836cda3c97 Binary files /dev/null and b/src/kOS/Lua/include/LuaModules/macOS/cjson.so differ diff --git a/src/kOS/Lua/include/liblua54.dylib b/src/kOS/Lua/include/liblua54.dylib new file mode 100644 index 0000000000..0682b7a1f8 Binary files /dev/null and b/src/kOS/Lua/include/liblua54.dylib differ diff --git a/src/kOS/Lua/include/liblua54.so b/src/kOS/Lua/include/liblua54.so new file mode 100644 index 0000000000..892b39cf85 Binary files /dev/null and b/src/kOS/Lua/include/liblua54.so differ diff --git a/src/kOS/Lua/include/lua54.dll b/src/kOS/Lua/include/lua54.dll new file mode 100644 index 0000000000..66191abad4 Binary files /dev/null and b/src/kOS/Lua/include/lua54.dll differ diff --git a/src/kOS/Lua/lua b/src/kOS/Lua/lua new file mode 160000 index 0000000000..89c872c836 --- /dev/null +++ b/src/kOS/Lua/lua @@ -0,0 +1 @@ +Subproject commit 89c872c836bb36a6f4e21ce35d8692ec2e869085 diff --git a/src/kOS/Lua/whitelist.lua b/src/kOS/Lua/whitelist.lua new file mode 100644 index 0000000000..8acba10ce5 --- /dev/null +++ b/src/kOS/Lua/whitelist.lua @@ -0,0 +1,158 @@ +local registry, setUpvalue = ... + +local _ENV = { + _VERSION = _VERSION, + assert = assert, + collectgarbage = collectgarbage, + dofile = dofile, + error = error, + getmetatable = getmetatable, + ipairs = ipairs, + load = load, + loadfile = loadfile, + next = next, + pairs = pairs, + pcall = pcall, + print = print, + rawequal = rawequal, + rawget = rawget, + rawlen = rawlen, + rawset = rawset, + select = select, + setmetatable = setmetatable, + tonumber = tonumber, + tostring = tostring, + type = type, + warn = warn, + xpcall = xpcall, + _type = _type, + require = require, + package = { + config = package.config, + path = package.path, + searchers = { + package.searchers[1], + package.searchers[2], + package.searchers[3], + package.searchers[4], + }, + }, + coroutine = { + close = coroutine.close, + create = coroutine.create, + isyieldable = coroutine.isyieldable, + resume = coroutine.resume, + running = coroutine.running, + status = coroutine.status, + wrap = coroutine.wrap, + yield = coroutine.yield, + }, + math = { + abs = math.abs, + acos = math.acos, + asin = math.asin, + atan = math.atan, + ceil = math.ceil, + cos = math.cos, + deg = math.deg, + exp = math.exp, + floor = math.floor, + fmod = math.fmod, + huge = math.huge, + log = math.log, + max = math.max, + maxinteger = math.maxinteger, + min = math.min, + mininteger = math.mininteger, + modf = math.modf, + pi = math.pi, + rad = math.rad, + random = math.random, + randomseed = math.randomseed, + sin = math.sin, + sqrt = math.sqrt, + tan = math.tan, + tointeger = math.tointeger, + type = math.type, + ult = math.ult, + }, + string = { + byte = string.byte, + char = string.char, + dump = string.dump, + find = string.find, + format = string.format, + gmatch = string.gmatch, + gsub = string.gsub, + len = string.len, + lower = string.lower, + match = string.match, + pack = string.pack, + packsize = string.packsize, + rep = string.rep, + reverse = string.reverse, + sub = string.sub, + unpack = string.unpack, + upper = string.upper, + }, + table = { + concat = table.concat, + insert = table.insert, + move = table.move, + pack = table.pack, + remove = table.remove, + sort = table.sort, + unpack = table.unpack, + }, + utf8 = { + char = utf8.char, + charpattern = utf8.charpattern, + codepoint = utf8.codepoint, + codes = utf8.codes, + len = utf8.len, + offset = utf8.offset, + }, +} + +local whitelistedRegistry = { + registry[1], -- main thread + _ENV, + _LOADED = { + _G = _ENV, + package = package, + coroutine = coroutine, + math = math, + string = string, + table = table, + utf8 = utf8, + }, + _PRELOAD = {}, + _CLIBS = setmetatable({}, getmetatable(registry._CLIBS)), +} + +local visitedTables = {} +local function deepCleanTable(tab) + visitedTables[tab] = true + for k,v in pairs(tab) do + if _type(v) == "table" and not visitedTables[v] then + deepCleanTable(v) + end + tab[k] = nil + end +end +deepCleanTable(registry) + +for k,v in pairs(whitelistedRegistry) do + registry[k] = v +end + +_G = _ENV +package.loaded = whitelistedRegistry._LOADED +package.preload = whitelistedRegistry._PRELOAD + +setUpvalue(require, 1, package) +for _,searcher in ipairs(package.searchers) do + setUpvalue(searcher, 1, package) +end + +getmetatable("").__index = string \ No newline at end of file diff --git a/src/kOS/Module/kOSCustomParameters.cs b/src/kOS/Module/kOSCustomParameters.cs index d06f9c2eec..b7bec24f33 100644 --- a/src/kOS/Module/kOSCustomParameters.cs +++ b/src/kOS/Module/kOSCustomParameters.cs @@ -66,6 +66,18 @@ public int InstructionsPerUpdate instructionsPerUpdate = Math.Max(ipuMin, Math.Min(ipuMax, value)); } } + + private const int luaIpuMin = 150; + private const int luaIpuMax = 3000; + private int luaInstructionsPerUpdate = 300; + + [GameParameters.CustomIntParameterUI("Lua instructions per update", minValue = luaIpuMin, maxValue = luaIpuMax, + toolTip = "Maximum number of instructions used per physics tick by CPUs using lua")] + public int LuaInstructionsPerUpdate + { + get => luaInstructionsPerUpdate; + set => luaInstructionsPerUpdate = Math.Max(luaIpuMin, Math.Min(luaIpuMax, value)); + } [GameParameters.CustomParameterUI("Enable compressed storage", toolTip = "When storing local volumes' data in the saved game,\n"+ diff --git a/src/kOS/Module/kOSProcessor.cs b/src/kOS/Module/kOSProcessor.cs index 68d02feb9f..2126d9a293 100644 --- a/src/kOS/Module/kOSProcessor.cs +++ b/src/kOS/Module/kOSProcessor.cs @@ -18,13 +18,14 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; - using kOS.Safe.Execution; using UnityEngine; using kOS.Safe.Encapsulation; using KSP.UI; using kOS.Suffixed; using kOS.Safe.Function; +using kOS.Lua; +using kOS.Screen; namespace kOS.Module { @@ -62,6 +63,7 @@ public string Tag private int vesselPartCount; private SharedObjects shared; private static readonly List allMyInstances = new List(); + private bool hasShutdown = true; public bool HasBooted { get; set; } private bool objectsInitialized = false; private int numUpdatesAfterStartHappened = 0; @@ -86,9 +88,15 @@ public string Tag private const string BootDirectoryName = "boot"; + [KSPField(isPersistant = true, guiActive = true, guiActiveEditor = true, guiName = "Interpreter", groupName = PAWGroup, groupDisplayName = PAWGroup), UI_ChooseOption(scene = UI_Scene.All)] + public string interpreterLanguage = "kerboscript"; + [KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Boot File", groupName = PAWGroup, groupDisplayName = PAWGroup), UI_ChooseOption(scene = UI_Scene.Editor)] public string bootFile = "None"; + [KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Lua Boot File", groupName = PAWGroup, groupDisplayName = PAWGroup), UI_ChooseOption(scene = UI_Scene.Editor)] + public string luaBootFile = "None"; + [KSPField(isPersistant = true, guiName = "kOS Disk Space", guiActive = true, groupName = PAWGroup, groupDisplayName = PAWGroup)] public int diskSpace = 1024; @@ -145,9 +153,9 @@ public kOSProcessor() public VolumePath BootFilePath { get { - if (string.IsNullOrEmpty(bootFile) || bootFile.Equals("None", StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(BootFilename) || BootFilename.Equals("None", StringComparison.OrdinalIgnoreCase)) return null; - return VolumePath.FromString(bootFile); + return VolumePath.FromString(BootFilename); } } @@ -403,6 +411,7 @@ public override void OnStart(StartState state) { FindRP1Modules(); UpdateRP1TechLevel(state == StartState.Editor); + InitInterpreterField(state); //if in Editor, populate boot script selector, diskSpace selector and etc. if (state == StartState.Editor) { @@ -438,10 +447,51 @@ public static void SetBootListDirty() bootListDirty = true; } + private void InitInterpreterField(StartState state) + { + BaseField interpreterLanguageField = Fields["interpreterLanguage"]; + string[] interpreterDisplayNames = { "KerboScript", "Lua" }; + UI_ChooseOption interpreterLanguageOption; + if (state == StartState.Editor) + { + interpreterLanguageOption = (UI_ChooseOption)interpreterLanguageField.uiControlEditor; + interpreterLanguageOption.onFieldChanged = (BaseField field, object obj) => InitUI(); + } else + { + interpreterLanguageOption = (UI_ChooseOption)interpreterLanguageField.uiControlFlight; + interpreterLanguageOption.onFieldChanged = OnInterpreterChanged; + } + interpreterLanguageOption.display = interpreterDisplayNames; + interpreterLanguageOption.options = interpreterDisplayNames.Select((display) => display.ToLower()).ToArray(); + } + + private void OnInterpreterChanged(BaseField field, object buggyPrevValue) + { + interpreterLanguage = interpreterLanguage.ToLower()[0]=='l'? "lua" : "kerboscript"; + if (shared.Interpreter.Name == interpreterLanguage) return; + shared.Logger.Log("Interpreter changed. "+shared.Interpreter.Name+" to "+interpreterLanguage+". Rebooting"); + if (ProcessorMode != ProcessorModes.READY) return; + SetMode(ProcessorModes.OFF); + SetMode(ProcessorModes.READY); + } + private void InitUI() { //Populate selector for boot scripts - BaseField field = Fields["bootFile"]; + BaseField ksField = Fields["bootFile"]; + BaseField luaField = Fields["luaBootFile"]; + BaseField field; + if (interpreterLanguage == "lua") + { + field = luaField; + ksField.guiActiveEditor = false; + ((UI_ChooseOption)ksField.uiControlEditor).controlEnabled = false; + } else + { + field = ksField; + luaField.guiActiveEditor = false; + ((UI_ChooseOption)luaField.uiControlEditor).controlEnabled = false; + } var options = (UI_ChooseOption)field.uiControlEditor; var bootFiles = new List(); @@ -497,15 +547,15 @@ private void InitUI() if (file != null) { // store the boot file information - bootFile = file.Path.ToString(); + BootFilename = file.Path.ToString(); if (!bootFiles.Contains(file.Path)) { - availableOptions.Insert(1, bootFile); + availableOptions.Insert(1, BootFilename); availableDisplays.Insert(1, "*" + file.Path.Name); // "*" is indication the file is not normally available } } } - SafeHouse.Logger.SuperVerbose("bootFile: " + bootFile); + SafeHouse.Logger.SuperVerbose("bootFile: " + BootFilename); options.options = availableOptions.ToArray(); options.display = availableDisplays.ToArray(); @@ -547,8 +597,10 @@ private IEnumerable BootDirectoryFiles() foreach (KeyValuePair pair in files) { - if (pair.Value is VolumeFile && (pair.Value.Extension.Equals(Volume.KERBOSCRIPT_EXTENSION) - || pair.Value.Extension.Equals(Volume.KOS_MACHINELANGUAGE_EXTENSION))) + string[] filenameExtensions = interpreterLanguage == "lua" ? + LuaInterpreter.FilenameExtensions : + KSInterpreter.FilenameExtensions; + if (pair.Value is VolumeFile && filenameExtensions.Any(extension => extension == pair.Value.Extension)) { result.Add(pair.Value.Path); } @@ -577,15 +629,16 @@ public void InitObjects() shared.KSPPart = part; shared.UpdateHandler = new UpdateHandler(); shared.BindingMgr = new BindingManager(shared); - shared.Interpreter = new Screen.ConnectivityInterpreter(shared); - shared.Screen = shared.Interpreter; + shared.Terminal = new Screen.ConnectivityTerminal(shared); + shared.Screen = shared.Terminal; shared.ScriptHandler = new KSScript(); shared.Logger = new KSPLogger(shared); shared.VolumeMgr = new ConnectivityVolumeManager(shared); shared.ProcessorMgr = new ProcessorManager(); shared.FunctionManager = new FunctionManager(shared); shared.TransferManager = new TransferManager(shared); - shared.Cpu = new CPU(shared); + shared.Cpu = interpreterLanguage == "lua" ? new LuaCPU(shared) : new CPU(shared); + shared.Interpreter = interpreterLanguage == "lua" ? (IInterpreter)new LuaInterpreter(shared) : new KSInterpreter(shared); shared.AddonManager = new AddOns.AddonManager(shared); shared.GameEventDispatchManager = new GameEventDispatchManager(shared); @@ -762,6 +815,7 @@ public void OnDestroy() if (shared != null) { shared.Cpu.BreakExecution(false); + shared.Interpreter.Dispose(); shared.Cpu.Dispose(); shared.DestroyObjects(); shared = null; @@ -861,10 +915,45 @@ public void FixedUpdate() if (!vessel.HoldPhysics) { + if (!hasShutdown) + { + shared.Interpreter.Dispose(); + hasShutdown = true; + } if (!HasBooted) { SafeHouse.Logger.LogWarning("First Update()"); + // interpreter swap + // dispose current cpu and interpreter + shared.Interpreter.Dispose(); + shared.Cpu.Dispose(); + // update shared cpu and interpreter + if (interpreterLanguage == "lua") + { + shared.Cpu = new LuaCPU(shared); + shared.Interpreter = new LuaInterpreter(shared); + } + else + { + shared.Cpu = new CPU(shared); + shared.Interpreter = new KSInterpreter(shared); + } + // run boot methods shared.Cpu.Boot(); + shared.Interpreter.Boot(); + + // if booted by calling RunBootFile reset bootFile to its previous value + if (beforeRunLuaBootFile != null) + { + luaBootFile = beforeRunLuaBootFile; + beforeRunLuaBootFile = null; + } + if (beforeRunBootFile != null) + { + bootFile = beforeRunBootFile; + beforeRunBootFile = null; + } + HasBooted = true; } UpdateVessel(); @@ -1096,7 +1185,7 @@ private void ProcessElectricity(Part partObj, float time) { // Because the processor is not STARVED, evaluate the power requirement based on actual operation. // For EC drain purposes, always pretend atleast 1 instruction happened, so idle drain isn't quite zero: - int instructions = System.Math.Max(shared.Cpu.InstructionsThisUpdate, 1); + int instructions = System.Math.Max(shared.Interpreter.ECInstructionsThisUpdate(), 1); var request = volumePower * time + instructions * ECPerInstruction; if (request > 0) { @@ -1145,15 +1234,17 @@ private void ProcessorModeChanged() shared.VolumeMgr.SwitchTo(HardDisk); } HasBooted = false; // When FixedUpdate() first happesn, then the boot will happen. - if (shared.Interpreter != null) shared.Interpreter.SetInputLock(false); + if (shared.Terminal != null) shared.Terminal.SetInputLock(false); if (shared.Window != null) shared.Window.IsPowered = true; foreach (var w in shared.ManagedWindows) w.IsPowered = true; break; case ProcessorModes.OFF: case ProcessorModes.STARVED: + hasShutdown = false; + if (shared.Interpreter != null) shared.Interpreter.BreakExecution(); if (shared.Cpu != null) shared.Cpu.BreakExecution(true); - if (shared.Interpreter != null) shared.Interpreter.SetInputLock(true); + if (shared.Terminal != null) shared.Terminal.SetInputLock(true); if (shared.Window != null) shared.Window.IsPowered = false; if (shared.SoundMaker != null) shared.SoundMaker.StopAllVoices(); foreach (var w in shared.ManagedWindows) w.IsPowered = false; @@ -1164,6 +1255,39 @@ private void ProcessorModeChanged() } + private string beforeRunLuaBootFile; + private string beforeRunBootFile; + + public void RunBootFile(VolumeFile file) + { + if (KSInterpreter.FilenameExtensions.Contains(file.Extension)) + { + interpreterLanguage = "kerboscript"; + } + else if (LuaInterpreter.FilenameExtensions.Contains(file.Extension)) + { + interpreterLanguage = "lua"; + } + + if (interpreterLanguage == "kerboscript") + { + beforeRunBootFile = bootFile; + bootFile = file.Path.ToString(); + } + else if (interpreterLanguage == "lua") + { + beforeRunLuaBootFile = luaBootFile; + luaBootFile = file.Path.ToString(); + } + SetMode(ProcessorModes.OFF); + SetMode(ProcessorModes.READY); + } + + public void RunCommand(string command) + { + shared.Interpreter.ProcessCommand(command); + } + public void ExecuteInterProcCommand(InterProcCommand command) { if (command != null) @@ -1180,8 +1304,8 @@ public void SetAutopilotMode(int mode) public string BootFilename { - get { return bootFile; } - set { bootFile = value; } + get { return interpreterLanguage == "lua"? luaBootFile : bootFile; } + set { if (interpreterLanguage == "lua") luaBootFile = value; else bootFile = value; } } public bool CheckCanBoot() diff --git a/src/kOS/Screen/ConnectivityInterpreter.cs b/src/kOS/Screen/ConnectivityTerminal.cs similarity index 97% rename from src/kOS/Screen/ConnectivityInterpreter.cs rename to src/kOS/Screen/ConnectivityTerminal.cs index c080ddeff4..88cd1b3829 100644 --- a/src/kOS/Screen/ConnectivityInterpreter.cs +++ b/src/kOS/Screen/ConnectivityTerminal.cs @@ -7,7 +7,7 @@ namespace kOS.Screen { - public class ConnectivityInterpreter : Interpreter, IUpdateObserver + public class ConnectivityTerminal : Terminal, IUpdateObserver { private readonly List commandQueue = new List(); private readonly List batchQueue = new List(); @@ -20,14 +20,14 @@ public class ConnectivityInterpreter : Interpreter, IUpdateObserver private string deploymentMessage; private bool signalLossWarning; - public ConnectivityInterpreter(SharedObjects shared) : base(shared) + public ConnectivityTerminal(SharedObjects shared) : base(shared) { Shared.UpdateHandler.AddObserver(this); CreateProgressBarSubBuffer(this); AddResizeNotifier(CreateProgressBarSubBuffer); } - ~ConnectivityInterpreter() + ~ConnectivityTerminal() { // Normally this design pattern would fail because the notifier hooks // would be references that prevent orphaning and thus we can't remove diff --git a/src/kOS/Screen/KSInterpreter.cs b/src/kOS/Screen/KSInterpreter.cs new file mode 100644 index 0000000000..6c1feaf333 --- /dev/null +++ b/src/kOS/Screen/KSInterpreter.cs @@ -0,0 +1,106 @@ +using kOS.Safe.Compilation; +using kOS.Safe.Execution; +using kOS.Safe.Persistence; +using kOS.Safe.Screen; +using kOS.Safe.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace kOS.Screen +{ + public class KSInterpreter : IInterpreter + { + public static readonly string[] FilenameExtensions = new string[] { Volume.KERBOSCRIPT_EXTENSION, Volume.KOS_MACHINELANGUAGE_EXTENSION }; + public string Name => "kerboscript"; + private SharedObjects Shared { get; } + + public KSInterpreter(SharedObjects shared) + { + Shared = shared; + } + + public void Boot() { } + + public void ProcessCommand(string commandText) + { + if (Shared.ScriptHandler == null) return; + try + { + CompilerOptions options = new CompilerOptions + { + LoadProgramsInSameAddressSpace = false, + FuncManager = Shared.FunctionManager, + BindManager = Shared.BindingMgr, + AllowClobberBuiltins = SafeHouse.Config.AllowClobberBuiltIns, + IsCalledFromRun = false + }; + + List commandParts = Shared.ScriptHandler.Compile(new InterpreterPath(Shared.Terminal as Terminal), + Shared.Terminal.GetCommandHistoryIndex(), commandText, Terminal.InterpreterName, options); + if (commandParts == null) return; + + var interpreterContext = ((CPU)Shared.Cpu).GetInterpreterContext(); + interpreterContext.AddParts(commandParts); + } + catch (Exception e) + { + if (Shared.Logger != null) + { + Shared.Logger.Log(e); + } + } + } + + public bool IsCommandComplete(string commandText) + { + return Shared.ScriptHandler.IsCommandComplete(commandText); + } + + public bool IsWaitingForCommand() + { + IProgramContext context = ((CPU)Shared.Cpu).GetInterpreterContext(); + // If running from a boot script, there will be no interpreter instructions, + // only a single OpcodeEOF. So we check to see if the interpreter is locked, + // which is a sign that a sub-program is running. + return context.Program[context.InstructionPointer] is OpcodeEOF; + } + + public void BreakExecution() + { + if (Shared.Cpu.GetCurrentContext() == null) return; + Shared.Cpu.GetCurrentOpcode().AbortProgram = true; + } + + public int InstructionsThisUpdate() + { + return Shared.Cpu.InstructionsThisUpdate; + } + + public int ECInstructionsThisUpdate() => InstructionsThisUpdate(); + + public void Dispose() { } // Everything is disposed in CPU + + private class InterpreterPath : InternalPath + { + private Terminal terminal; + + public InterpreterPath(Terminal terminal) : base() + { + this.terminal = terminal; + } + + public override string Line(int line) + { + return terminal.GetCommandHistoryAbsolute(line); + } + + public override string ToString() + { + return Terminal.InterpreterName; + } + } + } +} diff --git a/src/kOS/Screen/TermWindow.cs b/src/kOS/Screen/TermWindow.cs index a84233f2be..16eb0aa26c 100644 --- a/src/kOS/Screen/TermWindow.cs +++ b/src/kOS/Screen/TermWindow.cs @@ -680,7 +680,7 @@ public bool ProcessOneInputChar(char ch, TelnetSingletonServer whichTelnet, bool case (char)0x0004/*control-D*/: // How users of unix shells are used to doing this. case (char)0x0018/*control-X*/: // How kOS did it in the past in the GUI window. - if (shared.Interpreter.IsAtStartOfCommand()) + if (shared.Terminal.IsAtStartOfCommand()) { if (whichTelnet == null) Close(); @@ -758,9 +758,9 @@ bool Type(char ch, bool doQueuing = true, bool forceQueue = true) bool accepted = false; if (shared != null) { - if ((!forceQueue) && shared.Interpreter != null && shared.Interpreter.IsWaitingForCommand()) + if ((!forceQueue) && shared.Terminal != null && shared.Terminal.IsWaitingForCommand()) { - shared.Interpreter.Type(ch); + shared.Terminal.Type(ch); accepted = true; } else if (doQueuing) @@ -803,10 +803,10 @@ bool SpecialKey(char key, bool doQueuing = true, bool forceQueue = true) bool wasUsed = false; if ((!forceQueue) && - shared.Interpreter != null && - (shared.Interpreter.IsWaitingForCommand() || rudeQueueSkipping)) + shared.Terminal != null && + (shared.Terminal.IsWaitingForCommand() || rudeQueueSkipping)) { - wasUsed = shared.Interpreter.SpecialKey(key); + wasUsed = shared.Terminal.SpecialKey(key); accepted = true; } else if (doQueuing) @@ -834,11 +834,11 @@ void ProcessUnconsumedInput() { return; // Fix race condition (Github issue #2925) where Update() calls this before FixedUpdate() has set up the CPU. } - if (shared != null && shared.Interpreter != null && shared.Interpreter.IsWaitingForCommand()) + if (shared != null && shared.Terminal != null && shared.Terminal.IsWaitingForCommand()) { Queue q = shared.Screen.CharInputQueue; - while (q.Count > 0 && shared.Interpreter.IsWaitingForCommand()) + while (q.Count > 0 && shared.Terminal.IsWaitingForCommand()) { // Setting doQueuing to false here just as an // additional safety measure. Hypothetically it diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Terminal.cs similarity index 68% rename from src/kOS/Screen/Interpreter.cs rename to src/kOS/Screen/Terminal.cs index 997fc3d201..8a71120c29 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Terminal.cs @@ -11,7 +11,7 @@ namespace kOS.Screen { - public class Interpreter : TextEditor, IInterpreter + public class Terminal : TextEditor, ITerminal { public const string InterpreterName = "interpreter"; private readonly List commandHistory = new List(); @@ -20,7 +20,7 @@ public class Interpreter : TextEditor, IInterpreter protected SharedObjects Shared { get; private set; } - public Interpreter(SharedObjects shared) + public Terminal(SharedObjects shared) { Shared = shared; } @@ -29,7 +29,7 @@ protected override void NewLine() { string commandText = LineBuilder.ToString(); - if (Shared.ScriptHandler.IsCommandComplete(commandText)) + if (Shared.Interpreter.IsCommandComplete(commandText)) { base.NewLine(); AddCommandHistoryEntry(commandText); // add to history first so that if ProcessCommand generates an exception, @@ -67,6 +67,7 @@ public override bool SpecialKey(char key) { if (key == (char)UnicodeCommand.BREAK) { + Shared.Interpreter.BreakExecution(); Shared.Cpu.BreakExecution(true); LineBuilder.Remove(0, LineBuilder.Length); // why isn't there a StringBuilder.Clear()? @@ -123,49 +124,19 @@ public string GetCommandHistoryAbsolute(int absoluteIndex) return commandHistory[absoluteIndex-1]; } - protected virtual void ProcessCommand(string commandText) + public int GetCommandHistoryIndex() { - CompileCommand(commandText); + return commandHistoryIndex; } - protected void CompileCommand(string commandText) + protected virtual void ProcessCommand(string commandText) { - if (Shared.ScriptHandler == null) return; - - try - { - CompilerOptions options = new CompilerOptions - { - LoadProgramsInSameAddressSpace = false, - FuncManager = Shared.FunctionManager, - BindManager = Shared.BindingMgr, - AllowClobberBuiltins = SafeHouse.Config.AllowClobberBuiltIns, - IsCalledFromRun = false - }; - - List commandParts = Shared.ScriptHandler.Compile(new InterpreterPath(this), - commandHistoryIndex, commandText, InterpreterName, options); - if (commandParts == null) return; - - var interpreterContext = ((CPU)Shared.Cpu).GetInterpreterContext(); - interpreterContext.AddParts(commandParts); - } - catch (Exception e) - { - if (Shared.Logger != null) - { - Shared.Logger.Log(e); - } - } + Shared.Interpreter.ProcessCommand(commandText); } public bool IsWaitingForCommand() { - IProgramContext context = ((CPU)Shared.Cpu).GetInterpreterContext(); - // If running from a boot script, there will be no interpreter instructions, - // only a single OpcodeEOF. So we check to see if the interpreter is locked, - // which is a sign that a sub-program is running. - return !locked && context.Program[context.InstructionPointer] is OpcodeEOF; + return !locked && Shared.Interpreter.IsWaitingForCommand(); } public void SetInputLock(bool isLocked) @@ -189,25 +160,5 @@ public override void PrintAt(string textToPrint, int row, int column) base.PrintAt(textToPrint, row, column); RestoreCursorPos(); } - - private class InterpreterPath : InternalPath - { - private Interpreter interpreter; - - public InterpreterPath(Interpreter interpreter) : base() - { - this.interpreter = interpreter; - } - - public override string Line(int line) - { - return interpreter.GetCommandHistoryAbsolute(line); - } - - public override string ToString() - { - return InterpreterName; - } - } } } diff --git a/src/kOS/SharedObjects.cs b/src/kOS/SharedObjects.cs index ddcd53e781..f31045f8e1 100644 --- a/src/kOS/SharedObjects.cs +++ b/src/kOS/SharedObjects.cs @@ -53,7 +53,7 @@ public void DestroyObjects() if (SoundMaker != null) { SoundMaker.StopAllVoices(); } if (UpdateHandler != null) { UpdateHandler.ClearAllObservers(); } if (GameEventDispatchManager != null) { GameEventDispatchManager.Clear(); } - if (Interpreter != null) { Interpreter.RemoveAllResizeNotifiers(); } + if (Terminal != null) { Terminal.RemoveAllResizeNotifiers(); } var props = typeof(SharedObjects).GetProperties(); IDisposable tempDisp; foreach (var prop in props) diff --git a/src/kOS/Suffixed/Config.cs b/src/kOS/Suffixed/Config.cs index cae46522f6..739c474182 100644 --- a/src/kOS/Suffixed/Config.cs +++ b/src/kOS/Suffixed/Config.cs @@ -17,6 +17,7 @@ public class Config : Structure, IConfig private readonly Dictionary properties; public int InstructionsPerUpdate { get { return kOSCustomParameters.Instance.InstructionsPerUpdate; } set { kOSCustomParameters.Instance.InstructionsPerUpdate = value; } } + public int LuaInstructionsPerUpdate { get { return kOSCustomParameters.Instance.LuaInstructionsPerUpdate; } set { kOSCustomParameters.Instance.LuaInstructionsPerUpdate = value; } } public bool UseCompressedPersistence { get { return kOSCustomParameters.Instance.useCompressedPersistence; } set { kOSCustomParameters.Instance.useCompressedPersistence = value; } } public bool ShowStatistics { get { return kOSCustomParameters.Instance.showStatistics; } set { kOSCustomParameters.Instance.showStatistics = value; } } public bool StartOnArchive { get { return kOSCustomParameters.Instance.startOnArchive; } set { kOSCustomParameters.Instance.startOnArchive = value; } } @@ -54,6 +55,7 @@ private Config() private void InitializeSuffixes() { AddSuffix("IPU", new SetSuffix(() => InstructionsPerUpdate, value => InstructionsPerUpdate = value)); + AddSuffix("LUAIPU", new SetSuffix(() => LuaInstructionsPerUpdate, value => LuaInstructionsPerUpdate = value)); AddSuffix("UCP", new SetSuffix(() => UseCompressedPersistence, value => UseCompressedPersistence = value)); AddSuffix("STAT", new SetSuffix(() => ShowStatistics, value => ShowStatistics = value)); AddSuffix("ARCH", new SetSuffix(() => StartOnArchive, value => StartOnArchive = value)); @@ -238,25 +240,26 @@ public override string ToString() private enum PropId { InstructionsPerUpdate = 1, - UseCompressedPersistence = 2, - ShowStatistics = 3, - EnableRTIntegration = 4, - StartOnArchive = 5, - ObeyHideUI = 6, - EnableSafeMode = 7, - AudibleExceptions = 8, - VerboseExceptions = 9, - EnableTelnet = 10, - TelnetPort = 11, - TelnetIPAddrString = 12, - UseBlizzyToolbarOnly = 13, - DebugEachOpcode = 14, - TerminalFontDefaultSize = 15, - TerminalFontName = 16, - TerminalBrightness = 17, - TerminalDefaultWidth = 18, - TerminalDefaultHeight = 19, - SuppressAutopilot = 20 + LuaInstructionsPerUpdate = 2, + UseCompressedPersistence = 3, + ShowStatistics = 4, + EnableRTIntegration = 5, + StartOnArchive = 6, + ObeyHideUI = 7, + EnableSafeMode = 8, + AudibleExceptions = 9, + VerboseExceptions = 10, + EnableTelnet = 11, + TelnetPort = 12, + TelnetIPAddrString = 13, + UseBlizzyToolbarOnly = 14, + DebugEachOpcode = 15, + TerminalFontDefaultSize = 16, + TerminalFontName = 17, + TerminalBrightness = 18, + TerminalDefaultWidth = 19, + TerminalDefaultHeight = 20, + SuppressAutopilot = 21 } } } diff --git a/src/kOS/Suffixed/VesselTarget.cs b/src/kOS/Suffixed/VesselTarget.cs index 1ab591fd46..d5adefc297 100644 --- a/src/kOS/Suffixed/VesselTarget.cs +++ b/src/kOS/Suffixed/VesselTarget.cs @@ -550,7 +550,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Vessel.rootPart.flightID.GetHashCode(); + return Vessel.id.GetHashCode(); } public static bool operator ==(VesselTarget left, VesselTarget right) diff --git a/src/kOS/kOS.csproj b/src/kOS/kOS.csproj index da387bdce5..f3af90aabd 100644 --- a/src/kOS/kOS.csproj +++ b/src/kOS/kOS.csproj @@ -82,6 +82,17 @@ + + + + + + + + + + + @@ -98,13 +109,15 @@ Resources.resx + + - + @@ -144,7 +157,7 @@ - + @@ -263,15 +276,36 @@ + + ..\..\packages\KeraLua.1.4.1\lib\net46\KeraLua.dll + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + diff --git a/src/kOS/packages.config b/src/kOS/packages.config index b00c06934f..78cb230595 100644 --- a/src/kOS/packages.config +++ b/src/kOS/packages.config @@ -1,5 +1,6 @@  +