Depthris [Release]

Not yet, but I plan to in the future. I need to get myself one first :slight_smile:

I can give it a try if you want.

I have a Deck. Let me know if you need help with testing.

@xuan @iamcreasy thanks for your offer. I am considering it after this release. I have two jobs besides this so I am slow in the progress…

well back to some stuff…

I did not know how nice the steam overlay UI can appear in the game.

I just have an AppState than handles everything Steam related and so far it works great

6 Likes

Depthis has now been released on Steam :slight_smile:

I managed to add leaderboards support. No DRM:

  • If Steam is online… you have access to online leaderboards.
  • If Steam is offline (not opened): you have local leaderboards (type your name)

I wanted to add Steam Input support but Steamworks4j does not support yet, it so I am forking it to add that in the future

15 Likes

Nice job. congratulations on the shipped project milestone!

1 Like

Well done. This is a fantastic milestone.

2 Likes

Currently working on releasing it on the Epic Games Store as well.

you can see it here

I found a library that warps their SDK for Java (eos4j) but I am waiting for leaderboards support and hopefully by the end of the month it will be released there. I will be documenting any differences in implementing this compared to Steamworks4j

9 Likes

Great work!

I’m impressed you managed to work out how to use steamworks4j leader boards! I’ve been trying and failing with my arcade shooter game and getting nowhere. If you have a moment could you give us some code snippets to show how it’s done? Pretty please :slight_smile: I can connect to steam but it’s the leader board stuff that catches me out. I understand if not.

3 Likes

@Mike_Robinson
Sure, I will make a simplified version of what I did for the leaderboards. I hope it helps. Add or fix what fits your game better :slight_smile:

NOTE: you will need either a callback listener or a poll to pass the scores to your game. If you choose the callback, I recommend calling it on the App State’s update method

package org.mypackage.scores;

import com.codedisaster.steamworks.*;
import com.jme3.app.Application;

public class SteamScoresManager extends extends BaseAppState {
    private static final float TIME_BETWEEN_CALLBACKS = 0.083f; // about 12 calls per second

    private SteamUtils utils;
    private SteamUserStats userStats;
    private SteamFriends friends;
    private SteamLeaderboardHandle currentLeaderboard = null;
    private boolean saveFirst;
    private float currentTime = 0;
    private int scoreToSave = -1;

    @Override
    protected void initialize(Application app) {
        try{
            SteamAPI.loadLibraries();

            if(!SteamAPI.init()){
                return;
            }

            userStats = new SteamUserStats(userStatsCallback);
            friends = new SteamFriends(friendsCallback);
            utils = new SteamUtils(utilsCallback);
        }catch(SteamException ex){
            //Handle Error here
        }
    }

    @Override
    protected void cleanup(Application app) {
        if(null != userStats) {
            userStats.dispose();
            userStats = null;
        }

        if(null != friends) {
            friends.dispose();
            friends = null;
        }

        if(null != utils) {
            utils.dispose();
            utils = null;
        }

        currentLeaderboard = null;
    }

    @Override
    public void update(float tpf) {
        // You need to tell Steamworks to run the callbacks or you will get nothing
        if(SteamAPI.isSteamRunning()){
            currentTime += tpf;

            if(currentTime >= TIME_BETWEEN_CALLBACKS ){
                currentTime = 0;
                SteamAPI.runCallbacks();
            }
        }
    }


    public void loadScores(String leaderboardName) {
        userStats.findOrCreateLeaderboard(leaderboardName,
                SteamUserStats.LeaderboardSortMethod.Descending, SteamUserStats.LeaderboardDisplayType.Numeric);
    }

    public void saveScores(String leaderboardName, int score) {
        // This is when you want to upload the score before before getting the leaderboard
        if(null != currentLeaderboard){
            userStats.uploadLeaderboardScore(currentLeaderboard, SteamUserStats.LeaderboardUploadScoreMethod.KeepBest,
                    score, null);
        }else{
            saveFirst = true;
            scoreToSave = score;
            userStats.findOrCreateLeaderboard(leaderboardName,
                    SteamUserStats.LeaderboardSortMethod.Descending, SteamUserStats.LeaderboardDisplayType.Numeric);
        }
    }

