ShopCore is the base shop system for SwiftlyS2. It provides:
- A shared contract (
IShopCoreApiV1) for other plugins to register items and interact with credits. - Buy and inventory menus with category and optional subcategory navigation.
- Item preview flow from buy menus (
Preview item) for module-defined effects. - Credit economy integration via
Economy.API.v1. - Item state persistence and expiration via
Cookies.Player.V1. - Optional item selling, gifting, starting credits, and timed income.
Current repository modules built for ShopCore:
Shop_Healthshot: Healthshot consumables and timed healthshot ownership.Shop_SmokeColor: Smoke grenade color customization.Shop_Killscreen: Kill-screen visual trigger item.Shop_Bhop: Bunnyhop item with smooth convar replication behavior.Shop_Coinflip: Credit coinflip system integrated with ShopCore credits.Shop_Flags: Player chat/identity flags integration.Shop_HitSounds: Hit sound playback module.Shop_Parachute: Hold-Eparachute with per-item physics settings.Shop_PlayerColor: Player render-color module (static and rainbow).Shop_PlayerModels: Player model items with team-default fallback.Shop_Tracers: Bullet tracer beam module (static/team/random color modes).
This plugin depends on these shared interfaces:
Cookies.Player.V1Click HereEconomy.API.v1Click Here
Make sure the Cookies and Economy plugins are loaded and export matching contract DLLs.
All command aliases are configurable in shopcore.jsonc.
| Command (default alias) | Description |
|---|---|
!shop / !store |
Opens the main shop menu. |
!buy |
Opens the buy categories menu directly. |
!inventory / !inv |
Opens the inventory categories menu directly. |
!credits / !balance |
Shows your current credits balance. |
!giftcredits <target> <amount> / !gift <target> <amount> |
Transfers credits to another player (if enabled). |
!givecredits <target> <amount> / !addcredits ... |
Adds credits to a player (admin permission required). |
!removecredits <target> <amount> / !takecredits ... / !subcredits ... |
Removes credits from a player (admin permission required). |
!shopcorereload / !shopreload |
Reloads ShopCore runtime config and command bindings. |
!reloadmodulesconfig / !shopmodulesreload |
Reloads ShopCore module config sync and reloads known shop modules. |
!shopcorestatus / !shopstatus |
Shows ShopCore runtime diagnostics. |
Default admin permission: shopcore.admin.credits
ShopCore reads config from the Main section.
| Setting | Default | Description |
|---|---|---|
RegisterAsRawCommands |
true |
Registers commands as raw commands through Swiftly command service. |
OpenShopMenu |
["shop", "store"] |
Aliases for main shop menu. |
OpenBuyMenu |
["buy"] |
Aliases for buy menu. |
OpenInventoryMenu |
["inventory", "inv"] |
Aliases for inventory menu. |
ShowCredits |
["credits", "balance"] |
Aliases to display current balance. |
GiftCredits |
["giftcredits", "gift"] |
Aliases for player-to-player transfer. |
| Setting | Default | Description |
|---|---|---|
Permission |
shopcore.admin.credits |
Permission required for admin credit commands. |
GiveCredits |
["givecredits", "addcredits"] |
Aliases to add credits to a target. |
RemoveCredits |
["removecredits", "takecredits", "subcredits"] |
Aliases to remove credits from a target. |
ReloadCore |
["shopcorereload", "shopreload"] |
Aliases to reload ShopCore config/commands. |
ReloadModulesConfig |
["reloadmodulesconfig", "shopmodulesreload"] |
Aliases to re-sync module configs and reload known ShopCore modules. |
Status |
["shopcorestatus", "shopstatus"] |
Aliases to show ShopCore runtime status. |
| Setting | Default | Description |
|---|---|---|
WalletName |
credits |
Economy wallet kind used by ShopCore. |
StartingBalance |
0 |
Minimum initial balance to enforce. |
GrantStartingBalanceOncePerPlayer |
true |
Applies starting balance only once per player using cookies. |
NotifyWhenStartingBalanceApplied |
true |
Sends a localized message when starting balance is applied. |
| Setting | Default | Description |
|---|---|---|
Enabled |
false |
Enables periodic credit rewards. |
AmountPerInterval |
0 |
Credits granted each interval. |
IntervalSeconds |
300 |
Interval in seconds. |
NotifyPlayers |
false |
Sends message each time credits are granted. |
| Setting | Default | Description |
|---|---|---|
Enabled |
true |
Enables gifting credits between players. |
MinimumAmount |
1 |
Minimum transferable amount. |
AllowSelfTransfer |
false |
Allows gifting credits to yourself. |
NotifyReceiver |
true |
Notifies receiver on successful transfer. |
| Setting | Default | Description |
|---|---|---|
NotifyTargetPlayer |
true |
Notifies player when admin changes their balance. |
ClampRemovalToAvailableBalance |
true |
Caps remove command to available credits instead of failing. |
| Setting | Default | Description |
|---|---|---|
FreezePlayerWhileOpen |
false |
Freezes player while menu is open. |
EnableMenuSound |
true |
Enables menu sound effects. |
MaxVisibleItems |
5 |
Max visible items in menu page (clamped to 1..5). |
DefaultCommentTranslationKey |
shop.menu.comment |
Translation key for default menu comment line. |
| Setting | Default | Description |
|---|---|---|
AllowSelling |
true |
Enables/disables selling items. |
DefaultSellRefundRatio |
0.50 |
Fallback refund ratio when item does not define SellPrice. |
| Setting | Default | Description |
|---|---|---|
Enabled |
true |
Enables transaction ledger recording. |
MaxInMemoryEntries |
2000 |
Max entries kept when using in-memory ledger backend. |
| Setting | Default | Description |
|---|---|---|
Enabled |
false |
Enables persistent ledger backend via FreeSql. |
Provider |
sqlite |
Persistence provider (sqlite, mysql, or auto). |
ConnectionName |
default |
Swiftly database connection name used when ConnectionString is empty. |
ConnectionString |
"" |
FreeSql connection string. Supports ${PluginDataDirectory} token for sqlite paths. |
AutoSyncStructure |
true |
Auto-creates/updates the ledger table structure. |
ShopCore keeps module runtime configs centralized in Swiftly configs.
When a module calls IShopCoreApiV1.LoadModuleConfig<T>(moduleId, fileName, sectionName):
- ShopCore resolves the centralized config target in:
game/csgo/addons/swiftlys2/configs/plugins/ShopCore/modules/<fileName> - If the file does not exist, ShopCore creates it automatically.
- ShopCore reads/deserializes from that centralized file.
This lets module authors define a config class in code and server owners edit one centralized config location.
- Use
SaveModuleConfigif you want to write defaults explicitly. - JSONC is supported (comments + trailing commas).
sectionName(usuallyMain) is optional; if not found, root object is used.- Invalid relative paths (absolute paths /
..) are rejected for safety. - Keep module config file names unique (for example
healthshot_config.jsonc,smokecolor_config.jsonc) since all centralized files share the samemodules/folder.
ShopCore now supports optional subcategories by using category paths in item definitions.
- Single level:
Visuals - With subcategory:
Visuals/Smoke ColorsorVisuals > Smoke Colors
Menus resolve this as:
Category -> Itemswhen no subcategories existCategory -> Subcategory -> Itemswhen subcategories are present
This is fully backward compatible with existing one-level categories.
ShopCore supports optional item previews directly from buy item actions.
ShopItemDefinition.AllowPreviewcontrols whether preview option is shown (defaulttrue).- Buy flow is:
Buy -> Category -> Item -> (Preview item / Buy). - Modules can subscribe to
OnItemPreviewand run custom preview logic. - Modules can also trigger previews manually via
PreviewItem(player, itemId).
- Visual modules: temporary model/color/effect for a few seconds.
- Projectile/equipment modules: temporary preview window for next usage.
- Utility/consumable modules: descriptive chat preview explaining behavior.
- Movement modules: short timed trial with cooldown to avoid spam.
- Keep editable centralized files in Swiftly configs:
game/csgo/addons/swiftlys2/configs/plugins/ShopCore/modules/items_config.jsonc
game/csgo/addons/swiftlys2/configs/plugins/ShopCore/modules/items_config.jsonc
Although the contract uses decimal, current Economy integration is integer-based.
Use positive whole-number credit amounts for item prices and credit operations.
ShopItemDefinition fields:
Id,DisplayName,CategoryPrice, optionalSellPrice- optional
Duration Type(Passive,Consumable,Temporary,Permanent)Team(Any,T,CT)Enabled,CanBeSold,AllowPreview
Main capabilities exposed to other plugins:
- Register/unregister/query items.
- Load typed module configs through ShopCore centralized config path.
- Read/add/subtract/check player credits.
- Purchase and sell items with detailed
ShopTransactionResult. - Trigger item previews via
PreviewItem(...). - Enable/disable item per player.
- Read item expiration timestamp.
- Read recent transaction ledger entries (global/per-player).
- Subscribe to events:
OnBeforeItemPurchase(cancelable)OnBeforeItemSell(cancelable)OnBeforeItemToggle(cancelable)OnItemRegisteredOnItemPurchasedOnItemSoldOnItemToggledOnItemExpiredOnItemPreviewOnLedgerEntryRecorded
ShopTransactionResult returns:
Status(Success,ItemNotFound,ItemDisabled,TeamNotAllowed,AlreadyOwned,NotOwned,NotSellable,InsufficientCredits,InvalidAmount,InternalError,BlockedByModule)MessageItemCreditsAfterCreditsDeltaExpiresAtUnixSeconds
using ShopCore.Contract;
using SwiftlyS2.Shared;
using SwiftlyS2.Shared.Plugins;
public class MyModule : BasePlugin
{
private IShopCoreApiV1 shop = null!;
public MyModule(ISwiftlyCore core) : base(core) { }
public override void UseSharedInterface(IInterfaceManager interfaceManager)
{
shop = interfaceManager.GetSharedInterface<IShopCoreApiV1>("ShopCore.API.v1");
}
public override void Load(bool hotReload)
{
var config = shop.LoadModuleConfig<MyModuleConfig>(
"MyModule",
"items_config.jsonc",
"Main"
);
shop.RegisterItem(new ShopItemDefinition(
Id: "healthshot",
DisplayName: "Health Shot",
Category: "Healings",
Price: config.DefaultPrice,
SellPrice: 250,
Duration: TimeSpan.FromSeconds(30),
Type: ShopItemType.Temporary,
Team: ShopItemTeam.Any,
Enabled: true,
CanBeSold: true,
AllowPreview: true
));
}
public override void Unload() { }
}
public class MyModuleConfig
{
public decimal DefaultPrice { get; set; } = 400;
}// Example: block expensive items for non-admin players.
shop.OnBeforeItemPurchase += context =>
{
if (context.Item.Price > 5000 && !Core.Permission.PlayerHasPermission(context.Player.SteamID, "shop.vip"))
{
context.BlockLocalized("shop.error.vip_required", context.Item.DisplayName);
// or: context.Block("You need VIP to buy this item.");
}
};Default translations are in:
ShopCore/resources/translations/en.jsonc
- Build or download the plugin.
- Copy plugin output to:
.../game/csgo/addons/swiftlys2/plugins/ShopCore/ - Ensure these files exist in that folder:
ShopCore.dllresources/exports/ShopCore.Contract.dllresources/translations/en.jsonc.../addons/swiftlys2/configs/plugins/ShopCore/modules/...(for centralized module configs)
- Ensure Economy and Cookies plugins are also installed and loaded.
- Start/restart the server.
- Open
ShopCore.slnin your .NET IDE. - Build solution.
- Output is generated under:
build/plugins/ShopCore/build/plugins/ShopCore/resources/exports/
{ "Main": { "Commands": { "RegisterAsRawCommands": true, "OpenShopMenu": ["shop", "store"], "OpenBuyMenu": ["buy"], "OpenInventoryMenu": ["inventory", "inv"], "ShowCredits": ["credits", "balance"], "GiftCredits": ["giftcredits", "gift"], "Admin": { "Permission": "shopcore.admin.credits", "GiveCredits": ["givecredits", "addcredits"], "RemoveCredits": ["removecredits", "takecredits", "subcredits"], "ReloadCore": ["shopcorereload", "shopreload"], "ReloadModulesConfig": ["reloadmodulesconfig", "shopmodulesreload"], "Status": ["shopcorestatus", "shopstatus"], }, }, "Credits": { "WalletName": "credits", "StartingBalance": 0, "GrantStartingBalanceOncePerPlayer": true, "NotifyWhenStartingBalanceApplied": true, "TimedIncome": { "Enabled": false, "AmountPerInterval": 0, "IntervalSeconds": 300, "NotifyPlayers": false, }, "Transfer": { "Enabled": true, "MinimumAmount": 1, "AllowSelfTransfer": false, "NotifyReceiver": true, }, "AdminAdjustments": { "NotifyTargetPlayer": true, "ClampRemovalToAvailableBalance": true, }, }, "Menus": { "FreezePlayerWhileOpen": false, "EnableMenuSound": true, "MaxVisibleItems": 5, "DefaultCommentTranslationKey": "shop.menu.comment", }, "Behavior": { "AllowSelling": true, "DefaultSellRefundRatio": 0.5, }, "Ledger": { "Enabled": true, "MaxInMemoryEntries": 2000, "Persistence": { "Enabled": false, "Provider": "sqlite", "ConnectionName": "default", "ConnectionString": "", "AutoSyncStructure": true, }, }, }, }