Restructure Steam and Electron code

This commit is contained in:
Andrei Andreev 2023-02-16 01:03:21 +01:00
parent 7ec351dd2b
commit c33e34d50d
37 changed files with 6608 additions and 421 deletions

View File

@ -1,4 +1,4 @@
public/**/*.js
src/components/SliderComponent.vue
javascripts/supported-browsers.js
src/steam/PlayFabClientApi.js
src/steam/bindings/PlayFabClientApi.js

View File

@ -1,3 +1,4 @@
import { SteamRuntime } from "@/steam";
import { GameMechanicState } from "../game-mechanics/index";
class AchievementState extends GameMechanicState {
@ -70,7 +71,7 @@ class AchievementState extends GameMechanicState {
GameUI.notify.reality(`Automatically unlocked: ${this.name}`);
} else {
GameUI.notify.success(`Achievement: ${this.name}`);
SteamFunctions.GiveAchievement(this.id);
SteamRuntime.activateAchievement(this.id);
}
if (player.speedrun.isActive && !player.speedrun.achievementTimes[this.id]) {
// This stores a lot of data in the savefile and seems particularly suceptible to floating-point rounding issues
@ -173,6 +174,12 @@ export const Achievements = {
get power() {
if (Pelle.isDisabled("achievementMult")) return 1;
return Achievements._power.value;
},
updateSteamStatus() {
for (const achievement in Achievements.all.filter(x => x.isUnlocked)) {
SteamRuntime.activateAchievement(achievement.id);
}
}
};

View File

@ -1,3 +1,4 @@
import { ElectronRuntime } from "@/steam";
import { sha512_256 } from "js-sha512";
import { DEV } from "@/env";
@ -18,7 +19,7 @@ export class GameOptions {
// This is needed because .s-base--dark is on newUI/normal but not on oldUI/normal
// So the classes on body need to be updated
Themes.find(Theme.currentName()).set();
SteamFunctions.UIZoom()
ElectronRuntime.updateZoom();
GameStorage.save();
}

View File

@ -46,3 +46,7 @@ window.onerror = (event, source) => {
if (!source.endsWith(".js")) return;
GlobalErrorHandler.onerror(event);
};
window.addEventListener("unhandledrejection", event => {
GlobalErrorHandler.onerror(event);
});

View File

@ -1,14 +1,15 @@
import { discordRichPresence } from "./secret-formula/discord-rich-presence";
export const RichPresenceInfo = {
get gameStage() {
const stageDB = GameDatabase.discordRichPresence.stages;
const stageDB = discordRichPresence.stages;
for (let stage = stageDB.length - 1; stage >= 0; stage--) {
if (stageDB[stage].hasReached()) return stageDB[stage];
}
throw Error("No valid progress stage found");
},
get challengeState() {
const challDB = GameDatabase.discordRichPresence.challenges;
const challDB = discordRichPresence.challenges;
for (let index = 0; index < challDB.length; index++) {
const chall = challDB[index];
if (chall.activityToken()) return chall;

View File

@ -1,3 +1,4 @@
import { ElectronRuntime } from "@/steam";
import Mousetrap from "mousetrap";
import { GameKeyboard } from "./keyboard";
@ -280,28 +281,28 @@ export const shortcuts = [
name: "Zoom In",
keys: ["ctrl", "="],
type: "bind",
function: () => {SteamFunctions.SetZoomLevel("Increase")},
function: () => ElectronRuntime.increaseZoom(),
visible: () => false
},
{
name: "Zoom In",
keys: ["ctrl", "+"],
type: "bind",
function: () => {SteamFunctions.SetZoomLevel("Increase")},
function: () => ElectronRuntime.increaseZoom(),
visible: () => false
},
{
name: "Zoom Out",
keys: ["ctrl", "-"],
type: "bind",
function: () => {SteamFunctions.SetZoomLevel("Decrease")},
function: () => ElectronRuntime.decreaseZoom(),
visible: () => false
},
{
name: "Reset Zoom",
keys: ["ctrl", "0"],
type: "bind",
function: () => {SteamFunctions.ResetZoom()},
function: () => ElectronRuntime.resetZoom(),
visible: () => false
},
];

View File

@ -90,7 +90,7 @@ const Payments = {
},
// Sends a request to purchase a STD upgrade, returning true if successful (and syncs data), false if not
async buyUpgrade(upgradeKey) {
async buyUpgrade(upgradeKey, cosmeticName) {
if (!Cloud.loggedIn) return false;
let res;
try {
@ -103,7 +103,7 @@ const Payments = {
user: Cloud.user.id,
upgrade: upgradeKey,
extraData: {
requestedSet: GlyphAppearanceHandler.chosenFromModal?.id,
requestedSet: cosmeticName,
fullCompletions: player.records.fullGameCompletions
}
})

View File

@ -1,42 +0,0 @@
import { PlayFab } from "@/steam/PlayFabClientApi";
export function playFabLogin() {
try {
Steam.getAuthSessionTicket(ticket => {
const SteamTicket = ticket.ticket.toString("hex");
PlayFab.settings.titleId = "59813";
const requestData = {
TitleId: PlayFab.settings.titleId,
SteamTicket,
CreateAccount: true
};
try {
PlayFab.ClientApi.LoginWithSteam(requestData, playFabLoginCallback);
} catch (e) {
console.log("Unable to send login request to PlayFab.");
}
});
} catch (e) {
console.log(e);
}
}
PlayFab.settings.titleId = "59813";
let playFabId = -1;
function playFabLoginCallback(data, error) {
if (error) {
console.log(error.errorMessage);
GameUI.notify.error("Couldn't log in to PlayFab Cloud.");
return;
}
if (data) {
playFabId = data.data.PlayFabId;
PlayFab.PlayFabID = playFabId;
GameUI.notify.info("Logged in to PlayFab Cloud");
PlayFab.ClientApi.UpdateUserTitleDisplayName({ "DisplayName": Steam.getSteamId().screenName });
console.log("Logged in to playFab");
SteamFunctions.autoLogin();
}
}

View File

@ -21,7 +21,7 @@ function formatMachines(realPart, imagPart) {
// This is used for Discord Rich Presence, the information which shows up on a person's profile badge in Discord if
// they are playing a game on Steam which has integration that pushes the info to Discord
GameDatabase.discordRichPresence = {
export const discordRichPresence = {
/**
* List of all challenges to display within DRP, checked from the first entry and iterating forward. It will only
* show the first one it finds for space reasons, but this also has the desirable effect of hiding key challenges
@ -261,3 +261,5 @@ GameDatabase.discordRichPresence = {
},
]
};
GameDatabase.discordRichPresence = discordRichPresence;

View File

@ -1,3 +1,4 @@
import { SteamRuntime } from "@/steam";
import { RebuyableMechanicState } from "./game-mechanics/index";
import Payments from "./payments";
@ -177,8 +178,14 @@ class ShopPurchaseState extends RebuyableMechanicState {
if (GameEnd.creditsEverClosed) return false;
if (this.config.instantPurchase && ui.$viewModel.modal.progressBar) return false;
// Contact the firebase server to verify the purchase
const success = true//await Payments.buyUpgrade(this.config.key);
const cosmeticName = this.config.key === "singleCosmeticSet"
? GlyphAppearanceHandler.chosenFromModal?.name
: undefined;
// Contact the purchase provider to verify the purchase
const success = SteamRuntime.isActive
? await SteamRuntime.purchaseShopItem(this.cost, this.config.key, cosmeticName)
: await Payments.buyUpgrade(this.config.key, cosmeticName);
if (!success) return false;
if (player.IAP.enabled) Speedrun.setSTDUse(true);

View File

@ -1,4 +1,5 @@
/* eslint-disable import/extensions */
import { SteamRuntime } from "@/steam";
import pako from "pako/dist/pako.esm.mjs";
/* eslint-enable import/extensions */
@ -52,6 +53,29 @@ export const Cloud = {
}
},
async loginWithSteam(accountId, staticAccountId, screenName) {
if (this.loggedIn) {
Cloud.user.displayName = screenName;
return true;
}
const email = `${accountId}@ad.com`;
const pass = staticAccountId;
try {
await Cloud.manualCloudCreate(email, pass);
} catch {
try {
await Cloud.manualCloudLogin(email, pass);
} catch (error) {
// eslint-disable-next-line no-console
console.log(`Firebase Login Error: ${error}`);
return false;
}
}
Cloud.user.displayName = screenName;
return true;
},
async manualCloudLogin(EmailAddress,Password) {
//try{
@ -234,16 +258,15 @@ export const Cloud = {
if (user) {
this.user = {
id: user.uid,
displayName: steamOn ? Steam.getSteamId().screenName : "",//user.displayName,
displayName: SteamRuntime.isActive
? SteamRuntime.screenName
: user.displayName,
email: user.email,
};
SteamFunctions.SyncPlayFabSTD()
SteamRuntime.syncIap();
} else {
this.user = null;
}
});
},
};
Cloud.init();
//this.user.displayName = Steam.getSteamId().screenName

View File

@ -1,8 +1,10 @@
import { SteamRuntime } from "@/steam";
import * as ADNotations from "@antimatter-dimensions/notations";
import { DEV } from "@/env";
import { deepmergeAll } from "@/utility/deepmerge";
import { Achievement } from "../achievements/normal-achievement";
export const GameStorage = {
currentSlot: 0,
@ -30,7 +32,7 @@ export const GameStorage = {
const root = GameSaveSerializer.deserialize(save);
this.loadRoot(root);
SteamFunctions.BackfillAchievements()
Achievements.updateSteamStatus();
},
loadRoot(root) {
@ -66,7 +68,7 @@ export const GameStorage = {
Tabs.all.find(t => t.id === player.options.lastOpenTab).show(true);
Cloud.resetTempState();
GameUI.notify.info("Game loaded");
SteamFunctions.BackfillAchievements()
Achievements.updateSteamStatus();
},
import(saveData) {
@ -93,7 +95,7 @@ export const GameStorage = {
// is showing
Tab.options.subtabs[0].show();
GameUI.notify.info("Game imported");
SteamFunctions.BackfillAchievements()
Achievements.updateSteamStatus();
},
importAsFile() {

View File

@ -1,10 +1,11 @@
import { ElectronRuntime, SteamRuntime } from "@/steam";
import TWEEN from "tween.js";
import { DC } from "./core/constants";
import { deepmergeAll } from "@/utility/deepmerge";
import { playFabLogin } from "./core/playfab";
import { DEV } from "@/env";
import { SpeedrunMilestones } from "./core/speedrun";
import { Cloud } from "./core/storage";
import { supportedBrowsers } from "./supported-browsers";
import Payments from "./core/payments";
@ -1022,12 +1023,6 @@ window.onload = function() {
GameUI.initialized = supportedBrowser;
ui.view.initialized = supportedBrowser;
setTimeout(() => {
if(Steam){
if(Steam.initAPI()){
playFabLogin();
if(SteamFunctions.discordOn){SteamFunctions.SetRichPresence()};
}
}
document.getElementById("loading").style.display = "none";
}, 500);
if (!supportedBrowser) {
@ -1064,15 +1059,11 @@ export function init() {
// eslint-disable-next-line no-console
console.log("👨‍💻 Development Mode 👩‍💻");
}
ElectronRuntime.initialize();
SteamRuntime.initialize();
Cloud.init();
GameStorage.load();
Tabs.all.find(t => t.config.id === player.options.lastOpenTab).show(true);
if(steamOn){
SteamFunctions.UIZoom();
if(!Cloud.loggedIn){
SteamFunctions.autoLogin()
}
}
//shop.init();
Payments.init();
}

View File

@ -1,282 +0,0 @@
"use strict";
const SteamFunctions = {
purchaseChecker: [],
purchasesInitiated: true,
macUser: false,
macInterval: 0,
discordInterval: 0,
richPresenceInterval: 0,
discordOn: false,
macIntervalOn: false,
zoomLevel: 1,
SteamInitialize() {
this.forceRefresh();
this.EventHandlers();
if (window.navigator.platform === "MacIntel") {
SteamFunctions.macUser = true;
}else{
Steam.initDiscordAPI("1057439416819396689",1399720)
SteamFunctions.discordOn = true
SteamFunctions.richPresenceInterval = setInterval(SteamFunctions.SetRichPresence,8000)
SteamFunctions.discordInterval = setInterval(Steam.runDiscordCallbacks, 4000);
}
this.GetZoom()
},
forceRefresh() { // Canvas workaround to enable overlay
const canvas = document.getElementById("forceRefreshCanvas");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
window.requestAnimationFrame(SteamFunctions.forceRefresh);
},
SetRichPresence(){
if(SteamFunctions.discordOn && RichPresenceInfo){
Steam.setDiscordActivity({
smallImage: "icon",
largeImage: "icon",
state: RichPresenceInfo.state,
details: RichPresenceInfo.details
})
}
},
UIZoom() {
if (nodeOn) {
const setSize = 1020;
const SizeDiff = window.outerHeight / setSize;
require("electron").webFrame.setZoomFactor(SizeDiff*SteamFunctions.zoomLevel);
}
},
SetZoomLevel(ZoomType){
const zoomMAXed = SteamFunctions.zoomLevel >= 1.5 && ZoomType==="Increase"
const zoomMINed = SteamFunctions.zoomLevel <= 0.5 && ZoomType==="Decrease"
const zoomPossible = !(zoomMAXed || zoomMINed)
if(zoomPossible){
ZoomType==="Increase" && !zoomMAXed ? SteamFunctions.zoomLevel += .1 : SteamFunctions.zoomLevel = SteamFunctions.zoomLevel
ZoomType==="Decrease" && !zoomMINed ? SteamFunctions.zoomLevel -= .1 : SteamFunctions.zoomLevel = SteamFunctions.zoomLevel
SteamFunctions.zoomLevel = Math.round(SteamFunctions.zoomLevel * 10) / 10
localStorage.setItem("Zoom",SteamFunctions.zoomLevel)
SteamFunctions.UIZoom()
GameUI.notify.info(`Size changed to ${Math.round(SteamFunctions.zoomLevel*100)}%`)
}else{
zoomMAXed ? GameUI.notify.info(`Zoom Level is at Maximum`) : GameUI.notify.info(`Zoom Level is at Minimum`)
}
},
GetZoom(){
let zoomGet;
if(localStorage.getItem("Zoom")){
zoomGet = Number(localStorage.getItem("Zoom"))
}else{
localStorage.setItem("Zoom",1)
zoomGet = 1
}
SteamFunctions.zoomLevel = zoomGet
},
ResetZoom(){
localStorage.setItem("Zoom",1)
SteamFunctions.zoomLevel = 1
SteamFunctions.UIZoom()
GameUI.notify.info(`Size reverted to 100%`)
},
EventHandlers() {
Steam.on("micro-txn-authorization-response", (data, ordered, orderstate) => {
if (orderstate === true) {
SteamFunctions.PurchaseValidation();
}
});
},
GiveAchievement(AchieveID) {
if (Steam && steamOn) {
if (Steam.activateAchievement) {
Steam.activateAchievement(`Achievement${AchieveID}`,
() => console.log(`Successfully Achieved Achievement${AchieveID}`),
err => console.log(`Achievement Error: ${err}`)
);
}
}
},
BackfillAchievements() {
if (Steam && steamOn) {
if (Steam.activateAchievement) {
const achAchieved = []
const achErrored = []
for (const ach in Achievements.all) {
if (Achievements.all[ach].isUnlocked) {
Steam.activateAchievement(`Achievement${Achievements.all[ach].id}`,
() => achAchieved.push(Achievements.all[ach].id),
err => achErrored.push(Achievements.all[ach].id)
);
}
}
}
}
},
async autoLogin(){
if(!Cloud.loggedIn){
const AutoEmail = `${Steam.getSteamId().accountId}@ad.com`
const AutoPass = Steam.getSteamId().staticAccountId
try{
await Cloud.manualCloudCreate(AutoEmail,AutoPass);
}catch(e){
try{
await Cloud.manualCloudLogin(AutoEmail,AutoPass)
}catch(LoginError){
this.error = true;
this.errorMessage = "Unable to login, please recheck email/password";
console.log(`Login Error, Retrying (${LoginError})`)
return;
}
}
Cloud.user.displayName = Steam.getSteamId().screenName
}
SteamFunctions.SyncPlayFabSTD()
},
PurchaseIAP(STD, kreds) {
if (!steamOn) return;
const TheItem = { ItemId: `${STD}STD`, Quantity: 1, Annotation: "Purchased via in-game store" };
PlayFab.settings.titleId = "59813";
const loginRequest = {
Items: [TheItem]
};
PlayFab.ClientApi.StartPurchase(loginRequest, (result, error) => {
console.log(result, error);
if (result !== null) {
const TheOrder = result.data.OrderId;
const PurchaseRequest = {
OrderId: TheOrder,
Currency: "RM",
ProviderName: "Steam"
};
PlayFab.ClientApi.PayForPurchase(PurchaseRequest, (purchaseResult, purchaseError) => {
if (purchaseResult !== null) {
const txnID = purchaseResult.data.ProviderData;
SteamFunctions.purchaseChecker.push(purchaseResult.data.OrderId);
if (window.navigator.platform === "MacIntel") {
shell.openExternal("https://store.steampowered.com/checkout/approvetxn/" + txnID + "/?returnurl=steam");
SteamFunctions.macInterval = setInterval(async()=>{
SteamFunctions.PurchaseValidation()
},2000)
SteamFunctions.macIntervalOn = true
setTimeout(()=>{clearInterval(SteamFunctions.macInterval);SteamFunctions.macIntervalOn = false},300000)
}
} else if (purchaseError !== null) {
console.log(purchaseError);
}
});
} else if (error !== null) {
console.log(error);
}
});
},
ConfirmSteamPurchase(OrderIdentifier) {
PlayFab.ClientApi.ConfirmPurchase({ OrderId: OrderIdentifier }, (result, error) => {
if (result !== null && result.data.Items != null) {
const PurchaseName = result.data.Items[0].ItemId;
const PurchaseInstance = result.data.Items[0].ItemInstanceId;
PlayFab.ClientApi.ConsumeItem({ ItemInstanceId: PurchaseInstance, ConsumeCount: 1 },
(consumeResult, consumeError) => {
if (consumeResult !== null) {
const stdsBought = Number(PurchaseName.replace("STD", ""));
const currencyAddRequest = {Amount: stdsBought,VirtualCurrency: "ST"}
PlayFab.ClientApi.AddUserVirtualCurrency(currencyAddRequest, (result, error) => {
if (result !== null) {
SteamFunctions.SyncPlayFabSTD()
} else if (error !== null) {
console.log(error);
}
})
SteamFunctions.purchaseChecker = SteamFunctions.purchaseChecker.filter(item => item !== OrderIdentifier);
GameUI.notify.info(`${stdsBought} STDs Obtained!`);
} else if (consumeError !== null) {
console.log(consumeError);
}
});
} else if (error !== null) {
console.log("Awaiting Payment Confirmation");
}
});
},
PurchaseValidation() {
if (SteamFunctions.purchaseChecker.length > 0) {
SteamFunctions.purchaseChecker.forEach(
anOrder => SteamFunctions.ConfirmSteamPurchase(anOrder)
);
}
},
SyncPlayFabSTD(){
if(PlayFab.ClientApi.IsClientLoggedIn()){
PlayFab.ClientApi.GetUserInventory({PlayFabId: PlayFab.PlayFabId}, (result, error) => {
if (result !== null) {
const CurrentSTD = result.data.VirtualCurrency.ST
const Inventory = result.data.Inventory
ShopPurchaseData.totalSTD = CurrentSTD
const inventoryData = {}
Inventory.forEach(
ShopItem => inventoryData[ShopItem.ItemId] = ShopItem.RemainingUses
);
for (const key of Object.keys(GameDatabase.shopPurchases)) ShopPurchaseData[key] = inventoryData[key] ?? 0;
GameUI.update();
SteamFunctions.GetCosmetics()
} else if (error !== null) {
console.log(error);
}
})
}
},
PurchaseShopItem(itemCost,itemKey,itemConfig,chosenSet){
const itemPurchaseRequest = {
ItemId: itemKey,
Price: typeof itemCost === "function" ? itemCost() : itemCost,
VirtualCurrency: "ST"
}
PlayFab.ClientApi.PurchaseItem(itemPurchaseRequest, (result, error) => {
if (result !== null) {
if (itemConfig.instantPurchase) itemConfig.onPurchase();
if (itemKey === "singleCosmeticSet") this.StoreCosmetics(chosenSet)
SteamFunctions.SyncPlayFabSTD();
} else if (error !== null) {
console.log(error);
GameUI.notify.error(error.errorMessage)
}
})
},
StoreCosmetics(Cosmetic){
const CosmeticID = Object.entries(GameDatabase.reality.glyphCosmeticSets).filter(item => item[1]["name"]===Cosmetic)[0][0]
var CosmeticList = [CosmeticID]
PlayFab.ClientApi.GetUserData({PlayFabId: PlayFab.PlayFabId}, (result, error) => {
if (result !== null) {
if(result.data.Data["Cosmetics"]){
CosmeticList = CosmeticList.concat(result.data.Data["Cosmetics"].Value.split(","))
}
const updatedCosmetics = [...new Set(CosmeticList)]
const UpdateRequest = {
Data: {
Cosmetics: updatedCosmetics.join(",")
}
}
PlayFab.ClientApi.UpdateUserData(UpdateRequest, (result, error) => {
if (result !== null) {
console.log("Cosmetics Updated on Server");
ShopPurchaseData.unlockedCosmetics = updatedCosmetics
GameUI.update();
} else if (error !== null) {
console.log(error);
}
})
} else if (error !== null) {
console.log("Error Getting User Data");
}
})
},
GetCosmetics(){
PlayFab.ClientApi.GetUserData({PlayFabId: PlayFab.PlayFabId}, (result, error) => {
if (result !== null) {
const currentCosmetics = result.data.Data["Cosmetics"] ? result.data.Data["Cosmetics"].Value.split(",") : []
ShopPurchaseData.unlockedCosmetics = currentCosmetics
GameUI.update();
} else if (error !== null) {
console.log(error);
}
})
}
};

View File

@ -44,37 +44,6 @@
</video>
</div>
<div id="performance-stats" class="c-performance-stats" style="display: none;"></div>
<script type="text/javascript" src="Steam/steam.js"></script>
<canvas id="forceRefreshCanvas" style="position: fixed; right: 0px; bottom: 0px; width: 100vw; height: 100%; opacity: 0.01; pointer-events: none; z-index: -20;"></canvas>
<script>
/*--Post GameLoad Steam Initialization--*/
var nodeOn = window.require!==undefined ? true : false
steamOn=false
if(nodeOn){
var JQ = require('jquery');
var Steam = require('greenworks')
var shell = require('electron').shell;
if (Steam) {
if (Steam.initAPI()) {
console.log("Steam is Functional!")
steamOn = true
} else {
console.log("Steam API not activated. Are you sure you opened the game in Steam?")
}
}
JQ(window).resize(function(){
SteamFunctions.UIZoom()
});
if(Steam){
if(Steam.initAPI()){
SteamFunctions.SteamInitialize()
}else{
}
}
//console.log("SteamOn is "+steamOn)
}
</script>
</body>
<script>
// We use an IE only document variable to check here to force it to show the browser warning.

View File

@ -31,6 +31,17 @@ html {
background: white;
}
._steam-refresh-canvas {
width: 100%;
height: 100%;
position: fixed;
right: 0;
bottom: 0;
z-index: -20;
opacity: 0.01;
pointer-events: none;
}
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

View File

@ -1,4 +1,6 @@
<script>
import { openExternalLink } from "@/utility/open-external-link";
export default {
name: "InformationModalButton",
props: {
@ -25,9 +27,7 @@ export default {
openAssociatedModal() {
Modal[this.showModal].show();
},
openExternalLink(){
shell.openExternal(this.link);
}
openExternalLink
}
};
</script>

View File

@ -1,6 +1,7 @@
<script>
import ModalWrapper from "@/components/modals/ModalWrapper";
import StdStoreRow from "@/components/modals/StdStoreRow";
import { SteamRuntime } from "@/steam";
export default {
name: "StdStoreModal",
@ -15,10 +16,10 @@ export default {
},
methods: {
update() {
this.macPurchaser = SteamFunctions.macUser && SteamFunctions.purchaseChecker.length > 0;
this.macPurchaser = SteamRuntime.hasPendingPurchaseConfirmations;
},
macConfirm() {
SteamFunctions.PurchaseValidation();
SteamRuntime.validatePurchases();
}
},
};
@ -29,8 +30,8 @@ export default {
<template #header>
Support The Developer - coins
</template>
<span id='MacConfirm' v-if="macPurchaser">
<button class='o-shop-button-button' @click="macConfirm()">Confirm Purchase to Receive STDs</button>
<span v-if="macPurchaser">
<button class="o-shop-button-button" @click="macConfirm()">Confirm Purchase to Receive STDs</button>
<br><span>(Required on Mac)</span><br>
</span>
<div class="l-modal-store-content">

View File

@ -1,4 +1,5 @@
<script>
import { SteamRuntime } from "@/steam";
import Payments from "../../../javascripts/core/payments";
export default {
@ -15,7 +16,7 @@ export default {
},
methods: {
purchase() {
SteamFunctions.PurchaseIAP(this.amount, this.cost);
SteamRuntime.purchaseIap(this.amount);
}
},

View File

@ -1,4 +1,5 @@
<script>
import { ElectronRuntime } from "@/steam";
import SliderComponent from "@/components/SliderComponent";
export default {
@ -8,7 +9,7 @@ export default {
},
data() {
return {
updatedZoom: 0
zoomLevel: 0
};
},
computed: {
@ -24,14 +25,11 @@ export default {
},
methods: {
update() {
this.updatedZoom = Math.round(SteamFunctions.zoomLevel*100);
this.zoomLevel = Math.round(ElectronRuntime.zoomFactor * 100);
},
adjustSliderValue(value) {
this.updatedZoom = value;
SteamFunctions.zoomLevel = Math.round(value/10)/10
localStorage.setItem("Zoom",SteamFunctions.zoomLevel)
SteamFunctions.UIZoom()
GameUI.notify.info(`Size changed to ${Math.round(SteamFunctions.zoomLevel*100)}%`);
this.zoomLevel = value;
ElectronRuntime.zoomLevel = Math.round(value / 10) / 10;
}
}
};
@ -39,11 +37,11 @@ export default {
<template>
<div class="o-primary-btn o-primary-btn--option o-primary-btn--slider l-options-grid__button">
<b>Zoom Level: {{ formatInt(updatedZoom) }}%</b>
<b>Zoom Level: {{ formatInt(zoomLevel) }}%</b>
<SliderComponent
class="o-primary-btn--slider__slider"
v-bind="sliderProps"
:value="updatedZoom"
:value="zoomLevel"
@input="adjustSliderValue($event)"
/>
</div>

View File

@ -1,5 +1,4 @@
<script>
import { purchase } from 'vue-gtag';
export default {
name: "ShopButton",
props: {
@ -45,10 +44,11 @@ export default {
openSelectionModal() {
Modal.cosmeticSetChoice.show();
},
SteamPurchase(){
if(!this.isSingleCosmeticSet || this.hasChosen){
SteamFunctions.PurchaseShopItem(this.purchase.cost,this.purchase.config.key,this.purchase.config,this.chosenSet)
performPurchase() {
if (this.isSingleCosmeticSet && !this.hasChosen) {
return;
}
this.purchase.purchase();
},
purchaseButtonObject() {
return {
@ -105,7 +105,7 @@ export default {
</div>
<button
:class="purchaseButtonObject()"
@click="SteamPurchase()"
@click="performPurchase"
>
Cost: {{ cost }}
<img

View File

@ -1,5 +1,6 @@
<script>
import "vue-loading-overlay/dist/vue-loading.css";
import { STEAM } from "@/env";
import Loading from "vue-loading-overlay";
@ -33,7 +34,7 @@ export default {
return ShopPurchase.all;
},
buySTDText() {
return steamOn ? "Buy More" : "Play Online on Steam to buy STDs";
return STEAM ? "Buy More" : "Play Online on Steam to buy STDs";
}
},
watch: {
@ -67,7 +68,7 @@ export default {
}
},
showStore() {
if (!steamOn) return;
if (!STEAM) return;
if (this.creditsClosed) return;
SecretAchievement(33).unlock();
if (this.loggedIn) Modal.shop.show();

View File

@ -1,4 +1,7 @@
<script>
import { openExternalLink } from "@/utility/open-external-link";
import { STEAM } from "@/env";
export default {
name: "NewsTicker",
data() {
@ -72,7 +75,14 @@ export default {
this.currentNews.reset();
}
line.innerHTML = this.currentNews.text.replace(/href='/g, `onClick='shell.openExternal("`).replace(/' target='_blank'/g, `")'`);
let text = this.currentNews.text;
if (STEAM) {
window.openNewsLink = openExternalLink;
text = text
.replace(/href='/gu, `onClick='window.openNewsLink("`)
.replace(/' target='_blank'/gu, `")'`);
}
line.innerHTML = text;
line.style["transition-duration"] = "0ms";
if (this.currentNews?.id === "a244" || this.currentNews?.id === "ai63") {

View File

@ -1,2 +1,3 @@
export const DEV = process.env.VUE_APP_DEV === "true";
export const STEAM = process.env.VUE_APP_STEAM === "true";
export const MAC = window.navigator.platform === "MacIntel";

View File

@ -1303,7 +1303,7 @@ PlayFab.ClientApi = {
};
export var PlayFabClientSDK = PlayFab.ClientApi;
var PlayFabClientSDK = PlayFab.ClientApi;
PlayFab.RegisterWithPhaser = function() {
if ( typeof Phaser === "undefined" || typeof Phaser.Plugin === "undefined" )

View File

@ -0,0 +1,24 @@
import { NodeModule } from "../node-module";
/**
* @type {NodeModule<any>}
*/
const module = new NodeModule("electron");
export function isModuleLoaded() {
return module.isLoaded;
}
export function setZoomFactor(zoomFactor) {
const setSize = 1020;
const sizeDiff = window.outerHeight / setSize;
return module.safeCall(
x => x.webFrame.setZoomFactor(sizeDiff * zoomFactor)
);
}
export function openExternal(url) {
return module.safeCall(
x => x.shell.openExternal(url)
);
}

View File

@ -0,0 +1,72 @@
import { NodeModule } from "../node-module";
/**
* @type {NodeModule<Greenworks.NodeModule>}
*/
const module = new NodeModule("greenworks");
export function isModuleLoaded() {
return module.isLoaded;
}
export function initAPI() {
return module.safeCall(
x => x.initAPI(),
false
);
}
export function getSteamId() {
return module.safeCall(
x => x.getSteamId()
);
}
/**
* @returns {Promise<Greenworks.SteamAuthTicket>}
*/
export function getAuthSessionTicket() {
return module.makePromise(
(x, resolve, reject) => x.getAuthSessionTicket(resolve, reject)
);
}
/**
* @returns {Promise<void>}
*/
export function activateAchievement(id) {
return module.makePromise(
(x, resolve, reject) => x.activateAchievement(id, resolve, reject)
);
}
// Mako, please rename the second parameter. I have no idea what it is.
export function initDiscordAPI(clientId, steamGameId) {
return module.safeCall(
x => x.initDiscordAPI(clientId, steamGameId)
);
}
export function runDiscordCallbacks() {
return module.safeCall(
x => x.runDiscordCallbacks()
);
}
export function on(event, callback) {
return module.safeCall(
x => x.on(event, callback)
);
}
export function setDiscordActivity(state, details) {
return module.safeCall(
x => x.setDiscordActivity({
smallImage: "icon",
largeImage: "icon",
state,
details
})
);
}

View File

@ -0,0 +1,118 @@
import { PlayFab } from "@/steam/bindings/PlayFabClientApi";
/**
* @type {PlayFabClientModule.IPlayFabClient}
*/
const clientApi = PlayFab.ClientApi;
PlayFab.settings.titleId = "59813";
export function LoginWithSteam(ticket) {
return makePromise(clientApi.LoginWithSteam, {
SteamTicket: ticket,
CreateAccount: true
});
}
export function UpdateUserTitleDisplayName(displayName) {
makeAuthorizedPromise(clientApi.UpdateUserTitleDisplayName, {
DisplayName: displayName
});
}
export function GetUserData() {
return makeAuthorizedPromise(clientApi.GetUserData);
}
export function UpdateUserData(data) {
return makeAuthorizedPromise(clientApi.UpdateUserData, {
Data: data
});
}
export function GetUserInventory() {
return makeAuthorizedPromise(clientApi.GetUserInventory);
}
export function PurchaseItem(id, price, currency) {
return makeAuthorizedPromise(clientApi.PurchaseItem, {
ItemId: id,
Price: price,
VirtualCurrency: currency
});
}
export function StartPurchase(itemId, quantity, annotation) {
return makeAuthorizedPromise(clientApi.StartPurchase, {
Items: [{
ItemId: itemId,
Quantity: quantity,
Annotation: annotation
}]
});
}
export function PayForPurchase(orderId, currency, providerName) {
return makeAuthorizedPromise(clientApi.PayForPurchase, {
OrderId: orderId,
Currency: currency,
ProviderName: providerName
});
}
export function ConfirmPurchase(orderId) {
return makeAuthorizedPromise(clientApi.ConfirmPurchase, {
OrderId: orderId
});
}
export function ConsumeItem(itemInstanceId, consumeCount) {
return makeAuthorizedPromise(clientApi.ConsumeItem, {
ItemInstanceId: itemInstanceId,
ConsumeCount: consumeCount
});
}
export function AddUserVirtualCurrency(amount, virtualCurrency) {
return makeAuthorizedPromise(clientApi.ConfirmPurchase, {
Amount: amount,
VirtualCurrency: virtualCurrency
});
}
/**
* @template TRequest
* @template TResponse
* @param {(request: TRequest, callback: PlayFabModule.ApiCallback<TResponse>) => any} playFabFunction
* @param {TRequest} [request]
* @returns {Promise<TResponse>}
*/
function makeAuthorizedPromise(playFabFunction, request) {
if (!clientApi.IsClientLoggedIn()) {
return Promise.reject("PlayFab Client is not logged in.");
}
return makePromise(playFabFunction, request);
}
/**
* So, apparently, PlayFab Web SDK is so bad, the promises they are
* returning are not the actual promises for the api calls
* (just take a look inside PlayFabClient.js). This wrapper
* creates proper promises based on the callbacks.
* @template TRequest
* @template TResponse
* @param {(request: TRequest, callback: PlayFabModule.ApiCallback<TResponse>) => any} playFabFunction
* @param {TRequest} [request]
* @returns {Promise<TResponse>}
*/
function makePromise(playFabFunction, request) {
return new Promise((resolve, reject) => {
playFabFunction(request ?? {}, (data, error) => {
if (!error && data?.data) {
resolve(data.data);
} else {
reject(error ?? data);
}
});
});
}

View File

@ -0,0 +1,75 @@
import * as Electron from "./bindings/electron";
const MIN_ZOOM = 0.5;
const MAX_ZOOM = 1.5;
let zoomFactor = 1;
export const ElectronRuntime = {
initialize() {
if (!this.isActive) {
return;
}
zoomFactor = Number(localStorage.getItem("Zoom"));
zoomFactor = Number.isFinite(zoomFactor) ? zoomFactor : 1;
window.addEventListener("resize", () => this.updateZoom());
},
get isActive() {
return Electron.isModuleLoaded();
},
increaseZoom() {
if (!this.isActive) {
return;
}
if (zoomFactor > MAX_ZOOM) {
GameUI.notify.info("Zoom Level is at Maximum");
return;
}
this.zoomFactor = Math.round((zoomFactor + 0.1) * 10) / 10;
},
decreaseZoom() {
if (!this.isActive) {
return;
}
if (zoomFactor < MIN_ZOOM) {
GameUI.notify.info("Zoom Level is at Minimum");
return;
}
this.zoomFactor = Math.round((zoomFactor - 0.1) * 10) / 10;
},
resetZoom() {
if (!this.isActive) {
return;
}
this.zoomFactor = 1;
},
get zoomFactor() {
return zoomFactor;
},
set zoomFactor(value) {
zoomFactor = value;
localStorage.setItem("Zoom", zoomFactor.toString());
this.updateZoom();
GameUI.notify.info(`Size changed to ${Math.round(zoomFactor * 100)}%`);
},
updateZoom() {
if (!this.isActive) {
return;
}
Electron.setZoomFactor(zoomFactor);
}
};

2
src/steam/index.js Normal file
View File

@ -0,0 +1,2 @@
export { ElectronRuntime } from "./electron-runtime";
export { SteamRuntime } from "./steam-runtime";

60
src/steam/node-module.js Normal file
View File

@ -0,0 +1,60 @@
/* eslint-disable no-console */
import { STEAM } from "@/env";
/**
* @template TModule
*/
export class NodeModule {
/**
* @param {string} name
*/
constructor(name) {
this.name = name;
/**
* @type {TModule}
* @private
*/
this.__module__ = STEAM && window.require !== undefined ? window.require(name) : undefined;
}
get isLoaded() {
return this.__module__ !== undefined;
}
/**
* @template TResult
* @param {(module: TModule, resolve: (value?: TResult) => void, reject: (reason?: any) => void) => void} executor
* @returns {Promise<TResult>}
*/
makePromise(executor) {
if (!this.isLoaded) {
throw Error(`Node module "${this.name}" is not loaded.`);
}
return new Promise((resolve, reject) => {
executor(this.__module__, resolve, reject);
});
}
/**
* @template TResult
* @param {(module: TModule) => TResult} call
* @param {TResult} [defaultValue]
* @returns {TResult}
*/
safeCall(call, defaultValue) {
if (!this.isLoaded) {
throw Error(`Node module "${this.name}" is not loaded.`);
}
try {
return call(this.__module__);
} catch (e) {
console.error(`Failed to make a call to node module "${this.name}".`);
console.error(e);
return defaultValue;
}
}
}

View File

@ -0,0 +1,99 @@
import { MAC } from "@/env";
import { openExternalLink } from "@/utility/open-external-link";
import * as PlayFab from "./bindings/playfab";
let pendingConfirmations = [];
export function hasPendingPurchaseConfirmations() {
return MAC && pendingConfirmations.length > 0;
}
export async function purchaseIap(std) {
const itemId = `${std}STD`;
const quantity = 1;
const annotation = "Purchased via in-game store";
const order = await PlayFab.StartPurchase(itemId, quantity, annotation);
const orderId = order.OrderId;
const currency = "RM";
const providerName = "Steam";
const result = await PlayFab.PayForPurchase(orderId, currency, providerName);
pendingConfirmations.push(result.OrderId);
if (MAC) {
const txnId = result.ProviderData;
openExternalLink(`https://store.steampowered.com/checkout/approvetxn/${txnId}/?returnurl=steam`);
const macInterval = setInterval(() => {
validatePurchases();
}, 2000);
setTimeout(() => {
clearInterval(macInterval);
}, 300000);
}
}
export function validatePurchases() {
for (const order of pendingConfirmations) {
validatePurchase(order);
}
}
async function validatePurchase(orderId) {
const confirm = await PlayFab.ConfirmPurchase(orderId);
const purchaseName = confirm.Items[0].ItemId;
const purchaseInstance = confirm.Items[0].ItemInstanceId;
await PlayFab.ConsumeItem(purchaseInstance, 1);
const stdsBought = Number(purchaseName.replace("STD", ""));
pendingConfirmations = pendingConfirmations.filter(item => item !== orderId);
await PlayFab.AddUserVirtualCurrency(stdsBought, "ST");
GameUI.notify.info(`${stdsBought} STDs Obtained!`);
syncIap();
}
export async function syncIap() {
const userInventory = await PlayFab.GetUserInventory();
ShopPurchaseData.totalSTD = userInventory.VirtualCurrency?.ST ?? 0;
for (const key of Object.keys(GameDatabase.shopPurchases)) {
const item = userInventory.Inventory.find(x => x.ItemId === key);
ShopPurchaseData[key] = item?.RemainingUses ?? 0;
}
GameUI.update();
const userData = await PlayFab.GetUserData();
ShopPurchaseData.unlockedCosmetics = userData.Data.Cosmetics?.Value?.split(",") ?? [];
GameUI.update();
}
export async function purchaseShopItem(key, cost, cosmeticName) {
await PlayFab.PurchaseItem(key, cost, "ST");
await storeCosmetic(cosmeticName);
syncIap();
}
async function storeCosmetic(name) {
if (name === undefined) {
return;
}
const cosmetic = Object.values(GameDatabase.reality.glyphCosmeticSets)
.find(x => x.name === name);
if (cosmetic === undefined) {
GameUI.notify.error(`Failed to store cosmetic "${name}"`);
return;
}
const userData = await PlayFab.GetUserData();
const cosmetics = new Set(userData.Data?.Cosmetics?.Value?.split(",") ?? []);
cosmetics.add(cosmetic.id);
const updatedCosmetics = [...cosmetics];
await PlayFab.UpdateUserData({
Cosmetics: updatedCosmetics.join(",")
});
ShopPurchaseData.unlockedCosmetics = updatedCosmetics;
GameUI.update();
}

178
src/steam/steam-runtime.js Normal file
View File

@ -0,0 +1,178 @@
/* eslint-disable no-console */
import { RichPresenceInfo } from "../../javascripts/core/discord-parser";
import {
hasPendingPurchaseConfirmations,
purchaseIap,
purchaseShopItem,
syncIap,
validatePurchases
} from "./steam-purchases";
import * as Greenworks from "./bindings/greenworks";
import * as PlayFab from "./bindings/playfab";
import { MAC, STEAM } from "@/env";
let isInitialized = false;
let isActive = false;
export const SteamRuntime = {
initialize() {
if (isInitialized) {
throw Error("Steam Runtime was initialized already.");
}
isInitialized = true;
if (!STEAM || !Greenworks.isModuleLoaded() || !Greenworks.initAPI()) {
return;
}
isActive = true;
const steamId = Greenworks.getSteamId();
loginPlayFab(steamId);
loginFirebase(steamId);
Greenworks.on("micro-txn-authorization-response", (data, ordered, orderstate) => {
if (orderstate === true) {
validatePurchases();
}
});
if (!MAC) {
initializeDiscord();
}
createForceRefreshCanvas();
},
get isActive() {
if (!isInitialized) {
throw Error("Steam Runtime was called before init.");
}
return isActive;
},
get screenName() {
if (!this.isActive) {
return "Non-Steam user";
}
return Greenworks.getSteamId()?.screenName ?? "Steam user";
},
activateAchievement(id) {
if (!this.isActive) {
return;
}
Greenworks.activateAchievement(`Achievement${id}`);
},
async purchaseIap(std) {
if (!this.isActive) {
return;
}
await purchaseIap(std);
},
validatePurchases() {
if (!this.isActive) {
return;
}
validatePurchases();
},
async syncIap() {
if (!this.isActive) {
return;
}
await syncIap();
},
async purchaseShopItem(key, cost, cosmeticName) {
if (!this.isActive) {
GameUI.notify.error("Shop purchases are not available.");
return false;
}
try {
await purchaseShopItem(key, cost, cosmeticName);
return true;
} catch (e) {
GameUI.notify.error(e.errorMessage ?? e);
return false;
}
},
get hasPendingPurchaseConfirmations() {
if (!this.isActive) {
return false;
}
return hasPendingPurchaseConfirmations();
}
};
async function loginPlayFab(steamId) {
try {
const screenName = steamId.screenName;
const ticket = await Greenworks.getAuthSessionTicket();
await PlayFab.LoginWithSteam(ticket.ticket.toString("hex"), screenName);
PlayFab.UpdateUserTitleDisplayName(screenName);
GameUI.notify.info("Logged in to PlayFab Cloud");
} catch (error) {
GameUI.notify.error("Couldn't log in to PlayFab Cloud.");
throw error;
}
}
async function loginFirebase(steamId) {
const accountId = steamId.accountId;
const staticAccountId = steamId.staticAccountId;
const screenName = steamId.screenName;
if (await Cloud.loginWithSteam(accountId, staticAccountId, screenName)) {
syncIap();
}
}
function initializeDiscord() {
Greenworks.initDiscordAPI("1057439416819396689", 1399720);
setDiscordActivity();
Greenworks.runDiscordCallbacks();
setInterval(setDiscordActivity, 8000);
setInterval(Greenworks.runDiscordCallbacks, 4000);
}
function setDiscordActivity() {
Greenworks.setDiscordActivity(RichPresenceInfo.state, RichPresenceInfo.details);
}
function createForceRefreshCanvas() {
// This canvas is required for Steam overlay to properly work in Electron.
// Makopaz:
// "essentially it makes the overlay have a refresh rate, otherwise it only
// updates based on parts of the screen which change, so without it the small
// areas of the screen where antimatter and such increment would be the only
// small portions of the overlay showing."
// There should be a less expensive approach. Please create a new issue or
// PR on GitHub if you know one, the planet will say thank you for saving
// megawatts of electricity spent on this canvas.
const canvas = document.createElement("canvas");
canvas.classList.add("_steam-refresh-canvas");
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
function forceRefresh() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
window.requestAnimationFrame(forceRefresh);
}
forceRefresh();
}

5731
src/typings/PlayFabClientApi.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

82
src/typings/Playfab.d.ts vendored Normal file
View File

@ -0,0 +1,82 @@
/// <reference path="PlayFabAdminApi.d.ts" />
/// <reference path="PlayFabClientApi.d.ts" />
/// <reference path="PlayFabMatchmakerApi.d.ts" />
/// <reference path="PlayFabServerApi.d.ts" />
/// <reference path="PlayFabAuthenticationApi.d.ts" />
/// <reference path="PlayFabCloudScriptApi.d.ts" />
/// <reference path="PlayFabDataApi.d.ts" />
/// <reference path="PlayFabEconomyApi.d.ts" />
/// <reference path="PlayFabEventsApi.d.ts" />
/// <reference path="PlayFabExperimentationApi.d.ts" />
/// <reference path="PlayFabInsightsApi.d.ts" />
/// <reference path="PlayFabGroupsApi.d.ts" />
/// <reference path="PlayFabLocalizationApi.d.ts" />
/// <reference path="PlayFabMultiplayerApi.d.ts" />
/// <reference path="PlayFabProfilesApi.d.ts" />
declare module PlayFabModule {
export interface ISettings {
titleId: string;
developerSecretKey: string;
GlobalHeaderInjection?: { [key: string]: string };
productionServerUrl: string;
}
export interface IPlayFabRequestCommon { }
export interface IPlayFabError {
code: number;
status: string;
error: string;
errorCode: number;
errorMessage: string;
errorDetails?: { [key: string]: string[] };
request?: any;
customData?: any;
retryAfterSeconds?: number;
}
export interface SuccessContainer<TResult extends IPlayFabResultCommon> extends IPlayFabError {
data: TResult;
}
export interface IPlayFabResultCommon extends IPlayFabError { }
export interface ApiCallback<TResult extends IPlayFabResultCommon> { (result: SuccessContainer<TResult>, error: IPlayFabError): void }
}
declare var PlayFab: {
buildIdentifier: string;
sdkVersion: string;
GenerateErrorReport(IPlayFabError): string;
settings: PlayFabModule.ISettings;
AdminApi: PlayFabAdminModule.IPlayFabAdmin;
ClientApi: PlayFabClientModule.IPlayFabClient;
MatchmakerApi: PlayFabMatchmakerModule.IPlayFabMatchmaker;
ServerApi: PlayFabServerModule.IPlayFabServer;
AuthenticationApi: PlayFabAuthenticationModule.IPlayFabAuthentication;
CloudScriptApi: PlayFabCloudScriptModule.IPlayFabCloudScript;
DataApi: PlayFabDataModule.IPlayFabData;
EconomyApi: PlayFabEconomyModule.IPlayFabEconomy;
EventsApi: PlayFabEventsModule.IPlayFabEvents;
ExperimentationApi: PlayFabExperimentationModule.IPlayFabExperimentation;
InsightsApi: PlayFabInsightsModule.IPlayFabInsights;
GroupsApi: PlayFabGroupsModule.IPlayFabGroups;
LocalizationApi: PlayFabLocalizationModule.IPlayFabLocalization;
MultiplayerApi: PlayFabMultiplayerModule.IPlayFabMultiplayer;
ProfilesApi: PlayFabProfilesModule.IPlayFabProfiles;
};
// Continue to support older usage
declare var PlayFabAdminSDK: PlayFabAdminModule.IPlayFabAdmin;
declare var PlayFabClientSDK: PlayFabClientModule.IPlayFabClient;
declare var PlayFabMatchmakerSDK: PlayFabMatchmakerModule.IPlayFabMatchmaker;
declare var PlayFabServerSDK: PlayFabServerModule.IPlayFabServer;
declare var PlayFabAuthenticationSDK: PlayFabAuthenticationModule.IPlayFabAuthentication;
declare var PlayFabCloudScriptSDK: PlayFabCloudScriptModule.IPlayFabCloudScript;
declare var PlayFabDataSDK: PlayFabDataModule.IPlayFabData;
declare var PlayFabEconomySDK: PlayFabEconomyModule.IPlayFabEconomy;
declare var PlayFabEventsSDK: PlayFabEventsModule.IPlayFabEvents;
declare var PlayFabExperimentationSDK: PlayFabExperimentationModule.IPlayFabExperimentation;
declare var PlayFabInsightsSDK: PlayFabInsightsModule.IPlayFabInsights;
declare var PlayFabGroupsSDK: PlayFabGroupsModule.IPlayFabGroups;
declare var PlayFabLocalizationSDK: PlayFabLocalizationModule.IPlayFabLocalization;
declare var PlayFabMultiplayerSDK: PlayFabMultiplayerModule.IPlayFabMultiplayer;
declare var PlayFabProfilesSDK: PlayFabProfilesModule.IPlayFabProfiles;

30
src/typings/greenworks.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
export declare module Greenworks {
export interface NodeModule {
initAPI(): boolean;
getSteamId(): SteamId;
getAuthSessionTicket(resolve: (ticket: SteamAuthTicket) => void, reject: (error: any) => void): void;
activateAchievement(id: string, resolve: () => void, reject: (error: any) => void): void;
initDiscordAPI(clientId: string, steamGameId: number): void;
runDiscordCallbacks(): void;
setDiscordActivity(info: DiscordActivityInfo): void;
on(event: string, callback: Function): void;
}
export interface SteamId {
screenName: string;
accountId: string;
staticAccountId: string;
}
export interface SteamAuthTicket {
ticket: Buffer;
}
export interface DiscordActivityInfo {
largeImage: string;
smallImage: string;
details: string;
state: string;
}
}

View File

@ -0,0 +1,9 @@
import * as Electron from "@/steam/bindings/electron";
export function openExternalLink(url) {
if (Electron.isModuleLoaded()) {
Electron.openExternal(url);
} else {
window.open(url, "_blank").focus();
}
}