    private final SteamUserStatsCallback userStatsCallback = new SteamUserStatsCallback() {
        @Override
        public void onUserStatsReceived(long gameId, SteamID steamIDUser, SteamResult result) {}

        @Override
        public void onUserStatsStored(long gameId, SteamResult result) {}

        @Override
        public void onUserStatsUnloaded(SteamID steamIDUser) {}

        @Override
        public void onUserAchievementStored(long gameId, boolean isGroupAchievement,
                                            String achievementName, int curProgress, int maxProgress) {}

        @Override
        public void onNumberOfCurrentPlayersReceived(boolean success, int players) {}

        @Override
        public void onGlobalStatsReceived(long gameId, SteamResult result) {}

        @Override
        public void onLeaderboardFindResult(SteamLeaderboardHandle leaderboard, boolean found) {
            if(!found){
                //<Code here when no leaderboard is found>
                return;
            }

            currentLeaderboard = leaderboard;

            if(saveFirst){
                saveFirst = false;
                userStats.uploadLeaderboardScore(currentLeaderboard, SteamUserStats.LeaderboardUploadScoreMethod.KeepBest,
                        scoreToSave, new int[]{});
                scoreToSave = -1;
            }else{
                performDownloadEntries(leaderboard);
            }
        }

        @Override
        public void onLeaderboardScoresDownloaded(SteamLeaderboardHandle leaderboard, SteamLeaderboardEntriesHandle entries, int numEntries) {
            if(numEntries < 0){
                //<Code here when no leaderboard entries are found>
                return;
            }

            int[] details = new int[16];

            for (int i = 0; i < numEntries; i++){
                SteamLeaderboardEntry entry = new SteamLeaderboardEntry();

                if(userStats.getDownloadedLeaderboardEntry(entries, i, entry, details)){
                    // Parse the scores here
                }
            }
        }

        @Override
        public void onLeaderboardScoreUploaded(boolean success, SteamLeaderboardHandle leaderboard, int score,
                                               boolean scoreChanged, int globalRankNew, int globalRankPrevious) {

            // You can check for success or not if you want
            performDownloadEntries(leaderboard);
        }

        private void performDownloadEntries(SteamLeaderboardHandle leaderboard){
            SteamUserStats.LeaderboardDataRequest request = SteamUserStats.LeaderboardDataRequest.GlobalAroundUser;
            int count = userStats.getLeaderboardEntryCount(leaderboard);
            userStats.downloadLeaderboardEntries(leaderboard, request, 0, count);
        }
    };

    private final SteamFriendsCallback friendsCallback = new SteamFriendsCallback() {
        @Override
        public void onSetPersonaNameResponse(boolean success, boolean localSuccess, SteamResult result) {}

        @Override
        public void onPersonaStateChange(SteamID steamID, SteamFriends.PersonaChange change) {}

        @Override
        public void onGameOverlayActivated(boolean active) {
            // Pay Close attention... 
            // You MUST pause the game when the overlay appears ot your app will be rejected
        }

        @Override
        public void onGameLobbyJoinRequested(SteamID steamIDLobby, SteamID steamIDFriend) {}

        @Override
        public void onAvatarImageLoaded(SteamID steamID, int image, int width, int height) {}

        @Override
        public void onFriendRichPresenceUpdate(SteamID steamIDFriend, int appID) {}

        @Override
        public void onGameRichPresenceJoinRequested(SteamID steamIDFriend, String connect) {}

        @Override
        public void onGameServerChangeRequested(String server, String password) {}
    };

    private final SteamUtilsCallback utilsCallback = new SteamUtilsCallback() {
        @Override
        public void onSteamShutdown() {
            // Check when steam is down
        }
    };
}
4 Likes

Thank you for that, that’s incredibly helpful!

It’s also a little more complex than I was expecting. Perhaps in my naivety I was thinking you’d just have a call to connect to your leader board then another simple call to write the players score to it. Maybe even a call to read the leader board for those scores nearest to the player.

Are all the imports necessary? ie blocks, arrays etc.

Not really… I cleaned up the code from my game project but I may have forgotten to remove some imports (the java.util ones). and my package name :slight_smile: let me edit that.

As for the complexity. Yes it is a bit complex but not as hard as you think. Epic’s Online Services on the other hand is a pain. I am still wrapping my head around it

3 Likes

Sorry for reviving this thread. Just letting everyone know that a free demo version is up on Steam :slight_smile:

Also, controls are customizable on keyboard and gamepads :stuck_out_tongue:

4 Likes