{"id":24329574,"url":"https://github.com/xerren09/contentwarningshopapi","last_synced_at":"2025-08-07T01:12:42.524Z","repository":{"id":272848728,"uuid":"913617993","full_name":"Xerren09/ContentWarningShopAPI","owner":"Xerren09","description":"Exposes an easy-to-use API to add custom items to Content Warning's in-game shop. ","archived":false,"fork":false,"pushed_at":"2025-08-04T23:55:47.000Z","size":61,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-05T01:21:10.945Z","etag":null,"topics":["api","content-warning","mod","tooling"],"latest_commit_sha":null,"homepage":"https://steamcommunity.com/sharedfiles/filedetails/?id=3408837293","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Xerren09.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-08T03:13:48.000Z","updated_at":"2025-08-04T23:55:50.000Z","dependencies_parsed_at":"2025-02-03T19:26:14.171Z","dependency_job_id":"5c03fd2c-5256-4cc7-9049-f175e5128c13","html_url":"https://github.com/Xerren09/ContentWarningShopAPI","commit_stats":null,"previous_names":["xerren09/contentwarningshopapi"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/Xerren09/ContentWarningShopAPI","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xerren09%2FContentWarningShopAPI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xerren09%2FContentWarningShopAPI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xerren09%2FContentWarningShopAPI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xerren09%2FContentWarningShopAPI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Xerren09","download_url":"https://codeload.github.com/Xerren09/ContentWarningShopAPI/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xerren09%2FContentWarningShopAPI/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268836285,"owners_count":24314951,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-05T02:00:12.334Z","response_time":2576,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","content-warning","mod","tooling"],"created_at":"2025-01-18T00:16:32.388Z","updated_at":"2025-08-07T01:12:42.472Z","avatar_url":"https://github.com/Xerren09.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"Content Warning Shop API\n===\n[![Steam Downloads](https://img.shields.io/steam/downloads/3408837293?style=for-the-badge\u0026logo=steam\u0026color=blue\u0026label=Downloads)](https://steamcommunity.com/sharedfiles/filedetails/?id=3408837293)\n[![Steam Subscriptions](https://img.shields.io/steam/subscriptions/3408837293?style=for-the-badge\u0026logo=steam\u0026color=blue\u0026label=Subscriptions)](https://steamcommunity.com/sharedfiles/filedetails/?id=3408837293)\n[![Thunderstore Downloads](https://img.shields.io/thunderstore/dt/Xerren/ShopAPI?style=for-the-badge\u0026logo=thunderstore\u0026logoColor=white\u0026color=blue\u0026label=Downloads)](https://thunderstore.io/c/content-warning/p/Xerren/ShopAPI/)\n[![NuGet Version](https://img.shields.io/nuget/v/Xerren.ContentWarning.ShopAPI?style=for-the-badge\u0026logo=nuget\u0026logoColor=white\u0026color=blue\u0026label=Version)](https://www.nuget.org/packages/Xerren.ContentWarning.ShopAPI)\n\nExposes an easy-to-use API to add custom items to the in-game shop. Loosely based on the now defunct [ShopUtils mod by hyydsz](https://github.com/hyydsz/ContentWarningShopUtils).\n\nThis project is built on top of the [mod template](https://github.com/landfallgames/ExampleCWPlugin) Landfall generously released. 🧡\n\n## Features\n\n* **Custom items:** Opens up the in-game shop to allow custom items to be added. This includes automatically synchronising item prices between players, and registering custom `ItemDataEntry` types defined in your mod to interface with the game's built-in item synchroniser.\n* **Synchronisation:** Easily synchronise any arbitrary settings between players in a lobby using [Steam Lobby Metadata](https://partner.steamgames.com/doc/features/multiplayer/matchmaking#6) keys via the `SynchronisedMetadata\u003cT\u003e` class.\n* **Localisation:** Extends and opens up the game's localisation system to allow items to be translated to the supported locales by patching item and shop related localised methods.\n\n## Setup\n\n### NuGet (Recommended)\n\nThe easiest way to use the library is by installing the latest version of the [NuGet package](https://www.nuget.org/packages/Xerren.ContentWarning.ShopAPI) to your project.\n\n\u003e [!NOTE]\n\u003e The NuGet package is built targeting the Steam Workshop (vanilla mod loader), but it can safely be used when developing BepInEx mods as well.\n\n### Manual\n\nDownload the [latest DLL](https://github.com/Xerren09/ContentWarningShopAPI/releases/latest) and add a reference to it in your project. If you are referencing a local DLL, be sure to set \"Copy Local\" to `No` to avoid distributing it with your mod, as this will cause issues.\n\n\u003e [!IMPORTANT]\n\u003e The DLLs available on the releases page are built for use with different mod loaders. **For development, use the one without a suffix**, regardless of if you are building your mod for the Steam Workshop (vanilla mod loader) or Thunderstore (BepInEx).\n\u003e\n\u003e The version suffixed with `.bepinex` expects BepInEx as a mod loader and will not work without it. It is there for manual installations but should not be used otherwise.\n\u003e\n\u003e In all releases of this mod the assembly's name will be without a suffix, so which version is loaded at runtime should not matter.\n\n### Integration\n\n**DO NOT** bundle the mod's DLL with your own. Ensure that no `ShopAPI.dll` is included with your build.\n\nDepending on your publishing target, you should instead require it as a dependency on your publishing platform:\n\n\u003e [!IMPORTANT]\n\u003e If you are referencing a local DLL, be sure to set \"Copy Local\" to `No` to avoid distributing it with your mod, as this will cause issues.\n\n#### Steam Workshop\n\nWhen publishing on the Steam Workshop, add [this Workshop Item ](https://steamcommunity.com/sharedfiles/filedetails/?id=3408837293) as your item's dependency via the \"Add/Remove Required Items\" option (on your mod's page right hand side panel). \n\nSteam will ensure that the dependency will load before your mod when the game is launched.\n\n#### Thunderstore (BepInEx)\n\nIf you are building a BepInEx plugin, add this mod as a dependency to your plugin's main file:\n\n```csharp\n[BepInDependency(ShopApiPlugin.MOD_GUID)]\npublic class YourCustomPlugin : BaseUnityPlugin \n{\n    // ...\n}\n```\n\nIf a specific version is needed, pass `ShopApiPlugin.MOD_VER` after the GUID.\n\nWhen publishing on Thunderstore, add the mod's [Dependency String](https://thunderstore.io/c/content-warning/p/Xerren/ShopAPI/) to the [manifest.json](https://thunderstore.io/c/content-warning/create/docs/) file, so mod managers can automatically fetch it.\n\n## Usage\n\nOnce added as a reference to your project, all classes are available under the `ContentWarningShop` namespace. \n\n### Creating items\n\nIdeally `Item`s should be preconfigured and packed into an [`AssetBundle`](https://docs.unity3d.com/Manual/AssetBundlesIntro.html), but you can also construct them at runtime if its easier.\n\nCustom `Item`s should have their `persistentID`, `price`, `purchasable`, `Category`, and `icon` properties set to work.\n\nIf you would like your item to have a chance to be randomly spawned in the Old World like other items, set `spawnable` to `true`, and `itemType` to `Item.ItemType.Tool`.\n\n### Registering items\n\nItems can be registered via the `RegisterItem` method:\n\n```csharp\n//using ContentWarningShop;\n\nvar yourCustomItem = yourAssetBundle.LoadAsset\u003cItem\u003e(\"yourItem\");\n\nShop.RegisterItem(yourCustomItem);\nShop.RegisterCustomDataEntries();\n```\n\n\u003e [!NOTE]\n\u003e Item prices are automatically synchronised between players on lobby join. The price set by the lobby's host will be used for the entire lobby.\n\nYou can check if a custom item has been already registered via the `IsItemRegistered` method. The list of **all** registered *custom* items is also available via the `CustomItems` property.\n\nIf your item uses custom `ItemDataEntry` types, call the `RegisterCustomDataEntries` method to fetch and register all custom types defined in your assembly. This will let the game automatically synchronise the items' custom state between players. (See [compatibility](#compatibility) if you run into issues) \nIt is enough call this method once per assembly / mod.\n\n### Updating item prices\n\nIf you need to update an item's price at any point after a lobby has been started, use the `UpdateItemPrice` method:\n\n```csharp\nvar success = Shop.UpdateItemPrice(yourCustomItem, price);\n```\n\nThis will re-synchronise the item's price to every player. Note that only the lobby's host can update an item's price, so the method returns a boolean indicating if it was successful (if the local player had permissions).\n\n### Synchronising Settings\n\nThe `SynchronisedMetadata\u003cT\u003e` class allows arbitrary settings to be synchronised between players through the use of [Steam Lobby Metadata](https://partner.steamgames.com/doc/features/multiplayer/matchmaking#6) keys. Simply create a new instance with a specific type and key and it will be automatically updated whenever the key's value is changed.\n\n\u003e [!TIP]\n\u003e Consider prepending your mod's GUID to the key to ensure it won't accidentally collide with a different mod. \n\nFor example to synchronise a simple boolean setting with `false` as the initial value:\n```csharp\npublic static readonly SynchronisedMetadata\u003cbool\u003e ExampleSetting = new(\"ExampleSetting\", false);\n```\nAn instance bound to a key will remain valid even if the player changes lobbies, so they can be kept for the entire run-time of the game.\n\nTo update a setting's value, call `SetValue(T value)`. Only the lobby's host may update the value of a key, so the method returns a boolean indicating if the set was allowed. If it was rejected, the instance's value isn't updated. Use `CanSet` method to check if the current player has permission to update the setting.\n\nThe `ValueChanged` event will be raised with the new (current) value when a key is updated either locally or remotely.\n\n\u003e [!IMPORTANT]  \n\u003e Values are converted to strings when passed on to the steam lobby, so make sure your type can be cast to string and back.\n\n\u003e [!NOTE]\n\u003e When not currently in a lobby, setting the value is permitted as if the current player was the host, and the `ValueChanged` event will still be raised.\n\nIn the scenario that you have separate player and lobby settings, and you want to apply the local player's settings when they host a new lobby, make sure to subscribe to the `LobbyHosted` event and overwrite the current value. This ensures that any values set by a previous lobby will be replaced with your player's settings:\n\n```csharp\nExampleSetting.LobbyHosted += () =\u003e {\n    ExampleSetting.SetValue(SomeContentWarningSetting.Value);\n};\n```\n\n#### Other members\n\n##### Properties\n\n* `IsHost`: Returns true if the local player is the lobby host.\n* `InLobby`: Returns true if the local player is in a lobby.\n* `Key`: The key this instance is bound to.\n* `Value`: The current value of the key.\n* `IsSynced`: Returns true if the key is currently synched with a lobby.\n* `IsConnected`: Returns true if this instance is connected to the Steamworks API and will receive events. Will only return false if `Dispose()` was called.\n\n##### Events\n* `ValueChanged`: Invoked when the instance's value is sucessfully updated either locally or remotely.\n* `LobbyHosted`: Invoked when the current (local) player has sucessfully created a new lobby.\n\n#### Disposing\n\n`SynchronisedMetadata` implements `IDisposable`, so if for some reason an instance is no longer needed, call `Dispose()`. This will cause it to no longer receive any events from the Steamworks API, and any subsequent instance method calls will throw `ObjectDisposedException`.\n\nCheck the `IsConnected` property to see if an instance was disposed.\n\n### Localisation\n\nThe game's built-in localisation implementation is not extendable, so a custom solution is included with the mod under the `ContentWarningShop.Localisation` namespace. This patches the `Item.GetLocalizedDisplayName` and `Item.GetTootipData` methods, and `ShopItem`'s constructor.\n\nUse the `ShopLocalisation` class to add localised strings to your items. Each string is represented as a key-value pair assigned to a specific locale. For built-in strings such as display name and tooltips, the item's Unity `Object.name` (filename) is used or prefixed. For example, to localise an item with the object name \"Spookbox\", the key would also be simply \"Spookbox\".\n\nWhen adding localised strings to a locale, use the constants defined in the `LocaleKeys` static class to retrieve a locale supported by the game via the `ShopLocalisation.TryGetLocale` method. These locales are guaranteed to be available:\n\n```csharp\nShopLocalisation.TryGetLocale(LocaleKeys.English, out UnityEngine.Localization.Locale locale);\n```\n\nThe returned standard Unity Locale object can then be used via the `AddLocaleString` extension method to register a key-value pair:\n\n```csharp\nlocale?.AddLocaleString(\"Spookbox_ToolTips\", $\"{ShopLocalisation.UseGlyphString} Play;{ShopLocalisation.Use2GlyphString} Next Track\");\n```\n\nWhen localising item tooltips, the key must be the item's name suffixed with `_ToolTips` (`ShopLocalisation.TooltipsSuffix`), and the value must be a `;` delimited list. To display action glyphs (for example \"[Left Click] Toggle\", etc) use the glyph strings defined on the `ShopLocalisation` class in your strings, and the appropriate icon will be inserted into the tooltip by the game:\n\n| Const | Glyph |\n| -------- | ------- |\n| ShopLocalisation.UseGlyph | Left click |\n| ShopLocalisation.Use2Glyph | Right click |\n| ShopLocalisation.SelfieGlyph | R (Default) |\n| ShopLocalisation.ZoomGlyph | Scroll wheel |\n\n\u003e [!IMPORTANT]\n\u003e If you don't want to add full localisation ( :( ), use the `SetDefaultTooltips` extension method on your `Item` to set default tooltips. \n\u003e If you set tooltips in the editor, they won't work: this is a bug on Unity's end, not this mod. (those tooltips are serialised to null when you save them, even if they look right in the inspector)\n\u003e Setting a default is recommended in any case, but especially if you don't- or only partially provide localisation.\n\n## Compatibility\n\nThis mod patches `ItemInstanceData`'s `GetEntryIdentifier` and `GetEntryType` methods which are used by the game to serialise and deserialise items when synchronising state between players. Unfortunately, the IDs are hardcoded and there is no way to \"reserve\" one for a specific type, which means two mods patching these same methods can interpret the same values as their own entries incorrectly. To avoid most (hopefully all) collisions like this, entry IDs used by this mod are counted backwards, from `byte.MaxValue`. However, just to be safe the `Shop` class exposes a `MaxUsedEntryID` property that returns the lowest entry ID it uses, above which all other IDs are reserved.\n\n\u003e [!WARNING]  \n\u003e `MaxUsedEntryID` will not be accurate until all other mods have initialised and registered their items. Try to check for this value as late as possible, after all other mods are loaded.\n\nObviously if you aren't already using this mod, you don't want to require it just for this; use this snippet to check if this mod is in use and attempt to fetch this value as a \"soft\" dependency:\n\n```csharp\nusing System.Reflection;\n\nprivate static byte GetMaxShopReservedID() \n{\n    var assemblies = AppDomain.CurrentDomain.GetAssemblies();\n    var target = Array.Find(assemblies, a =\u003e a.GetName().Name == \"ShopAPI\");\n    if (target != null)\n    {\n        var t = target.GetType(\"ContentWarningShop.Shop\");\n        var prop = t.GetProperty(\"MaxUsedEntryID\", BindingFlags.Public | BindingFlags.Static);\n        var val = prop.GetValue(null);\n        if (val != null)\n        {\n            return (byte)val;\n        }\n    }\n    else\n    {\n        Debug.Log($\"No ShopAPI is loaded; assuming unaltered ItemInstanceData entry registry.\");\n    }\n    return 0;\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxerren09%2Fcontentwarningshopapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxerren09%2Fcontentwarningshopapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxerren09%2Fcontentwarningshopapi/lists"}