From 7e693cb9d0cf91d816f1d2d964dfcde3aec931d2 Mon Sep 17 00:00:00 2001 From: aardappel Date: Wed, 26 Jul 2017 18:48:37 -0700 Subject: [PATCH] Minimal initial steamworks integration. --- dev/lobster/lobster.vcxproj | 9 +- dev/lobster/lobster.vcxproj.filters | 6 + dev/lobster/main.vcxproj | 16 +- dev/src/glsystem.cpp | 6 +- dev/src/graphics.cpp | 3 + dev/src/platform.h | 1 + dev/src/sdlsystem.cpp | 3 +- dev/src/steamworks.cpp | 200 ++++++++++++++++++ lobster/docs/builtin_functions_reference.html | 9 + lobster/docs/implementation.html | 1 + 10 files changed, 238 insertions(+), 16 deletions(-) create mode 100644 dev/src/steamworks.cpp diff --git a/dev/lobster/lobster.vcxproj b/dev/lobster/lobster.vcxproj index 15c9ebe40..8d568551f 100644 --- a/dev/lobster/lobster.vcxproj +++ b/dev/lobster/lobster.vcxproj @@ -102,7 +102,7 @@ Disabled WIN32;_DEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) Disabled - ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\SDL\include;$(DXSDK_DIR)\Include + ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\steamworks\include;..\external\SDL\include;$(DXSDK_DIR)\Include StreamingSIMDExtensions2 false 4127;4512;4201;4146;4456;4702 @@ -132,7 +132,7 @@ Disabled WIN32;_DEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) Disabled - ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\SDL\include;$(DXSDK_DIR)\Include + ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\steamworks\include;..\external\SDL\include;$(DXSDK_DIR)\Include StreamingSIMDExtensions2 true false @@ -161,7 +161,7 @@ true true WIN32;NDEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) - ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\SDL\include;$(DXSDK_DIR)\Include + ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\steamworks\include;..\external\SDL\include;$(DXSDK_DIR)\Include ProgramDatabase MultiThreaded StreamingSIMDExtensions2 @@ -194,7 +194,7 @@ true true WIN32;NDEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) - ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\SDL\include;$(DXSDK_DIR)\Include + ..\include;..\external\freetype\include;..\external\openvr\headers;..\external\steamworks\include;..\external\SDL\include;$(DXSDK_DIR)\Include ProgramDatabase MultiThreaded StreamingSIMDExtensions2 @@ -1047,6 +1047,7 @@ Create Level4 + diff --git a/dev/lobster/lobster.vcxproj.filters b/dev/lobster/lobster.vcxproj.filters index ef87bf3c8..0df0f5d38 100644 --- a/dev/lobster/lobster.vcxproj.filters +++ b/dev/lobster/lobster.vcxproj.filters @@ -44,6 +44,9 @@ {5b6e2421-9663-4999-a2ce-f1589fbbd7ec} + + {d702a622-4917-41d5-a8c2-6f609fe925a0} + @@ -1011,6 +1014,9 @@ engine\meshgen + + engine\steam + diff --git a/dev/lobster/main.vcxproj b/dev/lobster/main.vcxproj index cc7df1b79..d768501b6 100644 --- a/dev/lobster/main.vcxproj +++ b/dev/lobster/main.vcxproj @@ -113,8 +113,8 @@ Console true - openvr_api.lib;../../build/lobster/language/languageDBG.lib;../../build/lobster/engine/engineDBG.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) - ..\lib;$(DXSDK_DIR)\Lib\x86;..\external\openvr\lib\win32 + ..\external\openvr\lib\win32\openvr_api.lib;..\external\steamworks\lib\win32\steam_api.lib;../../build/lobster/language/languageDBG.lib;../../build/lobster/engine/engineDBG.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + ..\lib;$(DXSDK_DIR)\Lib\x86 @@ -141,8 +141,8 @@ Console true - openvr_api.lib;../../build/lobster/language/languageDBG64.lib;../../build/lobster/engine/engineDBG64.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) - ..\lib;$(DXSDK_DIR)\Lib\x86;..\external\openvr\lib\win64 + ..\external\openvr\lib\win64\openvr_api.lib;..\external\steamworks\lib\win64\steam_api.lib;../../build/lobster/language/languageDBG64.lib;../../build/lobster/engine/engineDBG64.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + ..\lib;$(DXSDK_DIR)\Lib\x86 @@ -172,8 +172,8 @@ true true true - ..\lib;$(DXSDK_DIR)\Lib\x86;..\external\openvr\lib\win32 - openvr_api.lib;../../build/lobster/language/language.lib;../../build/lobster/engine/engine.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + ..\lib;$(DXSDK_DIR)\Lib\x86 + ..\external\openvr\lib\win32\openvr_api.lib;..\external\steamworks\lib\win32\steam_api.lib;../../build/lobster/language/language.lib;../../build/lobster/engine/engine.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) $(TargetPath) $(IntDir)$(TargetName).pdb false @@ -205,8 +205,8 @@ true true true - ..\lib;$(DXSDK_DIR)\Lib\x86;..\external\openvr\lib\win64 - openvr_api.lib;../../build/lobster/language/language64.lib;../../build/lobster/engine/engine64.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + ..\lib;$(DXSDK_DIR)\Lib\x86 + ..\external\openvr\lib\win64\openvr_api.lib;..\external\steamworks\lib\win64\steam_api.lib;../../build/lobster/language/language64.lib;../../build/lobster/engine/engine64.lib;opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) $(TargetPath) $(IntDir)$(TargetName).pdb false diff --git a/dev/src/glsystem.cpp b/dev/src/glsystem.cpp index ae0f0b2f0..f000a6c02 100644 --- a/dev/src/glsystem.cpp +++ b/dev/src/glsystem.cpp @@ -106,10 +106,10 @@ void Set3DMode(float fovy, float ratio, float znear, float zfar) { bool Is2DMode() { return mode2d; } uchar *ReadPixels(const int2 &pos, const int2 &size) { - uchar *pixels = new uchar[size.x() * size.y() * 4]; + uchar *pixels = new uchar[size.x() * size.y() * 3]; for (int y = 0; y < size.y(); y++) - GL_CALL(glReadPixels(pos.x(), pos.y() + size.y() - y - 1, size.x(), 1, GL_RGBA, GL_UNSIGNED_BYTE, - pixels + y * (size.x() * 4))); + GL_CALL(glReadPixels(pos.x(), pos.y() + size.y() - y - 1, size.x(), 1, GL_RGB, GL_UNSIGNED_BYTE, + pixels + y * (size.x() * 3))); return pixels; } diff --git a/dev/src/graphics.cpp b/dev/src/graphics.cpp index 67caf4f67..f5dec61a9 100644 --- a/dev/src/graphics.cpp +++ b/dev/src/graphics.cpp @@ -40,7 +40,9 @@ uint GetTexture(Value &res) { } // Should be safe to call even if it wasn't initialized partially or at all. +// FIXME: move this elsewhere. void GraphicsShutDown() { + extern void SteamShutDown(); SteamShutDown(); VRShutDown(); extern void CleanPhysics(); CleanPhysics(); extern void MeshGenClear(); MeshGenClear(); @@ -62,6 +64,7 @@ void GraphicsShutDown() { bool GraphicsFrameStart() { extern void CullFonts(); CullFonts(); + extern void SteamUpdate(); SteamUpdate(); bool cb = SDLFrame(); lastframehitsize = lasthitsize; lasthitsize = float3_0; diff --git a/dev/src/platform.h b/dev/src/platform.h index 1e87f2f08..833fc93df 100644 --- a/dev/src/platform.h +++ b/dev/src/platform.h @@ -80,4 +80,5 @@ extern string GetDateTime(); #if defined(_WIN32) // FIXME: Also make work on Linux/OS X. #define PLATFORM_VR + #define PLATFORM_STEAMWORKS #endif diff --git a/dev/src/sdlsystem.cpp b/dev/src/sdlsystem.cpp index 05eed6152..4befa78de 100644 --- a/dev/src/sdlsystem.cpp +++ b/dev/src/sdlsystem.cpp @@ -630,7 +630,7 @@ int64_t SDLLoadFile(const char *absfilename, string *dest, int64_t start, int64_ bool ScreenShot(const char *filename) { auto pixels = ReadPixels(int2(0), screensize); - auto ok = stbi_write_png(filename, screensize.x(), screensize.y(), 4, pixels, screensize.x() * 4); + auto ok = stbi_write_png(filename, screensize.x(), screensize.y(), 3, pixels, screensize.x() * 3); delete[] pixels; return ok != 0; } @@ -659,6 +659,7 @@ void RegisterCoreEngineBuiltins() { extern void AddMeshGen(); lobster::RegisterBuiltin("meshgen", AddMeshGen); extern void AddCubeGen(); lobster::RegisterBuiltin("cubegen", AddCubeGen); extern void AddVR(); lobster::RegisterBuiltin("vr", AddVR); + extern void AddSteam(); lobster::RegisterBuiltin("steam", AddSteam); } void EngineExit(int code) { diff --git a/dev/src/steamworks.cpp b/dev/src/steamworks.cpp new file mode 100644 index 000000000..4d2ec34ea --- /dev/null +++ b/dev/src/steamworks.cpp @@ -0,0 +1,200 @@ +// Copyright 2017 Wouter van Oortmerssen. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "stdafx.h" + +#include "vmdata.h" +#include "natreg.h" + +#include "glinterface.h" +#include "sdlinterface.h" + +#ifdef PLATFORM_STEAMWORKS + +#include "steam/steam_api.h" + +struct SteamState { + bool steamoverlayactive = false; + STEAM_CALLBACK(SteamState, OnGameOverlayActivated, GameOverlayActivated_t); + STEAM_CALLBACK(SteamState, OnScreenshotRequested, ScreenshotRequested_t); +}; + +void SteamState::OnGameOverlayActivated(GameOverlayActivated_t *callback) { + steamoverlayactive = callback->m_bActive; + Output(OUTPUT_INFO, "steam overlay toggle: %d", steamoverlayactive); +} + +void SteamState::OnScreenshotRequested(ScreenshotRequested_t *) { + Output(OUTPUT_INFO, "steam screenshot requested"); + auto size = GetScreenSize(); + auto pixels = ReadPixels(int2(0), size); + SteamScreenshots()->WriteScreenshot(pixels, size.x() * size.y() * 3, size.x(), size.y()); + delete[] pixels; +} + +extern "C" void __cdecl SteamAPIDebugTextHook(int severity, const char *debugtext) { + Output(severity ? OUTPUT_WARN : OUTPUT_INFO, "%s", debugtext); +} + +SteamState *steam = nullptr; + +#endif // PLATFORM_STEAMWORKS + +void SteamShutDown() { + #ifdef PLATFORM_STEAMWORKS + if (steam) { + delete steam; + SteamAPI_Shutdown(); + } + steam = nullptr; + #endif // PLATFORM_STEAMWORKS +} + +int SteamInit(uint appid, bool screenshots) { + SteamShutDown(); + #ifdef PLATFORM_STEAMWORKS + #ifdef _DEBUG + (void)appid; + #else + if (appid && SteamAPI_RestartAppIfNecessary(appid)) { + Output(OUTPUT_INFO, "Not started from Steam"); + return -1; + } + #endif + bool steaminit = SteamAPI_Init(); + Output(OUTPUT_INFO, "Steam init: %d", steaminit); + if (!steaminit) return 0; + SteamUtils()->SetWarningMessageHook(&SteamAPIDebugTextHook); + steam = new SteamState(); + SteamUserStats()->RequestCurrentStats(); + if (screenshots) SteamScreenshots()->HookScreenshots(true); + return 1; + #else + return 0; + #endif // PLATFORM_STEAMWORKS +} + +void SteamUpdate() { + #ifdef PLATFORM_STEAMWORKS + SteamAPI_RunCallbacks(); + #endif // PLATFORM_STEAMWORKS +} + +const char *UserName() { + #ifdef PLATFORM_STEAMWORKS + if (steam) return SteamFriends()->GetPersonaName(); + #endif // PLATFORM_STEAMWORKS + return ""; +} + +bool UnlockAchievement(const char *name) { + #ifdef PLATFORM_STEAMWORKS + if (steam) { + auto ok = SteamUserStats()->SetAchievement(name); + return SteamUserStats()->StoreStats() && ok; // Force this to run. + } + #else + (void)name; + #endif // PLATFORM_STEAMWORKS + return false; +} + +int SteamReadFile(const char *fn, string &buf) { + #ifdef PLATFORM_STEAMWORKS + if (steam) { + auto len = SteamRemoteStorage()->GetFileSize(fn); + if (len) { + buf.resize(len); + return SteamRemoteStorage()->FileRead(fn, (void *)buf.data(), len); + } + } + #endif // PLATFORM_STEAMWORKS + return 0; +} + +bool SteamWriteFile(const char *fn, const void *buf, int len) { + #ifdef PLATFORM_STEAMWORKS + if (steam) { + return SteamRemoteStorage()->FileWrite(fn, buf, len); + } + #endif // PLATFORM_STEAMWORKS + return false; +} + +using namespace lobster; + +void AddSteam() { + STARTDECL(steam_init) (Value &appid, Value &ss) { + return Value(SteamInit((uint)appid.ival(), ss.True())); + } + ENDDECL2(steam_init, "appid,allowscreenshots", "II", "I", + "initializes SteamWorks. returns 1 if succesful, 0 on failure. Specify a non-0 appid if you" + " want to restart from steam if this wasn't started from steam (the return value in this" + " case will be -1 to indicate you should terminate this instance). If you don't specify an" + " appid here or in steam_appid.txt, init will likely fail. The other functions can still be" + " called even if steam isn't active." + " allowscreenshots automatically uploads screenshots to steam (triggered by steam)."); + + STARTDECL(steam_overlay) () { + return Value(steam && steam->steamoverlayactive); + } + ENDDECL0(steam_overlay, "", "", "I", + "returns true if the steam overlay is currently on (you may want to auto-pause if so)"); + + STARTDECL(steam_username) () { + return Value(g_vm->NewString(UserName())); + } + ENDDECL0(steam_username, "", "", "S", + "returns the name of the steam user, or empty string if not available."); + + STARTDECL(steam_unlock_achievement) (Value &name) { + auto ok = UnlockAchievement(name.sval()->str()); + name.DECRT(); + return Value(ok); + } + ENDDECL1(steam_unlock_achievement, "achievementname", "S", "I", + "Unlocks an achievement and shows the achievement overlay if not already achieved before." + " Will also Q-up saving achievement to Steam." + " Returns true if succesful."); + + STARTDECL(steam_write_file) (Value &file, Value &contents) { + auto fn = file.sval()->str(); + auto s = contents.sval(); + auto ok = SteamWriteFile(fn, s->str(), s->len); + if (!ok) { + ok = WriteFile(fn, true, s->str(), s->len); + } + file.DECRT(); + contents.DECRT(); + return Value(ok); + } + ENDDECL2(steam_write_file, "file,contents", "SS", "I", + "writes a file with the contents of a string to the steam cloud, or local storage if that" + " fails, returns false if writing wasn't possible at all"); + + STARTDECL(steam_read_file) (Value &file) { + auto fn = file.sval()->str(); + string buf; + auto len = SteamReadFile(fn, buf); + if (!len) len = (int)LoadFile(fn, &buf); + file.DECRT(); + if (len < 0) return Value(); + auto s = g_vm->NewString(buf); + return Value(s); + } + ENDDECL1(steam_read_file, "file", "S", "S", + "returns the contents of a file as a string from the steam cloud if available, or otherwise" + " from local storage, or nil if the file can't be found at all."); +} + diff --git a/lobster/docs/builtin_functions_reference.html b/lobster/docs/builtin_functions_reference.html index 0799bab4f..4a742c9b6 100644 --- a/lobster/docs/builtin_functions_reference.html +++ b/lobster/docs/builtin_functions_reference.html @@ -303,5 +303,14 @@

vr

vr_motioncontrollervec(n:int, i:int) -> [float]returns one of the vectors for motion controller n. 0 = left, 1 = up, 2 = fwd, 4 = pos. These are in Y up space. vr_hmdvec(i:int) -> [float]returns one of the vectors for hmd pose. 0 = left, 1 = up, 2 = fwd, 4 = pos. These are in Y up space. +

steam

+ + + + + + + +
steam_init(appid:int, allowscreenshots:int) -> intinitializes SteamWorks. returns 1 if succesful, 0 on failure. Specify a non-0 appid if you want to restart from steam if this wasn't started from steam (the return value in this case will be -1 to indicate you should terminate this instance). If you don't specify an appid here or in steam_appid.txt, init will likely fail. The other functions can still be called even if steam isn't active. allowscreenshots automatically uploads screenshots to steam (triggered by steam).
steam_overlay() -> intreturns true if the steam overlay is currently on (you may want to auto-pause if so)
steam_username() -> stringreturns the name of the steam user, or empty string if not available.
steam_unlock_achievement(achievementname:string) -> intUnlocks an achievement and shows the achievement overlay if not already achieved before. Will also Q-up saving achievement to Steam. Returns true if succesful.
steam_write_file(file:string, contents:string) -> intwrites a file with the contents of a string to the steam cloud, or local storage if that fails, returns false if writing wasn't possible at all
steam_read_file(file:string) -> stringreturns the contents of a file as a string from the steam cloud if available, or otherwise from local storage, or nil if the file can't be found at all.
diff --git a/lobster/docs/implementation.html b/lobster/docs/implementation.html index 6b165647d..cf7de4208 100644 --- a/lobster/docs/implementation.html +++ b/lobster/docs/implementation.html @@ -91,6 +91,7 @@

Distributing Lobster programs.

  • Any other files/directories you have specified with pakfile, e.g: gl_loadtexture(pakfile ”mypath/myfile.png”). pakfile can prefix filenames or directories (ending in /), in which case all files in the directory will (non-recursively) be added. When running with --verbose you can see what files are added/loaded from a pakfile, and which are loaded individually.

  • Any files your code references that are not in the pakfile (e.g. gl_loadtexture(”mypath/myfile.png”) ).

  • +
  • On Windows, you’ll need to include openvr_api.dll (and possibly steam_api.dll) next to lobster.exe even if you don’t use any VR functionality, this will hopefully be fixed in the future.

  • Where you place these files depends on the platform, on Windows / Linux it is next to the lobster executable, on OS X / iOS it is the application bundle under Contents, on Android it’s under assets in the .apk, and with emscripten there’s an assets directory also.

    Compiling Lobster code to C++