it is done
This commit is contained in:
319
desktop/src/mindustry/desktop/DesktopLauncher.java
Normal file
319
desktop/src/mindustry/desktop/DesktopLauncher.java
Normal file
@@ -0,0 +1,319 @@
|
||||
package mindustry.desktop;
|
||||
|
||||
import arc.*;
|
||||
import club.minnced.discord.rpc.*;
|
||||
import com.codedisaster.steamworks.*;
|
||||
import arc.Files.*;
|
||||
import io.anuke.arc.backends.sdl.*;
|
||||
import io.anuke.arc.backends.sdl.jni.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import arc.util.serialization.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.desktop.steam.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.net.Net.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class DesktopLauncher extends ClientLauncher{
|
||||
public final static String discordID = "610508934456934412";
|
||||
|
||||
boolean useDiscord = OS.is64Bit, loadError = false;
|
||||
Throwable steamError;
|
||||
|
||||
static{
|
||||
if(!Charset.forName("US-ASCII").newEncoder().canEncode(System.getProperty("user.name", ""))){
|
||||
System.setProperty("com.codedisaster.steamworks.SharedLibraryExtractPath", new File("").getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] arg){
|
||||
try{
|
||||
Vars.loadLogger();
|
||||
new SdlApplication(new DesktopLauncher(arg), new SdlConfig(){{
|
||||
title = "Mindustry";
|
||||
maximized = true;
|
||||
depth = 0;
|
||||
stencil = 0;
|
||||
width = 900;
|
||||
height = 700;
|
||||
setWindowIcon(FileType.internal, "icons/icon_64.png");
|
||||
}});
|
||||
}catch(Throwable e){
|
||||
handleCrash(e);
|
||||
}
|
||||
}
|
||||
|
||||
public DesktopLauncher(String[] args){
|
||||
Version.init();
|
||||
boolean useSteam = Version.modifier.contains("steam");
|
||||
testMobile = Array.with(args).contains("-testMobile");
|
||||
|
||||
if(useDiscord){
|
||||
try{
|
||||
DiscordEventHandlers handlers = new DiscordEventHandlers();
|
||||
DiscordRPC.INSTANCE.Discord_Initialize(discordID, handlers, true, "1127400");
|
||||
Log.info("Initialized Discord rich presence.");
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(DiscordRPC.INSTANCE::Discord_Shutdown));
|
||||
}catch(Throwable t){
|
||||
useDiscord = false;
|
||||
Log.err("Failed to initialize discord.", t);
|
||||
}
|
||||
}
|
||||
|
||||
if(useSteam){
|
||||
//delete leftover dlls
|
||||
Fi file = new Fi(".");
|
||||
for(Fi other : file.parent().list()){
|
||||
if(other.name().contains("steam") && (other.extension().equals("dll") || other.extension().equals("so") || other.extension().equals("dylib"))){
|
||||
other.delete();
|
||||
}
|
||||
}
|
||||
|
||||
Events.on(ClientLoadEvent.class, event -> {
|
||||
if(steamError != null){
|
||||
Core.app.post(() -> Core.app.post(() -> Core.app.post(() -> {
|
||||
ui.showErrorMessage(Core.bundle.format("steam.error", (steamError.getMessage() == null) ? steamError.getClass().getSimpleName() : steamError.getClass().getSimpleName() + ": " + steamError.getMessage()));
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
try{
|
||||
SteamAPI.loadLibraries();
|
||||
|
||||
if(!SteamAPI.init()){
|
||||
loadError = true;
|
||||
Log.err("Steam client not running.");
|
||||
}else{
|
||||
initSteam(args);
|
||||
Vars.steam = true;
|
||||
}
|
||||
|
||||
if(SteamAPI.restartAppIfNecessary(SVars.steamID)){
|
||||
System.exit(0);
|
||||
}
|
||||
}catch(NullPointerException ignored){
|
||||
steam = false;
|
||||
Log.info("Running in offline mode.");
|
||||
}catch(Throwable e){
|
||||
steam = false;
|
||||
Log.err("Failed to load Steam native libraries.");
|
||||
logSteamError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void logSteamError(Throwable e){
|
||||
steamError = e;
|
||||
loadError = true;
|
||||
Log.err(e);
|
||||
try(OutputStream s = new FileOutputStream(new File("steam-error-log-" + System.nanoTime() + ".txt"))){
|
||||
String log = Strings.parseException(e, true);
|
||||
s.write(log.getBytes());
|
||||
}catch(Exception e2){
|
||||
Log.err(e2);
|
||||
}
|
||||
}
|
||||
|
||||
void initSteam(String[] args){
|
||||
SVars.net = new SNet(new ArcNetProvider());
|
||||
SVars.stats = new SStats();
|
||||
SVars.workshop = new SWorkshop();
|
||||
SVars.user = new SUser();
|
||||
boolean[] isShutdown = {false};
|
||||
|
||||
Events.on(ClientLoadEvent.class, event -> {
|
||||
player.name = SVars.net.friends.getPersonaName();
|
||||
Core.settings.defaults("name", SVars.net.friends.getPersonaName());
|
||||
Core.settings.put("name", player.name);
|
||||
Core.settings.save();
|
||||
//update callbacks
|
||||
Core.app.addListener(new ApplicationListener(){
|
||||
@Override
|
||||
public void update(){
|
||||
if(SteamAPI.isSteamRunning()){
|
||||
SteamAPI.runCallbacks();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Core.app.post(() -> {
|
||||
if(args.length >= 2 && args[0].equals("+connect_lobby")){
|
||||
try{
|
||||
long id = Long.parseLong(args[1]);
|
||||
ui.join.connect("steam:" + id, port);
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to parse steam lobby ID: {0}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Events.on(DisposeEvent.class, event -> {
|
||||
SteamAPI.shutdown();
|
||||
isShutdown[0] = true;
|
||||
});
|
||||
|
||||
//steam shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
if(!isShutdown[0]){
|
||||
SteamAPI.shutdown();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
static void handleCrash(Throwable e){
|
||||
Cons<Runnable> dialog = Runnable::run;
|
||||
boolean badGPU = false;
|
||||
|
||||
if(e.getMessage() != null && (e.getMessage().contains("Couldn't create window") || e.getMessage().contains("OpenGL 2.0 or higher") || e.getMessage().toLowerCase().contains("pixel format"))){
|
||||
|
||||
dialog.get(() -> message(
|
||||
e.getMessage().contains("Couldn't create window") ? "A graphics initialization error has occured! Try to update your graphics drivers:\n" + e.getMessage() :
|
||||
"Your graphics card does not support OpenGL 2.0!\n" +
|
||||
"Try to update your graphics drivers.\n\n" +
|
||||
"(If that doesn't work, your computer just doesn't support Mindustry.)"));
|
||||
badGPU = true;
|
||||
}
|
||||
|
||||
boolean fbgp = badGPU;
|
||||
|
||||
CrashSender.send(e, file -> {
|
||||
Throwable fc = Strings.getFinalCause(e);
|
||||
if(!fbgp){
|
||||
dialog.get(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + fc.getClass().getSimpleName().replace("Exception", "") + (fc.getMessage() == null ? "" : ":\n" + fc.getMessage())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Array<Fi> getWorkshopContent(Class<? extends Publishable> type){
|
||||
return !steam ? super.getWorkshopContent(type) : SVars.workshop.getWorkshopFiles(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewListing(Publishable pub){
|
||||
SVars.workshop.viewListing(pub);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewListingID(String id){
|
||||
SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetProvider getNet(){
|
||||
return steam ? SVars.net : new ArcNetProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openWorkshop(){
|
||||
SVars.net.friends.activateGameOverlayToWebPage("https://steamcommunity.com/app/1127400/workshop/");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(Publishable pub){
|
||||
SVars.workshop.publish(pub);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inviteFriends(){
|
||||
SVars.net.showFriendInvites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLobby(){
|
||||
SVars.net.updateLobby();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRPC(){
|
||||
if(!useDiscord) return;
|
||||
|
||||
DiscordRichPresence presence = new DiscordRichPresence();
|
||||
|
||||
if(!state.is(State.menu)){
|
||||
String map = world.getMap() == null ? "Unknown Map" : world.isZone() ? world.getZone().localizedName : Strings.capitalize(world.getMap().name());
|
||||
String mode = state.rules.pvp ? "PvP" : state.rules.attackMode ? "Attack" : "Survival";
|
||||
String players = net.active() && playerGroup.size() > 1 ? " | " + playerGroup.size() + " Players" : "";
|
||||
|
||||
presence.state = mode + players;
|
||||
|
||||
if(!state.rules.waves){
|
||||
presence.details = map;
|
||||
}else{
|
||||
presence.details = map + " | Wave " + state.wave;
|
||||
presence.largeImageText = "Wave " + state.wave;
|
||||
}
|
||||
}else{
|
||||
if(ui.editor != null && ui.editor.isShown()){
|
||||
presence.state = "In Editor";
|
||||
}else if(ui.deploy != null && ui.deploy.isShown()){
|
||||
presence.state = "In Launch Selection";
|
||||
}else{
|
||||
presence.state = "In Menu";
|
||||
}
|
||||
}
|
||||
|
||||
presence.largeImageKey = "logo";
|
||||
|
||||
DiscordRPC.INSTANCE.Discord_UpdatePresence(presence);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUUID(){
|
||||
if(steam){
|
||||
try{
|
||||
byte[] result = new byte[8];
|
||||
new RandomXS128(SVars.user.user.getSteamID().getAccountID()).nextBytes(result);
|
||||
return new String(Base64Coder.encode(result));
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||
NetworkInterface out;
|
||||
for(out = e.nextElement(); (out.getHardwareAddress() == null || !validAddress(out.getHardwareAddress())) && e.hasMoreElements(); out = e.nextElement());
|
||||
|
||||
byte[] bytes = out.getHardwareAddress();
|
||||
byte[] result = new byte[8];
|
||||
System.arraycopy(bytes, 0, result, 0, bytes.length);
|
||||
|
||||
String str = new String(Base64Coder.encode(result));
|
||||
|
||||
if(str.equals("AAAAAAAAAOA=") || str.equals("AAAAAAAAAAA=")) throw new RuntimeException("Bad UUID.");
|
||||
|
||||
return str;
|
||||
}catch(Exception e){
|
||||
return super.getUUID();
|
||||
}
|
||||
}
|
||||
|
||||
private static void message(String message){
|
||||
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MESSAGEBOX_ERROR, "oh no", message);
|
||||
}
|
||||
|
||||
private boolean validAddress(byte[] bytes){
|
||||
if(bytes == null) return false;
|
||||
byte[] result = new byte[8];
|
||||
System.arraycopy(bytes, 0, result, 0, bytes.length);
|
||||
return !new String(Base64Coder.encode(result)).equals("AAAAAAAAAOA=") && !new String(Base64Coder.encode(result)).equals("AAAAAAAAAAA=");
|
||||
}
|
||||
}
|
||||
87
desktop/src/mindustry/desktop/steam/SAchievement.java
Normal file
87
desktop/src/mindustry/desktop/steam/SAchievement.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package mindustry.desktop.steam;
|
||||
|
||||
public enum SAchievement{
|
||||
completeTutorial,
|
||||
kill1kEnemies(SStat.unitsDestroyed, 1000),
|
||||
kill100kEnemies(SStat.unitsDestroyed, 1000 * 100),
|
||||
launch10kItems(SStat.itemsLaunched, 1000 * 10),
|
||||
launch1milItems(SStat.itemsLaunched, 1000 * 1000),
|
||||
win10Attack(SStat.attacksWon, 10),
|
||||
win10PvP(SStat.pvpsWon, 10),
|
||||
defeatAttack5Waves,
|
||||
launch30Times(SStat.timesLaunched, 30),
|
||||
survive100Waves(SStat.maxWavesSurvived, 100),
|
||||
survive500Waves(SStat.maxWavesSurvived, 500),
|
||||
researchAll,
|
||||
useAllMechs(SStat.zoneMechsUsed, 6),
|
||||
shockWetEnemy,
|
||||
killEnemyPhaseWall,
|
||||
researchRouter,
|
||||
place10kBlocks(SStat.blocksBuilt, 10 * 1000),
|
||||
destroy1kBlocks(SStat.blocksDestroyed, 1000),
|
||||
overheatReactor(SStat.reactorsOverheated, 1),
|
||||
make10maps(SStat.mapsMade, 10),
|
||||
downloadMapWorkshop,
|
||||
publishMap(SStat.mapsPublished, 1),
|
||||
defeatBoss(SStat.bossesDefeated, 1),
|
||||
unlockAllZones,
|
||||
configAllZones,
|
||||
drop10kitems,
|
||||
powerupImpactReactor,
|
||||
obtainThorium,
|
||||
obtainTitanium,
|
||||
suicideBomb,
|
||||
buildDaggerFactory,
|
||||
issueAttackCommand,
|
||||
active100Units(SStat.maxUnitActive, 100),
|
||||
active10Phantoms,
|
||||
active50Crawlers,
|
||||
build1000Units,
|
||||
earnSRank,
|
||||
earnSSRank,
|
||||
dieExclusion,
|
||||
drown,
|
||||
fillCoreAllCampaign,
|
||||
hostServer10(SStat.maxPlayersServer, 10),
|
||||
buildMeltdownSpectre,
|
||||
launchItemPad,
|
||||
skipLaunching2Death,
|
||||
chainRouters,
|
||||
survive10WavesNoBlocks,
|
||||
useFlameAmmo,
|
||||
coolTurret,
|
||||
enablePixelation,
|
||||
openWiki,
|
||||
;
|
||||
|
||||
private final SStat stat;
|
||||
private final int statGoal;
|
||||
public static final SAchievement[] all = values();
|
||||
|
||||
/** Creates an achievement that is triggered when this stat reaches a number.*/
|
||||
SAchievement(SStat stat, int goal){
|
||||
this.stat = stat;
|
||||
this.statGoal = goal;
|
||||
}
|
||||
|
||||
SAchievement(){
|
||||
this(null, 0);
|
||||
}
|
||||
|
||||
public void complete(){
|
||||
if(!isAchieved()){
|
||||
SVars.stats.stats.setAchievement(name());
|
||||
SVars.stats.stats.storeStats();
|
||||
}
|
||||
}
|
||||
|
||||
public void checkCompletion(){
|
||||
if(!isAchieved() && stat != null && stat.get() >= statGoal){
|
||||
complete();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAchieved(){
|
||||
return SVars.stats.stats.isAchieved(name(), false);
|
||||
}
|
||||
}
|
||||
478
desktop/src/mindustry/desktop/steam/SNet.java
Normal file
478
desktop/src/mindustry/desktop/steam/SNet.java
Normal file
@@ -0,0 +1,478 @@
|
||||
package mindustry.desktop.steam;
|
||||
|
||||
import arc.*;
|
||||
import com.codedisaster.steamworks.*;
|
||||
import com.codedisaster.steamworks.SteamFriends.*;
|
||||
import com.codedisaster.steamworks.SteamMatchmaking.*;
|
||||
import com.codedisaster.steamworks.SteamNetworking.*;
|
||||
import arc.struct.*;
|
||||
import arc.func.*;
|
||||
import arc.util.*;
|
||||
import arc.util.pooling.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.core.Version;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.net.ArcNetProvider.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.net.Net.*;
|
||||
import mindustry.net.Packets.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class SNet implements SteamNetworkingCallback, SteamMatchmakingCallback, SteamFriendsCallback, NetProvider{
|
||||
public final SteamNetworking snet = new SteamNetworking(this);
|
||||
public final SteamMatchmaking smat = new SteamMatchmaking(this);
|
||||
public final SteamFriends friends = new SteamFriends(this);
|
||||
|
||||
final NetProvider provider;
|
||||
|
||||
final PacketSerializer serializer = new PacketSerializer();
|
||||
final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(1024 * 4);
|
||||
final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024 * 4);
|
||||
|
||||
final CopyOnWriteArrayList<SteamConnection> connections = new CopyOnWriteArrayList<>();
|
||||
final IntMap<SteamConnection> steamConnections = new IntMap<>(); //maps steam ID -> valid net connection
|
||||
|
||||
SteamID currentLobby, currentServer;
|
||||
Cons<Host> lobbyCallback;
|
||||
Runnable lobbyDoneCallback, joinCallback;
|
||||
|
||||
public SNet(NetProvider provider){
|
||||
this.provider = provider;
|
||||
|
||||
Events.on(ClientLoadEvent.class, e -> Core.app.addListener(new ApplicationListener(){
|
||||
//read packets
|
||||
int length;
|
||||
SteamID from = new SteamID();
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
while((length = snet.isP2PPacketAvailable(0)) != 0){
|
||||
try{
|
||||
readBuffer.position(0);
|
||||
snet.readP2PPacket(from, readBuffer, 0);
|
||||
int fromID = from.getAccountID();
|
||||
Object output = serializer.read(readBuffer);
|
||||
|
||||
if(net.server()){
|
||||
SteamConnection con = steamConnections.get(fromID);
|
||||
try{
|
||||
//accept users on request
|
||||
if(con == null){
|
||||
con = new SteamConnection(SteamID.createFromNativeHandle(from.handle()));
|
||||
Connect c = new Connect();
|
||||
c.addressTCP = "steam:" + from.getAccountID();
|
||||
|
||||
Log.info("&bRecieved STEAM connection: {0}", c.addressTCP);
|
||||
|
||||
steamConnections.put(from.getAccountID(), con);
|
||||
connections.add(con);
|
||||
net.handleServerReceived(con, c);
|
||||
}
|
||||
|
||||
net.handleServerReceived(con, output);
|
||||
}catch(RuntimeException e){
|
||||
if(e.getCause() instanceof ValidateException){
|
||||
ValidateException v = (ValidateException)e.getCause();
|
||||
Log.err("Validation failed: {0} ({1})", v.player.name, v.getMessage());
|
||||
}else{
|
||||
Log.err(e);
|
||||
}
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
}
|
||||
}else if(currentServer != null && fromID == currentServer.getAccountID()){
|
||||
net.handleClientReceived(output);
|
||||
}
|
||||
}catch(SteamException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
Events.on(WaveEvent.class, e -> {
|
||||
if(currentLobby != null && net.server()){
|
||||
smat.setLobbyData(currentLobby, "wave", state.wave + "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isSteamClient(){
|
||||
return currentServer != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectClient(String ip, int port, Runnable success) throws IOException{
|
||||
if(ip.startsWith("steam:")){
|
||||
String lobbyname = ip.substring("steam:".length());
|
||||
try{
|
||||
SteamID lobby = SteamID.createFromNativeHandle(Long.parseLong(lobbyname));
|
||||
joinCallback = success;
|
||||
smat.joinLobby(lobby);
|
||||
}catch(NumberFormatException e){
|
||||
throw new IOException("Invalid Steam ID: " + lobbyname);
|
||||
}
|
||||
}else{
|
||||
provider.connectClient(ip, port, success);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendClient(Object object, SendMode mode){
|
||||
if(isSteamClient()){
|
||||
if(currentServer == null){
|
||||
Log.info("Not connected, quitting.");
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
writeBuffer.limit(writeBuffer.capacity());
|
||||
writeBuffer.position(0);
|
||||
serializer.write(writeBuffer, object);
|
||||
writeBuffer.flip();
|
||||
|
||||
snet.sendP2PPacket(currentServer, writeBuffer, mode == SendMode.tcp ? P2PSend.Reliable : P2PSend.UnreliableNoDelay, 0);
|
||||
}catch(Exception e){
|
||||
net.showError(e);
|
||||
}
|
||||
Pools.free(object);
|
||||
}else{
|
||||
provider.sendClient(object, mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void disconnectClient(){
|
||||
if(isSteamClient()){
|
||||
if(currentLobby != null){
|
||||
smat.leaveLobby(currentLobby);
|
||||
snet.closeP2PSessionWithUser(currentServer);
|
||||
currentServer = null;
|
||||
currentLobby = null;
|
||||
net.handleClientReceived(new Disconnect());
|
||||
}
|
||||
}else{
|
||||
provider.disconnectClient();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discoverServers(Cons<Host> callback, Runnable done){
|
||||
smat.addRequestLobbyListResultCountFilter(32);
|
||||
smat.requestLobbyList();
|
||||
lobbyCallback = callback;
|
||||
lobbyDoneCallback = done;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pingHost(String address, int port, Cons<Host> valid, Cons<Exception> failed){
|
||||
provider.pingHost(address, port, valid, failed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hostServer(int port) throws IOException{
|
||||
provider.hostServer(port);
|
||||
smat.createLobby(Core.settings.getBool("publichost") ? LobbyType.Public : LobbyType.FriendsOnly, 16);
|
||||
|
||||
Core.app.post(() -> Core.app.post(() -> Core.app.post(() -> Log.info("Server: {0}\nClient: {1}\nActive: {2}", net.server(), net.client(), net.active()))));
|
||||
}
|
||||
|
||||
public void updateLobby(){
|
||||
if(currentLobby != null && net.server()){
|
||||
smat.setLobbyType(currentLobby, Core.settings.getBool("publichost") ? LobbyType.Public : LobbyType.FriendsOnly);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeServer(){
|
||||
provider.closeServer();
|
||||
|
||||
if(currentLobby != null){
|
||||
smat.leaveLobby(currentLobby);
|
||||
for(SteamConnection con : steamConnections.values()){
|
||||
con.close();
|
||||
}
|
||||
currentLobby = null;
|
||||
}
|
||||
|
||||
steamConnections.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends NetConnection> getConnections(){
|
||||
//merge provider connections
|
||||
CopyOnWriteArrayList<NetConnection> connectionsOut = new CopyOnWriteArrayList<>(connections);
|
||||
for(NetConnection c : provider.getConnections()) connectionsOut.add(c);
|
||||
return connectionsOut;
|
||||
}
|
||||
|
||||
void disconnectSteamUser(SteamID steamid){
|
||||
//a client left
|
||||
int sid = steamid.getAccountID();
|
||||
snet.closeP2PSessionWithUser(steamid);
|
||||
|
||||
if(steamConnections.containsKey(sid)){
|
||||
SteamConnection con = steamConnections.get(sid);
|
||||
net.handleServerReceived(con, new Disconnect());
|
||||
steamConnections.remove(sid);
|
||||
connections.remove(con);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFavoritesListChanged(int i, int i1, int i2, int i3, int i4, boolean b, int i5){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyInvite(SteamID steamIDUser, SteamID steamIDLobby, long gameID){
|
||||
Log.info("onLobbyInvite {0} {1} {2}", steamIDLobby.getAccountID(), steamIDUser.getAccountID(), gameID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyEnter(SteamID steamIDLobby, int chatPermissions, boolean blocked, ChatRoomEnterResponse response){
|
||||
Log.info("enter lobby {0} {1}", steamIDLobby.getAccountID(), response);
|
||||
|
||||
if(response != ChatRoomEnterResponse.Success){
|
||||
ui.loadfrag.hide();
|
||||
ui.showErrorMessage(Core.bundle.format("cantconnect", response.toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
if(net.active()){
|
||||
net.disconnect();
|
||||
net.closeServer();
|
||||
logic.reset();
|
||||
state.set(State.menu);
|
||||
}
|
||||
|
||||
currentLobby = steamIDLobby;
|
||||
currentServer = smat.getLobbyOwner(steamIDLobby);
|
||||
|
||||
Log.info("Connect to owner {0}: {1}", currentServer.getAccountID(), friends.getFriendPersonaName(currentServer));
|
||||
|
||||
if(joinCallback != null){
|
||||
joinCallback.run();
|
||||
joinCallback = null;
|
||||
}
|
||||
|
||||
Connect con = new Connect();
|
||||
con.addressTCP = "steam:" + currentServer.getAccountID();
|
||||
|
||||
net.setClientConnected();
|
||||
net.handleClientReceived(con);
|
||||
|
||||
Core.app.post(() -> Core.app.post(() -> Core.app.post(() -> Log.info("Server: {0}\nClient: {1}\nActive: {2}", net.server(), net.client(), net.active()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyDataUpdate(SteamID steamID, SteamID steamID1, boolean b){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyChatUpdate(SteamID lobby, SteamID who, SteamID changer, ChatMemberStateChange change){
|
||||
Log.info("lobby {0}: {1} caused {2}'s change: {3}", lobby.getAccountID(), who.getAccountID(), changer.getAccountID(), change);
|
||||
if(change == ChatMemberStateChange.Disconnected || change == ChatMemberStateChange.Left){
|
||||
if(net.client()){
|
||||
//host left, leave as well
|
||||
if(who.equals(currentServer) || who.equals(currentLobby)){
|
||||
net.disconnect();
|
||||
Log.info("Current host left.");
|
||||
}
|
||||
}else{
|
||||
//a client left
|
||||
disconnectSteamUser(who);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyChatMessage(SteamID steamID, SteamID steamID1, ChatEntryType chatEntryType, int i){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyGameCreated(SteamID steamID, SteamID steamID1, int i, short i1){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyMatchList(int matches){
|
||||
Log.info("found {0} matches {1}", matches, lobbyDoneCallback);
|
||||
|
||||
if(lobbyDoneCallback != null){
|
||||
Array<Host> hosts = new Array<>();
|
||||
for(int i = 0; i < matches; i++){
|
||||
try{
|
||||
SteamID lobby = smat.getLobbyByIndex(i);
|
||||
Host out = new Host(
|
||||
smat.getLobbyData(lobby, "name"),
|
||||
"steam:" + lobby.handle(),
|
||||
smat.getLobbyData(lobby, "mapname"),
|
||||
Strings.parseInt(smat.getLobbyData(lobby, "wave"), -1),
|
||||
smat.getNumLobbyMembers(lobby),
|
||||
Strings.parseInt(smat.getLobbyData(lobby, "version"), -1),
|
||||
smat.getLobbyData(lobby, "versionType"),
|
||||
Gamemode.valueOf(smat.getLobbyData(lobby, "gamemode")),
|
||||
smat.getLobbyMemberLimit(lobby)
|
||||
);
|
||||
hosts.add(out);
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
}
|
||||
}
|
||||
|
||||
hosts.sort(Structs.comparingInt(h -> -h.players));
|
||||
hosts.each(lobbyCallback);
|
||||
|
||||
lobbyDoneCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyKicked(SteamID steamID, SteamID steamID1, boolean b){
|
||||
Log.info("Kicked: {0} {1} {2}", steamID, steamID1, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLobbyCreated(SteamResult result, SteamID steamID){
|
||||
if(!net.server()){
|
||||
Log.info("Lobby created on server: {0}, ignoring.", steamID);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.info("Lobby {1} created? {0}", result, steamID.getAccountID());
|
||||
if(result == SteamResult.OK){
|
||||
currentLobby = steamID;
|
||||
|
||||
smat.setLobbyData(steamID, "name", player.name);
|
||||
smat.setLobbyData(steamID, "mapname", world.getMap() == null ? "Unknown" : state.rules.zone == null ? world.getMap().name() : state.rules.zone.localizedName);
|
||||
smat.setLobbyData(steamID, "version", Version.build + "");
|
||||
smat.setLobbyData(steamID, "versionType", Version.type);
|
||||
smat.setLobbyData(steamID, "wave", state.wave + "");
|
||||
smat.setLobbyData(steamID, "gamemode", Gamemode.bestFit(state.rules).name() + "");
|
||||
}
|
||||
}
|
||||
|
||||
public void showFriendInvites(){
|
||||
if(currentLobby != null){
|
||||
friends.activateGameOverlayInviteDialog(currentLobby);
|
||||
Log.info("Activating overlay dialog");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFavoritesListAccountsUpdated(SteamResult steamResult){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onP2PSessionConnectFail(SteamID steamIDRemote, P2PSessionError sessionError){
|
||||
if(net.server()){
|
||||
Log.info("{0} has disconnected: {1}", steamIDRemote.getAccountID(), sessionError);
|
||||
disconnectSteamUser(steamIDRemote);
|
||||
}else if(steamIDRemote.equals(currentServer)){
|
||||
Log.info("Disconnected! {1}: {0}", steamIDRemote.getAccountID(), sessionError);
|
||||
net.handleClientReceived(new Disconnect());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onP2PSessionRequest(SteamID steamIDRemote){
|
||||
Log.info("Connection request: {0}", steamIDRemote.getAccountID());
|
||||
if(net.server()){
|
||||
Log.info("Am server, accepting request from " + steamIDRemote.getAccountID());
|
||||
snet.acceptP2PSessionWithUser(steamIDRemote);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetPersonaNameResponse(boolean b, boolean b1, SteamResult steamResult){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPersonaStateChange(SteamID steamID, PersonaChange personaChange){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGameOverlayActivated(boolean b){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGameLobbyJoinRequested(SteamID lobby, SteamID steamIDFriend){
|
||||
Log.info("onGameLobbyJoinRequested {0} {1}", lobby, steamIDFriend);
|
||||
smat.joinLobby(lobby);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvatarImageLoaded(SteamID steamID, int i, int i1, int i2){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFriendRichPresenceUpdate(SteamID steamID, int i){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGameRichPresenceJoinRequested(SteamID steamID, String connect){
|
||||
Log.info("onGameRichPresenceJoinRequested {0} {1}", steamID, connect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGameServerChangeRequested(String server, String password){
|
||||
|
||||
}
|
||||
|
||||
public class SteamConnection extends NetConnection{
|
||||
final SteamID sid;
|
||||
final P2PSessionState state = new P2PSessionState();
|
||||
|
||||
public SteamConnection(SteamID sid){
|
||||
super(sid.getAccountID() + "");
|
||||
this.sid = sid;
|
||||
Log.info("Create STEAM client {0}", sid.getAccountID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Object object, SendMode mode){
|
||||
try{
|
||||
writeBuffer.limit(writeBuffer.capacity());
|
||||
writeBuffer.position(0);
|
||||
serializer.write(writeBuffer, object);
|
||||
writeBuffer.flip();
|
||||
|
||||
snet.sendP2PPacket(sid, writeBuffer, mode == SendMode.tcp ? object instanceof StreamChunk ? P2PSend.ReliableWithBuffering : P2PSend.Reliable : P2PSend.UnreliableNoDelay, 0);
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
Log.info("Error sending packet. Disconnecting invalid client!");
|
||||
close();
|
||||
|
||||
SteamConnection k = steamConnections.get(sid.getAccountID());
|
||||
if(k != null) steamConnections.remove(sid.getAccountID());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected(){
|
||||
snet.getP2PSessionState(sid, state);
|
||||
return true;//state.isConnectionActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(){
|
||||
disconnectSteamUser(sid);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
desktop/src/mindustry/desktop/steam/SStat.java
Normal file
44
desktop/src/mindustry/desktop/steam/SStat.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package mindustry.desktop.steam;
|
||||
|
||||
public enum SStat{
|
||||
unitsDestroyed,
|
||||
attacksWon,
|
||||
pvpsWon,
|
||||
timesLaunched,
|
||||
zoneMechsUsed,
|
||||
blocksDestroyed,
|
||||
itemsLaunched,
|
||||
reactorsOverheated,
|
||||
maxUnitActive,
|
||||
unitsBuilt,
|
||||
bossesDefeated,
|
||||
maxPlayersServer,
|
||||
mapsMade,
|
||||
mapsPublished,
|
||||
maxWavesSurvived,
|
||||
blocksBuilt,
|
||||
;
|
||||
|
||||
public int get(){
|
||||
return SVars.stats.stats.getStatI(name(), 0);
|
||||
}
|
||||
|
||||
public void max(int amount){
|
||||
if(amount > get()){
|
||||
add(amount - get());
|
||||
}
|
||||
}
|
||||
|
||||
public void add(int amount){
|
||||
SVars.stats.stats.setStatI(name(), get() + amount);
|
||||
SVars.stats.onUpdate();
|
||||
|
||||
for(SAchievement a : SAchievement.all){
|
||||
a.checkCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
public void add(){
|
||||
add(1);
|
||||
}
|
||||
}
|
||||
329
desktop/src/mindustry/desktop/steam/SStats.java
Normal file
329
desktop/src/mindustry/desktop/steam/SStats.java
Normal file
@@ -0,0 +1,329 @@
|
||||
package mindustry.desktop.steam;
|
||||
|
||||
import arc.*;
|
||||
import com.codedisaster.steamworks.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.type.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.Stats.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.desktop.steam.SAchievement.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SStats implements SteamUserStatsCallback{
|
||||
public final SteamUserStats stats = new SteamUserStats(this);
|
||||
|
||||
private boolean updated = false;
|
||||
private ObjectSet<String> mechs = new ObjectSet<>();
|
||||
private int statSavePeriod = 4; //in minutes
|
||||
|
||||
public SStats(){
|
||||
stats.requestCurrentStats();
|
||||
|
||||
Events.on(ClientLoadEvent.class, e -> {
|
||||
mechs = Core.settings.getObject("mechs", ObjectSet.class, ObjectSet::new);
|
||||
|
||||
Core.app.addListener(new ApplicationListener(){
|
||||
Interval i = new Interval();
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(i.get(60f / 4f)){
|
||||
checkUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Timer.schedule(() -> {
|
||||
if(updated){
|
||||
stats.storeStats();
|
||||
}
|
||||
}, statSavePeriod * 60, statSavePeriod * 60);
|
||||
});
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
this.updated = true;
|
||||
}
|
||||
|
||||
private void checkUpdate(){
|
||||
if(campaign()){
|
||||
SStat.maxUnitActive.max(unitGroups[player.getTeam().ordinal()].size());
|
||||
|
||||
if(unitGroups[player.getTeam().ordinal()].count(u -> u.getType() == UnitTypes.phantom) >= 10){
|
||||
active10Phantoms.complete();
|
||||
}
|
||||
|
||||
if(unitGroups[player.getTeam().ordinal()].count(u -> u.getType() == UnitTypes.crawler) >= 50){
|
||||
active50Crawlers.complete();
|
||||
}
|
||||
|
||||
for(Tile tile : state.teams.get(player.getTeam()).cores){
|
||||
if(!content.items().contains(i -> i.type == ItemType.material && tile.entity.items.get(i) < tile.block().itemCapacity)){
|
||||
fillCoreAllCampaign.complete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerEvents(){
|
||||
Events.on(UnitDestroyEvent.class, e -> {
|
||||
if(ncustom()){
|
||||
if(e.unit.getTeam() != Vars.player.getTeam()){
|
||||
SStat.unitsDestroyed.add();
|
||||
|
||||
if(e.unit instanceof BaseUnit && ((BaseUnit)e.unit).isBoss()){
|
||||
SStat.bossesDefeated.add();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(ZoneConfigureCompleteEvent.class, e -> {
|
||||
if(!content.zones().contains(z -> !z.canConfigure())){
|
||||
configAllZones.complete();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(Trigger.newGame, () -> Core.app.post(() -> {
|
||||
if(campaign() && player.getClosestCore() != null && player.getClosestCore().items.total() >= 10 * 1000){
|
||||
drop10kitems.complete();
|
||||
}
|
||||
}));
|
||||
|
||||
Events.on(CommandIssueEvent.class, e -> {
|
||||
if(campaign() && e.command == UnitCommand.attack){
|
||||
issueAttackCommand.complete();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(BlockBuildEndEvent.class, e -> {
|
||||
if(campaign() && e.player == player && !e.breaking){
|
||||
SStat.blocksBuilt.add();
|
||||
|
||||
if(e.tile.block() == Blocks.router && e.tile.entity.proximity().contains(t -> t.block() == Blocks.router)){
|
||||
chainRouters.complete();
|
||||
}
|
||||
|
||||
if(e.tile.block() == Blocks.daggerFactory){
|
||||
buildDaggerFactory.complete();
|
||||
}
|
||||
|
||||
if(e.tile.block() == Blocks.meltdown || e.tile.block() == Blocks.spectre){
|
||||
if(e.tile.block() == Blocks.meltdown && !Core.settings.getBool("meltdownp", false)){
|
||||
Core.settings.putSave("meltdownp", true);
|
||||
}
|
||||
|
||||
if(e.tile.block() == Blocks.spectre && !Core.settings.getBool("spectrep", false)){
|
||||
Core.settings.putSave("spectrep", true);
|
||||
}
|
||||
|
||||
if(Core.settings.getBool("meltdownp", false) && Core.settings.getBool("spectrep", false)){
|
||||
buildMeltdownSpectre.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(BlockDestroyEvent.class, e -> {
|
||||
if(campaign() && e.tile.getTeam() != player.getTeam()){
|
||||
SStat.blocksDestroyed.add();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(MapMakeEvent.class, e -> SStat.mapsMade.add());
|
||||
|
||||
Events.on(MapPublishEvent.class, e -> SStat.mapsPublished.add());
|
||||
|
||||
Events.on(UnlockEvent.class, e -> {
|
||||
if(e.content == Items.thorium) obtainThorium.complete();
|
||||
if(e.content == Items.titanium) obtainTitanium.complete();
|
||||
|
||||
if(!content.zones().contains(Zone::locked)){
|
||||
unlockAllZones.complete();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(Trigger.openWiki, openWiki::complete);
|
||||
|
||||
Events.on(Trigger.exclusionDeath, dieExclusion::complete);
|
||||
|
||||
Events.on(Trigger.drown, drown::complete);
|
||||
|
||||
trigger(Trigger.impactPower, powerupImpactReactor);
|
||||
|
||||
trigger(Trigger.flameAmmo, useFlameAmmo);
|
||||
|
||||
trigger(Trigger.turretCool, coolTurret);
|
||||
|
||||
trigger(Trigger.suicideBomb, suicideBomb);
|
||||
|
||||
Events.on(Trigger.enablePixelation, enablePixelation::complete);
|
||||
|
||||
Events.on(Trigger.thoriumReactorOverheat, () -> {
|
||||
if(campaign()){
|
||||
SStat.reactorsOverheated.add();
|
||||
}
|
||||
});
|
||||
|
||||
trigger(Trigger.shock, shockWetEnemy);
|
||||
|
||||
trigger(Trigger.phaseDeflectHit, killEnemyPhaseWall);
|
||||
|
||||
trigger(Trigger.itemLaunch, launchItemPad);
|
||||
|
||||
Events.on(UnitCreateEvent.class, e -> {
|
||||
if(campaign() && e.unit.getTeam() == player.getTeam()){
|
||||
SStat.unitsBuilt.add();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(LoseEvent.class, e -> {
|
||||
if(campaign()){
|
||||
if(world.getZone().metCondition() && (state.wave - world.getZone().conditionWave) / world.getZone().launchPeriod >= 1){
|
||||
skipLaunching2Death.complete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(LaunchEvent.class, e -> {
|
||||
if(state.rules.tutorial){
|
||||
completeTutorial.complete();
|
||||
}
|
||||
|
||||
SStat.timesLaunched.add();
|
||||
});
|
||||
|
||||
Events.on(LaunchItemEvent.class, e -> {
|
||||
SStat.itemsLaunched.add(e.stack.amount);
|
||||
});
|
||||
|
||||
Events.on(WaveEvent.class, e -> {
|
||||
if(ncustom()){
|
||||
SStat.maxWavesSurvived.max(Vars.state.wave);
|
||||
|
||||
if(state.stats.buildingsBuilt == 0 && state.wave >= 10){
|
||||
survive10WavesNoBlocks.complete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(PlayerJoin.class, e -> {
|
||||
if(Vars.net.server()){
|
||||
SStat.maxPlayersServer.max(Vars.playerGroup.size());
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(ResearchEvent.class, e -> {
|
||||
if(e.content == Blocks.router) researchRouter.complete();
|
||||
|
||||
if(!TechTree.all.contains(t -> t.block.locked())){
|
||||
researchAll.complete();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(WinEvent.class, e -> {
|
||||
if(campaign()){
|
||||
if(Vars.state.wave <= 5 && state.rules.attackMode){
|
||||
defeatAttack5Waves.complete();
|
||||
}
|
||||
|
||||
if(Vars.state.rules.attackMode){
|
||||
SStat.attacksWon.add();
|
||||
}
|
||||
|
||||
RankResult result = state.stats.calculateRank(world.getZone(), state.launched);
|
||||
if(result.rank == Rank.S) earnSRank.complete();
|
||||
if(result.rank == Rank.SS) earnSSRank.complete();
|
||||
}
|
||||
|
||||
if(state.rules.pvp){
|
||||
SStat.pvpsWon.add();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(MechChangeEvent.class, e -> {
|
||||
if(campaign()){
|
||||
if(mechs.add(e.mech.name)){
|
||||
SStat.zoneMechsUsed.max(mechs.size);
|
||||
Core.settings.putObject("mechs", mechs);
|
||||
Core.settings.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void trigger(Trigger trigger, SAchievement ach){
|
||||
Events.on(trigger, () -> {
|
||||
if(campaign()){
|
||||
ach.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean ncustom(){
|
||||
return campaign();
|
||||
}
|
||||
|
||||
private boolean campaign(){
|
||||
return Vars.world.isZone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserStatsReceived(long gameID, SteamID steamID, SteamResult result){
|
||||
registerEvents();
|
||||
|
||||
if(result != SteamResult.OK){
|
||||
Log.err("Failed to recieve steam stats: {0}", result);
|
||||
}else{
|
||||
Log.err("Recieved steam stats.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserStatsStored(long gameID, SteamResult result){
|
||||
Log.info("Stored stats: {0}", result);
|
||||
|
||||
if(result == SteamResult.OK){
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserStatsUnloaded(SteamID steamID){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserAchievementStored(long l, boolean b, String s, int i, int i1){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeaderboardFindResult(SteamLeaderboardHandle steamLeaderboardHandle, boolean b){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeaderboardScoresDownloaded(SteamLeaderboardHandle steamLeaderboardHandle, SteamLeaderboardEntriesHandle steamLeaderboardEntriesHandle, int i){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeaderboardScoreUploaded(boolean b, SteamLeaderboardHandle steamLeaderboardHandle, int i, boolean b1, int i1, int i2){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGlobalStatsReceived(long l, SteamResult steamResult){
|
||||
|
||||
}
|
||||
}
|
||||
23
desktop/src/mindustry/desktop/steam/SUser.java
Normal file
23
desktop/src/mindustry/desktop/steam/SUser.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package mindustry.desktop.steam;
|
||||
|
||||
import com.codedisaster.steamworks.*;
|
||||
import com.codedisaster.steamworks.SteamAuth.*;
|
||||
|
||||
public class SUser implements SteamUserCallback{
|
||||
public final SteamUser user = new SteamUser(this);
|
||||
|
||||
@Override
|
||||
public void onValidateAuthTicket(SteamID steamID, AuthSessionResponse authSessionResponse, SteamID ownerSteamID){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMicroTxnAuthorization(int appID, long orderID, boolean authorized){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptedAppTicket(SteamResult result){
|
||||
|
||||
}
|
||||
}
|
||||
10
desktop/src/mindustry/desktop/steam/SVars.java
Normal file
10
desktop/src/mindustry/desktop/steam/SVars.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package mindustry.desktop.steam;
|
||||
|
||||
public class SVars{
|
||||
public final static int steamID = 1127400;
|
||||
|
||||
public static SNet net;
|
||||
public static SStats stats;
|
||||
public static SWorkshop workshop;
|
||||
public static SUser user;
|
||||
}
|
||||
356
desktop/src/mindustry/desktop/steam/SWorkshop.java
Normal file
356
desktop/src/mindustry/desktop/steam/SWorkshop.java
Normal file
@@ -0,0 +1,356 @@
|
||||
package mindustry.desktop.steam;
|
||||
|
||||
import arc.*;
|
||||
import com.codedisaster.steamworks.*;
|
||||
import com.codedisaster.steamworks.SteamRemoteStorage.*;
|
||||
import com.codedisaster.steamworks.SteamUGC.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.util.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.mod.Mods.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class SWorkshop implements SteamUGCCallback{
|
||||
public final SteamUGC ugc = new SteamUGC(this);
|
||||
|
||||
private ObjectMap<Class<? extends Publishable>, Array<Fi>> workshopFiles = new ObjectMap<>();
|
||||
private ObjectMap<SteamUGCQuery, Cons2<Array<SteamUGCDetails>, SteamResult>> detailHandlers = new ObjectMap<>();
|
||||
private Array<Cons<SteamPublishedFileID>> itemHandlers = new Array<>();
|
||||
private ObjectMap<SteamPublishedFileID, Runnable> updatedHandlers = new ObjectMap<>();
|
||||
|
||||
public SWorkshop(){
|
||||
int items = ugc.getNumSubscribedItems();
|
||||
SteamPublishedFileID[] ids = new SteamPublishedFileID[items];
|
||||
ItemInstallInfo info = new ItemInstallInfo();
|
||||
ugc.getSubscribedItems(ids);
|
||||
|
||||
Array<Fi> folders = Array.with(ids).map(f -> {
|
||||
ugc.getItemInstallInfo(f, info);
|
||||
return new Fi(info.getFolder());
|
||||
}).select(f -> f != null && f.list().length > 0);
|
||||
|
||||
workshopFiles.put(Map.class, folders.select(f -> f.list().length == 1 && f.list()[0].extension().equals(mapExtension)).map(f -> f.list()[0]));
|
||||
workshopFiles.put(Schematic.class, folders.select(f -> f.list().length == 1 && f.list()[0].extension().equals(schematicExtension)).map(f -> f.list()[0]));
|
||||
workshopFiles.put(LoadedMod.class, folders.select(f -> f.child("mod.json").exists() || f.child("mod.hjson").exists()));
|
||||
|
||||
if(!workshopFiles.get(Map.class).isEmpty()){
|
||||
SAchievement.downloadMapWorkshop.complete();
|
||||
}
|
||||
|
||||
workshopFiles.each((type, list) -> {
|
||||
Log.info("Fetched content ({0}): {1}", type.getSimpleName(), list.size);
|
||||
});
|
||||
}
|
||||
|
||||
public Array<Fi> getWorkshopFiles(Class<? extends Publishable> type){
|
||||
return workshopFiles.getOr(type, () -> new Array<>(0));
|
||||
}
|
||||
|
||||
/** Publish a new item and submit an update for it.
|
||||
* If it is already published, redirects to its page.*/
|
||||
public void publish(Publishable p){
|
||||
Log.info("publish(): " + p.steamTitle());
|
||||
if(p.hasSteamID()){
|
||||
Log.info("Content already published, redirecting to ID.");
|
||||
viewListing(p);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!p.prePublish()){
|
||||
Log.info("Rejecting due to pre-publish.");
|
||||
return;
|
||||
}
|
||||
|
||||
showPublish(id -> update(p, id, null));
|
||||
}
|
||||
|
||||
/** Update an existing item with a changelog. */
|
||||
public void updateItem(Publishable p, String changelog){
|
||||
String id = p.getSteamID();
|
||||
long handle = Strings.parseLong(id, -1);
|
||||
update(p, new SteamPublishedFileID(handle), changelog);
|
||||
}
|
||||
|
||||
/** Fetches info for an item, checking to make sure that it exists.*/
|
||||
public void viewListing(Publishable p){
|
||||
long handle = Strings.parseLong(p.getSteamID(), -1);
|
||||
SteamPublishedFileID id = new SteamPublishedFileID(handle);
|
||||
Log.info("Handle = " + handle);
|
||||
|
||||
ui.loadfrag.show();
|
||||
query(ugc.createQueryUGCDetailsRequest(id), (detailsList, result) -> {
|
||||
ui.loadfrag.hide();
|
||||
Log.info("Fetch result = " + result);
|
||||
|
||||
if(result == SteamResult.OK){
|
||||
SteamUGCDetails details = detailsList.first();
|
||||
Log.info("Details result = " + details.getResult());
|
||||
if(details.getResult() == SteamResult.OK){
|
||||
if(details.getOwnerID().equals(SVars.user.user.getSteamID())){
|
||||
|
||||
FloatingDialog dialog = new FloatingDialog("$workshop.info");
|
||||
dialog.setFillParent(false);
|
||||
dialog.cont.add("$workshop.menu").pad(20f);
|
||||
dialog.addCloseButton();
|
||||
|
||||
dialog.buttons.addImageTextButton("$view.workshop", Icon.linkSmall, () -> {
|
||||
viewListingID(id);
|
||||
dialog.hide();
|
||||
}).size(210f, 64f);
|
||||
|
||||
dialog.buttons.addImageTextButton("$workshop.update", Icon.upgradeSmall, () -> {
|
||||
new FloatingDialog("$workshop.update"){{
|
||||
setFillParent(false);
|
||||
cont.margin(10).add("$changelog").padRight(6f);
|
||||
cont.row();
|
||||
TextArea field = cont.addArea("", t -> {}).size(500f, 160f).get();
|
||||
field.setMaxLength(400);
|
||||
buttons.defaults().size(120, 54).pad(4);
|
||||
buttons.addButton("$ok", () -> {
|
||||
if(!p.prePublish()){
|
||||
Log.info("Rejecting due to pre-publish.");
|
||||
return;
|
||||
}
|
||||
|
||||
ui.loadfrag.show("$publishing");
|
||||
updateItem(p, field.getText().replace("\r", "\n"));
|
||||
dialog.hide();
|
||||
hide();
|
||||
});
|
||||
buttons.addButton("$cancel", this::hide);
|
||||
}}.show();
|
||||
|
||||
}).size(210f, 64f);
|
||||
dialog.show();
|
||||
}else{
|
||||
SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + details.getPublishedFileID().handle());
|
||||
}
|
||||
}else if(details.getResult() == SteamResult.FileNotFound){
|
||||
p.removeSteamID();
|
||||
ui.showErrorMessage("$missing");
|
||||
}else{
|
||||
ui.showErrorMessage(Core.bundle.format("workshop.error", details.getResult().name()));
|
||||
}
|
||||
}else{
|
||||
ui.showErrorMessage(Core.bundle.format("workshop.error", result.name()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void viewListingID(SteamPublishedFileID id){
|
||||
SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + id.handle());
|
||||
}
|
||||
|
||||
void update(Publishable p, SteamPublishedFileID id, String changelog){
|
||||
Log.info("Calling update({0}) {1}", p.steamTitle(), id.handle());
|
||||
String sid = id.handle() + "";
|
||||
|
||||
updateItem(id, h -> {
|
||||
if(p.steamDescription() != null){
|
||||
ugc.setItemDescription(h, p.steamDescription());
|
||||
}
|
||||
|
||||
Array<String> tags = p.extraTags();
|
||||
tags.add(p.steamTag());
|
||||
|
||||
ugc.setItemTitle(h, p.steamTitle());
|
||||
ugc.setItemTags(h, tags.toArray(String.class));
|
||||
ugc.setItemPreview(h, p.createSteamPreview(sid).absolutePath());
|
||||
ugc.setItemContent(h, p.createSteamFolder(sid).absolutePath());
|
||||
if(changelog == null){
|
||||
ugc.setItemVisibility(h, PublishedFileVisibility.Private);
|
||||
}
|
||||
ugc.submitItemUpdate(h, changelog == null ? "<Created>" : changelog);
|
||||
|
||||
if(p instanceof Map){
|
||||
SAchievement.publishMap.complete();
|
||||
}
|
||||
}, () -> p.addSteamID(sid));
|
||||
}
|
||||
|
||||
void showPublish(Cons<SteamPublishedFileID> published){
|
||||
FloatingDialog dialog = new FloatingDialog("$confirm");
|
||||
dialog.setFillParent(false);
|
||||
dialog.cont.add("$publish.confirm").width(600f).wrap();
|
||||
dialog.addCloseButton();
|
||||
dialog.buttons.addImageTextButton("$eula", Icon.linkSmall,
|
||||
() -> SVars.net.friends.activateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement"))
|
||||
.size(210f, 64f);
|
||||
|
||||
dialog.buttons.addImageTextButton("$ok", Icon.checkSmall, () -> {
|
||||
Log.info("Accepted, publishing item...");
|
||||
itemHandlers.add(published);
|
||||
ugc.createItem(SVars.steamID, WorkshopFileType.Community);
|
||||
ui.loadfrag.show("$publishing");
|
||||
dialog.hide();
|
||||
}).size(170f, 64f);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
void query(SteamUGCQuery query, Cons2<Array<SteamUGCDetails>, SteamResult> handler){
|
||||
Log.info("POST QUERY " + query);
|
||||
detailHandlers.put(query, handler);
|
||||
ugc.sendQueryUGCRequest(query);
|
||||
}
|
||||
|
||||
void updateItem(SteamPublishedFileID publishedFileID, Cons<SteamUGCUpdateHandle> tagger, Runnable updated){
|
||||
try{
|
||||
SteamUGCUpdateHandle h = ugc.startItemUpdate(SVars.steamID, publishedFileID);
|
||||
Log.info("begin updateItem({0})", publishedFileID.handle());
|
||||
|
||||
tagger.get(h);
|
||||
Log.info("Tagged.");
|
||||
|
||||
ItemUpdateInfo info = new ItemUpdateInfo();
|
||||
|
||||
ui.loadfrag.setProgress(() -> {
|
||||
ItemUpdateStatus status = ugc.getItemUpdateProgress(h, info);
|
||||
ui.loadfrag.setText("$" + status.name().toLowerCase());
|
||||
if(status == ItemUpdateStatus.Invalid){
|
||||
ui.loadfrag.setText("$done");
|
||||
return 1f;
|
||||
}
|
||||
return (float)status.ordinal() / (float)ItemUpdateStatus.values().length;
|
||||
});
|
||||
|
||||
updatedHandlers.put(publishedFileID, updated);
|
||||
}catch(Throwable t){
|
||||
ui.loadfrag.hide();
|
||||
Log.err(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestUGCDetails(SteamUGCDetails details, SteamResult result){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUGCQueryCompleted(SteamUGCQuery query, int numResultsReturned, int totalMatchingResults, boolean isCachedData, SteamResult result){
|
||||
Log.info("GET QUERY " + query);
|
||||
|
||||
if(detailHandlers.containsKey(query)){
|
||||
Log.info("Query being handled...");
|
||||
if(numResultsReturned > 0){
|
||||
Log.info("{0} q results", numResultsReturned);
|
||||
Array<SteamUGCDetails> details = new Array<>();
|
||||
for(int i = 0; i < numResultsReturned; i++){
|
||||
details.add(new SteamUGCDetails());
|
||||
ugc.getQueryUGCResult(query, i, details.get(i));
|
||||
}
|
||||
detailHandlers.get(query).get(details, result);
|
||||
}else{
|
||||
Log.info("Nothing found.");
|
||||
detailHandlers.get(query).get(new Array<>(), SteamResult.FileNotFound);
|
||||
}
|
||||
|
||||
detailHandlers.remove(query);
|
||||
}else{
|
||||
Log.info("Query not handled.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribeItem(SteamPublishedFileID publishedFileID, SteamResult result){
|
||||
ItemInstallInfo info = new ItemInstallInfo();
|
||||
ugc.getItemInstallInfo(publishedFileID, info);
|
||||
Log.info("Item subscribed from {0}", info.getFolder());
|
||||
SAchievement.downloadMapWorkshop.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribeItem(SteamPublishedFileID publishedFileID, SteamResult result){
|
||||
ItemInstallInfo info = new ItemInstallInfo();
|
||||
ugc.getItemInstallInfo(publishedFileID, info);
|
||||
Log.info("Item unsubscribed from {0}", info.getFolder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateItem(SteamPublishedFileID publishedFileID, boolean needsToAcceptWLA, SteamResult result){
|
||||
Log.info("onCreateItem(" + result + ")");
|
||||
if(!itemHandlers.isEmpty()){
|
||||
if(result == SteamResult.OK){
|
||||
Log.info("Passing to first handler.");
|
||||
itemHandlers.first().get(publishedFileID);
|
||||
}else{
|
||||
ui.showErrorMessage(Core.bundle.format("publish.error", result.name()));
|
||||
}
|
||||
|
||||
itemHandlers.remove(0);
|
||||
}else{
|
||||
Log.err("No handlers for createItem()");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubmitItemUpdate(SteamPublishedFileID publishedFileID, boolean needsToAcceptWLA, SteamResult result){
|
||||
ui.loadfrag.hide();
|
||||
Log.info("onsubmititemupdate {0} {1} {2}", publishedFileID.handle(), needsToAcceptWLA, result);
|
||||
if(result == SteamResult.OK){
|
||||
//redirect user to page for further updates
|
||||
SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + publishedFileID.handle());
|
||||
if(needsToAcceptWLA){
|
||||
SVars.net.friends.activateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement");
|
||||
}
|
||||
|
||||
if(updatedHandlers.containsKey(publishedFileID)){
|
||||
updatedHandlers.get(publishedFileID).run();
|
||||
}
|
||||
}else{
|
||||
ui.showErrorMessage(Core.bundle.format("publish.error", result.name()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadItemResult(int appID, SteamPublishedFileID publishedFileID, SteamResult result){
|
||||
SAchievement.downloadMapWorkshop.complete();
|
||||
ItemInstallInfo info = new ItemInstallInfo();
|
||||
ugc.getItemInstallInfo(publishedFileID, info);
|
||||
Log.info("Item downloaded to {0}", info.getFolder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserFavoriteItemsListChanged(SteamPublishedFileID publishedFileID, boolean wasAddRequest, SteamResult result){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetUserItemVote(SteamPublishedFileID publishedFileID, boolean voteUp, SteamResult result){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetUserItemVote(SteamPublishedFileID publishedFileID, boolean votedUp, boolean votedDown, boolean voteSkipped, SteamResult result){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartPlaytimeTracking(SteamResult result){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopPlaytimeTracking(SteamResult result){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopPlaytimeTrackingForAllItems(SteamResult result){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteItem(SteamPublishedFileID publishedFileID, SteamResult result){
|
||||
ItemInstallInfo info = new ItemInstallInfo();
|
||||
ugc.getItemInstallInfo(publishedFileID, info);
|
||||
Log.info("Item removed from {0}", info.getFolder());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user