-
Notifications
You must be signed in to change notification settings - Fork 238
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8541c95
commit d916741
Showing
3 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# Steamworks | ||
|
||
The Steamworks extension lets you use these Steam APIs: | ||
|
||
- Basic user information (name, id, level, country) | ||
- Achievements | ||
- DLC | ||
- Opening URLs in the Steam Overlay | ||
|
||
## Enabling Steamworks | ||
|
||
Steamworks support will be automatically enabled when you [package](https://packager.turbowarp.org/) your project using one of these environments: | ||
|
||
- Electron Windows application (64-bit) | ||
- Electron macOS application | ||
- Electron Linux application (64-bit) | ||
|
||
The blocks will not work in the editor, 32-bit environments, ARM environments, plain HTML files, WKWebView, or NW.js. You can still run the blocks, they just won't interact with Steam at all. | ||
|
||
When you package a project that uses the Steamworks extension, the packager willl ask you to enter your game's App ID, which you can find on the Steamworks website. If you don't have an App ID yet, see the demo game section below. | ||
|
||
You can just run the executable directly as usual; you don't need to start the game from Steam for Steamworks to function. There are a couple caveats: | ||
|
||
- On macOS and Linux, the Steam overlay may not function | ||
- On Linux, Steam needs to be installed as a native package, not as a Flatpak/Snap as the sandbox will prevent the apps from communicating | ||
|
||
## Security considerations | ||
|
||
Using the Steamworks extension will not prevent people from pirating your game. | ||
|
||
The Steamworks extension is also inherently client-side, so a cheater could manipulate all of the Steamworks blocks to return whatever they want. You shouldn't use them for things that are security critical. | ||
|
||
## Demo game | ||
|
||
For testing the Steamworks extension without paying for a Steamworks Partner Program membership, you can use the free Steamworks demo game. It's called Spacewar and its App ID is `480`. You don't need to install Spacewar; rather you can use its App ID to test various Steamworks APIs. | ||
|
||
Spacewar has achievements with the following API Names, which can used for testing the achievement blocks: | ||
|
||
- `ACH_WIN_ONE_GAME` | ||
- `ACH_WIN_100_GAMES` | ||
- `ACH_TRAVEL_FAR_ACCUM` | ||
- `ACH_TRAVEL_FAR_SINGLE` | ||
|
||
## Basic information | ||
|
||
Remember that Steamworks is only properly enabled when your project is packaged in a few specific environments. You can detect if this is the case using: | ||
|
||
```scratch | ||
<has steamworks? :: #136C9F> | ||
``` | ||
|
||
Then you can get basic information about the user using: | ||
|
||
```scratch | ||
(get user (name v) :: #136C9F) | ||
``` | ||
|
||
## Achievements | ||
|
||
Achievements are created in the Steamworks website. The **API Name** of each achievement is what you need to provide in your project's code to the Steamworks extension. | ||
|
||
This would unlock the `ACH_WIN_ONE_GAME` achievement from Spacewar: | ||
|
||
```scratch | ||
when this sprite clicked | ||
set achievement [ACH_WIN_ONE_GAME] unlocked to (true v) :: #136C9F | ||
``` | ||
|
||
You can also detect if an achievement has already been unlocked: | ||
|
||
```scratch | ||
when flag clicked | ||
forever | ||
if <achievement [ACH_WIN_ONE_GAME] unlocked? :: #136C9F> then | ||
say [Unlocked!] | ||
else | ||
say [Not unlocked :(] | ||
end | ||
end | ||
``` | ||
|
||
## DLC | ||
|
||
Each DLC has its own App ID which you can find in the Steamworks website. You can detect if it is installed using: | ||
|
||
```scratch | ||
if <(DLC v) [1234] installed? :: #136C9F> then | ||
end | ||
``` | ||
|
||
## Overlay | ||
|
||
The Steamworks extension has a block to open URLs in the Steam Overlay's web browser. If the overlay is not working, it might open in the Steam app instead. If that also doesn't work, it will open in the default web browser. Regardless it won't display the "The project wants to open a new window or tab" security prompt when packaged. | ||
|
||
```scratch | ||
open (URL v) [https://example.com/] in overlay :: #136C9F | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
// Name: Steamworks | ||
// ID: steamworks | ||
// Description: Connect your project to Steamworks APIs. | ||
// License: MPL-2.0 | ||
// Context: Probably don't translate the word "Steamworks". | ||
|
||
(function (Scratch) { | ||
"use strict"; | ||
|
||
/* globals Steamworks */ | ||
|
||
const canUseSteamworks = typeof Steamworks !== "undefined" && Steamworks.ok(); | ||
|
||
class SteamworksExtension { | ||
getInfo() { | ||
return { | ||
id: "steamworks", | ||
name: "Steamworks", | ||
color1: "#136C9F", | ||
color2: "#105e8c", | ||
color3: "#0d486b", | ||
docsURI: "https://extensions.turbowarp.org/steamworks", | ||
blocks: [ | ||
{ | ||
blockType: Scratch.BlockType.BOOLEAN, | ||
opcode: "hasSteamworks", | ||
text: Scratch.translate("has steamworks?"), | ||
}, | ||
|
||
{ | ||
blockType: Scratch.BlockType.REPORTER, | ||
opcode: "getUserInfo", | ||
text: Scratch.translate({ | ||
default: "get user [THING]", | ||
description: | ||
"[THING] is a dropdown with name, steam ID, account level, IP country, etc.", | ||
}), | ||
arguments: { | ||
THING: { | ||
type: Scratch.ArgumentType.STRING, | ||
menu: "userInfo", | ||
}, | ||
}, | ||
}, | ||
|
||
"---", | ||
|
||
{ | ||
blockType: Scratch.BlockType.COMMAND, | ||
opcode: "setAchievement", | ||
text: Scratch.translate({ | ||
default: "set achievement [ACHIEVEMENT] unlocked to [STATUS]", | ||
description: "[STATUS] is true/false dropdown", | ||
}), | ||
arguments: { | ||
ACHIEVEMENT: { | ||
type: Scratch.ArgumentType.STRING, | ||
defaultValue: "", | ||
}, | ||
STATUS: { | ||
type: Scratch.ArgumentType.STRING, | ||
menu: "achievementUnlocked", | ||
}, | ||
}, | ||
}, | ||
{ | ||
blockType: Scratch.BlockType.BOOLEAN, | ||
opcode: "getAchievement", | ||
text: Scratch.translate("achievement [ACHIEVEMENT] unlocked?"), | ||
arguments: { | ||
ACHIEVEMENT: { | ||
type: Scratch.ArgumentType.STRING, | ||
defaultValue: "", | ||
}, | ||
}, | ||
}, | ||
|
||
"---", | ||
|
||
{ | ||
blockType: Scratch.BlockType.BOOLEAN, | ||
opcode: "getInstalled", | ||
text: Scratch.translate({ | ||
default: "[TYPE] [ID] installed?", | ||
description: "eg. can be read as 'DLC 1234 installed?'", | ||
}), | ||
arguments: { | ||
TYPE: { | ||
type: Scratch.ArgumentType.STRING, | ||
menu: "installType", | ||
}, | ||
ID: { | ||
type: Scratch.ArgumentType.STRING, | ||
defaultValue: "", | ||
}, | ||
}, | ||
}, | ||
|
||
"---", | ||
|
||
{ | ||
blockType: Scratch.BlockType.COMMAND, | ||
opcode: "openInOverlay", | ||
text: Scratch.translate({ | ||
default: "open [TYPE] [DATA] in overlay", | ||
description: "eg. 'open URL example.com in overlay'", | ||
}), | ||
arguments: { | ||
TYPE: { | ||
type: Scratch.ArgumentType.STRING, | ||
menu: "overlayType", | ||
}, | ||
DATA: { | ||
type: Scratch.ArgumentType.STRING, | ||
defaultValue: "https://example.com/", | ||
}, | ||
}, | ||
}, | ||
], | ||
menus: { | ||
userInfo: { | ||
acceptReporters: true, | ||
items: [ | ||
{ | ||
value: "name", | ||
text: Scratch.translate("name"), | ||
}, | ||
{ | ||
value: "level", | ||
text: Scratch.translate({ | ||
default: "level", | ||
description: "Steam account level", | ||
}), | ||
}, | ||
{ | ||
value: "IP country", | ||
text: Scratch.translate("IP country"), | ||
}, | ||
{ | ||
value: "steam ID", | ||
text: Scratch.translate("steam ID"), | ||
}, | ||
], | ||
}, | ||
|
||
achievementUnlocked: { | ||
acceptReporters: true, | ||
items: [ | ||
{ | ||
value: "true", | ||
text: Scratch.translate("true"), | ||
}, | ||
{ | ||
value: "false", | ||
text: Scratch.translate("false"), | ||
}, | ||
], | ||
}, | ||
|
||
installType: { | ||
acceptReporters: true, | ||
items: [ | ||
{ | ||
value: "DLC", | ||
text: Scratch.translate({ | ||
default: "DLC", | ||
description: "Downloadable content", | ||
}), | ||
}, | ||
], | ||
}, | ||
|
||
overlayType: { | ||
acceptReporters: true, | ||
items: [ | ||
{ | ||
value: "URL", | ||
text: Scratch.translate("URL"), | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
hasSteamworks() { | ||
return canUseSteamworks; | ||
} | ||
|
||
getUserInfo({ THING }) { | ||
if (!canUseSteamworks) return "Steamworks unavailable"; | ||
switch (THING) { | ||
case "name": | ||
return Steamworks.localplayer.getName(); | ||
case "level": | ||
return Steamworks.localplayer.getLevel(); | ||
case "IP country": | ||
return Steamworks.localplayer.getIpCountry(); | ||
case "steam ID": | ||
return Steamworks.localplayer.getSteamId().steamId64; | ||
} | ||
return "???"; | ||
} | ||
|
||
setAchievement({ ACHIEVEMENT, STATUS }) { | ||
if (!canUseSteamworks) return; | ||
if (Scratch.Cast.toBoolean(STATUS)) { | ||
Steamworks.achievement.activate(Scratch.Cast.toString(ACHIEVEMENT)); | ||
} else { | ||
Steamworks.achievement.clear(Scratch.Cast.toString(ACHIEVEMENT)); | ||
} | ||
} | ||
|
||
getAchievement({ ACHIEVEMENT }) { | ||
if (!canUseSteamworks) return false; | ||
return Steamworks.achievement.isActivated( | ||
Scratch.Cast.toString(ACHIEVEMENT) | ||
); | ||
} | ||
|
||
getInstalled({ TYPE, ID }) { | ||
if (!canUseSteamworks) return false; | ||
if (TYPE === "DLC") { | ||
return Steamworks.apps.isDlcInstalled(Scratch.Cast.toNumber(ID)); | ||
} | ||
return false; | ||
} | ||
|
||
openInOverlay({ TYPE, DATA }) { | ||
if (TYPE === "URL") { | ||
const url = Scratch.Cast.toString(DATA); | ||
if (canUseSteamworks) { | ||
// This will always be a packaged environment so don't need to bother | ||
// with canOpenWindow() | ||
Steamworks.overlay.activateToWebPage(DATA); | ||
} else { | ||
// Don't await result, we don't care | ||
Scratch.openWindow(url); | ||
} | ||
} | ||
} | ||
} | ||
|
||
Scratch.extensions.register(new SteamworksExtension()); | ||
})(Scratch); |