@idosgames/mcp 0.1.32 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/registry/host.json +1 -1
- package/registry/index.json +177 -152
- package/registry/modules/ai.json +7 -7
- package/registry/modules/board-game.json +3 -3
- package/registry/modules/boosts.json +7 -7
- package/registry/modules/character.json +7 -7
- package/registry/modules/chat.json +7 -7
- package/registry/modules/collections.json +7 -7
- package/registry/modules/coop-events.json +7 -7
- package/registry/modules/craft.json +7 -7
- package/registry/modules/deals.json +7 -7
- package/registry/modules/events.json +7 -7
- package/registry/modules/friends.json +7 -7
- package/registry/modules/idle-rpg.json +3 -3
- package/registry/modules/inventory.json +7 -7
- package/registry/modules/invite.json +7 -7
- package/registry/modules/leaderboard.json +7 -7
- package/registry/modules/lootboxes.json +7 -7
- package/registry/modules/mailbox.json +7 -7
- package/registry/modules/marketplace.json +7 -7
- package/registry/modules/mini-game.json +3 -3
- package/registry/modules/multiplayer.json +7 -7
- package/registry/modules/premium.json +7 -7
- package/registry/modules/profile.json +9 -9
- package/registry/modules/pvp.json +7 -7
- package/registry/modules/quests.json +7 -7
- package/registry/modules/rewards.json +7 -7
- package/registry/modules/season.json +7 -7
- package/registry/modules/store.json +7 -7
- package/registry/modules/tutorials.json +7 -7
- package/registry/modules/voxelcraft.json +478 -97
- package/registry/modules/workshop.json +7 -7
- package/registry/skills/idosgames-compose-modules.json +1 -1
- package/registry/skills/idosgames-module-contract.json +2 -2
- package/registry/skills/idosgames-ui-kit.json +1 -1
- package/registry/skills/voxelcraft-characters.json +6 -0
- package/registry/skills/voxelcraft-quests.json +6 -0
- package/registry/skills/voxelcraft-worlds.json +2 -2
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
"name": "iDos Games",
|
|
33
33
|
"url": "https://idosgames.com"
|
|
34
34
|
},
|
|
35
|
-
"version": "0.1.
|
|
35
|
+
"version": "0.1.2"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@idosgames/core": "0.21.
|
|
39
|
-
"@idosgames/module-sdk": "0.
|
|
40
|
-
"@idosgames/react": "0.
|
|
38
|
+
"@idosgames/core": "0.21.6",
|
|
39
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
40
|
+
"@idosgames/react": "0.5.0",
|
|
41
41
|
"react": "19.2.7"
|
|
42
42
|
},
|
|
43
|
-
"contentHash": "
|
|
43
|
+
"contentHash": "7d314ec2a1801e22dbe5b2e2df731e7a582f8efb9cca0328de5e30b52cc27047",
|
|
44
44
|
"files": [
|
|
45
45
|
{
|
|
46
46
|
"path": "i18n.ts",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"path": "module.ts",
|
|
59
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeReferralScreen, watchReferral } from \"./ReferralScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// Invite friends as a feature of the game. The watcher sets the badge (invite rewards ready) once\n// on login and whenever the cached referral state changes — on a title with invites on.\nexport const inviteModule: Module = defineModule({\n id: \"invite\",\n meta: { name: \"Invite\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n // Always in the lobby (owner's decision 29.09.2026); with invites off the screen says so.\n const enabled = hasTitleData(\n configSection<{ IsEnabled?: boolean | null }>(ctx.client, \"Referral\"),\n );\n ctx.features.register({\n id: \"invite\",\n label: t(\"title\"),\n icon: \"link\",\n group: \"social\",\n order: 90,\n Screen: makeReferralScreen(enabled),\n });\n // The badge reads the referral definitions — only where invites are on. On a title without them\n // that read was one more request in the startup burst, and the engine's per-player limit\n // answered it 429.\n if (enabled) watchReferral(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"invite\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
59
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeReferralScreen, watchReferral } from \"./ReferralScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// Invite friends as a feature of the game. The watcher sets the badge (invite rewards ready) once\n// on login and whenever the cached referral state changes — on a title with invites on.\nexport const inviteModule: Module = defineModule({\n id: \"invite\",\n meta: { name: \"Invite\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n // Always in the lobby (owner's decision 29.09.2026); with invites off the screen says so.\n const enabled = hasTitleData(\n configSection<{ IsEnabled?: boolean | null }>(ctx.client, \"Referral\"),\n );\n ctx.features.register({\n id: \"invite\",\n label: () => t(\"title\"),\n icon: \"link\",\n group: \"social\",\n order: 90,\n Screen: makeReferralScreen(enabled),\n });\n // The badge reads the referral definitions — only where invites are on. On a title without them\n // that read was one more request in the startup burst, and the engine's per-player limit\n // answered it 429.\n if (enabled) watchReferral(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"invite\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"path": "ReferralScreen.tsx",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"path": "module.meta.json",
|
|
67
|
-
"content": "{\n \"id\": \"invite\",\n \"type\": \"feature\",\n \"inBase\": true,\n \"summary\": \"Invite friends for any game: your code and link to share, friends who came by it, rewards for them, and entering a friend's code in a popup.\",\n \"description\": \"A ready invite-a-friend screen (Unity V2/Runtime/UI/Referral) for ANY game, built on the @idosgames/react/ui kit and client.referral. The player's own short code (minted by the server when the screen opens) with 'Copy' and 'Share link' (the system share sheet, else the clipboard), how many friends came by it and the reward ladder for them (claim one or all). Entering a friend's code is a popup: it shows the newcomer's gift, reads the code the way the server does (8 letters, separators and case ignored) and pays the gift once. A player bound at login by an invite link sees 'You joined by a friend's code'. Registers the 'invite' feature — always shown in the lobby; while the title has invites off (Referral.IsEnabled, off by default) the screen says so and makes no request. Its badge counts invite rewards ready. Ships title-seed.json: a gift for the newcomer and rewards for 1, 3 and 5 friends.\",\n \"provides\": [\n \"own invite code and link\",\n \"share or copy the invite\",\n \"friends who came by your code\",\n \"invite reward ladder with claim all\",\n \"enter a friend's code popup\",\n \"invite badge\",\n \"demo referral rewards\"\n ],\n \"tags\": [\"referral\", \"invite\", \"friends\", \"growth\", \"social\", \"ui\", \"screen\"],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
67
|
+
"content": "{\n \"id\": \"invite\",\n \"type\": \"feature\",\n \"inBase\": true,\n \"summary\": \"Invite friends for any game: your code and link to share, friends who came by it, rewards for them, and entering a friend's code in a popup.\",\n \"description\": \"A ready invite-a-friend screen (Unity V2/Runtime/UI/Referral) for ANY game, built on the @idosgames/react/ui kit and client.referral. The player's own short code (minted by the server when the screen opens) with 'Copy' and 'Share link' (the system share sheet, else the clipboard), how many friends came by it and the reward ladder for them (claim one or all). Entering a friend's code is a popup: it shows the newcomer's gift, reads the code the way the server does (8 letters, separators and case ignored) and pays the gift once. A player bound at login by an invite link sees 'You joined by a friend's code'. Registers the 'invite' feature — always shown in the lobby; while the title has invites off (Referral.IsEnabled, off by default) the screen says so and makes no request. Its badge counts invite rewards ready. Ships title-seed.json: a gift for the newcomer and rewards for 1, 3 and 5 friends.\",\n \"provides\": [\n \"own invite code and link\",\n \"share or copy the invite\",\n \"friends who came by your code\",\n \"invite reward ladder with claim all\",\n \"enter a friend's code popup\",\n \"invite badge\",\n \"demo referral rewards\"\n ],\n \"tags\": [\"referral\", \"invite\", \"friends\", \"growth\", \"social\", \"ui\", \"screen\"],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.2\"\n}\n"
|
|
68
68
|
}
|
|
69
69
|
]
|
|
70
70
|
}
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"name": "iDos Games",
|
|
34
34
|
"url": "https://idosgames.com"
|
|
35
35
|
},
|
|
36
|
-
"version": "0.1.
|
|
36
|
+
"version": "0.1.2"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@idosgames/core": "0.21.
|
|
40
|
-
"@idosgames/module-sdk": "0.
|
|
41
|
-
"@idosgames/react": "0.
|
|
39
|
+
"@idosgames/core": "0.21.6",
|
|
40
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
41
|
+
"@idosgames/react": "0.5.0",
|
|
42
42
|
"react": "19.2.7"
|
|
43
43
|
},
|
|
44
|
-
"contentHash": "
|
|
44
|
+
"contentHash": "587cf8ee4685d907eaebfb1a0f96a7b269685cfefd990c05b057cf9626e75193",
|
|
45
45
|
"files": [
|
|
46
46
|
{
|
|
47
47
|
"path": "components/CycleReward.tsx",
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"path": "module.ts",
|
|
80
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeCycleRewardWatcher } from \"./components/CycleReward\";\nimport { makeLeaderboardScreen } from \"./LeaderboardScreen\";\nimport { hasTitleData, type LeaderboardSection } from \"./model\";\nimport { t } from \"./i18n\";\n\n// Leaderboards as a feature of the game. The always-on watcher pops up the reward of a finished\n// round and keeps the badge (a rank reward or a milestone waiting).\nexport const leaderboardModule: Module = defineModule({\n id: \"leaderboard\",\n meta: { name: \"Leaderboards\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"leaderboard\",\n label: t(\"title\"),\n icon: \"trophy\",\n group: \"events\",\n order: 30,\n primary: true,\n available: false,\n Screen: makeLeaderboardScreen(ctx.features),\n });\n ctx.features.setAvailable(\n \"leaderboard\",\n hasTitleData(\n configSection<LeaderboardSection>(ctx.client, \"Leaderboard\"),\n ),\n \"no enabled leaderboard on the title\",\n );\n ctx.registerPanel({\n id: \"cycle-reward\",\n slot: \"modal\",\n activeOnly: false,\n component: makeCycleRewardWatcher(ctx.features),\n });\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"leaderboard\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
80
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeCycleRewardWatcher } from \"./components/CycleReward\";\nimport { makeLeaderboardScreen } from \"./LeaderboardScreen\";\nimport { hasTitleData, type LeaderboardSection } from \"./model\";\nimport { t } from \"./i18n\";\n\n// Leaderboards as a feature of the game. The always-on watcher pops up the reward of a finished\n// round and keeps the badge (a rank reward or a milestone waiting).\nexport const leaderboardModule: Module = defineModule({\n id: \"leaderboard\",\n meta: { name: \"Leaderboards\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"leaderboard\",\n label: () => t(\"title\"),\n icon: \"trophy\",\n group: \"events\",\n order: 30,\n primary: true,\n available: false,\n Screen: makeLeaderboardScreen(ctx.features),\n });\n ctx.features.setAvailable(\n \"leaderboard\",\n hasTitleData(\n configSection<LeaderboardSection>(ctx.client, \"Leaderboard\"),\n ),\n \"no enabled leaderboard on the title\",\n );\n ctx.registerPanel({\n id: \"cycle-reward\",\n slot: \"modal\",\n activeOnly: false,\n component: makeCycleRewardWatcher(ctx.features),\n });\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"leaderboard\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"path": "module.meta.json",
|
|
84
|
-
"content": "{\n \"id\": \"leaderboard\",\n \"type\": \"feature\",\n \"summary\": \"Leaderboards screen for any game: boards as tabs, top players and my rank, rank rewards, score milestones and the reward of the last round.\",\n \"description\": \"A ready leaderboards screen for ANY game, built on the @idosgames/react/ui kit. The title's enabled leaderboards are tabs; each shows a card with the player's score, rank and the countdown to the end of the round, and the top list with the player highlighted and medals for the top three. Rank rewards and score milestones open as popups (claim a milestone there). When a round ends, the reward for the place the player took pops up by itself with a Claim button (like LeaderboardCycleRewardPopup in the Unity SDK). A game that has something to play for the score registers a 'play' feature and a Play button appears on the card. Registers the 'leaderboard' feature; its badge counts boards with a reward waiting. Ships title-seed.json: a weekly best-score leaderboard with rank rewards.\",\n \"provides\": [\n \"leaderboard screen with several boards as tabs\",\n \"top players and my rank\",\n \"round countdown\",\n \"rank rewards popup\",\n \"score milestones with claiming\",\n \"auto popup for the reward of the finished round\",\n \"badge for waiting rewards\",\n \"demo leaderboard settings\"\n ],\n \"tags\": [\n \"leaderboard\",\n \"ranking\",\n \"top\",\n \"milestones\",\n \"rank-rewards\",\n \"ui\",\n \"screen\",\n \"events\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
84
|
+
"content": "{\n \"id\": \"leaderboard\",\n \"type\": \"feature\",\n \"summary\": \"Leaderboards screen for any game: boards as tabs, top players and my rank, rank rewards, score milestones and the reward of the last round.\",\n \"description\": \"A ready leaderboards screen for ANY game, built on the @idosgames/react/ui kit. The title's enabled leaderboards are tabs; each shows a card with the player's score, rank and the countdown to the end of the round, and the top list with the player highlighted and medals for the top three. Rank rewards and score milestones open as popups (claim a milestone there). When a round ends, the reward for the place the player took pops up by itself with a Claim button (like LeaderboardCycleRewardPopup in the Unity SDK). A game that has something to play for the score registers a 'play' feature and a Play button appears on the card. Registers the 'leaderboard' feature; its badge counts boards with a reward waiting. Ships title-seed.json: a weekly best-score leaderboard with rank rewards.\",\n \"provides\": [\n \"leaderboard screen with several boards as tabs\",\n \"top players and my rank\",\n \"round countdown\",\n \"rank rewards popup\",\n \"score milestones with claiming\",\n \"auto popup for the reward of the finished round\",\n \"badge for waiting rewards\",\n \"demo leaderboard settings\"\n ],\n \"tags\": [\n \"leaderboard\",\n \"ranking\",\n \"top\",\n \"milestones\",\n \"rank-rewards\",\n \"ui\",\n \"screen\",\n \"events\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.2\"\n}\n"
|
|
85
85
|
}
|
|
86
86
|
]
|
|
87
87
|
}
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
"name": "iDos Games",
|
|
30
30
|
"url": "https://idosgames.com"
|
|
31
31
|
},
|
|
32
|
-
"version": "0.1.
|
|
32
|
+
"version": "0.1.1"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@idosgames/core": "0.21.
|
|
36
|
-
"@idosgames/module-sdk": "0.
|
|
37
|
-
"@idosgames/react": "0.
|
|
35
|
+
"@idosgames/core": "0.21.6",
|
|
36
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
37
|
+
"@idosgames/react": "0.5.0",
|
|
38
38
|
"react": "19.2.7"
|
|
39
39
|
},
|
|
40
|
-
"contentHash": "
|
|
40
|
+
"contentHash": "7306c6a8ffcd0d526839a7ef2827dd2dbeabc1d0b6827005ae939bbdd7ec54a3",
|
|
41
41
|
"files": [
|
|
42
42
|
{
|
|
43
43
|
"path": "i18n.ts",
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"path": "module.ts",
|
|
60
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { LootboxesScreen, watchLootboxes } from \"./LootboxesScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// Chests as a feature of the game. The inventory hands a chest or key item over here\n// (features.open(\"lootboxes\", { itemID })). Badge: chests the player can open right now with the\n// items they hold — read from the cached inventory, no request of its own.\nexport const lootboxesModule: Module = defineModule({\n id: \"lootboxes\",\n meta: { name: \"Chests\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"lootboxes\",\n label: t(\"title\"),\n icon: \"chest\",\n group: \"economy\",\n order: 55,\n available: false,\n Screen: LootboxesScreen,\n });\n const enabled = hasTitleData(configSection(ctx.client, \"Lootbox\"));\n ctx.features.setAvailable(\n \"lootboxes\",\n enabled,\n \"no lootboxes on the title (Lootbox)\",\n );\n if (enabled) watchLootboxes(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"lootboxes\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
60
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { LootboxesScreen, watchLootboxes } from \"./LootboxesScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// Chests as a feature of the game. The inventory hands a chest or key item over here\n// (features.open(\"lootboxes\", { itemID })). Badge: chests the player can open right now with the\n// items they hold — read from the cached inventory, no request of its own.\nexport const lootboxesModule: Module = defineModule({\n id: \"lootboxes\",\n meta: { name: \"Chests\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"lootboxes\",\n label: () => t(\"title\"),\n icon: \"chest\",\n group: \"economy\",\n order: 55,\n available: false,\n Screen: LootboxesScreen,\n });\n const enabled = hasTitleData(configSection(ctx.client, \"Lootbox\"));\n ctx.features.setAvailable(\n \"lootboxes\",\n enabled,\n \"no lootboxes on the title (Lootbox)\",\n );\n if (enabled) watchLootboxes(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"lootboxes\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"path": "module.meta.json",
|
|
64
|
-
"content": "{\n \"id\": \"lootboxes\",\n \"type\": \"feature\",\n \"summary\": \"Chests for any game: the title's lootboxes with every way to open them, once or ten at a time, with an opening animation.\",\n \"description\": \"A ready chests screen (Unity LootboxOpeningPopUp) for ANY game, built on the @idosgames/react/ui kit and client.lootbox. Every lootbox of the title as a card for each way to open it (currencies, a chest item, a key); 'Open' once, and 'Open ×10' where the lootbox's ceiling (MaxOpenCount) and the player's balance allow — the box shakes while the server rolls it, bursts, and the rewards fly out. The inventory hands a chest or key item over (ctx.features.open('lootboxes', { itemID })) — the screen then shows what that item opens. Registers the 'lootboxes' feature; its badge counts chests the player can open with the items they hold.\",\n \"provides\": [\n \"lootbox list with every way to open\",\n \"opening animation with rewards\",\n \"open ten at once\",\n \"open a chest with a key item\",\n \"badge for openable chests\"\n ],\n \"tags\": [\"lootbox\", \"chest\", \"gacha\", \"rewards\", \"ui\", \"screen\", \"economy\"],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
64
|
+
"content": "{\n \"id\": \"lootboxes\",\n \"type\": \"feature\",\n \"summary\": \"Chests for any game: the title's lootboxes with every way to open them, once or ten at a time, with an opening animation.\",\n \"description\": \"A ready chests screen (Unity LootboxOpeningPopUp) for ANY game, built on the @idosgames/react/ui kit and client.lootbox. Every lootbox of the title as a card for each way to open it (currencies, a chest item, a key); 'Open' once, and 'Open ×10' where the lootbox's ceiling (MaxOpenCount) and the player's balance allow — the box shakes while the server rolls it, bursts, and the rewards fly out. The inventory hands a chest or key item over (ctx.features.open('lootboxes', { itemID })) — the screen then shows what that item opens. Registers the 'lootboxes' feature; its badge counts chests the player can open with the items they hold.\",\n \"provides\": [\n \"lootbox list with every way to open\",\n \"opening animation with rewards\",\n \"open ten at once\",\n \"open a chest with a key item\",\n \"badge for openable chests\"\n ],\n \"tags\": [\"lootbox\", \"chest\", \"gacha\", \"rewards\", \"ui\", \"screen\", \"economy\"],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.1\"\n}\n"
|
|
65
65
|
}
|
|
66
66
|
]
|
|
67
67
|
}
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"name": "iDos Games",
|
|
34
34
|
"url": "https://idosgames.com"
|
|
35
35
|
},
|
|
36
|
-
"version": "0.1.
|
|
36
|
+
"version": "0.1.1"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@idosgames/core": "0.21.
|
|
40
|
-
"@idosgames/module-sdk": "0.
|
|
41
|
-
"@idosgames/react": "0.
|
|
39
|
+
"@idosgames/core": "0.21.6",
|
|
40
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
41
|
+
"@idosgames/react": "0.5.0",
|
|
42
42
|
"react": "19.2.7"
|
|
43
43
|
},
|
|
44
|
-
"contentHash": "
|
|
44
|
+
"contentHash": "6863f9fdc8aa3520038c623143ed8b3905c8ab7ae4f4498ab999236732e9e60c",
|
|
45
45
|
"files": [
|
|
46
46
|
{
|
|
47
47
|
"path": "components/FriendChip.tsx",
|
|
@@ -89,11 +89,11 @@
|
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"path": "module.ts",
|
|
92
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeMailboxScreen, watchMailbox } from \"./MailboxScreen\";\nimport { mailboxEnabled } from \"./model\";\nimport { t } from \"./i18n\";\n\n// The mailbox as a feature of the game: letters with rewards, broadcasts, friends' gifts and\n// transfers, missed rewards of other systems. The always-on watcher keeps the badge — one cheap\n// counter read on start and whenever the player comes back to the tab; every mailbox action brings\n// fresh counters itself (mailbox:countersChanged). Hidden until the title turns the mailbox on.\nexport const mailboxModule: Module = defineModule({\n id: \"mailbox\",\n meta: { name: \"Mailbox\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n const enabled = mailboxEnabled(\n configSection<{ Enabled?: boolean | null }>(ctx.client, \"Mailbox\"),\n );\n ctx.features.register({\n id: \"mailbox\",\n label: t(\"title\"),\n icon: \"gift\",\n group: \"social\",\n order: 85,\n available: false,\n Screen: makeMailboxScreen(ctx.features),\n });\n ctx.features.setAvailable(\n \"mailbox\",\n enabled,\n \"the mailbox is off on the title (Mailbox.Enabled)\",\n );\n if (enabled) watchMailbox(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"mailbox\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
92
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeMailboxScreen, watchMailbox } from \"./MailboxScreen\";\nimport { mailboxEnabled } from \"./model\";\nimport { t } from \"./i18n\";\n\n// The mailbox as a feature of the game: letters with rewards, broadcasts, friends' gifts and\n// transfers, missed rewards of other systems. The always-on watcher keeps the badge — one cheap\n// counter read on start and whenever the player comes back to the tab; every mailbox action brings\n// fresh counters itself (mailbox:countersChanged). Hidden until the title turns the mailbox on.\nexport const mailboxModule: Module = defineModule({\n id: \"mailbox\",\n meta: { name: \"Mailbox\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n const enabled = mailboxEnabled(\n configSection<{ Enabled?: boolean | null }>(ctx.client, \"Mailbox\"),\n );\n ctx.features.register({\n id: \"mailbox\",\n label: () => t(\"title\"),\n icon: \"gift\",\n group: \"social\",\n order: 85,\n available: false,\n Screen: makeMailboxScreen(ctx.features),\n });\n ctx.features.setAvailable(\n \"mailbox\",\n enabled,\n \"the mailbox is off on the title (Mailbox.Enabled)\",\n );\n if (enabled) watchMailbox(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"mailbox\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "module.meta.json",
|
|
96
|
-
"content": "{\n \"id\": \"mailbox\",\n \"type\": \"feature\",\n \"inBase\": true,\n \"summary\": \"Mailbox for any game: letters with rewards, publisher broadcasts, gifts from friends, resource transfers and missed rewards of other systems — with 'Claim all'.\",\n \"description\": \"A ready mailbox screen for ANY game, built on the @idosgames/react/ui kit and client.mailbox. Tabs by category (all, rewards, gifts, system); a letter opens with its text in the player's language, the sender, a banner, the rewards and a button to the screen or page the letter links to. 'Claim' per letter and 'Claim all' in one server operation; rewards of other systems the player has not taken yet (leaderboard cycle, team event prize, invite rewards) are shown on top and claimed right here. 'Send gifts': a free daily gift to friends ('to everyone' in one tap) and a transfer of own coins or items to one friend — declined or expired transfers come back. 'Delete read' cleans up. The lobby badge counts unread letters and rewards waiting. The server owns everything: limits, friendship, expiry, what can be transferred. Opens straight on a friend with ctx.features.open('mailbox', { friendUserID, mode: 'gift' | 'transfer' }) — the friends system does that from a friend's card. Registers the 'mailbox' feature, hidden until the title turns the mailbox on. Ships title-seed.json: the mailbox on, a welcome template, a daily coin gift and coin/potion transfers.\",\n \"provides\": [\n \"mailbox screen\",\n \"letters with rewards and claim all\",\n \"publisher broadcasts in the inbox\",\n \"gifts to friends\",\n \"resource transfers between friends\",\n \"missed rewards of other systems\",\n \"mailbox badge\"\n ],\n \"tags\": [\n \"mailbox\",\n \"mail\",\n \"inbox\",\n \"gifts\",\n \"rewards\",\n \"social\",\n \"ui\",\n \"screen\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
96
|
+
"content": "{\n \"id\": \"mailbox\",\n \"type\": \"feature\",\n \"inBase\": true,\n \"summary\": \"Mailbox for any game: letters with rewards, publisher broadcasts, gifts from friends, resource transfers and missed rewards of other systems — with 'Claim all'.\",\n \"description\": \"A ready mailbox screen for ANY game, built on the @idosgames/react/ui kit and client.mailbox. Tabs by category (all, rewards, gifts, system); a letter opens with its text in the player's language, the sender, a banner, the rewards and a button to the screen or page the letter links to. 'Claim' per letter and 'Claim all' in one server operation; rewards of other systems the player has not taken yet (leaderboard cycle, team event prize, invite rewards) are shown on top and claimed right here. 'Send gifts': a free daily gift to friends ('to everyone' in one tap) and a transfer of own coins or items to one friend — declined or expired transfers come back. 'Delete read' cleans up. The lobby badge counts unread letters and rewards waiting. The server owns everything: limits, friendship, expiry, what can be transferred. Opens straight on a friend with ctx.features.open('mailbox', { friendUserID, mode: 'gift' | 'transfer' }) — the friends system does that from a friend's card. Registers the 'mailbox' feature, hidden until the title turns the mailbox on. Ships title-seed.json: the mailbox on, a welcome template, a daily coin gift and coin/potion transfers.\",\n \"provides\": [\n \"mailbox screen\",\n \"letters with rewards and claim all\",\n \"publisher broadcasts in the inbox\",\n \"gifts to friends\",\n \"resource transfers between friends\",\n \"missed rewards of other systems\",\n \"mailbox badge\"\n ],\n \"tags\": [\n \"mailbox\",\n \"mail\",\n \"inbox\",\n \"gifts\",\n \"rewards\",\n \"social\",\n \"ui\",\n \"screen\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.1\"\n}\n"
|
|
97
97
|
}
|
|
98
98
|
]
|
|
99
99
|
}
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"name": "iDos Games",
|
|
34
34
|
"url": "https://idosgames.com"
|
|
35
35
|
},
|
|
36
|
-
"version": "0.1.
|
|
36
|
+
"version": "0.1.2"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@idosgames/core": "0.21.
|
|
40
|
-
"@idosgames/module-sdk": "0.
|
|
41
|
-
"@idosgames/react": "0.
|
|
39
|
+
"@idosgames/core": "0.21.6",
|
|
40
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
41
|
+
"@idosgames/react": "0.5.0",
|
|
42
42
|
"react": "19.2.7"
|
|
43
43
|
},
|
|
44
|
-
"contentHash": "
|
|
44
|
+
"contentHash": "9c279916de34ee33f0993ebc3f73dec8885a343110e6ad36f3c984a909b45060",
|
|
45
45
|
"files": [
|
|
46
46
|
{
|
|
47
47
|
"path": "components/controls.tsx",
|
|
@@ -89,11 +89,11 @@
|
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"path": "module.ts",
|
|
92
|
-
"content": "import {\n defineModule,\n type Module,\n type ModuleContext,\n} from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeMarketplaceScreen } from \"./MarketplaceScreen\";\nimport { hasTitleData, marketplaceBadge } from \"./model\";\nimport { t } from \"./i18n\";\n\n// The marketplace as a feature of the game. Badge: things to claim (won lots, expired escrow) and\n// incoming trades waiting for an answer — read once at start and after every marketplace action.\nexport const marketplaceModule: Module = defineModule({\n id: \"marketplace\",\n meta: { name: \"Marketplace\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"marketplace\",\n label: t(\"title\"),\n icon: \"market\",\n group: \"economy\",\n order: 70,\n available: false,\n Screen: makeMarketplaceScreen(ctx.features),\n });\n const decide = () =>\n ctx.features.setAvailable(\n \"marketplace\",\n hasTitleData(\n configSection<{ Enabled?: boolean | null }>(\n ctx.client,\n \"Marketplace\",\n ),\n ),\n \"the marketplace is not set up on the title, or switched off\",\n );\n decide();\n // The engine does not put Marketplace into the client's title config: read it once with its own\n // call (it lands in the same config section). Without this the tab never showed on any title.\n if (!configSection(ctx.client, \"Marketplace\"))\n void ctx.client.marketplace.getDefinitions().then(decide);\n watchBadge(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"marketplace\" }),\n describeActions: {},\n });\n },\n});\n\nfunction watchBadge(ctx: ModuleContext): void {\n const { client, features } = ctx;\n ctx.effect(() => {\n // A title without the section has no marketplace — no request at all. The section arrives by\n // its own call (module setup), so the watcher starts once the definitions are in.\n let id: ReturnType<typeof setInterval> | null = null;\n const check = () =>\n void client.marketplace\n .getMyState()\n .then(\n (res) =>\n res.ok &&\n features.setBadge(\"marketplace\", marketplaceBadge(res.data)),\n );\n const start = () => {\n if (id || !hasTitleData(configSection(client, \"Marketplace\"))) return;\n check();\n id = setInterval(check, 5 * 60_000);\n };\n start();\n const off = client.on(\"marketplace:definitionsLoaded\", start);\n return () => {\n off();\n if (id) clearInterval(id);\n };\n });\n}\n"
|
|
92
|
+
"content": "import {\n defineModule,\n type Module,\n type ModuleContext,\n} from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeMarketplaceScreen } from \"./MarketplaceScreen\";\nimport { hasTitleData, marketplaceBadge } from \"./model\";\nimport { t } from \"./i18n\";\n\n// The marketplace as a feature of the game. Badge: things to claim (won lots, expired escrow) and\n// incoming trades waiting for an answer — read once at start and after every marketplace action.\nexport const marketplaceModule: Module = defineModule({\n id: \"marketplace\",\n meta: { name: \"Marketplace\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"marketplace\",\n label: () => t(\"title\"),\n icon: \"market\",\n group: \"economy\",\n order: 70,\n available: false,\n Screen: makeMarketplaceScreen(ctx.features),\n });\n const decide = () =>\n ctx.features.setAvailable(\n \"marketplace\",\n hasTitleData(\n configSection<{ Enabled?: boolean | null }>(\n ctx.client,\n \"Marketplace\",\n ),\n ),\n \"the marketplace is not set up on the title, or switched off\",\n );\n decide();\n // The engine does not put Marketplace into the client's title config: read it once with its own\n // call (it lands in the same config section). Without this the tab never showed on any title.\n if (!configSection(ctx.client, \"Marketplace\"))\n void ctx.client.marketplace.getDefinitions().then(decide);\n watchBadge(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"marketplace\" }),\n describeActions: {},\n });\n },\n});\n\nfunction watchBadge(ctx: ModuleContext): void {\n const { client, features } = ctx;\n ctx.effect(() => {\n // A title without the section has no marketplace — no request at all. The section arrives by\n // its own call (module setup), so the watcher starts once the definitions are in.\n let id: ReturnType<typeof setInterval> | null = null;\n const check = () =>\n void client.marketplace\n .getMyState()\n .then(\n (res) =>\n res.ok &&\n features.setBadge(\"marketplace\", marketplaceBadge(res.data)),\n );\n const start = () => {\n if (id || !hasTitleData(configSection(client, \"Marketplace\"))) return;\n check();\n id = setInterval(check, 5 * 60_000);\n };\n start();\n const off = client.on(\"marketplace:definitionsLoaded\", start);\n return () => {\n off();\n if (id) clearInterval(id);\n };\n });\n}\n"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "module.meta.json",
|
|
96
|
-
"content": "{\n \"id\": \"marketplace\",\n \"type\": \"feature\",\n \"summary\": \"Marketplace screen for any game: player listings and auctions, selling, bids, direct trades, claims and history.\",\n \"description\": \"A ready player marketplace (Unity Alikhan/Marketplace) for ANY game, built on the @idosgames/react/ui kit and client.marketplace. Tabs: the market (items grouped with their cheapest price), my lots, my bids, direct trades, things to claim and history. Everything detailed is a popup: an item's offers (cheapest listing first, then auctions ending soonest), one offer (buy, bid with the engine's minimum next bid, cancel your own), selling (pick an item — only tradable, never a worn one — then listing or auction, amount, price in a currency tradable between players, duration and the commission), and incoming trades (accept / decline). The server holds the escrow and settles every trade. Registers the 'marketplace' feature; its badge counts things to claim and trades waiting. Ships title-seed.json: listings and auctions for currencies with a 5% commission.\",\n \"provides\": [\n \"player marketplace screen\",\n \"listings and auctions\",\n \"sell popup with price and duration\",\n \"bids with the minimum next bid\",\n \"direct trades\",\n \"claims and history\",\n \"marketplace badge\",\n \"demo marketplace rules\"\n ],\n \"tags\": [\n \"marketplace\",\n \"trading\",\n \"auction\",\n \"listing\",\n \"p2p\",\n \"ui\",\n \"screen\",\n \"economy\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
96
|
+
"content": "{\n \"id\": \"marketplace\",\n \"type\": \"feature\",\n \"summary\": \"Marketplace screen for any game: player listings and auctions, selling, bids, direct trades, claims and history.\",\n \"description\": \"A ready player marketplace (Unity Alikhan/Marketplace) for ANY game, built on the @idosgames/react/ui kit and client.marketplace. Tabs: the market (items grouped with their cheapest price), my lots, my bids, direct trades, things to claim and history. Everything detailed is a popup: an item's offers (cheapest listing first, then auctions ending soonest), one offer (buy, bid with the engine's minimum next bid, cancel your own), selling (pick an item — only tradable, never a worn one — then listing or auction, amount, price in a currency tradable between players, duration and the commission), and incoming trades (accept / decline). The server holds the escrow and settles every trade. Registers the 'marketplace' feature; its badge counts things to claim and trades waiting. Ships title-seed.json: listings and auctions for currencies with a 5% commission.\",\n \"provides\": [\n \"player marketplace screen\",\n \"listings and auctions\",\n \"sell popup with price and duration\",\n \"bids with the minimum next bid\",\n \"direct trades\",\n \"claims and history\",\n \"marketplace badge\",\n \"demo marketplace rules\"\n ],\n \"tags\": [\n \"marketplace\",\n \"trading\",\n \"auction\",\n \"listing\",\n \"p2p\",\n \"ui\",\n \"screen\",\n \"economy\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.2\"\n}\n"
|
|
97
97
|
}
|
|
98
98
|
]
|
|
99
99
|
}
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"version": "0.1.1"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@idosgames/core": "0.21.
|
|
34
|
-
"@idosgames/module-sdk": "0.
|
|
35
|
-
"@idosgames/react": "0.
|
|
33
|
+
"@idosgames/core": "0.21.6",
|
|
34
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
35
|
+
"@idosgames/react": "0.5.0",
|
|
36
36
|
"react": "19.2.7"
|
|
37
37
|
},
|
|
38
38
|
"contentHash": "eb39139d93bfb84e5a20d2485b081a264234452b5d90978fdf15b940cf3b7ac0",
|
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
"name": "iDos Games",
|
|
33
33
|
"url": "https://idosgames.com"
|
|
34
34
|
},
|
|
35
|
-
"version": "0.1.
|
|
35
|
+
"version": "0.1.3"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@idosgames/core": "0.21.
|
|
39
|
-
"@idosgames/module-sdk": "0.
|
|
40
|
-
"@idosgames/react": "0.
|
|
38
|
+
"@idosgames/core": "0.21.6",
|
|
39
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
40
|
+
"@idosgames/react": "0.5.0",
|
|
41
41
|
"react": "19.2.7"
|
|
42
42
|
},
|
|
43
|
-
"contentHash": "
|
|
43
|
+
"contentHash": "8d98faeff282b02addea3cb3db2dee565a5f64e244d13c16118d68822ab80169",
|
|
44
44
|
"files": [
|
|
45
45
|
{
|
|
46
46
|
"path": "components/CodeForm.tsx",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"path": "module.ts",
|
|
79
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeMultiplayerScreen } from \"./MultiplayerScreen\";\nimport { hasTitleData } from \"./model\";\nimport { createRooms } from \"./rooms\";\nimport { t } from \"./i18n\";\n\n// The rooms system of the app: the lobby tab with the rooms, and the owner of the player's room and\n// its connection. Games that play online register in `ctx.multiplayer` and take the room in `start`;\n// they never create or join rooms. The tab shows while multiplayer is on for the title AND the app has\n// a game that plays online. No badge and no startup request.\nexport const multiplayerModule: Module = defineModule({\n id: \"multiplayer\",\n meta: { name: \"Multiplayer\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n const rooms = createRooms({\n client: ctx.client,\n games: ctx.multiplayer,\n navigate: (mode) => ctx.navigate(mode),\n loading: ctx.loading,\n connectingLabel: t(\"connecting\"),\n });\n ctx.features.register({\n id: \"multiplayer\",\n label: t(\"title\"),\n icon: \"globe\",\n group: \"social\",\n order: 75,\n available: false,\n Screen: makeMultiplayerScreen({\n rooms,\n games: ctx.multiplayer,\n modes: ctx.modes,\n }),\n });\n const on = hasTitleData(configSection(ctx.client, \"Multiplayer\"));\n // Games register in their own setup() — before or after this one: follow the registry.\n const refresh = () =>\n ctx.features.setAvailable(\n \"multiplayer\",\n on && ctx.multiplayer.listGames().length > 0,\n on\n ? \"no game of the app plays online (ctx.multiplayer.registerGame)\"\n : \"multiplayer is off on the title (Multiplayer.SystemState.Enabled)\",\n );\n refresh();\n ctx.multiplayer.subscribe(refresh);\n ctx.exposeToAgent({\n state: () => {\n const s = rooms.get();\n return {\n ui: \"dom\",\n feature: \"multiplayer\",\n games: ctx.multiplayer.listGames().map((g) => g.mode),\n room: s.session\n ? {\n id: s.session.roomId,\n mode: s.session.mode,\n host: s.session.isHost,\n members: s.session.members.size,\n started: s.started,\n }\n : null,\n };\n },\n describeActions: {},\n });\n },\n});\n"
|
|
79
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makeMultiplayerScreen } from \"./MultiplayerScreen\";\nimport { hasTitleData } from \"./model\";\nimport { createRooms } from \"./rooms\";\nimport { t } from \"./i18n\";\n\n// The rooms system of the app: the lobby tab with the rooms, and the owner of the player's room and\n// its connection. Games that play online register in `ctx.multiplayer` and take the room in `start`;\n// they never create or join rooms. The tab shows while multiplayer is on for the title AND the app has\n// a game that plays online. No badge and no startup request.\nexport const multiplayerModule: Module = defineModule({\n id: \"multiplayer\",\n meta: { name: \"Multiplayer\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n const rooms = createRooms({\n client: ctx.client,\n games: ctx.multiplayer,\n navigate: (mode) => ctx.navigate(mode),\n loading: ctx.loading,\n connectingLabel: t(\"connecting\"),\n });\n ctx.features.register({\n id: \"multiplayer\",\n label: () => t(\"title\"),\n icon: \"globe\",\n group: \"social\",\n order: 75,\n available: false,\n Screen: makeMultiplayerScreen({\n rooms,\n games: ctx.multiplayer,\n modes: ctx.modes,\n }),\n });\n const on = hasTitleData(configSection(ctx.client, \"Multiplayer\"));\n // Games register in their own setup() — before or after this one: follow the registry.\n const refresh = () =>\n ctx.features.setAvailable(\n \"multiplayer\",\n on && ctx.multiplayer.listGames().length > 0,\n on\n ? \"no game of the app plays online (ctx.multiplayer.registerGame)\"\n : \"multiplayer is off on the title (Multiplayer.SystemState.Enabled)\",\n );\n refresh();\n ctx.multiplayer.subscribe(refresh);\n ctx.exposeToAgent({\n state: () => {\n const s = rooms.get();\n return {\n ui: \"dom\",\n feature: \"multiplayer\",\n games: ctx.multiplayer.listGames().map((g) => g.mode),\n room: s.session\n ? {\n id: s.session.roomId,\n mode: s.session.mode,\n host: s.session.isHost,\n members: s.session.members.size,\n started: s.started,\n }\n : null,\n };\n },\n describeActions: {},\n });\n },\n});\n"
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
"path": "MultiplayerScreen.tsx",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"path": "module.meta.json",
|
|
95
|
-
"content": "{\n \"id\": \"multiplayer\",\n \"type\": \"feature\",\n \"summary\": \"The rooms system for any game: open rooms of the app's online games, public or private rooms by invite, members, ready and chat — the host takes the whole room into a game.\",\n \"description\": \"A ready multiplayer lobby (engine Multiplayer) for ANY game, built on the @idosgames/react/ui kit, client.multiplayer and RealtimeSession (@idosgames/core/realtime). It OWNS the player's room and its WebRTC session: the rooms of the games this app plays online (each room remembers its game), 'Create a room' for one of them (name, 2/4/8 players, public or private with an invite code, the game's own room settings), joining by invite, members (host, ready, offline), 'Ready', the room chat (server relay, when the game has no chat of its own), removing a member and leaving. The host's 'Play' takes the whole room into the game: every device switches to the game's mode and the game gets the room in its start() — games never create, list or join rooms themselves. A game plays online by registering in ctx.multiplayer (registerGame: topology, chat mode, room size, joinInProgress for late joiners, room settings, start(room)); the lobby then draws 'With friends' next to its 'Play'. Opened with { mode } the screen shows one game's rooms, with { mode, create: true } straight on 'Create'. Registers the 'multiplayer' feature, shown while multiplayer is on for the title and the app has a game that plays online. Ships title-seed.json: multiplayer on, up to 8 players per room.\",\n \"provides\": [\n \"multiplayer rooms lobby\",\n \"rooms of one game\",\n \"public and private rooms by invite\",\n \"room settings of the game\",\n \"room members with ready and host\",\n \"room chat\",\n \"host starts the game for the whole room\",\n \"late joiners into a running game\"\n ],\n \"tags\": [\n \"multiplayer\",\n \"rooms\",\n \"lobby\",\n \"realtime\",\n \"social\",\n \"ui\",\n \"screen\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
95
|
+
"content": "{\n \"id\": \"multiplayer\",\n \"type\": \"feature\",\n \"summary\": \"The rooms system for any game: open rooms of the app's online games, public or private rooms by invite, members, ready and chat — the host takes the whole room into a game.\",\n \"description\": \"A ready multiplayer lobby (engine Multiplayer) for ANY game, built on the @idosgames/react/ui kit, client.multiplayer and RealtimeSession (@idosgames/core/realtime). It OWNS the player's room and its WebRTC session: the rooms of the games this app plays online (each room remembers its game), 'Create a room' for one of them (name, 2/4/8 players, public or private with an invite code, the game's own room settings), joining by invite, members (host, ready, offline), 'Ready', the room chat (server relay, when the game has no chat of its own), removing a member and leaving. The host's 'Play' takes the whole room into the game: every device switches to the game's mode and the game gets the room in its start() — games never create, list or join rooms themselves. A game plays online by registering in ctx.multiplayer (registerGame: topology, chat mode, room size, joinInProgress for late joiners, room settings, start(room)); the lobby then draws 'With friends' next to its 'Play'. Opened with { mode } the screen shows one game's rooms, with { mode, create: true } straight on 'Create'. Registers the 'multiplayer' feature, shown while multiplayer is on for the title and the app has a game that plays online. Ships title-seed.json: multiplayer on, up to 8 players per room.\",\n \"provides\": [\n \"multiplayer rooms lobby\",\n \"rooms of one game\",\n \"public and private rooms by invite\",\n \"room settings of the game\",\n \"room members with ready and host\",\n \"room chat\",\n \"host starts the game for the whole room\",\n \"late joiners into a running game\"\n ],\n \"tags\": [\n \"multiplayer\",\n \"rooms\",\n \"lobby\",\n \"realtime\",\n \"social\",\n \"ui\",\n \"screen\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.3\"\n}\n"
|
|
96
96
|
}
|
|
97
97
|
]
|
|
98
98
|
}
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
"name": "iDos Games",
|
|
30
30
|
"url": "https://idosgames.com"
|
|
31
31
|
},
|
|
32
|
-
"version": "0.1.
|
|
32
|
+
"version": "0.1.2"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@idosgames/core": "0.21.
|
|
36
|
-
"@idosgames/module-sdk": "0.
|
|
37
|
-
"@idosgames/react": "0.
|
|
35
|
+
"@idosgames/core": "0.21.6",
|
|
36
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
37
|
+
"@idosgames/react": "0.5.0",
|
|
38
38
|
"react": "19.2.7"
|
|
39
39
|
},
|
|
40
|
-
"contentHash": "
|
|
40
|
+
"contentHash": "fc37c3801909ed96833f1c07e64c659ca174de8667720d0f19f43f3613e754ac",
|
|
41
41
|
"files": [
|
|
42
42
|
{
|
|
43
43
|
"path": "i18n.ts",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": "module.ts",
|
|
56
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { PremiumScreen, watchPremium } from \"./PremiumScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// VIP as a feature of the game. Events and season send the player here for their VIP prizes\n// (features.open(\"premium\", { premiumID })). Badge: a free trial waiting.\nexport const premiumModule: Module = defineModule({\n id: \"premium\",\n meta: { name: \"VIP\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"premium\",\n label: t(\"title\"),\n icon: \"crown\",\n group: \"economy\",\n order: 12,\n available: false,\n Screen: PremiumScreen,\n });\n const enabled = hasTitleData(configSection(ctx.client, \"Premium\"));\n ctx.features.setAvailable(\n \"premium\",\n enabled,\n \"no premium pass on the title (Premium)\",\n );\n if (enabled) watchPremium(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"premium\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
56
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { PremiumScreen, watchPremium } from \"./PremiumScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// VIP as a feature of the game. Events and season send the player here for their VIP prizes\n// (features.open(\"premium\", { premiumID })). Badge: a free trial waiting.\nexport const premiumModule: Module = defineModule({\n id: \"premium\",\n meta: { name: \"VIP\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"premium\",\n label: () => t(\"title\"),\n icon: \"crown\",\n group: \"economy\",\n order: 12,\n available: false,\n Screen: PremiumScreen,\n });\n const enabled = hasTitleData(configSection(ctx.client, \"Premium\"));\n ctx.features.setAvailable(\n \"premium\",\n enabled,\n \"no premium pass on the title (Premium)\",\n );\n if (enabled) watchPremium(ctx);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"premium\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"path": "PremiumScreen.tsx",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"path": "module.meta.json",
|
|
64
|
-
"content": "{\n \"id\": \"premium\",\n \"type\": \"feature\",\n \"summary\": \"VIP for any game: premium passes with benefits, until when the player holds one, a free trial and buying with in-game currency.\",\n \"description\": \"A ready VIP screen (Unity PremiumPopUp) for ANY game, built on the @idosgames/react/ui kit and client.premium. Every premium pass of the title, lowest tier first: its name, benefits and duration, a timer while the player holds it, a one-time free trial, and buying (or extending) it with currencies or items. A pass paid in a store (real money) is the platform's checkout and is not sold here. Events and season send the player here for their VIP prizes (ctx.features.open('premium', { premiumID }) marks that pass). Registers the 'premium' feature; its badge shows a free trial waiting. The demo pass comes with the base's settings.\",\n \"provides\": [\n \"VIP passes screen\",\n \"free VIP trial\",\n \"buy or extend VIP with in-game currency\",\n \"VIP timer\",\n \"badge for a free trial\"\n ],\n \"tags\": [\n \"vip\",\n \"premium\",\n \"subscription\",\n \"monetization\",\n \"ui\",\n \"screen\",\n \"economy\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
64
|
+
"content": "{\n \"id\": \"premium\",\n \"type\": \"feature\",\n \"summary\": \"VIP for any game: premium passes with benefits, until when the player holds one, a free trial and buying with in-game currency.\",\n \"description\": \"A ready VIP screen (Unity PremiumPopUp) for ANY game, built on the @idosgames/react/ui kit and client.premium. Every premium pass of the title, lowest tier first: its name, benefits and duration, a timer while the player holds it, a one-time free trial, and buying (or extending) it with currencies or items. A pass paid in a store (real money) is the platform's checkout and is not sold here. Events and season send the player here for their VIP prizes (ctx.features.open('premium', { premiumID }) marks that pass). Registers the 'premium' feature; its badge shows a free trial waiting. The demo pass comes with the base's settings.\",\n \"provides\": [\n \"VIP passes screen\",\n \"free VIP trial\",\n \"buy or extend VIP with in-game currency\",\n \"VIP timer\",\n \"badge for a free trial\"\n ],\n \"tags\": [\n \"vip\",\n \"premium\",\n \"subscription\",\n \"monetization\",\n \"ui\",\n \"screen\",\n \"economy\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.2\"\n}\n"
|
|
65
65
|
}
|
|
66
66
|
]
|
|
67
67
|
}
|
|
@@ -31,19 +31,19 @@
|
|
|
31
31
|
"name": "iDos Games",
|
|
32
32
|
"url": "https://idosgames.com"
|
|
33
33
|
},
|
|
34
|
-
"version": "0.1.
|
|
34
|
+
"version": "0.1.4"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@idosgames/core": "0.21.
|
|
38
|
-
"@idosgames/module-sdk": "0.
|
|
39
|
-
"@idosgames/react": "0.
|
|
37
|
+
"@idosgames/core": "0.21.6",
|
|
38
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
39
|
+
"@idosgames/react": "0.5.0",
|
|
40
40
|
"react": "19.2.7"
|
|
41
41
|
},
|
|
42
|
-
"contentHash": "
|
|
42
|
+
"contentHash": "374cc0e7260f5286c9195509a1879fd6b58cabcec3411c48fe5611abf696fe00",
|
|
43
43
|
"files": [
|
|
44
44
|
{
|
|
45
45
|
"path": "i18n.ts",
|
|
46
|
-
"content": "import { makeT } from \"@idosgames/react/ui\";\n\nconst EN = {\n title: \"Profile\",\n playerID: \"Player ID\",\n copied: \"Copied\",\n rename: \"Change name\",\n newName: \"New name\",\n save: \"Save\",\n nameShort: \"At least 3 characters\",\n nameLong: \"At most 24 characters\",\n renamed: \"Name changed\",\n settings: \"Settings\",\n sound: \"Sound\",\n volume: \"Volume\",\n motion: \"Fewer animations\",\n language: \"Language\",\n tutorials: \"Tutorials\",\n logout: \"Log out\",\n logoutAsk:\n \"Log out of this device? A guest account is lost unless it is linked.\",\n on: \"On\",\n off: \"Off\",\n guest: \"Guest\",\n wallets: \"Wallets\",\n unlink: \"Unlink\",\n unlinkTitle: \"Unlink the wallet?\",\n unlinkAsk:\n \"The wallet will be taken off this account. For 7 days it cannot be linked to another account; you can link it back here at any time. If the game is played with a wallet, it closes until you link one.\",\n unlinked: \"Wallet unlinked\",\n unlinkLogin: \"You sign in with this wallet — it cannot be unlinked.\",\n unlinkTooMany:\n \"Too many wallets unlinked this week. Try again in a few days.\",\n unlinkMissing: \"This wallet is no longer linked.\",\n loginWallet: \"Sign-in wallet\",\n} as const;\n\nexport const t = makeT<keyof typeof EN>(EN, {\n title: \"Профиль\",\n playerID: \"ID игрока\",\n copied: \"Скопировано\",\n rename: \"Сменить имя\",\n newName: \"Новое имя\",\n save: \"Сохранить\",\n nameShort: \"Минимум 3 символа\",\n nameLong: \"Максимум 24 символа\",\n renamed: \"Имя изменено\",\n settings: \"Настройки\",\n sound: \"Звук\",\n volume: \"Громкость\",\n motion: \"Меньше анимации\",\n language: \"Язык\",\n tutorials: \"Обучение\",\n logout: \"Выйти\",\n logoutAsk:\n \"Выйти на этом устройстве? Гостевой аккаунт без привязки будет потерян.\",\n on: \"Вкл\",\n off: \"Выкл\",\n guest: \"Гость\",\n wallets: \"Кошельки\",\n unlink: \"Отвязать\",\n unlinkTitle: \"Отвязать кошелёк?\",\n unlinkAsk:\n \"Кошелёк будет снят с этого аккаунта. 7 дней его нельзя привязать к другому аккаунту, а к этому — можно вернуть в любой момент. Если в игру играют с кошельком, она закроется, пока вы не привяжете другой.\",\n unlinked: \"Кошелёк отвязан\",\n unlinkLogin: \"Вы входите этим кошельком — его нельзя отвязать.\",\n unlinkTooMany:\n \"На этой неделе отвязано слишком много кошельков. Попробуйте через несколько дней.\",\n unlinkMissing: \"Этот кошелёк уже не привязан.\",\n loginWallet: \"Кошелёк входа\",\n});\n"
|
|
46
|
+
"content": "import { makeT } from \"@idosgames/react/ui\";\n\nconst EN = {\n title: \"Profile\",\n playerID: \"Player ID\",\n copied: \"Copied\",\n rename: \"Change name\",\n newName: \"New name\",\n save: \"Save\",\n nameShort: \"At least 3 characters\",\n nameLong: \"At most 24 characters\",\n renamed: \"Name changed\",\n settings: \"Settings\",\n sound: \"Sound\",\n volume: \"Volume\",\n motion: \"Fewer animations\",\n language: \"Language\",\n contentLanguage: \"Language of the game texts\",\n tutorials: \"Tutorials\",\n logout: \"Log out\",\n logoutAsk:\n \"Log out of this device? A guest account is lost unless it is linked.\",\n on: \"On\",\n off: \"Off\",\n guest: \"Guest\",\n wallets: \"Wallets\",\n unlink: \"Unlink\",\n unlinkTitle: \"Unlink the wallet?\",\n unlinkAsk:\n \"The wallet will be taken off this account. For 7 days it cannot be linked to another account; you can link it back here at any time. If the game is played with a wallet, it closes until you link one.\",\n unlinked: \"Wallet unlinked\",\n unlinkLogin: \"You sign in with this wallet — it cannot be unlinked.\",\n unlinkTooMany:\n \"Too many wallets unlinked this week. Try again in a few days.\",\n unlinkMissing: \"This wallet is no longer linked.\",\n loginWallet: \"Sign-in wallet\",\n} as const;\n\nexport const t = makeT<keyof typeof EN>(EN, {\n title: \"Профиль\",\n playerID: \"ID игрока\",\n copied: \"Скопировано\",\n rename: \"Сменить имя\",\n newName: \"Новое имя\",\n save: \"Сохранить\",\n nameShort: \"Минимум 3 символа\",\n nameLong: \"Максимум 24 символа\",\n renamed: \"Имя изменено\",\n settings: \"Настройки\",\n sound: \"Звук\",\n volume: \"Громкость\",\n motion: \"Меньше анимации\",\n language: \"Язык\",\n contentLanguage: \"Язык текстов игры\",\n tutorials: \"Обучение\",\n logout: \"Выйти\",\n logoutAsk:\n \"Выйти на этом устройстве? Гостевой аккаунт без привязки будет потерян.\",\n on: \"Вкл\",\n off: \"Выкл\",\n guest: \"Гость\",\n wallets: \"Кошельки\",\n unlink: \"Отвязать\",\n unlinkTitle: \"Отвязать кошелёк?\",\n unlinkAsk:\n \"Кошелёк будет снят с этого аккаунта. 7 дней его нельзя привязать к другому аккаунту, а к этому — можно вернуть в любой момент. Если в игру играют с кошельком, она закроется, пока вы не привяжете другой.\",\n unlinked: \"Кошелёк отвязан\",\n unlinkLogin: \"Вы входите этим кошельком — его нельзя отвязать.\",\n unlinkTooMany:\n \"На этой неделе отвязано слишком много кошельков. Попробуйте через несколько дней.\",\n unlinkMissing: \"Этот кошелёк уже не привязан.\",\n loginWallet: \"Кошелёк входа\",\n});\n"
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
"path": "index.ts",
|
|
@@ -55,15 +55,15 @@
|
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"path": "module.ts",
|
|
58
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { makeProfileScreen } from \"./ProfileScreen\";\nimport { t } from \"./i18n\";\n\n// Profile and settings as a feature of the game. The lobby of the base opens it from its profile\n// button (features.open(\"profile\")).\nexport const profileModule: Module = defineModule({\n id: \"profile\",\n meta: { name: \"Profile\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"profile\",\n label: t(\"title\"),\n icon: \"user\",\n group: \"profile\",\n order: 100,\n Screen: makeProfileScreen(ctx.features),\n });\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"profile\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
58
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { makeProfileScreen } from \"./ProfileScreen\";\nimport { t } from \"./i18n\";\n\n// Profile and settings as a feature of the game. The lobby of the base opens it from its profile\n// button (features.open(\"profile\")).\nexport const profileModule: Module = defineModule({\n id: \"profile\",\n meta: { name: \"Profile\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"profile\",\n label: () => t(\"title\"),\n icon: \"user\",\n group: \"profile\",\n order: 100,\n Screen: makeProfileScreen(ctx.features),\n });\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"profile\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"path": "ProfileScreen.tsx",
|
|
62
|
-
"content": "import { useCallback, useEffect, useState, type ReactNode } from \"react\";\nimport type { PlayAccessWallet } from \"@idosgames/core\";\nimport type {\n FeatureRegistry,\n FeatureScreenProps,\n} from \"@idosgames/module-sdk\";\nimport { useIDosGamesClient, useUserState } from \"@idosgames/react\";\nimport {\n Button,\n ConfirmPopup,\n errorText,\n Icon,\n outlined,\n panel,\n Popup,\n Tabs,\n TextInput,\n usePlayerPrefs,\n useToast,\n useUiKit,\n v,\n} from \"@idosgames/react/ui\";\nimport {\n languageChoices,\n nameProblem,\n shortAddress,\n unlinkRefusal,\n volumePercent,\n} from \"./model\";\nimport { t } from \"./i18n\";\n\n// Profile and settings (Unity Alikhan/Username, UI/HUD/Settings, Runtime/UI/Consent, Localization):\n// the name and ID, sound, volume and motion (kept on this device), the game's language, tutorials,\n// linked wallets and logging out. Renaming and unlinking a wallet are popups. No \"Delete account\"\n// button (owner's decision 03.10.2026) — `client.user.deleteUserAccount()` stays in the SDK. No analytics switch (owner's decision 29.09.2026): anonymous analytics stays on as the SDK\n// ships it (client.analytics).\n\nexport function makeProfileScreen(features: FeatureRegistry) {\n return function ProfileScreen(_: FeatureScreenProps): ReactNode {\n const client = useIDosGamesClient();\n const state = useUserState();\n const toast = useToast();\n const kit = useUiKit();\n const [prefs, setPrefs] = usePlayerPrefs();\n const [name, setName] = useState<string | null>(null);\n const [renaming, setRenaming] = useState(false);\n const [ask, setAsk] = useState<\"logout\" | null>(null);\n const [locale, setLocale] = useState(client.localization.locale ?? \"\");\n const id = client.auth.context?.userID ?? \"\";\n const shown = name ?? state?.PublicData?.Username ?? t(\"guest\");\n const languages = languageChoices(client.localization.locales);\n\n const switchLanguage = async (next: string) => {\n const res = await client.localization.setLocale(next);\n if (!res.ok) return toast(errorText(res.error), \"error\");\n setLocale(res.data);\n };\n\n return (\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"minmax(0, 1fr)\",\n gap: 12,\n maxWidth: 560,\n margin: \"0 auto\",\n width: \"100%\",\n }}\n >\n <div\n style={{\n ...panel,\n padding: 16,\n display: \"grid\",\n justifyItems: \"center\",\n gap: 8,\n }}\n >\n <div\n style={{\n width: 84,\n height: 84,\n borderRadius: \"50%\",\n display: \"grid\",\n placeItems: \"center\",\n background: `linear-gradient(180deg, ${v.blue}, ${v.blueDeep})`,\n border: `3px solid color-mix(in srgb, ${v.text} 70%, transparent)`,\n ...outlined,\n fontSize: 36,\n }}\n >\n {(shown.trim()[0] ?? \"?\").toUpperCase()}\n </div>\n <div style={{ ...outlined, fontSize: 20 }}>{shown}</div>\n <Button\n size=\"sm\"\n tone=\"blue\"\n onClick={() => setRenaming(true)}\n data-tutorial-anchor=\"profile:rename\"\n >\n <Icon glyph=\"edit\" size={16} /> {t(\"rename\")}\n </Button>\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n maxWidth: \"100%\",\n }}\n >\n <span style={{ ...outlined, fontSize: 11, color: v.textDim }}>\n {t(\"playerID\")}\n </span>\n <span\n style={{\n fontFamily: \"monospace\",\n fontSize: 12,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n }}\n >\n {id}\n </span>\n <button\n type=\"button\"\n aria-label={t(\"playerID\")}\n onClick={() =>\n void navigator.clipboard\n ?.writeText(id)\n .then(() => toast(t(\"copied\")))\n }\n style={{\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n padding: 2,\n }}\n >\n <Icon glyph=\"copy\" size={16} />\n </button>\n </div>\n </div>\n\n <div style={{ ...panel, padding: 12, display: \"grid\", gap: 12 }}>\n <div style={{ ...outlined, fontSize: 14, color: v.textDim }}>\n {t(\"settings\")}\n </div>\n <Toggle\n glyph={prefs.muted ? \"mute\" : \"sound\"}\n label={t(\"sound\")}\n value={!prefs.muted}\n onChange={(on) => {\n setPrefs({ muted: !on });\n if (on) kit.play(\"click\");\n }}\n />\n <div\n style={{ display: \"grid\", gap: 4, opacity: prefs.muted ? 0.5 : 1 }}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n ...outlined,\n fontSize: 13,\n }}\n >\n <span>{t(\"volume\")}</span>\n <span>{volumePercent(prefs.volume)}%</span>\n </div>\n <input\n type=\"range\"\n min={0}\n max={100}\n step={5}\n disabled={prefs.muted}\n value={volumePercent(prefs.volume)}\n onChange={(e) =>\n setPrefs({ volume: Number(e.target.value) / 100 })\n }\n onPointerUp={() => kit.play(\"coin\")}\n style={{ width: \"100%\", accentColor: v.gold }}\n />\n </div>\n <Toggle\n glyph=\"flame\"\n label={t(\"motion\")}\n value={prefs.reducedMotion}\n onChange={(on) => setPrefs({ reducedMotion: on })}\n />\n {languages.length > 1 ? (\n <div style={{ display: \"grid\", gap: 6 }}>\n <div style={{ ...outlined, fontSize: 13 }}>{t(\"language\")}</div>\n <Tabs\n tabs={languages.map((l) => ({ id: l.id, label: l.name }))}\n value={\n languages.find((l) => locale.startsWith(l.id))?.id ??\n languages[0]!.id\n }\n onChange={(l) => void switchLanguage(l)}\n />\n </div>\n ) : null}\n </div>\n\n <WalletsSection />\n\n {features.get(\"tutorials\") ? (\n <Button tone=\"blue\" onClick={() => features.open(\"tutorials\")}>\n <Icon glyph=\"bulb\" size={18} /> {t(\"tutorials\")}\n </Button>\n ) : null}\n <Button tone=\"grey\" onClick={() => setAsk(\"logout\")}>\n <Icon glyph=\"logout\" size={18} /> {t(\"logout\")}\n </Button>\n\n {renaming ? (\n <RenamePopup\n current={shown}\n onClose={() => setRenaming(false)}\n onDone={(n) => {\n setName(n);\n setRenaming(false);\n }}\n />\n ) : null}\n {ask === \"logout\" ? (\n <ConfirmPopup\n title={t(\"logout\")}\n tone=\"red\"\n onClose={() => setAsk(null)}\n // Only the logout: the host returns to the sign-in screen on \"auth:loggedOut\" by itself.\n // A page reload here blanked the AI Coder's live preview (its files arrive by message\n // and do not survive one) and restarted the whole app for nothing.\n onConfirm={() => client.auth.logout()}\n >\n <div style={{ ...outlined, fontSize: 14, textAlign: \"center\" }}>\n {t(\"logoutAsk\")}\n </div>\n </ConfirmPopup>\n ) : null}\n </div>\n );\n };\n}\n\nfunction Toggle({\n glyph,\n label,\n value,\n onChange,\n}: {\n glyph: \"sound\" | \"mute\" | \"flame\" | \"globe\";\n label: string;\n value: boolean;\n onChange: (on: boolean) => void;\n}): ReactNode {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={value}\n onClick={() => onChange(!value)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n background: \"none\",\n border: \"none\",\n color: v.text,\n cursor: \"pointer\",\n padding: 0,\n textAlign: \"left\",\n }}\n >\n <Icon glyph={glyph} size={24} />\n <span style={{ flex: 1, ...outlined, fontSize: 14 }}>{label}</span>\n <span\n style={{\n width: 52,\n height: 30,\n borderRadius: 15,\n background: value ? v.green : v.wellSoft,\n boxShadow: `inset 0 0 0 1px ${v.line}`,\n position: \"relative\",\n transition: \"background .2s\",\n }}\n >\n <span\n style={{\n position: \"absolute\",\n top: 3,\n left: value ? 25 : 3,\n width: 24,\n height: 24,\n borderRadius: \"50%\",\n background: \"#fff\",\n transition: \"left .2s\",\n boxShadow: \"0 2px 4px rgba(0,0,0,.3)\",\n }}\n />\n </span>\n </button>\n );\n}\n\n/**\n * Wallets linked by signature, each with \"Unlink\" (`client.auth.unlinkWallet`). Hidden while there are\n * none. The wallet an account signs in with has no button: the server refuses it (`CanUnlink`).\n */\nfunction WalletsSection(): ReactNode {\n const client = useIDosGamesClient();\n const toast = useToast();\n const [wallets, setWallets] = useState<PlayAccessWallet[]>([]);\n const [asking, setAsking] = useState<PlayAccessWallet | null>(null);\n\n const load = useCallback(async () => {\n const res = await client.blockchain.getPlayAccess();\n if (res.ok) setWallets(res.data.LinkedWallets ?? []);\n }, [client]);\n useEffect(() => {\n void load();\n }, [load]);\n\n // The popup shows the spinner and closes itself when this resolves.\n const unlink = async (wallet: PlayAccessWallet) => {\n const res = await client.auth.unlinkWallet(wallet.NetworkID ?? \"\");\n if (!res.ok) {\n const known = unlinkRefusal(res.error);\n toast(known ? t(known) : errorText(res.error), \"error\");\n } else toast(t(\"unlinked\"), \"success\");\n await load();\n };\n\n if (wallets.length === 0) return null;\n return (\n <div style={{ ...panel, padding: 12, display: \"grid\", gap: 10 }}>\n <div style={{ ...outlined, fontSize: 14, color: v.textDim }}>\n {t(\"wallets\")}\n </div>\n {wallets.map((w) => (\n <div\n key={`${w.NetworkID}:${w.Address}`}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n minWidth: 0,\n }}\n >\n <Icon glyph=\"link\" size={22} />\n <div style={{ flex: 1, minWidth: 0, display: \"grid\" }}>\n <span\n style={{\n fontFamily: \"monospace\",\n fontSize: 13,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n }}\n >\n {shortAddress(w.Address)}\n </span>\n <span style={{ ...outlined, fontSize: 11, color: v.textDim }}>\n {(w.NetworkID ?? \"\").toUpperCase()}\n {w.CanUnlink === false ? ` · ${t(\"loginWallet\")}` : \"\"}\n </span>\n </div>\n {w.CanUnlink !== false ? (\n <Button size=\"sm\" tone=\"grey\" onClick={() => setAsking(w)}>\n {t(\"unlink\")}\n </Button>\n ) : null}\n </div>\n ))}\n {asking ? (\n <ConfirmPopup\n title={t(\"unlinkTitle\")}\n confirmLabel={t(\"unlink\")}\n tone=\"red\"\n onClose={() => setAsking(null)}\n onConfirm={() => unlink(asking)}\n >\n <div\n style={{\n fontFamily: \"monospace\",\n fontSize: 14,\n textAlign: \"center\",\n }}\n >\n {shortAddress(asking.Address)}\n </div>\n <div\n style={{\n ...outlined,\n fontSize: 13,\n textAlign: \"center\",\n lineHeight: 1.4,\n }}\n >\n {t(\"unlinkAsk\")}\n </div>\n </ConfirmPopup>\n ) : null}\n </div>\n );\n}\n\nfunction RenamePopup({\n current,\n onClose,\n onDone,\n}: {\n current: string;\n onClose: () => void;\n onDone: (name: string) => void;\n}): ReactNode {\n const client = useIDosGamesClient();\n const toast = useToast();\n const [value, setValue] = useState(current);\n const [busy, setBusy] = useState(false);\n const problem = nameProblem(value);\n const save = async () => {\n if (problem) return;\n setBusy(true);\n const res = await client.user.changeUsername(value.trim());\n setBusy(false);\n if (!res.ok) return toast(errorText(res.error), \"error\");\n toast(t(\"renamed\"), \"success\");\n // The SDK does not patch the cached name — the screen shows the server's answer.\n onDone(res.data.Username ?? value.trim());\n };\n return (\n <Popup title={t(\"rename\")} onClose={onClose} width={400}>\n <TextInput\n value={value}\n autoFocus\n maxLength={40}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => e.key === \"Enter\" && void save()}\n placeholder={t(\"newName\")}\n style={{ fontSize: 16 }}\n />\n {problem ? (\n <div style={{ ...outlined, fontSize: 12, color: v.red }}>\n {problem === \"short\" ? t(\"nameShort\") : t(\"nameLong\")}\n </div>\n ) : null}\n <Button\n tone=\"green\"\n disabled={!!problem}\n busy={busy}\n onClick={() => void save()}\n >\n {t(\"save\")}\n </Button>\n </Popup>\n );\n}\n"
|
|
62
|
+
"content": "import { useCallback, useEffect, useState, type ReactNode } from \"react\";\nimport type { PlayAccessWallet } from \"@idosgames/core\";\nimport type {\n FeatureRegistry,\n FeatureScreenProps,\n} from \"@idosgames/module-sdk\";\nimport { useIDosGamesClient, useUserState } from \"@idosgames/react\";\nimport {\n Button,\n ConfirmPopup,\n errorText,\n Icon,\n LanguageSwitch,\n onLanguageChange,\n outlined,\n panel,\n Popup,\n Tabs,\n TextInput,\n usePlayerPrefs,\n useToast,\n useUiKit,\n v,\n} from \"@idosgames/react/ui\";\nimport {\n languageChoices,\n nameProblem,\n shortAddress,\n unlinkRefusal,\n volumePercent,\n} from \"./model\";\nimport { t } from \"./i18n\";\n\n// Profile and settings (Unity Alikhan/Username, UI/HUD/Settings, Runtime/UI/Consent, Localization):\n// the name and ID, sound, volume and motion (kept on this device), the game's language, tutorials,\n// linked wallets and logging out. Renaming and unlinking a wallet are popups. No \"Delete account\"\n// button (owner's decision 03.10.2026) — `client.user.deleteUserAccount()` stays in the SDK. No analytics switch (owner's decision 29.09.2026): anonymous analytics stays on as the SDK\n// ships it (client.analytics).\n\nexport function makeProfileScreen(features: FeatureRegistry) {\n return function ProfileScreen(_: FeatureScreenProps): ReactNode {\n const client = useIDosGamesClient();\n const state = useUserState();\n const toast = useToast();\n const kit = useUiKit();\n const [prefs, setPrefs] = usePlayerPrefs();\n const [name, setName] = useState<string | null>(null);\n const [renaming, setRenaming] = useState(false);\n const [ask, setAsk] = useState<\"logout\" | null>(null);\n const [locale, setLocale] = useState(client.localization.locale ?? \"\");\n const id = client.auth.context?.userID ?? \"\";\n const shown = name ?? state?.PublicData?.Username ?? t(\"guest\");\n const languages = languageChoices(client.localization.locales);\n\n const switchLanguage = async (next: string) => {\n const res = await client.localization.setLocale(next);\n if (!res.ok) return toast(errorText(res.error), \"error\");\n setLocale(res.data);\n };\n // сменили язык интерфейса — тексты тайтла (если у тайтла есть такой язык) переходят на него же\n useEffect(\n () =>\n onLanguageChange((l) => {\n if (languages.some((x) => x.id === l)) void switchLanguage(l);\n }),\n [languages.map((l) => l.id).join()],\n );\n\n return (\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"minmax(0, 1fr)\",\n gap: 12,\n maxWidth: 560,\n margin: \"0 auto\",\n width: \"100%\",\n }}\n >\n <div\n style={{\n ...panel,\n padding: 16,\n display: \"grid\",\n justifyItems: \"center\",\n gap: 8,\n }}\n >\n <div\n style={{\n width: 84,\n height: 84,\n borderRadius: \"50%\",\n display: \"grid\",\n placeItems: \"center\",\n background: `linear-gradient(180deg, ${v.blue}, ${v.blueDeep})`,\n border: `3px solid color-mix(in srgb, ${v.text} 70%, transparent)`,\n ...outlined,\n fontSize: 36,\n }}\n >\n {(shown.trim()[0] ?? \"?\").toUpperCase()}\n </div>\n <div style={{ ...outlined, fontSize: 20 }}>{shown}</div>\n <Button\n size=\"sm\"\n tone=\"blue\"\n onClick={() => setRenaming(true)}\n data-tutorial-anchor=\"profile:rename\"\n >\n <Icon glyph=\"edit\" size={16} /> {t(\"rename\")}\n </Button>\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n maxWidth: \"100%\",\n }}\n >\n <span style={{ ...outlined, fontSize: 11, color: v.textDim }}>\n {t(\"playerID\")}\n </span>\n <span\n style={{\n fontFamily: \"monospace\",\n fontSize: 12,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n }}\n >\n {id}\n </span>\n <button\n type=\"button\"\n aria-label={t(\"playerID\")}\n onClick={() =>\n void navigator.clipboard\n ?.writeText(id)\n .then(() => toast(t(\"copied\")))\n }\n style={{\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n padding: 2,\n }}\n >\n <Icon glyph=\"copy\" size={16} />\n </button>\n </div>\n </div>\n\n <div style={{ ...panel, padding: 12, display: \"grid\", gap: 12 }}>\n <div style={{ ...outlined, fontSize: 14, color: v.textDim }}>\n {t(\"settings\")}\n </div>\n <Toggle\n glyph={prefs.muted ? \"mute\" : \"sound\"}\n label={t(\"sound\")}\n value={!prefs.muted}\n onChange={(on) => {\n setPrefs({ muted: !on });\n if (on) kit.play(\"click\");\n }}\n />\n <div\n style={{ display: \"grid\", gap: 4, opacity: prefs.muted ? 0.5 : 1 }}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n ...outlined,\n fontSize: 13,\n }}\n >\n <span>{t(\"volume\")}</span>\n <span>{volumePercent(prefs.volume)}%</span>\n </div>\n <input\n type=\"range\"\n min={0}\n max={100}\n step={5}\n disabled={prefs.muted}\n value={volumePercent(prefs.volume)}\n onChange={(e) =>\n setPrefs({ volume: Number(e.target.value) / 100 })\n }\n onPointerUp={() => kit.play(\"coin\")}\n style={{ width: \"100%\", accentColor: v.gold }}\n />\n </div>\n <Toggle\n glyph=\"flame\"\n label={t(\"motion\")}\n value={prefs.reducedMotion}\n onChange={(on) => setPrefs({ reducedMotion: on })}\n />\n {/* язык интерфейса — всего приложения (UI kit setLanguage), тексты тайтла идут следом */}\n <div style={{ display: \"grid\", gap: 6 }}>\n <div style={{ ...outlined, fontSize: 13 }}>{t(\"language\")}</div>\n <LanguageSwitch />\n </div>\n {languages.some((l) => l.id !== \"en\" && l.id !== \"ru\") ? (\n <div style={{ display: \"grid\", gap: 6 }}>\n <div style={{ ...outlined, fontSize: 13 }}>\n {t(\"contentLanguage\")}\n </div>\n <Tabs\n tabs={languages.map((l) => ({ id: l.id, label: l.name }))}\n value={\n languages.find((l) => locale.startsWith(l.id))?.id ??\n languages[0]!.id\n }\n onChange={(l) => void switchLanguage(l)}\n />\n </div>\n ) : null}\n </div>\n\n <WalletsSection />\n\n {features.get(\"tutorials\") ? (\n <Button tone=\"blue\" onClick={() => features.open(\"tutorials\")}>\n <Icon glyph=\"bulb\" size={18} /> {t(\"tutorials\")}\n </Button>\n ) : null}\n <Button tone=\"grey\" onClick={() => setAsk(\"logout\")}>\n <Icon glyph=\"logout\" size={18} /> {t(\"logout\")}\n </Button>\n\n {renaming ? (\n <RenamePopup\n current={shown}\n onClose={() => setRenaming(false)}\n onDone={(n) => {\n setName(n);\n setRenaming(false);\n }}\n />\n ) : null}\n {ask === \"logout\" ? (\n <ConfirmPopup\n title={t(\"logout\")}\n tone=\"red\"\n onClose={() => setAsk(null)}\n // Only the logout: the host returns to the sign-in screen on \"auth:loggedOut\" by itself.\n // A page reload here blanked the AI Coder's live preview (its files arrive by message\n // and do not survive one) and restarted the whole app for nothing.\n onConfirm={() => client.auth.logout()}\n >\n <div style={{ ...outlined, fontSize: 14, textAlign: \"center\" }}>\n {t(\"logoutAsk\")}\n </div>\n </ConfirmPopup>\n ) : null}\n </div>\n );\n };\n}\n\nfunction Toggle({\n glyph,\n label,\n value,\n onChange,\n}: {\n glyph: \"sound\" | \"mute\" | \"flame\" | \"globe\";\n label: string;\n value: boolean;\n onChange: (on: boolean) => void;\n}): ReactNode {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={value}\n onClick={() => onChange(!value)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n background: \"none\",\n border: \"none\",\n color: v.text,\n cursor: \"pointer\",\n padding: 0,\n textAlign: \"left\",\n }}\n >\n <Icon glyph={glyph} size={24} />\n <span style={{ flex: 1, ...outlined, fontSize: 14 }}>{label}</span>\n <span\n style={{\n width: 52,\n height: 30,\n borderRadius: 15,\n background: value ? v.green : v.wellSoft,\n boxShadow: `inset 0 0 0 1px ${v.line}`,\n position: \"relative\",\n transition: \"background .2s\",\n }}\n >\n <span\n style={{\n position: \"absolute\",\n top: 3,\n left: value ? 25 : 3,\n width: 24,\n height: 24,\n borderRadius: \"50%\",\n background: \"#fff\",\n transition: \"left .2s\",\n boxShadow: \"0 2px 4px rgba(0,0,0,.3)\",\n }}\n />\n </span>\n </button>\n );\n}\n\n/**\n * Wallets linked by signature, each with \"Unlink\" (`client.auth.unlinkWallet`). Hidden while there are\n * none. The wallet an account signs in with has no button: the server refuses it (`CanUnlink`).\n */\nfunction WalletsSection(): ReactNode {\n const client = useIDosGamesClient();\n const toast = useToast();\n const [wallets, setWallets] = useState<PlayAccessWallet[]>([]);\n const [asking, setAsking] = useState<PlayAccessWallet | null>(null);\n\n const load = useCallback(async () => {\n const res = await client.blockchain.getPlayAccess();\n if (res.ok) setWallets(res.data.LinkedWallets ?? []);\n }, [client]);\n useEffect(() => {\n void load();\n }, [load]);\n\n // The popup shows the spinner and closes itself when this resolves.\n const unlink = async (wallet: PlayAccessWallet) => {\n const res = await client.auth.unlinkWallet(wallet.NetworkID ?? \"\");\n if (!res.ok) {\n const known = unlinkRefusal(res.error);\n toast(known ? t(known) : errorText(res.error), \"error\");\n } else toast(t(\"unlinked\"), \"success\");\n await load();\n };\n\n if (wallets.length === 0) return null;\n return (\n <div style={{ ...panel, padding: 12, display: \"grid\", gap: 10 }}>\n <div style={{ ...outlined, fontSize: 14, color: v.textDim }}>\n {t(\"wallets\")}\n </div>\n {wallets.map((w) => (\n <div\n key={`${w.NetworkID}:${w.Address}`}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n minWidth: 0,\n }}\n >\n <Icon glyph=\"link\" size={22} />\n <div style={{ flex: 1, minWidth: 0, display: \"grid\" }}>\n <span\n style={{\n fontFamily: \"monospace\",\n fontSize: 13,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n }}\n >\n {shortAddress(w.Address)}\n </span>\n <span style={{ ...outlined, fontSize: 11, color: v.textDim }}>\n {(w.NetworkID ?? \"\").toUpperCase()}\n {w.CanUnlink === false ? ` · ${t(\"loginWallet\")}` : \"\"}\n </span>\n </div>\n {w.CanUnlink !== false ? (\n <Button size=\"sm\" tone=\"grey\" onClick={() => setAsking(w)}>\n {t(\"unlink\")}\n </Button>\n ) : null}\n </div>\n ))}\n {asking ? (\n <ConfirmPopup\n title={t(\"unlinkTitle\")}\n confirmLabel={t(\"unlink\")}\n tone=\"red\"\n onClose={() => setAsking(null)}\n onConfirm={() => unlink(asking)}\n >\n <div\n style={{\n fontFamily: \"monospace\",\n fontSize: 14,\n textAlign: \"center\",\n }}\n >\n {shortAddress(asking.Address)}\n </div>\n <div\n style={{\n ...outlined,\n fontSize: 13,\n textAlign: \"center\",\n lineHeight: 1.4,\n }}\n >\n {t(\"unlinkAsk\")}\n </div>\n </ConfirmPopup>\n ) : null}\n </div>\n );\n}\n\nfunction RenamePopup({\n current,\n onClose,\n onDone,\n}: {\n current: string;\n onClose: () => void;\n onDone: (name: string) => void;\n}): ReactNode {\n const client = useIDosGamesClient();\n const toast = useToast();\n const [value, setValue] = useState(current);\n const [busy, setBusy] = useState(false);\n const problem = nameProblem(value);\n const save = async () => {\n if (problem) return;\n setBusy(true);\n const res = await client.user.changeUsername(value.trim());\n setBusy(false);\n if (!res.ok) return toast(errorText(res.error), \"error\");\n toast(t(\"renamed\"), \"success\");\n // The SDK does not patch the cached name — the screen shows the server's answer.\n onDone(res.data.Username ?? value.trim());\n };\n return (\n <Popup title={t(\"rename\")} onClose={onClose} width={400}>\n <TextInput\n value={value}\n autoFocus\n maxLength={40}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => e.key === \"Enter\" && void save()}\n placeholder={t(\"newName\")}\n style={{ fontSize: 16 }}\n />\n {problem ? (\n <div style={{ ...outlined, fontSize: 12, color: v.red }}>\n {problem === \"short\" ? t(\"nameShort\") : t(\"nameLong\")}\n </div>\n ) : null}\n <Button\n tone=\"green\"\n disabled={!!problem}\n busy={busy}\n onClick={() => void save()}\n >\n {t(\"save\")}\n </Button>\n </Popup>\n );\n}\n"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"path": "module.meta.json",
|
|
66
|
-
"content": "{\n \"id\": \"profile\",\n \"type\": \"feature\",\n \"inBase\": true,\n \"summary\": \"Profile and settings for any game: name and ID, sound, volume and motion, language, tutorials and log out.\",\n \"description\": \"A ready profile screen (Unity Alikhan/Username, UI/HUD/Settings, Runtime/UI/Consent, Localization) for ANY game, built on the @idosgames/react/ui kit. The player's name (rename in a popup with the server's rules: 3–24 characters, trimmed) and ID with copy; settings: sound on/off, volume and 'fewer animations' — the kit's player prefs, kept on this device and applied to every screen at once — the game's language (the title's languages, when there are two or more). Buttons: Tutorials (when tutorials is installed) and Log out (confirmed). Registers the 'profile' feature; the lobby's profile button opens it. No title settings needed.\",\n \"provides\": [\n \"player name and ID\",\n \"rename popup\",\n \"sound, volume and fewer animations\",\n \"language picker\",\n \"log out\"\n ],\n \"tags\": [\n \"profile\",\n \"settings\",\n \"account\",\n \"sound\",\n \"language\",\n \"privacy\",\n \"ui\",\n \"screen\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
66
|
+
"content": "{\n \"id\": \"profile\",\n \"type\": \"feature\",\n \"inBase\": true,\n \"summary\": \"Profile and settings for any game: name and ID, sound, volume and motion, language, tutorials and log out.\",\n \"description\": \"A ready profile screen (Unity Alikhan/Username, UI/HUD/Settings, Runtime/UI/Consent, Localization) for ANY game, built on the @idosgames/react/ui kit. The player's name (rename in a popup with the server's rules: 3–24 characters, trimmed) and ID with copy; settings: sound on/off, volume and 'fewer animations' — the kit's player prefs, kept on this device and applied to every screen at once — the game's language (the title's languages, when there are two or more). Buttons: Tutorials (when tutorials is installed) and Log out (confirmed). Registers the 'profile' feature; the lobby's profile button opens it. No title settings needed.\",\n \"provides\": [\n \"player name and ID\",\n \"rename popup\",\n \"sound, volume and fewer animations\",\n \"language picker\",\n \"log out\"\n ],\n \"tags\": [\n \"profile\",\n \"settings\",\n \"account\",\n \"sound\",\n \"language\",\n \"privacy\",\n \"ui\",\n \"screen\"\n ],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.4\"\n}\n"
|
|
67
67
|
}
|
|
68
68
|
]
|
|
69
69
|
}
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"name": "iDos Games",
|
|
31
31
|
"url": "https://idosgames.com"
|
|
32
32
|
},
|
|
33
|
-
"version": "0.1.
|
|
33
|
+
"version": "0.1.1"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@idosgames/core": "0.21.
|
|
37
|
-
"@idosgames/module-sdk": "0.
|
|
38
|
-
"@idosgames/react": "0.
|
|
36
|
+
"@idosgames/core": "0.21.6",
|
|
37
|
+
"@idosgames/module-sdk": "0.11.0",
|
|
38
|
+
"@idosgames/react": "0.5.0",
|
|
39
39
|
"react": "19.2.7"
|
|
40
40
|
},
|
|
41
|
-
"contentHash": "
|
|
41
|
+
"contentHash": "bf7c4ec82041941ef2251c1fb5d4b00633ca81b80321020ee03dccd395f98d51",
|
|
42
42
|
"files": [
|
|
43
43
|
{
|
|
44
44
|
"path": "components/BattlePopups.tsx",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
"path": "module.ts",
|
|
85
|
-
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makePvpScreen } from \"./PvpScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// PvP duels as a feature of the game: open duels to join, my duels, and creating one. The fight is\n// resolved instantly by the server from the heroes' stats and the saved strategy.\nexport const pvpModule: Module = defineModule({\n id: \"pvp\",\n meta: { name: \"Arena\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"pvp\",\n label: t(\"title\"),\n icon: \"swords\",\n group: \"social\",\n order: 95,\n available: false,\n Screen: makePvpScreen(ctx.features),\n });\n const decide = () =>\n ctx.features.setAvailable(\n \"pvp\",\n hasTitleData(\n configSection<Parameters<typeof hasTitleData>[0]>(\n ctx.client,\n \"Match\",\n ),\n ),\n \"no duel rules on the title (Match.InstantBattle.Rules)\",\n );\n decide();\n // The engine does not put Match into the client's title config: read it once with its own call\n // (it lands in the same config section). Without this the tab never showed on any title.\n if (!configSection(ctx.client, \"Match\"))\n void ctx.client.match.getDefinitions().then(decide);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"pvp\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
85
|
+
"content": "import { defineModule, type Module } from \"@idosgames/module-sdk\";\nimport { configSection } from \"@idosgames/react/ui\";\nimport { makePvpScreen } from \"./PvpScreen\";\nimport { hasTitleData } from \"./model\";\nimport { t } from \"./i18n\";\n\n// PvP duels as a feature of the game: open duels to join, my duels, and creating one. The fight is\n// resolved instantly by the server from the heroes' stats and the saved strategy.\nexport const pvpModule: Module = defineModule({\n id: \"pvp\",\n meta: { name: \"Arena\", type: \"app\", engine: \"dom\" },\n setup(ctx) {\n ctx.features.register({\n id: \"pvp\",\n label: () => t(\"title\"),\n icon: \"swords\",\n group: \"social\",\n order: 95,\n available: false,\n Screen: makePvpScreen(ctx.features),\n });\n const decide = () =>\n ctx.features.setAvailable(\n \"pvp\",\n hasTitleData(\n configSection<Parameters<typeof hasTitleData>[0]>(\n ctx.client,\n \"Match\",\n ),\n ),\n \"no duel rules on the title (Match.InstantBattle.Rules)\",\n );\n decide();\n // The engine does not put Match into the client's title config: read it once with its own call\n // (it lands in the same config section). Without this the tab never showed on any title.\n if (!configSection(ctx.client, \"Match\"))\n void ctx.client.match.getDefinitions().then(decide);\n ctx.exposeToAgent({\n state: () => ({ ui: \"dom\", feature: \"pvp\" }),\n describeActions: {},\n });\n },\n});\n"
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
"path": "PvpScreen.tsx",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"path": "module.meta.json",
|
|
93
|
-
"content": "{\n \"id\": \"pvp\",\n \"type\": \"feature\",\n \"summary\": \"PvP duels for any game: open duels to join, your duels, creating a duel for coins, a zone strategy and the fight replayed round by round.\",\n \"description\": \"A ready PvP arena (Unity Alikhan/PVP/Lobby) for ANY game, built on the @idosgames/react/ui kit, client.match and client.character. Tabs: open duels (join with one of your heroes) and your duels (waiting — cancel returns the entry; finished — won / lost / draw). Popups: a new duel (rule, hero by power, entry within the rule's bounds and what the winner takes after the burn), the battle strategy (attack and defence zone — head, body, legs — per round, saved as the default) and the result, replayed round by round (whose blow, where, hit / critical / block / dodge, damage, HP left). The server fights the battle instantly from the heroes' stats; a draw returns the entries. Without a hero the screen offers the Heroes feature. Registers the 'pvp' feature. Ships title-seed.json: a coin duel (10–500, 10% burned) fought by the demo heroes' Attack and Health.\",\n \"provides\": [\n \"open duels to join\",\n \"my duels with cancel\",\n \"create a duel with an entry\",\n \"zone battle strategy\",\n \"fight result replayed round by round\",\n \"demo duel rule\"\n ],\n \"tags\": [\"pvp\", \"duel\", \"arena\", \"battle\", \"matchmaking\", \"ui\", \"screen\"],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.
|
|
93
|
+
"content": "{\n \"id\": \"pvp\",\n \"type\": \"feature\",\n \"summary\": \"PvP duels for any game: open duels to join, your duels, creating a duel for coins, a zone strategy and the fight replayed round by round.\",\n \"description\": \"A ready PvP arena (Unity Alikhan/PVP/Lobby) for ANY game, built on the @idosgames/react/ui kit, client.match and client.character. Tabs: open duels (join with one of your heroes) and your duels (waiting — cancel returns the entry; finished — won / lost / draw). Popups: a new duel (rule, hero by power, entry within the rule's bounds and what the winner takes after the burn), the battle strategy (attack and defence zone — head, body, legs — per round, saved as the default) and the result, replayed round by round (whose blow, where, hit / critical / block / dodge, damage, HP left). The server fights the battle instantly from the heroes' stats; a draw returns the entries. Without a hero the screen offers the Heroes feature. Registers the 'pvp' feature. Ships title-seed.json: a coin duel (10–500, 10% burned) fought by the demo heroes' Attack and Health.\",\n \"provides\": [\n \"open duels to join\",\n \"my duels with cancel\",\n \"create a duel with an entry\",\n \"zone battle strategy\",\n \"fight result replayed round by round\",\n \"demo duel rule\"\n ],\n \"tags\": [\"pvp\", \"duel\", \"arena\", \"battle\", \"matchmaking\", \"ui\", \"screen\"],\n \"author\": {\n \"name\": \"iDos Games\",\n \"url\": \"https://idosgames.com\"\n },\n \"version\": \"0.1.1\"\n}\n"
|
|
94
94
|
}
|
|
95
95
|
]
|
|
96
96
|
}
|