├── .gitignore ├── LICENSE ├── README.md ├── bin └── release.cmd ├── config.lua ├── debug.lua ├── init.lua ├── mod.lua ├── mod ├── Compiler.lua ├── Configuration.lua ├── Respector.lua ├── Tweaker.lua ├── data │ ├── attachment-slots.lua │ ├── attributes.lua │ ├── config-schema.lua │ ├── crafting-components.lua │ ├── dev-type-aliases.lua │ ├── equipment-areas.lua │ ├── perks-by-alias.lua │ ├── perks.lua │ ├── sample-packs.lua │ ├── skills.lua │ ├── spec-schema.lua │ ├── tweakdb-ids.lua │ ├── tweakdb-meta.lua │ └── tweakdb-meta.xlsm ├── enums │ ├── Quality.lua │ └── RarityFilter.lua ├── hacks │ ├── AutoScaleItems.lua │ ├── SpawnVehicle.lua │ ├── UnlockAllSlots.lua │ └── UnmarkQuestItems.lua ├── helpers │ ├── PersistentState.lua │ ├── SimpleDb.lua │ ├── StructWriter.lua │ └── TweakDb.lua ├── modules │ ├── Character.lua │ ├── Crafting.lua │ ├── Inventory.lua │ └── Transport.lua ├── stores │ └── SpecStore.lua ├── ui │ ├── api.lua │ ├── cli.lua │ ├── gui.lua │ ├── gui.respec.lua │ ├── gui.tweaks.lua │ └── imguix.lua └── utils │ ├── array.lua │ ├── bit32.lua │ ├── export.lua │ ├── fs.lua │ └── str.lua ├── samples ├── NoCyberware.lua ├── NoEquipment.lua ├── Noname.lua ├── Overload.lua ├── config │ └── defaults.lua ├── packs │ ├── clothing-mods.lua │ ├── clothing-sets.lua │ ├── clothing-unique.lua │ ├── clothing.lua │ ├── cyberware-iconic.lua │ ├── cyberware-mods.lua │ ├── cyberware.lua │ ├── gog.lua │ ├── johnny.lua │ ├── nibbles.lua │ ├── patch-130.lua │ ├── patch-150.lua │ ├── patch-160.lua │ ├── quickhacks.lua │ ├── stash-wall.lua │ ├── vehicles.lua │ ├── weapons-atts.lua │ ├── weapons-iconic.lua │ ├── weapons-mods.lua │ └── weapons.lua └── specs │ ├── spec-01-overview.lua │ ├── spec-02-character.lua │ ├── spec-03-perks.lua │ ├── spec-04-inventory.lua │ ├── spec-05-crafting.lua │ └── spec-06-scripting.lua └── specs └── V.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /.dev 2 | /.idea 3 | /dist 4 | /mod/data/*.csv 5 | /mod/data/*.txt 6 | /specs/* 7 | !/specs/V.lua 8 | /db.sqlite3 9 | /*.log 10 | ~$* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pavel Siberx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Respector 2 | 3 | A Cyberpunk 2077 mod for managing player builds in the form of spec files 4 | containing player experience, attributes, skills, perks, and equipment, 5 | with useful extra features. 6 | 7 | - **New Game Plus:** Transfer your character and equipment between playthroughs. 8 | You can try each life path while keeping the experience gained from other playthroughs, 9 | replay the same mission with different abilities and equipment, etc. 10 | - **Character Respec:** Adjust your Attributes and restore all spent Perk Points to redistribute them. 11 | - **Build Management:** Try different builds switching them on the fly while playing, share builds with others. 12 | - **Item Management:** Create item packs (eg. "Stash Wall Weapons", "Legendary Outfits"). 13 | - **Quick Tweaks:** Get items and resources, unlock vehicles, manipulate facts with ease 14 | just by typing the in-game name of a thing in the search bar. 15 | 16 | ## Requirements 17 | 18 | - [Cyber Engine Tweaks](https://github.com/yamashi/CyberEngineTweaks) 1.20.0 19 | - Cyberpunk 2077 1.6 20 | 21 | ## Installation 22 | 23 | 1. Download [the release archive](https://github.com/psiberx/cp2077-respector/releases). 24 | 2. Extract it into the Cyberpunk 2077 installation folder. 25 | 26 | You should have `/bin/x64/plugins/cyber_engine_tweaks/mods/respector` directory now. 27 | 28 | ## Usage 29 | 30 | ### Specs 31 | 32 | The main concept of the mod is the spec file. Spec defines: 33 | 34 | - Character Level 35 | - Street Cred 36 | - Attributes 37 | - Skills and their progression 38 | - Perks 39 | - Unused points 40 | - Equipped gear 41 | * Weapons with attachments and mods 42 | * Clothing with mods 43 | * Quick use items 44 | - Equipped cyberware 45 | * Cyberware with mods 46 | * Quickhacks 47 | - Backpack items 48 | - Crafting components 49 | - Crafting recipes 50 | - Own vehicles 51 | 52 | To get kind of New Game Plus experience, you need to save a spec in one playthrough 53 | and load that spec in another playthrough. This will transfer your character 54 | and equipment from one playthrough to another. 55 | 56 | Spec files are human readable and designed to be created and edited manually. 57 | Here is a portion of the spec file to get the overall idea: 58 | 59 | ```lua 60 | Character = { 61 | Level = 50, 62 | StreetCred = 50, 63 | Attributes = { 64 | Body = 20, 65 | Reflexes = 18, 66 | TechnicalAbility = 20, 67 | Intelligence = 9, 68 | Cool = 4, 69 | }, 70 | Skills = { 71 | Athletics = 20, 72 | Annihilation = 20, 73 | StreetBrawler = 11, 74 | Assault = 18, 75 | Handguns = 18, 76 | Blades = 15, 77 | Crafting = 19, 78 | Engineering = 20, 79 | BreachProtocol = 7, 80 | Quickhacking = 9, 81 | Stealth = 4, 82 | ColdBlood = 4, 83 | }, 84 | Perks = { 85 | ..., 86 | Engineering = { 87 | Bladerunner = 2, -- Max: 2 / Increase damage to drones, mech and robots by 20/40%. 88 | Tesla = 3, -- Max: 3 / Increase the charge multiplier for Tech weapons by 15/35/55%. 89 | Insulation = 1, -- Max: 1 / Grants immunity to shock. 90 | FuckAllWalls = 1, -- Max: 1 / Reduces the charge amount needed for Tech weapons ... 91 | LicketySplit = 2, -- Max: 2 / Tech weapons charge time is reduced by 10/20%. 92 | Superconductor = 1, -- Max: 1 / Tech weapons ignore Armor. 93 | Revamp = 1, -- Max: 0+ / Increases damage Tech weapons by 25%, increases charge ... 94 | }, 95 | ... 96 | }, 97 | }, 98 | Equipment = { 99 | -- WIDOW MAKER / Weapon / Tech / Legendary 100 | { 101 | id = "Preset_Achilles_Nash_Legendary", 102 | seed = 4114643488, 103 | slots = { 104 | -- E255 PERCIPIENT / Rare 105 | -- ADS Time -0.13% / Range +0.31 106 | { slot = "Scope", id = "w_att_scope_long_02", seed = 442254023, upgrade = "Rare" }, 107 | -- COUNTERMASS / Epic 108 | { slot = "Mod1", id = "SimpleWeaponMod11" }, 109 | -- WEAKEN / Rare 110 | { slot = "Mod2", id = "SimpleWeaponMod13" }, 111 | -- CRUNCH / Epic 112 | { slot = "Mod3", id = "SimpleWeaponMod01", upgrade = "Epic" }, 113 | -- CRUNCH / Epic 114 | { slot = "Mod4", id = "SimpleWeaponMod01", upgrade = "Epic" }, 115 | }, 116 | equip = 2, 117 | }, 118 | -- MAXDOC MK.1 / Consumable / Meds / Uncommon 119 | { id = "FirstAidWhiffV0", equip = 3, qty = 149 }, 120 | ... 121 | }, 122 | Cyberware = { 123 | -- NETWATCH NETDRIVER MK.5 / Cyberware / Operating System / Legendary 124 | { 125 | id = "NetwatchNetdriverLegendaryMKV", 126 | seed = 1441747907, 127 | slots = { 128 | -- SYSTEM RESET / Legendary 129 | { slot = "Program1", id = "SystemCollapseLvl4Program" }, 130 | -- SYNAPSE BURNOUT / Epic 131 | { slot = "Program2", id = "BrainMeltLvl3Program" }, 132 | -- SHORT CIRCUIT / Rare 133 | { slot = "Program3", id = "EMPOverloadLvl2Program" }, 134 | -- REBOOT OPTICS / Rare 135 | { slot = "Program4", id = "BlindLvl2Program" }, 136 | -- PING / Rare 137 | { slot = "Program5", id = "PingLvl2Program" }, 138 | }, 139 | equip = true, 140 | }, 141 | ... 142 | }, 143 | Crafting = { 144 | Recipes = { 145 | "Preset_Zhuo_Eight_Star", -- BA XING CHONG / Legendary 146 | ... 147 | } 148 | }, 149 | Vehicles = { 150 | "v_sport1_quadra_turbo_r_player", -- TURBO-R V-TECH 151 | ... 152 | } 153 | ``` 154 | 155 | You can edit spec files with a simple text editor. By default, spec files are stored in the `specs` directory of the mod. 156 | 157 | This entire example, including comments, is generated by the mod. 158 | Here is [the full example](https://github.com/psiberx/cp2077-respector/blob/main/samples/Noname.lua). 159 | In the [`samples/specs`](https://github.com/psiberx/cp2077-respector/tree/main/samples/specs) 160 | directory you can find sample specs with comments explaining some details about spec features. 161 | 162 | ### Packs 163 | 164 | Every section of the spec file is optional and can be omitted. 165 | For example, it's possible to create the spec containing only items, 166 | thus allowing creation of item packs. 167 | 168 | In the [`samples/packs`](https://github.com/psiberx/cp2077-respector/tree/main/samples/packs) 169 | directory you can find packs of different categories. 170 | To get stuff from a pack just drop it to the `specs` directory and then load the spec you want. 171 | These packs can also be used as a reference to find the ID of item of intereset. 172 | All items have in-game names, and many of them even have full descriptions and stats. 173 | 174 | ### Scripting 175 | 176 | Beside declarative syntax the spec format also allows scripting. 177 | Here is [the example of the scripted spec](https://github.com/psiberx/cp2077-respector/blob/main/samples/specs/spec-06-scripting.lua) 178 | that for every legendary clothing mod creates a clothing item filled with that mod in each slot. 179 | 180 | ### Console 181 | 182 | To access the mod from the console you have to use `GetMod()`: 183 | 184 | ``` 185 | Respector = GetMod("respector") 186 | ``` 187 | 188 | Then you can call mod functions using the `Respector` global object. 189 | 190 | #### `Respector.LoadSpec(specName)` 191 | 192 | Loads spec named `specName`. 193 | 194 | Calling without parameters `Respector.LoadSpec()` will load spec with 195 | a default name from the [configuration](#configuration). 196 | 197 | #### `Respector.SaveSpec(specName, specOptions)` 198 | 199 | Saves spec with the name `specName` and using `specOptions`. 200 | 201 | If name is empty or `nil`, then the default name from the [configuration](#configuration) is used. 202 | 203 | Overwrites a spec file with the same name if existing. 204 | 205 | Available options for `specOptions` are: 206 | 207 | | Option | Values | Default | Description | 208 | | :--- | :---: | :---: | :--- | 209 | | `character` | `bool` | `true` | If enabled, the character levels, attributes, skills, and perks will be added to the spec. | 210 | | `allPerks` | `bool` | `false` | If enabled, all perks will be saved in the spec, including those not purchased. If disabled, only purchased perks will be saved. | 211 | | `equipment` | `bool` | `true` | If enabled, the currently equipped items will be added to the spec. | 212 | | `cyberware` | `bool` | `true` | If enabled, the currently equipped cyberware will be added to the spec. | 213 | | `backpack` | `bool` | `true` | If enabled, items in the backpack will be added to the spec. | 214 | | `components` | `bool` | `true` | If enabled, crafting components will be added to the spec. | 215 | | `recipes` | `bool` | `true` | If enabled, crafting recipes will be added to the spec. | 216 | | `vehicles` | `bool` | `true` | If enabled, own vehicles will be added to the spec. | 217 | | `itemFormat` | `"auto"`,
`"hash"`,
`"struct"` | `"auto"` | The preferred ItemID format for use in item specs:
`"auto"` – Use hash name whenever possible.
`"hash"` – Always use a raw hash value (eg. `0x00000018026C324A`).
`"struct"` – Always use a struct with hash and length (eg. `{ hash = 0x026C324A, length = 27 }`). | 218 | | `keepSeed` | `"auto"`,
`"always"` | `"auto"` | How to save the RNG seed in item specs:
`"auto"` – Save the seed only for items that can be randomized.
`"always"` – Always save the seed for all items. | 219 | | `timestamp` | `bool` | `false` | If enabled, saves the spec with a name appended with the current date and time (eg. `V-201210-042037`). Useful to never overwrite existing spec files, only create new ones. | 220 | 221 | Any particular option and `specOptions` parameter itself can be omitted. 222 | In this case, the default option values will be used. 223 | The defaults for most of the options can be changed in the [configuration](#configuration). 224 | 225 | For example, `Respector.SaveSpec("Legend", { itemFormat = "hash", recipes = false, timestamp = true })` 226 | will create a spec named `Legend-210105-142037` (assuming it's January 5, 2021, 14:20) 227 | containing everything but crafting recipes and having hash values for item IDs instead of names. 228 | 229 | Calling without parameters `Respector.SaveSpec()` will save the spec with a default name, overwriting the existing one, and using all default options. 230 | 231 | #### `Respector.SaveSnap()` 232 | 233 | Saves spec with a default name appended with the current date and time. 234 | Has the same results as `Respector.SaveSpec(nil, { timestamp = true })` but is slightly shorter. 235 | 236 | #### `Respector.ExecSpec(specData)` 237 | 238 | Applies spec defined by `specData`. The format of the spec data is the same as for spec files. 239 | 240 | For example, this useful snippet `Respector.ExecSpec({ Character = { Perks = {} } })` 241 | will reset all purchased Perks and restore spent Perk Points. 242 | 243 | ### GUI 244 | 245 | The mod window opens with the Cyber Engine Tweaks console, 246 | which is usually opened with the backtick (`` ` ``) or tilde (`~`) key. 247 | 248 | #### Save / Load 249 | 250 | The mod has a simple GUI that allows you to save and load specs. 251 | You can choose the options for saving specs and see recent history. 252 | 253 | ![Respector Save](https://siberx.dev/cp2077-respector/respector-110-01-save.png) ![Respector Load](https://siberx.dev/cp2077-respector/respector-110-02-load.png) 254 | 255 | #### Respec 256 | 257 | Here you can reset Perk Points, like with the TABULA E-RASA shard, and respec the Attributes. 258 | Lowering an Attribute will lower the corresponding Skills and reset the Perks, which requirements are no longer met. 259 | 260 | ![Respector Respec](https://siberx.dev/cp2077-respector/respector-110-03-respec.png) 261 | 262 | #### Options 263 | 264 | Here you can switch the operating mode. 265 | If you are using a character that exceeds the base game limits and are using other mods that exceed the base game capabilities, 266 | then you will probably need to enable *Unlimited Mode* in order for the mod to properly handle your build. 267 | 268 | ![Respector Options](https://siberx.dev/cp2077-respector/respector-110-04-options.png) 269 | 270 | #### Quick Tweaks 271 | 272 | Hitting green button will open another window with Quick Tweaks. 273 | 274 | Start the search by typing in your query or by selecting a recent search from the list. 275 | Middle clicking on the recent search will remove it from the list. 276 | 277 | ![Quick Tweaks](https://siberx.dev/cp2077-respector/respector-110-11-qt-search.png) 278 | 279 | You can find a different kind of things in the database. For example, weapons that you can produce right there or unlock for crafting. 280 | 281 | ![Quick Tweaks Weapon](https://siberx.dev/cp2077-respector/respector-110-12-qt-comrade.png) 282 | 283 | Another example is a vehicle that you can unlock for yourself if you haven't done so before: 284 | 285 | ![Quick Tweaks Vehicle](https://siberx.dev/cp2077-respector/respector-110-13-qt-shion.png) 286 | 287 | Also, you can find game facts. They indicate what you have done or can do. 288 | 289 | ![Quick Tweaks Fact](https://siberx.dev/cp2077-respector/respector-110-14-qt-judy.png) 290 | 291 | The classic cheat is also there: 292 | 293 | ![Quick Tweaks Money](https://siberx.dev/cp2077-respector/respector-110-15-qt-money.png) 294 | 295 | Some items can be found by group tags. For example, to find all parts of the Corpo Set outfit you can use simple request: 296 | 297 | ![Quick Tweaks Corpo Set](https://siberx.dev/cp2077-respector/respector-110-16-qt-corpo-set.png) 298 | 299 | For clothing items there is an extra option to always get the max mod slots. 300 | 301 | There are also a couple of simple hacks: 302 | 303 | ![Quick Tweaks Hack](https://siberx.dev/cp2077-respector/respector-110-19-qt-hack.png) 304 | 305 | ### Configuration 306 | 307 | The configuration is stored in the `config.lua` file. 308 | 309 | | Parameter | Default | Options | Description | 310 | | :--- | :---: | :---: | :--- | 311 | | `specsDir` | `""` | `string` | The directory for storing spec files. If empty then the `specs` dir of the mod is used. | 312 | | `defaultSpec` | `"V"` | `string` | The defalt spec name. Used when saving and loading without specifying a spec name (aka quick saving an quick loading). | 313 | | `defaultOptions` | [`{...}`](#spec-options) | [`{...}`](#spec-options) | Default options for saving specs. See [spec options](#spec-options) for details. | 314 | | `useGui` | `true` | `bool` | Enables the GUI. | 315 | | `useModApi` | `true` | `bool` | Enables API access using `GetMod()`. | 316 | 317 | A copy of the config file with default values can be found at `samples/config/defaults.lua`. 318 | 319 | ## TODO 320 | 321 | - Ability to manage the individual stats of the weapon. 322 | 323 | ## Credits 324 | 325 | - [yamashi](https://github.com/yamashi), [WSSDude420](https://github.com/WSSDude420) and [Cyber Engine Tweaks](https://github.com/yamashi/CyberEngineTweaks) team 326 | - [WolvenKit](https://github.com/WolvenKit), [WopsS](https://github.com/WopsS), [rfuzzo](https://github.com/rfuzzo), [Rick Gibbed](https://github.com/gibbed), [PixelRick](https://github.com/PixelRick) and all researchers 327 | - [NLDW#1337](https://docs.google.com/spreadsheets/d/1aVeMGOg7mhrz-vgs5fFTrr72bh0kudK0jQmtSDYPjBQ/edit?usp=sharing), [clairepliz4647](https://docs.google.com/spreadsheets/d/1RGy4O_jqIPp9k9wokJP-wr8uZPTquxNDKwLs1em6EUU/edit?usp=sharing), all contributors to public spreadsheets and wikis 328 | -------------------------------------------------------------------------------- /bin/release.cmd: -------------------------------------------------------------------------------- 1 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 2 | 3 | @set GameDir=D:\Games\Cyberpunk 2077 4 | @set SevenZipExe=C:\Program Files\7-Zip\7z.exe 5 | 6 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 7 | 8 | @set WorkDir=%CD% 9 | @set DistDir=%WorkDir%\dist 10 | 11 | @set ModPath=bin\x64\plugins\cyber_engine_tweaks\mods\respector 12 | 13 | @findstr /r "[0-9]\.[0-9]\.[0-9]*" "%GameDir%\%ModPath%\mod\Respector.lua" > "%WorkDir%\release.ver" 14 | @set /p Version=<"%WorkDir%\release.ver" 15 | @set Version=%Version:~31,5% 16 | @del /f "%WorkDir%\release.ver" > nul 17 | 18 | @set ReleaseZip=%DistDir%\respector-%Version%.zip 19 | 20 | @echo Version: %Version% 21 | @echo Release: %ReleaseZip% 22 | 23 | @if not exist "%DistDir%" mkdir "%DistDir%" 24 | @if exist "%ReleaseZip%" del /f "%ReleaseZip%" 25 | 26 | @cd "%GameDir%" 27 | 28 | @"%SevenZipExe%" a -tzip -mx9 ^ 29 | -x!"%ModPath%\.dev\" ^ 30 | -x!"%ModPath%\.git\" ^ 31 | -x!"%ModPath%\.idea\" ^ 32 | -x!"%ModPath%\bin\" ^ 33 | -x!"%ModPath%\dist\" ^ 34 | -x!"%ModPath%\mod\data\*.csv" ^ 35 | -x!"%ModPath%\mod\data\*.txt" ^ 36 | -x!"%ModPath%\mod\data\*.xlsm" ^ 37 | -x!"%ModPath%\mod\data\tweakdb-ids.*" ^ 38 | -x!"%ModPath%\mod\data\tweakdb-names.*" ^ 39 | -x!"%ModPath%\specs\??*.lua" ^ 40 | -x!"%ModPath%\.gitignore" ^ 41 | -x!"%ModPath%\debug.lua" ^ 42 | -x!"%ModPath%\db.sqlite3" ^ 43 | -x!"%ModPath%\respector*.log" ^ 44 | "%ReleaseZip%" ^ 45 | "%ModPath%" ^ 46 | > nul 47 | 48 | @echo Done. 49 | 50 | @cd "%WorkDir%" 51 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- The directory for storing spec files. 3 | -- If empty then the "specs" dir of the mod is used. 4 | specsDir = "", 5 | 6 | -- The defalt spec name. 7 | -- Used when saving and loading without specifying a spec name (aka quick saving an quick loading). 8 | defaultSpec = "V", 9 | 10 | -- Default options for saving specs. 11 | defaultOptions = { 12 | 13 | -- If enabled, the character levels, attributes, skills, and perks will be added to the spec. 14 | -- If disabled, the character data will NOT be added to the spec. 15 | character = true, 16 | 17 | -- If enabled, all perks will be saved in the spec, including those not purchased. 18 | -- If disabled, only purchased perks will be saved. 19 | allPerks = false, 20 | 21 | -- If enabled, the currently equipped items will be added to the spec. 22 | -- If disabled, the current equipment will NOT be added to the spec. 23 | equipment = true, 24 | 25 | -- If enabled, the currently equipped cyberware will be added to the spec. 26 | -- If disabled, the current cyberware will NOT be added to the spec. 27 | cyberware = true, 28 | 29 | -- If enabled, items in the backpack will be added to the spec. 30 | -- If disabled, items in the backpack will NOT be added to the spec. 31 | backpack = true, 32 | 33 | -- Filter backpack items. 34 | rarity = false, 35 | 36 | -- If enabled, crafting components will be added to the spec. 37 | -- If disabled, crafting components will NOT be added to the spec. 38 | components = true, 39 | 40 | -- If enabled, crafting recipes will be added to the spec. 41 | -- If disabled, crafting recipes will NOT be added to the spec. 42 | recipes = true, 43 | 44 | -- If enabled, own vehicles will be added to the spec. 45 | -- If disabled, vehicles will NOT be added to the spec. 46 | vehicles = true, 47 | 48 | -- The preferred ItemID format for use in item specs: 49 | -- "auto" - Use hash name whenever possible. 50 | -- "hash" - Always use a struct with hash and length values (eg. `{ hash = 0x026C324A, length = 27 }`). 51 | itemFormat = "auto", 52 | 53 | -- How to save the RNG seed in the item spec: 54 | -- "auto" - Save the seed only for items that can be randomized. 55 | -- "always" - Always save the seed for all items. 56 | keepSeed = "auto", 57 | }, 58 | 59 | -- Enables the GUI. 60 | useGui = true, 61 | 62 | -- Enables API access using `GetMod()`. 63 | useModApi = true, 64 | } -------------------------------------------------------------------------------- /debug.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local export = mod.require('mod/utils/export') 3 | 4 | local debug = {} 5 | 6 | function debug.dump(t, keys) 7 | local dump 8 | 9 | if type(t) == 'table' then 10 | if keys then 11 | dump = export.keys(t) 12 | else 13 | dump = export.table(t) 14 | end 15 | elseif type(t) == 'string' then 16 | dump = DumpType(t) 17 | else 18 | dump = Dump(t) 19 | end 20 | 21 | return dump 22 | end 23 | 24 | return debug -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- -------------------------------------------------------------------------- -- 2 | -- Cyber Engine Tweaks Version 3 | -- -------------------------------------------------------------------------- -- 4 | 5 | local cetVer = tonumber((GetVersion():gsub('^v(%d+)%.(%d+)%.(%d+)(.*)', function(major, minor, patch, wip) 6 | return ('%d.%02d%02d%d'):format(major, minor, patch, (wip == '' and 0 or 1)) 7 | end))) or 1.21 8 | 9 | -- -------------------------------------------------------------------------- -- 10 | -- Debug Mode 11 | -- -------------------------------------------------------------------------- -- 12 | -- Enables debug output in the console. 13 | -- -------------------------------------------------------------------------- -- 14 | 15 | local debugMode = false 16 | 17 | -- -------------------------------------------------------------------------- -- 18 | -- Developer Mode 19 | -- -------------------------------------------------------------------------- -- 20 | -- 1. Resets Lua package cache forcing require() to always read the source. 21 | -- 2. Recalculates hashes of known TweakDBIDs on every mod load. 22 | -- 3. Collects localized name and description of TweakDB records. 23 | -- 4. Recompiles the samples on every mod load. 24 | -- 5. Recreates default confing file on every mod load. 25 | -- -------------------------------------------------------------------------- -- 26 | 27 | local devMode = false 28 | 29 | -- -------------------------------------------------------------------------- -- 30 | 31 | local coreReq = 'mod.lua' 32 | 33 | if cetVer <= 1.0906 then 34 | coreReq = 'plugins/cyber_engine_tweaks/mods/respector/mod' 35 | 36 | if package.loaded[coreReq] ~= nil then 37 | package.loaded[coreReq] = nil 38 | 39 | if devMode then 40 | package.loaded[coreReq .. '-state'] = nil 41 | end 42 | 43 | if debugMode then 44 | print(('[DEBUG] Respector: Reloaded module %q.'):format(coreReq)) 45 | end 46 | end 47 | end 48 | 49 | local api = {} 50 | local mod = require(coreReq) 51 | 52 | mod.init(debugMode) 53 | 54 | local Respector = mod.require('mod/Respector') 55 | local respector = Respector:new() 56 | 57 | local Tweaker = mod.require('mod/Tweaker') 58 | local tweaker = Tweaker:new(respector) 59 | 60 | if mod.config.useModApi or mod.config.useGlobalApi then 61 | if mod.debug then 62 | print(('[DEBUG] Respector: Initializing CLI...')) 63 | end 64 | 65 | local cli = mod.require('mod/ui/cli') 66 | 67 | cli.init(respector, tweaker) 68 | 69 | if mod.config.useModApi then 70 | api = cli.getModApi() 71 | end 72 | end 73 | 74 | if mod.config.useGui then 75 | if mod.debug then 76 | print(('[DEBUG] Respector: Initializing GUI...')) 77 | end 78 | 79 | local gui = mod.require('mod/ui/gui') 80 | 81 | registerForEvent('onInit', function() 82 | if devMode then 83 | local Compiler = mod.require('mod/Compiler') 84 | local compiler = Compiler:new() 85 | 86 | compiler:run() 87 | end 88 | 89 | gui.init(respector, tweaker) 90 | end) 91 | 92 | registerForEvent('onOverlayOpen', gui.onOverlayOpen) 93 | registerForEvent('onOverlayClose', gui.onOverlayClose) 94 | 95 | registerForEvent('onDraw', gui.onDrawEvent) 96 | end 97 | 98 | registerForEvent('onUpdate', mod.onUpdateEvent) 99 | 100 | --print(('Respector v%s loaded.'):format(respector.version)) 101 | 102 | return api -------------------------------------------------------------------------------- /mod.lua: -------------------------------------------------------------------------------- 1 | local mod = { 2 | baseReq = (...) and string.gsub(({...})[1], '[/.]mod$', '') or '', 3 | baseDir = (...) and string.gsub(({...})[2], '/mod%.lua$', ''):gsub('\\', '/') .. '/' or '', 4 | config = {}, 5 | debug = false, 6 | } 7 | 8 | local loaded = {} 9 | local timers = {} 10 | local counter = 0 11 | local debug 12 | 13 | function mod.init(debugMode) 14 | mod.debug = debugMode 15 | 16 | mod.configure() 17 | 18 | debug = mod.load('debug') 19 | end 20 | 21 | function mod.configure() 22 | mod.config = mod.load('config') or {} 23 | end 24 | 25 | function mod.dir(path) 26 | local result = path 27 | 28 | if not result:find('[\\/]$') then 29 | result = result .. '/' 30 | end 31 | 32 | if not result:find('^[./]') and not result:find('^%a:') then 33 | result = mod.baseDir .. result 34 | end 35 | 36 | return result 37 | end 38 | 39 | function mod.path(path) 40 | local result = path 41 | 42 | if not result:find('[^/]%.(%w?%w?%w?%w?)$') then 43 | result = result .. '.lua' 44 | end 45 | 46 | if not result:find('^[./]') and not result:find('^%a:') then 47 | result = mod.baseDir .. result 48 | end 49 | 50 | return result 51 | end 52 | 53 | function mod.load(path) 54 | local chunk = loadfile(mod.path(path)) 55 | 56 | return chunk and chunk(mod, debug) or nil 57 | end 58 | 59 | function mod.require(path) 60 | if not loaded[path] then 61 | loaded[path] = mod.load(path) 62 | end 63 | 64 | return loaded[path] 65 | end 66 | 67 | ---@param timeout number 68 | ---@param recurring boolean 69 | ---@param callback function 70 | ---@param args 71 | ---@return any 72 | local function addTimer(timeout, recurring, callback, args) 73 | if type(timeout) ~= 'number' then 74 | return 75 | end 76 | 77 | if timeout <= 0 then 78 | return 79 | end 80 | 81 | if type(recurring) ~= 'boolean' then 82 | return 83 | end 84 | 85 | if type(callback) ~= 'function' then 86 | return 87 | end 88 | 89 | if type(args) ~= 'table' then 90 | args = { arg = args } 91 | end 92 | 93 | counter = counter + 1 94 | 95 | local timer = { 96 | id = counter, 97 | callback = callback, 98 | recurring = recurring, 99 | timeout = timeout, 100 | delay = timeout, 101 | args = args, 102 | } 103 | 104 | if args.id == nil then 105 | args.id = timer.id 106 | end 107 | 108 | table.insert(timers, timer) 109 | 110 | return timer.id 111 | end 112 | 113 | ---@param timeout number 114 | ---@param callback function 115 | ---@param data 116 | ---@return any 117 | function mod.after(timeout, callback, data) 118 | return addTimer(timeout, false, callback, data) 119 | end 120 | 121 | ---@param timeout number 122 | ---@param callback function 123 | ---@param data 124 | ---@return any 125 | function mod.every(timeout, callback, data) 126 | return addTimer(timeout, true, callback, data) 127 | end 128 | 129 | ---@param timerId any 130 | ---@return void 131 | function mod.halt(timerId) 132 | if type(timerId) == 'table' then 133 | timerId = timerId.id 134 | end 135 | 136 | for i, timer in ipairs(timers) do 137 | if timer.id == timerId then 138 | table.remove(timers, i) 139 | break 140 | end 141 | end 142 | end 143 | 144 | ---@param delta number 145 | ---@return void 146 | function mod.onUpdateEvent(delta) 147 | if #timers > 0 then 148 | for i, timer in ipairs(timers) do 149 | timer.delay = timer.delay - delta 150 | 151 | if timer.delay <= 0 then 152 | if timer.recurring then 153 | timer.delay = timer.delay + timer.timeout 154 | else 155 | table.remove(timers, i) 156 | i = i - 1 157 | end 158 | 159 | timer.callback(timer.args) 160 | end 161 | end 162 | end 163 | end 164 | 165 | mod.onUpdate = mod.onUpdateEvent 166 | 167 | return mod -------------------------------------------------------------------------------- /mod/Compiler.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local Quality = mod.require('mod/enums/Quality') 3 | 4 | local Compiler = {} 5 | Compiler.__index = Compiler 6 | 7 | function Compiler:new() 8 | local this = {} 9 | 10 | setmetatable(this, self) 11 | 12 | return this 13 | end 14 | 15 | function Compiler:run() 16 | self:rehashTweakDbIds() 17 | self:collectTweakDbInfo() 18 | self:collectPerkInfo() 19 | self:compileSamplePacks() 20 | self:writeDefaultConfig() 21 | end 22 | 23 | function Compiler:rehashTweakDbIds(stringsListPath, hashNamesDbPath, hashNamesCsvPath) 24 | if not stringsListPath then 25 | stringsListPath = mod.path('mod/data/tweakdb-strings.txt') 26 | end 27 | 28 | local fin = io.open(stringsListPath, 'r') 29 | 30 | if not fin then 31 | return 32 | end 33 | 34 | if not hashNamesDbPath then 35 | hashNamesDbPath = mod.path('mod/data/tweakdb-ids.lua') 36 | end 37 | 38 | if not hashNamesCsvPath then 39 | hashNamesCsvPath = mod.path('mod/data/tweakdb-ids.csv') 40 | end 41 | 42 | local TweakDb = mod.require('mod/helpers/TweakDb') 43 | 44 | local fdb = io.open(hashNamesDbPath, 'w') 45 | local fcsv = io.open(hashNamesCsvPath, 'w') 46 | 47 | fdb:write('return {\n') 48 | 49 | local allowedGroups = { 50 | Ammo = true, 51 | AttachmentSlots = true, 52 | Items = true, 53 | Vehicle = true, 54 | } 55 | 56 | for line in fin:lines() do 57 | local group, name = line:match('^(%w+)%.(.+)$') 58 | local valid = false 59 | 60 | if not group then 61 | name = line:match('^()$') 62 | if name then 63 | valid = true 64 | end 65 | elseif allowedGroups[group] and not name:find('%.') and not name:find('_inline%d+$') then 66 | valid = (group ~= 'Vehicle' or name:find('^v_')) 67 | if valid then 68 | name = group .. '.' .. name 69 | end 70 | end 71 | 72 | if valid then 73 | local hash = TweakDb.toKey(name) 74 | 75 | fdb:write(string.format('[0x%016X] = %q,\n', hash, name)) 76 | fcsv:write(string.format('%s,0x%016X\n', name, hash)) 77 | end 78 | end 79 | 80 | fdb:write('}') 81 | 82 | fin:close() 83 | fdb:close() 84 | fcsv:close() 85 | 86 | if mod.debug then 87 | print(('[DEBUG] Respector: Rehashed TweakDBIDs using list %q.'):format(stringsListPath)) 88 | end 89 | end 90 | 91 | function Compiler:collectTweakDbInfo(outputCsvPath) 92 | if not outputCsvPath then 93 | outputCsvPath = mod.path('mod/data/tweakdb-info.csv') 94 | end 95 | 96 | local fcsv = io.open(outputCsvPath, 'w') 97 | 98 | local TweakDb = mod.require('mod/helpers/TweakDb') 99 | local tweakDb = TweakDb:new('mod/data/tweakdb-ids') 100 | 101 | local normalize = function(str) 102 | return str:gsub('"', '""'):gsub('“', '""'):gsub('’', '\''):gsub('–', '-') 103 | end 104 | 105 | local bool = function(value) 106 | return value and 'TRUE' or 'FALSE' 107 | end 108 | 109 | local boolopt = function(value) 110 | return value and 'TRUE' or '' 111 | end 112 | 113 | local iconicModId = TweakDBID('Quality.IconicItem') 114 | 115 | for key, id in tweakDb:each() do 116 | local record = TweakDB:GetRecord(TweakDb.toTweakId(key)) 117 | 118 | if record and record:IsA('gamedataBaseObject_Record') then 119 | local name = GetLocalizedTextByKey(record:DisplayName()) 120 | local desc = '' 121 | local ability = '' 122 | local category = '' 123 | local quality = '' 124 | local craftable = false 125 | local iconic = false 126 | 127 | if record:IsA('gamedataItem_Record') and (record:ItemCategory() or record:IsPart()) then 128 | desc = GetLocalizedTextByKey(record:LocalizedDescription()) 129 | category = record:IsPart() and 'Mod' or NameToString(record:ItemCategory():Name()) 130 | 131 | local qualityData = record:Quality() 132 | if qualityData and qualityData:Value() > 0 then 133 | quality = ('%d-%s'):format(qualityData:Value() + 1, qualityData:Name()) 134 | end 135 | 136 | local attachData = record:GetOnAttachItem(0) 137 | if attachData then 138 | local uiData = attachData:UIData() 139 | if uiData then 140 | ability = GetLocalizedText(uiData:LocalizedDescription()) 141 | end 142 | end 143 | 144 | local craftingData = record:CraftingData() 145 | if craftingData then 146 | craftable = true 147 | end 148 | 149 | for _, statMod in ipairs(record:StatModifiers()) do 150 | if statMod:GetID() == iconicModId then 151 | iconic = true 152 | break 153 | end 154 | end 155 | elseif record:IsA('gamedataVehicle_Record') then 156 | category = 'Vehicle' 157 | end 158 | 159 | if name ~= '' and category ~= '' then 160 | fcsv:write(('0x%016X,%s,"%s","%s","%s","%s","%s","%s","%s"\n'):format( 161 | key, id, normalize(name), normalize(desc), normalize(ability), 162 | category, quality, boolopt(iconic), bool(craftable) 163 | )) 164 | end 165 | end 166 | end 167 | 168 | tweakDb:unload() 169 | 170 | fcsv:close() 171 | 172 | if mod.debug then 173 | print(('[DEBUG] Respector: Collected TweakDB display names and descriptions.')) 174 | end 175 | end 176 | 177 | function Compiler:collectPerkInfo(outputInfoPath, outputSchemaPath) 178 | if not outputInfoPath then 179 | outputInfoPath = mod.path('mod/data/perks-info.lua') 180 | end 181 | 182 | if not outputSchemaPath then 183 | outputSchemaPath = mod.path('mod/data/perks-schema.lua') 184 | end 185 | 186 | local finfo = io.open(outputInfoPath, 'w') 187 | finfo:write('return {\n') 188 | 189 | local fschema = io.open(outputSchemaPath, 'w') 190 | fschema:write('return {\n') 191 | 192 | local sep = false 193 | 194 | --for _, attrRec in ipairs(TweakDB:GetRecords('gamedataAttribute_Record')) do 195 | -- local attr = attrRec:EnumName() 196 | -- for _, skillRec in ipairs(attrRec:Proficiencies()) do 197 | -- local skill = skillRec:EnumName().value 198 | 199 | local skillMetas = { 200 | { id = "Proficiencies.Athletics", name = "Athletics", attr = "Body" }, 201 | { id = "Proficiencies.Demolition", name = "Annihilation", attr = "Body" }, 202 | { id = "Proficiencies.Brawling", name = "Street Brawler", attr = "Body" }, 203 | { id = "Proficiencies.Assault", name = "Assault", attr = "Reflexes" }, 204 | { id = "Proficiencies.Gunslinger", name = "Handguns", attr = "Reflexes" }, 205 | { id = "Proficiencies.Kenjutsu", name = "Blades", attr = "Reflexes" }, 206 | { id = "Proficiencies.Crafting", name = "Crafting", attr = "TechnicalAbility" }, 207 | { id = "Proficiencies.Engineering", name = "Engineering", attr = "TechnicalAbility" }, 208 | { id = "Proficiencies.Hacking", name = "Breach Protocol", attr = "Intelligence" }, 209 | { id = "Proficiencies.CombatHacking", name = "Quickhacking", attr = "Intelligence" }, 210 | { id = "Proficiencies.Stealth", name = "Stealth", attr = "Cool" }, 211 | { id = "Proficiencies.ColdBlood", name = "Cold Blood", attr = "Cool" }, 212 | } 213 | 214 | for _, skillMeta in ipairs(skillMetas) do 215 | local skillRec = TweakDB:GetRecord(skillMeta.id) 216 | local skill = skillMeta.name:gsub('[^%w]', '') 217 | local attr = skillMeta.attr 218 | 219 | if sep then 220 | finfo:write('\n') 221 | else 222 | sep = true 223 | end 224 | 225 | finfo:write(('\t-- %s\n'):format(skillMeta.name)) 226 | 227 | fschema:write('{\n') 228 | fschema:write(('\tname = %q,\n'):format(skill)) 229 | fschema:write('\tscope = "Perks",\n') 230 | fschema:write('\tchildren = {\n') 231 | 232 | for _, areaRec in ipairs(skillRec:PerkAreas()) do 233 | local req = math.max(3, areaRec:Requirement():ValueToCheck()) 234 | for _, perkRec in ipairs(areaRec:Perks()) do 235 | local type = perkRec:EnumName().value 236 | local max = perkRec:GetLevelsCount() 237 | local name = GetLocalizedText(perkRec:Loc_name_key()) 238 | local desc = GetLocalizedText(perkRec:Loc_desc_key()):gsub('{.+}', 'X') 239 | local alias = name:gsub('%s%l', string.upper):gsub('[^%w]', ''):gsub('^200', 'TwoHundred') 240 | 241 | finfo:write( 242 | ('\t{ alias = %q, type = %q, max = %d, attr = %q, req = %d, skill = %q, name = %q, desc = %q },\n') 243 | :format(alias, type, max, attr, req, skill, name, desc) 244 | ) 245 | 246 | fschema:write(('\t\t{ name = %q, comment = perkDescription },\n'):format(alias)) 247 | end 248 | end 249 | 250 | local traitRec = skillRec:Trait() 251 | local type = traitRec:EnumName().value 252 | local req = traitRec:Requirement():ValueToCheck() 253 | local max = 999 254 | local name = GetLocalizedText(traitRec:Loc_name_key()) 255 | local desc = GetLocalizedText(traitRec:Loc_desc_key()):gsub('{.+}', 'X') 256 | local alias = name:gsub('%s%l', string.upper):gsub('[^%w]', '') 257 | 258 | finfo:write( 259 | ('\t{ alias = %q, type = %q, max = %d, attr = %q, trait = true, req = %d, skill = %q, name = %q, desc = %q },\n') 260 | :format(alias, type, max, attr, req, skill, name, desc) 261 | ) 262 | 263 | fschema:write(('\t\t{ name = %q, comment = perkDescription },\n'):format(alias)) 264 | fschema:write('\t},\n') 265 | fschema:write('},\n') 266 | end 267 | 268 | finfo:write('}') 269 | finfo:close() 270 | 271 | fschema:write('}') 272 | fschema:close() 273 | end 274 | 275 | function Compiler:compileSamplePacks(samplePacksDir, samplePacks) 276 | if not samplePacksDir then 277 | samplePacksDir = mod.dir('samples/packs') 278 | end 279 | 280 | if not samplePacks then 281 | samplePacks = mod.load('mod/data/sample-packs') 282 | end 283 | 284 | local partSlots = mod.load('mod/data/attachment-slots') 285 | 286 | local SpecStore = mod.require('mod/stores/SpecStore') 287 | local specStore = SpecStore:new(samplePacksDir) 288 | 289 | local TweakDb = mod.require('mod/helpers/TweakDb') 290 | local tweakDb = TweakDb:new(true) 291 | 292 | for _, samplePack in ipairs(samplePacks) do 293 | if mod.debug then 294 | print(('[DEBUG] Respector: Creating sample pack %q...'):format(samplePack.name)) 295 | end 296 | 297 | local specName = samplePack.name 298 | local specData = {} 299 | 300 | specData._comment = samplePack.desc or '' 301 | 302 | if samplePack.items then 303 | local itemSpecs = {} 304 | 305 | samplePack.items.ref = false 306 | 307 | for _, itemMeta in tweakDb:filter(samplePack.items) do 308 | if itemMeta.kind ~= 'Pack' then 309 | local itemSpec = {} 310 | 311 | itemSpec._comment = tweakDb:describe(itemMeta, true, true) 312 | itemSpec._order = tweakDb:order(itemMeta) 313 | 314 | if itemMeta.comment then 315 | itemSpec._comment = itemSpec._comment .. '\n' .. itemMeta.comment --itemMeta.comment:gsub('([.!?]) ', '%1\n') 316 | end 317 | 318 | if itemMeta.desc then 319 | itemSpec._comment = itemSpec._comment .. '\n' .. itemMeta.desc:gsub('([.!?]) ', '%1\n') 320 | end 321 | 322 | itemSpec.id = TweakDb.toItemAlias(itemMeta.id) 323 | 324 | if samplePack.items.kind == 'Cyberware' then 325 | local itemSlots = {} 326 | 327 | for _, partSlot in ipairs(partSlots) do 328 | if tweakDb:match(itemMeta, partSlot.criteria) then 329 | local matched = true 330 | 331 | if itemMeta.group2 == 'Cyberdeck' then 332 | local slotsNum = Quality.toValue(itemMeta.quality) + 1 333 | 334 | if partSlot.index > slotsNum then 335 | matched = false 336 | end 337 | end 338 | 339 | if matched then 340 | table.insert(itemSlots, { slot = partSlot.slot }) 341 | end 342 | end 343 | end 344 | 345 | if #itemSlots > 0 then 346 | itemSpec.slots = itemSlots 347 | itemSpec._inline = false 348 | end 349 | 350 | elseif samplePack.name == 'stash-wall' then 351 | if itemMeta.quest then 352 | itemSpec.quest = false 353 | end 354 | end 355 | 356 | table.insert(itemSpecs, itemSpec) 357 | end 358 | end 359 | 360 | tweakDb:sort(itemSpecs) 361 | 362 | specData.Inventory = itemSpecs 363 | end 364 | 365 | if samplePack.vehicles then 366 | local vehicleSpecs = {} 367 | 368 | samplePack.vehicles.ref = false 369 | 370 | for _, vehicleMeta in tweakDb:filter(samplePack.vehicles) do 371 | local vehicleSpec = {} 372 | 373 | vehicleSpec[1] = vehicleMeta.id 374 | vehicleSpec._comment = tweakDb:describe(vehicleMeta) 375 | vehicleSpec._order = tweakDb:order(vehicleMeta) 376 | 377 | table.insert(vehicleSpecs, vehicleSpec) 378 | end 379 | 380 | tweakDb:sort(vehicleSpecs) 381 | 382 | specData.Vehicles = vehicleSpecs 383 | end 384 | 385 | specStore:writeSpec(specName, specData) 386 | end 387 | 388 | tweakDb:unload() 389 | end 390 | 391 | function Compiler:writeDefaultConfig(configPath) 392 | local Configuration = mod.require('mod/Configuration') 393 | local configuration = Configuration:new() 394 | 395 | configuration:writeDefaults(configPath) 396 | end 397 | 398 | function Compiler:writeDefaultSpec() 399 | local SpecStore = mod.require('mod/stores/SpecStore') 400 | local specStore = SpecStore:new() 401 | 402 | specStore:writeSpec(nil, { 403 | _comment = { 404 | 'This is just a placeholder.', 405 | 'If you want to try to load a spec then copy "samples/Noname.lua" here.', 406 | } 407 | }) 408 | end 409 | 410 | return Compiler -------------------------------------------------------------------------------- /mod/Configuration.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local StructWriter = mod.require('mod/helpers/StructWriter') 3 | 4 | local Configuration = {} 5 | Configuration.__index = Configuration 6 | 7 | function Configuration:new() 8 | local this = {} 9 | 10 | setmetatable(this, self) 11 | 12 | return this 13 | end 14 | 15 | function Configuration:writeConfig(configData) 16 | local configPath = mod.path('config') 17 | local configSchema = mod.load('mod/data/config-schema') 18 | 19 | local configWriter = StructWriter:new(configSchema) 20 | 21 | configWriter:writeStruct(configPath, configData or mod.config) 22 | 23 | if mod.debug then 24 | print(('[DEBUG] Respector: Saved config at %q.'):format(configPath)) 25 | end 26 | end 27 | 28 | function Configuration:resetConfig(configData) 29 | self:writeConfig(configData or {}) 30 | end 31 | 32 | function Configuration:writeDefaults(defaultsPath) 33 | local configPath = defaultsPath or mod.path('samples/config/defaults') 34 | local configWriter = StructWriter:new(mod.load('mod/data/config-schema')) 35 | 36 | configWriter:writeStruct(configPath, {}) 37 | 38 | if mod.debug then 39 | print(('[DEBUG] Respector: Saved config defaults at %q.'):format(configPath)) 40 | end 41 | end 42 | 43 | return Configuration -------------------------------------------------------------------------------- /mod/Respector.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | 3 | local Respector = { version = '1.5.2' } 4 | Respector.__index = Respector 5 | 6 | local asyncWait = false 7 | 8 | local components = { 9 | modules = { 10 | { name = 'character', source = 'mod/modules/Character' }, 11 | { name = 'inventory', source = 'mod/modules/Inventory' }, 12 | { name = 'crafting', source = 'mod/modules/Crafting' }, 13 | { name = 'transport', source = 'mod/modules/Transport' }, 14 | }, 15 | stores = { 16 | { name = 'specStore', source = 'mod/stores/SpecStore' }, 17 | } 18 | } 19 | 20 | function Respector:new() 21 | local this = { eventHandlers = {} } 22 | 23 | setmetatable(this, self) 24 | 25 | this:loadComponents() 26 | 27 | return this 28 | end 29 | 30 | function Respector:loadComponents() 31 | for _, componentList in pairs(components) do 32 | for _, component in ipairs(componentList) do 33 | if mod.debug then 34 | print(('[DEBUG] Respector: Loading %q component...'):format(component.name)) 35 | end 36 | 37 | local componentType = mod.require(component.source) 38 | 39 | if componentType then 40 | self[component.name] = componentType:new() 41 | end 42 | end 43 | end 44 | end 45 | 46 | function Respector:prepareModules() 47 | for _, module in ipairs(components.modules) do 48 | self[module.name]:prepare() 49 | end 50 | end 51 | 52 | function Respector:prepareModules() 53 | for _, module in ipairs(components.modules) do 54 | self[module.name]:prepare() 55 | end 56 | end 57 | 58 | function Respector:releaseModules() 59 | for _, module in ipairs(components.modules) do 60 | self[module.name]:release() 61 | end 62 | end 63 | 64 | function Respector:releaseModulesAsync(waitTime) 65 | asyncWait = true 66 | 67 | mod.after(waitTime or 2.0, function() 68 | self:releaseModules() 69 | 70 | asyncWait = false 71 | end) 72 | end 73 | 74 | function Respector:usingModule(moduleName, callback) 75 | if not self[moduleName] then 76 | return nil 77 | end 78 | 79 | self[moduleName]:prepare() 80 | 81 | local result = callback(self[moduleName]) 82 | 83 | self[moduleName]:release() 84 | 85 | return result 86 | end 87 | 88 | function Respector:usingModuleAsync(moduleName, waitTime, callback) 89 | if not self[moduleName] then 90 | return nil 91 | end 92 | 93 | self[moduleName]:prepare() 94 | 95 | local result = callback(self[moduleName]) 96 | 97 | asyncWait = true 98 | 99 | mod.after(waitTime or 1.0, function() 100 | self[moduleName]:release() 101 | 102 | asyncWait = false 103 | end) 104 | 105 | return result 106 | end 107 | 108 | function Respector:saveSpec(specName, specOptions) 109 | if asyncWait then 110 | return false 111 | end 112 | 113 | specOptions = self:getSpecOptions(specOptions) 114 | 115 | self:prepareModules() 116 | 117 | local specData = {} 118 | 119 | for _, module in ipairs(components.modules) do 120 | if mod.debug then 121 | print(('[DEBUG] Respector: Filling spec using %q module...'):format(module.name)) 122 | end 123 | 124 | self[module.name]:fillSpec(specData, specOptions) 125 | end 126 | 127 | self:releaseModulesAsync() 128 | 129 | if not specData then 130 | print(('Respector: Failed to create spec.')) 131 | return false 132 | end 133 | 134 | local success, specName = self.specStore:writeSpec(specName, specData, specOptions.timestamp) 135 | 136 | if success then 137 | self:triggerEvent('save', specName) 138 | 139 | print(('Respector: Spec %q saved.'):format(specName)) 140 | else 141 | print(('Respector: Failed to save %q spec.'):format(specName)) 142 | end 143 | 144 | return success 145 | end 146 | 147 | function Respector:loadSpec(specName, specOptions) 148 | if asyncWait then 149 | return false 150 | end 151 | 152 | local specData, specName = self.specStore:readSpec(specName) 153 | 154 | if not specData then 155 | print(('Respector: Can\'t load %q spec.'):format(specName)) 156 | return false 157 | end 158 | 159 | specOptions = self:getSpecOptions(specOptions) 160 | 161 | self:prepareModules() 162 | 163 | for _, module in ipairs(components.modules) do 164 | if mod.debug then 165 | print(('[DEBUG] Respector: Applying spec using %q module...'):format(module.name)) 166 | end 167 | 168 | self[module.name]:applySpec(specData, specOptions) 169 | end 170 | 171 | self:releaseModulesAsync() 172 | 173 | self:triggerEvent('load', specName) 174 | 175 | print(('Respector: Spec %q loaded.'):format(specName)) 176 | 177 | return true 178 | end 179 | 180 | function Respector:execSpec(specData, specOptions) 181 | if asyncWait then 182 | return false 183 | end 184 | 185 | specOptions = self:getSpecOptions(specOptions) 186 | 187 | self:prepareModules() 188 | 189 | for _, module in ipairs(components.modules) do 190 | if mod.debug then 191 | print(('[DEBUG] Respector: Applying spec using %q module...'):format(module.name)) 192 | end 193 | 194 | self[module.name]:applySpec(specData, specOptions) 195 | end 196 | 197 | self:releaseModulesAsync() 198 | 199 | if mod.debug then 200 | print(('[DEBUG] Respector: Quick spec applied.')) 201 | end 202 | 203 | return true 204 | end 205 | 206 | function Respector:getSpecOptions(specOptions) 207 | if not specOptions then 208 | specOptions = {} 209 | elseif specOptions == true then 210 | specOptions = { timestamp = true } 211 | end 212 | 213 | for optionName, optionDefault in pairs(mod.config.defaultOptions) do 214 | if specOptions[optionName] == nil then 215 | specOptions[optionName] = optionDefault 216 | end 217 | end 218 | 219 | return specOptions 220 | end 221 | 222 | function Respector:addEventHandler(handler) 223 | table.insert(self.eventHandlers, handler) 224 | end 225 | 226 | function Respector:triggerEvent(event, specName) 227 | if #self.eventHandlers > 0 then 228 | local eventData = { 229 | event = event, 230 | time = os.date('%d.%m.%Y %H:%M'), 231 | specName = specName, 232 | } 233 | 234 | for _, eventHandler in ipairs(self.eventHandlers) do 235 | eventHandler(eventData) 236 | end 237 | end 238 | end 239 | 240 | return Respector -------------------------------------------------------------------------------- /mod/Tweaker.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local TweakDb = mod.require('mod/helpers/TweakDb') 3 | 4 | local Tweaker = {} 5 | Tweaker.__index = Tweaker 6 | 7 | function Tweaker:new(respector) 8 | local this = { respector = respector } 9 | 10 | setmetatable(this, self) 11 | 12 | return this 13 | end 14 | 15 | function Tweaker:addPack(packSpec, cheatMode) 16 | local tweakDb = TweakDb:new(true) 17 | 18 | local itemSpecs = {} 19 | local recipeSpecs = {} 20 | local vehicleSpecs = {} 21 | 22 | for itemKey, itemMeta in tweakDb:filter(self:toPackCriteria(packSpec)) do 23 | if TweakDb.isRealKey(itemKey) then 24 | if packSpec.type == 'Recipe' then 25 | table.insert(recipeSpecs, itemMeta.id) 26 | elseif itemMeta.kind == 'Vehicle' then 27 | table.insert(vehicleSpecs, itemMeta.id) 28 | else 29 | local itemSpec = {} 30 | 31 | itemSpec.id = itemMeta.id 32 | 33 | if not itemMeta.quality then 34 | if packSpec.upgrade then 35 | itemSpec.upgrade = packSpec.upgrade 36 | elseif itemMeta.max then 37 | itemSpec.upgrade = itemMeta.max 38 | end 39 | end 40 | 41 | if itemMeta.kind == 'Clothing' then 42 | itemSpec.slots = 'max' 43 | end 44 | 45 | itemSpec.quest = false 46 | 47 | table.insert(itemSpecs, itemSpec) 48 | end 49 | end 50 | end 51 | 52 | tweakDb:unload() 53 | 54 | local specData = { 55 | Backpack = itemSpecs, 56 | Vehicles = vehicleSpecs, 57 | Crafting = { 58 | Recipes = recipeSpecs 59 | } 60 | } 61 | 62 | local specOptions = { 63 | cheat = cheatMode 64 | } 65 | 66 | self.respector:execSpec(specData, specOptions) 67 | end 68 | 69 | function Tweaker:getPackSize(packSpec) 70 | local tweakDb = TweakDb:new(true) 71 | 72 | local packSize = 0 73 | 74 | for itemKey, _ in tweakDb:filter(self:toPackCriteria(packSpec)) do 75 | if TweakDb.isRealKey(itemKey) then 76 | packSize = packSize + 1 77 | end 78 | end 79 | 80 | tweakDb:unload() 81 | 82 | return packSize 83 | end 84 | 85 | function Tweaker:toPackCriteria(packSpec) 86 | if packSpec.kind == 'Recipe' then 87 | return { 88 | kind = { 'Weapon', 'Clothing', 'Cyberware', 'Mod', 'Grenade', 'Consumable', 'Quickhack', 'Ammo' } 89 | } 90 | end 91 | 92 | return { 93 | kind = packSpec.kind, 94 | group = packSpec.group, 95 | group2 = packSpec.group2, 96 | iconic = packSpec.iconic, 97 | tag = packSpec.tag, 98 | set = packSpec.set, 99 | quality = packSpec.quality, 100 | --craft = packSpec.craft, 101 | } 102 | end 103 | 104 | function Tweaker:addItem(itemSpec, cheatMode) 105 | self.respector:execSpec({ Inventory = { itemSpec } }, { cheat = cheatMode }) 106 | end 107 | 108 | function Tweaker:addRecipe(itemId) 109 | self.respector:usingModule('crafting', function(crafting) 110 | crafting:addRecipe(itemId) 111 | end) 112 | end 113 | 114 | function Tweaker:addRecipes(itemIds) 115 | self.respector:usingModule('crafting', function(crafting) 116 | crafting:addRecipes(itemIds) 117 | end) 118 | end 119 | 120 | function Tweaker:getResource(resourceId) 121 | resourceId = TweakDb.toItemId(resourceId, false) 122 | 123 | return Game.GetTransactionSystem():GetItemQuantity(Game.GetPlayer(), resourceId) 124 | end 125 | 126 | function Tweaker:addResource(resourceId, resourceAmount) 127 | resourceId = TweakDb.toItemId(resourceId, false) 128 | 129 | Game.GetTransactionSystem():GiveItem(Game.GetPlayer(), resourceId, resourceAmount) 130 | end 131 | 132 | function Tweaker:hasVehicle(vehicleId) 133 | return self.respector:usingModule('transport', function(transport) 134 | return transport:isVehicleUnlocked(vehicleId) 135 | end) 136 | end 137 | 138 | function Tweaker:canHaveVehicle(vehicleId) 139 | return self.respector:usingModule('transport', function(transport) 140 | return transport:isVehicleUnlockable(vehicleId) 141 | end) 142 | end 143 | 144 | function Tweaker:addVehicle(vehicleId) 145 | self.respector:usingModule('transport', function(transport) 146 | transport:unlockVehicle(vehicleId) 147 | end) 148 | end 149 | 150 | function Tweaker:spawnVehicle(vehicleId) 151 | self:execHack('SpawnVehicle', vehicleId) 152 | end 153 | 154 | function Tweaker:getFact(factName) 155 | return Game.GetQuestsSystem():GetFactStr(factName) == 1 156 | end 157 | 158 | function Tweaker:setFact(factName, factState) 159 | Game.GetQuestsSystem():SetFactStr(factName, factState and 1 or 0) 160 | end 161 | 162 | function Tweaker:execHack(tweakName, ...) 163 | local tweakFunc = mod.load('mod/hacks/' .. tweakName) 164 | 165 | if type(tweakFunc) == 'function' then 166 | tweakFunc(select(1, ...)) 167 | end 168 | end 169 | 170 | return Tweaker -------------------------------------------------------------------------------- /mod/data/attachment-slots.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { id = "AttachmentSlots.Scope", slot = "Scope", criteria = { kind = "Weapon", group = { "Power", "Smart", "Tech" } } }, 3 | { id = "AttachmentSlots.PowerModule", slot = "Muzzle", criteria = { kind = "Weapon", group = { "Power", "Smart", "Tech" } } }, 4 | { id = "AttachmentSlots.GenericWeaponMod1", slot = "Mod1", index = 1, criteria = { kind = "Weapon", group = "GenericWeapon" } }, 5 | { id = "AttachmentSlots.GenericWeaponMod2", slot = "Mod2", index = 2, criteria = { kind = "Weapon", group = "GenericWeapon" } }, 6 | { id = "AttachmentSlots.GenericWeaponMod3", slot = "Mod3", index = 3, criteria = { kind = "Weapon", group = "GenericWeapon" } }, 7 | { id = "AttachmentSlots.GenericWeaponMod4", slot = "Mod4", index = 4, criteria = { kind = "Weapon", group = "GenericWeapon" } }, 8 | { id = "AttachmentSlots.PowerWeaponModRare", slot = "ModR", index = 11, criteria = { kind = "Weapon", group = "GenericWeapon" } }, 9 | { id = "AttachmentSlots.PowerWeaponModEpic", slot = "ModE", index = 12, criteria = { kind = "Weapon", group = "GenericWeapon" } }, 10 | { id = "AttachmentSlots.PowerWeaponModLegendary", slot = "ModL", index = 13, criteria = { kind = "Weapon", group = "GenericWeapon" } }, 11 | { id = "AttachmentSlots.MeleeWeaponMod1", slot = "Mod1", index = 1, criteria = { kind = "Weapon", group = "Melee" } }, 12 | { id = "AttachmentSlots.MeleeWeaponMod2", slot = "Mod2", index = 2, criteria = { kind = "Weapon", group = "Melee" } }, 13 | { id = "AttachmentSlots.MeleeWeaponMod3", slot = "Mod3", index = 3, criteria = { kind = "Weapon", group = "Melee" } }, 14 | { id = "AttachmentSlots.HeadFabricEnhancer1", slot = "Mod1", index = 1, criteria = { kind = "Clothing", group = "Head" } }, 15 | --{ id = "AttachmentSlots.HeadFabricEnhancer2", slot = "Mod2", index = 2, criteria = { kind = "Clothing", group = "Head" } }, 16 | --{ id = "AttachmentSlots.HeadFabricEnhancer3", slot = "Mod3", index = 3, criteria = { kind = "Clothing", group = "Head" } }, 17 | --{ id = "AttachmentSlots.HeadFabricEnhancer4", slot = "Mod4", index = 4, criteria = { kind = "Clothing", group = "Head" } }, 18 | { id = "AttachmentSlots.FaceFabricEnhancer1", slot = "Mod1", index = 1, criteria = { kind = "Clothing", group = "Face" } }, 19 | --{ id = "AttachmentSlots.FaceFabricEnhancer2", slot = "Mod2", index = 2, criteria = { kind = "Clothing", group = "Face" } }, 20 | --{ id = "AttachmentSlots.FaceFabricEnhancer3", slot = "Mod3", index = 3, criteria = { kind = "Clothing", group = "Face" } }, 21 | --{ id = "AttachmentSlots.FaceFabricEnhancer4", slot = "Mod4", index = 4, criteria = { kind = "Clothing", group = "Face" } }, 22 | { id = "AttachmentSlots.InnerChestFabricEnhancer1", slot = "Mod1", index = 1, criteria = { kind = "Clothing", group = "Inner Torso" } }, 23 | { id = "AttachmentSlots.InnerChestFabricEnhancer2", slot = "Mod2", index = 2, criteria = { kind = "Clothing", group = "Inner Torso" } }, 24 | --{ id = "AttachmentSlots.InnerChestFabricEnhancer3", slot = "Mod3", index = 3, criteria = { kind = "Clothing", group = "Inner Torso" } }, 25 | --{ id = "AttachmentSlots.InnerChestFabricEnhancer4", slot = "Mod4", index = 4, criteria = { kind = "Clothing", group = "Inner Torso" } }, 26 | { id = "AttachmentSlots.OuterChestFabricEnhancer1", slot = "Mod1", index = 1, criteria = { kind = "Clothing", group = "Outer Torso" } }, 27 | { id = "AttachmentSlots.OuterChestFabricEnhancer2", slot = "Mod2", index = 2, criteria = { kind = "Clothing", group = "Outer Torso" } }, 28 | --{ id = "AttachmentSlots.OuterChestFabricEnhancer3", slot = "Mod3", index = 3, criteria = { kind = "Clothing", group = "Outer Torso" } }, 29 | --{ id = "AttachmentSlots.OuterChestFabricEnhancer4", slot = "Mod4", index = 4, criteria = { kind = "Clothing", group = "Outer Torso" } }, 30 | { id = "AttachmentSlots.LegsFabricEnhancer1", slot = "Mod1", index = 1, criteria = { kind = "Clothing", group = "Legs" } }, 31 | { id = "AttachmentSlots.LegsFabricEnhancer2", slot = "Mod2", index = 2, criteria = { kind = "Clothing", group = "Legs" } }, 32 | --{ id = "AttachmentSlots.LegsFabricEnhancer3", slot = "Mod3", index = 3, criteria = { kind = "Clothing", group = "Legs" } }, 33 | --{ id = "AttachmentSlots.LegsFabricEnhancer4", slot = "Mod4", index = 4, criteria = { kind = "Clothing", group = "Legs" } }, 34 | { id = "AttachmentSlots.FootFabricEnhancer1", slot = "Mod1", index = 1, criteria = { kind = "Clothing", group = "Feet" } }, 35 | --{ id = "AttachmentSlots.FootFabricEnhancer2", slot = "Mod2", index = 2, criteria = { kind = "Clothing", group = "Feet" } }, 36 | --{ id = "AttachmentSlots.FootFabricEnhancer3", slot = "Mod3", index = 3, criteria = { kind = "Clothing", group = "Feet" } }, 37 | --{ id = "AttachmentSlots.FootFabricEnhancer4", slot = "Mod4", index = 4, criteria = { kind = "Clothing", group = "Feet" } }, 38 | { id = "AttachmentSlots.BerserkSlot1", slot = "Slot1", index = 1, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Berserk" } }, 39 | { id = "AttachmentSlots.BerserkSlot2", slot = "Slot2", index = 2, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Berserk" } }, 40 | { id = "AttachmentSlots.BerserkSlot3", slot = "Slot3", index = 3, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Berserk" } }, 41 | { id = "AttachmentSlots.SandevistanSlot1", slot = "Slot1", index = 1, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Sandevistan" } }, 42 | { id = "AttachmentSlots.SandevistanSlot2", slot = "Slot2", index = 2, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Sandevistan" } }, 43 | { id = "AttachmentSlots.SandevistanSlot3", slot = "Slot3", index = 3, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Sandevistan" } }, 44 | { id = "AttachmentSlots.CyberdeckProgram1", slot = "Program1", index = 1, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 45 | { id = "AttachmentSlots.CyberdeckProgram2", slot = "Program2", index = 2, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 46 | { id = "AttachmentSlots.CyberdeckProgram3", slot = "Program3", index = 3, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 47 | { id = "AttachmentSlots.CyberdeckProgram4", slot = "Program4", index = 4, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 48 | { id = "AttachmentSlots.CyberdeckProgram5", slot = "Program5", index = 5, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 49 | { id = "AttachmentSlots.CyberdeckProgram6", slot = "Program6", index = 6, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 50 | { id = "AttachmentSlots.CyberdeckProgram7", slot = "Program7", index = 7, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 51 | { id = "AttachmentSlots.CyberdeckProgram8", slot = "Program8", index = 8, criteria = { kind = "Cyberware", group = "Operating System", group2 = "Cyberdeck" } }, 52 | { id = "AttachmentSlots.KiroshiOpticsSlot1", slot = "Slot1", index = 1, criteria = { kind = "Cyberware", group = "Ocular System" } }, 53 | { id = "AttachmentSlots.KiroshiOpticsSlot2", slot = "Slot2", index = 2, criteria = { kind = "Cyberware", group = "Ocular System" } }, 54 | { id = "AttachmentSlots.KiroshiOpticsSlot3", slot = "Slot3", index = 3, criteria = { kind = "Cyberware", group = "Ocular System" } }, 55 | { id = "AttachmentSlots.StrongArmsKnuckles", slot = "Knuckles", criteria = { kind = "Cyberware", group = "Arms", group2 = "Gorilla Arms" } }, 56 | { id = "AttachmentSlots.StrongArmsBattery", slot = "Battery", criteria = { kind = "Cyberware", group = "Arms", group2 = "Gorilla Arms" } }, 57 | { id = "AttachmentSlots.MantisBladesEdge", slot = "Edge", criteria = { kind = "Cyberware", group = "Arms", group2 = "Mantis Blades" } }, 58 | { id = "AttachmentSlots.MantisBladesRotor", slot = "Rotor", criteria = { kind = "Cyberware", group = "Arms", group2 = "Mantis Blades" } }, 59 | { id = "AttachmentSlots.NanoWiresCable", slot = "Cable", criteria = { kind = "Cyberware", group = "Arms", group2 = "Monowire" } }, 60 | { id = "AttachmentSlots.NanoWiresBattery", slot = "Battery", criteria = { kind = "Cyberware", group = "Arms", group2 = "Monowire" } }, 61 | { id = "AttachmentSlots.ProjectileLauncherRound", slot = "Round", criteria = { kind = "Cyberware", group = "Arms", group2 = "Projectile Launch System" } }, 62 | { id = "AttachmentSlots.ProjectileLauncherWiring", slot = "Wiring", criteria = { kind = "Cyberware", group = "Arms", group2 = "Projectile Launch System" } }, 63 | { id = "AttachmentSlots.ArmsCyberwareGeneralSlot", slot = "Mod", criteria = { kind = "Cyberware", group = "Arms" } }, 64 | } -------------------------------------------------------------------------------- /mod/data/attributes.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { alias = "Body", type = "Strength", min = 3, max = 20, skills = { "Athletics", "Annihilation", "StreetBrawler" } }, 3 | { alias = "Reflexes", type = "Reflexes", min = 3, max = 20, skills = { "Assault", "Handguns", "Blades" } }, 4 | { alias = "TechnicalAbility", type = "TechnicalAbility", min = 3, max = 20, skills = { "Crafting", "Engineering" } }, 5 | { alias = "Intelligence", type = "Intelligence", min = 3, max = 20, skills = { "BreachProtocol", "Quickhacking" } }, 6 | { alias = "Cool", type = "Cool", min = 3, max = 20, skills = { "Stealth", "ColdBlood" } }, 7 | } -------------------------------------------------------------------------------- /mod/data/config-schema.lua: -------------------------------------------------------------------------------- 1 | return { 2 | children = { 3 | { 4 | name = "specsDir", 5 | comment = { 6 | "The directory for storing spec files.", 7 | "If empty then the \"specs\" dir of the mod is used.", 8 | }, 9 | default = "", 10 | margin = true, 11 | }, 12 | { 13 | name = "defaultSpec", 14 | comment = { 15 | "The defalt spec name.", 16 | "Used when saving and loading without specifying a spec name (aka quick saving an quick loading).", 17 | }, 18 | default = "V", 19 | margin = true, 20 | }, 21 | { 22 | name = "defaultOptions", 23 | comment = { 24 | "Default options for saving specs.", 25 | }, 26 | default = {}, 27 | margin = true, 28 | children = { 29 | { 30 | name = "character", 31 | comment = { 32 | "If enabled, the character levels, attributes, skills, and perks will be added to the spec.", 33 | "If disabled, the character data will NOT be added to the spec.", 34 | }, 35 | default = true, 36 | margin = "always", 37 | }, 38 | { 39 | name = "allPerks", 40 | comment = { 41 | "If enabled, all perks will be saved in the spec, including those not purchased.", 42 | "If disabled, only purchased perks will be saved.", 43 | }, 44 | default = false, 45 | margin = true, 46 | }, 47 | { 48 | name = "equipment", 49 | comment = { 50 | "If enabled, the currently equipped items will be added to the spec.", 51 | "If disabled, the current equipment will NOT be added to the spec.", 52 | }, 53 | default = true, 54 | margin = true, 55 | }, 56 | { 57 | name = "cyberware", 58 | comment = { 59 | "If enabled, the currently equipped cyberware will be added to the spec.", 60 | "If disabled, the current cyberware will NOT be added to the spec.", 61 | }, 62 | default = true, 63 | margin = true, 64 | }, 65 | { 66 | name = "backpack", 67 | comment = { 68 | "If enabled, items in the backpack will be added to the spec.", 69 | "If disabled, items in the backpack will NOT be added to the spec.", 70 | }, 71 | default = true, 72 | margin = true, 73 | }, 74 | { 75 | name = "rarity", 76 | comment = { 77 | "Filter backpack items.", 78 | }, 79 | default = false, 80 | margin = true, 81 | }, 82 | { 83 | name = "components", 84 | comment = { 85 | "If enabled, crafting components will be added to the spec.", 86 | "If disabled, crafting components will NOT be added to the spec.", 87 | }, 88 | default = true, 89 | margin = true, 90 | }, 91 | { 92 | name = "recipes", 93 | comment = { 94 | "If enabled, crafting recipes will be added to the spec.", 95 | "If disabled, crafting recipes will NOT be added to the spec.", 96 | }, 97 | default = true, 98 | margin = true, 99 | }, 100 | { 101 | name = "vehicles", 102 | comment = { 103 | "If enabled, own vehicles will be added to the spec.", 104 | "If disabled, vehicles will NOT be added to the spec.", 105 | }, 106 | default = true, 107 | margin = true, 108 | }, 109 | { 110 | name = "itemFormat", 111 | comment = { 112 | "The preferred ItemID format for use in item specs:", 113 | "\"auto\" - Use hash name whenever possible.", 114 | "\"hash\" - Always use a raw hash value (eg. `0x00000018026C324A`).", 115 | "\"struct\" - Always use a struct with hash and length (eg. `{ hash = 0x026C324A, length = 27 }`).", 116 | }, 117 | default = "auto", 118 | margin = true, 119 | }, 120 | { 121 | name = "keepSeed", 122 | comment = { 123 | "How to save the RNG seed in the item spec:", 124 | "\"auto\" - Save the seed only for items that can be randomized.", 125 | "\"always\" - Always save the seed for all items.", 126 | }, 127 | default = "auto", 128 | margin = true, 129 | }, 130 | } 131 | }, 132 | { 133 | name = "useGui", 134 | comment = { 135 | "Enables the GUI.", 136 | }, 137 | default = true, 138 | margin = true, 139 | }, 140 | { 141 | name = "useModApi", 142 | comment = { 143 | "Enables API access using `GetMod()`.", 144 | }, 145 | default = true, 146 | margin = true, 147 | }, 148 | --{ 149 | -- name = "useGlobalApi", 150 | -- comment = { 151 | -- "Enables API access using global `Respector` object.", 152 | -- }, 153 | -- default = true, 154 | -- margin = true, 155 | --}, 156 | } 157 | } -------------------------------------------------------------------------------- /mod/data/crafting-components.lua: -------------------------------------------------------------------------------- 1 | return { 2 | CommonItem = { id = "Items.CommonMaterial1", alias = "CommonItem", name = "Common Item Components" }, 3 | UncommonItem = { id = "Items.UncommonMaterial1", alias = "UncommonItem", name = "Uncommon Item Components" }, 4 | RareItem = { id = "Items.RareMaterial1", alias = "RareItem", name = "Rare Item Components" }, 5 | RareUpgrade = { id = "Items.RareMaterial2", alias = "RareUpgrade", name = "Rare Upgrade Components" }, 6 | EpicItem = { id = "Items.EpicMaterial1", alias = "EpicItem", name = "Epic Item Components" }, 7 | EpicUpgrade = { id = "Items.EpicMaterial2", alias = "EpicUpgrade", name = "Epic Upgrade Components" }, 8 | LegendaryItem = { id = "Items.LegendaryMaterial1", alias = "LegendaryItem", name = "Legendary Item Components" }, 9 | LegendaryUpgrade = { id = "Items.LegendaryMaterial2", alias = "LegendaryUpgrade", name = "Legendary Upgrade Components" }, 10 | UncommonQuickhack = { id = "Items.QuickHackUncommonMaterial1", alias = "UncommonQuickhack", name = "Uncommon Quickhack Components" }, 11 | RareQuickhack = { id = "Items.QuickHackRareMaterial1", alias = "RareQuickhack", name = "Rare Quickhack Components" }, 12 | EpicQuickhack = { id = "Items.QuickHackEpicMaterial1", alias = "EpicQuickhack", name = "Epic Quickhack Components" }, 13 | LegenaryQuickhack = { id = "Items.QuickHackLegendaryMaterial1", alias = "LegenaryQuickhack", name = "Legenary Quickhack Components" }, 14 | } -------------------------------------------------------------------------------- /mod/data/dev-type-aliases.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- Attributes (gamedataStatType) 3 | Body = "Strength", 4 | -- Skills (gamedataProficiencyType) 5 | Annihilation = "Demolition", 6 | StreetBrawler = "Brawling", 7 | Handguns = "Gunslinger", 8 | Blades = "Kenjutsu", 9 | BreachProtocol = "Hacking", 10 | Quickhacking = "CombatHacking", 11 | -- Points (gamedataDevelopmentPointType) 12 | Perk = "Primary", 13 | } -------------------------------------------------------------------------------- /mod/data/equipment-areas.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { type = "Weapon", max = 3, name = "Weapon", kind = "Weapon" }, 3 | 4 | { type = "Head", max = 1, name = "Clothing / Head", kind = "Clothing" }, 5 | { type = "Face", max = 1, name = "Clothing / Face", kind = "Clothing" }, 6 | { type = "OuterChest", max = 1, name = "Clothing / Outer Torso", kind = "Clothing" }, 7 | { type = "InnerChest", max = 1, name = "Clothing / Inner Torso", kind = "Clothing" }, 8 | { type = "Legs", max = 1, name = "Clothing / Legs", kind = "Clothing" }, 9 | { type = "Feet", max = 1, name = "Clothing / Feet", kind = "Clothing" }, 10 | { type = "Outfit", max = 1, name = "Clothing / Special", kind = "Clothing" }, 11 | 12 | { type = "SystemReplacementCW", max = 1, name = "Cyberware / Operating System", kind = "Cyberware", group = "Operating System" }, 13 | { type = "FrontalCortexCW", max = 3, name = "Cyberware / Frontal Cortex", kind = "Cyberware", group = "Frontal Cortex" }, 14 | { type = "EyesCW", max = 1, name = "Cyberware / Ocular System", kind = "Cyberware", group = "Ocular System" }, 15 | { type = "CardiovascularSystemCW", max = 3, name = "Cyberware / Circulatory System", kind = "Cyberware", group = "Circulatory System" }, 16 | { type = "ImmuneSystemCW", max = 2, name = "Cyberware / Immune System", kind = "Cyberware", group = "Immune System" }, 17 | { type = "NervousSystemCW", max = 2, name = "Cyberware / Nervous System", kind = "Cyberware", group = "Nervous System" }, 18 | { type = "IntegumentarySystemCW", max = 3, name = "Cyberware / Integumentary System", kind = "Cyberware", group = "Integumentary System" }, 19 | { type = "MusculoskeletalSystemCW", max = 2, name = "Cyberware / Skeleton", kind = "Cyberware", group = "Skeleton" }, 20 | { type = "HandsCW", max = 1, name = "Cyberware / Hands", kind = "Cyberware", group = "Hands" }, 21 | { type = "ArmsCW", max = 1, name = "Cyberware / Arms", kind = "Cyberware", group = "Arms" }, 22 | { type = "LegsCW", max = 1, name = "Cyberware / Legs", kind = "Cyberware", group = "Legs" }, 23 | 24 | { type = "QuickSlot", index = 17, max = 3, name = "Grenade", kind = "Grenade" }, 25 | { type = "Consumable", index = 14, max = 3, name = "Consumable", kind = "Consumable" }, 26 | 27 | --{ type = "AbilityCW", max = 6, name = "?" }, 28 | --{ type = "Gadget", max = 1, name = "?" }, 29 | --{ type = "Splinter", max = 1, name = "?" }, 30 | --{ type = "PersonalLink", max = 1, name = "?" }, 31 | --{ type = "UnderwearTop", max = 1, name = "?" }, 32 | --{ type = "UnderwearBottom", max = 1, name = "?" }, 33 | } -------------------------------------------------------------------------------- /mod/data/perks-by-alias.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | 3 | local perks = mod.load('mod/data/perks') 4 | local indexed = {} 5 | 6 | for _, perk in ipairs(perks) do 7 | indexed[perk.alias] = perk 8 | end 9 | 10 | return indexed -------------------------------------------------------------------------------- /mod/data/sample-packs.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | name = "clothing", 4 | items = { kind = "Clothing", tag = false, set = false }, 5 | }, 6 | { 7 | name = "clothing-mods", 8 | items = { kind = "Mod", group = "Clothing" }, 9 | }, 10 | { 11 | name = "clothing-sets", 12 | items = { kind = "Clothing", set = true }, 13 | }, 14 | { 15 | name = "clothing-unique", 16 | items = { kind = "Clothing", tag = "Unique" }, 17 | }, 18 | { 19 | name = "cyberware", 20 | items = { kind = "Cyberware" }, 21 | }, 22 | { 23 | name = "cyberware-iconic", 24 | items = { kind = "Cyberware", iconic = 1 }, 25 | }, 26 | { 27 | name = "cyberware-mods", 28 | items = { kind = "Mod", group = "Cyberware" }, 29 | }, 30 | { 31 | name = "gog", 32 | desc = "The stuff you get for buying the game in the GOG or by linking your GOG account.", 33 | items = { tag = "GOG" }, 34 | }, 35 | { 36 | name = "johnny", 37 | desc = "The stuff related to Johnny Silverhand.", 38 | items = { tag = "Johnny", iconic = 1 }, 39 | vehicles = { kind = "Vehicle", tag = "Johnny" }, 40 | }, 41 | { 42 | name = "nibbles", 43 | desc = "The cat food item that needed to obtain Nibbles as a pet.", 44 | items = { tag = "Nibbles" }, 45 | }, 46 | { 47 | name = "stash-wall", 48 | desc = "The weapons that show up on the Stash Wall.", 49 | items = { set = "Stash Wall" }, 50 | }, 51 | { 52 | name = "quickhacks", 53 | items = { kind = "Quickhack" }, 54 | }, 55 | { 56 | name = "vehicles", 57 | vehicles = { kind = "Vehicle", set = "Garage" }, 58 | }, 59 | { 60 | name = "weapons", 61 | items = { kind = "Weapon", iconic = false }, 62 | }, 63 | { 64 | name = "weapons-atts", 65 | items = { kind = "Mod", group = { "Scope", "Muzzle" } }, 66 | }, 67 | { 68 | name = "weapons-iconic", 69 | items = { kind = "Weapon", iconic = 1 }, 70 | }, 71 | { 72 | name = "weapons-mods", 73 | items = { kind = "Mod", group = { "Ranged", "Melee" } } 74 | }, 75 | { 76 | name = "patch-130", 77 | items = { tag = "DLC: V's New Jackets" }, 78 | vehicles = { tag = "DLC: Atcher Quartz Bandit" }, 79 | }, 80 | { 81 | name = "patch-150", 82 | items = { kind = { "Clothing", "Weapon", "Mod" }, tag = "Patch 1.5" }, 83 | vehicles = { kind = "Vehicle", tag = "Patch 1.5" }, 84 | }, 85 | { 86 | name = "patch-160", 87 | items = { kind = { "Clothing", "Weapon", "Mod" }, tag = "Patch 1.6" }, 88 | }, 89 | } -------------------------------------------------------------------------------- /mod/data/skills.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Athletics = { alias = "Athletics", type = "Athletics", min = 1, max = 20, attr = "Body", perkPoints = { 0, 0, 1, 1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 5, 5, 6, 6, 6, 7, 7 } }, 3 | Annihilation = { alias = "Annihilation", type = "Demolition", min = 1, max = 20, attr = "Body", perkPoints = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7 } }, 4 | StreetBrawler = { alias = "StreetBrawler", type = "Brawling", min = 1, max = 20, attr = "Body", perkPoints = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7 } }, 5 | Assault = { alias = "Assault", type = "Assault", min = 1, max = 20, attr = "Reflexes", perkPoints = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7 } }, 6 | Handguns = { alias = "Handguns", type = "Gunslinger", min = 1, max = 20, attr = "Reflexes", perkPoints = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7 } }, 7 | Blades = { alias = "Blades", type = "Kenjutsu", min = 1, max = 20, attr = "Reflexes", perkPoints = { 0, 0, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 5, 6, 7, 7, 7, 7 } }, 8 | Crafting = { alias = "Crafting", type = "Crafting", min = 1, max = 20, attr = "TechnicalAbility", perkPoints = { 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6 } }, 9 | Engineering = { alias = "Engineering", type = "Engineering", min = 1, max = 20, attr = "TechnicalAbility", perkPoints = { 0, 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 7, 7, 7 } }, 10 | BreachProtocol = { alias = "BreachProtocol", type = "Hacking", min = 1, max = 20, attr = "Intelligence", perkPoints = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 6 } }, 11 | Quickhacking = { alias = "Quickhacking", type = "CombatHacking", min = 1, max = 20, attr = "Intelligence", perkPoints = { 0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6 } }, 12 | Stealth = { alias = "Stealth", type = "Stealth", min = 1, max = 20, attr = "Cool", perkPoints = { 0, 0, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 7, 7, 7 } }, 13 | ColdBlood = { alias = "ColdBlood", type = "ColdBlood", min = 1, max = 20, attr = "Cool", perkPoints = { 0, 0, 0, 1, 2, 2, 2, 2, 3, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7 } }, 14 | } -------------------------------------------------------------------------------- /mod/data/tweakdb-meta.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-respector/ab8e8f9612654b77be2a5f3e4efdaa89c628be26/mod/data/tweakdb-meta.xlsm -------------------------------------------------------------------------------- /mod/enums/Quality.lua: -------------------------------------------------------------------------------- 1 | local Quality = {} 2 | --Quality.__index = Quality 3 | 4 | local qualityNames = { 5 | 'Common', -- 1 6 | 'Uncommon', -- 2 7 | 'Rare', -- 3 8 | 'Epic', -- 4 9 | 'Legendary', -- 5 10 | } 11 | 12 | local qualityValues = { 13 | ['Common'] = 1, 14 | ['Uncommon'] = 2, 15 | ['Rare'] = 3, 16 | ['Epic'] = 4, 17 | ['Legendary'] = 5, 18 | } 19 | 20 | local qualityColors = { 21 | ['Common'] = 0xffcccccc, 22 | ['Uncommon'] = 0xff85ed1e, -- #1eed85 23 | ['Rare'] = 0xffff9936, 24 | ['Epic'] = 0xffff42bd, -- #a537ff 25 | ['Legendary'] = 0xff3195fa, 26 | } 27 | 28 | --function Quality:new(value) 29 | -- local this = { value = value } 30 | -- 31 | -- setmetatable(this, self) 32 | -- 33 | -- return this 34 | --end 35 | 36 | function Quality.toName(value) 37 | return qualityNames[value] 38 | end 39 | 40 | function Quality.toValue(name) 41 | return qualityValues[name] 42 | end 43 | 44 | function Quality.maxValue() 45 | return #qualityNames 46 | end 47 | 48 | function Quality.toColor(name) 49 | return qualityColors[name] 50 | end 51 | 52 | function Quality.all() 53 | return qualityNames 54 | end 55 | 56 | function Quality.upTo(max) 57 | local list = {} 58 | 59 | for _, quality in ipairs(qualityNames) do 60 | table.insert(list, quality) 61 | 62 | if quality == max then 63 | break 64 | end 65 | end 66 | 67 | return list 68 | end 69 | 70 | function Quality.min(quality, max) 71 | if Quality.toValue(quality) > Quality.toValue(max) then 72 | return max 73 | end 74 | 75 | return quality 76 | end 77 | 78 | return Quality -------------------------------------------------------------------------------- /mod/enums/RarityFilter.lua: -------------------------------------------------------------------------------- 1 | local RarityFilter = {} 2 | RarityFilter.__index = RarityFilter 3 | 4 | local values = { 5 | false, 6 | 'Iconic', 7 | 'Rare', 8 | 'Rare+Iconic', 9 | 'Epic', 10 | 'Epic+Iconic', 11 | 'Legendary', 12 | 'Legendary+Iconic' 13 | } 14 | 15 | local labels = { 16 | 'Any rarity', 17 | 'Iconic only', 18 | 'Rare and higher', 19 | 'Rare and higher or Iconic', 20 | 'Epic and higher', 21 | 'Epic and higher or Iconic', 22 | 'Legendary only', 23 | 'Legendary or Iconic', 24 | } 25 | 26 | function RarityFilter:new(value) 27 | local this = { value = value } 28 | 29 | setmetatable(this, self) 30 | 31 | return this 32 | end 33 | 34 | function RarityFilter.asCriteria(value) 35 | if not value then 36 | return nil 37 | end 38 | 39 | local criteria = {} 40 | 41 | value:gsub('([^%+$]+)', function(token) 42 | if token == 'Iconic' then 43 | criteria.iconic = true 44 | else 45 | criteria.quality = token 46 | end 47 | end) 48 | 49 | return criteria 50 | end 51 | 52 | function RarityFilter.all() 53 | return values 54 | end 55 | 56 | function RarityFilter.labels() 57 | return labels 58 | end 59 | 60 | return RarityFilter -------------------------------------------------------------------------------- /mod/hacks/AutoScaleItems.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local TweakDb = mod.require('mod/helpers/TweakDb') 3 | local SimpleDb = mod.require('mod/helpers/SimpleDb') 4 | 5 | return function() 6 | local player = Game.GetPlayer() 7 | local transactionSystem = Game.GetTransactionSystem() 8 | local inventoryManager = Game.GetInventoryManager() 9 | local equipmentSystem = Game.GetScriptableSystemsContainer():Get(CName.new('EquipmentSystem')) 10 | local craftingSystem = Game.GetScriptableSystemsContainer():Get(CName.new('CraftingSystem')) 11 | local playerEquipmentData = equipmentSystem:GetPlayerData(player) 12 | 13 | playerEquipmentData['GetItemInEquipSlotArea'] = playerEquipmentData['GetItemInEquipSlot;gamedataEquipmentAreaInt32'] 14 | 15 | local tweakDb = TweakDb:new(true) 16 | local equipAreaDb = SimpleDb:new('mod/data/equipment-areas') 17 | 18 | for _, equipArea in equipAreaDb:each() do 19 | for slotIndex = 1, equipArea.max do 20 | local itemId = playerEquipmentData:GetItemInEquipSlotArea(equipArea.type, slotIndex - 1) 21 | 22 | if itemId.id.hash ~= 0 then 23 | local itemData = transactionSystem:GetItemData(player, itemId) 24 | 25 | if itemData ~= nil then 26 | craftingSystem:SetItemLevel(itemData) 27 | 28 | for _, part in ipairs(itemData:GetItemParts()) do 29 | if part then 30 | local slotId = part:GetSlotID(part) 31 | local slotMeta = tweakDb:resolve(slotId) 32 | 33 | if slotMeta and slotMeta.kind == 'Slot' then 34 | local partId = part:GetItemID(part) 35 | local partData = inventoryManager:CreateItemData(partId, player) 36 | 37 | craftingSystem:SetItemLevel(partData) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | 46 | equipAreaDb:unload() 47 | tweakDb:unload() 48 | end -------------------------------------------------------------------------------- /mod/hacks/SpawnVehicle.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local TweakDb = mod.require('mod/helpers/TweakDb') 3 | 4 | return function(vehicleTweakId, spawnDistance, unlockDoors) 5 | vehicleTweakId = TweakDb.toVehicleTweakId(vehicleTweakId) 6 | 7 | if spawnDistance == nil then 8 | spawnDistance = 7 9 | end 10 | 11 | if unlockDoors == nil then 12 | unlockDoors = true 13 | end 14 | 15 | local player = Game.GetPlayer() 16 | 17 | local forwardVector = player:GetWorldForward() 18 | local offsetVector = Vector3.new(forwardVector.x * spawnDistance, forwardVector.y * spawnDistance, forwardVector.z) 19 | 20 | local spawnTransform = player:GetWorldTransform() 21 | local spawnPosition = spawnTransform.Position:ToVector4() 22 | spawnPosition = Vector4.new(spawnPosition.x + offsetVector.x, spawnPosition.y + offsetVector.y, spawnPosition.z + offsetVector.z, spawnPosition.w) 23 | 24 | spawnTransform:SetPosition(spawnTransform, spawnPosition) 25 | 26 | local vehicleEntityId = Game.GetPreventionSpawnSystem():RequestSpawn(vehicleTweakId, 1, spawnTransform) 27 | 28 | if unlockDoors then 29 | mod.every(0.5, function(timer) 30 | local vehicleHandle = Game.FindEntityByID(vehicleEntityId) 31 | 32 | if vehicleHandle then 33 | vehicleHandle:GetVehiclePS():UnlockAllVehDoors() 34 | 35 | mod.halt(timer.id) 36 | end 37 | end) 38 | end 39 | end -------------------------------------------------------------------------------- /mod/hacks/UnlockAllSlots.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local SimpleDb = mod.require('mod/helpers/SimpleDb') 3 | local TweakDb = mod.require('mod/helpers/TweakDb') 4 | 5 | return function() 6 | local player = Game.GetPlayer() 7 | local transactionSystem = Game.GetTransactionSystem() 8 | local itemModSystem = Game.GetScriptableSystemsContainer():Get(CName.new('ItemModificationSystem')) 9 | local equipmentSystem = Game.GetScriptableSystemsContainer():Get(CName.new('EquipmentSystem')) 10 | local playerEquipmentData = equipmentSystem:GetPlayerData(player) 11 | 12 | playerEquipmentData['GetItemInEquipSlotArea'] = playerEquipmentData['GetItemInEquipSlot;gamedataEquipmentAreaInt32'] 13 | 14 | local equipAreaDb = SimpleDb:new('mod/data/equipment-areas') 15 | 16 | for _, equipArea in equipAreaDb:filter({ kind = { 'Clothing', 'Weapon' } }) do 17 | for slotIndex = 1, equipArea.max do 18 | local itemId = playerEquipmentData:GetItemInEquipSlotArea(equipArea.type, slotIndex - 1) 19 | 20 | if itemId.id.hash ~= 0 then 21 | local itemData = transactionSystem:GetItemData(player, itemId) 22 | 23 | if itemData ~= nil then 24 | for _, part in ipairs(itemData:GetItemParts()) do 25 | local slotId = part:GetSlotID(part) 26 | local partId = part:GetItemID(part) 27 | local tweakId = TweakDBID.new(partId.id) 28 | 29 | if TweakDb.isSlotBlocker(tweakId) then 30 | itemModSystem:RemoveItemPart(player, itemId, slotId, true) 31 | transactionSystem:RemoveItem(player, partId, 1) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | 39 | equipAreaDb:unload() 40 | end -------------------------------------------------------------------------------- /mod/hacks/UnmarkQuestItems.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local SimpleDb = mod.require('mod/helpers/SimpleDb') 3 | 4 | return function() 5 | local player = Game.GetPlayer() 6 | local transactionSystem = Game.GetTransactionSystem() 7 | local equipmentSystem = Game.GetScriptableSystemsContainer():Get(CName.new('EquipmentSystem')) 8 | local playerEquipmentData = equipmentSystem:GetPlayerData(player) 9 | 10 | playerEquipmentData['GetItemInEquipSlotArea'] = playerEquipmentData['GetItemInEquipSlot;gamedataEquipmentAreaInt32'] 11 | 12 | local equipAreaDb = SimpleDb:new('mod/data/equipment-areas') 13 | 14 | for _, equipArea in equipAreaDb:filter({ kind = { 'Weapon', 'Clothing' } }) do 15 | for slotIndex = 1, equipArea.max do 16 | local itemId = playerEquipmentData:GetItemInEquipSlotArea(equipArea.type, slotIndex - 1) 17 | 18 | if itemId.id.hash ~= 0 then 19 | local itemData = transactionSystem:GetItemData(player, itemId) 20 | 21 | if itemData ~= nil then 22 | if itemData:HasTag('Quest') then 23 | itemData:RemoveDynamicTag('Quest') 24 | end 25 | end 26 | end 27 | end 28 | end 29 | 30 | equipAreaDb:unload() 31 | end -------------------------------------------------------------------------------- /mod/helpers/PersistentState.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local export = mod.require('mod/utils/export') 3 | 4 | local PersistentState = {} 5 | PersistentState.__index = PersistentState 6 | 7 | function PersistentState:new(path) 8 | local this = { path = nil, state = nil } 9 | 10 | setmetatable(this, self) 11 | 12 | if path then 13 | this:load(path) 14 | end 15 | 16 | return this 17 | end 18 | 19 | function PersistentState:load(path) 20 | self.path = mod.path(path) 21 | self.state = mod.load(path) 22 | 23 | if mod.debug then 24 | print(('[DEBUG] Respector: Initialized persitent state %q.'):format(self.path)) 25 | end 26 | end 27 | 28 | function PersistentState:unload() 29 | self.state = nil 30 | end 31 | 32 | function PersistentState:isEmpty() 33 | return self.state == nil 34 | end 35 | 36 | function PersistentState:setState(state) 37 | self.state = state 38 | end 39 | 40 | function PersistentState:getState(prop) 41 | return prop and self.state[prop] or self.state 42 | end 43 | 44 | function PersistentState:flush() 45 | local stateFile = io.open(self.path, 'w') 46 | 47 | if stateFile ~= nil then 48 | stateFile:write('return ' .. export.table(self.state)) 49 | stateFile:close() 50 | 51 | if mod.debug then 52 | print(('[DEBUG] Respector: Updated persitent state %q.'):format(self.path)) 53 | end 54 | else 55 | if mod.debug then 56 | print(('[DEBUG] Respector: Failed to update persitent state %q.'):format(self.path)) 57 | end 58 | end 59 | end 60 | 61 | return PersistentState -------------------------------------------------------------------------------- /mod/helpers/SimpleDb.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | 3 | local SimpleDb = {} 4 | SimpleDb.__index = SimpleDb 5 | 6 | local shared = {} 7 | 8 | function SimpleDb:new(path, key) 9 | local this = { 10 | path = nil, 11 | key = nil, 12 | data = {}, 13 | indexed = {}, 14 | } 15 | 16 | setmetatable(this, self) 17 | 18 | if path then 19 | this:load(path, key) 20 | end 21 | 22 | return this 23 | end 24 | 25 | function SimpleDb:load(path, key) 26 | if path == self.path then 27 | return 28 | end 29 | 30 | if self.path ~= nil then 31 | self:unload() 32 | end 33 | 34 | self.path = path 35 | 36 | if not shared[self.path] then 37 | shared[self.path] = { 38 | data = mod.load(path), 39 | refs = 1 40 | } 41 | else 42 | shared[self.path].refs = shared[self.path].refs + 1 43 | end 44 | 45 | self.data = shared[self.path].data 46 | 47 | if key then 48 | self:index(key) 49 | end 50 | 51 | if mod.debug then 52 | print(('[DEBUG] Respector: Shared DB %q used by %d client(s).'):format(self.path, shared[self.path].refs)) 53 | end 54 | end 55 | 56 | function SimpleDb:index(key) 57 | self.key = key 58 | self.indexed = {} 59 | 60 | for _, item in pairs(self.data) do 61 | self.indexed[item[key]] = item 62 | end 63 | end 64 | 65 | function SimpleDb:unload() 66 | if shared[self.path] then 67 | shared[self.path].refs = shared[self.path].refs - 1 68 | 69 | if shared[self.path].refs < 1 then 70 | shared[self.path] = nil 71 | end 72 | end 73 | 74 | if mod.debug then 75 | if shared[self.path] then 76 | print(('[DEBUG] Respector: Shared DB %q used by %d client(s).'):format(self.path, shared[self.path].refs)) 77 | else 78 | print(('[DEBUG] Respector: Shared DB %q disposed.'):format(self.path)) 79 | end 80 | end 81 | 82 | self.path = nil 83 | self.key = nil 84 | self.data = {} 85 | self.indexed = {} 86 | end 87 | 88 | function SimpleDb:get(key) 89 | return self:complete(self.data[key] or self.indexed[key]) 90 | end 91 | 92 | function SimpleDb:has(key) 93 | return self.data[key] ~= nil or self.indexed[key] ~= nil 94 | end 95 | 96 | function SimpleDb:each() 97 | local key, item 98 | 99 | return function() 100 | key, item = next(self.data, key) 101 | 102 | if item ~= nil then 103 | return key, self:complete(item) 104 | end 105 | end 106 | end 107 | 108 | function SimpleDb:find(criteria) 109 | local key, item 110 | 111 | while true do 112 | key, item = next(self.data, key) 113 | 114 | if item == nil then 115 | return nil 116 | end 117 | 118 | local match = self:match(item, criteria) 119 | 120 | if match then 121 | return self:complete(item) 122 | end 123 | end 124 | end 125 | 126 | function SimpleDb:filter(criteria) 127 | local key, item 128 | 129 | return function() 130 | while true do 131 | key, item = next(self.data, key) 132 | 133 | if item == nil then 134 | return nil 135 | end 136 | 137 | local match = self:match(item, criteria) 138 | 139 | if match then 140 | return key, self:complete(item) 141 | end 142 | end 143 | end 144 | end 145 | 146 | function SimpleDb:search(term, schema) 147 | term = term:upper() 148 | 149 | local termEsc = term:gsub('([^%w])', '%%%1') 150 | local termRe = termEsc:gsub('%s+', '.* ') .. '.*' 151 | 152 | local key, item 153 | 154 | return function() 155 | while true do 156 | key, item = next(self.data, key) 157 | 158 | if item == nil then 159 | return nil 160 | end 161 | 162 | for _, param in ipairs(schema) do 163 | if item[param.field] then 164 | local value = item[param.field]:upper() 165 | 166 | if term == value then 167 | return key, self:complete(item), param.weight 168 | end 169 | end 170 | end 171 | 172 | for _, param in ipairs(schema) do 173 | if item[param.field] then 174 | local value = item[param.field]:upper() 175 | local position = value:find(termEsc) 176 | 177 | if not position then 178 | position = value:find(termRe) 179 | end 180 | 181 | if position then 182 | return key, self:complete(item), position * param.weight 183 | end 184 | end 185 | end 186 | end 187 | end 188 | end 189 | 190 | function SimpleDb:match(item, criteria) 191 | local match = true 192 | 193 | for field, condition in pairs(criteria) do 194 | if type(condition) == 'table' then 195 | match = false 196 | for _, value in ipairs(condition) do 197 | if item[field] == value then 198 | match = true 199 | break 200 | end 201 | end 202 | elseif type(condition) == 'boolean' then 203 | if (item[field] and true or false) ~= condition then 204 | match = false 205 | break 206 | end 207 | else 208 | if item[field] ~= condition then 209 | match = false 210 | break 211 | end 212 | end 213 | end 214 | 215 | return match 216 | end 217 | 218 | function SimpleDb:complete(item) 219 | return item 220 | end 221 | 222 | function SimpleDb:sort(items, field) 223 | table.sort(items, function(a, b) 224 | if field then 225 | return a[field] < b[field] 226 | end 227 | 228 | return a < b 229 | end) 230 | end 231 | 232 | function SimpleDb:limit(items, limit) 233 | while #items > limit do 234 | table.remove(items) 235 | end 236 | end 237 | 238 | function SimpleDb.unloadAll() 239 | -- Not eloquent but ok for now 240 | shared = {} 241 | end 242 | 243 | return SimpleDb -------------------------------------------------------------------------------- /mod/helpers/StructWriter.lua: -------------------------------------------------------------------------------- 1 | local StructWriter = {} 2 | StructWriter.__index = StructWriter 3 | 4 | function StructWriter:new(structSchema) 5 | local this = {} 6 | 7 | this.structSchema = structSchema 8 | 9 | setmetatable(this, self) 10 | 11 | return this 12 | end 13 | 14 | function StructWriter:writeStruct(structPath, structData) 15 | local structFile = io.open(structPath, 'w') 16 | 17 | if structFile == nil then 18 | return false 19 | end 20 | 21 | self:writeNodeData(structFile, self.structSchema, structData) 22 | 23 | io.close(structFile) 24 | 25 | return true 26 | end 27 | 28 | function StructWriter:writeNodeData(structFile, nodeSchema, nodeData, depth, inline, index) 29 | if depth == nil then 30 | depth = 0 31 | elseif depth > 32 then 32 | return 33 | end 34 | 35 | if nodeData == nil and nodeSchema.default ~= nil then 36 | nodeData = nodeSchema.default 37 | end 38 | 39 | local br = inline and ' ' or '\n' 40 | local indent = string.rep('\t', depth) 41 | 42 | local istable = type(nodeData) == 'table' 43 | local comment = (istable and nodeData._comment) or nodeSchema.comment 44 | 45 | if depth == 0 then 46 | if comment then 47 | self:writeCommentBlock(structFile, nodeSchema, nodeData, comment, indent) 48 | comment = nil 49 | end 50 | structFile:write('return ') 51 | end 52 | 53 | if nodeData ~= nil or nodeSchema.nullable then 54 | if not inline and nodeSchema.margin and (index > 1 or nodeSchema.margin == 'always') then 55 | structFile:write(br) 56 | end 57 | 58 | if comment and (istable or type(comment) == 'table') then 59 | if not inline then 60 | structFile:write(indent) 61 | end 62 | self:writeCommentBlock(structFile, nodeSchema, nodeData, comment, indent) 63 | if inline then 64 | structFile:write(indent) 65 | end 66 | comment = nil 67 | end 68 | 69 | if not inline then 70 | structFile:write(indent) 71 | end 72 | 73 | if nodeSchema.name ~= nil then 74 | structFile:write(nodeSchema.name) 75 | structFile:write(' = ') 76 | end 77 | 78 | if istable then 79 | structFile:write('{') 80 | structFile:write(br) 81 | if nodeSchema.children then 82 | local childIndex = 0 83 | for _, childSchema in ipairs(nodeSchema.children) do 84 | local childNames = { childSchema.name } 85 | if childSchema.aliases then 86 | for _, childAlias in ipairs(childSchema.aliases) do 87 | table.insert(childNames, childAlias) 88 | end 89 | end 90 | for _, childName in ipairs(childNames) do 91 | local childData = nodeData[childName] 92 | if childData ~= nil or childSchema.nullable or childSchema.default ~= nil then 93 | childIndex = childIndex + 1 94 | childSchema.name = childName 95 | local childInline = inline or childSchema.inline 96 | if not inline and childInline then 97 | structFile:write(indent .. '\t') 98 | end 99 | self:writeNodeData(structFile, childSchema, childData, depth + 1, childInline, childIndex) 100 | if not inline and childInline then 101 | structFile:write(br) 102 | end 103 | end 104 | end 105 | -- Restore original name 106 | childSchema.name = childNames[1] 107 | end 108 | if not inline then 109 | structFile:write(indent) 110 | 111 | -- fix line break if table is empty 112 | --if childIndex == 0 then 113 | -- structFile:seek('cur', -2) 114 | --end 115 | else 116 | -- fix trailing comma 117 | structFile:seek('cur', -2) 118 | structFile:write(br) 119 | end 120 | elseif nodeSchema.table then 121 | local childSchema = { children = nodeSchema.table } 122 | local childIndex = 0 123 | for _, childData in ipairs(nodeData) do 124 | if childData ~= nil then 125 | childIndex = childIndex + 1 126 | local childInline = (childData._inline == nil or childData._inline) 127 | if not inline then 128 | if (nodeSchema.spacing or childData._spacing) and childIndex > 1 then 129 | structFile:write(br) 130 | end 131 | if childInline then 132 | structFile:write(indent .. '\t') 133 | end 134 | end 135 | self:writeNodeData(structFile, childSchema, childData, depth + 1, childInline, childIndex) 136 | if not inline and childInline then 137 | structFile:write(br) 138 | end 139 | end 140 | end 141 | if not inline then 142 | structFile:write(indent) 143 | end 144 | else 145 | local childIndex = 0 146 | local childSchema = { format = nodeSchema.format } 147 | for _, childData in ipairs(nodeData) do 148 | if childData ~= nil then 149 | childIndex = childIndex + 1 150 | if type(childData) == 'table' then 151 | childSchema.comment = childData._comment 152 | self:writeNodeData(structFile, childSchema, childData[1], depth + 1, inline, childIndex) 153 | else 154 | self:writeNodeData(structFile, childSchema, childData, depth + 1, inline, childIndex) 155 | end 156 | end 157 | end 158 | if not inline then 159 | structFile:write(indent) 160 | end 161 | end 162 | structFile:write('}') 163 | elseif nodeSchema.nullable and nodeSchema.children then 164 | structFile:write('{}') 165 | else 166 | self:writeScalarValue(structFile, nodeSchema, nodeData) 167 | end 168 | 169 | if depth > 0 then 170 | structFile:write(',') 171 | end 172 | 173 | if not inline then 174 | local comment2 = comment or nodeSchema.comment2 175 | 176 | if comment2 then 177 | self:writeCommentAfter(structFile, nodeSchema, nodeData, comment2) 178 | end 179 | end 180 | 181 | if depth > 0 then 182 | structFile:write(br) 183 | end 184 | end 185 | end 186 | 187 | function StructWriter:writeScalarValue(structFile, nodeSchema, nodeData) 188 | if nodeSchema then 189 | local format 190 | if type(nodeSchema.format) == 'string' then 191 | format = nodeSchema.format 192 | elseif type(nodeSchema.format) == 'table' then 193 | format = nodeSchema.format[type(nodeData)] or nodeSchema.format.default 194 | end 195 | 196 | if format then 197 | structFile:write(string.format(format, nodeData)) 198 | return 199 | end 200 | end 201 | 202 | if type(nodeData) == 'string' then 203 | structFile:write(string.format('%q', nodeData)) 204 | return 205 | end 206 | 207 | structFile:write(tostring(nodeData)) 208 | end 209 | 210 | function StructWriter:writeCommentBlock(structFile, nodeSchema, nodeData, comment, indent) 211 | if type(comment) == 'function' then 212 | comment = comment(nodeData, nodeSchema) 213 | end 214 | 215 | if type(comment) == 'table' then 216 | for i, line in ipairs(comment) do 217 | if i > 1 then 218 | structFile:write(indent) 219 | end 220 | structFile:write('-- ') 221 | structFile:write(line) 222 | structFile:write('\n') 223 | end 224 | elseif comment ~= '' then 225 | comment = comment:gsub('\n', '\n' .. indent .. '-- ') 226 | structFile:write('-- ') 227 | structFile:write(comment) 228 | structFile:write('\n') 229 | end 230 | end 231 | 232 | function StructWriter:writeCommentAfter(structFile, nodeSchema, nodeData, comment) 233 | if type(comment) == 'function' then 234 | comment = comment(nodeData, nodeSchema) 235 | end 236 | 237 | if comment ~= '' then 238 | comment = comment:gsub('\n', ' ') 239 | 240 | structFile:write(' -- ') 241 | structFile:write(comment) 242 | end 243 | end 244 | 245 | return StructWriter -------------------------------------------------------------------------------- /mod/helpers/TweakDb.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local str = mod.require('mod/utils/str') 3 | local Quality = mod.require('mod/enums/Quality') 4 | local SimpleDb = mod.require('mod/helpers/SimpleDb') 5 | 6 | local TweakDb = {} 7 | TweakDb.__index = TweakDb 8 | 9 | setmetatable(TweakDb, { __index = SimpleDb }) 10 | 11 | local searchSchema = { 12 | { field = 'name', weight = 1 }, 13 | { field = 'tag', weight = 2 }, 14 | { field = 'set', weight = 2 }, 15 | { field = 'kind', weight = 2 }, 16 | { field = 'id', weight = 3 }, 17 | } 18 | 19 | local kindOrders = { 20 | ['Weapon'] = 1, 21 | ['Clothing'] = 2, 22 | ['Cyberware'] = 3, 23 | ['Mod'] = 11, 24 | ['Grenade'] = 21, 25 | ['Consumable'] = 22, 26 | ['Progression'] = 23, 27 | ['Quickhack'] = 31, 28 | ['Junk'] = 71, 29 | ['Recipe'] = 81, 30 | ['Component'] = 82, 31 | ['Misc'] = 91, 32 | ['Shard'] = 92, 33 | } 34 | 35 | local groupOrders = { 36 | ['Head'] = 11, 37 | ['Face'] = 12, 38 | ['Outer Torso'] = 13, 39 | ['Inner Torso'] = 14, 40 | ['Legs'] = 15, 41 | ['Feet'] = 16, 42 | ['Special'] = 17, 43 | } 44 | 45 | local itemModPrefixes = { 46 | ['Clo_Face'] = 'FaceFabricEnhancer', 47 | ['Clo_Feet'] = 'FootFabricEnhancer', 48 | ['Clo_Head'] = 'HeadFabricEnhancer', 49 | ['Clo_InnerChest'] = 'InnerChestFabricEnhancer', 50 | ['Clo_Legs'] = 'LegsFabricEnhancer', 51 | ['Clo_OuterChest'] = 'OuterChestFabricEnhancer', 52 | --['Cyb_Ability'] = 'ModPrefix', 53 | ['Cyb_Launcher'] = 'ProjectileLauncher', 54 | ['Cyb_MantisBlades'] = 'MantisBlades', 55 | ['Cyb_NanoWires'] = 'NanoWires', 56 | ['Cyb_StrongArms'] = 'StrongArms', 57 | --['Fla_Launcher'] = 'ModPrefix', 58 | --['Fla_Rifle'] = 'ModPrefix', 59 | --['Fla_Shock'] = 'ModPrefix', 60 | --['Fla_Support'] = 'ModPrefix', 61 | ['Wea_AssaultRifle'] = 'GenericWeaponMod', 62 | ['Wea_Fists'] = 'GenericWeaponMod', 63 | ['Wea_Hammer'] = 'GenericWeaponMod', 64 | ['Wea_Handgun'] = 'GenericWeaponMod', 65 | ['Wea_HeavyMachineGun'] = 'GenericWeaponMod', 66 | ['Wea_Katana'] = 'GenericWeaponMod', 67 | ['Wea_Knife'] = 'GenericWeaponMod', 68 | ['Wea_LightMachineGun'] = 'GenericWeaponMod', 69 | ['Wea_LongBlade'] = 'GenericWeaponMod', 70 | ['Wea_Melee'] = 'GenericWeaponMod', 71 | ['Wea_OneHandedClub'] = 'GenericWeaponMod', 72 | ['Wea_PrecisionRifle'] = 'GenericWeaponMod', 73 | ['Wea_Revolver'] = 'GenericWeaponMod', 74 | ['Wea_Rifle'] = 'GenericWeaponMod', 75 | ['Wea_ShortBlade'] = 'GenericWeaponMod', 76 | ['Wea_Shotgun'] = 'GenericWeaponMod', 77 | ['Wea_ShotgunDual'] = 'GenericWeaponMod', 78 | ['Wea_SniperRifle'] = 'GenericWeaponMod', 79 | ['Wea_SubmachineGun'] = 'GenericWeaponMod', 80 | ['Wea_TwoHandedClub'] = 'GenericWeaponMod', 81 | } 82 | 83 | local slotBlockers 84 | 85 | function TweakDb:load(path) 86 | if not path or path == true then 87 | path = 'mod/data/tweakdb-meta' 88 | end 89 | 90 | SimpleDb.load(self, path) 91 | end 92 | 93 | function TweakDb:resolve(tweakId) 94 | return self:get(TweakDb.toKey(tweakId)) 95 | end 96 | 97 | function TweakDb:resolvable(tweakId) 98 | return self:has(TweakDb.toKey(tweakId)) 99 | end 100 | 101 | function TweakDb:search(term) 102 | return SimpleDb.search(self, term, searchSchema) 103 | end 104 | 105 | function TweakDb:complete(itemMeta) 106 | if itemMeta and itemMeta.ref and itemMeta.ref ~= true then 107 | local refMeta = self:get(itemMeta.ref) 108 | 109 | for prop, value in pairs(refMeta) do 110 | if itemMeta[prop] == nil then 111 | itemMeta[prop] = value 112 | end 113 | end 114 | 115 | itemMeta.ref = true 116 | end 117 | 118 | return itemMeta 119 | end 120 | 121 | function TweakDb:isTaggedAsSet(itemMeta) 122 | return itemMeta.set and itemMeta.kind ~= 'Pack' and itemMeta.set:find(' Set$') 123 | end 124 | 125 | function TweakDb:describe(itemMeta, extended, sets, ellipsis) 126 | local comment = '' 127 | 128 | if itemMeta.name then 129 | comment = itemMeta.name 130 | end 131 | 132 | if sets and self:isTaggedAsSet(itemMeta) then 133 | comment = string.upper(itemMeta.set) .. ': ' .. comment 134 | end 135 | 136 | if ellipsis then 137 | comment = str.ellipsis(comment, ellipsis) 138 | end 139 | 140 | if extended then 141 | comment = comment .. ' / ' .. itemMeta.kind 142 | 143 | if itemMeta.kind ~= 'Pack' then 144 | if itemMeta.group then 145 | comment = comment .. ' / ' .. itemMeta.group 146 | end 147 | 148 | if itemMeta.kind == 'Weapon' or (itemMeta.kind == 'Mod' and itemMeta.group == 'Cyberware') then 149 | comment = comment .. ' / ' .. itemMeta.group2 150 | end 151 | end 152 | end 153 | 154 | if itemMeta.quality then 155 | comment = comment .. ' / ' .. itemMeta.quality 156 | elseif itemMeta.kind == 'Pack' and itemMeta.max then 157 | comment = comment .. ' / ' .. itemMeta.max 158 | end 159 | 160 | return comment 161 | end 162 | 163 | function TweakDb:order(itemMeta, orderKind, orderPrefix) 164 | local order = '' 165 | 166 | if orderPrefix then 167 | order = orderPrefix .. '|' 168 | end 169 | 170 | if orderKind then 171 | order = order .. ('%02d'):format(kindOrders[itemMeta.kind] or 99) .. '|' 172 | end 173 | 174 | if itemMeta.kind == 'Mod' then 175 | order = order .. itemMeta.group .. '|' 176 | 177 | if itemMeta.group == 'Cyberware' then 178 | order = order .. itemMeta.group2 .. '|' 179 | end 180 | elseif itemMeta.kind == 'Pack' then 181 | if itemMeta.pack == 'Clothing' and itemMeta.set == false and itemMeta.tag == false then 182 | order = order .. 'Z' .. ('%02d'):format(groupOrders[itemMeta.group] or 99) 183 | end 184 | elseif self:isTaggedAsSet(itemMeta) then 185 | order = order .. itemMeta.set .. '|' 186 | end 187 | 188 | if itemMeta.name then 189 | order = order .. itemMeta.name 190 | elseif itemMeta.id then 191 | order = order .. TweakDb.toItemAlias(itemMeta.id) 192 | end 193 | 194 | if itemMeta.quality then 195 | order = order .. '|' .. Quality.toValue(itemMeta.quality) 196 | --order = order .. '|' .. (Quality.maxValue() - Quality.toValue(itemMeta.quality)) 197 | elseif itemMeta.kind == 'Pack' and itemMeta.max then 198 | order = order .. '|' .. Quality.toValue(itemMeta.max) 199 | else 200 | order = order .. '|0' 201 | end 202 | 203 | if itemMeta.name and itemMeta.id then 204 | order = order .. '|' .. TweakDb.toItemAlias(itemMeta.id) 205 | end 206 | 207 | return string.upper(order) 208 | end 209 | 210 | function TweakDb:orderLast() 211 | return '99' 212 | end 213 | 214 | function TweakDb:sort(items) 215 | SimpleDb.sort(self, items, '_order') 216 | end 217 | 218 | function TweakDb.toKey(data) 219 | if type(data) == 'number' then 220 | return data 221 | end 222 | 223 | if type(data) == 'string' then 224 | data = TweakDb.toTweakId(data) 225 | end 226 | 227 | if type(data) == 'userdata' then 228 | data = TweakDb.extract(data) 229 | end 230 | 231 | if type(data) == 'table' then 232 | return data.length * 0x100000000 + data.hash 233 | --return (data.length << 32 | data.hash) 234 | end 235 | 236 | return 0 237 | end 238 | 239 | function TweakDb.isRealKey(key) 240 | return key <= 0xFFFFFFFFFF 241 | end 242 | 243 | function TweakDb.toStruct(data) 244 | if type(data) == 'table' then 245 | return data 246 | end 247 | 248 | if type(data) == 'number' then 249 | -- { hash = data & 0xFFFFFFFF, length = data >> 32 } 250 | local length = math.floor(data / 0x100000000) 251 | local hash = data - (length * 0x100000000) 252 | return { hash = hash, length = length } 253 | end 254 | 255 | if type(data) == 'string' then 256 | data = TweakDb.toTweakId(data) 257 | end 258 | 259 | if type(data) == 'userdata' then 260 | return TweakDb.extract(data) 261 | end 262 | 263 | return nil 264 | end 265 | 266 | function TweakDb.toType(tweakId, prefix) 267 | if type(tweakId) == 'string' then 268 | return str.with(tweakId, prefix) 269 | end 270 | 271 | return '' 272 | end 273 | 274 | function TweakDb.toAlias(tweakId--[[, prefix]]) 275 | return tweakId 276 | 277 | --if type(tweakId) == 'string' then 278 | -- return str.without(tweakId, prefix) 279 | --end 280 | -- 281 | --return '' 282 | end 283 | 284 | function TweakDb.toTweakId(tweakId, prefix) 285 | if type(tweakId) == 'number' then 286 | tweakId = TweakDb.toStruct(tweakId) 287 | end 288 | 289 | if type(tweakId) == 'table' then 290 | return TweakDBID.new(tweakId.hash, tweakId.length) 291 | end 292 | 293 | if type(tweakId) == 'string' then 294 | local hashHex, lenHex = tweakId:match('^$') 295 | if hashHex and lenHex then 296 | return TweakDBID.new(tonumber(hashHex, 16), tonumber(lenHex, 16)) 297 | elseif tweakId:find('%.') then 298 | return TweakDBID.new(tweakId) 299 | else 300 | return TweakDBID.new(str.with(tweakId, prefix)) 301 | end 302 | end 303 | 304 | if type(tweakId) == 'userdata' then 305 | return tweakId 306 | end 307 | end 308 | 309 | function TweakDb.toItemId(tweakId, seed) 310 | if type(tweakId) == 'string' then 311 | tweakId = TweakDb.toItemTweakId(tweakId) 312 | end 313 | 314 | if seed then 315 | return ItemID.new(tweakId, seed) 316 | elseif seed == false then 317 | return ItemID.new(tweakId) 318 | else 319 | return GetSingleton('gameItemID'):FromTDBID(tweakId) 320 | end 321 | end 322 | 323 | function TweakDb.toItemTweakId(tweakId) 324 | return TweakDb.toTweakId(tweakId, 'Items.') 325 | end 326 | 327 | function TweakDb.toItemType(alias) 328 | return TweakDb.toType(alias, 'Items.') 329 | end 330 | 331 | function TweakDb.toItemAlias(type) 332 | return TweakDb.toAlias(type, 'Items.') 333 | end 334 | 335 | function TweakDb.toVehicleTweakId(tweakId) 336 | return TweakDb.toTweakId(tweakId, 'Vehicle.') 337 | end 338 | 339 | function TweakDb.toVehicleType(alias) 340 | return TweakDb.toType(alias, 'Vehicle.') 341 | end 342 | 343 | function TweakDb.toVehicleAlias(type) 344 | return TweakDb.toAlias(type, 'Vehicle.') 345 | end 346 | 347 | function TweakDb:toSlotTweakId(slotAlias, itemMeta) 348 | if slotAlias == 'Muzzle' then 349 | slotAlias = 'PowerModule' 350 | end 351 | 352 | local slotName = str.with(slotAlias, 'AttachmentSlots.') 353 | local tweakId = TweakDBID.new(slotName) 354 | 355 | --if not TDB.GetAttachmentSlotRecord(tweakId) 356 | if not self:resolvable(tweakId) then 357 | local slotPrefix 358 | 359 | if type(itemMeta) == 'table' and itemMeta.mod then 360 | if slotAlias == 'Mod' and itemMeta.kind == 'Cyberware' and itemMeta.group == 'Arms' then 361 | slotName = 'AttachmentSlots.ArmsCyberwareGeneralSlot' 362 | else 363 | slotPrefix = itemMeta.mod 364 | end 365 | elseif type(itemMeta) == 'string' then 366 | slotPrefix = itemModPrefixes[itemMeta] 367 | end 368 | 369 | if slotPrefix then 370 | local slotMeta = self:find({ kind = 'Slot', name = slotAlias:upper(), mod = slotPrefix }) 371 | 372 | if slotMeta then 373 | slotName = slotMeta.id 374 | else 375 | local index = string.match(slotAlias, '%d$') 376 | 377 | if index ~= nil then 378 | slotName = 'AttachmentSlots.' .. slotPrefix .. index 379 | else 380 | if slotPrefix == 'GenericWeaponMod' and Quality.toValue(slotAlias) then 381 | slotName = 'AttachmentSlots.PowerWeaponMod' .. slotAlias 382 | else 383 | slotName = 'AttachmentSlots.' .. slotPrefix .. slotAlias 384 | end 385 | end 386 | end 387 | end 388 | 389 | tweakId = TweakDBID.new(slotName) 390 | end 391 | 392 | return tweakId 393 | end 394 | 395 | function TweakDb.toSlotType(alias) 396 | return TweakDb.toType(alias, 'AttachmentSlots.') 397 | end 398 | 399 | function TweakDb.localize(tweakId) 400 | tweakId = TweakDb.toTweakId(tweakId) 401 | 402 | return { 403 | name = Game.GetLocalizedTextByKey(Game['TDB::GetLocKey;TweakDBID'](TweakDBID.new(tweakId, '.displayName'))), 404 | comment = Game.GetLocalizedTextByKey(Game['TDB::GetLocKey;TweakDBID'](TweakDBID.new(tweakId, '.localizedDescription'))), 405 | } 406 | end 407 | 408 | function TweakDb.extract(data) 409 | if data.hash then 410 | return { hash = data.hash, length = data.length } 411 | end 412 | 413 | if data.id then 414 | return { id = { data.id.hash, length = data.id.length }, rng_seed = data.rng_seed } 415 | end 416 | 417 | return data 418 | end 419 | 420 | function TweakDb.isSlotBlocker(tweakId) 421 | if not slotBlockers then 422 | slotBlockers = { 423 | TweakDBID('Items.BootsFabricEnhancer'), 424 | TweakDBID('Items.DummyBootsFabricEnhancer'), 425 | TweakDBID('Items.DummyFabricEnhancer'), 426 | TweakDBID('Items.DummyFabricEnhancerBase'), 427 | TweakDBID('Items.DummyFaceFabricEnhancer'), 428 | TweakDBID('Items.DummyHeadFabricEnhancer'), 429 | TweakDBID('Items.DummyPantsFabricEnhancer'), 430 | TweakDBID('Items.DummyTorsoFabricEnhancer'), 431 | TweakDBID('Items.FabricEnhancer'), 432 | TweakDBID('Items.FaceFabricEnhancer'), 433 | TweakDBID('Items.GenericFabricEnhancer'), 434 | TweakDBID('Items.HeadFabricEnhancer'), 435 | TweakDBID('Items.InnerAndOuterChestFabricEnhancer'), 436 | TweakDBID('Items.IntrinsicFabricEnhancer01'), 437 | TweakDBID('Items.IntrinsicFabricEnhancer02'), 438 | TweakDBID('Items.IntrinsicFabricEnhancer03'), 439 | TweakDBID('Items.IntrinsicFabricEnhancer04'), 440 | TweakDBID('Items.IntrinsicFabricEnhancer05'), 441 | TweakDBID('Items.IntrinsicFabricEnhancer06'), 442 | TweakDBID('Items.IntrinsicFabricEnhancer07a'), 443 | TweakDBID('Items.IntrinsicFabricEnhancer08'), 444 | TweakDBID('Items.IntrinsicFabricEnhancer09'), 445 | TweakDBID('Items.IntrinsicFabricEnhancer10'), 446 | TweakDBID('Items.IntrinsicFabricEnhancer11'), 447 | TweakDBID('Items.IntrinsicFabricEnhancer12'), 448 | TweakDBID('Items.IntrinsicFabricEnhancer13'), 449 | TweakDBID('Items.IntrinsicFabricEnhancerBase'), 450 | TweakDBID('Items.OuterChestFabricEnhancer'), 451 | TweakDBID('Items.PantsFabricEnhancer'), 452 | } 453 | end 454 | 455 | for _, slotBlocker in ipairs(slotBlockers) do 456 | if tweakId == slotBlocker then 457 | return true 458 | end 459 | end 460 | 461 | return false 462 | end 463 | 464 | return TweakDb -------------------------------------------------------------------------------- /mod/modules/Crafting.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local TweakDb = mod.require('mod/helpers/TweakDb') 3 | 4 | local CraftingModule = {} 5 | CraftingModule.__index = CraftingModule 6 | 7 | function CraftingModule:new() 8 | local this = { tweakDb = TweakDb:new() } 9 | 10 | setmetatable(this, self) 11 | 12 | return this 13 | end 14 | 15 | function CraftingModule:prepare() 16 | local scriptableSystemsContainer = Game.GetScriptableSystemsContainer() 17 | 18 | self.player = Game.GetPlayer() 19 | self.transactionSystem = Game.GetTransactionSystem() 20 | self.craftingSystem = scriptableSystemsContainer:Get(CName.new('CraftingSystem')) 21 | self.playerCraftBook = self.craftingSystem:GetPlayerCraftBook() 22 | end 23 | 24 | function CraftingModule:release() 25 | self.player = nil 26 | self.transactionSystem = nil 27 | self.craftingSystem = nil 28 | self.playerCraftBook = nil 29 | end 30 | 31 | function CraftingModule:fillSpec(specData, specOptions) 32 | if specOptions.components or specOptions.recipes then 33 | specData.Crafting = {} 34 | 35 | if specOptions.components then 36 | local componentData = self:getComponents() 37 | 38 | if componentData then 39 | specData.Crafting.Components = componentData 40 | end 41 | end 42 | 43 | if specOptions.recipes then 44 | local recipeData = self:getRecipes() 45 | 46 | if recipeData then 47 | specData.Crafting.Recipes = recipeData 48 | end 49 | end 50 | end 51 | end 52 | 53 | function CraftingModule:applySpec(specData) 54 | if specData.Crafting then 55 | if specData.Crafting.Components then 56 | self:setComponents(specData.Crafting.Components) 57 | end 58 | 59 | if specData.Crafting.Recipes then 60 | self:addRecipes(specData.Crafting.Recipes) 61 | end 62 | end 63 | end 64 | 65 | function CraftingModule:getComponents() 66 | local components = mod.load('mod/data/crafting-components') 67 | local componentSpecs = {} 68 | 69 | for componentAlias, component in pairs(components) do 70 | local componentId = ItemID.new(TweakDBID.new(component.id)) 71 | local componentQty = self.transactionSystem:GetItemQuantity(self.player, componentId) 72 | 73 | componentSpecs[componentAlias] = componentQty 74 | end 75 | 76 | return componentSpecs 77 | end 78 | 79 | function CraftingModule:setComponents(componentSpecs) 80 | local components = mod.load('mod/data/crafting-components') 81 | 82 | for componentAlias, componentQty in pairs(componentSpecs) do 83 | local component = components[componentAlias] 84 | local componentId = ItemID.new(TweakDBID.new(component.id)) 85 | local currentQty = self.transactionSystem:GetItemQuantity(self.player, componentId) 86 | 87 | if currentQty ~= componentQty then 88 | self.transactionSystem:GiveItem(self.player, componentId, componentQty - currentQty) 89 | end 90 | end 91 | end 92 | 93 | function CraftingModule:getRecipes() 94 | self.tweakDb:load('mod/data/tweakdb-meta') 95 | 96 | local recipeSpecs = {} 97 | 98 | for itemKey, itemMeta in self.tweakDb:each() do 99 | if self:isRecipeKnown(itemKey) then 100 | local recipeSpec = {} 101 | 102 | if itemMeta.id == '' then 103 | recipeSpec[1] = itemKey 104 | else 105 | recipeSpec[1] = TweakDb.toItemAlias(itemMeta.id) 106 | end 107 | 108 | recipeSpec._comment = self.tweakDb:describe(itemMeta) 109 | recipeSpec._order = self.tweakDb:order(itemMeta) 110 | 111 | table.insert(recipeSpecs, recipeSpec) 112 | end 113 | end 114 | 115 | self.tweakDb:unload() 116 | 117 | if #recipeSpecs == 0 then 118 | return nil 119 | end 120 | 121 | self.tweakDb:sort(recipeSpecs) 122 | 123 | return recipeSpecs 124 | end 125 | 126 | function CraftingModule:isRecipeKnown(tweakId) 127 | tweakId = TweakDb.toItemTweakId(tweakId) 128 | 129 | return self.playerCraftBook:KnowsRecipe(tweakId) 130 | end 131 | 132 | function CraftingModule:addRecipe(tweakId) 133 | tweakId = TweakDb.toItemTweakId(tweakId) 134 | 135 | if self.playerCraftBook:GetRecipeIndex(tweakId) < 0 then 136 | self.playerCraftBook:AddRecipe(tweakId) 137 | end 138 | end 139 | 140 | function CraftingModule:addRecipes(recipeSpecs) 141 | for _, itemId in ipairs(recipeSpecs) do 142 | self:addRecipe(itemId) 143 | end 144 | end 145 | 146 | return CraftingModule -------------------------------------------------------------------------------- /mod/modules/Transport.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local TweakDb = mod.require('mod/helpers/TweakDb') 3 | 4 | local TransportModule = {} 5 | TransportModule.__index = TransportModule 6 | 7 | function TransportModule:new() 8 | local this = { tweakDb = TweakDb:new() } 9 | 10 | setmetatable(this, self) 11 | 12 | return this 13 | end 14 | 15 | function TransportModule:prepare() 16 | self.vehicleSystem = Game.GetVehicleSystem() 17 | end 18 | 19 | function TransportModule:release() 20 | self.vehicleSystem = nil 21 | end 22 | 23 | function TransportModule:fillSpec(specData, specOptions) 24 | if specOptions.vehicles then 25 | local vehicleSpecs = self:getVehicles() 26 | 27 | if vehicleSpecs then 28 | specData.Vehicles = vehicleSpecs 29 | end 30 | end 31 | end 32 | 33 | function TransportModule:applySpec(specData) 34 | if specData.Vehicles and #specData.Vehicles > 0 then 35 | self:unlockVehicles(specData.Vehicles) 36 | end 37 | end 38 | 39 | function TransportModule:getVehicles() 40 | self.tweakDb:load('mod/data/tweakdb-meta') 41 | 42 | local vehicleSpecs = {} 43 | local vehicles = self.vehicleSystem:GetPlayerUnlockedVehicles() 44 | 45 | for _, vehicle in ipairs(vehicles) do 46 | local vehicleMeta = self.tweakDb:resolve(vehicle.recordID) 47 | 48 | if vehicleMeta then 49 | local vehicleSpec = {} 50 | vehicleSpec[1] = TweakDb.toVehicleAlias(vehicleMeta.id) 51 | vehicleSpec._comment = self.tweakDb:describe(vehicleMeta) 52 | vehicleSpec._order = self.tweakDb:order(vehicleMeta) 53 | 54 | table.insert(vehicleSpecs, vehicleSpec) 55 | end 56 | end 57 | 58 | self.tweakDb:unload() 59 | 60 | if #vehicleSpecs == 0 then 61 | return nil 62 | end 63 | 64 | self.tweakDb:sort(vehicleSpecs) 65 | 66 | return vehicleSpecs 67 | end 68 | 69 | function TransportModule:unlockVehicle(vehicle) 70 | local vehicleType = TweakDb.toVehicleType(vehicle) 71 | 72 | self.vehicleSystem:EnablePlayerVehicle(vehicleType, true, false) 73 | end 74 | 75 | function TransportModule:unlockVehicles(vehicles) 76 | for _, vehicle in ipairs(vehicles) do 77 | self:unlockVehicle(vehicle) 78 | end 79 | end 80 | 81 | function TransportModule:isVehicleUnlocked(vehicle) 82 | local tweakId = TweakDb.toVehicleTweakId(vehicle) 83 | 84 | local vehicles = self.vehicleSystem:GetPlayerUnlockedVehicles() 85 | 86 | for _, vehicle in ipairs(vehicles) do 87 | if tostring(tweakId) == tostring(vehicle.recordID) then 88 | return true 89 | end 90 | end 91 | 92 | return false 93 | end 94 | 95 | function TransportModule:isVehicleUnlockable(vehicleId) 96 | return (vehicleId:find('_player$')) and true or false 97 | end 98 | 99 | return TransportModule -------------------------------------------------------------------------------- /mod/stores/SpecStore.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local fs = mod.require('mod/utils/fs') 3 | local str = mod.require('mod/utils/str') 4 | local StructWriter = mod.require('mod/helpers/StructWriter') 5 | 6 | local SpecStore = {} 7 | SpecStore.__index = SpecStore 8 | 9 | function SpecStore:new(specsDir, defaultSpec) 10 | local this = {} 11 | 12 | this.writer = StructWriter:new(mod.load('mod/data/spec-schema')) 13 | 14 | this.specsDir = mod.dir(str.nonempty(specsDir, mod.config.specsDir, 'specs')) 15 | this.defaultSpec = str.nonempty(defaultSpec, mod.config.defaultSpec, 'V') 16 | 17 | if mod.debug then 18 | print(('[DEBUG] Respector: Created spec store using %q.'):format(this.specsDir)) 19 | end 20 | 21 | setmetatable(this, self) 22 | 23 | return this 24 | end 25 | 26 | function SpecStore:listSpecs() 27 | local specs = {} 28 | 29 | if type(dir) == 'function' then 30 | local existingSpecs = dir(self.specsDir) 31 | 32 | for _, specFileInfo in pairs(existingSpecs) do 33 | local specName = specFileInfo.name:match('^([^.].*)%.lua$') 34 | 35 | if specName then 36 | local specFile = io.open(self.specsDir .. specFileInfo.name, 'r') 37 | local specHeader = specFile:read('l') 38 | specFile:close() 39 | 40 | if specHeader ~= '-- This is just a placeholder.' then 41 | local time = specHeader:match('^-- (%d%d%.%d%d%.%d%d%d%d %d%d:%d%d)') -- :%d%d 42 | 43 | if not time then 44 | time = os.date('%d.%m.%Y %H:%M') 45 | end 46 | 47 | table.insert(specs, { specName = specName, time = time }) 48 | end 49 | end 50 | end 51 | end 52 | 53 | return specs 54 | end 55 | 56 | function SpecStore:hasSpec(specName) 57 | if not specName or specName == '' then 58 | specName = self.defaultSpec 59 | end 60 | 61 | local specPath = mod.path(self.specsDir .. specName) 62 | 63 | return fs.isfile(specPath) 64 | end 65 | 66 | function SpecStore:readSpec(specName) 67 | if not specName or specName == '' then 68 | specName = self.defaultSpec 69 | end 70 | 71 | local specPath = mod.path(self.specsDir .. specName) 72 | 73 | local specChunk = loadfile(specPath) 74 | 75 | if not specChunk then 76 | return false, specName 77 | end 78 | 79 | return specChunk(mod), specName 80 | end 81 | 82 | function SpecStore:writeSpec(specName, specData, timestamped) 83 | if type(specData) ~= 'table' then 84 | return false, specName 85 | end 86 | 87 | if not specName or specName == '' then 88 | specName = self.defaultSpec 89 | end 90 | 91 | if timestamped then 92 | specName = specName .. '-' .. os.date('%y%m%d-%H%M%S') 93 | end 94 | 95 | local specPath = mod.path(self.specsDir .. specName) 96 | 97 | if mod.debug then 98 | print(('[DEBUG] Respector: Writing spec %q...'):format(specPath)) 99 | end 100 | 101 | local success = self.writer:writeStruct(specPath, specData) 102 | 103 | return success, specName 104 | end 105 | 106 | function SpecStore:deleteSpec(specName) 107 | if not specName or specName == '' then 108 | specName = self.defaultSpec 109 | end 110 | 111 | local specPath = mod.path(self.specsDir .. specName) 112 | 113 | local success = os.remove(specPath) 114 | 115 | return success 116 | end 117 | 118 | return SpecStore -------------------------------------------------------------------------------- /mod/ui/api.lua: -------------------------------------------------------------------------------- 1 | local api = {} 2 | 3 | local respector, tweaker 4 | 5 | function api.init(_respector, _tweaker) 6 | respector = _respector 7 | tweaker = _tweaker 8 | end 9 | 10 | function api.LoadSpec(specName, specOptions, _) 11 | if specName == api then 12 | specName, specOptions = specOptions, _ 13 | end 14 | 15 | return respector:loadSpec(specName, specOptions) 16 | end 17 | 18 | function api.SaveSpec(specName, specOptions, _) 19 | if specName == api then 20 | specName, specOptions = specOptions, _ 21 | end 22 | 23 | return respector:saveSpec(specName, specOptions) 24 | end 25 | 26 | function api.SaveSnap() 27 | return respector:saveSpec(nil, { timestamp = true }) 28 | end 29 | 30 | function api.ExecSpec(specData, specOptions, _) 31 | if specData == api then 32 | specData, specOptions = specOptions, _ 33 | end 34 | 35 | return respector:execSpec(specData, specOptions) 36 | end 37 | 38 | function api.SpawnVehicle(vehicleTweakId, spawnDistance, unlockDoors, _) 39 | if vehicleTweakId == api then 40 | vehicleTweakId, spawnDistance, unlockDoors = spawnDistance, unlockDoors, _ 41 | end 42 | 43 | return tweaker:spawnVehicle(vehicleTweakId, spawnDistance, unlockDoors) 44 | end 45 | 46 | return api -------------------------------------------------------------------------------- /mod/ui/cli.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local api = mod.require('mod/ui/api') 3 | 4 | local cli = {} 5 | 6 | function cli.init(respector, tweaker) 7 | api.init(respector, tweaker) 8 | end 9 | 10 | function cli.getModApi() 11 | return api 12 | end 13 | 14 | function cli.registerGlobalApi() 15 | Respector = api 16 | 17 | if mod.debug then 18 | print(('[DEBUG] Respector: Registered global API.')) 19 | end 20 | end 21 | 22 | function cli.unregisterGlobalApi() 23 | if Respector then 24 | Respector = nil 25 | 26 | if mod.debug then 27 | print(('[DEBUG] Respector: Unregistered global API.')) 28 | end 29 | end 30 | end 31 | 32 | return cli -------------------------------------------------------------------------------- /mod/ui/gui.respec.lua: -------------------------------------------------------------------------------- 1 | local mod = ... 2 | local ImGuiX = mod.require('mod/ui/imguix') 3 | 4 | local respecGui = {} 5 | 6 | local respector, viewData, userState 7 | 8 | function respecGui.init(_respector, _viewData, _userState) 9 | respector = _respector 10 | viewData = _viewData 11 | userState = _userState 12 | 13 | respecGui.initViewState() 14 | end 15 | 16 | function respecGui.initViewData() 17 | viewData.respecAttrGroupWidth = viewData.gridOneThirdWidth 18 | viewData.respecAttrGroupHeight = 46 * viewData.viewScaleY 19 | viewData.respecAttrInputWidth = 70 * viewData.viewScaleX 20 | 21 | viewData.respecAttrs = { 22 | { attr = 'Body', label = 'Body', offsetX = 0 }, 23 | { attr = 'Reflexes', label = 'Reflexes' }, 24 | { attr = 'TechnicalAbility', label = 'Tech Ability' }, 25 | { attr = 'Intelligence', label = 'Intelligence', offsetX = (viewData.gridOneThirdWidth + viewData.gridGutter) / 2 }, 26 | { attr = 'Cool', label = 'Cool' }, 27 | } 28 | end 29 | 30 | function respecGui.initViewState() 31 | viewData.respecAttrsActive = false 32 | 33 | respector:usingModule('character', function(character) 34 | viewData.respecAttrsData = character:getAttributeLevels() 35 | viewData.respecAttrPoints = character:getAttributeEarnedPoints(userState.cheatMode) 36 | end) 37 | end 38 | 39 | -- GUI Event Handlers 40 | 41 | function respecGui.onDrawEvent(justOpened) 42 | if justOpened then 43 | respecGui.initViewData() 44 | respecGui.initViewState() 45 | end 46 | 47 | ImGui.Spacing() 48 | 49 | -- Reset Perks 50 | ImGui.Text('Reset Perks') 51 | ImGuiX.PushStyleColor(ImGuiCol.Text, 0xff9f9f9f) 52 | ImGui.TextWrapped('Restore all spent Perk Points, allowing you to redistribute them. Has the same effect as from buying the TABULA E-RASA shard.') 53 | ImGuiX.PopStyleColor() 54 | ImGui.Spacing() 55 | 56 | if ImGui.Button('Reset Perks', viewData.gridFullWidth, viewData.buttonHeight) then 57 | respecGui.onResetPerksClick() 58 | end 59 | 60 | ImGui.Spacing() 61 | ImGui.Separator() 62 | ImGui.Spacing() 63 | 64 | -- Respec Attributes 65 | ImGui.Text('Respec Attributes') 66 | ImGuiX.PushStyleColor(ImGuiCol.Text, 0xff9f9f9f) 67 | ImGui.TextWrapped('Adjust Attributes levels. Lowering an Attribute will lower corresponding Skills and reset Perks, which requirements are no longer met.') 68 | ImGuiX.PopStyleColor() 69 | 70 | local respecAttrUnusedPoints = viewData.respecAttrPoints 71 | 72 | for _, respecAttr in ipairs(viewData.respecAttrs) do 73 | local attrLevel = viewData.respecAttrsData[respecAttr.attr] 74 | 75 | respecAttrUnusedPoints = respecAttrUnusedPoints - attrLevel 76 | end 77 | 78 | ImGui.Spacing() 79 | 80 | local attrPointsText = ('Attribute Points: %-2d'):format(respecAttrUnusedPoints) 81 | local attrPointsTextWidth = ImGui.CalcTextSize(attrPointsText) 82 | 83 | ImGui.SetCursorPos(viewData.windowOffsetX + (viewData.gridFullWidth / 2) - (attrPointsTextWidth / 2), ImGui.GetCursorPosY()) 84 | ImGui.Text(attrPointsText) 85 | 86 | for i, respecAttr in ipairs(viewData.respecAttrs) do 87 | local attrLevel = viewData.respecAttrsData[respecAttr.attr] 88 | local labelWidth = ImGui.CalcTextSize(respecAttr.label) 89 | local valueWidth = viewData.respecAttrsActive and viewData.respecAttrInputWidth or (ImGui.CalcTextSize(tostring(attrLevel)) + 8) 90 | 91 | if respecAttr.offsetX then 92 | ImGui.Spacing() 93 | ImGui.SetCursorPos(viewData.windowOffsetX + respecAttr.offsetX, ImGui.GetCursorPosY()) 94 | else 95 | ImGui.SameLine() 96 | end 97 | 98 | ImGui.BeginGroup() 99 | ImGuiX.PushStyleVar(ImGuiStyleVar.FrameRounding, 8) 100 | ImGuiX.PushStyleVar(ImGuiStyleVar.FramePadding, 0, 2 * viewData.viewScaleY) 101 | ImGui.BeginChildFrame(i, viewData.respecAttrGroupWidth, viewData.respecAttrGroupHeight) 102 | ImGuiX.PopStyleVar(2) 103 | 104 | ImGui.Spacing() 105 | 106 | ImGui.SetCursorPos((viewData.respecAttrGroupWidth / 2) - (labelWidth / 2), ImGui.GetCursorPosY()) 107 | ImGui.Text(respecAttr.label) 108 | 109 | ImGui.SetCursorPos((viewData.respecAttrGroupWidth / 2) - (valueWidth / 2), ImGui.GetCursorPosY()) 110 | ImGui.SetNextItemWidth(valueWidth) 111 | ImGuiX.PushStyleColor(ImGuiCol.FrameBg, 0) 112 | if viewData.respecAttrsActive then 113 | local attrNewLevel, attrChanged = ImGui.InputInt('##Respec' .. respecAttr.attr, attrLevel, 1, 3) 114 | 115 | if attrChanged and attrNewLevel ~= attrLevel then 116 | if attrNewLevel - attrLevel > respecAttrUnusedPoints then 117 | attrNewLevel = attrLevel + respecAttrUnusedPoints 118 | ImGui.SetWindowFocus() 119 | end 120 | 121 | attrNewLevel = math.max(attrNewLevel, 3) 122 | attrNewLevel = math.min(attrNewLevel, 20) 123 | 124 | viewData.respecAttrsData[respecAttr.attr] = attrNewLevel 125 | end 126 | else 127 | ImGui.InputText('##Respec' .. respecAttr.attr, tostring(attrLevel), 2, ImGuiInputTextFlags.ReadOnly) 128 | end 129 | ImGuiX.PopStyleColor() 130 | 131 | ImGui.EndChildFrame() 132 | ImGui.EndGroup() 133 | end 134 | 135 | ImGui.Spacing() 136 | 137 | if viewData.respecAttrsActive then 138 | ImGuiX.PushStyleColor(ImGuiCol.Button, 0xaa60ae27) 139 | ImGuiX.PushStyleColor(ImGuiCol.ButtonHovered, 0xee60ae27) 140 | if ImGui.Button('Save Attributes', viewData.gridHalfWidth, viewData.buttonHeight) then 141 | respecGui.onSaveAttrsClick() 142 | end 143 | ImGuiX.PopStyleColor(2) 144 | 145 | ImGui.SameLine() 146 | 147 | ImGuiX.PushStyleColor(ImGuiCol.Button, 0xaa3c4ce7) 148 | ImGuiX.PushStyleColor(ImGuiCol.ButtonHovered, 0xee3c4ce7) 149 | if ImGui.Button('Discard Changes', viewData.gridHalfWidth, viewData.buttonHeight) then 150 | respecGui.onDiscardAttrsClick() 151 | end 152 | ImGuiX.PopStyleColor(2) 153 | else 154 | if ImGui.Button('Respec Attributes', viewData.gridFullWidth, viewData.buttonHeight) then 155 | respecGui.onRespecAttrsClick() 156 | end 157 | end 158 | end 159 | 160 | -- GUI Action Handlers 161 | 162 | function respecGui.onResetPerksClick() 163 | respector:execSpec({ Character = { Perks = {} } }, userState.specOptions) 164 | end 165 | 166 | function respecGui.onRespecAttrsClick() 167 | respecGui.initViewState() 168 | 169 | viewData.respecAttrsActive = true 170 | end 171 | 172 | function respecGui.onSaveAttrsClick() 173 | respector:execSpec({ Character = { Attributes = viewData.respecAttrsData } }, userState.specOptions) 174 | 175 | viewData.respecAttrsActive = false 176 | end 177 | 178 | function respecGui.onDiscardAttrsClick() 179 | respecGui.initViewState() 180 | end 181 | 182 | return respecGui -------------------------------------------------------------------------------- /mod/ui/imguix.lua: -------------------------------------------------------------------------------- 1 | local ImGuiX = {} 2 | 3 | local varStackDepth = 0 4 | local colorStackDepth = 0 5 | local clipStackDepth = 0 6 | 7 | -- Style Var 8 | 9 | function ImGuiX.PushStyleVar(...) 10 | ImGui.PushStyleVar(select(1, ...)) 11 | 12 | varStackDepth = varStackDepth + 1 13 | end 14 | 15 | function ImGuiX.PopStyleVar(depth) 16 | ImGui.PopStyleVar(depth or 1) 17 | 18 | varStackDepth = varStackDepth - (depth or 1) 19 | end 20 | 21 | -- Style Color 22 | 23 | function ImGuiX.PushStyleColor(...) 24 | ImGui.PushStyleColor(select(1, ...)) 25 | 26 | colorStackDepth = colorStackDepth + 1 27 | end 28 | 29 | function ImGuiX.PopStyleColor(depth) 30 | ImGui.PopStyleColor(depth or 1) 31 | 32 | colorStackDepth = colorStackDepth - (depth or 1) 33 | end 34 | 35 | -- Clip Rect 36 | 37 | function ImGuiX.PushClipRect(...) 38 | ImGui.PushClipRect(select(1, ...)) 39 | 40 | clipStackDepth = clipStackDepth + 1 41 | end 42 | 43 | function ImGuiX.PopClipRect(depth) 44 | ImGui.PopClipRect(depth or 1) 45 | 46 | clipStackDepth = clipStackDepth - (depth or 1) 47 | end 48 | 49 | -- Restore Stack 50 | 51 | function ImGuiX.RestoreStack() 52 | if varStackDepth > 0 then 53 | ImGui.PopStyleVar(varStackDepth) 54 | 55 | varStackDepth = 0 56 | end 57 | 58 | if colorStackDepth > 0 then 59 | ImGui.PopStyleColor(colorStackDepth) 60 | 61 | colorStackDepth = 0 62 | end 63 | 64 | if clipStackDepth > 0 then 65 | ImGui.PopClipRect(clipStackDepth) 66 | 67 | clipStackDepth = 0 68 | end 69 | end 70 | 71 | -- Polyfill 72 | 73 | --if not ImGuiMouseButton then 74 | -- ImGuiMouseButton = { 75 | -- Left = 0, 76 | -- Right = 1, 77 | -- Middle = 2, 78 | -- } 79 | --end 80 | 81 | --if not ImGui.BeginListBox then 82 | -- ImGui.BeginListBox = ImGui.ListBoxHeader 83 | -- ImGui.EndListBox = ImGui.ListBoxFooter 84 | --end 85 | 86 | return ImGuiX -------------------------------------------------------------------------------- /mod/utils/array.lua: -------------------------------------------------------------------------------- 1 | local array = {} 2 | 3 | function array.find(items, value) 4 | for index, item in ipairs(items) do 5 | if item == value then 6 | return index 7 | end 8 | end 9 | 10 | return nil 11 | end 12 | 13 | function array.map(items, mapper) 14 | local result = {} 15 | local isCallback = type(mapper) == 'function' 16 | 17 | for index, item in ipairs(items) do 18 | if isCallback then 19 | result[index] = mapper(item, index) 20 | else 21 | result[index] = item[mapper] 22 | end 23 | end 24 | 25 | return result 26 | end 27 | 28 | function array.sort(items, sorter) 29 | table.sort(items, function(a, b) 30 | if sorter then 31 | return a[sorter] < b[sorter] 32 | end 33 | 34 | return a < b 35 | end) 36 | end 37 | 38 | function array.limit(items, limit) 39 | while #items > limit do 40 | table.remove(items) 41 | end 42 | end 43 | 44 | --function array.splice(items, start, count) 45 | --end 46 | 47 | return array -------------------------------------------------------------------------------- /mod/utils/bit32.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | How to use: 3 | local bit32 = _VERSION == 'Lua 5.1' and bit32 or mod.require('mod/utils/bit32') 4 | ]]-- 5 | 6 | return { 7 | band = function(a, b) 8 | return a & b 9 | end, 10 | bor = function(a, b) 11 | return a | b 12 | end, 13 | lshift = function(a, b) 14 | return a << b 15 | end, 16 | rshift = function(a, b) 17 | return a << b 18 | end, 19 | } -------------------------------------------------------------------------------- /mod/utils/export.lua: -------------------------------------------------------------------------------- 1 | local export = {} 2 | 3 | function export.table(t, max, depth) 4 | if type(t) ~= 'table' then 5 | return '' 6 | end 7 | 8 | max = max or 63 9 | depth = depth or 0 10 | 11 | local dumpStr = '{\n' 12 | local indent = string.rep('\t', depth) 13 | 14 | for k, v in pairs(t) do 15 | local kstr = '' 16 | if type(k) == 'string' then 17 | kstr = string.format('[\'%s\'] = ', k) 18 | end 19 | 20 | local vstr = tostring(v) 21 | if type(v) == 'string' then 22 | vstr = string.format('\'%s\'', tostring(v)) 23 | elseif type(v) == 'table' then 24 | if depth < max then 25 | vstr = export.table(v, max, depth + 1) 26 | else 27 | vstr = '...' 28 | end 29 | end 30 | 31 | dumpStr = string.format('%s\t%s%s%s,\n', dumpStr, indent, kstr, vstr) 32 | end 33 | 34 | return string.format('%s%s}', dumpStr, indent) 35 | end 36 | 37 | function export.keys(t, max, depth) 38 | if type(t) ~= 'table' then 39 | return '' 40 | end 41 | 42 | max = max or 63 43 | depth = depth or 0 44 | 45 | local dumpStr = '{\n' 46 | local indent = string.rep('\t', depth) 47 | 48 | for k, v in pairs(t) do 49 | local kstr = tostring(k) 50 | if type(k) == 'string' then 51 | kstr = string.format('[\'%s\']', k) 52 | end 53 | 54 | local vstr = '' 55 | if type(v) == 'table' and depth < max then 56 | vstr = ' = ' .. export.keys(v, max, depth + 1) 57 | else 58 | vstr = ' = ...' 59 | end 60 | 61 | dumpStr = string.format('%s\t%s%s%s,\n', dumpStr, indent, kstr, vstr) 62 | end 63 | 64 | return string.format('%s%s}', dumpStr, indent) 65 | end 66 | 67 | return export -------------------------------------------------------------------------------- /mod/utils/fs.lua: -------------------------------------------------------------------------------- 1 | local fs = {} 2 | 3 | function fs.isfile(path) 4 | local f = io.open(path, 'r') 5 | 6 | if f == nil then 7 | return false 8 | end 9 | 10 | io.close(f) 11 | 12 | return true 13 | end 14 | 15 | return fs -------------------------------------------------------------------------------- /mod/utils/str.lua: -------------------------------------------------------------------------------- 1 | local str = {} 2 | 3 | function str.trim(s) 4 | return (s:gsub('^%s*(.-)%s*$', '%1')) 5 | end 6 | 7 | function str.rtrim(s) 8 | return (s:gsub('^(.-)%s*$', '%1')) 9 | end 10 | 11 | function str.ucfirst(s) 12 | return (s:gsub('^%l', string.upper)) 13 | end 14 | 15 | function str.with(s, prefix, suffix) 16 | if prefix and not s:find('^' .. prefix) then 17 | s = prefix .. s 18 | end 19 | 20 | if suffix and not s:find(suffix .. '$') then 21 | s = s .. suffix 22 | end 23 | 24 | return s 25 | end 26 | 27 | function str.without(s, prefix, suffix) 28 | if prefix then 29 | s = s:gsub('^' .. prefix, '') 30 | end 31 | 32 | if suffix then 33 | s = s:gsub(suffix .. '$', '') 34 | end 35 | 36 | return s 37 | end 38 | 39 | function str.isempty(s) 40 | return s == nil or s == '' 41 | end 42 | 43 | function str.nonempty(...) 44 | for i = 1, select('#', ...) do 45 | local s = select(i, ...) 46 | if s ~= nil and s ~= '' then 47 | return s 48 | end 49 | end 50 | 51 | return nil 52 | end 53 | 54 | function str.limit(s, limit) 55 | return s:len() > limit and str.rtrim(s:sub(1, limit)) or s 56 | end 57 | 58 | function str.ellipsis(s, limit, ending) 59 | if not ending then 60 | ending = '...' 61 | end 62 | 63 | if s:len() - ending:len() <= limit then 64 | return s 65 | end 66 | 67 | return str.rtrim(s:sub(1, limit)) .. ending 68 | end 69 | 70 | --function str.padnul(s, len) 71 | -- return s .. string.rep('\0', len - s:len()) 72 | --end 73 | 74 | --function str.stripnul(s) 75 | -- return (string.gsub(s, '\0.+$', '')) 76 | --end 77 | 78 | return str -------------------------------------------------------------------------------- /samples/NoCyberware.lua: -------------------------------------------------------------------------------- 1 | return { Cyberware = {} } -------------------------------------------------------------------------------- /samples/NoEquipment.lua: -------------------------------------------------------------------------------- 1 | return { Equipment = {} } -------------------------------------------------------------------------------- /samples/config/defaults.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- The directory for storing spec files. 3 | -- If empty then the "specs" dir of the mod is used. 4 | specsDir = "", 5 | 6 | -- The defalt spec name. 7 | -- Used when saving and loading without specifying a spec name (aka quick saving an quick loading). 8 | defaultSpec = "V", 9 | 10 | -- Default options for saving specs. 11 | defaultOptions = { 12 | 13 | -- If enabled, the character levels, attributes, skills, and perks will be added to the spec. 14 | -- If disabled, the character data will NOT be added to the spec. 15 | character = true, 16 | 17 | -- If enabled, all perks will be saved in the spec, including those not purchased. 18 | -- If disabled, only purchased perks will be saved. 19 | allPerks = false, 20 | 21 | -- If enabled, the currently equipped items will be added to the spec. 22 | -- If disabled, the current equipment will NOT be added to the spec. 23 | equipment = true, 24 | 25 | -- If enabled, the currently equipped cyberware will be added to the spec. 26 | -- If disabled, the current cyberware will NOT be added to the spec. 27 | cyberware = true, 28 | 29 | -- If enabled, items in the backpack will be added to the spec. 30 | -- If disabled, items in the backpack will NOT be added to the spec. 31 | backpack = true, 32 | 33 | -- Filter backpack items. 34 | rarity = false, 35 | 36 | -- If enabled, crafting components will be added to the spec. 37 | -- If disabled, crafting components will NOT be added to the spec. 38 | components = true, 39 | 40 | -- If enabled, crafting recipes will be added to the spec. 41 | -- If disabled, crafting recipes will NOT be added to the spec. 42 | recipes = true, 43 | 44 | -- If enabled, own vehicles will be added to the spec. 45 | -- If disabled, vehicles will NOT be added to the spec. 46 | vehicles = true, 47 | 48 | -- The preferred ItemID format for use in item specs: 49 | -- "auto" - Use hash name whenever possible. 50 | -- "hash" - Always use a raw hash value (eg. `0x00000018026C324A`). 51 | -- "struct" - Always use a struct with hash and length (eg. `{ hash = 0x026C324A, length = 27 }`). 52 | itemFormat = "auto", 53 | 54 | -- How to save the RNG seed in the item spec: 55 | -- "auto" - Save the seed only for items that can be randomized. 56 | -- "always" - Always save the seed for all items. 57 | keepSeed = "auto", 58 | }, 59 | 60 | -- Enables the GUI. 61 | useGui = true, 62 | 63 | -- Enables API access using `GetMod()`. 64 | useModApi = true, 65 | } -------------------------------------------------------------------------------- /samples/packs/clothing-mods.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- ANTIVENOM / Mod / Clothing / Epic 4 | -- Immunity to Poison. 5 | { id = "Items.PowerfulFabricEnhancer02" }, 6 | 7 | -- ARMADILLO / Mod / Clothing 8 | -- Increases Armor by +4.46 and up to +270 depending on the player's level and mod rarity. 9 | { id = "Items.SimpleFabricEnhancer01" }, 10 | 11 | -- BACKPACKER / Mod / Clothing 12 | -- Increases carrying capacity by 5/7/9/11/13. 13 | { id = "Items.SimpleFabricEnhancer05" }, 14 | 15 | -- BOOM BREAKER / Mod / Clothing 16 | -- Reduces damage from explosives by 5/8/11/14/17%. 17 | { id = "Items.SimpleFabricEnhancer14" }, 18 | 19 | -- BULLY / Mod / Clothing / Legendary 20 | -- Increases Crit Damage by 30%. 21 | { id = "Items.SimpleFabricEnhancer04" }, 22 | 23 | -- COOLIT / Mod / Clothing / Legendary 24 | -- Immunity to Burn. 25 | { id = "Items.PowerfulFabricEnhancer01" }, 26 | 27 | -- CUT-IT-OUT / Mod / Clothing / Epic 28 | -- Immunity to Bleeding. 29 | { id = "Items.PowerfulFabricEnhancer06" }, 30 | 31 | -- DEAD-EYE / Mod / Clothing / Legendary 32 | -- Adds 15% Crit Chance, 30% Crit Damage. 33 | { id = "Items.PowerfulFabricEnhancer08" }, 34 | 35 | -- FOOTLOOSE / Mod / Clothing 36 | -- Increased Evasion by 0.2/0.3/0.4/0.5/0.6. 37 | { id = "Items.SimpleFabricEnhancer06" }, 38 | 39 | -- FORTUNA / Mod / Clothing / Legendary 40 | -- Increases Crit Chance by 15%. 41 | { id = "Items.SimpleFabricEnhancer03" }, 42 | 43 | -- OSMOSIS / Mod / Clothing 44 | -- Increase max Oxygen by 5/6.5/8/9.5/11. 45 | { id = "Items.SimpleFabricEnhancer09" }, 46 | 47 | -- PANACEA / Mod / Clothing / Legendary 48 | -- Immunity to Poison & Shock. 49 | { id = "Items.PowerfulFabricEnhancer03" }, 50 | 51 | -- PLUME / Mod / Clothing 52 | -- Reduces fall damage by 5/8/11/14/17%. 53 | { id = "Items.SimpleFabricEnhancer10" }, 54 | 55 | -- PREDATOR / Mod / Clothing / Legendary 56 | -- 25% bonus dam to med/hi level threats. 57 | { id = "Items.PowerfulFabricEnhancer07" }, 58 | 59 | -- RESIST! / Mod / Clothing 60 | -- Reduces damage from negative status effects by 5/8/11/14/17%. 61 | { id = "Items.SimpleFabricEnhancer02" }, 62 | 63 | -- SHOWTIME / Mod / Clothing 64 | -- Increases damage against enemies with Moderate and High treat levels by 5/6.5/8/9.5/11%. 65 | { id = "Items.SimpleFabricEnhancer07" }, 66 | 67 | -- SOFT-SOLE / Mod / Clothing / Epic 68 | -- Land silently. 69 | { id = "Items.PowerfulFabricEnhancer05" }, 70 | 71 | -- SUPERINSULATOR / Mod / Clothing / Epic 72 | -- Immunity to Shock. 73 | { id = "Items.PowerfulFabricEnhancer04" }, 74 | 75 | -- TENACITY / Mod / Clothing 76 | -- Reduces Stamina cost of melee attacks by -5/6/7/8/9%. 77 | { id = "Items.SimpleFabricEnhancer12" }, 78 | 79 | -- VANGUARD / Mod / Clothing 80 | -- Reduces Stamina consumption when blocking by 5/8/11/14/17%. 81 | { id = "Items.SimpleFabricEnhancer13" }, 82 | 83 | -- ZERO DRAG / Mod / Clothing 84 | -- Increases movement speed by 10/13/16/19/22. 85 | { id = "Items.SimpleFabricEnhancer11" }, 86 | }, 87 | } -------------------------------------------------------------------------------- /samples/packs/clothing-unique.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- ALDECALDOS RALLY BOLERO JACKET / Clothing / Outer Torso / Legendary 4 | -- Welcome to the family. 5 | { id = "Items.Q114_Aldecaldo_Jacket" }, 6 | 7 | -- ARASAKA SPACESUIT / Clothing / Special / Uncommon 8 | -- One giant leap for merckind. 9 | { id = "Items.Q203_Spacesuit_Outfit_WithHelmet" }, 10 | 11 | -- ARASAKA SPACESUIT / Clothing / Special / Epic 12 | -- One giant leap for merckind. 13 | { id = "Items.Q203_Spacesuit_Outfit_NoHelmet" }, 14 | 15 | -- SPACESUIT HELMET / Clothing / Head / Rare 16 | -- Put it on to hear the music of the spheres. 17 | { id = "Items.Q203_Spacesuit_Helmet" }, 18 | }, 19 | } -------------------------------------------------------------------------------- /samples/packs/cyberware-iconic.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- MILITECH "FALCON" SANDEVISTAN MK.5 / Cyberware / Operating System / Legendary 4 | -- Manufactured by Militech, the "Falcon" is hands down the best and most advanced Sandevistan model out there. Previously used only by elite Militech soldiers, its now available to the average consumer. The nickname is no accident - the peregrine falcon was once one of the fastest animals in the world, as this implant is among cyberware. 5 | -- Slows time by 30% for 18 sec. 6 | -- Increase any damage dealt by 15% when Sandevistan is active. 7 | -- Increase Crit Chance by 20% and Crit Damage by 35% when Sandevistan is active. 8 | -- Cooldown 60 sec. 9 | { 10 | id = "Items.SandevistanC4MK5", 11 | slots = { 12 | { slot = "Slot1" }, 13 | { slot = "Slot2" }, 14 | { slot = "Slot3" }, 15 | }, 16 | }, 17 | 18 | -- MILITECH BERSERK MK.5 / Cyberware / Operating System / Legendary 19 | -- The only official Berserk implant from Militech available on the market, but an exceptionally fine one at that. Its genius lies in being nanopowered, making it highly suited for regenerative purposes. 20 | -- When activated, ranged weapon recoil and sway -15%, melee damage +15% and Armor +10% for 10 seconds. 21 | -- Max health and stamina +40%, defeating enemies restores 5% health. 22 | -- Cooldown 60 seconds. 23 | { 24 | id = "Items.BerserkC4MK5", 25 | slots = { 26 | { slot = "Slot1" }, 27 | { slot = "Slot2" }, 28 | { slot = "Slot3" }, 29 | }, 30 | }, 31 | 32 | -- NETWATCH NETDRIVER MK.5 / Cyberware / Operating System / Legendary 33 | -- A cyberdeck series used by the best NetWatch agents and a frightening beast in terms of its offensive capabilities. It's best if NetWatch didn't catch you using this. 34 | -- Allows yout to preform quickhacks on targets and devices while scanning. 35 | -- Offensive quickhacks can be uploaded to 3 targets within a 6-meter radius. 36 | -- Increases damage dealt by quickhacks by 30%. 37 | -- Increases cyberdeck RAM recovery rare by 9 unit(s) per 60 sec. 38 | -- Increases quickhack spread distance by 60%. 39 | { 40 | id = "Items.NetwatchNetdriverLegendaryMKV", 41 | slots = { 42 | { slot = "Program1" }, 43 | { slot = "Program2" }, 44 | { slot = "Program3" }, 45 | { slot = "Program4" }, 46 | { slot = "Program5" }, 47 | { slot = "Program6" }, 48 | }, 49 | }, 50 | 51 | -- QIANT "WARP DANCER" SANDEVISTAN MK.5 / Cyberware / Operating System / Legendary 52 | -- QianT's pride and joy - the company's latest Sandevistan model has already reached a legendary status with its astounding craftsmanship and precision, while its artificial neural network-powered software is virtually unmatched in the market. 53 | -- Slows time by 10% for 8 sec. 54 | -- Increase any damage dealt by 15% when Sandevistan is active. 55 | -- Increase Crit Chance by 10% and Crit Damage by 50% when Sandevistan is active. 56 | -- Cooldown 30 sec. 57 | { 58 | id = "Items.SandevistanC3MK5", 59 | slots = { 60 | { slot = "Slot1" }, 61 | { slot = "Slot2" }, 62 | { slot = "Slot3" }, 63 | }, 64 | }, 65 | 66 | -- ZETATECH BERSERK MK.5 / Cyberware / Operating System / Legendary 67 | -- A true work of beauty from Zetatech - one of the most potent and most valuable Berserk implants on the market. Many private security companies allegedly outfit their mercenaries with this very model. 68 | -- When activated, reduces weapon recoil and increases melee damage, Armor, Resistances by 20% for 10 seconds. 69 | -- While active, defeating enemies restores 5% health and jumping from a high height create a Shockwave. 70 | -- Cooldown 30 seconds. 71 | -- 3 Mod Slots. 72 | { 73 | id = "Items.BerserkC3MK5", 74 | slots = { 75 | { slot = "Slot1" }, 76 | { slot = "Slot2" }, 77 | { slot = "Slot3" }, 78 | }, 79 | }, 80 | }, 81 | } -------------------------------------------------------------------------------- /samples/packs/cyberware-mods.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- SENSORY AMPLIFER - ARMOR / Mod / Cyberware / Arms / Rare 4 | -- Increases Armor by 5%. 5 | { id = "Items.ArmsCyberwareSharedFragment4" }, 6 | 7 | -- SENSORY AMPLIFER - CRIT CHANCE / Mod / Cyberware / Arms / Rare 8 | -- Increases Crit Chance by 10%. 9 | { id = "Items.ArmsCyberwareSharedFragment1" }, 10 | 11 | -- SENSORY AMPLIFER - CRIT DAMAGE / Mod / Cyberware / Arms / Rare 12 | -- Increases Crit Damage by 30%. 13 | { id = "Items.ArmsCyberwareSharedFragment2" }, 14 | 15 | -- SENSORY AMPLIFER - HEALTTH / Mod / Cyberware / Arms / Rare 16 | -- Increases Max Health by 15%. 17 | { id = "Items.ArmsCyberwareSharedFragment3" }, 18 | 19 | -- ANIMAL KNUCKLES / Mod / Cyberware / Berserk / Legendary 20 | -- Causes Internal Bleeding on successful hits. 21 | { id = "Items.AnimalsStrongArmsKnuckles1" }, 22 | 23 | -- ARMORED BERSERK / Mod / Cyberware / Berserk 24 | -- While Berserk is active, increases Armor bonus by 6/7/8/9/10 and all Resistances by 6/7/8/9/10%. 25 | { id = "Items.BerserkFragment3" }, 26 | 27 | -- BEAST MODE / Mod / Cyberware / Berserk / Legendary 28 | -- While Berserk is active Armor, Resistance, health +15%, Melee damage 100%. 29 | { id = "Items.AnimalsBerserkFragment1" }, 30 | 31 | -- BLACK MARKET BATTERY / Mod / Cyberware / Berserk / Legendary 32 | -- Max Charge & Charge damage 100%. 33 | { id = "Items.AnimalsStrongArmsBattery1" }, 34 | 35 | -- BRUSING BERSERK / Mod / Cyberware / Berserk / Uncommon 36 | -- Increases Berserk melee damage bonus by 13%. 37 | { id = "Items.BerserkFragment4" }, 38 | 39 | -- CHAINED BERSERK / Mod / Cyberware / Berserk 40 | -- Reduces Berserk Cooldown -5/5.5/6/6.5/7 sec. 41 | { id = "Items.BerserkFragment2" }, 42 | 43 | -- DEVASTATING BERSERK / Mod / Cyberware / Berserk / Rare 44 | -- When Berserk is active, increase Crit Chance by 15%. 45 | { id = "Items.BerserkFragment7" }, 46 | 47 | -- EXTENDED BERSERK / Mod / Cyberware / Berserk 48 | -- Increases Berserk duration by 1/2/3/4/5 sec. 49 | { id = "Items.BerserkFragment1" }, 50 | 51 | -- FOCUSED BERSERK / Mod / Cyberware / Berserk 52 | -- While Berserk is active, reduces weapon sway by 10/15/20/25/30% and recoil by 10/15/20/25/30%. 53 | { id = "Items.BerserkFragment5" }, 54 | 55 | -- INVIGORATING BERSERK / Mod / Cyberware / Berserk 56 | -- While Berserk is active, increases Health regen by 10/13/16/19/22%. 57 | { id = "Items.BerserkFragment6" }, 58 | 59 | -- SHARPENED BERSERK / Mod / Cyberware / Berserk / Rare 60 | -- When Berserk is active, increase Crit Chance by 25%. 61 | { id = "Items.BerserkFragment8" }, 62 | 63 | -- BATTERY: HIGH-CAPACITY / Mod / Cyberware / Gorilla Arms / Epic 64 | -- Increases max charge and charge damage by 50%. 65 | { id = "Items.HighChargedBattery" }, 66 | 67 | -- BATTERY: LOW-CAPACITY / Mod / Cyberware / Gorilla Arms / Rare 68 | -- Increases max charge and charge damage by 10%. 69 | { id = "Items.LowChargedBattery" }, 70 | 71 | -- BATTERY: MEDIUM-CAPACITY / Mod / Cyberware / Gorilla Arms / Epic 72 | -- Increases max charge and charge damage by 25%. 73 | { id = "Items.MediumChargedBattery" }, 74 | 75 | -- KNUCKLES - CHEMICAL DAMAGE / Mod / Cyberware / Gorilla Arms / Rare 76 | -- Gorilla Arms deals Chemcial damage and have a small chance to apply poison. 77 | { id = "Items.ChemicalDamageKnuckles" }, 78 | 79 | -- KNUCKLES - ELECTRICAL DAMAGE / Mod / Cyberware / Gorilla Arms / Rare 80 | -- Gorilla Arms deals Electrical damage and have a small chance to apply Shock. 81 | -- Increases damage to drones, mechs and robots. 82 | { id = "Items.ElectricDamageKnuckles" }, 83 | 84 | -- KNUCKLES - PHYSICAL DAMAGE / Mod / Cyberware / Gorilla Arms / Rare 85 | -- Gorilla Arms deal Physical damage. 86 | { id = "Items.PhysicalDamageKnuckles" }, 87 | 88 | -- KNUCKLES - THEREMAL DAMAGE / Mod / Cyberware / Gorilla Arms / Rare 89 | -- Gorilla Arms deal Theremal damage and have a small chance to apply Burn. 90 | { id = "Items.ThermalDamageKnuckles" }, 91 | 92 | -- RIN3U BATTERY / Mod / Cyberware / Gorilla Arms / Legendary 93 | -- Defeating an enemy fully restores Stamina. 94 | { id = "Items.TygerClawsStrongArmsBattery1" }, 95 | 96 | -- EXPLOSIVE ANALYSIS / Mod / Cyberware / Kiroshi / Uncommon 97 | -- An upgrade to Kiroshi optical cyberware. 98 | -- Allows you to see the precise explosion radius of your grenades. 99 | { id = "Items.KiroshiOpticsFragment2" }, 100 | 101 | -- TARGET ANALYSIS / Mod / Cyberware / Kiroshi / Rare 102 | -- An upgrade to Kiroshi optical cyberware. 103 | -- All weapons become non-lethal. 104 | -- Headshots do not deal additional damage. 105 | -- Smart Weapons primarily target limbs. 106 | { id = "Items.KiroshiOpticsFragment1" }, 107 | 108 | -- TARGET ANALYSIS / Mod / Cyberware / Kiroshi / Rare 109 | -- An upgrade to Kiroshi optical cyberware. 110 | -- All weapons become non-lethal. 111 | -- Headshots do not deal additional damage. 112 | -- Smart Weapons primarily target limbs. 113 | { id = "Items.KiroshiOpticsFragment3" }, 114 | 115 | -- THREAT DETECTOR / Mod / Cyberware / Kiroshi / Rare 116 | -- An upgrade to Kiroshi optical cyberware. 117 | -- Automatically highlights who have detected you. 118 | { id = "Items.KiroshiOpticsFragment4" }, 119 | 120 | -- TRAJECTORY ANALYSIS / Mod / Cyberware / Kiroshi / Legendary 121 | -- An upgrade to Kiroshi optical cyberware. 122 | -- Increase headshot bonus damage by 50%. 123 | { id = "Items.KiroshiOpticsFragment5" }, 124 | 125 | -- TRAJECTORY GENERATOR / Mod / Cyberware / Kiroshi / Uncommon 126 | -- Allows you to preview your bullets' ricochet trajectory. 127 | -- In order to control your bullets' trajectory, you must install both this mod and Ballistic Coprocessor cyberware. 128 | { id = "Items.KiroshiOpticsFragment7" }, 129 | 130 | -- WEAKSPOT DETECTION / Mod / Cyberware / Kiroshi / Uncommon 131 | -- An upgrade to Kiroshi optical cyberware. 132 | -- Increases Crit Chance by 5%. 133 | { id = "Items.KiroshiOpticsFragment6" }, 134 | 135 | -- BLADE - CHEMICAL DAMAGE / Mod / Cyberware / Mantis Blades / Rare 136 | -- Mantis Baldes deals Chemcial damage. 137 | -- Due to the blades' fast attack speed, they can rapidly apply poison. 138 | -- Very effective against an individual Target. 139 | { id = "Items.ChemicalDamageEdge" }, 140 | 141 | -- BLADE - ELECTRICAL DAMAGE / Mod / Cyberware / Mantis Blades / Rare 142 | -- Mantis Baldes deals Electrical damage. 143 | -- Due to the blades' fast attack speed, they can rapidly apply Shock. 144 | -- Very effective against drones, mechs and robots. 145 | { id = "Items.ElectricDamageEdge" }, 146 | 147 | -- BLADE - PHYSICAL DAMAGE / Mod / Cyberware / Mantis Blades / Rare 148 | -- Mantis Blades deal Physical damage. 149 | -- Due the blades' inherently fast attack speed and combos, they can rapidly deal devastating damage, granting a higher chance to dismember the enemy. 150 | -- Very effective against an individual target. 151 | { id = "Items.PhysicalDamageEdge" }, 152 | 153 | -- BLADE - THEREMAL DAMAGE / Mod / Cyberware / Mantis Blades / Rare 154 | -- Mantis Blades deal Thermal damage. 155 | -- Due to the blades' fast attack speed, they can quickly apply Burn. 156 | -- Very effective against an individual target. 157 | { id = "Items.ThermalDamageEdge" }, 158 | 159 | -- FAST ROTOR / Mod / Cyberware / Mantis Blades / Epic 160 | -- Increases Mantis Blade attack speed by 35%. 161 | { id = "Items.FastRotor" }, 162 | 163 | -- HAMING-8 ROTOR / Mod / Cyberware / Mantis Blades / Legendary 164 | -- Increases Mantis Blade attack speed by 45%. 165 | { id = "Items.TygerClawsMantisBladesRotor1" }, 166 | 167 | -- SLOW ROTOR / Mod / Cyberware / Mantis Blades / Rare 168 | -- Increases Mantis Blade attack speed by 20%. 169 | { id = "Items.SlowRotor" }, 170 | 171 | -- MONOWIRE - CHEMICAL DAMAGE / Mod / Cyberware / Monowire / Rare 172 | -- Monowire deals Chemcial damage. 173 | -- Can apply poison to multiple targets with one attack. 174 | -- Effective against groups of enemies. 175 | { id = "Items.ChemicalDamageCable" }, 176 | 177 | -- MONOWIRE - ELECTRICAL DAMAGE / Mod / Cyberware / Monowire / Rare 178 | -- Monowire deals Electrical damage. 179 | -- Can apply Shock and Stun to multiple targets with one attack. 180 | -- Very effective against rapidly moving targets and groups of enemies. 181 | { id = "Items.ElectricDamageCable" }, 182 | 183 | -- MONOWIRE - PHYSICAL DAMAGE / Mod / Cyberware / Monowire / Rare 184 | -- Monowire deals Physical damage. 185 | -- Deals high amount of concentrated damage and can instantly dismember an enemy with one quick action. 186 | { id = "Items.PhysicalDamageCable" }, 187 | 188 | -- MONOWIRE - THEREMAL DAMAGE / Mod / Cyberware / Monowire / Rare 189 | -- Monowire deals Thermal damage. 190 | -- Can apply Burn to multiple targets with one attack. 191 | -- Very effective against groupos of enemies. 192 | { id = "Items.ThermalDamageCable" }, 193 | 194 | -- MONOWIRE BATTERY: HIGH-CAPACITY / Mod / Cyberware / Monowire / Epic 195 | -- Increases charge damage by 50%. 196 | { id = "Items.HighChargedWiresBattery" }, 197 | 198 | -- MONOWIRE BATTERY: LOW-CAPACITY / Mod / Cyberware / Monowire / Rare 199 | -- Increases charge damage by 10%. 200 | { id = "Items.LowChargedWiresBattery" }, 201 | 202 | -- MONOWIRE BATTERY: MEDIUM-CAPACITY / Mod / Cyberware / Monowire / Epic 203 | -- Increases charge damage by 25%. 204 | { id = "Items.MediumChargedWiresBattery" }, 205 | 206 | -- CHEMICAL ROUND / Mod / Cyberware / Projectile Launch System / Rare 207 | -- Explodes on impact and deals Chemcial Damage to enemeies with one area of effect with a small change to apply poison. 208 | -- Charged shot inscrease damage, the explosion radius and the chance to apply poison. 209 | -- Effective against groups. 210 | { id = "Items.ChemicalDamageRound" }, 211 | 212 | -- ELECTRICAL ROUND / Mod / Cyberware / Projectile Launch System / Rare 213 | -- Explodes on impact and deals Electrical damage to enemeies with one area of effect with a chance to apply Stun. 214 | -- Charged shot inscrease explosion radius and guarantees applying Stun. 215 | -- Effective against fast-moving enemies, drones, mechs and robots, as well as groups of enemies. 216 | { id = "Items.ElectricDamageRound" }, 217 | 218 | -- EXPLOSIVE ROUND / Mod / Cyberware / Projectile Launch System / Rare 219 | -- Explodes on impact and deals major Physical damage to enemies within the area of effect. 220 | -- Charged shots increase damage, the explosion radius and the chance to dismember. 221 | -- Effective against groups of enemies. 222 | { id = "Items.ExplosiveDamageRound" }, 223 | 224 | -- METAL PLATING / Mod / Cyberware / Projectile Launch System / Rare 225 | -- Metal inserts that are integrated into the body to better prevent soft-tissue damage. 226 | -- Increases all Resistances by 10%. 227 | { id = "Items.MetalPlating" }, 228 | 229 | -- MILITECH INCENDIARY ROUND / Mod / Cyberware / Projectile Launch System / Rare 230 | -- Explodes on impact and deals thermal damage to enemies withing the are of effect with a small chance to apply Burn. 231 | -- Charged shots increase damage, the explosion radius and the chance to apply Burn. 232 | -- Effective against groups of enemies. 233 | { id = "Items.MilitechProjectileLauncherRound1" }, 234 | 235 | -- NEOPLASTIC PLATING / Mod / Cyberware / Projectile Launch System / Rare 236 | -- A covering made from a durable and flexible material. 237 | -- Increases Crit Chance by 10%. 238 | { id = "Items.NeoplasticPlating" }, 239 | 240 | -- THERMAL ROUND / Mod / Cyberware / Projectile Launch System / Rare 241 | -- Exploses on impact and deals Thermal damage to enemies within the area of effect with a small chance to apply burn. 242 | -- Charged shots increase damage, the explosion radius and the chance to apply Burn. 243 | -- Effective against groups of enemies. 244 | { id = "Items.ThermalDamageRound" }, 245 | 246 | -- TITANIUM PLATING / Mod / Cyberware / Projectile Launch System / Epic 247 | -- Titanium inserts that are integrated into the body to better prevent soft-tissue damage. 248 | -- Increases Armor by 7%. 249 | { id = "Items.TitaniumPlating" }, 250 | 251 | -- TRANQUILIZER ROUNDS / Mod / Cyberware / Projectile Launch System / Rare 252 | -- Enemies shot with this type of round will lose consciousness. 253 | -- Non-Leathal. 254 | { id = "Items.TranquilizerRound" }, 255 | 256 | -- SANDEVISTAN: ARASAKA SOFWARE / Mod / Cyberware / Sandevistan / Legendary 257 | -- Enemies take 70% more time to notice you while Sandevistan is active. 258 | { id = "Items.ArasakaSandevistanFragment1" }, 259 | 260 | -- SANDEVISTAN: HEATSINK / Mod / Cyberware / Sandevistan 261 | -- Reduces Sandevistan Cooldown by -2/2.5/3/3.5/4 sec. 262 | { id = "Items.SandevistanFragment4" }, 263 | 264 | -- SANDEVISTAN: MICRO-AMPLIFIER / Mod / Cyberware / Sandevistan / Legendary 265 | -- When Sandevistan is active, it removes all BURN, Shock, Poison and Bleeding effects. 266 | { id = "Items.SandevistanFragment8" }, 267 | 268 | -- SANDEVISTAN: NEUROTRANSMITTERS / Mod / Cyberware / Sandevistan / Rare 269 | -- While Sandevistan is active, increase Crit Chance Damage by 15%. 270 | { id = "Items.SandevistanFragment3" }, 271 | 272 | -- SANDEVISTAN: OVERCLOCKED PROCESSOR / Mod / Cyberware / Sandevistan 273 | -- Increases duration by 0.5/0.7/0.9/1.1/1.3 sec. 274 | { id = "Items.SandevistanFragment1" }, 275 | 276 | -- SANDEVISTAN: PROTOTYPE CHIP / Mod / Cyberware / Sandevistan / Rare 277 | -- While Sandevistan is active, increase Crit Chance by 5%. 278 | { id = "Items.SandevistanFragment2" }, 279 | 280 | -- SANDEVISTAN: RABID BULL / Mod / Cyberware / Sandevistan / Epic 281 | -- While Sandevistan is active, defeating an enemy restores 5% health. 282 | { id = "Items.ValentinosSandevistanFragment1" }, 283 | 284 | -- SANDEVISTAN: TYGER PAW / Mod / Cyberware / Sandevistan / Epic 285 | -- While Sandevistan is active, defeating an enemy restores 15% Stamina. 286 | { id = "Items.TygerClawsSandevistanFragment1" }, 287 | }, 288 | } -------------------------------------------------------------------------------- /samples/packs/gog.lua: -------------------------------------------------------------------------------- 1 | -- The stuff you get for buying the game in the GOG or by linking your GOG account. 2 | return { 3 | Inventory = { 4 | -- BLACK UNICORN / Weapon / Melee / Blade / Rare 5 | -- The razor-like blade is as merciless as death itself, the guard is fashioned from premium materials and the handle is crafted so exquisitely it belongs in a museum. Most importantly, it remains light and nimble to wield because it was forged DRM-free. 6 | { id = "Items.Preset_Katana_GoG" }, 7 | 8 | -- BLACK UNICORN / Weapon / Melee / Blade / Epic 9 | -- The razor-like blade is as merciless as death itself, the guard is fashioned from premium materials and the handle is crafted so exquisitely it belongs in a museum. Most importantly, it remains light and nimble to wield because it was forged DRM-free. 10 | { id = "Items.Preset_Katana_GoG_Epic" }, 11 | 12 | -- BLACK UNICORN / Weapon / Melee / Blade / Legendary 13 | -- The razor-like blade is as merciless as death itself, the guard is fashioned from premium materials and the handle is crafted so exquisitely it belongs in a museum. Most importantly, it remains light and nimble to wield because it was forged DRM-free. 14 | { id = "Items.Preset_Katana_GoG_Legendary" }, 15 | 16 | -- GALAXY T-SHIRT / Clothing / Inner Torso / Rare 17 | -- Find your place amongst the stars. 18 | { id = "Items.GOG_Galaxy_TShirt" }, 19 | 20 | -- GALAXY T-SHIRT / Clothing / Inner Torso / Epic 21 | -- Find your place amongst the stars. 22 | { id = "Items.GOG_Galaxy_TShirt_Epic" }, 23 | 24 | -- GALAXY T-SHIRT / Clothing / Inner Torso / Legendary 25 | -- Find your place amongst the stars. 26 | { id = "Items.GOG_Galaxy_TShirt_Legendary" }, 27 | 28 | -- WOLF SCHOOL JACKET / Clothing / Outer Torso / Rare 29 | -- Because the world in 2077 could still use a monster hunter. 30 | { id = "Items.GOG_DLC_Jacket" }, 31 | 32 | -- WOLF SCHOOL JACKET / Clothing / Outer Torso / Epic 33 | -- Because the world in 2077 could still use a monster hunter. 34 | { id = "Items.GOG_DLC_Jacket_Epic" }, 35 | 36 | -- WOLF SCHOOL JACKET / Clothing / Outer Torso / Legendary 37 | -- Because the world in 2077 could still use a monster hunter. 38 | { id = "Items.GOG_DLC_Jacket_Legendary" }, 39 | 40 | -- WOLF SCHOOL T-SHIRT / Clothing / Inner Torso / Rare 41 | -- When you have to choose between neokitsch and neomilitaristic but can't make up your mind. 42 | { id = "Items.GOG_DLC_TShirt" }, 43 | 44 | -- WOLF SCHOOL T-SHIRT / Clothing / Inner Torso / Epic 45 | -- When you have to choose between neokitsch and neomilitaristic but can't make up your mind. 46 | { id = "Items.GOG_DLC_TShirt_Epic" }, 47 | 48 | -- WOLF SCHOOL T-SHIRT / Clothing / Inner Torso / Legendary 49 | -- When you have to choose between neokitsch and neomilitaristic but can't make up your mind. 50 | { id = "Items.GOG_DLC_TShirt_Legendary" }, 51 | }, 52 | } -------------------------------------------------------------------------------- /samples/packs/johnny.lua: -------------------------------------------------------------------------------- 1 | -- The stuff related to Johnny Silverhand. 2 | return { 3 | Inventory = { 4 | -- JOHNNY'S AVIATORS / Clothing / Face / Rare 5 | { id = "Items.Q005_Johnny_Glasses" }, 6 | 7 | -- JOHNNY'S PANTS / Clothing / Legs / Rare 8 | -- Authentic leather for an authentic rockerboy. 9 | { id = "Items.Q005_Johnny_Pants" }, 10 | 11 | -- JOHNNY'S SHOES / Clothing / Feet / Rare 12 | -- Oh, to spend a day in the shoes of a legend... 13 | { id = "Items.Q005_Johnny_Shoes" }, 14 | 15 | -- JOHNNY'S TANK TOP / Clothing / Inner Torso / Rare 16 | -- Without the dog tags it looks like any other fanboy shirt. 17 | { id = "Items.Q005_Johnny_Shirt" }, 18 | 19 | -- MALORIAN ARMS 3516 / Weapon / Power / Pistol / Legendary 20 | -- Custom-made for Johnny Silverhand. The guy had taste. 21 | { id = "Items.Preset_Silverhand_3516" }, 22 | 23 | -- REPLICA OF JOHNNY'S SAMURAI JACKET / Clothing / Outer Torso / Rare 24 | -- A copy of the original from the days when Johnny still had a body. 25 | { id = "Items.SQ031_Samurai_Jacket" }, 26 | }, 27 | 28 | Vehicles = { 29 | "Vehicle.v_sport2_porsche_911turbo_player", -- 911 II (930) TURBO 30 | }, 31 | } -------------------------------------------------------------------------------- /samples/packs/nibbles.lua: -------------------------------------------------------------------------------- 1 | -- The cat food item that needed to obtain Nibbles as a pet. 2 | return { 3 | Inventory = { 4 | -- CAT FOOD / Consumable / Food / Common 5 | -- It'll have you purring in no time. 6 | -- Increases max health by 5%. 7 | -- Regenerates 0.5% health every second outside combat. 8 | { id = "Items.LowQualityFood11" }, 9 | }, 10 | } -------------------------------------------------------------------------------- /samples/packs/patch-130.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- 4RMY L1F3 TACTICAL ARAMID JACKET WITH BUCKLES / Clothing / Outer Torso 4 | -- A strong defense against bullets and poor taste. 5 | { id = "Items.Jacket_19_rich_06" }, 6 | 7 | -- ARAMID-COATED DELTAJOCK JACKET / Clothing / Outer Torso 8 | -- Perfect for highriding. 9 | { id = "Items.Jacket_20_basic_05" }, 10 | 11 | -- ASTRONAUTE REINFORCED-STITCH COMPOSITE JACKET / Clothing / Outer Torso 12 | -- Hope you've been to the moon, 'cause this thing costs hella cheddar. 13 | { id = "Items.Jacket_19_rich_02" }, 14 | 15 | -- BLUTPANTHER ELASTIC DELTAJOCK JACKET WITH POLYAMID WEAVE / Clothing / Outer Torso 16 | -- Stained with the blood of its enemies, or so legend has it. 17 | { id = "Items.Jacket_20_rich_04" }, 18 | 19 | -- COMPOSITE BIKER JACKET WITH BUCKLES / Clothing / Outer Torso 20 | -- Those things on the shoulders are not stove dials. It's called fashion, sweetie. 21 | { id = "Items.Jacket_19_basic_02" }, 22 | 23 | -- DURABLE ZEBRIGER LED-LINED JACKET / Clothing / Outer Torso 24 | -- Black with white stripes, or white with black? 25 | { id = "Items.Jacket_19_rich_04" }, 26 | 27 | -- DUSTY ROCKER JACKET WITH BUCKLES / Clothing / Outer Torso 28 | -- The starry heavens above me and the sense of style within me. 29 | { id = "Items.Jacket_19_old_02" }, 30 | 31 | -- FRAYED TACTICAL FLIGHT JACKET / Clothing / Outer Torso 32 | -- Old-school jacket in keen camo. 33 | { id = "Items.Jacket_20_old_03" }, 34 | 35 | -- GADYUKA COMPOSITE FLIGHT JACKET / Clothing / Outer Torso 36 | -- Perfect camo for blending into a corporate viper pit. 37 | { id = "Items.Jacket_20_basic_06" }, 38 | 39 | -- GREEN DEATH DELTAJOCK BIKER JACKET / Clothing / Outer Torso 40 | -- They think they can catch you? Let 'em try. 41 | { id = "Items.Jacket_20_basic_04" }, 42 | 43 | -- JACKETCEPTION DUOLAYER NANOWEAVE FLIGHT JACKET / Clothing / Outer Torso 44 | -- A bomber within a jacket. 45 | { id = "Items.Jacket_20_basic_07" }, 46 | 47 | -- KAWAII ARUMORU BULLETPROOF DELTAJOCK JACKET / Clothing / Outer Torso 48 | -- Dodging bullets never looked so cute :3 49 | { id = "Items.Jacket_20_basic_03" }, 50 | 51 | -- KIRĀKYANDĪ ELASTI-COMPOSITE JACKET WITH BUCKLES / Clothing / Outer Torso 52 | -- If something is tacky and expensive, it isn't tacky. 53 | { id = "Items.Jacket_19_rich_03" }, 54 | 55 | -- KUROI RAION COMPOSITE DELTAJOCK BIKER JACKET / Clothing / Outer Torso 56 | -- Fire all your guns at once and explode into space. 57 | { id = "Items.Jacket_20_rich_01" }, 58 | 59 | -- LA PROFONDITÀ DELL'OCEANO POLYCARBONATE REINFORCED FLIGHT JACKET WITH COMPOSITE PLATING / Clothing / Outer Torso 60 | -- A design with a name this long must be worth the price. 61 | { id = "Items.Jacket_20_rich_05" }, 62 | 63 | -- LED-LINED POLICE JACKET / Clothing / Outer Torso 64 | -- Shouting "wee-woo wee-woo" when running is highly encouraged. 65 | { id = "Items.Jacket_19_basic_03" }, 66 | 67 | -- LUMINESCENT PUNK JACKET / Clothing / Outer Torso / Rare 68 | -- More zippers and buckles means more dangerous. Like bright colors on a poison dart frog. 69 | { id = "Items.Jacket_19_basic_04" }, 70 | 71 | -- LUMINESCENT PUNK JACKET / Clothing / Outer Torso / Epic 72 | -- More zippers and buckles means more dangerous. Like bright colors on a poison dart frog. 73 | { id = "Items.Jacket_19_basic_04_DLC_Epic" }, 74 | 75 | -- LUMINESCENT PUNK JACKET / Clothing / Outer Torso / Legendary 76 | -- More zippers and buckles means more dangerous. Like bright colors on a poison dart frog. 77 | { id = "Items.Jacket_19_basic_04_DLC_Legendary" }, 78 | 79 | -- MIKAN ENHANCED FLIGHT JACKET / Clothing / Outer Torso 80 | -- Faux fur doesn't mean faux style. 81 | { id = "Items.Jacket_20_basic_02" }, 82 | 83 | -- MILITARY EXTRA-MEMBRANE DELTAJOCK JACKET / Clothing / Outer Torso 84 | -- Two membranes? That's insane. 85 | { id = "Items.Jacket_20_old_04" }, 86 | 87 | -- MOSAIC GLEAM LED-HYBRID TRILAYER JACKET / Clothing / Outer Torso 88 | -- Trends are silver, but good taste is golden. 89 | { id = "Items.Jacket_19_rich_01" }, 90 | 91 | -- MUDDY 6TH STREET HYBRID JACKET WITH BUCKLES / Clothing / Outer Torso 92 | -- Stained with mud. AMERICAN mud. 93 | { id = "Items.Jacket_19_old_06" }, 94 | 95 | -- MUDDY DELTAJOCK BIKER JACKET / Clothing / Outer Torso 96 | -- Washing it was too much of a hassle. 97 | { id = "Items.Jacket_20_old_05" }, 98 | 99 | -- MULTILAYERED SYN-LEATHER DELTAJOCK JACKET / Clothing / Outer Torso / Rare 100 | -- Synthetic fabric, retro feel. 101 | { id = "Items.Jacket_20_basic_01" }, 102 | 103 | -- MULTILAYERED SYN-LEATHER DELTAJOCK JACKET / Clothing / Outer Torso / Epic 104 | -- Synthetic fabric, retro feel. 105 | { id = "Items.Jacket_20_basic_01_DLC_Epic" }, 106 | 107 | -- MULTILAYERED SYN-LEATHER DELTAJOCK JACKET / Clothing / Outer Torso / Legendary 108 | -- Synthetic fabric, retro feel. 109 | { id = "Items.Jacket_20_basic_01_DLC_Legendary" }, 110 | 111 | -- OLD DUOLAYER JACKET WITH BUCKLES / Clothing / Outer Torso 112 | -- If you toss it now, you'll just have to buy it back secondhand in a few years. 113 | { id = "Items.Jacket_19_old_03" }, 114 | 115 | -- PINK RAGE ULTRA-LIGHT ROCKER DELTAJOCK JACKET / Clothing / Outer Torso 116 | -- A fashion release for frenzied times. 117 | { id = "Items.Jacket_20_rich_03" }, 118 | 119 | -- PIXEL ROCK ARAMID JACKET WITH BUCKLES / Clothing / Outer Torso 120 | -- Protects your body from harm and your style from embarrasing stains. 121 | { id = "Items.Jacket_19_old_04" }, 122 | 123 | -- ROBOSKIN 2.0 ANTI-SHRAPNEL DELTAJOCK JACKET / Clothing / Outer Torso 124 | -- Now with 50% more shine and a price tag to match. 125 | { id = "Items.Jacket_20_rich_02" }, 126 | 127 | -- ROBOSKIN STAINED ROCKER DELTAJOCK JACKET / Clothing / Outer Torso 128 | -- The jury's still out whether it's rust or mud. 129 | { id = "Items.Jacket_20_old_01" }, 130 | 131 | -- ROSA MARMOR BREATHABLE POLYCARBONATE JACKET WITH BUCKLES / Clothing / Outer Torso 132 | -- Try on a layer of confidence. 133 | { id = "Items.Jacket_19_rich_05" }, 134 | 135 | -- SYNTHETIC BIKER JACKET WITH BUCKLES / Clothing / Outer Torso 136 | -- Everything grows old under the power of time. But not black leather - that shit never goes out of style. 137 | { id = "Items.Jacket_19_basic_01" }, 138 | 139 | -- TYGER CLAWS POLYMER JACKET WITH BUCKLES / Clothing / Outer Torso 140 | -- The guys and gals from the gang will just be green with envy. 141 | { id = "Items.Jacket_19_basic_05" }, 142 | 143 | -- USED FLIGHT JACKET WITH COMPOSITE LINING / Clothing / Outer Torso 144 | -- Looks like it's been to the stars and back. 145 | { id = "Items.Jacket_20_old_02" }, 146 | 147 | -- WHITE HIVE LED-LINED ANTI-PIERCING JACKET / Clothing / Outer Torso 148 | -- With coolness regulators on the shoulders. 149 | { id = "Items.Jacket_19_basic_06" }, 150 | 151 | -- WORN WRAITHS JACKET WITH BUCKLES / Clothing / Outer Torso 152 | -- Smells like blood, dust and wanderlust. 153 | { id = "Items.Jacket_19_old_05" }, 154 | 155 | -- WOUNDED MACHINE REINFORCED JACKET / Clothing / Outer Torso 156 | -- For when you need something that expresses the state of your soul. 157 | { id = "Items.Jacket_19_old_01" }, 158 | }, 159 | 160 | Vehicles = { 161 | "Vehicle.v_standard2_archer_bandit_player", -- QUARTZ "BANDIT" 162 | }, 163 | } -------------------------------------------------------------------------------- /samples/packs/patch-150.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- BLOODY MARIA / Weapon / Power / Shotgun / Epic 4 | -- As mean throughout all Night City as it is on the streets of Santo. 5 | -- Increase knockdown, bleeding and dismemberment chance. 6 | -- It's going to be a mess. 7 | { id = "Items.Preset_Tactician_Dino" }, 8 | 9 | -- BLOODY MARIA / Weapon / Power / Shotgun / Legendary 10 | -- As mean throughout all Night City as it is on the streets of Santo. 11 | -- Increase knockdown, bleeding and dismemberment chance. 12 | -- It's going to be a mess. 13 | { id = "Items.Preset_Tactician_Dino_Legendary" }, 14 | 15 | -- BYAKKO / Weapon / Melee / Blade / Epic 16 | -- Spent most of the time collecting dust on Wakako's desk. Except for the occasional heated negotiation. 17 | -- Byakko is perfectly balanced, allowing to jump the enemy and attack them with full force. 18 | -- It's incredible sharpness increases bleeding chance.. 19 | { id = "Items.Preset_Katana_Wakako" }, 20 | 21 | -- BYAKKO / Weapon / Melee / Blade / Legendary 22 | -- Spent most of the time collecting dust on Wakako's desk. Except for the occasional heated negotiation. 23 | -- Byakko is perfectly balanced, allowing to jump the enemy and attack them with full force. 24 | -- It's incredible sharpness increases bleeding chance.. 25 | { id = "Items.Preset_Katana_Wakako_Legendary" }, 26 | 27 | -- DA8 UMBRA (MILITARY) / Weapon / Power / Assault Rifle 28 | -- Fire, kill, toss. Not exactly a keepsake - you'll ditch the Umbra as soon as something better comes along, and not a moment later. 29 | { id = "Items.Preset_Umbra_Military" }, 30 | 31 | -- DA8 UMBRA (NEON) / Weapon / Power / Assault Rifle 32 | -- Fire, kill, toss. Not exactly a keepsake - you'll ditch the Umbra as soon as something better comes along, and not a moment later. 33 | { id = "Items.Preset_Umbra_Neon" }, 34 | 35 | -- DA8 UMBRA (PIMP) / Weapon / Power / Assault Rifle 36 | -- Fire, kill, toss. Not exactly a keepsake - you'll ditch the Umbra as soon as something better comes along, and not a moment later. 37 | { id = "Items.Preset_Umbra_Pimp" }, 38 | 39 | -- DA8 UMBRA / Weapon / Power / Assault Rifle 40 | -- Fire, kill, toss. Not exactly a keepsake - you'll ditch the Umbra as soon as something better comes along, and not a moment later. 41 | { id = "Items.Preset_Umbra_Default" }, 42 | 43 | -- GUILLOTINE (MILITARY) / Weapon / Power / SMG 44 | -- Only slightly better than bringing a knife to a gunfight. 45 | { id = "Items.Preset_Guillotine_Military" }, 46 | 47 | -- GUILLOTINE (NEON) / Weapon / Power / SMG 48 | -- Only slightly better than bringing a knife to a gunfight. 49 | { id = "Items.Preset_Guillotine_Neon" }, 50 | 51 | -- GUILLOTINE (PIMP) / Weapon / Power / SMG 52 | -- Only slightly better than bringing a knife to a gunfight. 53 | { id = "Items.Preset_Guillotine_Pimp" }, 54 | 55 | -- GUILLOTINE / Weapon / Power / SMG 56 | -- Only slightly better than bringing a knife to a gunfight. 57 | { id = "Items.Preset_Guillotine_Default" }, 58 | 59 | -- RC-7 ASWANG / Mod / Muzzle Brake 60 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 61 | -- Increases ricochet angle by A° and ricochet chance by B%. 62 | { id = "Items.w_muzzle_brake_05" }, 63 | 64 | -- RC-7 BABAROGA / Mod / Muzzle Brake 65 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 66 | -- Increases ricochet angle by A° and ricochet chance by B%. 67 | { id = "Items.w_muzzle_brake_10" }, 68 | 69 | -- RC-7 DYBBUK / Mod / Muzzle Brake 70 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 71 | -- Increases ricochet angle by A° and ricochet chance by B%. 72 | { id = "Items.w_muzzle_brake_09" }, 73 | 74 | -- RC-7 IFRIT / Mod / Muzzle Brake 75 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 76 | -- Increases ricochet angle by A° and ricochet chance by B%. 77 | { id = "Items.w_muzzle_brake_07" }, 78 | 79 | -- RC-7 KUTRUB / Mod / Muzzle Brake 80 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 81 | -- Increases ricochet angle by A° and ricochet chance by B%. 82 | { id = "Items.w_muzzle_brake_08" }, 83 | 84 | -- RC-7 LIGER / Mod / Muzzle Brake 85 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 86 | -- Increases ricochet angle by A° and ricochet chance by B%. 87 | { id = "Items.w_muzzle_brake_11" }, 88 | 89 | -- RC-7 STRIGOI / Mod / Muzzle Brake 90 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 91 | -- Increases ricochet angle by A° and ricochet chance by B%. 92 | { id = "Items.w_muzzle_brake_01" }, 93 | 94 | -- RC-7 VARKOLAK / Mod / Muzzle Brake 95 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 96 | -- Increases ricochet angle by A° and ricochet chance by B%. 97 | { id = "Items.w_muzzle_brake_03" }, 98 | 99 | -- RC-7 YOKAI / Mod / Muzzle Brake 100 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 101 | -- Increases ricochet angle by A° and ricochet chance by B%. 102 | { id = "Items.w_muzzle_brake_06" }, 103 | 104 | -- RC-7 ZAAR / Mod / Muzzle Brake 105 | -- Ricochet? More like rico-slay! It's an art, not a lucky strike. 106 | -- Increases ricochet angle by A° and ricochet chance by B%. 107 | { id = "Items.w_muzzle_brake_02" }, 108 | 109 | -- GAKI / Mod / Scope 110 | -- A precise, high-quality digital sniper scope by Tsunami. The only thing that could bring you closer to your target is a romantic dinner. 111 | { id = "Items.w_att_scope_sniper_03" }, 112 | 113 | -- HANDYMAN / Mod / Scope 114 | -- A digital short scope handcrafted by one of Night City's countless iron techies. 115 | { id = "Items.w_att_scope_short_06" }, 116 | 117 | -- JUE / Mod / Scope 118 | -- Although Kang Tao excels in smart targeting systems, the Jue digital long scope is proof they still value good old-fashioned marksmanship. 119 | { id = "Items.w_att_scope_long_05" }, 120 | 121 | -- KANETSUGU / Mod / Scope 122 | -- Precise, discreet and reliable. This holographic handgun scope is nothing short of yet another masterpiece delivered by Arasaka. 123 | { id = "Items.w_att_scope_short_07" }, 124 | 125 | -- SERAPH / Weapon / Power / Pistol / Epic 126 | -- Padre's closest companion. Judge, jury and executioner for divine justice - all rolled into one. 127 | -- Every subsequent hit increases the chance of setting the enemy ablaze in Seraphs cleansing flames. 128 | -- While on fire, bullets inflict additional damage. 129 | -- Does not synergize with mods. 130 | { id = "Items.Preset_Liberty_Padre" }, 131 | 132 | -- SERAPH / Weapon / Power / Pistol / Legendary 133 | -- Padre's closest companion. Judge, jury and executioner for divine justice - all rolled into one. 134 | -- Every subsequent hit increases the chance of setting the enemy ablaze in Seraphs cleansing flames. 135 | -- While on fire, bullets inflict additional damage. 136 | -- Does not synergize with mods. 137 | { id = "Items.Preset_Liberty_Padre_Legendary" }, 138 | }, 139 | 140 | Vehicles = { 141 | "Vehicle.v_standard3_thorton_mackinaw_02_player", -- MACKINAW "SAGUARO" 142 | "Vehicle.v_sport2_quadra_type66_02_player", -- TYPE-66 640 TS 143 | }, 144 | } -------------------------------------------------------------------------------- /samples/packs/patch-160.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- BLUE FANG / Weapon / Melee / Blade / Rare 4 | -- The neurotoxin coating the blade of this prototype knife is stunningly effective. 5 | -- Stuns the enemy on hit. 6 | -- Headshot stuns after 1 sec while other hits stun after 3 sec. 7 | -- Enemies outside combat state get stunned immediately. 8 | { id = "Items.Preset_Neurotoxin_Knife_Iconic" }, 9 | 10 | -- BLUE FANG / Weapon / Melee / Blade / Epic 11 | -- The neurotoxin coating the blade of this prototype knife is stunningly effective. 12 | -- Stuns the enemy on hit. 13 | -- Headshot stuns after 1 sec while other hits stun after 3 sec. 14 | -- Enemies outside combat state get stunned immediately. 15 | { id = "Items.Preset_Neurotoxin_Knife_Iconic_Epic" }, 16 | 17 | -- BLUE FANG / Weapon / Melee / Blade / Legendary 18 | -- The neurotoxin coating the blade of this prototype knife is stunningly effective. 19 | -- Stuns the enemy on hit. 20 | -- Headshot stuns after 1 sec while other hits stun after 3 sec. 21 | -- Enemies outside combat state get stunned immediately. 22 | { id = "Items.Preset_Neurotoxin_Knife_Iconic_Legendary" }, 23 | 24 | -- BUTCHER'S CLEAVER / Weapon / Melee / Blade / Rare 25 | -- This cleaver seems to have a thirst for blood. You can slice and dice, but throwing it is out of the question. 26 | -- Definition of "bloodthirsty" When your enemy is bleeding, the cleaver attacks faster and uses less stamina. 27 | { id = "Items.Preset_Butchers_Knife_Iconic" }, 28 | 29 | -- BUTCHER'S CLEAVER / Weapon / Melee / Blade / Epic 30 | -- This cleaver seems to have a thirst for blood. You can slice and dice, but throwing it is out of the question. 31 | -- Definition of "bloodthirsty" When your enemy is bleeding, the cleaver attacks faster and uses less stamina. 32 | { id = "Items.Preset_Butchers_Knife_Iconic_Epic" }, 33 | 34 | -- BUTCHER'S CLEAVER / Weapon / Melee / Blade / Legendary 35 | -- This cleaver seems to have a thirst for blood. You can slice and dice, but throwing it is out of the question. 36 | -- Definition of "bloodthirsty" When your enemy is bleeding, the cleaver attacks faster and uses less stamina. 37 | { id = "Items.Preset_Butchers_Knife_Iconic_Legendary" }, 38 | 39 | -- CLAW (NEON) / Weapon / Melee / Blade 40 | -- Deals heavy damage, but requires serious brawn to wield it. 41 | { id = "Items.Preset_Fanged_Axe_Neon" }, 42 | 43 | -- CLAW / Weapon / Melee / Blade 44 | -- Deals heavy damage, but requires serious brawn to wield it. 45 | { id = "Items.Preset_Fanged_Axe_Default" }, 46 | 47 | -- CUT-O-MATIC (MILITARY) / Weapon / Melee / Blade 48 | -- The missing link between machetes and chainsaws. As effective and it is noisy - forget about stealth when using this baby. 49 | { id = "Items.Preset_Chainsword_Military" }, 50 | 51 | -- CUT-O-MATIC (NEON) / Weapon / Melee / Blade 52 | -- The missing link between machetes and chainsaws. As effective and it is noisy - forget about stealth when using this baby. 53 | { id = "Items.Preset_Chainsword_Neon" }, 54 | 55 | -- CUT-O-MATIC (PIMP) / Weapon / Melee / Blade 56 | -- The missing link between machetes and chainsaws. As effective and it is noisy - forget about stealth when using this baby. 57 | { id = "Items.Preset_Chainsword_Pimp" }, 58 | 59 | -- CUT-O-MATIC / Weapon / Melee / Blade 60 | -- The missing link between machetes and chainsaws. As effective and it is noisy - forget about stealth when using this baby. 61 | { id = "Items.Preset_Chainsword_Default" }, 62 | 63 | -- CUT-O-MATIC / Weapon / Melee / Blade 64 | -- The missing link between machetes and chainsaws. As effective and it is noisy - forget about stealth when using this baby. 65 | { id = "Items.Preset_Chainsword_Legendary" }, 66 | 67 | -- GUTS / Weapon / Power / Shotgun / Epic 68 | -- Originally belonged to Rebecca from David Martinez's crew. It'll wreak bloody havoc, though it can be a little unpredictable. Much like its original owner. 69 | -- Let the lead fly! 70 | -- Shoot faster, deal more damage.. 71 | -- accuracy be damned. 72 | -- Just don't shoot your eye out. 73 | { id = "Items.Preset_Carnage_Edgerunners" }, 74 | 75 | -- GUTS / Weapon / Power / Shotgun / Legendary 76 | -- Originally belonged to Rebecca from David Martinez's crew. It'll wreak bloody havoc, though it can be a little unpredictable. Much like its original owner. 77 | -- Let the lead fly! 78 | -- Shoot faster, deal more damage.. 79 | -- accuracy be damned. 80 | -- Just don't shoot your eye out. 81 | { id = "Items.Preset_Carnage_Edgerunners_Legendary" }, 82 | 83 | -- HEADHUNTER / Weapon / Melee / Blade / Rare 84 | -- Throwing this knife is like imposing a death sentence. 85 | -- Mark enemy on hit. 86 | -- Attacking the enemie's head wth any weapon deals 250% damage, returns the Headhunter and clears the mark. 87 | { id = "Items.Preset_Punk_Knife_Iconic" }, 88 | 89 | -- HEADHUNTER / Weapon / Melee / Blade / Epic 90 | -- Throwing this knife is like imposing a death sentence. 91 | -- Mark enemy on hit. 92 | -- Attacking the enemie's head wth any weapon deals 250% damage, returns the Headhunter and clears the mark. 93 | { id = "Items.Preset_Punk_Knife_Iconic_Epic" }, 94 | 95 | -- HEADHUNTER / Weapon / Melee / Blade / Legendary 96 | -- Throwing this knife is like imposing a death sentence. 97 | -- Mark enemy on hit. 98 | -- Attacking the enemie's head wth any weapon deals 250% damage, returns the Headhunter and clears the mark. 99 | { id = "Items.Preset_Punk_Knife_Iconic_Legendary" }, 100 | 101 | -- HYPERCRITICAL / Weapon / Power / Precision Rifle / Epic 102 | -- Tiny Mike somehow got a hold of this Serbian gem and turned it into a masterpiece of weaponry. 103 | -- Sturdy design and Tiny Mike's Mods - an explosive combination. 104 | -- Bullets detonate knocking down enemies while the final one always deals critical damage. 105 | { id = "Items.Preset_Kolac_Tiny_Mike" }, 106 | 107 | -- HYPERCRITICAL / Weapon / Power / Precision Rifle / Legendary 108 | -- Tiny Mike somehow got a hold of this Serbian gem and turned it into a masterpiece of weaponry. 109 | -- Sturdy design and Tiny Mike's Mods - an explosive combination. 110 | -- Bullets detonate knocking down enemies while the final one always deals critical damage. 111 | { id = "Items.Preset_Kolac_Tiny_Mike_Legendary" }, 112 | 113 | -- KAPPA (MILITARY) / Weapon / Smart / Pistol 114 | -- This hyperactive weapon can mark up to two targets at the same time, though it works best at close range. 115 | { id = "Items.Preset_Kappa_Military" }, 116 | 117 | -- KAPPA (NEON) / Weapon / Smart / Pistol 118 | -- This hyperactive weapon can mark up to two targets at the same time, though it works best at close range. 119 | { id = "Items.Preset_Kappa_Neon" }, 120 | 121 | -- KAPPA (PIMP) / Weapon / Smart / Pistol 122 | -- This hyperactive weapon can mark up to two targets at the same time, though it works best at close range. 123 | { id = "Items.Preset_Kappa_Pimp" }, 124 | 125 | -- KAPPA / Weapon / Smart / Pistol 126 | -- This hyperactive weapon can mark up to two targets at the same time, though it works best at close range. 127 | { id = "Items.Preset_Kappa_Default" }, 128 | 129 | -- KAPPA / Weapon / Smart / Pistol 130 | -- This hyperactive weapon can mark up to two targets at the same time, though it works best at close range. 131 | { id = "Items.Preset_Kappa_Legendary" }, 132 | 133 | -- KYUBI (MILITARY) / Weapon / Power / Assault Rifle 134 | -- Semi-auto, all awesome. 135 | { id = "Items.Preset_Kyubi_Military" }, 136 | 137 | -- KYUBI (NEON) / Weapon / Power / Assault Rifle 138 | -- Semi-auto, all awesome. 139 | { id = "Items.Preset_Kyubi_Neon" }, 140 | 141 | -- KYUBI (PIMP) / Weapon / Power / Assault Rifle 142 | -- Semi-auto, all awesome. 143 | { id = "Items.Preset_Kyubi_Pimp" }, 144 | 145 | -- KYUBI / Weapon / Power / Assault Rifle 146 | -- Semi-auto, all awesome. 147 | { id = "Items.Preset_Kyubi_Default" }, 148 | 149 | -- KYUBI / Weapon / Power / Assault Rifle 150 | -- Semi-auto, all awesome. 151 | { id = "Items.Preset_Kyubi_Legendary" }, 152 | 153 | -- MA70 HB (MILITARY) / Weapon / Power / LMG 154 | -- It's time to bring out the big guns. 155 | { id = "Items.Preset_MA70_Military" }, 156 | 157 | -- MA70 HB (NEON) / Weapon / Power / LMG 158 | -- It's time to bring out the big guns. 159 | { id = "Items.Preset_MA70_Neon" }, 160 | 161 | -- MA70 HB (PIMP) / Weapon / Power / LMG 162 | -- It's time to bring out the big guns. 163 | { id = "Items.Preset_MA70_Pimp" }, 164 | 165 | -- MA70 HB / Weapon / Power / LMG 166 | -- It's time to bring out the big guns. 167 | { id = "Items.Preset_MA70_Default" }, 168 | 169 | -- MA70 HB / Weapon / Power / LMG 170 | -- It's time to bring out the big guns. 171 | { id = "Items.Preset_MA70_Legendary" }, 172 | 173 | -- NEUROTOXIN KNIFE (MILITARY) / Weapon / Melee / Blade 174 | -- Deadly combination of physical and chemical damage. 175 | { id = "Items.Preset_Neurotoxin_Knife_Military" }, 176 | 177 | -- NEUROTOXIN KNIFE (NEON) / Weapon / Melee / Blade 178 | -- Deadly combination of physical and chemical damage. 179 | { id = "Items.Preset_Neurotoxin_Knife_Neon" }, 180 | 181 | -- NEUROTOXIN KNIFE (PIMP) / Weapon / Melee / Blade 182 | -- Deadly combination of physical and chemical damage. 183 | { id = "Items.Preset_Neurotoxin_Knife_Pimp" }, 184 | 185 | -- PUNKNIFE (MILITARY) / Weapon / Melee / Blade 186 | -- Perfectly balanced, lethal and fun. Be careful when throwing this knife - you could (and will) poke someone's Kiroshi out. 187 | { id = "Items.Preset_Punk_Knife_Military" }, 188 | 189 | -- PUNKNIFE (NEON) / Weapon / Melee / Blade 190 | -- Perfectly balanced, lethal and fun. Be careful when throwing this knife - you could (and will) poke someone's Kiroshi out. 191 | { id = "Items.Preset_Punk_Knife_Neon" }, 192 | 193 | -- PUNKNIFE (PIMP) / Weapon / Melee / Blade 194 | -- Perfectly balanced, lethal and fun. Be careful when throwing this knife - you could (and will) poke someone's Kiroshi out. 195 | { id = "Items.Preset_Punk_Knife_Pimp" }, 196 | 197 | -- PUNKNIFE / Weapon / Melee / Blade 198 | -- Perfectly balanced, lethal and fun. Be careful when throwing this knife - you could (and will) poke someone's Kiroshi out. 199 | { id = "Items.Preset_Punk_Knife_Default" }, 200 | 201 | -- RAZOR (MILITARY) / Weapon / Melee / Blade 202 | -- If it ain't reinforced concrete, you'll dismember it in a few slashes. Gotta be beefy enough to wield it, though. 203 | { id = "Items.Preset_Machete_Borg_Military" }, 204 | 205 | -- RAZOR (NEON) / Weapon / Melee / Blade 206 | -- If it ain't reinforced concrete, you'll dismember it in a few slashes. Gotta be beefy enough to wield it, though. 207 | { id = "Items.Preset_Machete_Borg_Neon" }, 208 | 209 | -- RAZOR (PIMP) / Weapon / Melee / Blade 210 | -- If it ain't reinforced concrete, you'll dismember it in a few slashes. Gotta be beefy enough to wield it, though. 211 | { id = "Items.Preset_Machete_Borg_Pimp" }, 212 | 213 | -- RAZOR / Weapon / Melee / Blade 214 | -- If it ain't reinforced concrete, you'll dismember it in a few slashes. Gotta be beefy enough to wield it, though. 215 | { id = "Items.Preset_Machete_Borg_Default" }, 216 | 217 | -- ROACH WHISPERER / Clothing / Special / Rare 218 | -- Top prize in the Roach Race arcade minigame. Your car ended up on some roof? Call it with style. 219 | { id = "Items.Roach_Race_Outfit" }, 220 | 221 | -- SENKOH LX (MILITARY) / Weapon / Tech / SMG 222 | -- Self-defense has never been more stylish. 223 | { id = "Items.Preset_Senkoh_Military" }, 224 | 225 | -- SENKOH LX (NEON) / Weapon / Tech / SMG 226 | -- Self-defense has never been more stylish. 227 | { id = "Items.Preset_Senkoh_Neon" }, 228 | 229 | -- SENKOH LX (PIMP) / Weapon / Tech / SMG 230 | -- Self-defense has never been more stylish. 231 | { id = "Items.Preset_Senkoh_Pimp" }, 232 | 233 | -- SENKOH LX / Weapon / Tech / SMG 234 | -- Self-defense has never been more stylish. 235 | { id = "Items.Preset_Senkoh_Default" }, 236 | 237 | -- SENKOH LX / Weapon / Tech / SMG 238 | -- Self-defense has never been more stylish. 239 | { id = "Items.Preset_Senkoh_Legendary" }, 240 | 241 | -- VST-37 POZHAR (MILITARY) / Weapon / Power / Shotgun 242 | -- High caliber, high damage, high recoil. 243 | { id = "Items.Preset_Pozhar_Military" }, 244 | 245 | -- VST-37 POZHAR (NEON) / Weapon / Power / Shotgun 246 | -- High caliber, high damage, high recoil. 247 | { id = "Items.Preset_Pozhar_Neon" }, 248 | 249 | -- VST-37 POZHAR / Weapon / Power / Shotgun 250 | -- High caliber, high damage, high recoil. 251 | { id = "Items.Preset_Pozhar_Default" }, 252 | 253 | -- VST-37 POZHAR / Weapon / Power / Shotgun 254 | -- High caliber, high damage, high recoil. 255 | { id = "Items.Preset_Pozhar_Legendary" }, 256 | }, 257 | } -------------------------------------------------------------------------------- /samples/packs/stash-wall.lua: -------------------------------------------------------------------------------- 1 | -- The weapons that show up on the Stash Wall. 2 | return { 3 | Inventory = { 4 | -- ARCHANGEL / Weapon / Power / Revolver / Rare 5 | -- A piece of art that should never waste away in a display case. 6 | -- Deals electrical damage with a chance to apply Stun and has signfically reduced recoil. 7 | -- An angel of a weapon with a devil's attitude. 8 | { id = "Items.Preset_Overture_Kerry" }, 9 | 10 | -- BA XING CHONG / Weapon / Smart / Shotgun / Legendary 11 | -- The perfect arsenal for a one-person army. 12 | -- Fires explosive rounds and rips enemies to shreads. 13 | { id = "Items.Preset_Zhuo_Eight_Star" }, 14 | 15 | -- BREAKTHROUGH / Weapon / Tech / Sniper Rifle / Epic 16 | -- Pierces concrete like cardboard. 17 | -- Can penatrate through walls and ricochet multiple times. 18 | { id = "Items.Preset_Nekomata_Breakthrough" }, 19 | 20 | -- BUZZSAW / Weapon / Power / SMG / Rare 21 | -- An absolute beast with a nasty bite. Don't get too close. 22 | -- A unique mod that allows you to fire high penetration rounds. 23 | { id = "Items.Preset_Pulsar_Buzzsaw" }, 24 | 25 | -- COMRADE'S HAMMER / Weapon / Tech / Revolver / Epic 26 | -- Powered by revolutionary fervor. 27 | -- A modified clip contains only a single bullet laden with explosive material that wreaks havoc at the point of impact. 28 | -- Reloading this monster requires addtional time. 29 | { id = "Items.Preset_Burya_Comrade" }, 30 | 31 | -- CRASH / Weapon / Power / Revolver / Epic 32 | -- A weapon for someone who won't miss the first time. 33 | -- Charing for a brief time while aiming enables full auto mode. 34 | -- Decreases recoil and bullet spread. 35 | -- decreases fire rate. 36 | { id = "Items.Preset_Overture_River", quest = false }, 37 | 38 | -- JINCHU-MARU / Weapon / Melee / Blade / Epic 39 | -- The purest essence of a katana. No add-ons, no modifications - only razor-sharp steel. 40 | -- Gains 100% Crit Chance while Kerenzikov is active. 41 | -- Last Strike in a combo deals double damage. 42 | -- Doubles damage against enemies with twice your current health. 43 | { id = "Items.Preset_Katana_Takemura" }, 44 | 45 | -- LA CHINGONA DORADA / Weapon / Power / Pistol / Rare 46 | -- Jackie's pistol. A beautiful marriage of Japanese technology and Valentino style. 47 | -- Reduces reload time and adds an extra mod slot. 48 | -- Higher Chance to apply burn and stun. 49 | { id = "Items.Preset_Nue_Jackie" }, 50 | 51 | -- MALORIAN ARMS 3516 / Weapon / Power / Pistol / Legendary 52 | -- Custom-made for Johnny Silverhand. The guy had taste. 53 | { id = "Items.Preset_Silverhand_3516" }, 54 | 55 | -- MORON LABE / Weapon / Power / Assault Rifle / Epic 56 | -- A weapon for an alpha among alphas. 57 | -- Increases fire rate. 58 | -- Chance to dismember target. 59 | { id = "Items.Preset_Ajax_Moron" }, 60 | 61 | -- MOX / Weapon / Power / Shotgun / Rare 62 | -- An uncompromising force weapons technology that once belonged to Judy. 63 | -- Suited for high reflexes. 64 | -- Low reload speed. 65 | -- Modded barrel reduces spread while aiming. 66 | { id = "Items.Preset_Carnage_Mox" }, 67 | 68 | -- OVERWATCH / Weapon / Power / Sniper Rifle / Rare 69 | -- A sniper rifle that belonged to Panam. Increased reload spead and lethal accuracy - a rifle you can count on. 70 | -- Increases reload speed. 71 | -- Comes with a custom silencer. 72 | { id = "Items.Preset_Grad_Panam" }, 73 | 74 | -- PLAN B / Weapon / Power / Pistol / Rare 75 | -- Always have a backup plan for when your Plan A falls through. 76 | -- Bullets have a higher chance to apply bleeding, But keep an eye on your wallet! 77 | -- Every shot cost you eddies. 78 | { id = "Items.Preset_Liberty_Dex" }, 79 | 80 | -- PSALM 11:6 / Weapon / Power / Assault Rifle / Rare 81 | -- "Let him rain coals on the wicked; fire and sulfur and a scorching wind shall be the portion of their cup." 82 | -- Pure hellfire. 83 | -- This gun deals additional Thermal damage, greatly increasing the chance to apply burn. 84 | { id = "Items.Preset_Copperhead_Genesis" }, 85 | 86 | -- SATORI / Weapon / Melee / Blade / Rare 87 | -- Property of Saburo Arasaka. An antique katana forged in the first half of the 20th century that hasn't dulled a day. 88 | -- Reduces base damge but increases Crit damage significally. 89 | -- +500%. 90 | { id = "Items.Preset_Katana_Saburo" }, 91 | 92 | -- SKIPPY / Weapon / Smart / Pistol / Epic 93 | -- A smartgun with a unique voice-user interface. Slightly unpredictable. 94 | -- Scales to the user's level. 95 | { id = "Items.mq007_skippy", quest = false }, 96 | 97 | -- SOVEREIGN / Weapon / Power / Shotgun / Epic 98 | -- Become the king of the hunt. 99 | -- Reduces reload time and bullet spread. 100 | -- Fires two rounds per shot while aiming. 101 | { id = "Items.Preset_Igla_Sovereign" }, 102 | 103 | -- THE HEADSMAN / Weapon / Power / Shotgun / Epic 104 | -- The go-to choice for when you've lost track of your enemies. 105 | -- Doubles the number of projectiles per shot and increases the hance for dismemberment or Bleeding. 106 | -- Increases spread, reduces reload time, reduces rate of fire and reduces clip capacity compared to the base version of the gun. 107 | { id = "Items.Preset_Tactician_Headsman" }, 108 | 109 | -- YINGLONG / Weapon / Smart / SMG / Legendary 110 | -- A genius among smartguns. 111 | -- Deals Additional Electrical damage with a small chance to apply EMP on impact. 112 | { id = "Items.Preset_Dian_Yinglong" }, 113 | }, 114 | } -------------------------------------------------------------------------------- /samples/packs/vehicles.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Vehicles = { 3 | "Vehicle.v_sport2_porsche_911turbo_player", -- 911 II (930) TURBO 4 | "Vehicle.v_sport1_rayfield_aerondight_player", -- AERONDIGHT "GUINEVERE" 5 | "Vehicle.v_sport2_villefort_alvarado_valentinos_player", -- ALVARADO "VATO" 6 | "Vehicle.v_sport2_villefort_alvarado_player", -- ALVARADO V4F 570 DELEGATE 7 | "Vehicle.v_sportbike3_brennan_apollo_nomad_player", -- APOLLO "SCORPION" 8 | "Vehicle.v_sportbike3_brennan_apollo_player", -- APOLLO 9 | "Vehicle.v_sportbike2_arch_player", -- ARCH NAZARÉ 10 | "Vehicle.v_sport1_rayfield_caliburn_02_player", -- CALIBURN (BLACK) 11 | "Vehicle.v_sport1_rayfield_caliburn_player", -- CALIBURN (WHITE) 12 | "Vehicle.v_standard25_thorton_colby_nomad_player", -- COLBY "LITTLE MULE" 13 | "Vehicle.v_standard2_thorton_colby_player", -- COLBY C125 14 | "Vehicle.v_standard25_thorton_colby_pickup_player", -- COLBY CX410 BUTTE (GREEN) 15 | "Vehicle.v_standard25_thorton_colby_pickup_02_player", -- COLBY CX410 BUTTE (RED) 16 | "Vehicle.v_standard25_villefort_columbus_player", -- COLUMBUS V340-F FREIGHT 17 | "Vehicle.v_standard2_villefort_cortes_player", -- CORTES V5000 VALOR 18 | "Vehicle.v_standard2_villefort_cortes_delamain_player", -- DELAMAIN NO.21 19 | "Vehicle.v_standard3_chevalier_emperor_player", -- EMPEROR 620 RAGNAR 20 | "Vehicle.v_standard2_thorton_galena_nomad_player", -- GALENA "GECKO" 21 | "Vehicle.v_standard2_thorton_galena_bobas_player", -- GALENA "RATTLER" 22 | "Vehicle.v_standard2_thorton_galena_player", -- GALENA G240 23 | "Vehicle.v_standard2_archer_hella_player", -- HELLA EC-D I360 24 | "Vehicle.v_sportbike2_arch_jackie_player", -- JACKIE'S ARCH 25 | "Vehicle.v_sportbike2_arch_jackie_tuned_player", -- JACKIE'S TUNED ARCH 26 | "Vehicle.v_sportbike1_yaiba_kusanagi_tyger_player", -- KUSANAGI "MIZUCHI" 27 | "Vehicle.v_sportbike1_yaiba_kusanagi_player", -- KUSANAGI CT-3X 28 | "Vehicle.v_standard3_thorton_mackinaw_ncu_player", -- MACKINAW "BEAST" 29 | "Vehicle.v_standard3_thorton_mackinaw_02_player", -- MACKINAW "SAGUARO" 30 | "Vehicle.v_standard3_thorton_mackinaw_player", -- MACKINAW MTL1 31 | "Vehicle.v_standard2_makigai_maimai_player", -- MAIMAI P126 32 | "Vehicle.v_sportbike2_arch_tyger_player", -- NAZARÉ "ITSUMADE" 33 | "Vehicle.v_sport1_herrera_outlaw_player", -- OUTLAW GTS 34 | "Vehicle.v_standard2_archer_bandit_player", -- QUARTZ "BANDIT" 35 | "Vehicle.v_standard2_archer_quartz_player", -- QUARTZ EC-T2 R660 36 | "Vehicle.v_sport2_mizutani_shion_nomad_02_player", -- SHION "COYOTE" (RED) 37 | "Vehicle.v_sport2_mizutani_shion_nomad_player", -- SHION "COYOTE" 38 | "Vehicle.v_sport2_mizutani_shion_player", -- SHION MZ2 39 | "Vehicle.v_standard25_mahir_supron_player", -- SUPRON FS3 40 | "Vehicle.v_standard2_chevalier_thrax_player", -- THRAX 388 JEFFERSON 41 | "Vehicle.v_sport1_quadra_turbo_player", -- TURBO-R 740 42 | "Vehicle.v_sport1_quadra_turbo_r_player", -- TURBO-R V-TECH 43 | "Vehicle.v_sport2_quadra_type66_nomad_ncu_player", -- TYPE-66 "CTHULHU" 44 | "Vehicle.v_sport2_quadra_type66_nomad_player", -- TYPE-66 "JAVELINA" 45 | "Vehicle.v_sport2_quadra_type66_player", -- TYPE-66 "JEN ROWLEY" 46 | "Vehicle.v_sport2_quadra_type66_02_player", -- TYPE-66 640 TS 47 | "Vehicle.v_sport2_quadra_type66_avenger_player", -- TYPE-66 AVENGER 48 | }, 49 | } -------------------------------------------------------------------------------- /samples/packs/weapons-atts.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- CS-1 TAIPAN / Mod / Muzzle / Common 4 | -- They'll never know what hit 'em. 5 | -- 2x Damage Multiplier when attacked from stealth, Reduces base damge by 30%. 6 | { id = "Items.w_silencer_01" }, 7 | 8 | -- XC-10 ALECTO / Mod / Muzzle / Rare 9 | -- They'll never know what hit 'em. 10 | -- 2.5x Damage Multiplier when attacked from stealth, 15% Reduced damage. 11 | { id = "Items.w_silencer_04" }, 12 | 13 | -- XC-10 CETUS / Mod / Muzzle / Uncommon 14 | -- They'll never know what hit 'em. 15 | -- 2.5x Damage Multiplier when attacked from stealth, Silencer, Increases Crit Chance by 5%, Reduces base damge by 25%. 16 | { id = "Items.w_silencer_03" }, 17 | 18 | -- XC-10 STRIX / Mod / Muzzle / Uncommon 19 | -- They'll never know what hit 'em. 20 | -- 2.5x Damage Multiplier when attacked from stealth, Silencer, Increases Crit Chance by 10%, Reduces base damge by 30%. 21 | { id = "Items.w_silencer_02" }, 22 | 23 | -- ADD-VANTAGE / Mod / Scope 24 | -- A holographic sight produced cheaply for the mass market, but able to change colors based on detected threats. 25 | { id = "Items.w_att_scope_short_03" }, 26 | 27 | -- CQO MK.72 KANONE MINI / Mod / Scope 28 | -- A sight designed by Militech. No bells, no whistles - just good, clean, standard-issue functionality. 29 | { id = "Items.w_att_scope_short_04" }, 30 | 31 | -- E255 PERCIPIENT / Mod / Scope 32 | -- A long scope developed by Nokota Manufacturing. Its digitial targeting system enables precise coordinate-tracking and target acquisition at range. 33 | { id = "Items.w_att_scope_long_02" }, 34 | 35 | -- E305 PROSPECTA / Mod / Scope 36 | -- A sleek, digital sniper scope developed by Nokota Manufacturing. With a gem like this, no enemy will be safe - no matter the range. 37 | { id = "Items.w_att_scope_sniper_02" }, 38 | 39 | -- GAKI / Mod / Scope 40 | -- A precise, high-quality digital sniper scope by Tsunami. The only thing that could bring you closer to your target is a romantic dinner. 41 | { id = "Items.w_att_scope_sniper_03" }, 42 | 43 | -- HANDYMAN / Mod / Scope 44 | -- A digital short scope handcrafted by one of Night City's countless iron techies. 45 | { id = "Items.w_att_scope_short_06" }, 46 | 47 | -- HPO MK.77 KANONE MAX / Mod / Scope 48 | -- A standard-issue sniper scope designed by Militech. It guarantees reliability at extreme range. 49 | { id = "Items.w_att_scope_sniper_01" }, 50 | 51 | -- HYAKUME / Mod / Scope 52 | -- A fully digital sight featuring a built-in virtual ammo counter. A must-have for the modern battlefield. 53 | { id = "Items.w_att_scope_short_02" }, 54 | 55 | -- JUE / Mod / Scope 56 | -- Although Kang Tao excels in smart targeting systems, the Jue digital long scope is proof they still value good old-fashioned marksmanship. 57 | { id = "Items.w_att_scope_long_05" }, 58 | 59 | -- KANETSUGU / Mod / Scope 60 | -- Precise, discreet and reliable. This holographic handgun scope is nothing short of yet another masterpiece delivered by Arasaka. 61 | { id = "Items.w_att_scope_short_07" }, 62 | 63 | -- MK.2X GRANDSTAND / Mod / Scope 64 | -- A long scope with digitally enhanced targeting systems. Most effective in conditions with low visibility. 65 | { id = "Items.w_att_scope_long_01" }, 66 | 67 | -- MK.8 CLEARVUE / Mod / Scope 68 | -- A long scope lacking advanced features, but does include an ammo counter attached to the left side. Simple, but effective. 69 | { id = "Items.w_att_scope_long_03" }, 70 | 71 | -- OS-1 GIMLETEYE / Mod / Scope 72 | -- A sight designed to keep a low profile. As light and simple as optical scopes come. 73 | { id = "Items.w_att_scope_short_01" }, 74 | 75 | -- SO-21 SAIKA / Mod / Scope 76 | -- A holographic long scope developed by the Arasaka Corporation. Precise and unobtrusive. 77 | { id = "Items.w_att_scope_long_04" }, 78 | 79 | -- TYPE 2067 / Mod / Scope 80 | -- A designer holographic sight built for function as well as beauty. Its built-in glass reflector improves visiblity at all times of day. 81 | { id = "Items.w_att_scope_short_05" }, 82 | }, 83 | } -------------------------------------------------------------------------------- /samples/packs/weapons-mods.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Inventory = { 3 | -- COLD SHOULDER / Mod / Melee / Rare 4 | -- Increases damage by 7. 5 | { id = "Items.ValentinosMeleeWeaponMod" }, 6 | 7 | -- KUNAI / Mod / Melee / Rare 8 | -- Increases weapon attack speed by 0.3. 9 | { id = "Items.ArasakaMeleeWeaponMod" }, 10 | 11 | -- SCOURGE / Mod / Melee / Rare 12 | -- Increases Crit Damage by 10%. 13 | { id = "Items.WraithsMeleeWeaponMod" }, 14 | 15 | -- WHITE KNUCKLED / Mod / Melee / Rare 16 | -- Increases Crit Chance by 7%. 17 | { id = "Items.TygerMeleeWeaponMod" }, 18 | 19 | -- AUTOCORRECT / Mod / Ranged / Epic 20 | -- Locking on lasts for X sec. 21 | -- longer after losing sight of the target. 22 | { id = "Items.SmartWeaponMod02" }, 23 | 24 | -- AUTOLOADER / Mod / Ranged / Rare 25 | -- Automatically loads a bullet in the chamber after depleting a magazine. 26 | { id = "Items.SimpleWeaponMod16" }, 27 | 28 | -- BATTERY / Mod / Ranged / Epic 29 | -- Reduces charge time for Tech weapons by X%. 30 | { id = "Items.SimpleWeaponMod10" }, 31 | 32 | -- BESMART / Mod / Ranged / Legendary 33 | -- Allows you to use Smart weapons without the required cyberware. 34 | { id = "Items.SimpleWeaponMod15" }, 35 | 36 | -- BLACK KNIGHT / Mod / Ranged / Epic 37 | -- Tech weapons discharge X% faster. 38 | { id = "Items.TechWeaponMod05" }, 39 | 40 | -- BOOSTERS / Mod / Ranged / Epic 41 | -- Increases projectile velocity by X%. 42 | { id = "Items.SmartWeaponMod05" }, 43 | 44 | -- BOUNCY / Mod / Ranged / Epic 45 | -- Increases damage from ricochets by X%. 46 | { id = "Items.SimpleWeaponMod08" }, 47 | 48 | -- CHARGE SPIKE / Mod / Ranged / Epic 49 | -- Increases damage from charged Tech weapons by a maximum of X% (depending on amount charged). 50 | { id = "Items.SimpleWeaponMod09" }, 51 | 52 | -- COMBAT AMPLIFIER / Mod / Ranged / Rare 53 | -- Increases chance to apply damage-related status effect by 5%. 54 | { id = "Items.SimpleWeaponMod04" }, 55 | 56 | -- COUNTERMASS / Mod / Ranged / Epic 57 | -- Eliminates vertical recoil while aiming. 58 | { id = "Items.SimpleWeaponMod11" }, 59 | 60 | -- CRUNCH / Mod / Ranged 61 | -- Increases damage by 5/6/7/8/9. 62 | { id = "Items.SimpleWeaponMod01" }, 63 | 64 | -- DOUBLE SHOT / Mod / Ranged / Epic 65 | -- Fires 2 rounds at once, but significantly lowers the rate of fire. 66 | { id = "Items.TechWeaponMod02" }, 67 | 68 | -- FOCUS / Mod / Ranged / Epic 69 | -- Eliminates vertical recoil while aiming. 70 | { id = "Items.PowerWeaponMod03" }, 71 | 72 | -- KNOCKDOWN / Mod / Ranged / Epic 73 | -- Reduces enemy Armor by {int_0} for {int_1} sec. 74 | { id = "Items.PowerWeaponMod01" }, 75 | 76 | -- MARKFINDER / Mod / Ranged / Epic 77 | -- Reduces lock-on time by X%. 78 | { id = "Items.SmartWeaponMod01" }, 79 | 80 | -- MICROREACTOR / Mod / Ranged / Epic 81 | -- Reduces weapon charge time by X%. 82 | { id = "Items.TechWeaponMod04" }, 83 | 84 | -- NANOINFECTION / Mod / Ranged / Epic 85 | -- Increases size of smart markfinder by X%. 86 | { id = "Items.SmartWeaponMod04" }, 87 | 88 | -- NEON ARROW / Mod / Ranged / Rare 89 | -- Reduces weapon reload time by 5%. 90 | { id = "Items.TygerRangedWeaponMod" }, 91 | 92 | -- OVERPENETRATE / Mod / Ranged / Epic 93 | -- Increases projectile kinetic energy by {int_0} times, increasing the chance to knock down an enemy. 94 | { id = "Items.PowerWeaponMod04" }, 95 | 96 | -- PACIFIER / Mod / Ranged 97 | -- Increases Crit Damage by 6/8/10/12/14%. 98 | { id = "Items.SimpleWeaponMod03" }, 99 | 100 | -- PACIFIER / Mod / Ranged / Rare 101 | -- Increases Crit Damage by X%. 102 | { id = "Items.SimpleWeaponMod03Rare" }, 103 | 104 | -- PAX / Mod / Ranged / Uncommon 105 | -- Renders your weapon non-leathal, Increases damage by 6. 106 | { id = "Items.SimpleWeaponMod17" }, 107 | 108 | -- PENETRATOR / Mod / Ranged 109 | -- Increases Crit Chance by 2/3/4/5/6%. 110 | { id = "Items.SimpleWeaponMod02" }, 111 | 112 | -- PHANTOM / Mod / Ranged / Rare 113 | -- Increases rate of fire by 5%. 114 | { id = "Items.WraithsRangedWeaponMod" }, 115 | 116 | -- PULPIFY / Mod / Ranged / Epic 117 | -- Bullet spread is reduced after every shot by {int_0}%. 118 | { id = "Items.PowerWeaponMod02" }, 119 | 120 | -- PULVERIZE / Mod / Ranged / Uncommon 121 | -- Increases damage to limbs by 5%. 122 | { id = "Items.SimpleWeaponMod12" }, 123 | 124 | -- RICOCHET ENGINE / Mod / Ranged / Epic 125 | { id = "Items.PowerWeaponMod07" }, 126 | 127 | -- SCORCH / Mod / Ranged / Legendary 128 | -- Increases charge threshold to X%. 129 | { id = "Items.SimpleWeaponMod14" }, 130 | 131 | -- STABILIZER / Mod / Ranged / Epic 132 | -- Reduces weapon sway by {int_0}%. 133 | { id = "Items.PowerWeaponMod05" }, 134 | 135 | -- SUBSONIC / Mod / Ranged / Epic 136 | -- Reduces weapon draw time by half. 137 | -- Increases damage by {int_0}%. 138 | { id = "Items.PowerWeaponMod06" }, 139 | 140 | -- SUPERCAPACITOR / Mod / Ranged / Epic 141 | -- Fully charged weapon deals X% more damage. 142 | { id = "Items.TechWeaponMod01" }, 143 | 144 | -- SUPERCAPACITOR / Mod / Ranged / Legendary 145 | -- Fully charged weapon deals X% more damage. 146 | { id = "Items.Patricia_Weapon_Mod" }, 147 | 148 | -- TARGET ANALYSIS / Mod / Ranged / Epic 149 | -- Reduces the minimum charge required to penetrate walls by X%. 150 | { id = "Items.TechWeaponMod03" }, 151 | 152 | -- TRACKER / Mod / Ranged / Epic 153 | -- Weapon tracks X additional target(s). 154 | { id = "Items.SmartWeaponMod03" }, 155 | 156 | -- VENDETTA / Mod / Ranged / Rare 157 | -- Increases headshot damage by 20%. 158 | { id = "Items.ValentinosRangedWeaponMod" }, 159 | 160 | -- WEAKEN / Mod / Ranged / Rare 161 | -- Hitting a targe reduces their shooting accuracy by 20% for 10 seconds. 162 | { id = "Items.SimpleWeaponMod13" }, 163 | }, 164 | } -------------------------------------------------------------------------------- /samples/specs/spec-01-overview.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- This is just an overview to get the idea what spec can contain. 3 | -- 4 | return { 5 | Character = { 6 | Level = ..., 7 | StreetCred = ..., 8 | Attributes = {...}, 9 | Skills = {...}, 10 | Progression = {...}, 11 | Perks = {...}, 12 | }, 13 | 14 | Inventory = {...}, 15 | 16 | Crafting = { 17 | Components = {...}, 18 | Recipes = {...}, 19 | }, 20 | 21 | Transport = {...}, 22 | } -------------------------------------------------------------------------------- /samples/specs/spec-02-character.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- The Character section is very simple and self-explanatory. 3 | -- It defines the character level, street cred, attributes, skills, 4 | -- perks, and unused attribute and perks points. 5 | -- 6 | return { 7 | Character = { 8 | Level = 1, 9 | StreetCred = 1, 10 | 11 | Attributes = { 12 | Body = 3, 13 | Reflexes = 3, 14 | TechnicalAbility = 3, 15 | Intelligence = 3, 16 | Cool = 3, 17 | }, 18 | 19 | Skills = { 20 | Athletics = 1, 21 | Annihilation = 1, 22 | StreetBrawler = 1, 23 | Assault = 1, 24 | Handguns = 1, 25 | Blades = 1, 26 | Crafting = 1, 27 | Engineering = 1, 28 | BreachProtocol = 1, 29 | Quickhacking = 1, 30 | Stealth = 1, 31 | ColdBlood = 1, 32 | }, 33 | 34 | -- Progression in each skill for curren skill level (not from the level 1). 35 | Progression = { 36 | Athletics = 0, 37 | Annihilation = 0, 38 | StreetBrawler = 0, 39 | Assault = 0, 40 | Handguns = 0, 41 | Blades = 0, 42 | Crafting = 0, 43 | Engineering = 0, 44 | BreachProtocol = 0, 45 | Quickhacking = 0, 46 | Stealth = 0, 47 | ColdBlood = 0, 48 | }, 49 | 50 | -- For fulle Perk list see "samples/specs/spec-03-perks.lua". 51 | Perks = {...}, 52 | 53 | -- Extra Perk Points earned from Perk Shards. 54 | -- There are currently 9 known Perk Shard locations in the game. 55 | PerkShards = 9, 56 | }, 57 | } -------------------------------------------------------------------------------- /samples/specs/spec-04-inventory.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- The Inventory section defines items of any kind. 3 | -- Items can go into a backpack or be equipped in the suitable slot. 4 | -- 5 | return { 6 | Inventory = { 7 | -- This is a complete example of item to get the overall idea. 8 | -- This example defines WIDOW MAKER tech precision rifle. 9 | -- 10 | -- When saving items each item entry gets a comment with 11 | -- the in-game name, category and rarity. 12 | 13 | -- WIDOW MAKER / Weapon / Tech / Legendary 14 | { 15 | id = "Preset_Achilles_Nash_Legendary", 16 | seed = 4114643488, 17 | slots = { 18 | -- E255 PERCIPIENT / Rare 19 | { slot = "Scope", id = "w_att_scope_long_02", seed = 442254023, upgrade = "Rare" }, 20 | -- COUNTERMASS / Epic 21 | { slot = "Mod1", id = "SimpleWeaponMod11" }, 22 | -- WEAKEN / Rare 23 | { slot = "Mod2", id = "SimpleWeaponMod13" }, 24 | -- CRUNCH / Epic 25 | { slot = "Mod3", id = "SimpleWeaponMod01", upgrade = "Epic" }, 26 | -- CRUNCH / Epic 27 | { slot = "Mod4", id = "SimpleWeaponMod01", upgrade = "Epic" }, 28 | }, 29 | equip = 2, 30 | }, 31 | 32 | -- The first and the only required property is `id`. 33 | -- There are 3 ways to define an item ID: 34 | 35 | -- 1. Item ID as a name. 36 | { id = "Preset_Achilles_Nash_Legendary" }, 37 | 38 | -- 2. Item ID as a struct. 39 | { id = { hash = 0x73677016, length = 36 } }, 40 | 41 | -- 3. Item ID as numeric value. 42 | { id = 0x243677016 }, 43 | 44 | -- There is also a shorthand definition on an item, 45 | -- when other properties are irrelevant: 46 | "Preset_Achilles_Nash_Legendary", 47 | 48 | -- Or with numeric value: 49 | 0x2473677016, 50 | 51 | -- The next property is `seed`. 52 | -- The seed is an integer that defines unique instance of the item. 53 | -- when paired with the item ID 54 | -- 55 | -- Some items can be actually randomized and have versions with 56 | -- different stats. In this case the seed is the ID of the version. 57 | -- The same pair of item ID + seed will always produce the same 58 | -- version of the item. 59 | 60 | -- WIDOW MAKER / Weapon / Tech / Legendary 61 | { id = "Preset_Achilles_Nash_Legendary", seed = 4114643488 }, 62 | 63 | -- But not every item is actually randomizable. Some items will 64 | -- always have the same stats despite the different seed values. 65 | -- For example all cyberware have fixed stats, so the seed value 66 | -- doesn't affect cyberware stats. 67 | -- 68 | -- In such case the seed serves only as a global unique ID of an 69 | -- item if paired with the item ID. This can be better explained 70 | -- using another property `qty` which is pretty straight forward 71 | -- and just defines how many pieces of an item will be produced. 72 | -- 73 | -- The most basic example of `qty` use is stackable item like 74 | -- meds or food. This will add 50 heals to the inventory: 75 | 76 | -- MAXDOC MK.1 / Consumable / Meds / Uncommon 77 | { id = "FirstAidWhiffV0", qty = 50 }, 78 | 79 | -- When used with the equipment it will produce the given number of 80 | -- individual copies of an item. And every item will have a random seed: 81 | 82 | -- BIODYNE BERSERK MK.1 / Cyberware / Operating System / Uncommon 83 | { id = "BerserkC2MK1", qty = 3 }, 84 | 85 | -- WIDOW MAKER / Weapon / Tech / Legendary 86 | { id = "Preset_Achilles_Nash_Legendary", qty = 3 }, 87 | 88 | -- The difference is that all 3 copy of a weapon will have random 89 | -- different stats. While all 3 cyberware pieces will be the same. 90 | -- 91 | -- Since item ID + seed serves as a global unique ID, and you can't 92 | -- have two items with the same global ID, the next combination 93 | -- doesn't make sence: 94 | 95 | -- BIODYNE BERSERK MK.1 / Cyberware / Operating System / Uncommon 96 | { id = "BerserkC2MK1", seed = 1441747907, qty = 3 }, 97 | 98 | -- The mod will discard the `qty` value and produce only one item. 99 | 100 | -- The next property is `upgrade`. 101 | -- It serve two puposes: to scale item up to level of character, 102 | -- and to upgrade from one rarity level to another. 103 | -- 104 | -- When set to `true`, the item will be scale up to the character 105 | -- level but will remain of the same rarity: 106 | 107 | -- WIDOW MAKER / Weapon / Tech / Rare 108 | { id = "Preset_Achilles_Nash", upgrade = true }, 109 | 110 | -- When set to string with rarity name, such as "Common", "Uncommon", 111 | -- "Rare", "Epic", "Legendary", the item will be scale up to the character 112 | -- and upgraded to the given rarity: 113 | 114 | -- WIDOW MAKER / Weapon / Tech / Legendary 115 | { id = "Preset_Achilles_Nash", upgrade = "Legendary" }, 116 | 117 | -- For some items it's the only way to get all versions. For example, 118 | -- the ARMADILLO clothing mod. The only way to get the Epic version of 119 | -- the mod is using `upgrade`. There is no dedicated item ID for each 120 | -- rarity of that mod: 121 | 122 | -- ARMADILLO / Epic 123 | { id = "SimpleFabricEnhancer01", upgrade = "Epic" }, 124 | 125 | -- The next property is `slots`. 126 | -- It defines attachments and mods, and assigns them to the slot of the item. 127 | -- 128 | -- Each entry of the `slots` has to define a slot name using `slot` property. 129 | -- Besides that each entry is just an item and everything that was said before 130 | -- about defining items applies to the attachments and mods. 131 | -- 132 | -- For most items the names of the slots are easy to remeber or even guess: 133 | 134 | -- ARCHANGEL / Weapon / Power / Legendary 135 | { 136 | id = "Preset_Overture_Kerry_Legendary", 137 | slots = { 138 | -- OS-1 GIMLETEYE / Uncommon 139 | { slot = "Scope", id = "w_att_scope_short_01", seed = 3868463560, upgrade = "Uncommon" }, 140 | -- XC-10 ALECTO / Rare 141 | { slot = "Muzzle", id = "w_silencer_04" }, 142 | -- COUNTERMASS / Epic 143 | { slot = "Mod1", id = "SimpleWeaponMod11" }, 144 | -- PACIFIER / Epic 145 | { slot = "Mod2", id = "SimpleWeaponMod03", upgrade = "Epic" }, 146 | } 147 | }, 148 | 149 | -- In the "Scope" slot here is OS-1 GIMLETEYE scope. And it has both `seed` and 150 | -- `upgrade` properties. It's because scopes are randomizable and have different 151 | -- rarities under the same item ID. So this is the only way to specity the exact 152 | -- version of a scope with the desired stats. 153 | -- 154 | -- In the "Muzzle" slot here is XC-10 ALECTO silencer. And unlike scope it doesn't 155 | -- have any properties besides item ID. It's because silencers have fixed stats 156 | -- and rarity. They cannot be actually randomized or upgraded. 157 | -- 158 | -- There is also two weapon mods in slots "Mod1" and "Mod2". Ranged weapons can 159 | -- have up to 4 mods, so there are also "Mod3" and "Mod4". If slot is omitted in 160 | -- the spec, then it will be empty when the item is loaded. 161 | 162 | -- There are couple of cyberware with unique slot names. Those slot names can be 163 | -- found in "samples/packs/cyberware.lua". Here an example: 164 | 165 | -- GORILLA ARMS / Cyberware / Arms / Legendary 166 | { 167 | id = "StrongArmsLegendary", 168 | slots = { 169 | -- KNUCKLES - PHYSICAL DAMAGE / Rare 170 | { slot = "Knuckles", id = "PhysicalDamageKnuckles" }, 171 | -- BLACK MARKET BATTERY / Legendary 172 | { slot = "Battery", id = "AnimalsStrongArmsBattery1" }, 173 | -- SENSORY AMPLIFER - CRIT DAMAGE / Rare 174 | { slot = "Mod", id = "ArmsCyberwareSharedFragment2" }, 175 | } 176 | }, 177 | 178 | -- For the clothing items there is an extra option for `slots` property to get 179 | -- the max number of mod slots: 180 | 181 | -- TACTICAL HYBRID CORPORATE GLASSES / Clothing / Face / Legendary 182 | { id = "Corporate_01_Set_Glasses", slots = "max" }, 183 | 184 | -- The next property is `equip`. 185 | -- This property is used to assign the item to the equipment slot. There is no need 186 | -- to specify particular equipment area, like "Weapon" or "Head", it's resolved by 187 | -- the item type automatically. 188 | -- 189 | -- If there is only one slot for an item type, like "Head", then it's enough to 190 | -- just set `eauip` to `true` to assign the item to the suitable slot. 191 | -- 192 | -- If there is more than one slot, for example 3 weapon slots, then the valid 193 | -- options for `equip` are 1, 2, or 3: 194 | 195 | { id = "Preset_Achilles_Nash_Legendary", equip = 2 }, 196 | 197 | -- The final property is `quest`. 198 | -- It's used to control ther quest marker on the item. 199 | -- Just set to `true` or `false` to set or remove the marker: 200 | 201 | -- SKIPPY / Weapon / Smart / Epic 202 | { id = "mq007_skippy", quest = false }, 203 | }, 204 | } -------------------------------------------------------------------------------- /samples/specs/spec-05-crafting.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- The Crafting section defines crafting components and crafting recipes (specs). 3 | -- 4 | return { 5 | Crafting = { 6 | Components = { 7 | CommonItem = 23466, 8 | UncommonItem = 13685, 9 | RareItem = 7402, 10 | RareUpgrade = 15908, 11 | EpicItem = 1107, 12 | EpicUpgrade = 4244, 13 | LegendaryItem = 363, 14 | LegendaryUpgrade = 612, 15 | UncommonQuickhack = 1054, 16 | RareQuickhack = 613, 17 | EpicQuickhack = 230, 18 | LegenaryQuickhack = 98, 19 | }, 20 | 21 | Recipes = { 22 | -- Each recipe is defined by the item ID to craft, not by the item ID of a recipe as a loot. 23 | -- Each recipe entry has comment with the name of the item and a rarity if rarity is fixed. 24 | "Preset_Nekomata_Breakthrough", -- BREAKTHROUGH / Epic 25 | "Preset_Pulsar_Buzzsaw", -- BUZZSAW / Rare 26 | "SimpleFabricEnhancer01", -- ARMADILLO 27 | 28 | -- If the mod doesn't know the textual item ID, then the hash value will be used. 29 | 0x16BCFB0A12, -- OZOB'S NOSE / Legendary 30 | }, 31 | }, 32 | } -------------------------------------------------------------------------------- /samples/specs/spec-06-scripting.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Scripting example: 3 | -- Create a Corpo Glasses for every legendary clothing mod with that mod in each slot. 4 | -- 5 | 6 | -- This opens access to the mod functions 7 | local ctx = ... 8 | 9 | -- Load TweakDb class to access metadata 10 | local TweakDb = ctx.require("mod/helpers/TweakDb") 11 | 12 | -- Create instance and load the metadata 13 | local tweakDb = TweakDb:new(true) 14 | 15 | -- Table to store our items 16 | local backpack = {} 17 | 18 | -- Iterate every legendary clothing mod 19 | for _, mod in tweakDb:filter({ kind = "Mod", group = "Clothing", quality = "Legendary" }) do 20 | 21 | -- Item spec with slots container 22 | local glasses = { 23 | id = "Corporate_01_Set_Glasses", 24 | slots = {} 25 | } 26 | 27 | -- Put mod in every mod slot 28 | for index = 1, 3 do 29 | table.insert(glasses.slots, { 30 | slot = "Mod" .. index, 31 | id = mod.id, 32 | }) 33 | end 34 | 35 | -- Add to the table 36 | table.insert(backpack, glasses) 37 | end 38 | 39 | -- Free database resources 40 | tweakDb:unload() 41 | 42 | -- Return the final spec 43 | return { 44 | Backpack = backpack 45 | } -------------------------------------------------------------------------------- /specs/V.lua: -------------------------------------------------------------------------------- 1 | -- This is just a placeholder. 2 | -- If you want to try to load a spec then copy "samples/Noname.lua" here. 3 | return { 4 | } --------------------------------------------------------------------------------