├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── archive ├── EquipmentEx.cpmodproj ├── custom_refs.txt └── source │ ├── archive │ └── equipment_ex │ │ ├── gui │ │ └── wardrobe.inkwidget │ │ └── localization │ │ ├── ar-ar.json │ │ ├── cz-cz.json │ │ ├── de-de.json │ │ ├── en-us.json │ │ ├── es-es.json │ │ ├── fr-fr.json │ │ ├── it-it.json │ │ ├── kr-kr.json │ │ ├── pl-pl.json │ │ ├── pt-br.json │ │ ├── ru-ru.json │ │ ├── tr-tr.json │ │ └── zh-cn.json │ └── resources │ └── EquipmentEx.archive.xl ├── scripts ├── CompatibilityManager.reds ├── DevMode.reds ├── Facade.reds ├── InventoryHelper.reds ├── Migrations.reds ├── OutfitConfig.reds ├── OutfitEvents.reds ├── OutfitState.reds ├── OutfitSystem.reds ├── Overrides │ ├── BackpackMainGameController.reds │ ├── CraftingGarmentItemPreviewGameController.reds │ ├── EquipmentSystem.reds │ ├── InventoryItemDisplayController.reds │ ├── InventoryItemModeLogicController.reds │ ├── PhotoModePlayerEntityComponent.reds │ ├── PopupsManager.reds │ ├── QuestTrackerGameController.reds │ ├── Stash.reds │ ├── UIInventoryItem.reds │ ├── UIInventoryItemsManager.reds │ ├── WardrobeSetPreviewGameController.reds │ ├── WardrobeUIGameController.reds │ ├── gameuiInGameMenuGameController.reds │ ├── gameuiInventoryGameController.reds │ ├── gameuiPhotoModeMenuController.reds │ ├── inkInventoryPuppetPreviewGameController.reds │ └── inkScrollController.reds ├── PaperdollHelper.reds ├── Tweaks │ ├── OutfitSlotMatcher.reds │ ├── OutfitTweakHelper.reds │ ├── PatchCustomItems.reds │ ├── PatchOriginaltems.reds │ └── RegisterOutfitSlots.reds ├── UI │ ├── ArchivePopup.reds │ ├── CollapseButton.reds │ ├── ConflictsPopup.reds │ ├── InventoryGridData.reds │ ├── InventoryGridItem.reds │ ├── InventoryGridSlot.reds │ ├── ItemSourceOption.reds │ ├── OutfitListData.reds │ ├── OutfitListEntry.reds │ ├── OutfitManager.reds │ ├── OutfitMappingPopup.reds │ ├── OutfitSlotOption.reds │ ├── RequirementsPopup.reds │ ├── SettingsButton.reds │ ├── ViewSettingsPopup.reds │ ├── WardrobeHubBtn.reds │ ├── WardrobeHubLink.reds │ └── WardrobeScreen.reds ├── ViewEvents.reds ├── ViewManager.reds └── ViewState.reds ├── support ├── hints │ └── EquipmentEx.toml └── localization │ └── lang.json.json └── tools └── dist ├── install-mod.ps1 ├── package-mod.ps1 └── steps ├── compose-archives.ps1 ├── compose-hints.ps1 ├── compose-redscripts.ps1 ├── create-zip-from-stage.ps1 ├── get-version.ps1 └── install-from-stage.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | *.reds linguist-language=swift 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.dev 2 | /.idea 3 | /.vscode 4 | /archive/packed 5 | /archive/source/raw/**/*.json 6 | /archive/*.xml 7 | /archive/*.zip 8 | /archive/fileTreeState.json 9 | /build 10 | /dev 11 | /*.log 12 | ~$* 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 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 | # Equipment-EX 2 | 3 | - Adds a new transmog system with 50+ clothing slots 4 | - Adds a brand-new UI accessible from Hub menu and V's apartments 5 | - Allows you to manage an unlimited amount of outfits with your names 6 | - Converts your existing wardrobe sets to a new system at a first launch 7 | - Works with vanilla and custom items 8 | 9 | ## Getting Started 10 | 11 | ### Requirements 12 | 13 | - Cyberpunk 2077 2.2 14 | - [ArchiveXL](https://github.com/psiberx/cp2077-archive-xl) 1.18.0+ 15 | - [TweakXL](https://github.com/psiberx/cp2077-tweak-xl) 1.10.4+ 16 | - [Codeware](https://github.com/psiberx/cp2077-codeware) 1.13.0+ 17 | - [redscript](https://github.com/jac3km4/redscript) 0.5.25+ 18 | 19 | ### Installation 20 | 21 | 1. Install requirements 22 | 2. Download [the latest release](https://github.com/psiberx/cp2077-equipment-ex/releases) archive 23 | 3. Extract the archive into the Cyberpunk 2077 installation directory 24 | 25 | ### How to use 26 | 27 | - The outfit manager is accessible through the new "Wardrobe" button in the Inventory menu or from wardrobe call in V's apartments 28 | - On the right side of the screen, you will see all compatible gear grouped by slots 29 | - Clicking on any item will activate outfit mode, which applies the visuals of the selected items to your character over equipped gear 30 | - On the left side of the screen, you will see a list of your outfits 31 | - The "Save outfit" button becomes available when outfit mode is active 32 | - To equip a previously saved outfit, just click on the name in the list 33 | - To delete an outfit, hover over the outfit and press the hotkey from the hint 34 | - To disable the outfit mode, you can select "No outfit" or unequip the outfit from the Inventory menu 35 | - In photo mode, you will find the option to change outfits on the fly in the pose section 36 | 37 | ## Documentation 38 | 39 | ### Compatible items 40 | 41 | Any item can support one base slot and one outfit slot at the same time, 42 | making the item compatible with both the game equipment system and the outfit system. 43 | 44 | The outfit slot must be added to the `placementSlots` property after the base slot: 45 | 46 | ```yaml 47 | Items.MyNecklace: 48 | $base: Items.GenericHeadClothing 49 | placementSlots: 50 | - !append OutfitSlots.NecklaceShort 51 | ``` 52 | 53 | Which is equivalent to: 54 | 55 | ```yaml 56 | Items.MyNecklace: 57 | $base: Items.GenericHeadClothing 58 | placementSlots: 59 | - AttachmentSlots.Head 60 | - OutfitSlots.NecklaceShort 61 | ``` 62 | 63 | If user doesn't have Equipment-EX installed, the item will still work with the base slot. 64 | 65 | ### Outfit slots 66 | 67 | | Slot name | Purpose | 68 | |:-------------------------------|:-------------------------------------------------| 69 | | `OutfitSlots.Head` | Hats, Hijabs, Helmets | 70 | | `OutfitSlots.Balaclava` | Balaclavas | 71 | | `OutfitSlots.Mask` | Face Masks | 72 | | `OutfitSlots.Glasses` | Glasses, Visors | 73 | | `OutfitSlots.Eyes` | Lenses, Visors | 74 | | `OutfitSlots.EyeLeft` | Lenses, Visors | 75 | | `OutfitSlots.EyeRight` | Lenses, Visors | 76 | | `OutfitSlots.Wreath` | Wreaths | 77 | | `OutfitSlots.EarLeft` | Earrings | 78 | | `OutfitSlots.EarRight` | Earrings | 79 | | `OutfitSlots.Neckwear` | Scarves, Collars | 80 | | `OutfitSlots.NecklaceTight` | Chokers | 81 | | `OutfitSlots.NecklaceShort` | Short Necklaces | 82 | | `OutfitSlots.NecklaceLong` | Long Necklaces | 83 | | `OutfitSlots.TorsoUnder` | Bras, Tight Long-sleeves | 84 | | `OutfitSlots.TorsoInner` | Tops, T-Shirts, Tight Shirts, Tight Dresses | 85 | | `OutfitSlots.TorsoMiddle` | Waistcoats, Blazers, Loose Shirts, Loose Dresses | 86 | | `OutfitSlots.TorsoOuter` | Jackets, Coats | 87 | | `OutfitSlots.TorsoAux` | Vests, Harnesses, Puffers | 88 | | `OutfitSlots.Back` | Backpacks, Swords | 89 | | `OutfitSlots.ShoulderLeft` | | 90 | | `OutfitSlots.ShoulderRight` | | 91 | | `OutfitSlots.ElbowLeft` | | 92 | | `OutfitSlots.ElbowRight` | | 93 | | `OutfitSlots.WristLeft` | Watches, Bands | 94 | | `OutfitSlots.WristRight` | Watches, Bands | 95 | | `OutfitSlots.Hands` | Gloves | 96 | | `OutfitSlots.HandLeft` | Gloves | 97 | | `OutfitSlots.HandRight` | Gloves | 98 | | `OutfitSlots.FingersLeft` | Rings | 99 | | `OutfitSlots.FingersRight` | Rings | 100 | | `OutfitSlots.FingernailsLeft` | Nails | 101 | | `OutfitSlots.FingernailsRight` | Nails | 102 | | `OutfitSlots.Waist` | Belts | 103 | | `OutfitSlots.LegsInner` | Tights, Leggings | 104 | | `OutfitSlots.LegsMiddle` | Tight Pants, Tight Shorts | 105 | | `OutfitSlots.LegsOuter` | Loose Pants, Loose Shorts, Skirts | 106 | | `OutfitSlots.ThighLeft` | | 107 | | `OutfitSlots.ThighRight` | | 108 | | `OutfitSlots.KneeLeft` | | 109 | | `OutfitSlots.KneeRight` | | 110 | | `OutfitSlots.AnkleLeft` | | 111 | | `OutfitSlots.AnkleRight` | | 112 | | `OutfitSlots.Feet` | Footwear | 113 | | `OutfitSlots.ToesLeft` | Accessories | 114 | | `OutfitSlots.ToesRight` | Accessories | 115 | | `OutfitSlots.ToenailsLeft` | Nails | 116 | | `OutfitSlots.ToenailsRight` | Nails | 117 | | `OutfitSlots.BodyUnder` | Netrunning Suits | 118 | | `OutfitSlots.BodyInner` | | 119 | | `OutfitSlots.BodyMiddle` | Jumpsuits, Tracksuits | 120 | | `OutfitSlots.BodyOuter` | | 121 | | `OutfitSlots.HandPropLeft` | Props for photo mode | 122 | | `OutfitSlots.HandPropRight` | Props for photo mode | 123 | 124 | When proposing a new slot, please follow these recommendations: 125 | 126 | - The purpose of the slot must be clear and distinguishable from other slots 127 | - The slot must represent an area or layer for only one item equipped at a time 128 | - The slot must be named after a body part if possible, or equipment category otherwise 129 | 130 | ### Auto conversions 131 | 132 | If you don't specify any outfit slot for your item, 133 | then the slot will be automatically assigned according to this table: 134 | 135 | | Base record | Assigned slot | 136 | |:----------------------------------|:-------------------------| 137 | | `Items.GenericHeadClothing` | `OutfitSlots.Head` | 138 | | `Items.Glasses` | `OutfitSlots.Glasses` | 139 | | `Items.Visor` | `OutfitSlots.Glasses` | 140 | | `Items.GenericFaceClothing` | `OutfitSlots.Mask` | 141 | | `Items.GenericInnerChestClothing` | `OutfitSlots.TorsoInner` | 142 | | `Items.GenericOuterChestClothing` | `OutfitSlots.TorsoOuter` | 143 | | `Items.GenericLegClothing` | `OutfitSlots.LegsMiddle` | 144 | | `Items.Skirt` | `OutfitSlots.LegsOuter` | 145 | | `Items.GenericFootClothing` | `OutfitSlots.Feet` | 146 | | `Items.Outfit` | `OutfitSlots.BodyMiddle` | 147 | 148 | ## Translations 149 | 150 | If you want to translate this mod into your language, get translation template JSON 151 | [`support/localization/lang.json.json`](https://github.com/psiberx/cp2077-equipment-ex/blob/master/support/localization/lang.json.json), 152 | translate all `femaleVariant` values, and send it to us. 153 | Please try to make a translation as close to the English version as possible. 154 | Your translation will be added to the mod, and you will be credited on the mod page. 155 | 156 | Available translations: 157 | 158 | - English (`en-us`) 159 | - Arabic (`ar-ar`) by [@MONSTERaider](https://www.nexusmods.com/users/1630457) 160 | - Brazilian (`pt-br`) by [@Jaqueta](https://github.com/Jaqueta) 161 | - Czech (`cz-cz`) by [@starfis](https://www.nexusmods.com/users/933641) 162 | - French (`fr-fr`) by [@TFE71](https://www.nexusmods.com/users/5620844) 163 | - German (`de-de`) by [@Vorgash](https://www.nexusmods.com/users/3957237) 164 | - Italian (`it-it`) by [@chipzz97](https://www.nexusmods.com/users/46275402) 165 | - Korean (`kr-kr`) by [@manikda11](https://www.nexusmods.com/users/47584948) 166 | - Polish (`pl-pl`) by [@gojowenus](https://www.nexusmods.com/users/5672133) 167 | - Russian (`ru-ru`) by [@Locked15](https://github.com/Locked15) 168 | - Simplified Chinese (`zh-cn`) by [@Zo70](https://www.nexusmods.com/users/158442118) 169 | - Spanish (`es-es`) by [@Anrui](https://www.nexusmods.com/users/36190195) 170 | - Turkish (`tr-tr`) by [@paielcitra](https://www.nexusmods.com/users/54660342) 171 | -------------------------------------------------------------------------------- /archive/EquipmentEx.cpmodproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | EquipmentEx 4 | EquipmentEx 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /archive/custom_refs.txt: -------------------------------------------------------------------------------- 1 | equipment_ex\gui\wardrobe.inkwidget 2 | equipment_ex\localization\ar-ar.json 3 | equipment_ex\localization\cz-cz.json 4 | equipment_ex\localization\de-de.json 5 | equipment_ex\localization\en-us.json 6 | equipment_ex\localization\es-es.json 7 | equipment_ex\localization\fr-fr.json 8 | equipment_ex\localization\it-it.json 9 | equipment_ex\localization\kr-kr.json 10 | equipment_ex\localization\pl-pl.json 11 | equipment_ex\localization\pt-br.json 12 | equipment_ex\localization\ru-ru.json 13 | equipment_ex\localization\tr-tr.json 14 | equipment_ex\localization\zh-cn.json 15 | -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/gui/wardrobe.inkwidget: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/gui/wardrobe.inkwidget -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/ar-ar.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/ar-ar.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/cz-cz.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/cz-cz.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/de-de.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/de-de.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/en-us.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/en-us.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/es-es.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/es-es.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/fr-fr.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/fr-fr.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/it-it.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/it-it.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/kr-kr.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/kr-kr.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/pl-pl.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/pl-pl.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/pt-br.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/pt-br.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/ru-ru.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/ru-ru.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/tr-tr.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/tr-tr.json -------------------------------------------------------------------------------- /archive/source/archive/equipment_ex/localization/zh-cn.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-equipment-ex/9c3990e758b663dd6dee752fc077f41eada431ce/archive/source/archive/equipment_ex/localization/zh-cn.json -------------------------------------------------------------------------------- /archive/source/resources/EquipmentEx.archive.xl: -------------------------------------------------------------------------------- 1 | localization: 2 | onscreens: 3 | en-us: equipment_ex\localization\en-us.json 4 | ar-ar: equipment_ex\localization\ar-ar.json 5 | cz-cz: equipment_ex\localization\cz-cz.json 6 | de-de: equipment_ex\localization\de-de.json 7 | es-es: equipment_ex\localization\es-es.json 8 | fr-fr: equipment_ex\localization\fr-fr.json 9 | it-it: equipment_ex\localization\it-it.json 10 | kr-kr: equipment_ex\localization\kr-kr.json 11 | pl-pl: equipment_ex\localization\pl-pl.json 12 | pt-br: equipment_ex\localization\pt-br.json 13 | ru-ru: equipment_ex\localization\ru-ru.json 14 | tr-tr: equipment_ex\localization\tr-tr.json 15 | zh-cn: equipment_ex\localization\zh-cn.json 16 | -------------------------------------------------------------------------------- /scripts/CompatibilityManager.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public abstract class CompatibilityManager { 4 | public static func RequiredCodeware() -> String = "1.15.0"; 5 | public static func RequiredArchiveXL() -> String = "1.21.0"; 6 | public static func RequiredTweakXL() -> String = "1.10.7"; 7 | 8 | public static func CheckRequirements() -> Bool { 9 | return Codeware.Require(CompatibilityManager.RequiredCodeware()) 10 | && ArchiveXL.Require(CompatibilityManager.RequiredArchiveXL()) 11 | && TweakXL.Require(CompatibilityManager.RequiredTweakXL()); 12 | } 13 | 14 | public static func CheckConflicts(game: GameInstance, out conflicts: array) -> Bool { 15 | if IsDefined(GameInstance.GetScriptableSystemsContainer(game).Get(n"WardrobeSystemExtra")) { 16 | ArrayPush(conflicts, "Wardrobe Extras"); 17 | } 18 | 19 | let dataManager = new InventoryDataManagerV2(); 20 | dataManager.Initialize(GetPlayer(game)); 21 | let questsSystem = GameInstance.GetQuestsSystem(game); 22 | let transmogEnabled = questsSystem.GetFact(n"transmog_enabled"); 23 | questsSystem.SetFact(n"transmog_enabled", 7); 24 | if dataManager.IsTransmogEnabled() != 7 { 25 | ArrayPush(conflicts, "True Hidden Everything"); 26 | } 27 | questsSystem.SetFact(n"transmog_enabled", transmogEnabled); 28 | 29 | let itemController = new InventoryItemDisplayController(); 30 | itemController.SetLocked(true, true); 31 | if !itemController.m_isLocked { 32 | if itemController.m_visibleWhenLocked { 33 | ArrayPush(conflicts, "No Special Outfit Lock"); 34 | } else { 35 | ArrayPush(conflicts, "Never Lock Outfits"); 36 | } 37 | } 38 | 39 | if GameFileExists("archive/pc/mod/basegame_underwear_patch.archive") { 40 | ArrayPush(conflicts, "Underwear Remover by Sorrow446"); 41 | } 42 | 43 | return ArraySize(conflicts) == 0; 44 | } 45 | 46 | public static func CheckConflicts(game: GameInstance) -> Bool { 47 | let conflicts: array; 48 | return CompatibilityManager.CheckConflicts(game, conflicts); 49 | } 50 | 51 | public static func IsUserNotified() -> Bool { 52 | return TweakDBInterface.GetBool(t"EquipmentEx.isUserNotified", false); 53 | } 54 | 55 | public static func MarkAsNotified() { 56 | TweakDBManager.SetFlat(t"EquipmentEx.isUserNotified", true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scripts/DevMode.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | @if(ModuleExists("EquipmentEx.DevMode")) 4 | public static func DevMode() -> Bool = true; 5 | 6 | @if(!ModuleExists("EquipmentEx.DevMode")) 7 | public static func DevMode() -> Bool = false; 8 | -------------------------------------------------------------------------------- /scripts/Facade.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | public abstract class EquipmentEx { 4 | public static func Version() -> String = "1.2.8"; 5 | 6 | public static func Activate(game: GameInstance) { 7 | OutfitSystem.GetInstance(game).Activate(); 8 | } 9 | 10 | public static func Reactivate(game: GameInstance) { 11 | OutfitSystem.GetInstance(game).Reactivate(); 12 | } 13 | 14 | public static func Deactivate(game: GameInstance) { 15 | OutfitSystem.GetInstance(game).Deactivate(); 16 | } 17 | 18 | public static func EquipItem(game: GameInstance, itemID: TweakDBID) { 19 | OutfitSystem.GetInstance(game).EquipItem(itemID); 20 | } 21 | 22 | public static func EquipItem(game: GameInstance, itemID: TweakDBID, slotID: TweakDBID) { 23 | OutfitSystem.GetInstance(game).EquipItem(itemID, slotID); 24 | } 25 | 26 | public static func UnequipItem(game: GameInstance, itemID: TweakDBID) { 27 | OutfitSystem.GetInstance(game).UnequipItem(itemID); 28 | } 29 | 30 | public static func UnequipSlot(game: GameInstance, slotID: TweakDBID) { 31 | OutfitSystem.GetInstance(game).UnequipSlot(slotID); 32 | } 33 | 34 | public static func UnequipAll(game: GameInstance) { 35 | OutfitSystem.GetInstance(game).UnequipAll(); 36 | } 37 | 38 | public static func PrintItems(game: GameInstance) { 39 | let outfitSystem = OutfitSystem.GetInstance(game); 40 | let usedSlots = outfitSystem.GetUsedSlots(); 41 | 42 | if ArraySize(usedSlots) > 0 { 43 | let transactionSystem = GameInstance.GetTransactionSystem(game); 44 | let player = GetPlayer(game); 45 | 46 | Print("=== Equipped Items ==="); 47 | 48 | for slotID in usedSlots { 49 | let itemID = transactionSystem.GetItemInSlot(player, slotID).GetItemID(); 50 | Print(s"\(outfitSystem.GetSlotName(slotID)) : \(outfitSystem.GetItemName(itemID))"); 51 | } 52 | 53 | Print("==="); 54 | } else { 55 | Print("=== No Equipped Items ==="); 56 | } 57 | } 58 | 59 | public static func ExportItems(game: GameInstance) { 60 | let outfitSystem = OutfitSystem.GetInstance(game); 61 | let usedSlots = outfitSystem.GetUsedSlots(); 62 | 63 | if ArraySize(usedSlots) > 0 { 64 | let transactionSystem = GameInstance.GetTransactionSystem(game); 65 | let player = GetPlayer(game); 66 | let command = ""; 67 | 68 | for slotID in usedSlots { 69 | let itemID = transactionSystem.GetItemInSlot(player, slotID).GetItemID(); 70 | command += s"EquipmentEx.EquipItem(\"\(TDBID.ToStringDEBUG(ItemID.GetTDBID(itemID)))\") "; 71 | } 72 | 73 | Print(command); 74 | } 75 | } 76 | 77 | public static func LoadOutfit(game: GameInstance, name: CName) { 78 | OutfitSystem.GetInstance(game).LoadOutfit(name); 79 | } 80 | 81 | public static func SaveOutfit(game: GameInstance, name: String) { 82 | OutfitSystem.GetInstance(game).SaveOutfit(StringToName(name), true); 83 | } 84 | 85 | public static func CopyOutfit(game: GameInstance, name: String, from: CName) { 86 | OutfitSystem.GetInstance(game).CopyOutfit(StringToName(name), from); 87 | } 88 | 89 | public static func DeleteOutfit(game: GameInstance, name: CName) { 90 | OutfitSystem.GetInstance(game).DeleteOutfit(name); 91 | } 92 | 93 | public static func DeleteAllOutfits(game: GameInstance) { 94 | OutfitSystem.GetInstance(game).DeleteAllOutfits(); 95 | Print("All outfits deleted"); 96 | } 97 | 98 | public static func PrintOutfits(game: GameInstance) { 99 | let outfitSystem = OutfitSystem.GetInstance(game); 100 | let outfitNames = outfitSystem.GetOutfits(); 101 | 102 | if ArraySize(outfitNames) > 0 { 103 | Print("=== Saved Outfits ==="); 104 | 105 | for outfitName in outfitNames { 106 | Print(NameToString(outfitName)); 107 | } 108 | 109 | Print("==="); 110 | } else { 111 | Print("=== No Saved Outfits ==="); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /scripts/InventoryHelper.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class InventoryHelper extends ScriptableSystem { 4 | private let m_player: wref; 5 | private let m_transactionSystem: wref; 6 | private let m_wardrobeSystem: wref; 7 | private let m_inventoryManager: wref; 8 | private let m_stash: wref; 9 | 10 | private func OnPlayerAttach(request: ref) { 11 | this.m_player = GameInstance.GetPlayerSystem(this.GetGameInstance()).GetLocalPlayerMainGameObject(); 12 | this.m_transactionSystem = GameInstance.GetTransactionSystem(this.GetGameInstance()); 13 | this.m_wardrobeSystem = GameInstance.GetWardrobeSystem(this.GetGameInstance()); 14 | this.m_inventoryManager = EquipmentSystem.GetData(this.m_player).GetInventoryManager(); 15 | } 16 | 17 | private func IsValidItem(itemID: ItemID) -> Bool { 18 | let itemRecordId = ItemID.GetTDBID(itemID); 19 | let itemRecord = TweakDBInterface.GetClothingRecord(itemRecordId); 20 | 21 | return IsDefined(itemRecord); 22 | } 23 | 24 | public func GetStash() -> wref { 25 | return this.m_stash; 26 | } 27 | 28 | public func AddStash(stash: ref) { 29 | if !IsDefined(this.m_stash) { 30 | this.m_stash = stash; 31 | } 32 | } 33 | 34 | public func GetStashItems(out items: array) { 35 | let stashItems: array>; 36 | this.m_transactionSystem.GetItemList(this.m_stash, stashItems); 37 | 38 | for itemData in stashItems { 39 | if this.IsValidItem(itemData.GetID()) { 40 | ArrayPush(items, this.m_inventoryManager.GetCachedInventoryItemData(itemData)); 41 | } 42 | } 43 | } 44 | 45 | public func GetPlayerItems(out items: array, opt excludes: array) { 46 | for itemData in this.m_inventoryManager.GetPlayerInventoryData() { 47 | let itemID = itemData.ID; 48 | 49 | if this.IsValidItem(itemID) { 50 | let diff = 0; 51 | for exclude in excludes { 52 | if Equals(exclude.itemID, itemID) { 53 | diff += exclude.quantity; 54 | } 55 | } 56 | 57 | if itemData.Quantity - diff > 0 { 58 | ArrayPush(items, itemData); 59 | } 60 | } 61 | } 62 | } 63 | 64 | public func GetWardrobeItems(out items: array) { 65 | for itemID in this.m_wardrobeSystem.GetStoredItemIDs() { 66 | if this.IsValidItem(itemID) { 67 | ArrayPush(items, this.m_inventoryManager.GetInventoryItemDataFromItemID(itemID)); 68 | } 69 | } 70 | } 71 | 72 | public func GetAvailableItems(opt excludes: array) -> array { 73 | let items: array; 74 | 75 | switch ViewManager.GetInstance(this.GetGameInstance()).GetItemSource() { 76 | case WardrobeItemSource.InventoryOnly: 77 | this.GetPlayerItems(items, excludes); 78 | break; 79 | case WardrobeItemSource.InventoryAndStash: 80 | this.GetPlayerItems(items, excludes); 81 | this.GetStashItems(items); 82 | break; 83 | case WardrobeItemSource.WardrobeStore: 84 | this.GetWardrobeItems(items); 85 | break; 86 | } 87 | 88 | return items; 89 | } 90 | 91 | public func DiscardItem(itemID: ItemID) { 92 | switch ViewManager.GetInstance(this.GetGameInstance()).GetItemSource() { 93 | case WardrobeItemSource.InventoryOnly: 94 | this.m_transactionSystem.RemoveItem(this.m_player, itemID, 1); 95 | break; 96 | case WardrobeItemSource.InventoryAndStash: 97 | this.m_transactionSystem.RemoveItem(this.m_player, itemID, 1); 98 | this.m_transactionSystem.RemoveItem(this.m_stash, itemID, 1); 99 | break; 100 | case WardrobeItemSource.WardrobeStore: 101 | this.m_wardrobeSystem.ForgetItemID(itemID); 102 | break; 103 | } 104 | } 105 | 106 | public static func GetInstance(game: GameInstance) -> ref { 107 | return GameInstance.GetScriptableSystemsContainer(game).Get(n"EquipmentEx.InventoryHelper") as InventoryHelper; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /scripts/Migrations.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | struct ExtractedSet { 4 | public let setID: Int32; 5 | public let clothingList: array; 6 | } 7 | 8 | @if(!ModuleExists("ExtraWardrobeSlots.Utils")) 9 | func ExtractClothingSets(game: GameInstance) -> array { 10 | let wardrobeSystem = GameInstance.GetWardrobeSystem(game); 11 | let clothingSets = wardrobeSystem.GetClothingSets(); 12 | let extractedSets: array; 13 | 14 | for clothingSet in clothingSets { 15 | if ArraySize(clothingSet.clothingList) > 0 { 16 | ArrayPush(extractedSets, new ExtractedSet( 17 | EnumInt(clothingSet.setID) + 1, 18 | clothingSet.clothingList 19 | )); 20 | } 21 | } 22 | 23 | return extractedSets; 24 | } 25 | 26 | @if(ModuleExists("ExtraWardrobeSlots.Utils")) 27 | func ExtractClothingSets(game: GameInstance) -> array { 28 | let wardrobeSystem = WardrobeSystemExtra.GetInstance(game); 29 | let clothingSets = wardrobeSystem.GetClothingSets(); 30 | let extractedSets: array; 31 | 32 | for clothingSet in clothingSets { 33 | if ArraySize(clothingSet.clothingList) > 0 { 34 | ArrayPush(extractedSets, new ExtractedSet( 35 | EnumInt(clothingSet.setID) + 1, 36 | clothingSet.clothingList 37 | )); 38 | } 39 | } 40 | 41 | return extractedSets; 42 | } 43 | -------------------------------------------------------------------------------- /scripts/OutfitConfig.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | struct BaseSlotConfig { 4 | public let slotID: TweakDBID; 5 | public let equipmentArea: gamedataEquipmentArea; 6 | 7 | public static func Create(slotID: TweakDBID, equipmentArea: gamedataEquipmentArea) -> BaseSlotConfig { 8 | return new BaseSlotConfig(slotID, equipmentArea); 9 | } 10 | } 11 | 12 | struct ExtraSlotConfig { 13 | public let slotID: TweakDBID; 14 | public let slotName: CName; 15 | public let slotArea: CName; 16 | public let garmentOffset: Int32; 17 | public let relatedSlotIDs: array; 18 | public let dependencySlotIDs: array; 19 | public let displayName: String; 20 | 21 | public static func Create(slotArea: CName, slotName: CName, garmentOffset: Int32, opt relatedIDs: array, opt dependencyIDs: array) -> ExtraSlotConfig { 22 | return new ExtraSlotConfig( 23 | TDBID.Create(NameToString(slotName)), 24 | slotName, 25 | slotArea, 26 | garmentOffset, 27 | relatedIDs, 28 | dependencyIDs, 29 | "Gameplay-" + StrReplace(NameToString(slotName), ".", "-") 30 | ); 31 | } 32 | } 33 | 34 | public abstract class OutfitConfig { 35 | public static func BaseSlots() -> array = [ 36 | BaseSlotConfig.Create(t"AttachmentSlots.Head", gamedataEquipmentArea.Head), 37 | BaseSlotConfig.Create(t"AttachmentSlots.Eyes", gamedataEquipmentArea.Face), 38 | BaseSlotConfig.Create(t"AttachmentSlots.Chest", gamedataEquipmentArea.InnerChest), 39 | BaseSlotConfig.Create(t"AttachmentSlots.Torso", gamedataEquipmentArea.OuterChest), 40 | BaseSlotConfig.Create(t"AttachmentSlots.Legs", gamedataEquipmentArea.Legs), 41 | BaseSlotConfig.Create(t"AttachmentSlots.Feet", gamedataEquipmentArea.Feet), 42 | BaseSlotConfig.Create(t"AttachmentSlots.UnderwearTop", gamedataEquipmentArea.UnderwearTop), 43 | BaseSlotConfig.Create(t"AttachmentSlots.UnderwearBottom", gamedataEquipmentArea.UnderwearBottom) 44 | ]; 45 | 46 | public static func OutfitSlots() -> array = [ 47 | ExtraSlotConfig.Create(n"Head", n"OutfitSlots.Head", 310000, [t"AttachmentSlots.Head"]), 48 | ExtraSlotConfig.Create(n"Head", n"OutfitSlots.Balaclava", 160000, [t"AttachmentSlots.Head"]), 49 | ExtraSlotConfig.Create(n"Face", n"OutfitSlots.Mask", 170000, [t"AttachmentSlots.Eyes"]), 50 | ExtraSlotConfig.Create(n"Face", n"OutfitSlots.Glasses", 190000, [t"AttachmentSlots.Eyes"]), 51 | ExtraSlotConfig.Create(n"Face", n"OutfitSlots.Eyes", 130000, [t"AttachmentSlots.Eyes"]), 52 | ExtraSlotConfig.Create(n"Face", n"OutfitSlots.EyeLeft", 140000, [t"AttachmentSlots.Eyes"]), 53 | ExtraSlotConfig.Create(n"Face", n"OutfitSlots.EyeRight", 140000, [t"AttachmentSlots.Eyes"]), 54 | ExtraSlotConfig.Create(n"Face", n"OutfitSlots.Wreath", 180000, [t"AttachmentSlots.Eyes"]), 55 | ExtraSlotConfig.Create(n"Ears", n"OutfitSlots.EarLeft", 140000, [], [t"AttachmentSlots.Head"]), 56 | ExtraSlotConfig.Create(n"Ears", n"OutfitSlots.EarRight", 140000, [], [t"AttachmentSlots.Head"]), 57 | ExtraSlotConfig.Create(n"Neck", n"OutfitSlots.Neckwear", 200000, [], [t"AttachmentSlots.Head"]), 58 | ExtraSlotConfig.Create(n"Neck", n"OutfitSlots.NecklaceTight", 190000, [], [t"AttachmentSlots.Head"]), 59 | ExtraSlotConfig.Create(n"Neck", n"OutfitSlots.NecklaceShort", 190000), 60 | ExtraSlotConfig.Create(n"Neck", n"OutfitSlots.NecklaceLong", 190000), 61 | ExtraSlotConfig.Create(n"Torso", n"OutfitSlots.TorsoUnder", 120000, [t"AttachmentSlots.Chest"], [t"AttachmentSlots.Head", t"AttachmentSlots.Torso"]), 62 | ExtraSlotConfig.Create(n"Torso", n"OutfitSlots.TorsoInner", 150000, [t"AttachmentSlots.Chest"], [t"AttachmentSlots.Head", t"AttachmentSlots.Torso"]), 63 | ExtraSlotConfig.Create(n"Torso", n"OutfitSlots.TorsoMiddle", 180000, [t"AttachmentSlots.Torso"], [t"AttachmentSlots.Head"]), 64 | ExtraSlotConfig.Create(n"Torso", n"OutfitSlots.TorsoOuter", 210000, [t"AttachmentSlots.Torso"], [t"AttachmentSlots.Head"]), 65 | ExtraSlotConfig.Create(n"Torso", n"OutfitSlots.TorsoAux", 240000, [t"AttachmentSlots.Torso"], [t"AttachmentSlots.Head"]), 66 | ExtraSlotConfig.Create(n"Back", n"OutfitSlots.Back", 220000), 67 | ExtraSlotConfig.Create(n"Waist", n"OutfitSlots.Waist", 200000), 68 | ExtraSlotConfig.Create(n"Arms", n"OutfitSlots.ShoulderLeft", 200000), 69 | ExtraSlotConfig.Create(n"Arms", n"OutfitSlots.ShoulderRight", 200000), 70 | ExtraSlotConfig.Create(n"Arms", n"OutfitSlots.ElbowLeft", 200000), 71 | ExtraSlotConfig.Create(n"Arms", n"OutfitSlots.ElbowRight", 200000), 72 | ExtraSlotConfig.Create(n"Arms", n"OutfitSlots.WristLeft", 160000, [], [t"AttachmentSlots.Hands"]), 73 | ExtraSlotConfig.Create(n"Arms", n"OutfitSlots.WristRight", 160000, [], [t"AttachmentSlots.Hands"]), 74 | ExtraSlotConfig.Create(n"Hands", n"OutfitSlots.Hands", 160000, [], [t"AttachmentSlots.Hands"]), 75 | ExtraSlotConfig.Create(n"Hands", n"OutfitSlots.HandLeft", 170000, [], [t"AttachmentSlots.Hands"]), 76 | ExtraSlotConfig.Create(n"Hands", n"OutfitSlots.HandRight", 170000, [], [t"AttachmentSlots.Hands"]), 77 | ExtraSlotConfig.Create(n"Hands", n"OutfitSlots.HandPropLeft", 310000, [], [t"AttachmentSlots.Hands"]), 78 | ExtraSlotConfig.Create(n"Hands", n"OutfitSlots.HandPropRight", 310000, [], [t"AttachmentSlots.Hands"]), 79 | ExtraSlotConfig.Create(n"Fingers", n"OutfitSlots.FingersLeft", 180000, [], [t"AttachmentSlots.Hands"]), 80 | ExtraSlotConfig.Create(n"Fingers", n"OutfitSlots.FingersRight", 180000, [], [t"AttachmentSlots.Hands"]), 81 | ExtraSlotConfig.Create(n"Fingers", n"OutfitSlots.FingernailsLeft", 100000, [], [t"AttachmentSlots.Hands"]), 82 | ExtraSlotConfig.Create(n"Fingers", n"OutfitSlots.FingernailsRight", 100000, [], [t"AttachmentSlots.Hands"]), 83 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.LegsInner", 130000, [t"AttachmentSlots.Legs"], [t"AttachmentSlots.Feet"]), 84 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.LegsMiddle", 160000, [t"AttachmentSlots.Legs"], [t"AttachmentSlots.Feet"]), 85 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.LegsOuter", 190000, [t"AttachmentSlots.Legs"], [t"AttachmentSlots.Feet"]), 86 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.ThighLeft", 140000, [], [t"AttachmentSlots.Feet"]), 87 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.ThighRight", 140000, [], [t"AttachmentSlots.Feet"]), 88 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.KneeLeft", 140000, [], [t"AttachmentSlots.Feet"]), 89 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.KneeRight", 140000, [], [t"AttachmentSlots.Feet"]), 90 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.AnkleLeft", 140000, [], [t"AttachmentSlots.Feet"]), 91 | ExtraSlotConfig.Create(n"Legs", n"OutfitSlots.AnkleRight", 140000, [], [t"AttachmentSlots.Feet"]), 92 | ExtraSlotConfig.Create(n"Feet", n"OutfitSlots.Feet", 180000, [t"AttachmentSlots.Feet"]), 93 | ExtraSlotConfig.Create(n"Toes", n"OutfitSlots.ToesLeft", 120000, [], [t"AttachmentSlots.Feet"]), 94 | ExtraSlotConfig.Create(n"Toes", n"OutfitSlots.ToesRight", 120000, [], [t"AttachmentSlots.Feet"]), 95 | ExtraSlotConfig.Create(n"Toes", n"OutfitSlots.ToenailsLeft", 100000, [], [t"AttachmentSlots.Feet"]), 96 | ExtraSlotConfig.Create(n"Toes", n"OutfitSlots.ToenailsRight", 100000, [], [t"AttachmentSlots.Feet"]), 97 | ExtraSlotConfig.Create(n"Body", n"OutfitSlots.BodyUnder", 110000, [t"AttachmentSlots.Chest", t"AttachmentSlots.Legs"], [t"AttachmentSlots.Head", t"AttachmentSlots.Torso", t"AttachmentSlots.Feet"]), 98 | ExtraSlotConfig.Create(n"Body", n"OutfitSlots.BodyInner", 140000, [t"AttachmentSlots.Chest", t"AttachmentSlots.Legs"], [t"AttachmentSlots.Head", t"AttachmentSlots.Torso", t"AttachmentSlots.Feet"]), 99 | ExtraSlotConfig.Create(n"Body", n"OutfitSlots.BodyMiddle", 170000, [t"AttachmentSlots.Torso", t"AttachmentSlots.Legs"], [t"AttachmentSlots.Head", t"AttachmentSlots.Feet"]), 100 | ExtraSlotConfig.Create(n"Body", n"OutfitSlots.BodyOuter", 300000, [t"AttachmentSlots.Torso", t"AttachmentSlots.Legs"], [t"AttachmentSlots.Head", t"AttachmentSlots.Feet"]) 101 | ]; 102 | } 103 | -------------------------------------------------------------------------------- /scripts/OutfitEvents.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class OutfitUpdated extends Event { 4 | public let isActive: Bool; 5 | public let outfitName: CName; 6 | } 7 | 8 | public class OutfitPartUpdated extends Event { 9 | public let itemID: ItemID; 10 | public let itemName: String; 11 | public let slotID: TweakDBID; 12 | public let slotName: String; 13 | public let isEquipped: Bool; 14 | } 15 | 16 | public class OutfitMappingUpdated extends Event {} 17 | 18 | public class OutfitListUpdated extends Event {} 19 | -------------------------------------------------------------------------------- /scripts/OutfitState.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class OutfitPart { 4 | private persistent let m_itemID: ItemID; 5 | private persistent let m_slotID: TweakDBID; 6 | 7 | public func GetItemID() -> ItemID { 8 | return this.m_itemID; 9 | } 10 | 11 | public func GetItemHash() -> Uint64 { 12 | return ItemID.GetCombinedHash(this.m_itemID); 13 | } 14 | 15 | public func SetItemID(itemID: ItemID) { 16 | this.m_itemID = itemID; 17 | } 18 | 19 | public func GetSlotID() -> TweakDBID { 20 | return this.m_slotID; 21 | } 22 | 23 | public func SetSlotID(slotID: TweakDBID) { 24 | this.m_slotID = slotID; 25 | } 26 | 27 | public static func Create(itemID: ItemID, slotID: TweakDBID) -> ref { 28 | let instance = new OutfitPart(); 29 | instance.m_itemID = itemID; 30 | instance.m_slotID = slotID; 31 | return instance; 32 | } 33 | 34 | public static func Clone(source: ref) -> ref { 35 | return OutfitPart.Create(source.m_itemID, source.m_slotID); 36 | } 37 | } 38 | 39 | class OutfitSet { 40 | private persistent let m_name: CName; 41 | private persistent let m_parts: array>; 42 | private persistent let m_timestamp: Float; 43 | private let m_hash: Uint64; 44 | 45 | public func GetName() -> CName { 46 | return this.m_name; 47 | } 48 | 49 | public func SetName(name: CName) { 50 | this.m_name = name; 51 | } 52 | 53 | public func GetParts() -> array> { 54 | return this.m_parts; 55 | } 56 | 57 | public func SetParts(parts: array>) { 58 | ArrayResize(this.m_parts, ArraySize(parts)); 59 | 60 | let i = 0; 61 | for part in parts { 62 | this.m_parts[i] = OutfitPart.Clone(part); 63 | i += 1; 64 | } 65 | 66 | this.UpdateHash(); 67 | } 68 | 69 | public func GetHash() -> Uint64 { 70 | return this.m_hash; 71 | } 72 | 73 | public func UpdateHash() { 74 | this.m_hash = OutfitSet.MakeHash(this.m_parts); 75 | } 76 | 77 | public static func Create(name: CName, timestamp: Float, parts: array>) -> ref { 78 | let instance = new OutfitSet(); 79 | instance.m_name = name; 80 | instance.m_timestamp = timestamp; 81 | instance.SetParts(parts); 82 | return instance; 83 | } 84 | 85 | public static func Clone(name: CName, timestamp: Float, source: ref) -> ref { 86 | return OutfitSet.Create(name, timestamp, source.m_parts); 87 | } 88 | 89 | public static func MakeHash(parts: array>) -> Uint64 { 90 | if ArraySize(parts) == 0 { 91 | return 0ul; 92 | } 93 | 94 | let items: array; 95 | 96 | for part in parts { 97 | let item = part.GetItemHash(); 98 | 99 | let index = 0; 100 | while index < ArraySize(items) && items[index] < item { 101 | index += 1; 102 | } 103 | 104 | ArrayInsert(items, index, item); 105 | } 106 | 107 | let hash = 14695981039346656037ul; // 0xcbf29ce484222325 108 | let prime = 1099511628211ul; // 0x00000100000001B3 109 | let base = 256ul; 110 | 111 | for item in items { 112 | let i = 8; 113 | while i > 0 { 114 | hash = hash ^ (item % base); 115 | hash *= prime; 116 | item /= base; 117 | i -= 1; 118 | } 119 | } 120 | 121 | return hash; 122 | } 123 | } 124 | 125 | class OutfitState { 126 | private persistent let m_disabled: Bool; 127 | private persistent let m_active: Bool; 128 | private persistent let m_parts: array>; 129 | private persistent let m_outfits: array>; 130 | private persistent let m_mappings: array>; 131 | private let m_hash: Uint64; 132 | 133 | public func IsDisabled() -> Bool { 134 | return this.m_disabled; 135 | } 136 | 137 | public func SetDisabled(state: Bool) { 138 | this.m_disabled = state; 139 | } 140 | 141 | public func IsActive() -> Bool { 142 | return this.m_active; 143 | } 144 | 145 | public func SetActive(state: Bool) { 146 | this.m_active = state; 147 | } 148 | 149 | public func GetParts() -> array> { 150 | return this.m_parts; 151 | } 152 | 153 | public func HasPart(itemID: ItemID) -> Bool { 154 | return IsDefined(this.GetPart(itemID)); 155 | } 156 | 157 | public func HasPart(slotID: TweakDBID) -> Bool { 158 | return IsDefined(this.GetPart(slotID)); 159 | } 160 | 161 | public func GetPart(itemID: ItemID) -> ref { 162 | for part in this.m_parts { 163 | if Equals(part.GetItemID(), itemID) { 164 | return part; 165 | } 166 | } 167 | return null; 168 | } 169 | 170 | public func GetPart(slotID: TweakDBID) -> ref { 171 | for part in this.m_parts { 172 | if Equals(part.GetSlotID(), slotID) { 173 | return part; 174 | } 175 | } 176 | return null; 177 | } 178 | 179 | public func UpdatePart(itemID: ItemID, slotID: TweakDBID) { 180 | let updated = false; 181 | 182 | for part in this.m_parts { 183 | if Equals(part.GetItemID(), itemID) { 184 | if Equals(part.GetSlotID(), slotID) { 185 | return; 186 | } 187 | part.SetSlotID(slotID); 188 | updated = true; 189 | break; 190 | } 191 | } 192 | 193 | for part in this.m_parts { 194 | if Equals(part.GetSlotID(), slotID) { 195 | if updated { 196 | if NotEquals(part.GetItemID(), itemID) { 197 | ArrayRemove(this.m_parts, part); 198 | } 199 | } else { 200 | part.SetItemID(itemID); 201 | updated = true; 202 | } 203 | break; 204 | } 205 | } 206 | 207 | if !updated { 208 | ArrayPush(this.m_parts, OutfitPart.Create(itemID, slotID)); 209 | } 210 | 211 | this.UpdateHash(); 212 | } 213 | 214 | public func RemovePart(itemID: ItemID) -> Bool { 215 | for part in this.m_parts { 216 | if Equals(part.GetItemID(), itemID) { 217 | ArrayRemove(this.m_parts, part); 218 | this.UpdateHash(); 219 | return true; 220 | } 221 | } 222 | return false; 223 | } 224 | 225 | public func RemovePart(slotID: TweakDBID) -> Bool { 226 | for part in this.m_parts { 227 | if Equals(part.GetSlotID(), slotID) { 228 | ArrayRemove(this.m_parts, part); 229 | this.UpdateHash(); 230 | return true; 231 | } 232 | } 233 | return false; 234 | } 235 | 236 | public func ClearParts() { 237 | ArrayClear(this.m_parts); 238 | 239 | this.UpdateHash(); 240 | } 241 | 242 | public func GetOutfits() -> array> { 243 | return this.m_outfits; 244 | } 245 | 246 | public func GetOutfit(name: CName) -> ref { 247 | for outfit in this.m_outfits { 248 | if Equals(outfit.GetName(), name) { 249 | return outfit; 250 | } 251 | } 252 | return null; 253 | } 254 | 255 | public func SaveOutfit(name: CName, overwrite: Bool, timestamp: Float) -> Bool { 256 | return this.SaveOutfit(name, this.m_parts, overwrite, timestamp); 257 | } 258 | 259 | public func SaveOutfit(name: CName, parts: array>, overwrite: Bool, timestamp: Float) -> Bool { 260 | let outfit = this.GetOutfit(name); 261 | 262 | if IsDefined(outfit) { 263 | if !overwrite { 264 | return false; 265 | } 266 | 267 | outfit.SetParts(parts); 268 | return true; 269 | } 270 | 271 | ArrayPush(this.m_outfits, OutfitSet.Create(name, timestamp, parts)); 272 | return true; 273 | } 274 | 275 | public func CopyOutfit(name: CName, from: CName, timestamp: Float) -> Bool { 276 | let outfit = this.GetOutfit(name); 277 | 278 | if !IsDefined(outfit) { 279 | return false; 280 | } 281 | 282 | ArrayPush(this.m_outfits, OutfitSet.Clone(name, timestamp, outfit)); 283 | return true; 284 | } 285 | 286 | public func DeleteOutfit(name: CName) -> Bool { 287 | let outfit = this.GetOutfit(name); 288 | 289 | if !IsDefined(outfit) { 290 | return false; 291 | } 292 | 293 | ArrayRemove(this.m_outfits, outfit); 294 | return true; 295 | } 296 | 297 | public func DeleteAllOutfits() -> Bool { 298 | if ArraySize(this.m_outfits) > 0 { 299 | ArrayClear(this.m_outfits); 300 | return true; 301 | } 302 | 303 | return false; 304 | } 305 | 306 | public func IsOutfit(name: CName) -> Bool { 307 | let outfit = this.GetOutfit(name); 308 | 309 | return IsDefined(outfit) ? this.m_hash == outfit.GetHash() : false; 310 | } 311 | 312 | public func IsOutfit(hash: Uint64) -> Bool { 313 | return this.m_hash == hash; 314 | } 315 | 316 | public func GetMappings() -> array> { 317 | return this.m_mappings; 318 | } 319 | 320 | public func UpdateMapping(itemID: ItemID, slotID: TweakDBID) { 321 | let updated = false; 322 | 323 | for mapping in this.m_mappings { 324 | if Equals(mapping.GetItemID(), itemID) { 325 | if Equals(mapping.GetSlotID(), slotID) { 326 | return; 327 | } 328 | mapping.SetSlotID(slotID); 329 | updated = true; 330 | break; 331 | } 332 | } 333 | 334 | if !updated { 335 | ArrayPush(this.m_mappings, OutfitPart.Create(itemID, slotID)); 336 | } 337 | } 338 | 339 | public func UpdateHash() { 340 | this.m_hash = OutfitSet.MakeHash(this.m_parts); 341 | } 342 | 343 | public func Restore() { 344 | this.UpdateHash(); 345 | 346 | for outfit in this.m_outfits { 347 | outfit.UpdateHash(); 348 | } 349 | } 350 | 351 | public static func Create() -> ref { 352 | return new OutfitState(); 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /scripts/Overrides/BackpackMainGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @addField(BackpackMainGameController) 4 | private let m_outfitSystem: wref; 5 | 6 | @wrapMethod(BackpackMainGameController) 7 | protected cb func OnInitialize() -> Bool { 8 | wrappedMethod(); 9 | 10 | this.m_outfitSystem = OutfitSystem.GetInstance(this.GetPlayerControlledObject().GetGame()); 11 | } 12 | 13 | @wrapMethod(BackpackMainGameController) 14 | protected cb func OnItemDisplayClick(evt: ref) -> Bool { 15 | if this.m_outfitSystem.IsActive() && evt.actionName.IsAction(n"preview_item") { 16 | if evt.uiInventoryItem.IsClothing() { 17 | return false; 18 | } 19 | } 20 | 21 | return wrappedMethod(evt); 22 | } 23 | 24 | @wrapMethod(BackpackMainGameController) 25 | private final func NewShowItemHints(itemData: wref) { 26 | wrappedMethod(itemData); 27 | 28 | if this.m_outfitSystem.IsActive() { 29 | if itemData.IsClothing() { 30 | this.m_buttonHintsController.RemoveButtonHint(n"preview_item"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/Overrides/CraftingGarmentItemPreviewGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @wrapMethod(CraftingGarmentItemPreviewGameController) 4 | protected cb func OnCrafrtingPreview(evt: ref) -> Bool { 5 | if this.m_outfitSystem.IsActive() { 6 | if ItemID.IsValid(this.m_previewedItem) { 7 | this.m_previewedItem = ItemID.None(); 8 | this.m_outfitSystem.EquipPuppetOutfit(this.GetGamePuppet()); 9 | } 10 | 11 | if evt.isGarment { 12 | this.m_previewedItem = evt.itemID; 13 | this.m_outfitSystem.EquipPuppetItem(this.GetGamePuppet(), this.m_previewedItem); 14 | } 15 | } else { 16 | wrappedMethod(evt); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/Overrides/EquipmentSystem.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @addField(EquipmentSystemPlayerData) 4 | private let m_visualChangesAllowed: Bool; 5 | 6 | @addField(EquipmentSystemPlayerData) 7 | private let m_outfitSystem: wref; 8 | 9 | @wrapMethod(EquipmentSystemPlayerData) 10 | public final func OnAttach() { 11 | wrappedMethod(); 12 | 13 | this.m_outfitSystem = OutfitSystem.GetInstance(this.m_owner.GetGame()); 14 | } 15 | 16 | @addMethod(EquipmentSystemPlayerData) 17 | public func LockVisualChanges() { 18 | this.m_visualChangesAllowed = false; 19 | } 20 | 21 | @addMethod(EquipmentSystemPlayerData) 22 | public func UnlockVisualChanges() { 23 | this.m_visualChangesAllowed = true; 24 | } 25 | 26 | @wrapMethod(EquipmentSystemPlayerData) 27 | public final const func IsVisualSetActive() -> Bool { 28 | return wrappedMethod() || this.m_outfitSystem.IsActive(); 29 | } 30 | 31 | @wrapMethod(EquipmentSystemPlayerData) 32 | public final const func IsSlotOverriden(area: gamedataEquipmentArea) -> Bool { 33 | return wrappedMethod(area) || (this.m_outfitSystem.IsManagedArea(area) && this.m_outfitSystem.IsActive()); 34 | } 35 | 36 | @wrapMethod(EquipmentSystemPlayerData) 37 | private final const func ShouldUnderwearBeVisibleInSet() -> Bool { 38 | return !this.m_outfitSystem.IsActive() && !this.m_visualChangesAllowed && wrappedMethod(); 39 | } 40 | 41 | @wrapMethod(EquipmentSystemPlayerData) 42 | private final const func ShouldUnderwearTopBeVisibleInSet() -> Bool { 43 | return !this.m_outfitSystem.IsActive() && !this.m_visualChangesAllowed && wrappedMethod(); 44 | } 45 | 46 | @wrapMethod(EquipmentSystemPlayerData) 47 | public final func OnRestored() { 48 | this.m_outfitSystem = OutfitSystem.GetInstance(this.m_owner.GetGame()); 49 | this.m_wardrobeSystem = GameInstance.GetWardrobeSystem(this.m_owner.GetGame()); 50 | 51 | if NotEquals(this.m_wardrobeSystem.GetActiveClothingSetIndex(), gameWardrobeClothingSetIndex.INVALID) { 52 | this.m_wardrobeSystem.SetActiveClothingSetIndex(gameWardrobeClothingSetIndex.INVALID); 53 | this.m_lastActiveWardrobeSet = gameWardrobeClothingSetIndex.INVALID; 54 | } 55 | 56 | if !this.m_outfitSystem.IsActive() { 57 | let i = 0; 58 | while i <= ArraySize(this.m_clothingVisualsInfo) { 59 | this.m_clothingVisualsInfo[i].isHidden = false; 60 | this.m_clothingVisualsInfo[i].visualItem = ItemID.None(); 61 | i += 1; 62 | } 63 | } 64 | 65 | wrappedMethod(); 66 | } 67 | 68 | @replaceMethod(EquipmentSystemPlayerData) 69 | public final func OnQuestDisableWardrobeSetRequest(request: ref) { 70 | if this.m_outfitSystem.IsActive() { 71 | this.m_outfitSystem.Deactivate(); 72 | this.m_lastActiveWardrobeSet = gameWardrobeClothingSetIndex.Slot1; 73 | } 74 | 75 | if request.blockReequipping { 76 | this.m_outfitSystem.Disable(); 77 | } 78 | } 79 | 80 | @replaceMethod(EquipmentSystemPlayerData) 81 | public final func OnQuestRestoreWardrobeSetRequest(request: ref) { 82 | this.m_outfitSystem.Enable(); 83 | 84 | if NotEquals(this.m_lastActiveWardrobeSet, gameWardrobeClothingSetIndex.INVALID) { 85 | this.m_outfitSystem.Reactivate(); 86 | this.m_lastActiveWardrobeSet = gameWardrobeClothingSetIndex.INVALID; 87 | } 88 | } 89 | 90 | @replaceMethod(EquipmentSystemPlayerData) 91 | public final func OnQuestEnableWardrobeSetRequest(request: ref) { 92 | this.m_outfitSystem.Enable(); 93 | } 94 | 95 | @replaceMethod(EquipmentSystemPlayerData) 96 | public final func EquipWardrobeSet(setID: gameWardrobeClothingSetIndex) {} 97 | 98 | @replaceMethod(EquipmentSystemPlayerData) 99 | public final func UnequipWardrobeSet() {} 100 | 101 | @replaceMethod(EquipmentSystemPlayerData) 102 | public final func QuestHideSlot(area: gamedataEquipmentArea) {} 103 | 104 | @replaceMethod(EquipmentSystemPlayerData) 105 | public final func QuestRestoreSlot(area: gamedataEquipmentArea) {} 106 | 107 | @wrapMethod(EquipmentSystemPlayerData) 108 | private final func ClearItemAppearanceEvent(area: gamedataEquipmentArea) { 109 | if this.m_visualChangesAllowed || !this.m_outfitSystem.IsActive() { 110 | wrappedMethod(area); 111 | } 112 | } 113 | 114 | @wrapMethod(EquipmentSystemPlayerData) 115 | private final func ResetItemAppearanceEvent(area: gamedataEquipmentArea) { 116 | if this.m_visualChangesAllowed || !this.m_outfitSystem.IsActive() { 117 | wrappedMethod(area); 118 | } 119 | } 120 | 121 | @wrapMethod(EquipmentSystemPlayerData) 122 | private final func ResetItemAppearance(area: gamedataEquipmentArea, opt force: Bool) { 123 | wrappedMethod(area, force); 124 | 125 | if Equals(area, gamedataEquipmentArea.Feet) && !this.IsSlotHidden(area) && !this.IsSlotOverriden(area) { 126 | let itemID = this.GetActiveItem(area); 127 | if ItemID.IsValid(itemID) { 128 | let slotID = this.GetPlacementSlotByAreaType(area); 129 | let transactionSystem = GameInstance.GetTransactionSystem(this.m_owner.GetGame()); 130 | transactionSystem.RemoveItemFromSlot(this.m_owner, slotID); 131 | GameInstance.GetDelaySystem(this.m_owner.GetGame()).DelayCallback(EquipmentSystemReattachItem.Create(this, slotID, itemID), 1.0 / 60.0, false); 132 | } 133 | } 134 | } 135 | 136 | class EquipmentSystemReattachItem extends DelayCallback { 137 | protected let m_data: ref; 138 | protected let m_slotID: TweakDBID; 139 | protected let m_itemID: ItemID; 140 | 141 | public func Call() { 142 | let transactionSystem = GameInstance.GetTransactionSystem(this.m_data.m_owner.GetGame()); 143 | transactionSystem.AddItemToSlot(this.m_data.m_owner, this.m_slotID, this.m_itemID, true); 144 | } 145 | 146 | public static func Create(data: ref, slotID: TweakDBID, itemID: ItemID) -> ref { 147 | let self = new EquipmentSystemReattachItem(); 148 | self.m_data = data; 149 | self.m_slotID = slotID; 150 | self.m_itemID = itemID; 151 | 152 | return self; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /scripts/Overrides/InventoryItemDisplayController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @addField(InventoryItemDisplayController) 4 | private let m_outfitSystem: wref; 5 | 6 | @wrapMethod(InventoryItemDisplayController) 7 | public func Bind(inventoryDataManager: ref, equipmentArea: gamedataEquipmentArea, opt slotIndex: Int32, opt displayContext: ItemDisplayContext, opt setWardrobeOutfit: Bool, opt wardrobeOutfitIndex: Int32) { 8 | this.m_outfitSystem = OutfitSystem.GetInstance(inventoryDataManager.GetGame()); 9 | 10 | wrappedMethod(inventoryDataManager, equipmentArea, slotIndex, displayContext, setWardrobeOutfit, wardrobeOutfitIndex); 11 | } 12 | 13 | @wrapMethod(InventoryItemDisplayController) 14 | public func Bind(inventoryScriptableSystem: ref, equipmentArea: gamedataEquipmentArea, opt slotIndex: Int32, displayContext: ItemDisplayContext) { 15 | this.m_outfitSystem = OutfitSystem.GetInstance(inventoryScriptableSystem.GetGameInstance()); 16 | 17 | wrappedMethod(inventoryScriptableSystem, equipmentArea, slotIndex, displayContext); 18 | } 19 | 20 | @wrapMethod(InventoryItemDisplayController) 21 | protected func RefreshUI() { 22 | let isOutfit = Equals(this.m_equipmentArea, gamedataEquipmentArea.Outfit); 23 | let isOverriden = this.m_outfitSystem.IsActive(); 24 | 25 | if isOutfit && isOverriden { 26 | this.m_wardrobeOutfitIndex = 1; 27 | } else { 28 | this.m_wardrobeOutfitIndex = -1; 29 | } 30 | 31 | wrappedMethod(); 32 | 33 | if isOutfit && isOverriden { 34 | inkWidgetRef.SetVisible(this.m_wardrobeInfoText, false); 35 | inkWidgetRef.SetVisible(this.m_slotItemsCountWrapper, false); 36 | inkWidgetRef.SetMargin(this.m_wardrobeInfoContainer, new inkMargin(12.0, 0, 0, 12.0)); 37 | } 38 | } 39 | 40 | @wrapMethod(InventoryItemDisplayController) 41 | protected func NewUpdateRequirements(itemData: ref) { 42 | if !itemData.IsForWardrobe() { 43 | wrappedMethod(itemData); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scripts/Overrides/InventoryItemModeLogicController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @addField(InventoryItemModeLogicController) 4 | private let m_outfitSystem: wref; 5 | 6 | @addField(InventoryItemModeLogicController) 7 | public let m_isWardrobeScreen: Bool; 8 | 9 | @wrapMethod(InventoryItemModeLogicController) 10 | public final func SetupData(buttonHints: wref, tooltipsManager: wref, inventoryManager: ref, player: wref) { 11 | wrappedMethod(buttonHints, tooltipsManager, inventoryManager, player); 12 | 13 | this.m_outfitSystem = OutfitSystem.GetInstance(this.m_player.GetGame()); 14 | } 15 | 16 | @replaceMethod(InventoryItemModeLogicController) 17 | private final func UpdateOutfitWardrobe(active: Bool, activeSetOverride: Int32) { 18 | inkWidgetRef.SetVisible(this.m_wardrobeSlotsContainer, active); 19 | inkWidgetRef.SetVisible(this.m_wardrobeSlotsLabel, active); 20 | inkWidgetRef.SetVisible(this.m_outfitsFilterInfoText, active); 21 | inkWidgetRef.SetVisible(this.m_filterButtonsGrid, !active); 22 | 23 | if active && !this.m_outfitWardrobeSpawned { 24 | let wardrobeContainer = inkWidgetRef.Get(this.m_wardrobeSlotsContainer) as inkCompoundWidget; 25 | 26 | let wardrobeInfo = new inkText(); 27 | wardrobeInfo.SetLocalizedTextString("UI-Wardrobe-Tooltip-OutfitInfo"); 28 | wardrobeInfo.SetFontFamily("base\\gameplay\\gui\\fonts\\raj\\raj.inkfontfamily"); 29 | wardrobeInfo.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 30 | wardrobeInfo.BindProperty(n"tintColor", n"MainColors.Red"); 31 | wardrobeInfo.BindProperty(n"fontWeight", n"MainColors.BodyFontWeight"); 32 | wardrobeInfo.BindProperty(n"fontSize", n"MainColors.ReadableXSmall"); 33 | wardrobeInfo.SetWrapping(true, 660.0); 34 | wardrobeInfo.Reparent(wardrobeContainer); 35 | 36 | // let wardrobeLink = this.SpawnFromLocal(wardrobeContainer, n"HyperlinkButton:EquipmentEx.WardrobeHubLink"); 37 | // wardrobeLink.RegisterToCallback(n"OnClick", this.m_inventoryController, n"OnWardrobeScreenClick"); 38 | // wardrobeLink.SetMargin(new inkMargin(16.0, 0.0, 0.0, 0.0)); 39 | 40 | let wardrobeBtn = this.SpawnFromLocal(wardrobeContainer, n"wardrobeOutfitSlot:EquipmentEx.WardrobeHubBtnController"); 41 | wardrobeBtn.SetMargin(new inkMargin(16.0, 0.0, 0.0, 0.0)); 42 | 43 | this.m_outfitWardrobeSpawned = true; 44 | } 45 | } 46 | 47 | @replaceMethod(InventoryItemModeLogicController) 48 | protected cb func OnWardrobeOutfitSlotClicked(e: ref) -> Bool { 49 | this.m_inventoryController.ShowWardrobeScreen(); 50 | } 51 | 52 | @replaceMethod(InventoryItemModeLogicController) 53 | protected cb func OnWardrobeOutfitSlotHoverOver(e: ref) -> Bool { 54 | // 55 | } 56 | 57 | @wrapMethod(InventoryItemModeLogicController) 58 | protected cb func OnItemDisplayClick(evt: ref) -> Bool { 59 | if !this.m_isWardrobeScreen { 60 | wrappedMethod(evt); 61 | } 62 | } 63 | 64 | @wrapMethod(InventoryItemModeLogicController) 65 | protected cb func OnItemDisplayHoverOver(evt: ref) -> Bool { 66 | if !this.m_isWardrobeScreen { 67 | wrappedMethod(evt); 68 | } 69 | } 70 | 71 | @wrapMethod(InventoryItemModeLogicController) 72 | private final func SetInventoryItemButtonHintsHoverOver(const displayingData: script_ref, 73 | opt display: ref) { 74 | wrappedMethod(displayingData, display); 75 | 76 | if this.m_outfitSystem.IsActive() { 77 | let equipmentArea = InventoryItemData.GetEquipmentArea(displayingData); 78 | let isClothing = this.IsEquipmentAreaClothing(equipmentArea) || Equals(equipmentArea, gamedataEquipmentArea.Outfit); 79 | if isClothing { 80 | this.m_buttonHintsController.RemoveButtonHint(n"preview_item"); 81 | } 82 | } 83 | } 84 | 85 | @wrapMethod(InventoryItemModeLogicController) 86 | private final func HandleItemClick(const itemData: script_ref, actionName: ref, opt displayContext: ItemDisplayContext, opt isPlayerLocked: Bool) { 87 | if this.m_outfitSystem.IsActive() && actionName.IsAction(n"preview_item") { 88 | let equipmentArea = InventoryItemData.GetEquipmentArea(itemData); 89 | let isClothing = this.IsEquipmentAreaClothing(equipmentArea) || Equals(equipmentArea, gamedataEquipmentArea.Outfit); 90 | if isClothing { 91 | return; 92 | } 93 | } 94 | 95 | wrappedMethod(itemData, actionName, displayContext, isPlayerLocked); 96 | } 97 | -------------------------------------------------------------------------------- /scripts/Overrides/PhotoModePlayerEntityComponent.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.{OutfitSystem,PaperdollHelper} 2 | 3 | @addField(PhotoModePlayerEntityComponent) 4 | private let m_outfitSystem: wref; 5 | 6 | @addField(PhotoModePlayerEntityComponent) 7 | private let m_paperdollHelper: wref; 8 | 9 | @wrapMethod(PhotoModePlayerEntityComponent) 10 | private final func OnGameAttach() { 11 | wrappedMethod(); 12 | 13 | this.m_outfitSystem = OutfitSystem.GetInstance(this.GetOwner().GetGame()); 14 | this.m_paperdollHelper = PaperdollHelper.GetInstance(this.GetOwner().GetGame()); 15 | } 16 | 17 | @wrapMethod(PhotoModePlayerEntityComponent) 18 | private final func SetupInventory(isCurrentPlayerObjectCustomizable: Bool) { 19 | wrappedMethod(isCurrentPlayerObjectCustomizable); 20 | 21 | if this.customizable { 22 | this.m_paperdollHelper.AddPuppet(this.fakePuppet); 23 | 24 | if this.m_outfitSystem.IsActive() { 25 | this.m_outfitSystem.EquipPuppetOutfit(this.fakePuppet, this.loadingItems); 26 | } 27 | } 28 | } 29 | 30 | @wrapMethod(PhotoModePlayerEntityComponent) 31 | protected cb func OnItemAddedToSlot(evt: ref) -> Bool { 32 | if this.m_outfitSystem.IsActive() { 33 | ArrayRemove(this.loadingItems, evt.GetItemID()); 34 | } else { 35 | wrappedMethod(evt); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scripts/Overrides/PopupsManager.reds: -------------------------------------------------------------------------------- 1 | @wrapMethod(PopupsManager) 2 | private final func ShowTutorial() { 3 | if Equals(this.m_tutorialData.message, "LocKey#86091") || Equals(this.m_tutorialData.message, "LocKey#86092") { 4 | this.OnPopupCloseRequest(null); 5 | return; 6 | } 7 | 8 | wrappedMethod(); 9 | } 10 | -------------------------------------------------------------------------------- /scripts/Overrides/QuestTrackerGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.{CompatibilityManager, ConflictsPopup, RequirementsPopup} 2 | 3 | @addField(QuestTrackerGameController) 4 | private let m_wardrobePopup: ref; 5 | 6 | @wrapMethod(QuestTrackerGameController) 7 | protected cb func OnInitialize() -> Bool { 8 | wrappedMethod(); 9 | 10 | if !CompatibilityManager.IsUserNotified() { 11 | if !CompatibilityManager.CheckRequirements() { 12 | this.m_wardrobePopup = RequirementsPopup.Show(this); 13 | this.m_wardrobePopup.RegisterListener(this, n"OnWardrobePopupClose"); 14 | } else { 15 | if !CompatibilityManager.CheckConflicts(this.m_player.GetGame()) { 16 | this.m_wardrobePopup = ConflictsPopup.Show(this); 17 | this.m_wardrobePopup.RegisterListener(this, n"OnWardrobePopupClose"); 18 | } 19 | } 20 | CompatibilityManager.MarkAsNotified(); 21 | } 22 | } 23 | 24 | @addMethod(QuestTrackerGameController) 25 | protected cb func OnWardrobePopupClose(data: ref) { 26 | this.m_wardrobePopup = null; 27 | } 28 | -------------------------------------------------------------------------------- /scripts/Overrides/Stash.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.InventoryHelper 2 | 3 | @addMethod(Stash) 4 | protected cb func OnGameAttached() -> Bool { 5 | InventoryHelper.GetInstance(this.GetGame()).AddStash(this); 6 | } 7 | -------------------------------------------------------------------------------- /scripts/Overrides/UIInventoryItem.reds: -------------------------------------------------------------------------------- 1 | @addMethod(UIInventoryItem) 2 | public static func Make(owner: wref, slotID: TweakDBID, itemData: script_ref, opt manager: wref) -> ref { 3 | let self = UIInventoryItem.FromInventoryItemData(owner, itemData, manager); 4 | self.m_data.IconPath = UIInventoryItemsManager.ResolveItemIconName(self.m_itemTweakID, self.m_itemRecord, self.m_manager); 5 | self.m_slotID = slotID; 6 | 7 | return self; 8 | } 9 | 10 | @addMethod(UIInventoryItem) 11 | public func IsForWardrobe() -> Bool { 12 | return TDBID.IsValid(this.m_slotID); 13 | } 14 | 15 | @wrapMethod(UIInventoryItem) 16 | public final func IsEquipped(opt force: Bool) -> Bool { 17 | if this.IsForWardrobe() && IsDefined(this.m_manager) { 18 | return this.m_manager.IsItemEquippedInSlot(this.ID, this.m_slotID); 19 | } 20 | 21 | return wrappedMethod(force); 22 | } 23 | 24 | @wrapMethod(UIInventoryItem) 25 | public final func IsTransmogItem() -> Bool { 26 | if this.IsForWardrobe() { 27 | return false; 28 | } 29 | 30 | return wrappedMethod(); 31 | } 32 | -------------------------------------------------------------------------------- /scripts/Overrides/UIInventoryItemsManager.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @addField(UIInventoryItemsManager) 4 | private let m_outfitSystem: wref; 5 | 6 | @wrapMethod(UIInventoryItemsManager) 7 | public final static func Make(player: wref, transactionSystem: ref, uiScriptableSystem: wref) -> ref { 8 | let instance = wrappedMethod(player, transactionSystem, uiScriptableSystem); 9 | instance.m_outfitSystem = OutfitSystem.GetInstance(player.GetGame()); 10 | 11 | return instance; 12 | } 13 | 14 | @addMethod(UIInventoryItemsManager) 15 | public final func IsItemEquippedInSlot(itemID: ItemID, slotID: TweakDBID) -> Bool { 16 | return this.m_outfitSystem.IsActive() ? this.m_outfitSystem.IsEquipped(itemID) : this.IsItemEquipped(itemID); 17 | } 18 | 19 | @wrapMethod(UIInventoryItemsManager) 20 | public final func IsItemTransmog(itemID: ItemID) -> Bool { 21 | return this.m_outfitSystem.IsActive() && this.m_outfitSystem.IsEquipped(itemID); 22 | } 23 | -------------------------------------------------------------------------------- /scripts/Overrides/WardrobeSetPreviewGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @addField(WardrobeSetPreviewGameController) 4 | private let m_outfitSystem: wref; 5 | 6 | @wrapMethod(WardrobeSetPreviewGameController) 7 | protected cb func OnInitialize() -> Bool { 8 | wrappedMethod(); 9 | 10 | let cameraSetup: gameuiPuppetPreviewCameraSetup; 11 | cameraSetup.slotName = n"UISlotPreview_UpperBody"; 12 | cameraSetup.cameraZoom = 1.85; 13 | cameraSetup.interpolationTime = 1; 14 | 15 | ArrayResize(this.cameraController.cameraSetup, Cast(EnumGetMax(n"InventoryPaperdollZoomArea") + 1l)); 16 | this.cameraController.cameraSetup[EnumInt(InventoryPaperdollZoomArea.Head)] = cameraSetup; 17 | } 18 | 19 | @wrapMethod(WardrobeSetPreviewGameController) 20 | protected cb func OnPreviewInitialized() -> Bool { 21 | this.m_outfitSystem = OutfitSystem.GetInstance(this.GetGamePuppet().GetGame()); 22 | 23 | if this.m_isNotification && this.m_outfitSystem.IsActive() { 24 | this.m_outfitSystem.EquipPuppetOutfit(this.GetGamePuppet()); 25 | this.m_outfitSystem.EquipPuppetItem(this.GetGamePuppet(), this.m_data.itemID); 26 | } else { 27 | wrappedMethod(); 28 | } 29 | } 30 | 31 | @wrapMethod(WardrobeSetPreviewGameController) 32 | public final func RestorePuppetEquipment() { 33 | wrappedMethod(); 34 | 35 | if this.m_outfitSystem.IsActive() { 36 | this.m_outfitSystem.EquipPuppetOutfit(this.GetGamePuppet()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /scripts/Overrides/WardrobeUIGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.{CompatibilityManager, OutfitSystem, ArchivePopup, RequirementsPopup} 2 | 3 | @addField(WardrobeUIGameController) 4 | private let m_wardrobePopup: ref; 5 | 6 | @replaceMethod(WardrobeUIGameController) 7 | protected cb func OnInitialize() -> Bool { 8 | this.GetChildWidgetByPath(n"mainScreenContainer").SetVisible(false); 9 | this.GetChildWidgetByPath(n"setEditorScreenContainer").SetVisible(false); 10 | this.GetChildWidgetByPath(n"constantContainer/paperDoll").SetVisible(false); 11 | 12 | if !CompatibilityManager.CheckRequirements() { 13 | this.m_wardrobePopup = RequirementsPopup.Show(this); 14 | this.m_wardrobePopup.RegisterListener(this, n"OnWardrobePopupClose"); 15 | } else { 16 | let wardrobe = this.SpawnFromExternal(this.GetRootCompoundWidget(), r"equipment_ex\\gui\\wardrobe.inkwidget", n"Root:EquipmentEx.WardrobeScreenController"); 17 | if !IsDefined(wardrobe) { 18 | this.m_wardrobePopup = ArchivePopup.Show(this); 19 | this.m_wardrobePopup.RegisterListener(this, n"OnWardrobePopupClose"); 20 | return false; 21 | } 22 | } 23 | 24 | this.m_introAnimProxy = new inkAnimProxy(); 25 | } 26 | 27 | @replaceMethod(WardrobeUIGameController) 28 | protected cb func OnBack(userData: ref) -> Bool { 29 | this.m_menuEventDispatcher.SpawnEvent(n"OnWardrobeClose"); 30 | } 31 | 32 | @replaceMethod(WardrobeUIGameController) 33 | private final func CloseWardrobe() -> Void { 34 | this.m_menuEventDispatcher.SpawnEvent(n"OnWardrobeClose"); 35 | } 36 | 37 | @addMethod(WardrobeUIGameController) 38 | protected cb func OnWardrobePopupClose(data: ref) { 39 | this.m_wardrobePopup = null; 40 | this.m_menuEventDispatcher.SpawnEvent(n"OnWardrobeClose"); 41 | } 42 | -------------------------------------------------------------------------------- /scripts/Overrides/gameuiInGameMenuGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.OutfitSystem 2 | 3 | @addField(gameuiInGameMenuGameController) 4 | private let m_outfitSystem: wref; 5 | 6 | @wrapMethod(gameuiInGameMenuGameController) 7 | protected cb func OnInitialize() -> Bool { 8 | wrappedMethod(); 9 | 10 | this.m_outfitSystem = OutfitSystem.GetInstance(this.GetPlayerControlledObject().GetGame()); 11 | } 12 | 13 | @wrapMethod(gameuiInGameMenuGameController) 14 | protected cb func OnPuppetReady(sceneName: CName, puppet: ref) -> Bool { 15 | wrappedMethod(sceneName, puppet); 16 | 17 | if this.m_outfitSystem.IsActive() && Equals(sceneName, n"inventory") && !GetPlayer(puppet.GetGame()).IsReplacer() { 18 | this.m_outfitSystem.EquipPuppetOutfit(puppet) ; 19 | } 20 | } 21 | 22 | @wrapMethod(gameuiInGameMenuGameController) 23 | protected cb func OnEquipmentChanged(value: Variant) -> Bool { 24 | if !this.m_outfitSystem.UpdatePuppetFromBlackboard(this.GetPuppet(n"inventory")) { 25 | wrappedMethod(value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scripts/Overrides/gameuiInventoryGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.{CompatibilityManager, OutfitSystem, ArchivePopup, RequirementsPopup} 2 | 3 | @addField(gameuiInventoryGameController) 4 | private let m_outfitSystem: wref; 5 | 6 | @addField(gameuiInventoryGameController) 7 | private let m_wardrobeButton: wref; 8 | 9 | @addField(gameuiInventoryGameController) 10 | private let m_wardrobePopup: ref; 11 | 12 | @addField(gameuiInventoryGameController) 13 | private let m_wardrobeReady: Bool; 14 | 15 | @wrapMethod(gameuiInventoryGameController) 16 | protected cb func OnInitialize() -> Bool { 17 | wrappedMethod(); 18 | 19 | this.m_outfitSystem = OutfitSystem.GetInstance(this.GetPlayerControlledObject().GetGame()); 20 | } 21 | 22 | @wrapMethod(gameuiInventoryGameController) 23 | protected cb func OnUninitialize() -> Bool { 24 | wrappedMethod(); 25 | 26 | this.m_wardrobeButton.UnregisterFromCallback(n"OnClick", this, n"OnWardrobeBtnClick"); 27 | } 28 | 29 | @replaceMethod(gameuiInventoryGameController) 30 | private final func SetupSetButton() -> Void { 31 | let btnWrapper = this.GetChildWidgetByPath(n"default_wrapper/menuLinks") as inkCompoundWidget; 32 | let btnList = this.GetChildWidgetByPath(n"default_wrapper/menuLinks/btnsContainer") as inkCompoundWidget; 33 | 34 | // 35 | btnList.GetWidgetByIndex(3).SetVisible(false); 36 | btnList.GetWidgetByIndex(4).SetVisible(false); 37 | 38 | // Spawn new button 39 | this.m_wardrobeButton = this.SpawnFromLocal(btnList, n"HyperlinkButton:EquipmentEx.WardrobeHubLinkController"); 40 | this.m_wardrobeButton.RegisterToCallback(n"OnClick", this, n"OnWardrobeBtnClick"); 41 | 42 | // Adjust button container size 43 | let btnSpacing = btnList.GetChildMargin(); 44 | btnWrapper.SetHeight(btnWrapper.GetHeight() + this.m_wardrobeButton.GetHeight() + btnSpacing.top); 45 | 46 | // Adjust fluff text position 47 | let fluff = btnWrapper.GetWidget(n"buttonFluff2"); 48 | fluff.SetAnchor(inkEAnchor.BottomLeft); 49 | fluff.SetMargin(new inkMargin(0, 0, 0, 4.0)); 50 | 51 | // Force hide original button 52 | inkWidgetRef.SetVisible(this.m_btnSets, false); 53 | } 54 | 55 | @addMethod(gameuiInventoryGameController) 56 | protected cb func OnWardrobeBtnClick(evt: ref) -> Bool { 57 | if evt.IsAction(n"click") { 58 | this.ShowWardrobeScreen(); 59 | } 60 | } 61 | 62 | @addMethod(gameuiInventoryGameController) 63 | protected cb func OnWardrobePopupClose(data: ref) { 64 | this.m_wardrobePopup = null; 65 | } 66 | 67 | @wrapMethod(gameuiInventoryGameController) 68 | protected cb func OnBack(userData: ref) -> Bool { 69 | if this.m_wardrobeReady && IsDefined(this.GetChildWidgetByPath(n"wardrobe")) { 70 | return this.HideWardrobeScreen(); 71 | } else { 72 | return wrappedMethod(userData); 73 | } 74 | } 75 | 76 | @addMethod(gameuiInventoryGameController) 77 | protected func ShowWardrobeScreen() -> Bool { 78 | if !CompatibilityManager.CheckRequirements() { 79 | this.m_wardrobePopup = RequirementsPopup.Show(this); 80 | this.m_wardrobePopup.RegisterListener(this, n"OnWardrobePopupClose"); 81 | return false; 82 | } 83 | 84 | if IsDefined(this.GetChildWidgetByPath(n"wardrobe")) { 85 | return false; 86 | } 87 | 88 | let wardrobe = this.SpawnFromExternal(this.GetRootCompoundWidget(), r"equipment_ex\\gui\\wardrobe.inkwidget", n"Root:EquipmentEx.WardrobeScreenController") as inkCompoundWidget; 89 | 90 | if !IsDefined(wardrobe) { 91 | this.m_wardrobePopup = ArchivePopup.Show(this); 92 | this.m_wardrobePopup.RegisterListener(this, n"OnWardrobePopupClose"); 93 | return false; 94 | } 95 | 96 | wardrobe.SetName(n"wardrobe"); 97 | 98 | let alphaAnim = new inkAnimTransparency(); 99 | alphaAnim.SetStartTransparency(0.0); 100 | alphaAnim.SetEndTransparency(1.0); 101 | alphaAnim.SetType(inkanimInterpolationType.Linear); 102 | alphaAnim.SetMode(inkanimInterpolationMode.EasyOut); 103 | alphaAnim.SetDuration(0.8); 104 | 105 | let animDef = new inkAnimDef(); 106 | animDef.AddInterpolator(alphaAnim); 107 | 108 | wardrobe.GetWidgetByPathName(n"wrapper/wrapper").PlayAnimation(animDef); 109 | // animProxy.RegisterToCallback(inkanimEventType.OnFinish, this, n"OnWardrobeScreenShown"); 110 | 111 | this.m_wardrobeReady = true; 112 | 113 | if Equals(this.m_mode, InventoryModes.Item) { 114 | this.PlayShowHideItemChooserAnimation(false); 115 | } else { 116 | this.PlayLibraryAnimation(n"default_wrapper_outro"); 117 | } 118 | 119 | this.GetChildWidgetByPath(n"wardrobe/wrapper/preview").SetVisible(false); 120 | this.PlaySlidePaperdollAnimationToOutfit(); 121 | 122 | this.m_buttonHintsController.Hide(); 123 | 124 | let evt = new DropQueueUpdatedEvent(); 125 | evt.m_dropQueue = this.m_itemModeLogicController.m_itemDropQueue; 126 | wardrobe.GetController().QueueEvent(evt); 127 | 128 | this.m_itemModeLogicController.m_isWardrobeScreen = true; 129 | 130 | return true; 131 | } 132 | 133 | // @addMethod(gameuiInventoryGameController) 134 | // protected cb func OnWardrobeScreenShown(anim: ref) { 135 | // LogDebug(s"OnWardrobeScreenShown \(this.GetRootCompoundWidget().GetNumChildren())"); 136 | // this.m_wardrobeReady = true; 137 | // } 138 | 139 | @addMethod(gameuiInventoryGameController) 140 | protected func HideWardrobeScreen() -> Bool { 141 | if !this.m_wardrobeReady { 142 | return false; 143 | } 144 | 145 | let wardrobe = this.GetChildWidgetByPath(n"wardrobe") as inkCompoundWidget; 146 | 147 | this.m_wardrobeReady = false; 148 | 149 | let alphaAnim = new inkAnimTransparency(); 150 | alphaAnim.SetStartTransparency(1.0); 151 | alphaAnim.SetEndTransparency(0.0); 152 | alphaAnim.SetType(inkanimInterpolationType.Linear); 153 | alphaAnim.SetMode(inkanimInterpolationMode.EasyOut); 154 | alphaAnim.SetDuration(0.3); 155 | 156 | let animDef = new inkAnimDef(); 157 | animDef.AddInterpolator(alphaAnim); 158 | 159 | let animProxy = wardrobe.GetWidgetByPathName(n"wrapper/wrapper").PlayAnimation(animDef); 160 | animProxy.RegisterToCallback(inkanimEventType.OnFinish, this, n"OnWardrobeScreenHidden"); 161 | 162 | if Equals(this.m_mode, InventoryModes.Item) { 163 | this.SwapMode(InventoryModes.Default); 164 | this.m_itemModeLogicController.m_isShown = false; 165 | } 166 | 167 | this.PlayLibraryAnimation(n"default_wrapper_Intro"); 168 | 169 | this.GetChildWidgetByPath(n"wardrobe/wrapper/preview").SetVisible(false); 170 | inkWidgetRef.SetVisible(this.m_paperDollWidget, true); 171 | 172 | this.PlaySlidePaperdollAnimation(PaperdollPositionAnimation.Center, false); 173 | this.ZoomCamera(EnumInt(InventoryPaperdollZoomArea.Default)); 174 | 175 | this.m_buttonHintsController.Show(); 176 | 177 | this.m_itemModeLogicController.m_isWardrobeScreen = false; 178 | 179 | return true; 180 | } 181 | 182 | @addMethod(gameuiInventoryGameController) 183 | protected cb func OnWardrobeScreenHidden(anim: ref) { 184 | this.GetRootCompoundWidget().RemoveChildByName(n"wardrobe"); 185 | } 186 | 187 | @addMethod(gameuiInventoryGameController) 188 | protected final func PlaySlidePaperdollAnimationToOutfit() { 189 | let outfitPreview = this.GetChildWidgetByPath(n"wardrobe/wrapper/preview"); 190 | let outfitPreviewMargin = outfitPreview.GetMargin(); 191 | 192 | let translationInterpolator = new inkAnimTranslation(); 193 | translationInterpolator.SetDuration(0.2); 194 | translationInterpolator.SetDirection(inkanimInterpolationDirection.FromTo); 195 | translationInterpolator.SetType(inkanimInterpolationType.Linear); 196 | translationInterpolator.SetMode(inkanimInterpolationMode.EasyIn); 197 | translationInterpolator.SetStartTranslation(inkWidgetRef.GetTranslation(this.m_paperDollWidget)); 198 | translationInterpolator.SetEndTranslation(new Vector2(outfitPreviewMargin.left, 0.00)); 199 | 200 | let translationAnimation = new inkAnimDef(); 201 | translationAnimation.AddInterpolator(translationInterpolator); 202 | 203 | let animProxy = inkWidgetRef.PlayAnimation(this.m_paperDollWidget, translationAnimation); 204 | animProxy.RegisterToCallback(inkanimEventType.OnFinish, this, n"OnPaperDollSlideComplete"); 205 | } 206 | 207 | @addMethod(gameuiInventoryGameController) 208 | protected cb func OnPaperDollSlideComplete(anim: ref) { 209 | inkWidgetRef.SetVisible(this.m_paperDollWidget, false); 210 | this.GetChildWidgetByPath(n"wardrobe/wrapper/preview").SetVisible(true); 211 | } 212 | 213 | @wrapMethod(gameuiInventoryGameController) 214 | protected cb func OnEquipmentClick(evt: ref) -> Bool { 215 | if IsDefined(this.GetChildWidgetByPath(n"wardrobe")) { 216 | return false; 217 | } 218 | 219 | if evt.actionName.IsAction(n"unequip_item") && Equals(evt.display.GetEquipmentArea(), gamedataEquipmentArea.Outfit) && this.m_outfitSystem.IsActive() { 220 | this.m_outfitSystem.Deactivate(); 221 | } else { 222 | wrappedMethod(evt); 223 | } 224 | } 225 | 226 | @replaceMethod(gameuiInventoryGameController) 227 | private final func RefreshEquippedWardrobeItems() { 228 | ArrayClear(this.m_wardrobeOutfitAreas); 229 | 230 | if this.m_outfitSystem.IsActive() { 231 | ArrayPush(this.m_wardrobeOutfitAreas, gamedataEquipmentArea.Head); 232 | ArrayPush(this.m_wardrobeOutfitAreas, gamedataEquipmentArea.Face); 233 | ArrayPush(this.m_wardrobeOutfitAreas, gamedataEquipmentArea.OuterChest); 234 | ArrayPush(this.m_wardrobeOutfitAreas, gamedataEquipmentArea.InnerChest); 235 | ArrayPush(this.m_wardrobeOutfitAreas, gamedataEquipmentArea.Legs); 236 | ArrayPush(this.m_wardrobeOutfitAreas, gamedataEquipmentArea.Feet); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /scripts/Overrides/gameuiPhotoModeMenuController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.{OutfitSystem,PaperdollHelper} 2 | 3 | enum PhotoModeUI { 4 | CharacterPage = 2, 5 | VisibilityAttribute = 27, 6 | ExpressionAttribute = 28, 7 | OutfitAttribute = 3301, 8 | NoOutfitOption = 3302, 9 | CurrentOutfitOption = 3303 10 | } 11 | 12 | @addField(gameuiPhotoModeMenuController) 13 | private let m_outfitSystem: wref; 14 | 15 | @addField(gameuiPhotoModeMenuController) 16 | private let m_paperdollHelper: wref; 17 | 18 | @addField(gameuiPhotoModeMenuController) 19 | private let m_outfitAttribute: Uint32; 20 | 21 | @wrapMethod(gameuiPhotoModeMenuController) 22 | protected cb func OnInitialize() -> Bool { 23 | wrappedMethod(); 24 | 25 | this.m_outfitSystem = OutfitSystem.GetInstance(this.GetPlayerControlledObject().GetGame()); 26 | this.m_paperdollHelper = PaperdollHelper.GetInstance(this.GetPlayerControlledObject().GetGame()); 27 | this.m_outfitAttribute = Cast(EnumInt(PhotoModeUI.OutfitAttribute)); 28 | } 29 | 30 | @wrapMethod(gameuiPhotoModeMenuController) 31 | protected cb func OnAddMenuItem(label: String, attribute: Uint32, page: Uint32) -> Bool { 32 | wrappedMethod(label, attribute, page); 33 | 34 | if Equals(page, Cast(EnumInt(PhotoModeUI.CharacterPage))) && Equals(attribute, Cast(EnumInt(PhotoModeUI.VisibilityAttribute))) { 35 | this.AddMenuItem(StrUpper(GetLocalizedTextByKey(n"UI-Inventory-Labels-Outfit")), this.m_outfitAttribute, page, false); 36 | } 37 | } 38 | 39 | @wrapMethod(gameuiPhotoModeMenuController) 40 | protected cb func OnShow(reversedUI: Bool) -> Bool { 41 | let outfitMenuItem = this.GetMenuItem(this.m_outfitAttribute); 42 | if IsDefined(outfitMenuItem) { 43 | let outfits = this.m_outfitSystem.GetOutfits(); 44 | let active = this.m_outfitSystem.IsActive(); 45 | let options: array; 46 | let current: Int32 = 0; 47 | 48 | ArrayResize(options, ArraySize(outfits) + (active ? 2 : 1)); 49 | 50 | options[0].optionText = GetLocalizedTextByKey(n"UI-Wardrobe-NoOutfit"); 51 | options[0].optionData = EnumInt(PhotoModeUI.NoOutfitOption); 52 | 53 | if active { 54 | options[1].optionText = GetLocalizedTextByKey(n"UI-Wardrobe-CurrentOutfit"); 55 | options[1].optionData = EnumInt(PhotoModeUI.CurrentOutfitOption); 56 | } 57 | 58 | let i = (active ? 2 : 1); 59 | for outfitName in outfits { 60 | options[i].optionText = NameToString(outfitName); // StrUpper() 61 | options[i].optionData = i; 62 | 63 | if this.m_outfitSystem.IsEquipped(outfitName) { 64 | current = options[i].optionData; 65 | } 66 | 67 | i += 1; 68 | } 69 | 70 | if current == 0 { 71 | current = options[active ? 1 : 0].optionData; 72 | } 73 | 74 | outfitMenuItem.m_photoModeController = this; 75 | outfitMenuItem.SetupOptionSelector(options, current); 76 | outfitMenuItem.SetIsEnabled(true); 77 | 78 | this.GetChildWidgetByPath(n"options_panel").SetHeight(1000.0); 79 | this.GetChildWidgetByPath(n"options_panel/horizontalMenu").SetMargin(0.0, 0.0, -10.0, 920.0); 80 | } 81 | 82 | wrappedMethod(reversedUI); 83 | } 84 | 85 | @wrapMethod(gameuiPhotoModeMenuController) 86 | protected cb func OnSetAttributeOptionEnabled(attribute: Uint32, enabled: Bool) -> Bool { 87 | wrappedMethod(attribute, enabled); 88 | 89 | if Equals(attribute, Cast(EnumInt(PhotoModeUI.ExpressionAttribute))) { 90 | let outfitMenuItem = this.GetMenuItem(this.m_outfitAttribute); 91 | if IsDefined(outfitMenuItem) { 92 | outfitMenuItem.SetIsEnabled(enabled); 93 | } 94 | } 95 | } 96 | 97 | @addMethod(gameuiPhotoModeMenuController) 98 | public func OnAttributeOptionSelected(attribute: Uint32, option: PhotoModeOptionSelectorData) { 99 | if Equals(attribute, Cast(EnumInt(PhotoModeUI.OutfitAttribute))) { 100 | switch IntEnum(option.optionData) { 101 | case PhotoModeUI.NoOutfitOption: 102 | this.m_outfitSystem.EquipPuppetOutfit(this.m_paperdollHelper.GetPuppet(), false); 103 | break; 104 | case PhotoModeUI.CurrentOutfitOption: 105 | this.m_outfitSystem.EquipPuppetOutfit(this.m_paperdollHelper.GetPuppet(), true); 106 | break; 107 | default: 108 | let outfitName = StringToName(option.optionText); 109 | this.m_outfitSystem.EquipPuppetOutfit(this.m_paperdollHelper.GetPuppet(), outfitName); 110 | break; 111 | } 112 | } 113 | } 114 | 115 | @wrapMethod(PhotoModeMenuListItem) 116 | private final func StartArrowClickedEffect(widget: inkWidgetRef) { 117 | wrappedMethod(widget); 118 | 119 | this.m_photoModeController.OnAttributeOptionSelected( 120 | (this.GetData() as PhotoModeMenuListItemData).attributeKey, 121 | this.m_OptionSelectorValues[this.m_OptionSelector.GetCurrIndex()] 122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /scripts/Overrides/inkInventoryPuppetPreviewGameController.reds: -------------------------------------------------------------------------------- 1 | import EquipmentEx.PaperdollHelper 2 | 3 | @wrapMethod(inkInventoryPuppetPreviewGameController) 4 | protected cb func OnInitialize() -> Bool { 5 | wrappedMethod(); 6 | 7 | PaperdollHelper.GetInstance(this.GetPlayerControlledObject().GetGame()).AddPreview(this); 8 | } 9 | -------------------------------------------------------------------------------- /scripts/Overrides/inkScrollController.reds: -------------------------------------------------------------------------------- 1 | @addMethod(inkScrollController) 2 | public func SetScrollEnabled(enabled: Bool) { 3 | if enabled { 4 | if Equals(this.direction, inkEScrollDirection.Horizontal) { 5 | this.scrollDelta = this.contentSize.X - this.viewportSize.X; 6 | } else { 7 | this.scrollDelta = this.contentSize.Y - this.viewportSize.Y; 8 | } 9 | } else { 10 | this.scrollDelta = 0.0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scripts/PaperdollHelper.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class PaperdollHelper extends ScriptableSystem { 4 | private let m_puppet: wref; 5 | private let m_preview: wref; 6 | 7 | public func AddPreview(preview: ref) { 8 | this.m_preview = preview; 9 | } 10 | 11 | public func GetPreview() -> wref { 12 | return this.m_preview; 13 | } 14 | 15 | public func AddPuppet(puppet: ref) { 16 | this.m_puppet = puppet; 17 | } 18 | 19 | public func GetPuppet() -> wref { 20 | return this.m_puppet; 21 | } 22 | 23 | public static func GetInstance(game: GameInstance) -> ref { 24 | return GameInstance.GetScriptableSystemsContainer(game).Get(n"EquipmentEx.PaperdollHelper") as PaperdollHelper; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/Tweaks/OutfitSlotMatcher.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | struct RecordSlotMapping { 4 | public let slotID: TweakDBID; 5 | public let recordIDs: array; 6 | } 7 | 8 | struct EntityNameSlotMapping { 9 | public let slotID: TweakDBID; 10 | public let entityName: CName; 11 | } 12 | 13 | struct AppearanceNameSlotMapping { 14 | public let slotID: TweakDBID; 15 | public let appearanceTokens: array; 16 | } 17 | 18 | struct EquipmentAreaSlotMapping { 19 | public let slotID: TweakDBID; 20 | public let equipmentAreas: array; 21 | } 22 | 23 | struct PriceModifierSlotMapping { 24 | public let slotID: TweakDBID; 25 | public let priceModifiers: array; 26 | } 27 | 28 | struct SlotMappingMatch { 29 | public let slotID: TweakDBID; 30 | public let score: Int32; 31 | } 32 | 33 | class OutfitSlotMatcher { 34 | private let m_recordMappings: array; 35 | private let m_entityMappings: array; 36 | private let m_appearanceMappings: array; 37 | private let m_equipmentMappings: array; 38 | private let m_priceMappings: array; 39 | private let m_ignoredEntities: array; 40 | 41 | public func MapRecords(mappings: array) { 42 | this.m_recordMappings = mappings; 43 | } 44 | 45 | public func MapEntities(mappings: array) { 46 | this.m_entityMappings = mappings; 47 | } 48 | 49 | public func MapAppearances(mappings: array) { 50 | this.m_appearanceMappings = mappings; 51 | } 52 | 53 | public func MapEquipmentAreas(mappings: array) { 54 | this.m_equipmentMappings = mappings; 55 | } 56 | 57 | public func MapPrices(mappings: array) { 58 | this.m_priceMappings = mappings; 59 | } 60 | 61 | public func IgnoreEntities(ignores: array) { 62 | this.m_ignoredEntities = ignores; 63 | } 64 | 65 | public func Match(item: ref) -> TweakDBID { 66 | if Equals(item.AppearanceName(), n"") { 67 | return TDBID.None(); 68 | } 69 | 70 | let entityName = item.EntityName(); 71 | 72 | if ArrayContains(this.m_ignoredEntities, entityName) { 73 | return TDBID.None(); 74 | } 75 | 76 | let recordID = item.GetID(); 77 | let appearanceName = NameToString(item.AppearanceName()); 78 | let priceModifiers = TweakDBInterface.GetForeignKeyArray(item.GetID() + t".buyPrice"); 79 | let equipmentArea = item.EquipArea().GetID(); 80 | 81 | // Record exact match 82 | for mapping in this.m_recordMappings { 83 | if ArrayContains(mapping.recordIDs, recordID) { 84 | return mapping.slotID; 85 | } 86 | } 87 | 88 | // Appearance exact match 89 | for mapping in this.m_appearanceMappings { 90 | for appearanceToken in mapping.appearanceTokens { 91 | if Equals(appearanceName, appearanceToken) { 92 | return mapping.slotID; 93 | } 94 | } 95 | } 96 | 97 | // Appearance partial match 98 | let match: SlotMappingMatch; 99 | for mapping in this.m_appearanceMappings { 100 | for appearanceToken in mapping.appearanceTokens { 101 | if StrFindFirst(appearanceName, appearanceToken) >= 0 { 102 | // return mapping.slotID; 103 | if StrLen(appearanceToken) > match.score { 104 | match.score = StrLen(appearanceToken); 105 | match.slotID = mapping.slotID; 106 | } 107 | } 108 | } 109 | } 110 | if match.score > 0 { 111 | return match.slotID; 112 | } 113 | 114 | // Price exact match 115 | for mapping in this.m_priceMappings { 116 | for priceModifier in mapping.priceModifiers { 117 | if ArrayContains(priceModifiers, priceModifier) { 118 | return mapping.slotID; 119 | } 120 | } 121 | } 122 | 123 | // Equipment area exact match 124 | for mapping in this.m_equipmentMappings { 125 | if ArrayContains(mapping.equipmentAreas, equipmentArea) { 126 | return mapping.slotID; 127 | } 128 | } 129 | 130 | // Entity exact match 131 | for mapping in this.m_entityMappings { 132 | if Equals(entityName, mapping.entityName) { 133 | return mapping.slotID; 134 | } 135 | } 136 | 137 | return TDBID.None(); 138 | } 139 | 140 | public static func Create() -> ref { 141 | return new OutfitSlotMatcher(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /scripts/Tweaks/OutfitTweakHelper.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class OutfitTweakHelper { 4 | public static func PrepareCustomSlotMatcher() -> ref { 5 | let slotMatcher = OutfitSlotMatcher.Create(); 6 | 7 | slotMatcher.IgnoreEntities([ 8 | n"player_head_item", 9 | n"player_face_item", 10 | n"player_inner_torso_item", 11 | n"player_outer_torso_item", 12 | n"player_legs_item", 13 | n"player_feet_item", 14 | n"player_outfit_item" 15 | ]); 16 | 17 | slotMatcher.MapPrices([ 18 | new PriceModifierSlotMapping(t"OutfitSlots.Glasses", [t"Price.Glasses", t"Price.Visor"]), 19 | new PriceModifierSlotMapping(t"OutfitSlots.Wreath", [t"Price.TechFaceClothing"]), 20 | new PriceModifierSlotMapping(t"OutfitSlots.LegsOuter", [t"Price.Skirt"]) 21 | ]); 22 | 23 | slotMatcher.MapEquipmentAreas([ 24 | new EquipmentAreaSlotMapping(t"OutfitSlots.Head", [t"EquipmentArea.HeadArmor"]), 25 | new EquipmentAreaSlotMapping(t"OutfitSlots.Mask", [t"EquipmentArea.FaceArmor"]), 26 | new EquipmentAreaSlotMapping(t"OutfitSlots.TorsoInner", [t"EquipmentArea.InnerChest"]), 27 | new EquipmentAreaSlotMapping(t"OutfitSlots.TorsoOuter", [t"EquipmentArea.ChestArmor"]), 28 | new EquipmentAreaSlotMapping(t"OutfitSlots.LegsMiddle", [t"EquipmentArea.LegArmor"]), 29 | new EquipmentAreaSlotMapping(t"OutfitSlots.Feet", [t"EquipmentArea.Feet"]), 30 | new EquipmentAreaSlotMapping(t"OutfitSlots.BodyOuter", [t"EquipmentArea.Outfit"]) 31 | ]); 32 | 33 | return slotMatcher; 34 | } 35 | 36 | public static func PrepareOriginalSlotMatcher() -> ref { 37 | let slotMatcher = OutfitSlotMatcher.Create(); 38 | 39 | //slotMatcher.IgnoreEntities([ 40 | // n"player_outfit_item" 41 | //]); 42 | 43 | slotMatcher.MapEntities([ 44 | new EntityNameSlotMapping(t"OutfitSlots.Head", n"player_head_item"), 45 | new EntityNameSlotMapping(t"OutfitSlots.Mask", n"player_face_item"), 46 | new EntityNameSlotMapping(t"OutfitSlots.TorsoInner", n"player_inner_torso_item"), 47 | new EntityNameSlotMapping(t"OutfitSlots.TorsoOuter", n"player_outer_torso_item"), 48 | new EntityNameSlotMapping(t"OutfitSlots.LegsMiddle", n"player_legs_item"), 49 | new EntityNameSlotMapping(t"OutfitSlots.Feet", n"player_feet_item"), 50 | new EntityNameSlotMapping(t"OutfitSlots.BodyMiddle", n"player_outfit_item"), 51 | new EntityNameSlotMapping(t"OutfitSlots.BodyMiddle", n"player_outfit_item_ep1") 52 | ]); 53 | 54 | slotMatcher.MapAppearances([ 55 | new AppearanceNameSlotMapping(t"OutfitSlots.Glasses", ["f1_tech_01_"]), 56 | new AppearanceNameSlotMapping(t"OutfitSlots.Wreath", ["f1_tech_02_"]), 57 | new AppearanceNameSlotMapping(t"OutfitSlots.Balaclava", ["h1_balaclava_"]), 58 | new AppearanceNameSlotMapping(t"OutfitSlots.TorsoUnder", ["t1_undershirt_02_", "t1_undershirt_03_", "t1_shirt_01_", "t1_shirt_02_"]), 59 | new AppearanceNameSlotMapping(t"OutfitSlots.TorsoInner", ["t1_undershirt_01_", "t1_tshirt_", "t1_formal_", "set_01_fixer_01_t1_"]), 60 | new AppearanceNameSlotMapping(t"OutfitSlots.TorsoInner", ["t2_dress_01_", "t2_jacket_16_"]), 61 | new AppearanceNameSlotMapping(t"OutfitSlots.TorsoMiddle", ["t1_shirt_03_"]), 62 | new AppearanceNameSlotMapping(t"OutfitSlots.TorsoMiddle", ["t2_dress_", "t2_shirt_", "t2_vest_", "t2_formal_"]), 63 | new AppearanceNameSlotMapping(t"OutfitSlots.TorsoAux", ["t2_vest_01_", "t2_vest_02_", "t2_vest_03_", "t2_vest_04_", "t2_vest_06_", "t2_vest_07_", "t2_vest_08_", "t2_vest_10_", "t2_vest_12_", "t2_vest_16_"]), 64 | new AppearanceNameSlotMapping(t"OutfitSlots.LegsOuter", ["l1_shorts_03_", "l1_shorts_04_", "l1_shorts_05_", "set_01_fixer_01_l1_"]), 65 | new AppearanceNameSlotMapping(t"OutfitSlots.LegsOuter", ["l1_pants_04_", "l1_pants_05_", "l1_pants_06_", "l1_pants_07_", "l1_pants_08_", "l1_pants_09_", "l1_pants_10_", "l1_pants_11_", "l1_pants_12_", "l1_pants_13_", "l1_pants_14_"]), 66 | new AppearanceNameSlotMapping(t"OutfitSlots.BodyUnder", ["t1_jumpsuit_", "set_01_netrunner_01_t1_"]), 67 | new AppearanceNameSlotMapping(t"OutfitSlots.BodyMiddle", ["t2_jumpsuit_"]) // "outfit_02_q114_cyberspace_" 68 | ]); 69 | 70 | slotMatcher.MapPrices([ 71 | new PriceModifierSlotMapping(t"OutfitSlots.Mask", [t"Price.Mask"]), 72 | new PriceModifierSlotMapping(t"OutfitSlots.Glasses", [t"Price.Glasses", t"Price.Visor"]), 73 | new PriceModifierSlotMapping(t"OutfitSlots.Wreath", [t"Price.TechFaceClothing"]), 74 | new PriceModifierSlotMapping(t"OutfitSlots.LegsOuter", [t"Price.Skirt"]) 75 | ]); 76 | 77 | slotMatcher.MapRecords([ 78 | new RecordSlotMapping(t"OutfitSlots.Glasses", [ 79 | t"Items.Media_01_Set_Tech", 80 | t"Items.Techie_01_Set_Tech" 81 | ]), 82 | new RecordSlotMapping(t"OutfitSlots.TorsoUnder", [ 83 | t"Items.Media_01_Set_Shirt" 84 | ]), 85 | new RecordSlotMapping(t"OutfitSlots.TorsoMiddle", [ 86 | t"Items.Corporate_01_Set_FormalJacket", 87 | t"Items.Rockerboy_01_Set_Jacket" 88 | ]), 89 | new RecordSlotMapping(t"OutfitSlots.TorsoAux", [ 90 | t"Items.Media_01_Set_Vest", 91 | t"Items.SQ021_Wraiths_Vest", 92 | t"Items.Techie_01_Set_Vest" 93 | ]), 94 | new RecordSlotMapping(t"OutfitSlots.LegsOuter", [ 95 | t"Items.Cop_01_Set_Pants", 96 | t"Items.Media_01_Set_Pants", 97 | t"Items.Netrunner_01_Set_Pants", 98 | t"Items.Nomad_01_Set_Pants", 99 | t"Items.Q202_Epilogue_Pants", 100 | t"Items.Q203_Epilogue_Pants", 101 | t"Items.Q204_Epilogue_Pants", 102 | t"Items.Solo_01_Set_Pants", 103 | t"Items.Techie_01_Set_Pants" 104 | ]) 105 | ]); 106 | 107 | return slotMatcher; 108 | } 109 | 110 | public static func BuildOutfitSlotMap(out outfitSlots: array) -> ref { 111 | let outfitMap = new inkIntHashMap(); 112 | let index = 0; 113 | for outfitSlot in outfitSlots { 114 | outfitMap.Insert(TDBID.ToNumber(outfitSlot.slotID), index); 115 | index = index + 1; 116 | } 117 | return outfitMap; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /scripts/Tweaks/PatchCustomItems.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class PatchCustomItems extends ScriptableTweak { 4 | protected func OnApply() -> Void { 5 | let batch = TweakDBManager.StartBatch(); 6 | let outfitSlots = OutfitConfig.OutfitSlots(); 7 | let outfitMap = OutfitTweakHelper.BuildOutfitSlotMap(outfitSlots); 8 | let slotMatcher = OutfitTweakHelper.PrepareCustomSlotMatcher(); 9 | 10 | for record in TweakDBInterface.GetRecords(n"Clothing_Record") { 11 | let item = record as Clothing_Record; 12 | let placementSlots = TweakDBInterface.GetForeignKeyArray(item.GetID() + t".placementSlots"); 13 | 14 | if ArraySize(placementSlots) == 1 { 15 | let outfitSlotID = slotMatcher.Match(item); 16 | if TDBID.IsValid(outfitSlotID) { 17 | let outfitHash = TDBID.ToNumber(outfitSlotID); 18 | if outfitMap.KeyExist(outfitHash) { 19 | let outfitIndex = outfitMap.Get(outfitHash); 20 | let outfitSlot = outfitSlots[outfitIndex]; 21 | if !ArrayContains(placementSlots, outfitSlot.slotID) { 22 | ArrayPush(placementSlots, outfitSlot.slotID); 23 | batch.SetFlat(item.GetID() + t".placementSlots", placementSlots); 24 | batch.UpdateRecord(item.GetID()); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | batch.Commit(); 32 | 33 | for record in TweakDBInterface.GetRecords(n"Clothing_Record") { 34 | let item = record as Clothing_Record; 35 | let placementSlots = TweakDBInterface.GetForeignKeyArray(item.GetID() + t".placementSlots"); 36 | let garmentOffset = item.GarmentOffset(); 37 | 38 | if (garmentOffset == 0 || DevMode()) && ArraySize(placementSlots) > 1 { 39 | let outfitSlotID = ArrayLast(placementSlots); 40 | if TDBID.IsValid(outfitSlotID) { 41 | let outfitHash = TDBID.ToNumber(outfitSlotID); 42 | if outfitMap.KeyExist(outfitHash) { 43 | let outfitIndex = outfitMap.Get(outfitHash); 44 | let outfitSlot = outfitSlots[outfitIndex]; 45 | batch.SetFlat(item.GetID() + t".garmentOffset", outfitSlot.garmentOffset); 46 | batch.UpdateRecord(item.GetID()); 47 | } 48 | } 49 | } 50 | } 51 | 52 | batch.Commit(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scripts/Tweaks/PatchOriginaltems.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class PatchOriginaltems extends ScriptableTweak { 4 | protected func OnApply() -> Void { 5 | let batch = TweakDBManager.StartBatch(); 6 | let outfitSlots = OutfitConfig.OutfitSlots(); 7 | let outfitMap = OutfitTweakHelper.BuildOutfitSlotMap(outfitSlots); 8 | let slotMatcher = OutfitTweakHelper.PrepareOriginalSlotMatcher(); 9 | 10 | for record in TweakDBInterface.GetRecords(n"Clothing_Record") { 11 | let item = record as Clothing_Record; 12 | let placementSlots = TweakDBInterface.GetForeignKeyArray(item.GetID() + t".placementSlots"); 13 | let garmentOffset = item.GarmentOffset(); 14 | 15 | let outfitSlotID: TweakDBID; 16 | if ArraySize(placementSlots) == 1 || DevMode() { 17 | outfitSlotID = slotMatcher.Match(item); 18 | } else { 19 | outfitSlotID = ArrayLast(placementSlots); 20 | } 21 | 22 | if TDBID.IsValid(outfitSlotID) { 23 | let updated = false; 24 | 25 | let outfitHash = TDBID.ToNumber(outfitSlotID); 26 | if outfitMap.KeyExist(outfitHash) { 27 | let outfitIndex = outfitMap.Get(outfitHash); 28 | let outfitSlot = outfitSlots[outfitIndex]; 29 | 30 | if NotEquals(ArrayLast(placementSlots), outfitSlot.slotID) { 31 | ArrayRemove(placementSlots, outfitSlot.slotID); 32 | ArrayPush(placementSlots, outfitSlot.slotID); 33 | if garmentOffset == 0 || DevMode() { 34 | garmentOffset = outfitSlot.garmentOffset; 35 | } 36 | updated = true; 37 | } 38 | } 39 | 40 | if updated { 41 | batch.SetFlat(item.GetID() + t".placementSlots", placementSlots); 42 | batch.SetFlat(item.GetID() + t".garmentOffset", garmentOffset); 43 | batch.UpdateRecord(item.GetID()); 44 | } 45 | } 46 | } 47 | 48 | batch.Commit(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /scripts/Tweaks/RegisterOutfitSlots.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class RegisterOutfitSlots extends ScriptableTweak { 4 | protected func OnApply() -> Void { 5 | let batch = TweakDBManager.StartBatch(); 6 | let outfitSlots = OutfitConfig.OutfitSlots(); 7 | 8 | for outfitSlot in outfitSlots { 9 | batch.CreateRecord(outfitSlot.slotID, n"AttachmentSlot_Record"); 10 | batch.SetFlat(outfitSlot.slotID + t".localizedName", outfitSlot.displayName); 11 | 12 | if ArraySize(outfitSlot.relatedSlotIDs) > 0 { 13 | batch.SetFlat(outfitSlot.slotID + t".parentSlot", outfitSlot.relatedSlotIDs[0]); 14 | } 15 | 16 | if ArraySize(outfitSlot.dependencySlotIDs) > 0 { 17 | batch.SetFlat(outfitSlot.slotID + t".dependencySlots", outfitSlot.dependencySlotIDs); 18 | } 19 | 20 | batch.UpdateRecord(outfitSlot.slotID); 21 | batch.RegisterName(outfitSlot.slotName); 22 | } 23 | 24 | let playerEntityTemplates = [ 25 | r"base\\characters\\entities\\player\\player_wa_fpp.ent", 26 | r"base\\characters\\entities\\player\\player_wa_tpp.ent", 27 | r"base\\characters\\entities\\player\\player_wa_tpp_cutscene.ent", 28 | r"base\\characters\\entities\\player\\player_wa_tpp_cutscene_no_impostor.ent", 29 | r"base\\characters\\entities\\player\\player_wa_tpp_reflexion.ent", 30 | r"base\\characters\\entities\\player\\player_ma_fpp.ent", 31 | r"base\\characters\\entities\\player\\player_ma_tpp.ent", 32 | r"base\\characters\\entities\\player\\player_ma_tpp_cutscene.ent", 33 | r"base\\characters\\entities\\player\\player_ma_tpp_cutscene_no_impostor.ent", 34 | r"base\\characters\\entities\\player\\player_ma_tpp_reflexion.ent" 35 | ]; 36 | 37 | let playerDisplayName = GetLocalizedTextByKey(TweakDBInterface.GetLocKeyDefault(t"Character.Player_Puppet_Base.displayName")); 38 | 39 | for record in TweakDBInterface.GetRecords(n"Character_Record") { 40 | let character = record as Character_Record; 41 | if ArrayContains(playerEntityTemplates, character.EntityTemplatePath()) || Equals(GetLocalizedTextByKey(character.DisplayName()), playerDisplayName) { 42 | let characterSlots = TweakDBInterface.GetForeignKeyArray(character.GetID() + t".attachmentSlots"); 43 | if ArrayContains(characterSlots, t"AttachmentSlots.Chest") { 44 | for outfitSlot in outfitSlots { 45 | if !ArrayContains(characterSlots, outfitSlot.slotID) { 46 | ArrayPush(characterSlots, outfitSlot.slotID); 47 | } 48 | } 49 | 50 | batch.SetFlat(character.GetID() + t".attachmentSlots", characterSlots); 51 | batch.UpdateRecord(character.GetID()); 52 | } 53 | } 54 | } 55 | 56 | batch.Commit(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scripts/UI/ArchivePopup.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class ArchivePopup { 4 | public static func Show(controller: ref) -> ref { 5 | return GenericMessageNotification.Show( 6 | controller, 7 | GetLocalizedText("LocKey#11447"), 8 | "Equipment-EX has detected an issue:\n" + 9 | "- archive/pc/mod/EquipmentEx.archive is missing\n\n" + 10 | "Possible solutions:\n" + 11 | "- Reinstall the mod from the original distribution\n" + 12 | "- If you installed it as REDmod, make sure mods are enabled\n", 13 | GenericMessageNotificationType.OK 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/UI/CollapseButton.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | import Codeware.UI.* 3 | 4 | class CollapseButtonClick extends Event { 5 | public let collapse: Bool; 6 | public let action: ref; 7 | } 8 | 9 | class CollapseButton extends inkCustomController { 10 | protected let m_isFlipped: Bool; 11 | protected let m_isCollapse: Bool; 12 | 13 | protected let m_bg: wref; 14 | protected let m_frame: wref; 15 | protected let m_icon: wref; 16 | 17 | protected cb func OnCreate() { 18 | let root = new inkCanvas(); 19 | root.SetSize(110.0, 80.0); 20 | root.SetAnchorPoint(new Vector2(0.5, 0.5)); 21 | root.SetInteractive(true); 22 | 23 | let bg = new inkImage(); 24 | bg.SetName(n"bg"); 25 | bg.SetAnchor(inkEAnchor.Fill); 26 | bg.SetNineSliceScale(true); 27 | bg.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 28 | bg.SetStyle(r"base\\gameplay\\gui\\common\\components\\toggles_style.inkstyle"); 29 | bg.BindProperty(n"tintColor", n"FilterButton.backgroundColor"); 30 | bg.BindProperty(n"opacity", n"FilterButton.backgroundOpacity"); 31 | bg.Reparent(root); 32 | 33 | let frame = new inkImage(); 34 | frame.SetName(n"frame"); 35 | frame.SetAnchor(inkEAnchor.Fill); 36 | frame.SetNineSliceScale(true); 37 | frame.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 38 | frame.SetTexturePart(n"tooltip_map_fg"); 39 | frame.SetStyle(r"base\\gameplay\\gui\\common\\components\\toggles_style.inkstyle"); 40 | frame.BindProperty(n"tintColor", n"FilterButton.frameColor"); 41 | frame.BindProperty(n"opacity", n"FilterButton.frameOpacity"); 42 | frame.Reparent(root); 43 | 44 | let icon = new inkVerticalPanel(); 45 | icon.SetAnchor(inkEAnchor.Centered); 46 | icon.SetAnchorPoint(new Vector2(0.5, 0.5)); 47 | icon.Reparent(root); 48 | 49 | let arrowScale = 0.4; 50 | let arrowSize = new Vector2(44.0 * arrowScale, 38.0 * arrowScale); 51 | 52 | let arrowUp = new inkImage(); 53 | arrowUp.SetName(n"arrowUp"); 54 | arrowUp.SetHAlign(inkEHorizontalAlign.Center); 55 | arrowUp.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 56 | arrowUp.SetTexturePart(n"arrow_rect_bg"); 57 | arrowUp.SetSize(arrowSize); 58 | arrowUp.SetStyle(r"base\\gameplay\\gui\\common\\components\\toggles_style.inkstyle"); 59 | arrowUp.BindProperty(n"tintColor", n"FilterButton.iconColor"); 60 | arrowUp.Reparent(icon); 61 | 62 | let line = new inkRectangle(); 63 | line.SetHAlign(inkEHorizontalAlign.Center); 64 | line.SetSize(new Vector2(arrowSize.X + 12.0, 2.0)); 65 | line.SetStyle(r"base\\gameplay\\gui\\common\\components\\toggles_style.inkstyle"); 66 | line.BindProperty(n"tintColor", n"FilterButton.iconColor"); 67 | line.Reparent(icon); 68 | 69 | let arrowDown = new inkImage(); 70 | arrowDown.SetName(n"arrowDown"); 71 | arrowDown.SetHAlign(inkEHorizontalAlign.Center); 72 | arrowDown.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 73 | arrowDown.SetTexturePart(n"arrow_down_bg"); 74 | arrowDown.SetSize(arrowSize); 75 | arrowDown.SetStyle(r"base\\gameplay\\gui\\common\\components\\toggles_style.inkstyle"); 76 | arrowDown.BindProperty(n"tintColor", n"FilterButton.iconColor"); 77 | arrowDown.Reparent(icon); 78 | 79 | this.m_bg = bg; 80 | this.m_frame = frame; 81 | this.m_icon = icon; 82 | 83 | this.SetRootWidget(root); 84 | this.ApplyCollapseState(); 85 | this.ApplyFlippedState(); 86 | } 87 | 88 | protected cb func OnInitialize() { 89 | this.RegisterToCallback(n"OnClick", this, n"OnClick"); 90 | this.RegisterToCallback(n"OnHoverOver", this, n"OnHoverOver"); 91 | this.RegisterToCallback(n"OnHoverOut", this, n"OnHoverOut"); 92 | } 93 | 94 | protected cb func OnClick(evt: ref) { 95 | this.TriggerClickEvent(evt.GetActionName()); 96 | } 97 | 98 | protected cb func OnHoverOver(evt: ref) { 99 | this.GetRootWidget().SetState(n"Hover"); 100 | } 101 | 102 | protected cb func OnHoverOut(evt: ref) { 103 | this.GetRootWidget().SetState(n"Default"); 104 | } 105 | 106 | protected func ApplyCollapseState() { 107 | this.m_icon.SetChildOrder(this.m_isCollapse ? inkEChildOrder.Backward : inkEChildOrder.Forward); 108 | this.m_icon.SetChildMargin(this.m_isCollapse ? new inkMargin(0.0, 3.0, 0.0, 3.0) : new inkMargin(0.0, 3.0, 0.0, 3.0)); 109 | } 110 | 111 | protected func ApplyFlippedState() { 112 | this.m_bg.SetTexturePart(this.m_isFlipped ? n"cell_flip_bg" : n"cell_bg"); 113 | this.m_frame.SetBrushMirrorType(this.m_isFlipped ? inkBrushMirrorType.Horizontal : inkBrushMirrorType.NoMirror); 114 | } 115 | 116 | protected func TriggerClickEvent(action: ref) { 117 | let evt = new CollapseButtonClick(); 118 | evt.collapse = this.m_isCollapse; 119 | evt.action = action; 120 | 121 | let uiSystem = GameInstance.GetUISystem(this.GetGame()); 122 | uiSystem.QueueEvent(evt); 123 | } 124 | 125 | public func SetCollapse(isCollapse: Bool) { 126 | this.m_isCollapse = isCollapse; 127 | 128 | this.ApplyCollapseState(); 129 | } 130 | 131 | public func SetFlipped(isFlipped: Bool) { 132 | this.m_isFlipped = isFlipped; 133 | 134 | this.ApplyFlippedState(); 135 | } 136 | 137 | public static func Create() -> ref { 138 | let self = new CollapseButton(); 139 | self.CreateInstance(); 140 | 141 | return self; 142 | } 143 | 144 | func OnReparent(parent: ref) {} 145 | } 146 | -------------------------------------------------------------------------------- /scripts/UI/ConflictsPopup.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class ConflictsPopup { 4 | public static func Show(controller: ref) -> ref { 5 | let game = controller.GetPlayerControlledObject().GetGame(); 6 | let conflicts: array; 7 | CompatibilityManager.CheckConflicts(game, conflicts); 8 | 9 | let conflictStr: String; 10 | for conflict in conflicts { 11 | conflictStr += "- " + conflict + "\n"; 12 | } 13 | 14 | let params = new inkTextParams(); 15 | params.AddString("conflicts", conflictStr); 16 | 17 | return GenericMessageNotification.Show( 18 | controller, 19 | GetLocalizedText("LocKey#11447"), 20 | GetLocalizedTextByKey(n"UI-EquipmentEx-NotificationConflicts"), 21 | params, 22 | GenericMessageNotificationType.OK 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scripts/UI/InventoryGridData.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class InventoryGridItemData extends VendorUIInventoryItemData { 4 | public let Parent: wref; 5 | public let IsVisible: Bool; 6 | } 7 | 8 | class InventoryGridSlotData extends VendorUIInventoryItemData { 9 | public let Children: array>; 10 | public let TotalItems: Int32; 11 | public let VisibleItems: Int32; 12 | public let IsCollapsed: Bool; 13 | 14 | protected func GetActiveItem() -> wref { 15 | for uiItem in this.Children { 16 | if uiItem.Item.IsEquipped() { 17 | return uiItem; 18 | } 19 | } 20 | 21 | return null; 22 | } 23 | } 24 | 25 | class InventoryGridDataView extends BackpackDataView { 26 | private let m_filter: Bool; 27 | private let m_refresh: Bool; 28 | private let m_reverse: Bool; 29 | private let m_searchQuery: String; 30 | private let m_viewManager: wref; 31 | 32 | public func SetViewManager(viewManager: wref) { 33 | this.m_viewManager = viewManager; 34 | } 35 | 36 | public func SetCollapsed(state: Bool) { 37 | this.m_viewManager.SetCollapsed(state); 38 | } 39 | 40 | public func ToggleCollapsed() { 41 | this.m_viewManager.ToggleCollapsed(); 42 | } 43 | 44 | public func ToggleCollapsed(slotID: TweakDBID) { 45 | this.m_viewManager.ToggleCollapsed(slotID); 46 | } 47 | 48 | public func SetSearchQuery(searchQuery: String) { 49 | this.m_searchQuery = UTF8StrLower(searchQuery); 50 | } 51 | 52 | public func UpdateView() { 53 | this.DisableSorting(); 54 | this.m_filter = true; 55 | this.Filter(); 56 | this.m_filter = false; 57 | this.Filter(); 58 | } 59 | 60 | public func FilterItem(data: ref) -> Bool { 61 | let uiItem = data as InventoryGridItemData; 62 | 63 | if IsDefined(uiItem) { 64 | if this.m_filter { 65 | uiItem.IsVisible = true; 66 | 67 | if Equals(this.m_itemFilterType, ItemFilterCategory.Clothes) { 68 | if !uiItem.Item.IsEquipped() { 69 | uiItem.IsVisible = false; 70 | } 71 | } 72 | 73 | if NotEquals(this.m_searchQuery, "") { 74 | let itemName = UTF8StrLower(GetLocalizedText(uiItem.Item.m_data.Name)); 75 | if !StrContains(itemName, this.m_searchQuery) { 76 | uiItem.IsVisible = false; 77 | } 78 | } 79 | } 80 | 81 | return uiItem.IsVisible && !uiItem.Parent.IsCollapsed; 82 | } 83 | 84 | let uiSlot = data as InventoryGridSlotData; 85 | 86 | if IsDefined(uiSlot) { 87 | if this.m_filter { 88 | uiSlot.IsCollapsed = this.m_viewManager.IsCollapsed(uiSlot.ItemData.SlotID); 89 | } else { 90 | uiSlot.TotalItems = ArraySize(uiSlot.Children); 91 | uiSlot.VisibleItems = 0; 92 | 93 | for uiChildData in uiSlot.Children { 94 | if uiChildData.IsVisible { 95 | uiSlot.VisibleItems += 1; 96 | } 97 | } 98 | } 99 | 100 | return uiSlot.VisibleItems > 0; 101 | } 102 | 103 | return false; 104 | } 105 | } 106 | 107 | class InventoryGridTemplateClassifier extends inkVirtualItemTemplateClassifier { 108 | public func ClassifyItem(data: Variant) -> Uint32 { 109 | return IsDefined(FromVariant>(data) as InventoryGridSlotData) ? 1u : 0u; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /scripts/UI/InventoryGridItem.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class InventoryGridItemController extends VendorItemVirtualController { 4 | protected cb func OnOutfitUpdated(evt: ref) { 5 | this.UpdateEquippedState(); 6 | } 7 | 8 | protected cb func OnOutfitPartUpdated(evt: ref) { 9 | this.UpdateEquippedState(); 10 | } 11 | 12 | protected func UpdateEquippedState() { 13 | this.m_itemViewController.NewUpdateEquipped(this.m_itemViewController.m_uiInventoryItem); 14 | this.m_itemViewController.NewUpdateLocked(this.m_itemViewController.m_uiInventoryItem); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/UI/InventoryGridSlot.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class InventoryGridSlotClick extends Event { 4 | public let slot: ref; 5 | public let action: ref; 6 | } 7 | 8 | class InventoryGridSlotHoverOver extends Event { 9 | public let slot: ref; 10 | } 11 | 12 | class InventoryGridSlotHoverOut extends Event { 13 | public let slot: ref; 14 | } 15 | 16 | class InventoryGridSlotController extends inkVirtualCompoundItemController { 17 | private let m_uiSlot: ref; 18 | 19 | private let m_root: wref; 20 | private let m_arrow: wref; 21 | private let m_slotName: wref; 22 | private let m_itemName: wref; 23 | private let m_itemCount: wref; 24 | 25 | private let m_isToggled: Bool; 26 | private let m_isHovered: Bool; 27 | 28 | protected cb func OnInitialize() { 29 | this.m_root = this.GetRootCompoundWidget(); 30 | 31 | let content = new inkVerticalPanel(); 32 | content.SetName(n"content"); 33 | content.SetHAlign(inkEHorizontalAlign.Left); 34 | content.SetVAlign(inkEVerticalAlign.Center); 35 | content.SetAnchor(inkEAnchor.CenterLeft); 36 | content.SetAnchorPoint(0.0, 0.5); 37 | content.SetMargin(new inkMargin(28.0, 0.0, 0.0, 4.0)); 38 | content.Reparent(this.m_root); 39 | 40 | let slotName = new inkText(); 41 | slotName.SetName(n"slot_name"); 42 | slotName.SetFontFamily("base\\gameplay\\gui\\fonts\\raj\\raj.inkfontfamily"); 43 | slotName.SetLetterCase(textLetterCase.UpperCase); 44 | slotName.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 45 | slotName.BindProperty(n"tintColor", n"MainColors.Red"); 46 | slotName.BindProperty(n"fontWeight", n"MainColors.BodyFontWeight"); 47 | slotName.BindProperty(n"fontSize", n"MainColors.ReadableFontSize"); 48 | slotName.SetFitToContent(true); 49 | slotName.Reparent(content); 50 | 51 | let itemName = new inkText(); 52 | itemName.SetName(n"item_name"); 53 | itemName.SetFontFamily("base\\gameplay\\gui\\fonts\\raj\\raj.inkfontfamily"); 54 | itemName.SetLetterCase(textLetterCase.UpperCase); 55 | itemName.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 56 | itemName.BindProperty(n"tintColor", n"MainColors.Blue"); 57 | itemName.BindProperty(n"fontSize", n"MainColors.ReadableXSmall"); 58 | itemName.SetFitToContent(true); 59 | itemName.Reparent(content); 60 | 61 | let itemCount = new inkText(); 62 | itemCount.SetName(n"item_count"); 63 | itemCount.SetFontFamily("base\\gameplay\\gui\\fonts\\raj\\raj.inkfontfamily"); 64 | itemCount.SetLetterCase(textLetterCase.UpperCase); 65 | itemCount.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 66 | itemCount.BindProperty(n"tintColor", n"MainColors.Grey"); 67 | itemCount.BindProperty(n"fontSize", n"MainColors.ReadableXSmall"); 68 | itemCount.SetFitToContent(true); 69 | itemCount.Reparent(content); 70 | 71 | let panel = new inkCanvas(); 72 | panel.SetName(n"panel"); 73 | panel.SetAnchor(inkEAnchor.Fill); 74 | panel.SetMargin(new inkMargin(0.0, 2.0, 0.0, 8.0)); 75 | panel.Reparent(this.m_root); 76 | 77 | let bg1 = new inkImage(); 78 | bg1.SetName(n"bg1"); 79 | bg1.SetAnchor(inkEAnchor.Fill); 80 | bg1.SetAnchorPoint(new Vector2(0.5, 0.5)); 81 | bg1.SetNineSliceScale(true); 82 | bg1.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 83 | bg1.SetTexturePart(n"item_bg"); 84 | bg1.SetStyle(r"base\\gameplay\\gui\\common\\components\\slots_style.inkstyle"); 85 | bg1.BindProperty(n"tintColor", n"ItemDisplay.background"); 86 | bg1.BindProperty(n"opacity", n"ItemDisplay.backgroundOpacity"); 87 | bg1.Reparent(panel); 88 | 89 | let bg2 = new inkImage(); 90 | bg2.SetName(n"bg2"); 91 | bg2.SetAnchor(inkEAnchor.Fill); 92 | bg2.SetAnchorPoint(new Vector2(0.5, 0.5)); 93 | bg2.SetNineSliceScale(true); 94 | bg2.SetNineSliceGrid(new inkMargin(0.0, 0.0, 20.0, 0.0)); 95 | bg2.SetAtlasResource(r"base\\gameplay\\gui\\fullscreen\\inventory\\atlas_inventory.inkatlas"); 96 | bg2.SetTexturePart(n"texture_2slot_iconic"); 97 | bg2.SetOpacity(0.03); 98 | bg2.SetStyle(r"base\\gameplay\\gui\\common\\components\\slots_style.inkstyle"); 99 | bg2.BindProperty(n"tintColor", n"ItemDisplay.emptyLinesColor"); 100 | //bg2.BindProperty(n"opacity", n"ItemDisplay.emptyLinesOpacity"); 101 | bg2.Reparent(panel); 102 | 103 | let fg = new inkImage(); 104 | fg.SetName(n"fg"); 105 | fg.SetAnchor(inkEAnchor.Fill); 106 | fg.SetAnchorPoint(new Vector2(0.5, 0.5)); 107 | fg.SetNineSliceScale(true); 108 | fg.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 109 | fg.SetTexturePart(n"item_fg"); 110 | fg.SetStyle(r"base\\gameplay\\gui\\common\\components\\slots_style.inkstyle"); 111 | fg.BindProperty(n"tintColor", n"ItemDisplay.borderColor"); 112 | fg.BindProperty(n"opacity", n"ItemDisplay.borderOpacity"); 113 | fg.Reparent(panel); 114 | 115 | let arrow = new inkImage(); 116 | arrow.SetName(n"arrow"); 117 | arrow.SetAnchor(inkEAnchor.CenterRight); 118 | arrow.SetAnchorPoint(new Vector2(1.0, 0.5)); 119 | arrow.SetMargin(new inkMargin(0.0, 0.0, 40.0, 0.0)); 120 | arrow.SetFitToContent(true); 121 | arrow.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 122 | arrow.SetTexturePart(n"arrow_right_bg"); 123 | arrow.SetStyle(r"base\\gameplay\\gui\\common\\components\\slots_style.inkstyle"); 124 | arrow.BindProperty(n"tintColor", n"ItemDisplay.borderColor"); 125 | arrow.BindProperty(n"opacity", n"ItemDisplay.borderOpacity"); 126 | arrow.Reparent(panel); 127 | 128 | this.m_arrow = arrow; 129 | this.m_slotName = slotName; 130 | this.m_itemName = itemName; 131 | this.m_itemCount = itemCount; 132 | 133 | this.RegisterToCallback(n"OnClick", this, n"OnClick"); 134 | this.RegisterToCallback(n"OnEnter", this, n"OnHoverOver"); 135 | this.RegisterToCallback(n"OnLeave", this, n"OnHoverOut"); 136 | } 137 | 138 | protected cb func OnDataChanged(value: Variant) { 139 | this.m_uiSlot = FromVariant>(value) as InventoryGridSlotData; 140 | 141 | this.UpdateSlotInfo(); 142 | this.UpdateActiveItem(); 143 | this.UpdateState(); 144 | } 145 | 146 | protected cb func OnOutfitUpdated(evt: ref) { 147 | this.UpdateActiveItem(); 148 | } 149 | 150 | protected cb func OnOutfitPartUpdated(evt: ref) { 151 | if Equals(this.m_uiSlot.ItemData.SlotID, evt.slotID) { 152 | this.UpdateActiveItem(); 153 | } 154 | } 155 | 156 | protected cb func OnClick(evt: ref) { 157 | this.TriggerClickEvent(evt.GetActionName()); 158 | } 159 | 160 | protected cb func OnHoverOver(evt: ref) { 161 | this.UpdateState(); 162 | this.TriggerHoverOverEvent(); 163 | } 164 | 165 | protected cb func OnHoverOut(evt: ref) { 166 | this.UpdateState(); 167 | this.TriggerHoverOutEvent(); 168 | } 169 | 170 | protected func UpdateSlotInfo() { 171 | this.m_slotName.SetText(this.m_uiSlot.ItemData.CategoryName); 172 | 173 | let itemCount = GetLocalizedText("LocKey#53719") + ": "; 174 | if this.m_uiSlot.TotalItems != this.m_uiSlot.VisibleItems { 175 | itemCount += ToString(this.m_uiSlot.VisibleItems) + " / "; 176 | } 177 | itemCount += ToString(this.m_uiSlot.TotalItems); 178 | 179 | this.m_itemCount.SetText(itemCount); 180 | } 181 | 182 | protected func UpdateActiveItem() { 183 | let uiItem = this.m_uiSlot.GetActiveItem(); 184 | 185 | if IsDefined(uiItem) { 186 | this.m_itemName.SetText(uiItem.Item.GetName()); 187 | this.m_itemName.BindProperty(n"tintColor", n"MainColors.Blue"); 188 | } else { 189 | this.m_itemName.SetText(GetLocalizedTextByKey(n"UI-Labels-EmptySlot")); 190 | this.m_itemName.BindProperty(n"tintColor", n"MainColors.Grey"); 191 | } 192 | } 193 | 194 | protected func UpdateState() { 195 | this.m_arrow.SetRotation(this.m_uiSlot.IsCollapsed ? 0.0 : 90.0); 196 | } 197 | 198 | protected func TriggerClickEvent(action: ref) { 199 | let evt = new InventoryGridSlotClick(); 200 | evt.slot = this.m_uiSlot; 201 | evt.action = action; 202 | 203 | this.QueueEvent(evt); 204 | } 205 | 206 | protected func TriggerHoverOverEvent() { 207 | let evt = new InventoryGridSlotHoverOver(); 208 | evt.slot = this.m_uiSlot; 209 | 210 | this.QueueEvent(evt); 211 | } 212 | 213 | protected func TriggerHoverOutEvent() { 214 | let evt = new InventoryGridSlotHoverOut(); 215 | evt.slot = this.m_uiSlot; 216 | 217 | this.QueueEvent(evt); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /scripts/UI/ItemSourceOption.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class ItemSourceOptionChange extends Event { 4 | public let value: WardrobeItemSource; 5 | } 6 | 7 | class ItemSourceOptionController extends inkButtonController { 8 | private let m_value: WardrobeItemSource; 9 | 10 | private let m_root: wref; 11 | private let m_label: wref; 12 | private let m_checkbox: wref; 13 | private let m_selection: wref; 14 | 15 | private let m_disabled: Bool; 16 | private let m_hovered: Bool; 17 | private let m_selected: Bool; 18 | 19 | protected cb func OnInitialize() { 20 | this.m_root = this.GetRootCompoundWidget(); 21 | 22 | this.m_label = this.GetChildWidgetByPath(n"titleAndCheckbox/FilterName") as inkText; 23 | this.m_label.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 24 | 25 | this.m_checkbox = this.GetChildWidgetByPath(n"titleAndCheckbox/checkbox"); 26 | this.m_selection = this.GetChildWidgetByPath(n"titleAndCheckbox/checkbox/checkbox"); 27 | 28 | this.RegisterToCallback(n"OnRelease", this, n"OnRelease"); 29 | this.RegisterToCallback(n"OnEnter", this, n"OnHoverOver"); 30 | this.RegisterToCallback(n"OnLeave", this, n"OnHoverOut"); 31 | } 32 | 33 | protected cb func OnRelease(evt: ref) { 34 | if evt.IsAction(n"click") && !this.m_disabled && !this.m_selected { 35 | this.TriggerChangeEvent(); 36 | } 37 | } 38 | 39 | protected cb func OnHoverOver(evt: ref) { 40 | if !this.m_disabled { 41 | this.m_hovered = true; 42 | 43 | this.UpdateState(); 44 | // this.TriggerHoverOverEvent(); 45 | } 46 | } 47 | 48 | protected cb func OnHoverOut(evt: ref) { 49 | if !this.m_disabled { 50 | this.m_hovered = false; 51 | 52 | this.UpdateState(); 53 | // this.TriggerHoverOutEvent(); 54 | } 55 | } 56 | 57 | protected cb func OnOptionChange(evt: ref) { 58 | this.m_selected = Equals(this.m_value, evt.value); 59 | this.UpdateState(); 60 | } 61 | 62 | protected func UpdateView() { 63 | this.m_root.SetSize(new Vector2(this.m_label.GetDesiredWidth() + 170.0, 80.0)); 64 | 65 | this.m_label.SetText(GetLocalizedText("UI-EquipmentEx-WardrobeItemSource-" + ToString(this.m_value))); 66 | this.m_label.BindProperty(n"fontStyle", n"MainColors.BodyFontWeight"); 67 | this.m_label.BindProperty(n"fontSize", n"MainColors.ReadableSmall"); 68 | 69 | this.m_checkbox.SetVisible(true); 70 | this.m_label.SetMargin(new inkMargin(20, 0, 0, 0)); 71 | 72 | this.GetChildWidgetByPath(n"titleAndCheckbox").SetMargin(new inkMargin(0, 0, 0, 0)); 73 | this.GetChildWidgetByPath(n"background").SetVisible(false); 74 | } 75 | 76 | protected func UpdateState() { 77 | this.m_selection.SetVisible(this.m_selected); 78 | 79 | if this.m_disabled { 80 | this.m_root.SetState(n"Default"); 81 | this.m_root.SetOpacity(0.3); 82 | } else { 83 | this.m_root.SetOpacity(1.0); 84 | 85 | if this.m_hovered { 86 | this.m_root.SetState(n"Hover"); 87 | } 88 | else { 89 | if this.m_selected { 90 | this.m_root.SetState(n"Selected"); 91 | } 92 | else { 93 | this.m_root.SetState(n"Default"); 94 | } 95 | } 96 | 97 | this.m_label.BindProperty(n"tintColor", this.m_selected ? n"MainColors.Blue" : n"MainColors.Red"); 98 | } 99 | } 100 | 101 | protected func TriggerChangeEvent() { 102 | let evt = new ItemSourceOptionChange(); 103 | evt.value = this.m_value; 104 | 105 | this.QueueEvent(evt); 106 | } 107 | 108 | public func SetData(value: WardrobeItemSource, selected: Bool) { 109 | this.m_value = value; 110 | this.m_selected = selected; 111 | 112 | this.UpdateView(); 113 | this.UpdateState(); 114 | } 115 | 116 | public func GetValue() -> WardrobeItemSource { 117 | return this.m_value; 118 | } 119 | 120 | public func IsSelected() -> Bool { 121 | return this.m_selected; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /scripts/UI/OutfitListData.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | enum OutfitListAction { 4 | Equip = 0, 5 | Unequip = 1, 6 | Save = 2 7 | } 8 | 9 | class OutfitListEntryData { 10 | public let Name: CName; 11 | public let Title: String; 12 | public let Color: CName; 13 | public let Action: OutfitListAction; 14 | public let Postition: Int32 = 2147483647; 15 | public let IsRemovable: Bool; 16 | public let IsSelectable: Bool; 17 | public let IsSelected: Bool; 18 | } 19 | 20 | class OutfitListDataView extends ScriptableDataView { 21 | public func UpdateView() { 22 | this.EnableSorting(); 23 | this.Sort(); 24 | this.DisableSorting(); 25 | } 26 | 27 | public func SortItem(left: ref, right: ref) -> Bool { 28 | let leftEntry = left as OutfitListEntryData; 29 | let rightEntry = right as OutfitListEntryData; 30 | 31 | if leftEntry.Postition != rightEntry.Postition { 32 | return leftEntry.Postition < rightEntry.Postition; 33 | } 34 | 35 | return StrCmp(leftEntry.Title, rightEntry.Title) < 0; 36 | } 37 | } 38 | 39 | class OutfitListTemplateClassifier extends inkVirtualItemTemplateClassifier { 40 | // public func ClassifyItem(data: Variant) -> Uint32 { 41 | // return 0u; 42 | // } 43 | } 44 | -------------------------------------------------------------------------------- /scripts/UI/OutfitListEntry.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class OutfitListRefresh extends Event {} 4 | 5 | class OutfitListEntryClick extends Event { 6 | public let entry: ref; 7 | public let action: ref; 8 | } 9 | 10 | class OutfitListEntryHoverOver extends Event { 11 | public let entry: ref; 12 | } 13 | 14 | class OutfitListEntryHoverOut extends Event { 15 | public let entry: ref; 16 | } 17 | 18 | class OutfitListEntryController extends inkVirtualCompoundItemController { 19 | private let m_data: ref; 20 | 21 | private let m_root: wref; 22 | private let m_label: wref; 23 | private let m_checkbox: wref; 24 | private let m_selection: wref; 25 | 26 | private let m_isDisabled: Bool; 27 | private let m_isHovered: Bool; 28 | 29 | protected cb func OnInitialize() { 30 | this.m_root = this.GetRootCompoundWidget(); 31 | 32 | this.m_label = this.GetChildWidgetByPath(n"titleAndCheckbox/FilterName") as inkText; 33 | this.m_label.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 34 | 35 | this.m_checkbox = this.GetChildWidgetByPath(n"titleAndCheckbox/checkbox"); 36 | this.m_selection = this.GetChildWidgetByPath(n"titleAndCheckbox/checkbox/checkbox"); 37 | 38 | this.RegisterToCallback(n"OnRelease", this, n"OnRelease"); 39 | this.RegisterToCallback(n"OnEnter", this, n"OnHoverOver"); 40 | this.RegisterToCallback(n"OnLeave", this, n"OnHoverOut"); 41 | } 42 | 43 | protected cb func OnDataChanged(value: Variant) { 44 | this.m_data = FromVariant>(value) as OutfitListEntryData; 45 | 46 | if IsDefined(this.m_data) { 47 | this.UpdateView(); 48 | this.UpdateState(); 49 | } 50 | } 51 | 52 | protected cb func OnRefresh(evt: ref) { 53 | if IsDefined(this.m_data) { 54 | this.UpdateView(); 55 | this.UpdateState(); 56 | } 57 | } 58 | 59 | protected cb func OnRelease(evt: ref) { 60 | if !this.m_isDisabled { 61 | this.TriggerClickEvent(evt.GetActionName()); 62 | } 63 | } 64 | 65 | protected cb func OnHoverOver(evt: ref) { 66 | if !this.m_isDisabled { 67 | this.m_isHovered = true; 68 | 69 | this.UpdateState(); 70 | this.TriggerHoverOverEvent(); 71 | } 72 | } 73 | 74 | protected cb func OnHoverOut(evt: ref) { 75 | if !this.m_isDisabled { 76 | this.m_isHovered = false; 77 | 78 | this.UpdateState(); 79 | this.TriggerHoverOutEvent(); 80 | } 81 | } 82 | 83 | protected func UpdateView() { 84 | this.m_label.SetText(this.m_data.Title); 85 | 86 | if NotEquals(this.m_data.Color, n"") { 87 | this.m_label.BindProperty(n"tintColor", this.m_data.Color); 88 | } else { 89 | this.m_label.BindProperty(n"tintColor", n"MainColors.Red"); 90 | } 91 | 92 | if this.m_data.IsSelectable { 93 | this.m_checkbox.SetVisible(true); 94 | this.m_selection.SetVisible(this.m_data.IsSelected); 95 | this.m_label.SetMargin(new inkMargin(30.0, 0.0, 0.0, 0.0)); 96 | } else { 97 | this.m_checkbox.SetVisible(false); 98 | this.m_label.SetMargin(new inkMargin(10.0, 0.0, 0.0, 0.0)); 99 | } 100 | } 101 | 102 | protected func UpdateState() { 103 | if this.m_isDisabled { 104 | this.m_root.SetState(n"Default"); 105 | this.m_root.SetOpacity(0.3); 106 | } else { 107 | this.m_root.SetOpacity(1.0); 108 | 109 | if this.m_isHovered { 110 | this.m_root.SetState(n"Hover"); 111 | } 112 | else { 113 | if this.m_data.IsSelectable && this.m_data.IsSelected { 114 | this.m_root.SetState(n"Selected"); 115 | } 116 | else { 117 | this.m_root.SetState(n"Default"); 118 | } 119 | } 120 | } 121 | } 122 | 123 | protected func TriggerClickEvent(action: ref) { 124 | let evt = new OutfitListEntryClick(); 125 | evt.entry = this.m_data; 126 | evt.action = action; 127 | 128 | this.QueueEvent(evt); 129 | } 130 | 131 | protected func TriggerHoverOverEvent() { 132 | let evt = new OutfitListEntryHoverOver(); 133 | evt.entry = this.m_data; 134 | 135 | this.QueueEvent(evt); 136 | } 137 | 138 | protected func TriggerHoverOutEvent() { 139 | let evt = new OutfitListEntryHoverOut(); 140 | evt.entry = this.m_data; 141 | 142 | this.QueueEvent(evt); 143 | } 144 | 145 | // public final func PlayIntroAnimation(delay: Float) { 146 | // let animOptions: inkAnimOptions; 147 | // animOptions.executionDelay = delay; 148 | // this.PlayLibraryAnimation(n"OnFiltersListItem", animOptions); 149 | // } 150 | } 151 | -------------------------------------------------------------------------------- /scripts/UI/OutfitManager.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class OutfitManagerController extends inkLogicController { 4 | protected let m_player: wref; 5 | protected let m_outfitSystem: wref; 6 | 7 | protected let m_wardrobeScreen: wref; 8 | protected let m_buttonHints: wref; 9 | 10 | protected let m_outfitList: ref; 11 | protected let m_outfitListDataView: ref; 12 | protected let m_outfitListDataSource: ref; 13 | protected let m_outfitListTemplateClassifier: ref; 14 | protected let m_outfitListScroll: wref; 15 | 16 | protected let m_popupToken: ref; 17 | protected let m_popupOutfit: CName; 18 | 19 | protected let m_enabled: Bool; 20 | 21 | protected cb func OnInitialize() -> Bool { 22 | this.InitializeLayout(); 23 | this.InitializeList(); 24 | } 25 | 26 | public func Setup(outfitSystem: wref, wardrobeScreen: wref, buttonHints: wref) { 27 | this.m_outfitSystem = outfitSystem; 28 | this.m_wardrobeScreen = wardrobeScreen; 29 | this.m_buttonHints = buttonHints; 30 | 31 | this.PopulateList(); 32 | this.SetEnabled(true); 33 | } 34 | 35 | public func SetEnabled(enabled: Bool) { 36 | this.m_enabled = enabled; 37 | 38 | let widget = this.GetRootWidget(); 39 | widget.SetInteractive(this.m_enabled); 40 | widget.SetOpacity(this.m_enabled ? 1.0 : 0.6); 41 | } 42 | 43 | protected func InitializeLayout() { 44 | this.m_outfitListScroll = this.GetChildWidgetByPath(n"scroll_wrapper").GetControllerByType(n"inkScrollController") as inkScrollController; 45 | 46 | let scrollArea = this.GetChildWidgetByPath(n"scroll_wrapper/scroll_area"); 47 | scrollArea.RegisterToCallback(n"OnScrollChanged", this, n"OnScrollChanged"); 48 | 49 | let header = new inkVerticalPanel(); 50 | header.SetName(n"header"); 51 | header.SetChildMargin(new inkMargin(130.0, 0.0, 20.0, 0.0)); 52 | header.Reparent(this.GetRootCompoundWidget()); 53 | 54 | let title = new inkText(); 55 | title = new inkText(); 56 | title.SetName(n"title"); 57 | title.SetText("LocKey#82878"); 58 | title.SetFontFamily("base\\gameplay\\gui\\fonts\\raj\\raj.inkfontfamily"); 59 | title.SetLetterCase(textLetterCase.UpperCase); 60 | title.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 61 | title.BindProperty(n"tintColor", n"MainColors.Red"); 62 | title.BindProperty(n"fontStyle", n"MainColors.BodyFontWeight"); 63 | title.BindProperty(n"fontSize", n"MainColors.ReadableFontSize"); 64 | title.SetAnchor(inkEAnchor.TopLeft); 65 | title.SetMargin(new inkMargin(0.0, 0.0, 0.0, 4.0)); 66 | title.Reparent(header); 67 | 68 | let divider = new inkRectangle(); 69 | divider.SetName(n"divider"); 70 | divider.SetMargin(new inkMargin(0.0, 0.0, 0.0, 15.0)); 71 | divider.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 72 | divider.BindProperty(n"tintColor", n"MainColors.Red"); 73 | divider.SetOpacity(0.3); 74 | divider.SetSize(800.0, 3.0); 75 | divider.Reparent(header); 76 | } 77 | 78 | protected func InitializeList() { 79 | this.m_outfitListDataSource = new ScriptableDataSource(); 80 | this.m_outfitListDataView = new OutfitListDataView(); 81 | this.m_outfitListDataView.SetSource(this.m_outfitListDataSource); 82 | this.m_outfitListTemplateClassifier = new OutfitListTemplateClassifier(); 83 | 84 | this.m_outfitList = this.GetChildWidgetByPath(n"scroll_wrapper/scroll_area/outfit_list").GetController() as inkVirtualListController; 85 | this.m_outfitList.SetClassifier(this.m_outfitListTemplateClassifier); 86 | this.m_outfitList.SetSource(this.m_outfitListDataView); 87 | } 88 | 89 | protected func PopulateList() { 90 | let saveAction = new OutfitListEntryData(); 91 | saveAction.Title = GetLocalizedTextByKey(n"UI-Wardrobe-SaveSet"); 92 | saveAction.Color = n"MainColors.ActiveBlue"; 93 | saveAction.Action = OutfitListAction.Save; 94 | saveAction.Postition = 1; 95 | 96 | let unequipAction = new OutfitListEntryData(); 97 | unequipAction.Title = GetLocalizedTextByKey(n"UI-Wardrobe-NoOutfit"); 98 | unequipAction.Action = OutfitListAction.Unequip; 99 | unequipAction.IsSelectable = true; 100 | unequipAction.IsSelected = !this.m_outfitSystem.IsActive(); 101 | unequipAction.Postition = 2; 102 | 103 | this.m_outfitListDataSource.Clear(); 104 | this.m_outfitListDataSource.AppendItem(saveAction); 105 | this.m_outfitListDataSource.AppendItem(unequipAction); 106 | 107 | for outfitName in this.m_outfitSystem.GetOutfits() { 108 | this.AppendToList(outfitName, false); 109 | } 110 | 111 | this.m_outfitListDataView.UpdateView(); 112 | } 113 | 114 | protected func AppendToList(outfitName: CName, opt updateView: Bool) { 115 | let outfitEntry = new OutfitListEntryData(); 116 | outfitEntry.Name = outfitName; 117 | outfitEntry.Title = NameToString(outfitName); 118 | outfitEntry.IsRemovable = true; 119 | outfitEntry.IsSelectable = true; 120 | outfitEntry.IsSelected = this.m_outfitSystem.IsEquipped(outfitEntry.Name); 121 | 122 | this.m_outfitListDataSource.AppendItem(outfitEntry); 123 | 124 | if updateView { 125 | this.m_outfitListDataView.UpdateView(); 126 | } 127 | } 128 | 129 | protected func RemoveFromList(outfitName: CName, opt updateView: Bool) { 130 | for data in this.m_outfitListDataSource.GetArray() { 131 | let outfitEntry = data as OutfitListEntryData; 132 | if Equals(outfitEntry.Name, outfitName) { 133 | this.m_outfitListDataSource.RemoveItem(outfitEntry); 134 | 135 | if updateView { 136 | this.m_outfitListDataView.UpdateView(); 137 | } 138 | 139 | break; 140 | } 141 | } 142 | } 143 | 144 | protected func RefreshList(opt updateState: Bool) { 145 | if updateState { 146 | for data in this.m_outfitListDataSource.GetArray() { 147 | let outfitEntry = data as OutfitListEntryData; 148 | if outfitEntry.IsSelectable { 149 | outfitEntry.IsSelected = this.m_outfitSystem.IsEquipped(outfitEntry.Name); 150 | } 151 | } 152 | } 153 | 154 | this.QueueEvent(new OutfitListRefresh()); 155 | } 156 | 157 | protected cb func OnOutfitListEntryClick(evt: ref) { 158 | if !this.m_enabled { 159 | return; 160 | } 161 | 162 | if evt.action.IsAction(n"click") && this.AccessOutfitSystem() { 163 | this.PlaySound(n"Button", n"OnPress"); 164 | 165 | switch evt.entry.Action { 166 | case OutfitListAction.Equip: 167 | this.m_outfitSystem.LoadOutfit(evt.entry.Name); 168 | break; 169 | case OutfitListAction.Unequip: 170 | this.m_outfitSystem.Deactivate(); 171 | break; 172 | case OutfitListAction.Save: 173 | this.ShowSaveOutfitPopup(); 174 | break; 175 | } 176 | 177 | this.ShowButtonHints(evt.entry); 178 | return; 179 | } 180 | 181 | if evt.action.IsAction(n"drop_item") && Equals(evt.entry.Action, OutfitListAction.Equip) && this.AccessOutfitSystem() { 182 | this.ShowDeleteOutfitPopup(evt.entry.Name); 183 | } 184 | } 185 | 186 | protected cb func OnOutfitListEntryItemHoverOver(evt: ref) { 187 | this.ShowButtonHints(evt.entry); 188 | } 189 | 190 | protected cb func OnOutfitListEntryItemHoverOut(evt: ref) { 191 | this.ShowButtonHints(null); 192 | } 193 | 194 | protected cb func ShowSaveOutfitPopup() { 195 | this.m_popupToken = GenericMessageNotification.ShowInput(this.m_wardrobeScreen, GetLocalizedTextByKey(n"UI-Wardrobe-SaveSet"), GetLocalizedTextByKey(n"UI-Wardrobe-NotificationSaveSet"), GenericMessageNotificationType.ConfirmCancel); 196 | this.m_popupToken.RegisterListener(this, n"OnSaveOutfitPopupClosed"); 197 | } 198 | 199 | protected cb func OnSaveOutfitPopupClosed(data: ref) { 200 | let resultData = data as GenericMessageNotificationCloseData; 201 | 202 | if Equals(resultData.result, GenericMessageNotificationResult.Confirm) && NotEquals(resultData.input, "") { 203 | let outfitName = StringToName(resultData.input); 204 | 205 | if this.m_outfitSystem.HasOutfit(outfitName) { 206 | this.ShowReplaceOutfitPopup(outfitName); 207 | return; 208 | } 209 | 210 | this.PlaySound(n"Item", n"OnBuy"); 211 | 212 | if this.m_outfitSystem.SaveOutfit(outfitName, true) { 213 | this.AppendToList(outfitName, true); 214 | } 215 | } 216 | 217 | this.ResetPopupState(); 218 | } 219 | 220 | protected cb func ShowReplaceOutfitPopup(outfitName: CName) { 221 | this.m_popupOutfit = outfitName; 222 | this.m_popupToken = GenericMessageNotification.Show(this.m_wardrobeScreen, GetLocalizedTextByKey(n"UI-Wardrobe-SaveSet"), GetLocalizedTextByKey(n"UI-Wardrobe-NotificationReplaceSet"), GenericMessageNotificationType.ConfirmCancel); 223 | this.m_popupToken.RegisterListener(this, n"OnReplaceOutfitPopupClosed"); 224 | } 225 | 226 | protected cb func OnReplaceOutfitPopupClosed(data: ref) { 227 | let resultData = data as GenericMessageNotificationCloseData; 228 | 229 | if Equals(resultData.result, GenericMessageNotificationResult.Confirm) { 230 | this.PlaySound(n"Item", n"OnBuy"); 231 | 232 | if this.m_outfitSystem.SaveOutfit(this.m_popupOutfit, true) { 233 | this.RefreshList(true); 234 | } 235 | } 236 | 237 | this.ResetPopupState(); 238 | } 239 | 240 | protected cb func ShowDeleteOutfitPopup(outfitName: CName) { 241 | this.m_popupOutfit = outfitName; 242 | this.m_popupToken = GenericMessageNotification.Show(this.m_wardrobeScreen, GetLocalizedTextByKey(n"UI-Wardrobe-Deleteset"), GetLocalizedTextByKey(n"UI-Wardrobe-NotificationDeleteSet"), GenericMessageNotificationType.ConfirmCancel); 243 | this.m_popupToken.RegisterListener(this, n"OnDeleteOutfitPopupClosed"); 244 | } 245 | 246 | protected cb func OnDeleteOutfitPopupClosed(data: ref) { 247 | let resultData = data as GenericMessageNotificationCloseData; 248 | 249 | if Equals(resultData.result, GenericMessageNotificationResult.Confirm) { 250 | this.PlaySound(n"Item", n"OnDisassemble"); 251 | 252 | if this.m_outfitSystem.DeleteOutfit(this.m_popupOutfit) { 253 | this.RemoveFromList(this.m_popupOutfit, true); 254 | } 255 | } 256 | 257 | this.ResetPopupState(); 258 | } 259 | 260 | protected func ResetPopupState() { 261 | this.m_popupOutfit = n""; 262 | this.m_popupToken = null; 263 | } 264 | 265 | protected func ShowButtonHints(entry: wref) { 266 | this.m_buttonHints.RemoveButtonHint(n"click"); 267 | this.m_buttonHints.RemoveButtonHint(n"drop_item"); 268 | 269 | if IsDefined(entry) { 270 | if entry.IsRemovable { 271 | this.m_buttonHints.AddButtonHint(n"drop_item", GetLocalizedTextByKey(n"UI-Wardrobe-Deleteset")); 272 | } 273 | 274 | if entry.IsSelectable && !entry.IsSelected { 275 | this.m_buttonHints.AddButtonHint(n"click", GetLocalizedTextByKey(n"Gameplay-Devices-Interactions-Equip")); 276 | } 277 | } 278 | } 279 | 280 | protected cb func OnOutfitUpdated(evt: ref) { 281 | this.RefreshList(true); 282 | } 283 | 284 | protected cb func OnOutfitPartUpdated(evt: ref) { 285 | this.RefreshList(true); 286 | } 287 | 288 | protected cb func OnOutfitListUpdated(evt: ref) { 289 | this.PopulateList(); 290 | } 291 | 292 | protected cb func OnScrollChanged(value: Vector2) { 293 | this.RefreshList(); 294 | } 295 | 296 | protected func AccessOutfitSystem() -> Bool { 297 | if this.m_outfitSystem.IsBlocked() { 298 | let notification = new UIMenuNotificationEvent(); 299 | notification.m_notificationType = UIMenuNotificationType.InventoryActionBlocked; 300 | this.QueueEvent(notification); 301 | 302 | return false; 303 | } 304 | 305 | return true; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /scripts/UI/OutfitMappingPopup.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | import Codeware.UI.* 3 | 4 | public class OutfitMappingPopup extends InMenuPopup { 5 | private let m_itemID: ItemID; 6 | private let m_slotID: TweakDBID; 7 | private let m_system: ref; 8 | private let m_options: array>; 9 | private let m_arranged: Bool; 10 | 11 | protected cb func OnCreate() { 12 | super.OnCreate(); 13 | 14 | let content = InMenuPopupContent.Create(); 15 | content.SetTitle(this.m_system.GetItemName(this.m_itemID)); 16 | content.Reparent(this); 17 | 18 | let panel = new inkHorizontalPanel(); 19 | panel.SetMargin(new inkMargin(0, 24, 0, 0)); 20 | panel.Reparent(content.GetContainerWidget()); 21 | 22 | let outfitSlots = OutfitConfig.OutfitSlots(); 23 | 24 | let schema = [ 25 | [n"Head", n"Face", n"Ears", n"Neck"], 26 | [n"Torso", n"Back", n"Waist", n"Body"], 27 | [n"Arms", n"Hands", n"Fingers"], 28 | [n"Legs", n"Feet", n"Toes"] 29 | ]; 30 | 31 | for areas in schema { 32 | let column = new inkVerticalPanel(); 33 | column.Reparent(panel); 34 | 35 | for area in areas { 36 | for outfitSlot in outfitSlots { 37 | if Equals(outfitSlot.slotArea, area) { 38 | let option = this.SpawnOption(column); 39 | option.SetData(outfitSlot, Equals(this.m_slotID, outfitSlot.slotID)); 40 | 41 | ArrayPush(this.m_options, option); 42 | } 43 | } 44 | 45 | if NotEquals(area, ArrayLast(areas)) { 46 | let divider = new inkCanvas(); 47 | divider.SetMargin(0, 0, 0, 40); 48 | divider.Reparent(column); 49 | } 50 | } 51 | } 52 | 53 | let footer = InMenuPopupFooter.Create(); 54 | footer.Reparent(this); 55 | 56 | let confirmBtn = PopupButton.Create(); 57 | confirmBtn.SetText(GetLocalizedTextByKey(n"UI-UserActions-Equip")); // GetLocalizedText("LocKey#23123") 58 | confirmBtn.SetInputAction(n"one_click_confirm"); 59 | confirmBtn.Reparent(footer); 60 | 61 | let cancelBtn = PopupButton.Create(); 62 | cancelBtn.SetText(GetLocalizedText("LocKey#22175")); 63 | cancelBtn.SetInputAction(n"cancel"); 64 | cancelBtn.Reparent(footer); 65 | } 66 | 67 | protected func SpawnOption(parent: ref) -> ref { 68 | let widget = this.SpawnFromExternal(parent, r"equipment_ex\\gui\\wardrobe.inkwidget", 69 | n"OutfitListEntry:EquipmentEx.OutfitSlotOptionController"); 70 | return widget.GetController() as OutfitSlotOptionController; 71 | } 72 | 73 | protected cb func OnArrangeChildrenComplete() { 74 | if !this.m_arranged { 75 | for option in this.m_options { 76 | option.UpdateView(); 77 | } 78 | 79 | this.m_arranged = true; 80 | } 81 | } 82 | 83 | protected cb func OnChange(evt: ref) { 84 | this.m_slotID = evt.slotID; 85 | } 86 | 87 | protected cb func OnConfirm() { 88 | this.m_system.AssignItem(this.m_itemID, this.m_slotID); 89 | 90 | if !this.m_system.IsEquipped(this.m_itemID) { 91 | this.m_system.EquipItem(this.m_itemID); 92 | } 93 | } 94 | 95 | public static func Show(requester: ref, itemID: ItemID, system: ref) { 96 | let popup = new OutfitMappingPopup(); 97 | popup.m_itemID = itemID; 98 | popup.m_slotID = system.GetItemSlot(itemID); 99 | popup.m_system = system; 100 | popup.Open(requester); 101 | } 102 | 103 | func OnCancel() {} 104 | func OnShown() {} 105 | func OnReparent(parent: ref) {} 106 | } 107 | -------------------------------------------------------------------------------- /scripts/UI/OutfitSlotOption.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | class OutfitSlotOptionChange extends Event { 4 | public let slotID: TweakDBID; 5 | } 6 | 7 | class OutfitSlotOptionController extends inkButtonController { 8 | private let m_data: ExtraSlotConfig; 9 | 10 | private let m_root: wref; 11 | private let m_label: wref; 12 | private let m_checkbox: wref; 13 | private let m_selection: wref; 14 | 15 | private let m_disabled: Bool; 16 | private let m_hovered: Bool; 17 | private let m_selected: Bool; 18 | 19 | protected cb func OnInitialize() { 20 | this.m_root = this.GetRootCompoundWidget(); 21 | 22 | this.m_label = this.GetChildWidgetByPath(n"titleAndCheckbox/FilterName") as inkText; 23 | this.m_label.SetStyle(r"base\\gameplay\\gui\\common\\main_colors.inkstyle"); 24 | 25 | this.m_checkbox = this.GetChildWidgetByPath(n"titleAndCheckbox/checkbox"); 26 | this.m_selection = this.GetChildWidgetByPath(n"titleAndCheckbox/checkbox/checkbox"); 27 | 28 | this.RegisterToCallback(n"OnRelease", this, n"OnRelease"); 29 | this.RegisterToCallback(n"OnEnter", this, n"OnHoverOver"); 30 | this.RegisterToCallback(n"OnLeave", this, n"OnHoverOut"); 31 | } 32 | 33 | protected cb func OnRelease(evt: ref) { 34 | if evt.IsAction(n"click") && !this.m_disabled && !this.m_selected { 35 | this.TriggerChangeEvent(); 36 | } 37 | } 38 | 39 | protected cb func OnHoverOver(evt: ref) { 40 | if !this.m_disabled { 41 | this.m_hovered = true; 42 | 43 | this.UpdateState(); 44 | // this.TriggerHoverOverEvent(); 45 | } 46 | } 47 | 48 | protected cb func OnHoverOut(evt: ref) { 49 | if !this.m_disabled { 50 | this.m_hovered = false; 51 | 52 | this.UpdateState(); 53 | // this.TriggerHoverOutEvent(); 54 | } 55 | } 56 | 57 | protected cb func OnOptionChange(evt: ref) { 58 | this.m_selected = Equals(this.m_data.slotID, evt.slotID); 59 | this.UpdateState(); 60 | } 61 | 62 | protected func UpdateView() { 63 | this.m_root.SetSize(new Vector2(this.m_label.GetDesiredWidth() + 170.0, 80.0)); 64 | 65 | this.m_label.SetText(GetLocalizedText(this.m_data.displayName)); 66 | this.m_label.BindProperty(n"fontStyle", n"MainColors.BodyFontWeight"); 67 | this.m_label.BindProperty(n"fontSize", n"MainColors.ReadableSmall"); 68 | 69 | this.m_checkbox.SetVisible(true); 70 | this.m_label.SetMargin(new inkMargin(20, 0, 0, 0)); 71 | 72 | this.GetChildWidgetByPath(n"titleAndCheckbox").SetMargin(new inkMargin(0, 0, 0, 0)); 73 | this.GetChildWidgetByPath(n"background").SetVisible(false); 74 | } 75 | 76 | protected func UpdateState() { 77 | this.m_selection.SetVisible(this.m_selected); 78 | 79 | if this.m_disabled { 80 | this.m_root.SetState(n"Default"); 81 | this.m_root.SetOpacity(0.3); 82 | } else { 83 | this.m_root.SetOpacity(1.0); 84 | 85 | if this.m_hovered { 86 | this.m_root.SetState(n"Hover"); 87 | } 88 | else { 89 | if this.m_selected { 90 | this.m_root.SetState(n"Selected"); 91 | } 92 | else { 93 | this.m_root.SetState(n"Default"); 94 | } 95 | } 96 | 97 | this.m_label.BindProperty(n"tintColor", this.m_selected ? n"MainColors.Blue" : n"MainColors.Red"); 98 | } 99 | } 100 | 101 | protected func TriggerChangeEvent() { 102 | let evt = new OutfitSlotOptionChange(); 103 | evt.slotID = this.m_data.slotID; 104 | 105 | this.QueueEvent(evt); 106 | } 107 | 108 | public func SetData(data: ExtraSlotConfig, selected: Bool) { 109 | this.m_data = data; 110 | this.m_selected = selected; 111 | 112 | this.UpdateView(); 113 | this.UpdateState(); 114 | } 115 | 116 | public func GetSlotID() -> TweakDBID { 117 | return this.m_data.slotID; 118 | } 119 | 120 | public func IsSelected() -> Bool { 121 | return this.m_selected; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /scripts/UI/RequirementsPopup.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class RequirementsPopup { 4 | public static func Show(controller: ref) -> ref { 5 | let params = new inkTextParams(); 6 | 7 | params.AddString("archive_xl_req", CompatibilityManager.RequiredArchiveXL()); 8 | params.AddString("tweak_xl_req", CompatibilityManager.RequiredTweakXL()); 9 | params.AddString("codeware_req", CompatibilityManager.RequiredCodeware()); 10 | 11 | params.AddString("archive_xl_ver", ArchiveXL.Version()); 12 | params.AddString("tweak_xl_ver", TweakXL.Version()); 13 | params.AddString("codeware_ver", Codeware.Version()); 14 | 15 | return GenericMessageNotification.Show( 16 | controller, 17 | GetLocalizedText("LocKey#11447"), 18 | GetLocalizedTextByKey(n"UI-EquipmentEx-NotificationRequirements"), 19 | params, 20 | GenericMessageNotificationType.OK 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scripts/UI/SettingsButton.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | import Codeware.UI.* 3 | 4 | class SettingsButtonClick extends Event { 5 | public let action: ref; 6 | } 7 | 8 | class SettingsButton extends inkCustomController { 9 | protected let m_frame: wref; 10 | protected let m_icon: wref; 11 | 12 | protected cb func OnCreate() { 13 | let root = new inkCanvas(); 14 | root.SetSize(110.0, 80.0); 15 | root.SetAnchorPoint(new Vector2(0.5, 0.5)); 16 | root.SetInteractive(true); 17 | 18 | let frame = new inkImage(); 19 | frame.SetName(n"frame"); 20 | frame.SetAnchor(inkEAnchor.Fill); 21 | frame.SetNineSliceScale(true); 22 | frame.SetAtlasResource(r"base\\gameplay\\gui\\common\\shapes\\atlas_shapes_sync.inkatlas"); 23 | frame.SetTexturePart(n"status_cell_fg"); 24 | //frame.SetBrushMirrorType(inkBrushMirrorType.Horizontal); 25 | frame.SetStyle(r"base\\gameplay\\gui\\common\\components\\toggles_style.inkstyle"); 26 | frame.BindProperty(n"tintColor", n"FilterButton.frameColor"); 27 | frame.BindProperty(n"opacity", n"FilterButton.frameOpacity"); 28 | frame.Reparent(root); 29 | 30 | let icon = new inkVerticalPanel(); 31 | icon.SetAnchor(inkEAnchor.Centered); 32 | icon.SetAnchorPoint(new Vector2(0.5, 0.5)); 33 | icon.SetChildMargin(new inkMargin(0.0, 4.0, 0.0, 4.0)); 34 | icon.Reparent(root); 35 | 36 | let i = 0; 37 | while i < 3 { 38 | let line = new inkRectangle(); 39 | line.SetHAlign(inkEHorizontalAlign.Center); 40 | line.SetSize(new Vector2(33.0, 2.0)); 41 | line.SetStyle(r"base\\gameplay\\gui\\common\\components\\toggles_style.inkstyle"); 42 | line.BindProperty(n"tintColor", n"FilterButton.iconColor"); 43 | line.Reparent(icon); 44 | 45 | i += 1; 46 | } 47 | 48 | this.m_frame = frame; 49 | this.m_icon = icon; 50 | 51 | this.SetRootWidget(root); 52 | } 53 | 54 | protected cb func OnInitialize() { 55 | this.RegisterToCallback(n"OnClick", this, n"OnClick"); 56 | this.RegisterToCallback(n"OnHoverOver", this, n"OnHoverOver"); 57 | this.RegisterToCallback(n"OnHoverOut", this, n"OnHoverOut"); 58 | } 59 | 60 | protected cb func OnClick(evt: ref) { 61 | this.TriggerClickEvent(evt.GetActionName()); 62 | } 63 | 64 | protected cb func OnHoverOver(evt: ref) { 65 | this.GetRootWidget().SetState(n"Hover"); 66 | } 67 | 68 | protected cb func OnHoverOut(evt: ref) { 69 | this.GetRootWidget().SetState(n"Default"); 70 | } 71 | 72 | protected func TriggerClickEvent(action: ref) { 73 | let evt = new SettingsButtonClick(); 74 | evt.action = action; 75 | 76 | let uiSystem = GameInstance.GetUISystem(this.GetGame()); 77 | uiSystem.QueueEvent(evt); 78 | } 79 | 80 | public static func Create() -> ref { 81 | let self = new SettingsButton(); 82 | self.CreateInstance(); 83 | 84 | return self; 85 | } 86 | 87 | func OnReparent(parent: ref) {} 88 | } 89 | -------------------------------------------------------------------------------- /scripts/UI/ViewSettingsPopup.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | import Codeware.UI.* 3 | 4 | public class ViewSettingsPopup extends InMenuPopup { 5 | private let m_itemSource: WardrobeItemSource; 6 | private let m_viewManager: ref; 7 | private let m_options: array>; 8 | private let m_arranged: Bool; 9 | 10 | protected cb func OnCreate() { 11 | super.OnCreate(); 12 | 13 | this.m_viewManager = ViewManager.GetInstance(this.GetGame()); 14 | this.m_itemSource = this.m_viewManager.GetItemSource(); 15 | 16 | let content = InMenuPopupContent.Create(); 17 | content.SetTitle(GetLocalizedTextByKey(n"UI-EquipmentEx-WardrobeItemSource")); 18 | content.Reparent(this); 19 | 20 | let panel = new inkVerticalPanel(); 21 | panel.SetMargin(new inkMargin(0, 24, 0, 0)); 22 | panel.Reparent(content.GetContainerWidget()); 23 | 24 | for itemSource in [WardrobeItemSource.WardrobeStore, WardrobeItemSource.InventoryAndStash, WardrobeItemSource.InventoryOnly] { 25 | let option = this.SpawnOption(panel); 26 | option.SetData(itemSource, Equals(this.m_itemSource, itemSource)); 27 | ArrayPush(this.m_options, option); 28 | } 29 | 30 | let footer = InMenuPopupFooter.Create(); 31 | footer.Reparent(this); 32 | 33 | let confirmBtn = PopupButton.Create(); 34 | confirmBtn.SetText(GetLocalizedTextByKey(n"UI-ResourceExports-Confirm")); 35 | confirmBtn.SetInputAction(n"one_click_confirm"); 36 | confirmBtn.Reparent(footer); 37 | 38 | let cancelBtn = PopupButton.Create(); 39 | cancelBtn.SetText(GetLocalizedText("LocKey#22175")); 40 | cancelBtn.SetInputAction(n"cancel"); 41 | cancelBtn.Reparent(footer); 42 | } 43 | 44 | protected func SpawnOption(parent: ref) -> ref { 45 | let widget = this.SpawnFromExternal(parent, r"equipment_ex\\gui\\wardrobe.inkwidget", 46 | n"OutfitListEntry:EquipmentEx.ItemSourceOptionController"); 47 | return widget.GetController() as ItemSourceOptionController; 48 | } 49 | 50 | protected cb func OnArrangeChildrenComplete() { 51 | if !this.m_arranged { 52 | for option in this.m_options { 53 | option.UpdateView(); 54 | } 55 | 56 | this.m_arranged = true; 57 | } 58 | } 59 | 60 | protected cb func OnChange(evt: ref) { 61 | this.m_itemSource = evt.value; 62 | } 63 | 64 | protected cb func OnConfirm() { 65 | this.m_viewManager.SetItemSource(this.m_itemSource); 66 | } 67 | 68 | public static func Show(requester: ref) { 69 | let popup = new ViewSettingsPopup(); 70 | popup.Open(requester); 71 | } 72 | 73 | func OnCancel() {} 74 | func OnShown() {} 75 | func OnReparent(parent: ref) {} 76 | } 77 | -------------------------------------------------------------------------------- /scripts/UI/WardrobeHubBtn.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class WardrobeHubBtnController extends WardrobeOutfitSlotController { 4 | protected cb func OnInitialize() -> Bool { 5 | super.OnInitialize(); 6 | 7 | this.Setup(0, true, false, false); 8 | this.GetRootWidget().SetWidth(600); 9 | 10 | inkTextRef.SetText(this.m_slotNumberText, GetLocalizedTextByKey(n"UI-Wardrobe-Tooltip-OutfitInfoTitle")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scripts/UI/WardrobeHubLink.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class WardrobeHubLinkController extends MenuItemController { 4 | protected cb func OnInitialize() -> Bool { 5 | super.OnInitialize(); 6 | 7 | let data: MenuData; 8 | data.label = GetLocalizedTextByKey(n"UI-Wardrobe-Tooltip-OutfitInfoTitle"); 9 | data.icon = n"ico_wardrobe"; 10 | data.fullscreenName = n"inventory_screen"; 11 | data.identifier = EnumInt(HubMenuItems.Inventory); 12 | data.parentIdentifier = EnumInt(HubMenuItems.None); 13 | 14 | this.Init(data); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/ViewEvents.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class ItemSourceUpdated extends Event {} 4 | -------------------------------------------------------------------------------- /scripts/ViewManager.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | public class ViewManager extends ScriptableSystem { 4 | private persistent let m_state: ref; 5 | 6 | private func OnAttach() { 7 | if !IsDefined(this.m_state) { 8 | this.m_state = new ViewState(); 9 | this.m_state.SetItemSource(WardrobeItemSource.InventoryAndStash); 10 | } 11 | } 12 | 13 | public func GetItemSource() -> WardrobeItemSource { 14 | return this.m_state.GetItemSource(); 15 | } 16 | 17 | public func SetItemSource(source: WardrobeItemSource) { 18 | if NotEquals(this.m_state.GetItemSource(), source) { 19 | this.m_state.SetItemSource(source); 20 | this.TriggerItemSourceEvent(); 21 | } 22 | } 23 | 24 | public func IsCollapsed(slotID: TweakDBID) -> Bool { 25 | return this.m_state.IsCollapsed(slotID); 26 | } 27 | 28 | public func SetCollapsed(slotID: TweakDBID, state: Bool) { 29 | this.m_state.SetCollapsed(slotID, state); 30 | } 31 | 32 | public func SetCollapsed(state: Bool) { 33 | if state { 34 | let outfitSystem = OutfitSystem.GetInstance(this.GetGameInstance()); 35 | this.m_state.SetCollapsed(outfitSystem.GetOutfitSlots()); 36 | } else { 37 | this.m_state.SetCollapsed([]); 38 | } 39 | } 40 | 41 | public func ToggleCollapsed(slotID: TweakDBID) { 42 | this.m_state.ToggleCollapsed(slotID); 43 | } 44 | 45 | public func ToggleCollapsed() { 46 | let outfitSystem = OutfitSystem.GetInstance(this.GetGameInstance()); 47 | let outfitSlots = outfitSystem.GetOutfitSlots(); 48 | let collapsedSlots = this.m_state.GetCollapsed(); 49 | 50 | this.SetCollapsed(ArraySize(outfitSlots) != ArraySize(collapsedSlots)); 51 | } 52 | 53 | private func TriggerItemSourceEvent() { 54 | GameInstance.GetUISystem(this.GetGameInstance()).QueueEvent(new ItemSourceUpdated()); 55 | } 56 | 57 | public static func GetInstance(game: GameInstance) -> ref { 58 | return GameInstance.GetScriptableSystemsContainer(game).Get(n"EquipmentEx.ViewManager") as ViewManager; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /scripts/ViewState.reds: -------------------------------------------------------------------------------- 1 | module EquipmentEx 2 | 3 | enum WardrobeItemSource { 4 | WardrobeStore = 0, 5 | InventoryAndStash = 1, 6 | InventoryOnly = 2 7 | } 8 | 9 | class ViewState { 10 | private persistent let m_itemSource: WardrobeItemSource; 11 | private persistent let m_collapsedSlots: array; 12 | 13 | public func GetItemSource() -> WardrobeItemSource { 14 | return this.m_itemSource; 15 | } 16 | 17 | public func SetItemSource(source: WardrobeItemSource) { 18 | this.m_itemSource = source; 19 | } 20 | 21 | public func GetCollapsed() -> array { 22 | return this.m_collapsedSlots; 23 | } 24 | 25 | public func IsCollapsed(slotID: TweakDBID) -> Bool { 26 | return ArrayContains(this.m_collapsedSlots, slotID); 27 | } 28 | 29 | public func SetCollapsed(slotID: TweakDBID, state: Bool) { 30 | if (state) { 31 | if !ArrayContains(this.m_collapsedSlots, slotID) { 32 | ArrayPush(this.m_collapsedSlots, slotID); 33 | } 34 | } else { 35 | ArrayRemove(this.m_collapsedSlots, slotID); 36 | } 37 | } 38 | 39 | public func ToggleCollapsed(slotID: TweakDBID) { 40 | this.SetCollapsed(slotID, !ArrayContains(this.m_collapsedSlots, slotID)); 41 | } 42 | 43 | public func SetCollapsed(slots: array) { 44 | this.m_collapsedSlots = slots; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /support/hints/EquipmentEx.toml: -------------------------------------------------------------------------------- 1 | [[UNRESOLVED_REF]] 2 | id = "ARCHIVEXL_12100" 3 | span_starts_with = "ArchiveXL" 4 | message = "Install ArchiveXL 1.21.0 or higher" 5 | 6 | [[UNRESOLVED_METHOD]] 7 | id = "ARCHIVEXL_12100" 8 | span_starts_with = "ArchiveXL" 9 | message = "Install ArchiveXL 1.21.0 or higher" 10 | 11 | [[UNRESOLVED_REF]] 12 | id = "TWEAKXL_11007" 13 | span_starts_with = "TweakXL" 14 | message = "Install TweakXL 1.10.7 or higher" 15 | 16 | [[UNRESOLVED_FN]] 17 | id = "CODEWARE_11500" 18 | span_starts_with = "Print(command)" 19 | message = "Install Codeware 1.15.0 or higher" 20 | 21 | [[UNRESOLVED_FN]] 22 | id = "CODEWARE_11500" 23 | line_contains = "UTF8StrLower" 24 | message = "Install Codeware 1.15.0 or higher" 25 | 26 | [[UNRESOLVED_METHOD]] 27 | id = "CODEWARE_11500" 28 | line_contains = "ForgetItemID" 29 | message = "Install Codeware 1.15.0 or higher" 30 | 31 | [[UNRESOLVED_TYPE]] 32 | id = "CODEWARE_11500" 33 | line_contains = "InMenuPopup" 34 | message = "Install Codeware 1.15.0 or higher" 35 | 36 | [[UNRESOLVED_METHOD]] 37 | id = "EQEX_MIX" 38 | line_contains = "method CheckRequirements not found on EquipmentEx" 39 | message = "Remove r6/scripts/EquipmentEx and clean install the latest version of Equipment-EX" 40 | -------------------------------------------------------------------------------- /support/localization/lang.json.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.13.0-nightly.2024-04-27", 4 | "WKitJsonVersion": "0.0.8", 5 | "GameVersion": 2120, 6 | "ExportedDateTime": "2024-04-29T09:04:30.6910952Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "JsonResource", 14 | "cookingPlatform": "PLATFORM_PC", 15 | "root": { 16 | "HandleId": "0", 17 | "Data": { 18 | "$type": "localizationPersistenceOnScreenEntries", 19 | "entries": [ 20 | { 21 | "$type": "localizationPersistenceOnScreenEntry", 22 | "femaleVariant": "No outfit", 23 | "maleVariant": "", 24 | "primaryKey": "0", 25 | "secondaryKey": "UI-Wardrobe-NoOutfit" 26 | }, 27 | { 28 | "$type": "localizationPersistenceOnScreenEntry", 29 | "femaleVariant": "Current outfit", 30 | "maleVariant": "", 31 | "primaryKey": "0", 32 | "secondaryKey": "UI-Wardrobe-CurrentOutfit" 33 | }, 34 | { 35 | "$type": "localizationPersistenceOnScreenEntry", 36 | "femaleVariant": "Enter outfit name:", 37 | "maleVariant": "", 38 | "primaryKey": "0", 39 | "secondaryKey": "UI-Wardrobe-NotificationSaveSet" 40 | }, 41 | { 42 | "$type": "localizationPersistenceOnScreenEntry", 43 | "femaleVariant": "Outfit with this name already exists, do you want to overwrite it?", 44 | "maleVariant": "", 45 | "primaryKey": "0", 46 | "secondaryKey": "UI-Wardrobe-NotificationReplaceSet" 47 | }, 48 | { 49 | "$type": "localizationPersistenceOnScreenEntry", 50 | "femaleVariant": "Search by name", 51 | "maleVariant": "", 52 | "primaryKey": "0", 53 | "secondaryKey": "UI-Wardrobe-SearchByName" 54 | }, 55 | { 56 | "$type": "localizationPersistenceOnScreenEntry", 57 | "femaleVariant": "Item Source", 58 | "maleVariant": "", 59 | "primaryKey": "0", 60 | "secondaryKey": "UI-EquipmentEx-WardrobeItemSource" 61 | }, 62 | { 63 | "$type": "localizationPersistenceOnScreenEntry", 64 | "femaleVariant": "Preferred source of items for outfit manager: \u0022Inventory \u002B Stash\u0022 will only use the current items in your inventory and stashes, \u0022Wardrobe Store\u0022 will use every item you have ever obtained in the game (including dropped and disassembled items).", 65 | "maleVariant": "", 66 | "primaryKey": "0", 67 | "secondaryKey": "UI-EquipmentEx-WardrobeItemSource-Description" 68 | }, 69 | { 70 | "$type": "localizationPersistenceOnScreenEntry", 71 | "femaleVariant": "Inventory", 72 | "maleVariant": "", 73 | "primaryKey": "0", 74 | "secondaryKey": "UI-EquipmentEx-WardrobeItemSource-InventoryOnly" 75 | }, 76 | { 77 | "$type": "localizationPersistenceOnScreenEntry", 78 | "femaleVariant": "Inventory \u002B Stash", 79 | "maleVariant": "", 80 | "primaryKey": "0", 81 | "secondaryKey": "UI-EquipmentEx-WardrobeItemSource-InventoryAndStash" 82 | }, 83 | { 84 | "$type": "localizationPersistenceOnScreenEntry", 85 | "femaleVariant": "Wardrobe Store", 86 | "maleVariant": "", 87 | "primaryKey": "0", 88 | "secondaryKey": "UI-EquipmentEx-WardrobeItemSource-WardrobeStore" 89 | }, 90 | { 91 | "$type": "localizationPersistenceOnScreenEntry", 92 | "femaleVariant": "Equipment-EX requires:\\n- ArchiveXL {archive_xl_req} or higher\\n- TweakXL {tweak_xl_req} or higher\\n- Codeware {codeware_req} or higher\\n\\nDetected ArchiveXL {archive_xl_ver}, TweakXL {tweak_xl_ver} and Codeware {codeware_ver}.\\n\\nPlease update the dependencies to use this mod.", 93 | "maleVariant": "", 94 | "primaryKey": "0", 95 | "secondaryKey": "UI-EquipmentEx-NotificationRequirements" 96 | }, 97 | { 98 | "$type": "localizationPersistenceOnScreenEntry", 99 | "femaleVariant": "Equipment-EX has detected a conflicting mod.\\n\\nThe following mods may cause problems or block the functionality of this mod:\\n{conflicts}\\nPlease disable or remove conflicting mods if you wish to use this mod.", 100 | "maleVariant": "", 101 | "primaryKey": "0", 102 | "secondaryKey": "UI-EquipmentEx-NotificationConflicts" 103 | }, 104 | { 105 | "$type": "localizationPersistenceOnScreenEntry", 106 | "femaleVariant": "Head", 107 | "maleVariant": "", 108 | "primaryKey": "0", 109 | "secondaryKey": "Gameplay-OutfitSlots-Head" 110 | }, 111 | { 112 | "$type": "localizationPersistenceOnScreenEntry", 113 | "femaleVariant": "Balaclava", 114 | "maleVariant": "", 115 | "primaryKey": "0", 116 | "secondaryKey": "Gameplay-OutfitSlots-Balaclava" 117 | }, 118 | { 119 | "$type": "localizationPersistenceOnScreenEntry", 120 | "femaleVariant": "Mask", 121 | "maleVariant": "", 122 | "primaryKey": "0", 123 | "secondaryKey": "Gameplay-OutfitSlots-Mask" 124 | }, 125 | { 126 | "$type": "localizationPersistenceOnScreenEntry", 127 | "femaleVariant": "Eyes", 128 | "maleVariant": "", 129 | "primaryKey": "0", 130 | "secondaryKey": "Gameplay-OutfitSlots-Eyes" 131 | }, 132 | { 133 | "$type": "localizationPersistenceOnScreenEntry", 134 | "femaleVariant": "Left Eye", 135 | "maleVariant": "", 136 | "primaryKey": "0", 137 | "secondaryKey": "Gameplay-OutfitSlots-EyeLeft" 138 | }, 139 | { 140 | "$type": "localizationPersistenceOnScreenEntry", 141 | "femaleVariant": "Right Eye", 142 | "maleVariant": "", 143 | "primaryKey": "0", 144 | "secondaryKey": "Gameplay-OutfitSlots-EyeRight" 145 | }, 146 | { 147 | "$type": "localizationPersistenceOnScreenEntry", 148 | "femaleVariant": "Glasses", 149 | "maleVariant": "", 150 | "primaryKey": "0", 151 | "secondaryKey": "Gameplay-OutfitSlots-Glasses" 152 | }, 153 | { 154 | "$type": "localizationPersistenceOnScreenEntry", 155 | "femaleVariant": "Wreath", 156 | "maleVariant": "", 157 | "primaryKey": "0", 158 | "secondaryKey": "Gameplay-OutfitSlots-Wreath" 159 | }, 160 | { 161 | "$type": "localizationPersistenceOnScreenEntry", 162 | "femaleVariant": "Left Ear", 163 | "maleVariant": "", 164 | "primaryKey": "0", 165 | "secondaryKey": "Gameplay-OutfitSlots-EarLeft" 166 | }, 167 | { 168 | "$type": "localizationPersistenceOnScreenEntry", 169 | "femaleVariant": "Right Ear", 170 | "maleVariant": "", 171 | "primaryKey": "0", 172 | "secondaryKey": "Gameplay-OutfitSlots-EarRight" 173 | }, 174 | { 175 | "$type": "localizationPersistenceOnScreenEntry", 176 | "femaleVariant": "Neckwear", 177 | "maleVariant": "", 178 | "primaryKey": "0", 179 | "secondaryKey": "Gameplay-OutfitSlots-Neckwear" 180 | }, 181 | { 182 | "$type": "localizationPersistenceOnScreenEntry", 183 | "femaleVariant": "Necklace / Tight", 184 | "maleVariant": "", 185 | "primaryKey": "0", 186 | "secondaryKey": "Gameplay-OutfitSlots-NecklaceTight" 187 | }, 188 | { 189 | "$type": "localizationPersistenceOnScreenEntry", 190 | "femaleVariant": "Necklace / Short", 191 | "maleVariant": "", 192 | "primaryKey": "0", 193 | "secondaryKey": "Gameplay-OutfitSlots-NecklaceShort" 194 | }, 195 | { 196 | "$type": "localizationPersistenceOnScreenEntry", 197 | "femaleVariant": "Necklace / Long", 198 | "maleVariant": "", 199 | "primaryKey": "0", 200 | "secondaryKey": "Gameplay-OutfitSlots-NecklaceLong" 201 | }, 202 | { 203 | "$type": "localizationPersistenceOnScreenEntry", 204 | "femaleVariant": "Torso / Under", 205 | "maleVariant": "", 206 | "primaryKey": "0", 207 | "secondaryKey": "Gameplay-OutfitSlots-TorsoUnder" 208 | }, 209 | { 210 | "$type": "localizationPersistenceOnScreenEntry", 211 | "femaleVariant": "Torso / Inner", 212 | "maleVariant": "", 213 | "primaryKey": "0", 214 | "secondaryKey": "Gameplay-OutfitSlots-TorsoInner" 215 | }, 216 | { 217 | "$type": "localizationPersistenceOnScreenEntry", 218 | "femaleVariant": "Torso / Middle", 219 | "maleVariant": "", 220 | "primaryKey": "0", 221 | "secondaryKey": "Gameplay-OutfitSlots-TorsoMiddle" 222 | }, 223 | { 224 | "$type": "localizationPersistenceOnScreenEntry", 225 | "femaleVariant": "Torso / Outer", 226 | "maleVariant": "", 227 | "primaryKey": "0", 228 | "secondaryKey": "Gameplay-OutfitSlots-TorsoOuter" 229 | }, 230 | { 231 | "$type": "localizationPersistenceOnScreenEntry", 232 | "femaleVariant": "Torso / Aux", 233 | "maleVariant": "", 234 | "primaryKey": "0", 235 | "secondaryKey": "Gameplay-OutfitSlots-TorsoAux" 236 | }, 237 | { 238 | "$type": "localizationPersistenceOnScreenEntry", 239 | "femaleVariant": "Back", 240 | "maleVariant": "", 241 | "primaryKey": "0", 242 | "secondaryKey": "Gameplay-OutfitSlots-Back" 243 | }, 244 | { 245 | "$type": "localizationPersistenceOnScreenEntry", 246 | "femaleVariant": "Left Shoulder", 247 | "maleVariant": "", 248 | "primaryKey": "0", 249 | "secondaryKey": "Gameplay-OutfitSlots-ShoulderLeft" 250 | }, 251 | { 252 | "$type": "localizationPersistenceOnScreenEntry", 253 | "femaleVariant": "Right Shoulder", 254 | "maleVariant": "", 255 | "primaryKey": "0", 256 | "secondaryKey": "Gameplay-OutfitSlots-ShoulderRight" 257 | }, 258 | { 259 | "$type": "localizationPersistenceOnScreenEntry", 260 | "femaleVariant": "Left Elbow", 261 | "maleVariant": "", 262 | "primaryKey": "0", 263 | "secondaryKey": "Gameplay-OutfitSlots-ElbowLeft" 264 | }, 265 | { 266 | "$type": "localizationPersistenceOnScreenEntry", 267 | "femaleVariant": "Right Elbow", 268 | "maleVariant": "", 269 | "primaryKey": "0", 270 | "secondaryKey": "Gameplay-OutfitSlots-ElbowRight" 271 | }, 272 | { 273 | "$type": "localizationPersistenceOnScreenEntry", 274 | "femaleVariant": "Left Wrist", 275 | "maleVariant": "", 276 | "primaryKey": "0", 277 | "secondaryKey": "Gameplay-OutfitSlots-WristLeft" 278 | }, 279 | { 280 | "$type": "localizationPersistenceOnScreenEntry", 281 | "femaleVariant": "Right Wrist", 282 | "maleVariant": "", 283 | "primaryKey": "0", 284 | "secondaryKey": "Gameplay-OutfitSlots-WristRight" 285 | }, 286 | { 287 | "$type": "localizationPersistenceOnScreenEntry", 288 | "femaleVariant": "Hands", 289 | "maleVariant": "", 290 | "primaryKey": "0", 291 | "secondaryKey": "Gameplay-OutfitSlots-Hands" 292 | }, 293 | { 294 | "$type": "localizationPersistenceOnScreenEntry", 295 | "femaleVariant": "Left Hand", 296 | "maleVariant": "", 297 | "primaryKey": "0", 298 | "secondaryKey": "Gameplay-OutfitSlots-HandLeft" 299 | }, 300 | { 301 | "$type": "localizationPersistenceOnScreenEntry", 302 | "femaleVariant": "Right Hand", 303 | "maleVariant": "", 304 | "primaryKey": "0", 305 | "secondaryKey": "Gameplay-OutfitSlots-HandRight" 306 | }, 307 | { 308 | "$type": "localizationPersistenceOnScreenEntry", 309 | "femaleVariant": "Left Fingers", 310 | "maleVariant": "", 311 | "primaryKey": "0", 312 | "secondaryKey": "Gameplay-OutfitSlots-FingersLeft" 313 | }, 314 | { 315 | "$type": "localizationPersistenceOnScreenEntry", 316 | "femaleVariant": "Right Fingers", 317 | "maleVariant": "", 318 | "primaryKey": "0", 319 | "secondaryKey": "Gameplay-OutfitSlots-FingersRight" 320 | }, 321 | { 322 | "$type": "localizationPersistenceOnScreenEntry", 323 | "femaleVariant": "Left Fingernails", 324 | "maleVariant": "", 325 | "primaryKey": "0", 326 | "secondaryKey": "Gameplay-OutfitSlots-FingernailsLeft" 327 | }, 328 | { 329 | "$type": "localizationPersistenceOnScreenEntry", 330 | "femaleVariant": "Right Fingernails", 331 | "maleVariant": "", 332 | "primaryKey": "0", 333 | "secondaryKey": "Gameplay-OutfitSlots-FingernailsRight" 334 | }, 335 | { 336 | "$type": "localizationPersistenceOnScreenEntry", 337 | "femaleVariant": "Waist", 338 | "maleVariant": "", 339 | "primaryKey": "0", 340 | "secondaryKey": "Gameplay-OutfitSlots-Waist" 341 | }, 342 | { 343 | "$type": "localizationPersistenceOnScreenEntry", 344 | "femaleVariant": "Legs / Inner", 345 | "maleVariant": "", 346 | "primaryKey": "0", 347 | "secondaryKey": "Gameplay-OutfitSlots-LegsInner" 348 | }, 349 | { 350 | "$type": "localizationPersistenceOnScreenEntry", 351 | "femaleVariant": "Legs / Middle", 352 | "maleVariant": "", 353 | "primaryKey": "0", 354 | "secondaryKey": "Gameplay-OutfitSlots-LegsMiddle" 355 | }, 356 | { 357 | "$type": "localizationPersistenceOnScreenEntry", 358 | "femaleVariant": "Legs / Outer", 359 | "maleVariant": "", 360 | "primaryKey": "0", 361 | "secondaryKey": "Gameplay-OutfitSlots-LegsOuter" 362 | }, 363 | { 364 | "$type": "localizationPersistenceOnScreenEntry", 365 | "femaleVariant": "Left Thigh", 366 | "maleVariant": "", 367 | "primaryKey": "0", 368 | "secondaryKey": "Gameplay-OutfitSlots-ThighLeft" 369 | }, 370 | { 371 | "$type": "localizationPersistenceOnScreenEntry", 372 | "femaleVariant": "Right Thigh", 373 | "maleVariant": "", 374 | "primaryKey": "0", 375 | "secondaryKey": "Gameplay-OutfitSlots-ThighRight" 376 | }, 377 | { 378 | "$type": "localizationPersistenceOnScreenEntry", 379 | "femaleVariant": "Left Knee", 380 | "maleVariant": "", 381 | "primaryKey": "0", 382 | "secondaryKey": "Gameplay-OutfitSlots-KneeLeft" 383 | }, 384 | { 385 | "$type": "localizationPersistenceOnScreenEntry", 386 | "femaleVariant": "Right Knee", 387 | "maleVariant": "", 388 | "primaryKey": "0", 389 | "secondaryKey": "Gameplay-OutfitSlots-KneeRight" 390 | }, 391 | { 392 | "$type": "localizationPersistenceOnScreenEntry", 393 | "femaleVariant": "Left Ankle", 394 | "maleVariant": "", 395 | "primaryKey": "0", 396 | "secondaryKey": "Gameplay-OutfitSlots-AnkleLeft" 397 | }, 398 | { 399 | "$type": "localizationPersistenceOnScreenEntry", 400 | "femaleVariant": "Right Ankle", 401 | "maleVariant": "", 402 | "primaryKey": "0", 403 | "secondaryKey": "Gameplay-OutfitSlots-AnkleRight" 404 | }, 405 | { 406 | "$type": "localizationPersistenceOnScreenEntry", 407 | "femaleVariant": "Feet", 408 | "maleVariant": "", 409 | "primaryKey": "0", 410 | "secondaryKey": "Gameplay-OutfitSlots-Feet" 411 | }, 412 | { 413 | "$type": "localizationPersistenceOnScreenEntry", 414 | "femaleVariant": "Left Toes", 415 | "maleVariant": "", 416 | "primaryKey": "0", 417 | "secondaryKey": "Gameplay-OutfitSlots-ToesLeft" 418 | }, 419 | { 420 | "$type": "localizationPersistenceOnScreenEntry", 421 | "femaleVariant": "Right Toes", 422 | "maleVariant": "", 423 | "primaryKey": "0", 424 | "secondaryKey": "Gameplay-OutfitSlots-ToesRight" 425 | }, 426 | { 427 | "$type": "localizationPersistenceOnScreenEntry", 428 | "femaleVariant": "Left Toenails", 429 | "maleVariant": "", 430 | "primaryKey": "0", 431 | "secondaryKey": "Gameplay-OutfitSlots-ToenailsLeft" 432 | }, 433 | { 434 | "$type": "localizationPersistenceOnScreenEntry", 435 | "femaleVariant": "Right Toenails", 436 | "maleVariant": "", 437 | "primaryKey": "0", 438 | "secondaryKey": "Gameplay-OutfitSlots-ToenailsRight" 439 | }, 440 | { 441 | "$type": "localizationPersistenceOnScreenEntry", 442 | "femaleVariant": "Body / Under", 443 | "maleVariant": "", 444 | "primaryKey": "0", 445 | "secondaryKey": "Gameplay-OutfitSlots-BodyUnder" 446 | }, 447 | { 448 | "$type": "localizationPersistenceOnScreenEntry", 449 | "femaleVariant": "Body / Inner", 450 | "maleVariant": "", 451 | "primaryKey": "0", 452 | "secondaryKey": "Gameplay-OutfitSlots-BodyInner" 453 | }, 454 | { 455 | "$type": "localizationPersistenceOnScreenEntry", 456 | "femaleVariant": "Body / Middle", 457 | "maleVariant": "", 458 | "primaryKey": "0", 459 | "secondaryKey": "Gameplay-OutfitSlots-BodyMiddle" 460 | }, 461 | { 462 | "$type": "localizationPersistenceOnScreenEntry", 463 | "femaleVariant": "Body / Outer", 464 | "maleVariant": "", 465 | "primaryKey": "0", 466 | "secondaryKey": "Gameplay-OutfitSlots-BodyOuter" 467 | }, 468 | { 469 | "$type": "localizationPersistenceOnScreenEntry", 470 | "femaleVariant": "Left Hand Prop", 471 | "maleVariant": "", 472 | "primaryKey": "0", 473 | "secondaryKey": "Gameplay-OutfitSlots-HandPropLeft" 474 | }, 475 | { 476 | "$type": "localizationPersistenceOnScreenEntry", 477 | "femaleVariant": "Right Hand Prop", 478 | "maleVariant": "", 479 | "primaryKey": "0", 480 | "secondaryKey": "Gameplay-OutfitSlots-HandPropRight" 481 | } 482 | ] 483 | } 484 | } 485 | }, 486 | "EmbeddedFiles": [] 487 | } 488 | } -------------------------------------------------------------------------------- /tools/dist/install-mod.ps1: -------------------------------------------------------------------------------- 1 | param ($GameDir, $ReleaseBin, $ProjectName = "EquipmentEx") 2 | 3 | $StageDir = "build/package" 4 | $Version = & $($PSScriptRoot + "\steps\get-version.ps1") 5 | 6 | & $($PSScriptRoot + "\steps\compose-archives.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} 7 | & $($PSScriptRoot + "\steps\compose-redscripts.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} -Version ${Version} 8 | & $($PSScriptRoot + "\steps\compose-hints.ps1") -StageDir ${StageDir} 9 | & $($PSScriptRoot + "\steps\install-from-stage.ps1") -StageDir ${StageDir} -GameDir ${GameDir} 10 | 11 | Remove-Item -Recurse ${StageDir} 12 | -------------------------------------------------------------------------------- /tools/dist/package-mod.ps1: -------------------------------------------------------------------------------- 1 | param ($ProjectName = "EquipmentEx") 2 | 3 | $StageDir = "build/package" 4 | $DistDir = "build/dist" 5 | $Version = & $($PSScriptRoot + "\steps\get-version.ps1") 6 | 7 | & $($PSScriptRoot + "\steps\compose-archives.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} 8 | & $($PSScriptRoot + "\steps\compose-redscripts.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} -Version ${Version} 9 | & $($PSScriptRoot + "\steps\compose-hints.ps1") -StageDir ${StageDir} 10 | & $($PSScriptRoot + "\steps\create-zip-from-stage.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} -DistDir ${DistDir} -Version ${Version} 11 | 12 | Remove-Item -Recurse ${StageDir} 13 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-archives.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ReleaseBin, $ProjectName) 2 | 3 | $ArchiveDir = "${StageDir}/archive/pc/mod" 4 | 5 | New-Item -ItemType directory -Force -Path ${ArchiveDir} | Out-Null 6 | Copy-Item -Path "archive/packed/archive/pc/mod/*" -Destination ${ArchiveDir} 7 | Copy-Item -Path "archive/source/resources/*" -Destination ${ArchiveDir} 8 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-hints.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir) 2 | 3 | $HintsDir = "${StageDir}/r6/config/redsUserHints" 4 | 5 | New-Item -ItemType directory -Force -Path ${HintsDir} | Out-Null 6 | Copy-Item -Path "support/hints/*" -Destination ${HintsDir} 7 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-redscripts.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ProjectName, $Version) 2 | 3 | $ScriptsDir = "${StageDir}/r6/scripts/${ProjectName}" 4 | $GlobalScope = "${ProjectName}.Global" 5 | $SettingsScope = "${ProjectName}.Settings" 6 | $SettingsFile = "Settings.reds" 7 | 8 | $SourceFiles = Get-ChildItem -Path "scripts" -Filter *.reds -Recurse 9 | $Bundles = @{} 10 | 11 | foreach ($ScriptFile in $SourceFiles) { 12 | $Content = Get-Content $ScriptFile.FullName 13 | $Module = ($Content | Select-String -Pattern "^module\s+(.+)" -List | %{$_.matches.groups[1].Value}) 14 | $Scope = $Module ?? $GlobalScope 15 | 16 | if ($ScriptFile.Name -eq $SettingsFile) { 17 | $Scope = $SettingsScope 18 | $Imports = @() 19 | $Source = $Content | Out-String 20 | } 21 | else { 22 | $Imports = ($Content | Select-String -Pattern "^import\s+(.+)" -List | %{$_.matches.groups[1].Value}) 23 | $Source = ($Content | Select-String -Pattern "^\s*(//|module\s|import\s)" -NotMatch) | Out-String 24 | } 25 | 26 | if ($Bundles[$Scope] -eq $null) { 27 | $Bundles[$Scope] = @{ 28 | Scope = $Scope 29 | Module = $Module 30 | Imports = @{} 31 | Sources = [System.Collections.ArrayList]@() 32 | } 33 | } 34 | 35 | $Bundle = $Bundles[$Scope] 36 | $Bundle.Sources.Add($Source.Trim()) > $null 37 | 38 | if ($Imports -ne $null) { 39 | foreach ($Import in $Imports) { 40 | $Bundle.Imports[$Import] = 1 41 | } 42 | } 43 | } 44 | 45 | New-Item -ItemType directory -Force -Path ${ScriptsDir} | Out-Null 46 | Copy-Item -Path "LICENSE" -Destination ${ScriptsDir} 47 | 48 | foreach ($Bundle in $Bundles.Values) { 49 | $BundleFile = "${ScriptsDir}/$($Bundle.Scope).reds" 50 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "// ${ProjectName} ${Version}" 51 | 52 | if ($Bundle.Module -and ($Bundle.Scope -ne $SettingsScope)) { 53 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "module $($Bundle.Module)" -Append 54 | } 55 | 56 | foreach ($Import in $Bundle.Imports.Keys) { 57 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "import ${Import}" -Append 58 | } 59 | 60 | foreach ($Source in $Bundle.Sources) { 61 | if ($Source) { 62 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "`n${Source}" -Append 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tools/dist/steps/create-zip-from-stage.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ProjectName, $DistDir, $Version, $Suffix = "") 2 | 3 | New-Item -ItemType directory -Force -Path ${DistDir} | Out-Null 4 | Compress-Archive -Path "${StageDir}/*" -CompressionLevel Optimal -Force -DestinationPath "${DistDir}/${ProjectName}-${Version}${Suffix}.zip" 5 | -------------------------------------------------------------------------------- /tools/dist/steps/get-version.ps1: -------------------------------------------------------------------------------- 1 | Select-String -Path "scripts/Facade.reds" -Pattern """(\d+\.\d+\.\d+)""" -List | %{"$($_.Matches.Groups[1])"} | Write-Output 2 | -------------------------------------------------------------------------------- /tools/dist/steps/install-from-stage.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $GameDir) 2 | 3 | Copy-Item -Path "${StageDir}/*" -Recurse -Force -Destination ${GameDir} 4 | --------------------------------------------------------------------------------