├── .gitignore ├── src ├── test │ ├── resources │ │ └── com │ │ │ └── github │ │ │ └── koraktor │ │ │ └── steamcondenser │ │ │ ├── community │ │ │ ├── invalid.xml │ │ │ └── koraktor-friends.json │ │ │ └── servers │ │ │ ├── status_goldsrc │ │ │ └── status_source │ └── java │ │ └── com │ │ └── github │ │ └── koraktor │ │ └── steamcondenser │ │ ├── exceptions │ │ └── WebApiExceptionTest.java │ │ ├── community │ │ ├── portal2 │ │ │ └── Portal2StatsTest.java │ │ ├── GameStatsTestCase.java │ │ └── l4d │ │ │ └── L4D2StatsTest.java │ │ └── servers │ │ ├── ServerTest.java │ │ ├── sockets │ │ ├── MasterServerSocketTest.java │ │ ├── QuerySocketTest.java │ │ ├── RCONSocketTest.java │ │ ├── SteamSocketTest.java │ │ └── SourceSocketTest.java │ │ └── GoldSrcServerTest.java └── main │ └── java │ └── com │ └── github │ └── koraktor │ └── steamcondenser │ ├── exceptions │ ├── ConnectionResetException.java │ ├── PacketFormatException.java │ ├── RCONNoAuthException.java │ ├── RCONBanException.java │ └── SteamCondenserException.java │ ├── servers │ ├── packets │ │ ├── A2S_INFO_Packet.java │ │ ├── A2S_SERVERQUERY_GETCHALLENGE_Packet.java │ │ ├── rcon │ │ │ ├── RCONAuthRequestPacket.java │ │ │ ├── RCONExecRequestPacket.java │ │ │ ├── RCONAuthResponse.java │ │ │ ├── RCONTerminator.java │ │ │ ├── RCONGoldSrcResponsePacket.java │ │ │ ├── RCONExecResponsePacket.java │ │ │ ├── RCONGoldSrcRequestPacket.java │ │ │ ├── RCONPacketFactory.java │ │ │ └── RCONPacket.java │ │ ├── S2A_INFO_BasePacket.java │ │ ├── A2S_RULES_Packet.java │ │ ├── S2C_CHALLENGE_Packet.java │ │ ├── A2S_PLAYER_Packet.java │ │ ├── S2A_RULES_Packet.java │ │ ├── S2A_PLAYER_Packet.java │ │ ├── M2A_SERVER_BATCH_Packet.java │ │ ├── SteamPacket.java │ │ ├── S2A_INFO_DETAILED_Packet.java │ │ ├── A2M_GET_SERVERS_BATCH2_Packet.java │ │ └── S2A_INFO2_Packet.java │ └── sockets │ │ ├── MasterServerSocket.java │ │ ├── QuerySocket.java │ │ ├── SourceSocket.java │ │ └── RCONSocket.java │ ├── community │ ├── l4d │ │ ├── L4DWeapon.java │ │ ├── L4DExplosive.java │ │ ├── L4D2Weapon.java │ │ ├── AbtractL4DWeapon.java │ │ ├── L4DMap.java │ │ ├── L4DStats.java │ │ └── L4D2Map.java │ ├── GameClass.java │ ├── tf2 │ │ ├── TF2Sniper.java │ │ ├── TF2Medic.java │ │ ├── TF2ClassFactory.java │ │ ├── TF2Spy.java │ │ ├── TF2Engineer.java │ │ ├── TF2Item.java │ │ ├── TF2GoldenWrench.java │ │ ├── TF2Stats.java │ │ ├── TF2Class.java │ │ ├── TF2Inventory.java │ │ └── TF2BetaInventory.java │ ├── dota2 │ │ ├── Dota2Item.java │ │ ├── Dota2Inventory.java │ │ └── Dota2BetaInventory.java │ ├── GameWeapon.java │ ├── portal2 │ │ ├── Portal2Stats.java │ │ ├── Portal2Item.java │ │ └── Portal2Inventory.java │ ├── GameLeaderboardEntry.java │ ├── alien_swarm │ │ └── AlienSwarmWeapon.java │ ├── dods │ │ ├── DoDSWeapon.java │ │ ├── DoDSStats.java │ │ └── DoDSClass.java │ └── css │ │ ├── CSSMap.java │ │ └── CSSWeapon.java │ ├── Helper.java │ └── PacketBuffer.java ├── .travis.yml ├── LICENSE ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | target 3 | -------------------------------------------------------------------------------- /src/test/resources/com/github/koraktor/steamcondenser/community/invalid.xml: -------------------------------------------------------------------------------- 1 | 2 | Invalid 3 | -------------------------------------------------------------------------------- /src/test/resources/com/github/koraktor/steamcondenser/servers/status_goldsrc: -------------------------------------------------------------------------------- 1 | # name userid uniqueid frag time ping loss adr 2 | # 1 "someone" 1 STEAM_0:0:123456 10 3:52 12 0 0 3 | # 2 "somebody" 2 STEAM_0:0:123457 3 2:42 34 0 0 4 | -------------------------------------------------------------------------------- /src/test/resources/com/github/koraktor/steamcondenser/servers/status_source: -------------------------------------------------------------------------------- 1 | # userid name uniqueid score connected ping loss state 2 | # 1 "someone" STEAM_0:0:123456 10 3:52 12 0 active 3 | # 2 "somebody" STEAM_0:0:123457 3 2:42 34 0 active 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | os: linux 3 | dist: trusty 4 | 5 | jdk: 6 | - openjdk8 7 | - oraclejdk8 8 | - openjdk11 9 | - openjdk14 10 | - openjdk-ea 11 | 12 | jobs: 13 | allow_failures: 14 | - jdk: openjdk-ea 15 | fast_finish: true 16 | 17 | notifications: 18 | webhooks: 19 | urls: 20 | - https://webhooks.gitter.im/e/8229d26a4f90bcc6cd67 21 | script: mvn test -Dsurefire.useFile=false 22 | 23 | cache: 24 | directories: 25 | - $HOME/.m2 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/exceptions/ConnectionResetException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.exceptions; 9 | 10 | /** 11 | * Indicates that a connection has been reset by the peer 12 | * 13 | * @author Sebastian Staudt 14 | */ 15 | public class ConnectionResetException extends SteamCondenserException { 16 | 17 | public ConnectionResetException() { 18 | super("Connection reset by peer"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/A2S_INFO_Packet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | /** 11 | * The A2S_INFO_Packet class represents a A2S_INFO request send to the server 12 | * 13 | * @author Sebastian Staudt 14 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updateServerInfo 15 | */ 16 | public class A2S_INFO_Packet extends SteamPacket { 17 | 18 | /** 19 | * Creates a new A2S_INFO request object 20 | */ 21 | public A2S_INFO_Packet() { 22 | super(SteamPacket.A2S_INFO_HEADER, "Source Engine Query\0".getBytes()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/exceptions/PacketFormatException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.exceptions; 9 | 10 | /** 11 | * This exception class indicates a problem when parsing packet data from the 12 | * responses received from a game or master server 13 | * 14 | * @author Sebastian Staudt 15 | */ 16 | public class PacketFormatException extends SteamCondenserException { 17 | 18 | /** 19 | * Creates a new PacketFormatException instance 20 | * 21 | * @param message The message to attach to the exception 22 | */ 23 | public PacketFormatException(String message) { 24 | super(message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/exceptions/RCONNoAuthException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.exceptions; 9 | 10 | /** 11 | * This exception class indicates that you have not authenticated yet with the 12 | * game server you're trying to send commands via RCON 13 | * 14 | * @author Sebastian Staudt 15 | * @see com.github.koraktor.steamcondenser.servers.GameServer#rconAuth 16 | * @see com.github.koraktor.steamcondenser.servers.GameServer#rconExec 17 | */ 18 | public class RCONNoAuthException extends SteamCondenserException { 19 | 20 | /** 21 | * Creates a new RCONNoAuthException instance 22 | */ 23 | public RCONNoAuthException() { 24 | super("Not authenticated yet."); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/exceptions/RCONBanException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.exceptions; 9 | 10 | /** 11 | * This exception class indicates that the IP address your accessing the game 12 | * server from has been banned by the server 13 | *

14 | * You or the server operator will have to unban your IP address on the server. 15 | * 16 | * @author Sebastian Staudt 17 | * @see com.github.koraktor.steamcondenser.servers.GameServer#rconAuth 18 | */ 19 | public class RCONBanException extends SteamCondenserException { 20 | 21 | /** 22 | * Creates a new RCONBanException instance 23 | */ 24 | public RCONBanException() { 25 | super("You have been banned from this server."); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/l4d/L4DWeapon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.l4d; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * This class represents the statistics of a single weapon for a user in 14 | * Left4Dead 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class L4DWeapon extends AbtractL4DWeapon { 19 | 20 | /** 21 | * Creates a new instance of a weapon based on the given XML data 22 | * 23 | * @param weaponData The XML data for this weapon 24 | */ 25 | public L4DWeapon(XMLData weaponData) { 26 | super(weaponData); 27 | 28 | this.killPercentage = Float.parseFloat(weaponData 29 | .getString("killpct").replace("%", "")) * 0.01f; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/A2S_SERVERQUERY_GETCHALLENGE_Packet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | /** 11 | * This packet class represents a A2S_SERVERQUERY_GETCHALLENGE request send to 12 | * a game server 13 | *

14 | * It is used to retrieve a challenge number from the game server, which helps 15 | * to identify the requesting client. 16 | * 17 | * @author Sebastian Staudt 18 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updateChallengeNumber 19 | */ 20 | public class A2S_SERVERQUERY_GETCHALLENGE_Packet extends SteamPacket { 21 | 22 | /** 23 | * Creates a new A2S_SERVERQUERY_GETCHALLENGE request object 24 | */ 25 | public A2S_SERVERQUERY_GETCHALLENGE_Packet() { 26 | super(SteamPacket.A2S_SERVERQUERY_GETCHALLENGE_HEADER); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/GameClass.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community; 9 | 10 | /** 11 | * An abstract class implementing basic functionality for classes representing 12 | * player classes 13 | * 14 | * @author Sebastian Staudt 15 | */ 16 | public abstract class GameClass { 17 | 18 | protected String name; 19 | 20 | protected int playTime; 21 | 22 | /** 23 | * Returns the name of this class 24 | * 25 | * @return [String] The name of this class 26 | */ 27 | public String getName() { 28 | return this.name; 29 | } 30 | 31 | /** 32 | * Returns the time in minutes the player has played with this class 33 | * 34 | * @return The time this class has been played 35 | */ 36 | public int getPlayTime() { 37 | return this.playTime; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/exceptions/WebApiExceptionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.exceptions; 9 | 10 | import org.junit.Test; 11 | 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.hamcrest.CoreMatchers.equalTo; 14 | import static org.junit.Assert.assertThat; 15 | 16 | /** 17 | * @author Sebastian Staudt 18 | */ 19 | public class WebApiExceptionTest { 20 | 21 | @Test 22 | public void testInvalidKey() { 23 | WebApiException exception = new WebApiException(WebApiException.Cause.INVALID_KEY); 24 | assertThat(exception.getMessage(), is(equalTo("This is not a valid Steam Web API key."))); 25 | } 26 | 27 | @Test 28 | public void testSimple() { 29 | WebApiException exception = new WebApiException("message"); 30 | assertThat(exception.getMessage(), is(equalTo("message"))); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONAuthRequestPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | /** 11 | * This packet class represents a SERVERDATA_AUTH request sent to a Source 12 | * server 13 | *

14 | * It is used to authenticate the client for RCON communication. 15 | * 16 | * @author Sebastian Staudt 17 | * @see com.github.koraktor.steamcondenser.servers.SourceServer#rconAuth 18 | */ 19 | public class RCONAuthRequestPacket extends RCONPacket { 20 | 21 | /** 22 | * Creates a RCON authentication request for the given request ID and RCON 23 | * password 24 | * 25 | * @param requestId The request ID of the RCON connection 26 | * @param rconPassword The RCON password of the server 27 | */ 28 | public RCONAuthRequestPacket(int requestId, String rconPassword) { 29 | super(requestId, RCONPacket.SERVERDATA_AUTH, rconPassword); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONExecRequestPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | /** 11 | * This packet class represents a SERVERDATA_EXECCOMMAND packet sent to a 12 | * Source server 13 | *

14 | * It is used to request a command execution on the server. 15 | * 16 | * @author Sebastian Staudt 17 | * @see com.github.koraktor.steamcondenser.servers.SourceServer#rconExec 18 | */ 19 | public class RCONExecRequestPacket extends RCONPacket { 20 | 21 | /** 22 | * Creates a RCON command execution request for the given request ID and 23 | * command 24 | * 25 | * @param requestId The request ID of the RCON connection 26 | * @param rconCommand The command to execute on the server 27 | */ 28 | public RCONExecRequestPacket(int requestId, String rconCommand) { 29 | super(requestId, RCONPacket.SERVERDATA_EXECCOMMAND, rconCommand); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONAuthResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | /** 11 | * This packet class represents a SERVERDATA_AUTH_RESPONSE packet sent by a 12 | * Source server 13 | *

14 | * It is used to indicate the success or failure of an authentication attempt 15 | * of a client for RCON communication. 16 | * 17 | * @author Sebastian Staudt 18 | * @see com.github.koraktor.steamcondenser.servers.SourceServer#rconAuth 19 | */ 20 | public class RCONAuthResponse extends RCONPacket { 21 | 22 | /** 23 | * Creates a RCON authentication response for the given request ID 24 | *

25 | * The request ID of the packet will match the client's request if 26 | * authentication was successful 27 | * 28 | * @param requestId The request ID of the RCON connection 29 | */ 30 | public RCONAuthResponse(int requestId) { 31 | super(requestId, RCONPacket.SERVERDATA_AUTH_RESPONSE, ""); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONTerminator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | /** 11 | * This packet class represents a special SERVERDATA_RESPONSE_VALUE packet 12 | * which is sent to the server 13 | * 14 | * It is used to determine the end of a RCON response from Source servers. 15 | * Packets of this type are sent after the actual RCON command and the empty 16 | * response packet from the server will indicate the end of the response. 17 | * 18 | * @author Sebastian Staudt 19 | * @see com.github.koraktor.steamcondenser.servers.SourceServer#rconExec 20 | */ 21 | public class RCONTerminator extends RCONPacket { 22 | 23 | /** 24 | * Creates a new RCON terminator packet instance for the given request ID 25 | * 26 | * @param requestId The request ID for the current RCON communication 27 | */ 28 | public RCONTerminator(int requestId) { 29 | super(requestId, RCONPacket.SERVERDATA_RESPONSE_VALUE, ""); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/S2A_INFO_BasePacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import java.util.HashMap; 11 | 12 | /** 13 | * This module implements methods to generate and access server information 14 | * from S2A_INFO_DETAILED and S2A_INFO2 response packets 15 | * 16 | * @author Sebastian Staudt 17 | * @see S2A_INFO_DETAILED_Packet 18 | * @see S2A_INFO2_Packet 19 | */ 20 | public abstract class S2A_INFO_BasePacket extends SteamPacket { 21 | 22 | protected HashMap info; 23 | 24 | S2A_INFO_BasePacket(byte headerByte, byte[] dataBytes) { 25 | super(headerByte, dataBytes); 26 | 27 | this.info = new HashMap<>(); 28 | } 29 | 30 | /** 31 | * Returns a generated array of server properties from the instance 32 | * variables of the packet object 33 | * 34 | * @return The information provided by the server 35 | */ 36 | public HashMap getInfo() { 37 | return this.info; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Sniper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * Represents the stats for the Team Fortress 2 Sniper class for a specific 14 | * user 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class TF2Sniper extends TF2Class { 19 | 20 | private int maxHeadshots; 21 | 22 | /** 23 | * Creates a new instance of the Sniper class based on the given XML data 24 | * 25 | * @param classData The XML data for this Sniper 26 | */ 27 | public TF2Sniper(XMLData classData) { 28 | super(classData); 29 | 30 | this.maxHeadshots = classData.getInteger("iheadshots"); 31 | } 32 | 33 | /** 34 | * Returns the maximum number enemies killed with a headshot by the player 35 | * in single life as a Sniper 36 | * 37 | * @return Maximum number of headshots 38 | */ 39 | public int getMaxHeadshots() { 40 | return this.maxHeadshots; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/A2S_RULES_Packet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import com.github.koraktor.steamcondenser.Helper; 11 | 12 | /** 13 | * This packet class represents a A2S_RULES request send to a game server 14 | *

15 | * The game server will return a list of currently active game rules, e.g. 16 | * mp_friendlyfire = 1. 17 | *

18 | * This packet type requires the client to challenge the server in advance, 19 | * which is done automatically if required. 20 | * 21 | * @author Sebastian Staudt 22 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updateRules 23 | */ 24 | public class A2S_RULES_Packet extends SteamPacket { 25 | 26 | /** 27 | * Creates a new A2S_RULES request object including the challenge number 28 | * 29 | * @param challengeNumber The challenge number received from the server 30 | */ 31 | public A2S_RULES_Packet(int challengeNumber) { 32 | super(SteamPacket.A2S_RULES_HEADER, Helper.byteArrayFromInteger(Integer.reverseBytes(challengeNumber))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/community/portal2/Portal2StatsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011, Guto Maia 6 | * Copyright (c) 2011-2012, Sebastian Staudt 7 | */ 8 | 9 | package com.github.koraktor.steamcondenser.community.portal2; 10 | 11 | import org.junit.Test; 12 | 13 | import com.github.koraktor.steamcondenser.community.GameStatsTestCase; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | 17 | /** 18 | * @author Guto Maia 19 | * @author Sebastian Staudt 20 | */ 21 | public class Portal2StatsTest extends GameStatsTestCase { 22 | 23 | public Portal2StatsTest() { 24 | super("gutomaia", "portal2"); 25 | } 26 | 27 | @Test 28 | public void getPortal2Stats() throws Exception { 29 | assertEquals("Portal 2", stats.getGame().getName()); 30 | assertEquals("portal2", stats.getGame().getShortName()); 31 | assertEquals(620, stats.getGame().getAppId()); 32 | assertEquals("0", stats.getHoursPlayed()); 33 | assertEquals("gutomaia", stats.getUser().getCustomUrl()); 34 | } 35 | 36 | @Test 37 | public void achievements() throws Exception { 38 | assertEquals(17, stats.getAchievementsDone()); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/exceptions/SteamCondenserException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.exceptions; 9 | 10 | /** 11 | * This exception class is used as a base class for all exceptions related to 12 | * Steam Condenser's operation 13 | * 14 | * @author Sebastian Staudt 15 | */ 16 | public class SteamCondenserException extends Exception { 17 | 18 | /** 19 | * Creates a new SteamCondenserException instance 20 | */ 21 | public SteamCondenserException() {} 22 | 23 | /** 24 | * Creates a new SteamCondenserException instance 25 | * 26 | * @param message The message to attach to the exception 27 | */ 28 | public SteamCondenserException(String message) { 29 | super(message); 30 | } 31 | 32 | /** 33 | * Creates a new SteamCondenserException instance 34 | * 35 | * @param message The message to attach to the exception 36 | * @param cause The initial error that caused this exception 37 | */ 38 | public SteamCondenserException(String message, Throwable cause) { 39 | super(message, cause); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/l4d/L4DExplosive.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.l4d; 9 | 10 | import com.github.koraktor.steamcondenser.community.GameWeapon; 11 | import com.github.koraktor.steamcondenser.community.XMLData; 12 | 13 | /** 14 | * This class represents the statistics of a single explosive weapon for a user 15 | * in Left4Dead 16 | * 17 | * @author Sebastian Staudt 18 | */ 19 | public class L4DExplosive extends GameWeapon { 20 | 21 | /** 22 | * Creates a new instance of an explosivve based on the given XML data 23 | * 24 | * @param weaponData The XML data of this explosive 25 | */ 26 | public L4DExplosive(XMLData weaponData) { 27 | super(weaponData); 28 | 29 | this.id = weaponData.getName(); 30 | this.shots = weaponData.getInteger("thrown"); 31 | } 32 | 33 | /** 34 | * Returns the average number of killed zombies for one shot of this 35 | * explosive 36 | * 37 | * @return The average number of kills per shot 38 | */ 39 | public float getAvgKillsPerShot() { 40 | return 1 / this.getAvgShotsPerKill(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/S2C_CHALLENGE_Packet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | /** 11 | * This packet class represents a S2C_CHALLENGE response replied by a game 12 | * server 13 | *

14 | * It is used to provide a challenge number to a client requesting information 15 | * from the game server. 16 | * 17 | * @author Sebastian Staudt 18 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updateChallengeNumber 19 | */ 20 | public class S2C_CHALLENGE_Packet extends SteamPacket { 21 | 22 | /** 23 | * Creates a new S2C_CHALLENGE response object based on the given data 24 | * 25 | * @param challengeNumberBytes The raw packet data replied from the server 26 | */ 27 | public S2C_CHALLENGE_Packet(byte[] challengeNumberBytes) { 28 | super(SteamPacket.S2C_CHALLENGE_HEADER, challengeNumberBytes); 29 | } 30 | 31 | /** 32 | * Returns the challenge number received from the game server 33 | * 34 | * @return The challenge number provided by the game server 35 | */ 36 | public int getChallengeNumber() { 37 | return Integer.reverseBytes(this.contentData.getInt()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONGoldSrcResponsePacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 11 | 12 | /** 13 | * This packet class represents a RCON response packet sent by a GoldSrc server 14 | *

15 | * It is used to transport the output of a command from the server to the 16 | * client which requested the command execution. 17 | * 18 | * @author Sebastian Staudt 19 | * @see com.github.koraktor.steamcondenser.servers.GoldSrcServer#rconExec 20 | */ 21 | public class RCONGoldSrcResponsePacket extends SteamPacket { 22 | 23 | /** 24 | * Creates a RCON command response for the given command output 25 | * 26 | * @param commandResponse The output of the command executed on the server 27 | */ 28 | public RCONGoldSrcResponsePacket(byte[] commandResponse) { 29 | super(SteamPacket.RCON_GOLDSRC_RESPONSE_HEADER, commandResponse); 30 | } 31 | 32 | /** 33 | * Returns the output of the command execution 34 | * 35 | * @return The output of the command 36 | */ 37 | public String getResponse() { 38 | return this.contentData.getString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/Helper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser; 9 | 10 | /** 11 | * A helper class used to convert byte arrays into integers and vice-versa 12 | * 13 | * @author Sebastian Staudt 14 | */ 15 | public abstract class Helper { 16 | 17 | /** 18 | * Convert an integer value into the corresponding byte array 19 | * 20 | * @param integer The integer to convert 21 | * @return The byte array representing the given integer 22 | */ 23 | public static byte[] byteArrayFromInteger(int integer) { 24 | return new byte[] { 25 | (byte) (integer >> 24), 26 | (byte) (integer >> 16), 27 | (byte) (integer >> 8), 28 | (byte) integer 29 | }; 30 | } 31 | 32 | /** 33 | * Convert a byte array into the corresponding integer value of its bytes 34 | * 35 | * @param byteArray The byte array to convert 36 | * @return The integer represented by the byte array 37 | */ 38 | public static int integerFromByteArray(byte[] byteArray) { 39 | return byteArray[0] << 24 | 40 | (byteArray[1] & 0xff) << 16 | 41 | (byteArray[2] & 0xff) << 8 | 42 | (byteArray[3] & 0xff); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/A2S_PLAYER_Packet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import com.github.koraktor.steamcondenser.Helper; 11 | 12 | /** 13 | * This packet class represents a A2S_PLAYER request send to a game server 14 | *

15 | * It is used to request the list of players currently playing on the server. 16 | *

17 | * This packet type requires the client to challenge the server in advance, 18 | * which is done automatically if required. 19 | * 20 | * @author Sebastian Staudt 21 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updatePlayers 22 | */ 23 | public class A2S_PLAYER_Packet extends SteamPacket { 24 | 25 | /** 26 | * Creates a new A2S_PLAYER request object without a challenge number 27 | */ 28 | public A2S_PLAYER_Packet() { 29 | this(-1); 30 | } 31 | 32 | /** 33 | * Creates a new A2S_PLAYER request object including the challenge number 34 | * 35 | * @param challengeNumber The challenge number received from the server 36 | */ 37 | public A2S_PLAYER_Packet(int challengeNumber) { 38 | super(SteamPacket.A2S_PLAYER_HEADER, Helper.byteArrayFromInteger(Integer.reverseBytes(challengeNumber))); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2020, Sebastian Staudt 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of the author nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/dota2/Dota2Item.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.dota2; 9 | 10 | import org.json.JSONObject; 11 | 12 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 13 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 14 | import com.github.koraktor.steamcondenser.community.GameItem; 15 | 16 | /** 17 | * Represents a DotA 2 item 18 | * 19 | * @author Sebastian Staudt 20 | */ 21 | public class Dota2Item extends GameItem { 22 | 23 | private boolean equipped; 24 | 25 | /** 26 | * Creates a new instance of a Dota2Item with the given data 27 | * 28 | * @param inventory The inventory this item is contained in 29 | * @param itemData The data specifying this item 30 | * @throws WebApiException on Web API errors 31 | */ 32 | public Dota2Item(Dota2Inventory inventory, JSONObject itemData) 33 | throws SteamCondenserException { 34 | super(inventory, itemData); 35 | 36 | this.equipped = !itemData.isNull("equipped"); 37 | } 38 | 39 | /** 40 | * Returns whether this item is equipped by this player at all 41 | * 42 | * @return Whether this item is equipped by this player at all 43 | */ 44 | public boolean isEquipped() { 45 | return this.equipped; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONExecResponsePacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | /** 11 | * This packet class represents a SERVERDATA_RESPONSE_VALUE packet sent by a 12 | * Source server 13 | *

14 | * It is used to transport the output of a command from the server to the 15 | * client which requested the command execution. 16 | * 17 | * @author Sebastian Staudt 18 | * @see com.github.koraktor.steamcondenser.servers.SourceServer#rconExec 19 | */ 20 | public class RCONExecResponsePacket extends RCONPacket { 21 | 22 | /** 23 | * Creates a RCON command response for the given request ID and command 24 | * output 25 | * 26 | * @param requestId The request ID of the RCON connection 27 | * @param commandReturn The output of the command executed on the server 28 | */ 29 | public RCONExecResponsePacket(int requestId, String commandReturn) { 30 | super(requestId, RCONPacket.SERVERDATA_RESPONSE_VALUE, commandReturn); 31 | } 32 | 33 | /** 34 | * Returns the output of the command execution 35 | * 36 | * @return The output of the command 37 | */ 38 | public String getResponse() { 39 | String response = new String(this.contentData.array()); 40 | return response.substring(0, response.length() - 2); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Medic.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * Represents the stats for the Team Fortress 2 Medic class for a specific user 14 | * 15 | * @author Sebastian Staudt 16 | */ 17 | public class TF2Medic extends TF2Class { 18 | 19 | private int maxHealthHealed; 20 | 21 | private int maxUbercharges; 22 | 23 | /** 24 | * Creates a new instance of the Medic class based on the given XML data 25 | * 26 | * @param classData The XML data for this Medic 27 | */ 28 | public TF2Medic(XMLData classData) { 29 | super(classData); 30 | 31 | this.maxHealthHealed = classData.getInteger("ihealthpointshealed"); 32 | this.maxUbercharges = classData.getInteger("inuminvulnerable"); 33 | } 34 | 35 | /** 36 | * Returns the maximum health healed for teammates by the player in a 37 | * single life as a Medic 38 | * 39 | * @return Maximum health healed 40 | */ 41 | public int getMaxHealthHealed() { 42 | return this.maxHealthHealed; 43 | } 44 | 45 | /** 46 | * Returns the maximum number of ÜberCharges provided by the player in a 47 | * single life as a Medic 48 | * 49 | * @return Maximum number of ÜberCharges 50 | */ 51 | public int getMaxUbercharges() { 52 | return this.maxUbercharges; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/l4d/L4D2Weapon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.l4d; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * This class represents the statistics of a single weapon for a user in 14 | * Left4Dead 2 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class L4D2Weapon extends AbtractL4DWeapon { 19 | 20 | private int damage; 21 | 22 | private String weaponGroup; 23 | 24 | /** 25 | * Creates a new instance of a weapon based on the given XML data 26 | * 27 | * @param weaponData The XML data of this weapon 28 | */ 29 | public L4D2Weapon(XMLData weaponData) { 30 | super(weaponData); 31 | 32 | this.damage = weaponData.getInteger("damage"); 33 | this.killPercentage = Float.parseFloat(weaponData 34 | .getString("pctkills").replace("%", "")) * 0.01f; 35 | this.weaponGroup = weaponData.getAttribute("group"); 36 | } 37 | 38 | /** 39 | * Returns the amount of damage done by the player with this weapon 40 | * 41 | * @return The damage done by this weapon 42 | */ 43 | public int getDamage() { 44 | return this.damage; 45 | } 46 | 47 | /** 48 | * Returns the weapon group this weapon belongs to 49 | * 50 | * @return The group this weapon belongs to 51 | */ 52 | public String getWeaponGroup() { 53 | return this.weaponGroup; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2ClassFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * The TF2ClassFactory is used to created instances of 14 | * TF2Class based on the XML input data 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | abstract class TF2ClassFactory 19 | { 20 | /** 21 | * Creates a new instance of a TF2 class instance based on the given XML 22 | * data 23 | * 24 | * This returns an instance of TF2Class or its subclasses 25 | * TF2Engineer, TF2Medic, TF2Sniper 26 | * or TF2Spy depending on the given XML data. 27 | * 28 | * @param classData The XML data for the class 29 | * @return The statistics for the given class data 30 | */ 31 | public static TF2Class getTF2Class(XMLData classData) { 32 | String className = classData.getString("className"); 33 | 34 | if(className.equals("Engineer")) { 35 | return new TF2Engineer(classData); 36 | } else if(className.equals("Medic")) { 37 | return new TF2Medic(classData); 38 | } else if(className.equals("Sniper")) { 39 | return new TF2Sniper(classData); 40 | } else if(className.equals("Spy")) { 41 | return new TF2Spy(classData); 42 | } else { 43 | return new TF2Class(classData); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONGoldSrcRequestPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | import com.github.koraktor.steamcondenser.Helper; 11 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 12 | 13 | /** 14 | * This packet class represents a RCON request packet sent to a GoldSrc server 15 | *

16 | * It is used to request a command execution on the server. 17 | * 18 | * @author Sebastian Staudt 19 | * @see com.github.koraktor.steamcondenser.servers.GoldSrcServer#rconExec 20 | */ 21 | public class RCONGoldSrcRequestPacket extends SteamPacket { 22 | /** 23 | * Creates a request for the given request string 24 | *

25 | * The request string has the form rcon {challenge number} {RCON 26 | * password} {command}. 27 | * 28 | * @param request The request string to send to the server 29 | */ 30 | public RCONGoldSrcRequestPacket(String request) { 31 | super((byte) 0, request.getBytes()); 32 | } 33 | 34 | /** 35 | * Returns the raw data representing this packet 36 | * 37 | * @return A byte array containing the raw data of this request packet 38 | */ 39 | public byte[] getBytes() { 40 | byte[] bytes = new byte[this.contentData.getLength() + 4]; 41 | 42 | System.arraycopy(Helper.byteArrayFromInteger(0xFFFFFFFF), 0, bytes, 0, 4); 43 | System.arraycopy(this.contentData.array(), 0, bytes, 4, this.contentData.getLength()); 44 | 45 | return bytes; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/GameWeapon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community; 9 | 10 | /** 11 | * An abstract class implementing basic functionality for classes representing 12 | * game weapons 13 | * 14 | * @author Sebastian Staudt 15 | */ 16 | public abstract class GameWeapon { 17 | 18 | protected int kills; 19 | 20 | protected String id; 21 | 22 | protected int shots; 23 | 24 | /** 25 | * Creates a new game weapon instance with the data provided 26 | * 27 | * @param weaponData The data representing this weapon 28 | */ 29 | public GameWeapon(XMLData weaponData) { 30 | this.kills = weaponData.getInteger("kills"); 31 | } 32 | 33 | /** 34 | * Returns the average number of shots needed for a kill with this weapon 35 | * 36 | * @return The average number of shots needed for a kill 37 | */ 38 | public float getAvgShotsPerKill() { 39 | return this.shots / this.kills; 40 | } 41 | 42 | /** 43 | * Returns the unique identifier for this weapon 44 | * 45 | * @return The identifier of this weapon 46 | */ 47 | public String getId() { 48 | return this.id; 49 | } 50 | 51 | /** 52 | * Returns the number of kills achieved with this weapon 53 | * 54 | * @return The number of kills achieved 55 | */ 56 | public int getKills() { 57 | return this.kills; 58 | } 59 | 60 | /** 61 | * Returns the number of shots fired with this weapon 62 | * 63 | * @return The number of shots fired 64 | */ 65 | public int getShots() { 66 | return this.shots; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/community/GameStatsTestCase.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011, Guto Maia 6 | * Copyright (c) 2011-2013, Sebastian Staudt 7 | */ 8 | 9 | package com.github.koraktor.steamcondenser.community; 10 | 11 | import javax.xml.parsers.DocumentBuilder; 12 | import javax.xml.parsers.DocumentBuilderFactory; 13 | 14 | import org.junit.After; 15 | import org.junit.Before; 16 | 17 | import org.w3c.dom.Document; 18 | 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.verify; 21 | import static org.mockito.Mockito.when; 22 | 23 | /** 24 | * @author Guto Maia 25 | * @author Sebastian Staudt 26 | */ 27 | public abstract class GameStatsTestCase { 28 | 29 | private String game; 30 | 31 | private DocumentBuilder parser; 32 | 33 | protected STATS stats; 34 | 35 | private String user; 36 | 37 | public GameStatsTestCase(String user, String game){ 38 | this.game = game; 39 | this.user = user; 40 | } 41 | 42 | @Before 43 | @SuppressWarnings("unchecked") 44 | public void setUp() throws Exception { 45 | this.parser = mock(DocumentBuilder.class); 46 | XMLData.setDocumentBuilder(this.parser); 47 | Document statsDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(this.getClass().getResourceAsStream(this.user + "-" + this.game + ".xml")); 48 | when(parser.parse("http://steamcommunity.com/id/" + this.user + "/stats/" + this.game + "?xml=all")).thenReturn(statsDocument); 49 | 50 | this.stats = (STATS) GameStats.createGameStats(this.user, this.game); 51 | } 52 | 53 | @After 54 | public void tearDown() throws Exception{ 55 | verify(parser).parse("http://steamcommunity.com/id/"+ this.user + "/stats/" + this.game + "?xml=all"); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/servers/ServerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers; 9 | 10 | import java.net.InetAddress; 11 | import java.net.UnknownHostException; 12 | import java.util.ArrayList; 13 | 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertTrue; 22 | import static org.mockito.Mockito.spy; 23 | import static org.mockito.Mockito.times; 24 | import static org.mockito.Mockito.verify; 25 | 26 | /** 27 | * @author Sebastian Staudt 28 | */ 29 | public class ServerTest { 30 | 31 | private Server server; 32 | 33 | @Before 34 | public void setup() throws Exception { 35 | this.server = spy(new GenericServer()); 36 | } 37 | 38 | @Test 39 | public void testRotateIp() throws Exception { 40 | this.server.ipAddresses.add(InetAddress.getByAddress(new byte[] { 127, 0, 0, 2 })); 41 | 42 | assertFalse(this.server.rotateIp()); 43 | assertEquals(this.server.ipAddresses.get(1), this.server.ipAddress); 44 | assertTrue(this.server.rotateIp()); 45 | assertEquals(this.server.ipAddresses.get(0), this.server.ipAddress); 46 | 47 | verify(this.server, times(2)).initSocket(); 48 | } 49 | 50 | class GenericServer extends Server { 51 | 52 | public GenericServer() throws SteamCondenserException, UnknownHostException { 53 | super(InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }), 27015); 54 | } 55 | 56 | public void initSocket() {} 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONPacketFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | import com.github.koraktor.steamcondenser.PacketBuffer; 11 | import com.github.koraktor.steamcondenser.exceptions.PacketFormatException; 12 | 13 | /** 14 | * This module provides functionality to handle raw packet data for Source RCON 15 | * 16 | * It's is used to transform data bytes into packet objects for RCON 17 | * communication with Source servers. 18 | * 19 | * @author Sebastian Staudt 20 | * @see RCONPacket 21 | */ 22 | public abstract class RCONPacketFactory { 23 | 24 | /** 25 | * Creates a new packet object based on the header byte of the given raw 26 | * data 27 | * 28 | * @param rawData The raw data of the packet 29 | * @return RCONPacket The packet object generated from the packet data 30 | * @throws PacketFormatException if the packet header is not recognized 31 | */ 32 | public static RCONPacket getPacketFromData(byte[] rawData) 33 | throws PacketFormatException { 34 | PacketBuffer packetBuffer = new PacketBuffer(rawData); 35 | 36 | int requestId = Integer.reverseBytes(packetBuffer.getInt()); 37 | int header = Integer.reverseBytes(packetBuffer.getInt()); 38 | String data = packetBuffer.getString(); 39 | 40 | switch(header) { 41 | case RCONPacket.SERVERDATA_AUTH_RESPONSE: 42 | return new RCONAuthResponse(requestId); 43 | case RCONPacket.SERVERDATA_RESPONSE_VALUE: 44 | return new RCONExecResponsePacket(requestId, data); 45 | default: 46 | throw new PacketFormatException("Unknown packet with header " + header + " received."); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/portal2/Portal2Stats.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.portal2; 9 | 10 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 11 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 12 | import com.github.koraktor.steamcondenser.community.GameInventory; 13 | import com.github.koraktor.steamcondenser.community.GameStats; 14 | 15 | /** 16 | * The Portal2Stats class represents the game statistics for a single user in 17 | * Portal 2 18 | * 19 | * @author Sebastian Staudt 20 | */ 21 | public class Portal2Stats extends GameStats { 22 | 23 | private Portal2Inventory inventory; 24 | 25 | /** 26 | * Creates a new object holding Portal 2 statistics for the given user 27 | * 28 | * @param steamId The custom URL or 64bit Steam ID of the user 29 | * @throws SteamCondenserException if an error occurs while fetching the 30 | * stats data 31 | */ 32 | public Portal2Stats(Object steamId) throws SteamCondenserException { 33 | super(steamId, "portal2"); 34 | } 35 | 36 | /** 37 | * Returns the current Portal 2 inventory (a.k.a. Robot Enrichment) of this 38 | * player 39 | * 40 | * @return This player's Portal 2 inventory 41 | * @throws WebApiException if an error occurs while querying Steam's Web 42 | * API 43 | */ 44 | public Portal2Inventory getInventory() throws SteamCondenserException { 45 | if(!this.isPublic()) { 46 | return null; 47 | } 48 | 49 | if(this.inventory == null) { 50 | this.inventory = (Portal2Inventory) GameInventory.create(Portal2Inventory.APP_ID, this.user.getSteamId64()); 51 | } 52 | 53 | return this.inventory; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Spy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * Represents the stats for the Team Fortress 2 Spy class for a specific user 14 | * 15 | * @author Sebastian Staudt 16 | */ 17 | public class TF2Spy extends TF2Class { 18 | 19 | private int maxBackstabs; 20 | 21 | private int maxHeadShots; 22 | 23 | private int maxHealthLeeched; 24 | 25 | /** 26 | * Creates a new instance of the Spy class based on the given XML data 27 | * 28 | * @param classData The XML data for this Spy 29 | */ 30 | public TF2Spy(XMLData classData) { 31 | super(classData); 32 | 33 | this.maxBackstabs = classData.getInteger("ibackstabs"); 34 | this.maxHeadShots = classData.getInteger("iheadshots"); 35 | this.maxHealthLeeched = classData.getInteger("ihealthpointsleached"); 36 | } 37 | 38 | /** 39 | * Returns the maximum health leeched from enemies by the player in a single 40 | * life as a Spy 41 | * 42 | * @return Maximum health leeched 43 | */ 44 | public int getMaxBackstabs() { 45 | return this.maxBackstabs; 46 | } 47 | 48 | /** 49 | * Returns the head shots by the player in a single life as a Spy 50 | * 51 | * @return Maximum number of head shots 52 | */ 53 | public int getMaxHeadShots() { 54 | return this.maxHeadShots; 55 | } 56 | 57 | /** 58 | * Returns the maximum health leeched from enemies by the player in a single 59 | * life as a Spy 60 | * 61 | * @return Maximum health leeched 62 | */ 63 | public int getMaxHealthLeeched() { 64 | return this.maxHealthLeeched; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Engineer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * Represents the stats for the Team Fortress 2 Engineer class for a specific 14 | * user 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class TF2Engineer extends TF2Class { 19 | 20 | private int maxBuildingsBuilt; 21 | 22 | private int maxSentryKills; 23 | 24 | private int maxTeleports; 25 | 26 | /** 27 | * Creates a new instance of the Engineer class based on the given XML data 28 | * 29 | * @param classData The XML data for this Engineer 30 | */ 31 | public TF2Engineer(XMLData classData) { 32 | super(classData); 33 | 34 | this.maxBuildingsBuilt = classData.getInteger("ibuildingsbuilt"); 35 | this.maxSentryKills = classData.getInteger("isentrykills"); 36 | this.maxTeleports = classData.getInteger("inumteleports"); 37 | } 38 | 39 | /** 40 | * Returns the maximum number of buildings built by the player in a single 41 | * life as an Engineer 42 | * 43 | * @return Maximum number of buildings built 44 | */ 45 | public int getMaxBuildingsBuilt() { 46 | return this.maxBuildingsBuilt; 47 | } 48 | 49 | /** 50 | * Returns the maximum number of enemies killed by sentry guns built by the 51 | * player in a single life as an Engineer 52 | * 53 | * @return Maximum number of sentry kills 54 | */ 55 | public int getMaxSentryKills() { 56 | return this.maxSentryKills; 57 | } 58 | 59 | /** 60 | * Returns the maximum number of teammates teleported by teleporters built 61 | * by the player in a single life as an Engineer 62 | * 63 | * @return Maximum number of teleports 64 | */ 65 | public int getMaxTeleports() { 66 | return this.maxTeleports; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/S2A_RULES_Packet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import java.util.HashMap; 11 | 12 | import com.github.koraktor.steamcondenser.exceptions.PacketFormatException; 13 | 14 | /** 15 | * This class represents a S2A_RULES response sent by a game server 16 | *

17 | * It is used to transfer a list of server rules (a.k.a. CVARs) with their 18 | * active values. 19 | * 20 | * @author Sebastian Staudt 21 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updateRules 22 | */ 23 | public class S2A_RULES_Packet extends SteamPacket { 24 | 25 | private HashMap rulesHash; 26 | 27 | /** 28 | * Creates a new S2A_RULES response object based on the given data 29 | * 30 | * @param dataBytes The raw packet data sent by the server 31 | */ 32 | public S2A_RULES_Packet(byte[] dataBytes) 33 | throws PacketFormatException { 34 | super(SteamPacket.S2A_RULES_HEADER, dataBytes); 35 | 36 | if (this.contentData.getLength() == 0) { 37 | throw new PacketFormatException("Wrong formatted S2A_RULES response packet."); 38 | } 39 | 40 | int rulesCount = Short.reverseBytes(this.contentData.getShort()); 41 | this.rulesHash = new HashMap<>(rulesCount); 42 | 43 | String rule; 44 | String value; 45 | for (int i = 0; i < rulesCount; i++) { 46 | rule = this.contentData.getString(); 47 | value = this.contentData.getString(); 48 | 49 | if(rule.equals("")) { 50 | break; 51 | } 52 | 53 | this.rulesHash.put(rule, value); 54 | } 55 | } 56 | 57 | /** 58 | * Returns the list of server rules (a.k.a. CVars) with the current values 59 | * 60 | * @return array A list of server rules 61 | */ 62 | public HashMap getRulesHash() { 63 | return this.rulesHash; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/S2A_PLAYER_Packet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import java.util.HashMap; 11 | 12 | import com.github.koraktor.steamcondenser.exceptions.PacketFormatException; 13 | import com.github.koraktor.steamcondenser.servers.SteamPlayer; 14 | 15 | /** 16 | * This class represents a S2A_PLAYER response sent by a game server 17 | *

18 | * It is used to transfer a list of players currently playing on the server. 19 | * 20 | * @author Sebastian Staudt 21 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updatePlayers 22 | */ 23 | public class S2A_PLAYER_Packet extends SteamPacket { 24 | 25 | private HashMap playerHash; 26 | 27 | /** 28 | * Creates a new S2A_PLAYER response object based on the given data 29 | * 30 | * @param dataBytes The raw packet data sent by the server 31 | */ 32 | public S2A_PLAYER_Packet(byte[] dataBytes) 33 | throws PacketFormatException { 34 | super(SteamPacket.S2A_PLAYER_HEADER, dataBytes); 35 | 36 | if(this.contentData.getLength() == 0) { 37 | throw new PacketFormatException("Wrong formatted S2A_PLAYER response packet."); 38 | } 39 | 40 | this.playerHash = new HashMap<>(this.contentData.getByte()); 41 | 42 | while(this.contentData.hasRemaining()) { 43 | int playerId = this.contentData.getByte() & 0xff; 44 | String playerName = this.contentData.getString(); 45 | this.playerHash.put(playerName, new SteamPlayer( 46 | playerId, 47 | playerName, 48 | Integer.reverseBytes(this.contentData.getInt()), 49 | Float.intBitsToFloat(Integer.reverseBytes(this.contentData.getInt())) 50 | )); 51 | } 52 | } 53 | 54 | /** 55 | * Returns the list of active players provided by the server 56 | * 57 | * @return All active players on the server 58 | */ 59 | public HashMap getPlayerHash() { 60 | return this.playerHash; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/community/l4d/L4D2StatsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011, Guto Maia 6 | * Copyright (c) 2011-2012, Sebastian Staudt 7 | */ 8 | 9 | package com.github.koraktor.steamcondenser.community.l4d; 10 | 11 | import org.junit.Test; 12 | 13 | import com.github.koraktor.steamcondenser.community.GameStatsTestCase; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | 17 | /** 18 | * @author Guto Maia 19 | * @author Sebastian Staudt 20 | */ 21 | public class L4D2StatsTest extends GameStatsTestCase { 22 | 23 | public L4D2StatsTest() { 24 | super("gutomaia", "l4d2"); 25 | } 26 | 27 | @Test 28 | public void getL4D2Stats() throws Exception { 29 | 30 | assertEquals("Left 4 Dead 2", stats.getGame().getName()); 31 | assertEquals("l4d2", stats.getGame().getShortName()); 32 | assertEquals(550, stats.getGame().getAppId()); 33 | assertEquals("0s", stats.getHoursPlayed());// TODO: strange behavior 34 | assertEquals("gutomaia", stats.getUser().getCustomUrl()); 35 | } 36 | 37 | @Test 38 | public void achievements() throws Exception { 39 | assertEquals(7, stats.getAchievementsDone()); 40 | } 41 | 42 | @Test 43 | public void getDamagePercentages() throws Exception { 44 | assertEquals("9.9", stats.getDamagePercentages().get("melee") 45 | .toString()); 46 | assertEquals("28.8", stats.getDamagePercentages().get("pistols") 47 | .toString()); 48 | assertEquals("43.8", stats.getDamagePercentages().get("rifles") 49 | .toString()); 50 | assertEquals("17.0", stats.getDamagePercentages().get("shotguns") 51 | .toString()); 52 | } 53 | 54 | @Test 55 | public void getLifetimeStats() { 56 | assertEquals("0.0", stats.getLifetimeStats().get("avgAdrenalineShared") 57 | .toString()); 58 | assertEquals("0.263158", 59 | stats.getLifetimeStats().get("avgAdrenalineUsed").toString()); 60 | assertEquals("0.157895", 61 | stats.getLifetimeStats().get("avgDefibrillatorsUsed") 62 | .toString()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribution Guidelines 2 | ======================= 3 | 4 | First of all, each single contribution is appreciated, whether a typo fix, 5 | improved documentation, a fixed bug or a whole new feature. 6 | 7 | ## Making your changes 8 | 9 | 1. Fork the repository on GitHub 10 | 2. Create a topic branch with a descriptive name, e.g. `fix-issue-123` or 11 | `feature-x` 12 | 3. Make your modifications, complying with the 13 | [code conventions](#code-conventions) 14 | 4. Commit small logical changes, each with a descriptive commit message. 15 | Please don't mix unrelated changes in a single commit. 16 | 17 | ## Commit messages 18 | 19 | Please format your commit messages as follows: 20 | 21 | Short summary of the change (up to 50 characters) 22 | 23 | Optionally add a more extensive description of your change after a 24 | blank line. Wrap the lines in this and the following paragraphs after 25 | 72 characters. 26 | 27 | ## Submitting your changes 28 | 29 | 1. Push your changes to a topic branch in your fork of the repository. 30 | 2. [Submit a pull request][pr] to the original repository. 31 | Describe your changes as short as possible, but as detailed as needed for 32 | others to get an overview of your modifications. 33 | 3. *Optionally*, [open an issue][issue] in the meta-repository if your change 34 | might be relevant to other implementations of Steam Condenser. Please add a 35 | link to your pull request. 36 | 37 | ## Code conventions 38 | 39 | * White spaces: 40 | * Indent using 4 spaces 41 | * Line endings must be line feeds (\n) 42 | * Add a newline at end of file 43 | * Name conventions: 44 | * `UpperCamelCase` for classes, interfaces and enums 45 | * `lowerCamelCase` for fields, methods and variables 46 | * `UPPER_CASE` for constants and `final static` fields 47 | 48 | ## Further information 49 | 50 | * [General GitHub documentation][gh-help] 51 | * [GitHub pull request documentation][gh-pr] 52 | * [Google group][mail] 53 | * \#steam-condenser on freenode.net 54 | 55 | [gh-help]: https://help.github.com 56 | [gh-pr]: https://help.github.com/send-pull-requests 57 | [issue]: https://github.com/koraktor/steam-condenser/issues/new 58 | [mail]: https://groups.google.com/group/steam-condenser 59 | [pr]: https://github.com/koraktor/steam-condenser-java/pull/new 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/GameLeaderboardEntry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community; 9 | 10 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 11 | 12 | /** 13 | * The GameLeaderboard class represents a single entry in a leaderboard 14 | * 15 | * @author Sebastian Staudt 16 | */ 17 | public class GameLeaderboardEntry { 18 | 19 | protected SteamId steamId; 20 | 21 | protected int score; 22 | 23 | protected int rank; 24 | 25 | protected GameLeaderboard leaderboard; 26 | 27 | /** 28 | * Creates new entry instance for the given XML data and leaderboard 29 | * 30 | * @param entryData The XML data of the leaderboard of the leaderboard 31 | * entry 32 | * @param leaderboard The leaderboard this entry belongs to 33 | */ 34 | public GameLeaderboardEntry(XMLData entryData, GameLeaderboard leaderboard) { 35 | try { 36 | this.steamId = SteamId.create(entryData.getString("steamid"), false); 37 | } catch(SteamCondenserException e) {} 38 | this.score = entryData.getInteger("score"); 39 | this.rank = entryData.getInteger("rank"); 40 | this.leaderboard = leaderboard; 41 | } 42 | 43 | /** 44 | * Returns the Steam ID of this entry's player 45 | * 46 | * @return The Steam ID of the player 47 | */ 48 | public SteamId getSteamId() { 49 | return this.steamId; 50 | } 51 | 52 | /** 53 | * Returns the score of this entry 54 | * 55 | * @return The score of this player 56 | */ 57 | public int getScore() { 58 | return this.score; 59 | } 60 | 61 | /** 62 | * Returns the rank where this entry is listed in the leaderboard 63 | * 64 | * @return The rank of this entry 65 | */ 66 | public int getRank() { 67 | return this.rank; 68 | } 69 | 70 | /** 71 | * Returns the leaderboard this entry belongs to 72 | * 73 | * @return The leaderboard of this entry 74 | */ 75 | public GameLeaderboard getLeaderboard() { 76 | return this.leaderboard; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/alien_swarm/AlienSwarmWeapon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.alien_swarm; 9 | 10 | import com.github.koraktor.steamcondenser.community.GameWeapon; 11 | import com.github.koraktor.steamcondenser.community.XMLData; 12 | 13 | /** 14 | * This class holds statistical information about weapons used by a player 15 | * in Alien Swarm 16 | * 17 | * @author Sebastian Staudt 18 | */ 19 | public class AlienSwarmWeapon extends GameWeapon { 20 | 21 | private float accuracy; 22 | 23 | private int damage; 24 | 25 | private int friendlyFire; 26 | 27 | private String name; 28 | 29 | /** 30 | * Creates a new weapon instance based on the assigned weapon XML data 31 | * 32 | * @param weaponData The data representing this weapon 33 | */ 34 | public AlienSwarmWeapon(XMLData weaponData) { 35 | super(weaponData); 36 | 37 | this.accuracy = weaponData.getFloat("accuracy"); 38 | this.damage = weaponData.getInteger("damage"); 39 | this.friendlyFire = weaponData.getInteger("friendlyfire"); 40 | this.name = weaponData.getString("name"); 41 | this.shots = weaponData.getInteger("shotsfired"); 42 | } 43 | 44 | /** 45 | * Returns the accuracy of the player with this weapon 46 | * 47 | * @return The accuracy of the player with this weapon 48 | */ 49 | public float getAccuracy() { 50 | return this.accuracy; 51 | } 52 | 53 | /** 54 | * Returns the damage achieved with this weapon 55 | * 56 | * @return The damage achieved with this weapon 57 | */ 58 | public int getDamage() { 59 | return this.damage; 60 | } 61 | 62 | /** 63 | * Returns the damage dealt to team mates with this weapon 64 | * 65 | * @return The damage dealt to team mates with this weapon 66 | */ 67 | public int getFriendlyFire() { 68 | return this.friendlyFire; 69 | } 70 | 71 | /** 72 | * Returns the name of this weapon 73 | * 74 | * @return The name of this weapon 75 | */ 76 | public String getName() { 77 | return this.name; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/sockets/MasterServerSocket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.net.InetAddress; 11 | import java.util.concurrent.TimeoutException; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import com.github.koraktor.steamcondenser.exceptions.PacketFormatException; 16 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 17 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 18 | 19 | /** 20 | * This class represents a socket used to communicate with master servers 21 | * 22 | * @author Sebastian Staudt 23 | */ 24 | public class MasterServerSocket extends QuerySocket { 25 | 26 | protected static final Logger LOG = LoggerFactory.getLogger(MasterServerSocket.class); 27 | 28 | /** 29 | * Creates a new socket to communicate with the server on the given IP 30 | * address and port 31 | * 32 | * @param ipAddress Either the IP address or the DNS name of the server 33 | * @param portNumber The port the server is listening on 34 | * @throws SteamCondenserException if the socket cannot be opened 35 | */ 36 | public MasterServerSocket(InetAddress ipAddress, int portNumber) 37 | throws SteamCondenserException { 38 | super(ipAddress, portNumber); 39 | } 40 | 41 | /** 42 | * Reads a single packet from the socket 43 | * 44 | * @return The packet replied from the server 45 | * @throws SteamCondenserException if an error occurs while communicating 46 | * with the server 47 | * @throws PacketFormatException if the packet has the wrong format 48 | * @throws TimeoutException if the request times out 49 | */ 50 | public SteamPacket getReply() 51 | throws SteamCondenserException, TimeoutException { 52 | this.receivePacket(1500); 53 | 54 | if(this.buffer.getInt() != -1) { 55 | throw new PacketFormatException("Master query response has wrong packet header."); 56 | } 57 | 58 | SteamPacket packet = this.getPacketFromData(); 59 | 60 | LOG.info("Received reply of type \"" + packet.getClass().getSimpleName() + "\""); 61 | 62 | return packet; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/M2A_SERVER_BATCH_Packet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import java.util.Vector; 11 | 12 | import com.github.koraktor.steamcondenser.exceptions.PacketFormatException; 13 | 14 | /** 15 | * This packet class represents a M2A_SERVER_BATCH response replied by a master 16 | * server 17 | *

18 | * It contains a list of IP addresses and ports of game servers matching the 19 | * requested criteria. 20 | * 21 | * @author Sebastian Staudt 22 | * @see com.github.koraktor.steamcondenser.servers.MasterServer#getServers 23 | */ 24 | public class M2A_SERVER_BATCH_Packet extends SteamPacket { 25 | 26 | private Vector serverArray; 27 | 28 | /** 29 | * Creates a new M2A_SERVER_BATCH response object based on the given data 30 | * 31 | * @param data The raw packet data replied from the server 32 | * @throws PacketFormatException if the packet data is not well formatted 33 | */ 34 | public M2A_SERVER_BATCH_Packet(byte[] data) 35 | throws PacketFormatException { 36 | super(SteamPacket.M2A_SERVER_BATCH_HEADER, data); 37 | 38 | if(this.contentData.getByte() != 0x0A) { 39 | throw new PacketFormatException("Master query response is missing additional 0x0A byte."); 40 | } 41 | 42 | int firstOctet, secondOctet, thirdOctet, fourthOctet, portNumber; 43 | this.serverArray = new Vector<>(); 44 | 45 | do { 46 | firstOctet = this.contentData.getByte() & 0xFF; 47 | secondOctet = this.contentData.getByte() & 0xFF; 48 | thirdOctet = this.contentData.getByte() & 0xFF; 49 | fourthOctet = this.contentData.getByte() & 0xFF; 50 | portNumber = this.contentData.getShort() & 0xFFFF; 51 | 52 | this.serverArray.add(firstOctet + "." + secondOctet + "." + thirdOctet + "." + fourthOctet + ":" + portNumber); 53 | } while(this.contentData.remaining() > 0); 54 | } 55 | 56 | /** 57 | * Returns the list of servers returned from the server in this packet 58 | * 59 | * @return An array of server addresses (i.e. IP addresses + port numbers) 60 | */ 61 | public Vector getServers() { 62 | return this.serverArray; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Steam Condenser 2 | =============== 3 | 4 | [![Build Status](https://secure.travis-ci.org/koraktor/steam-condenser-java.png)](http://travis-ci.org/koraktor/steam-condenser-java) 5 | 6 | The Steam Condenser is a multi-language library for querying the Steam 7 | Community, Source and GoldSrc game servers as well as the Steam master servers. 8 | Currently it is implemented in Java, PHP and Ruby. 9 | 10 | ## Requirements 11 | 12 | * Linux, MacOS X or Windows 13 | * Java 7 or newer 14 | 15 | The following Java libraries are required: 16 | 17 | * Apache Commons Compress (for Source servers sending compressed responses) 18 | * Apache Commons Lang 3 19 | * Apache Commons HttpClient (for the Web API features) 20 | * JSON (for the Web API features) 21 | * JUnit (for testing) 22 | * PowerMock (for testing) 23 | 24 | Maven will install these for you. 25 | 26 | ## Installation 27 | 28 | To install and use Steam Condenser in your Maven managed project use the 29 | following dependency definition: 30 | 31 | 32 | com.github.koraktor 33 | steam-condenser 34 | x.y.z 35 | 36 | 37 | Remember to specify a version using appropriate tag. 38 | 39 | ## Logging 40 | 41 | Steam Condenser provides logging based on [SLF4J][slf4j]. To make use of it you 42 | have to add a logger implementation (like slf4j-log4j) to your application's 43 | classpath. See [this list][loggers] for some available SLF4J loggers. 44 | 45 | ## License 46 | 47 | This code is free software; you can redistribute it and/or modify it under the 48 | terms of the new BSD License. A copy of this license can be found in the 49 | included LICENSE file. 50 | 51 | ## Credits 52 | 53 | * Sebastian Staudt – koraktor(at)gmail.com 54 | * David Wursteisen – david.wursteisen(at)gmail.com 55 | * Guto Maia – guto(at)guto.net 56 | * Sam Kinard – snkinard(at)gmail.com 57 | 58 | ## See Also 59 | 60 | * [Steam Condenser home](http://koraktor.de/steam-condenser) 61 | * [GitHub project page](https://github.com/koraktor/steam-condenser) 62 | * [Wiki](https://github.com/koraktor/steam-condenser/wiki) 63 | * [Google group](http://groups.google.com/group/steam-condenser) 64 | * [Ohloh profile](http://www.ohloh.net/projects/steam-condenser) 65 | 66 | Follow Steam Condenser on Google Plus+ via 67 | [+Steam Condenser](https://plus.google.com/b/109400543549250623875/109400543549250623875) 68 | or on Twitter via [@steamcondenser](https://twitter.com/steamcondenser). 69 | 70 | [loggers]: http://www.slf4j.org/manual.html#swapping 71 | [slf4j]: http://www.slf4j.org 72 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/portal2/Portal2Item.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.portal2; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import org.json.JSONException; 15 | import org.json.JSONObject; 16 | 17 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 18 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 19 | import com.github.koraktor.steamcondenser.community.GameItem; 20 | 21 | /** 22 | * Represents a Portal 2 item 23 | * 24 | * @author Sebastian Staudt 25 | */ 26 | public class Portal2Item extends GameItem { 27 | 28 | private static final String[] BOTS = { "pbody", "atlas" }; 29 | 30 | private Map equipped; 31 | 32 | /** 33 | * Creates a new instance of a Portal2Item with the given data 34 | * 35 | * @param inventory The inventory this item is contained in 36 | * @param itemData The data specifying this item 37 | * @throws JSONException on invalid JSON data 38 | * @throws WebApiException on Web API errors 39 | */ 40 | public Portal2Item(Portal2Inventory inventory, JSONObject itemData) 41 | throws JSONException, SteamCondenserException { 42 | super(inventory, itemData); 43 | 44 | this.equipped = new HashMap<>(); 45 | for(int botId = 0; botId < BOTS.length; botId++) { 46 | this.equipped.put(BOTS[botId], (itemData.getLong("inventory") & (1 << 16 + botId)) != 0); 47 | } 48 | } 49 | 50 | /** 51 | * Returns the name for each bot this player has equipped this item 52 | * 53 | * @return The names of the bots this player has equipped this item 54 | */ 55 | public List getBotsEquipped() { 56 | List botsEquipped = new ArrayList<>(); 57 | for(Map.Entry botEquipped : this.equipped.entrySet()) { 58 | if(botEquipped.getValue()) { 59 | botsEquipped.add(botEquipped.getKey()); 60 | } 61 | } 62 | 63 | return botsEquipped; 64 | } 65 | 66 | /** 67 | * Returns whether this item is equipped by this player at all 68 | * 69 | * @return Whether this item is equipped by this player at all 70 | */ 71 | public boolean isEquipped() { 72 | return this.equipped.containsValue(true); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Item.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import org.json.JSONException; 15 | import org.json.JSONObject; 16 | 17 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 18 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 19 | import com.github.koraktor.steamcondenser.community.GameItem; 20 | 21 | /** 22 | * Represents a Team Fortress 2 item 23 | * 24 | * @author Sebastian Staudt 25 | */ 26 | public class TF2Item extends GameItem { 27 | 28 | private static final String[] CLASSES = { 29 | "scout", "sniper", "soldier", "demoman", "medic", "heavy", "pyro", 30 | "spy" 31 | }; 32 | 33 | private Map equipped; 34 | 35 | /** 36 | * Creates a new instance of a TF2Item with the given data 37 | * 38 | * @param inventory The inventory this item is contained in 39 | * @param itemData The data specifying this item 40 | * @throws JSONException on invalid JSON data 41 | * @throws WebApiException on Web API errors 42 | */ 43 | public TF2Item(TF2Inventory inventory, JSONObject itemData) 44 | throws JSONException, SteamCondenserException { 45 | super(inventory, itemData); 46 | 47 | this.equipped = new HashMap<>(); 48 | for(int classId = 0; classId < CLASSES.length; classId++) { 49 | this.equipped.put(CLASSES[classId], (itemData.getLong("inventory") & (1 << 16 + classId)) != 0); 50 | } 51 | } 52 | 53 | /** 54 | * Returns the class names for each class this player has equipped this 55 | * item 56 | * 57 | * @return The names of the classes this player has equipped this item 58 | */ 59 | public List getClassesEquipped() { 60 | List classesEquipped = new ArrayList<>(); 61 | for(Map.Entry classEquipped : this.equipped.entrySet()) { 62 | if(classEquipped.getValue()) { 63 | classesEquipped.add(classEquipped.getKey()); 64 | } 65 | } 66 | 67 | return classesEquipped; 68 | } 69 | 70 | /** 71 | * Returns whether this item is equipped by this player at all 72 | * 73 | * @return true if the player has equipped this item at all 74 | */ 75 | public boolean isEquipped() { 76 | return this.equipped.containsValue(true); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/dods/DoDSWeapon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.dods; 9 | 10 | import com.github.koraktor.steamcondenser.community.GameWeapon; 11 | import com.github.koraktor.steamcondenser.community.XMLData; 12 | 13 | /** 14 | * Represents the stats for a Day of Defeat: Source weapon for a specific user 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class DoDSWeapon extends GameWeapon { 19 | 20 | private int headshots; 21 | 22 | private int hits; 23 | 24 | private String name; 25 | 26 | /** 27 | * Creates a new instance of a Day of Defeat: Source weapon based on the 28 | * given XML data 29 | * 30 | * @param weaponData The XML data of the class 31 | */ 32 | public DoDSWeapon(XMLData weaponData) { 33 | super(weaponData); 34 | 35 | this.headshots = weaponData.getInteger("headshots"); 36 | this.id = weaponData.getAttribute("key"); 37 | this.name = weaponData.getString("name"); 38 | this.shots = weaponData.getInteger("shotsfired"); 39 | this.hits = weaponData.getInteger("shotshit"); 40 | } 41 | 42 | /** 43 | * Returns the average number of hits needed for a kill with this weapon 44 | * 45 | * @return The average number of hits needed for a kill 46 | */ 47 | public float getAvgHitsPerKill() { 48 | return this.hits / this.kills; 49 | } 50 | 51 | /** 52 | * Returns the percentage of headshots relative to the shots hit with this 53 | * weapon 54 | * 55 | * @return The percentage of headshots 56 | */ 57 | public float getHeadshotPercentage() { 58 | return this.headshots / this.hits; 59 | } 60 | 61 | /** 62 | * Returns the number of headshots achieved with this weapon 63 | * 64 | * @return The number of headshots achieved 65 | */ 66 | public int getHeadshots() { 67 | return this.headshots; 68 | } 69 | 70 | /** 71 | * Returns the percentage of hits relative to the shots fired with this 72 | * weapon 73 | * 74 | * @return The percentage of hits 75 | */ 76 | public float getHitPercentage() { 77 | return this.hits / this.shots; 78 | } 79 | 80 | /** 81 | * Returns the number of hits achieved with this weapon 82 | * 83 | * @return The number of hits achieved 84 | */ 85 | public int getHits() { 86 | return this.hits; 87 | } 88 | 89 | /** 90 | * Returns the name of this weapon 91 | * 92 | * @return The name of this weapon 93 | */ 94 | public String getName() { 95 | return this.name; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/css/CSSMap.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.css; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * Represents the stats for a Counter-Strike: Source map for a specific user 14 | * 15 | * @author Sebastian Staudt 16 | */ 17 | public class CSSMap { 18 | 19 | private boolean favorite; 20 | 21 | private String name; 22 | 23 | private int roundsPlayed; 24 | 25 | private int roundsLost; 26 | 27 | private int roundsWon; 28 | 29 | /** 30 | * Creates a new instance of a Counter-Strike: Source class based on the 31 | * given XML data 32 | * 33 | * @param mapName The name of the map 34 | * @param mapsData The XML data of all maps 35 | */ 36 | public CSSMap(String mapName, XMLData mapsData) { 37 | this.name = mapName; 38 | 39 | this.favorite = mapsData.getString("favorite").equals(this.name); 40 | this.roundsPlayed = mapsData.getInteger(this.name + "_rounds"); 41 | this.roundsWon = mapsData.getInteger(this.name + "_wins"); 42 | this.roundsLost = this.roundsPlayed - this.roundsWon; 43 | } 44 | 45 | /** 46 | * Returns whether this map is the favorite map of this player 47 | * 48 | * @return true if this is the favorite map 49 | */ 50 | public boolean isFavorite() { 51 | return this.favorite; 52 | } 53 | 54 | /** 55 | * Returns the name of this map 56 | * 57 | * @return The name of this map 58 | */ 59 | public String getName() { 60 | return this.name; 61 | } 62 | 63 | /** 64 | * Returns the number of rounds the player has lost on this map 65 | * 66 | * @return The number of rounds lost 67 | */ 68 | public int getRoundsLost() { 69 | return this.roundsLost; 70 | } 71 | 72 | /** 73 | * Returns the number of rounds the player has played on this map 74 | * 75 | * @return The number of rounds played 76 | */ 77 | public int getRoundsPlayed() { 78 | return this.roundsPlayed; 79 | } 80 | 81 | /** 82 | * Returns the number of rounds the player has won on this map 83 | * 84 | * @return The number of rounds won 85 | */ 86 | public int getRoundsWon() { 87 | return this.roundsWon; 88 | } 89 | 90 | /** 91 | * Returns the percentage of rounds the player has won on this map 92 | * 93 | * @return The percentage of rounds won 94 | */ 95 | public float getRoundsWonPercentage() { 96 | return (this.roundsPlayed > 0) ? (this.roundsWon / this.roundsPlayed) : 0; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/l4d/AbtractL4DWeapon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.l4d; 9 | 10 | import com.github.koraktor.steamcondenser.community.GameWeapon; 11 | import com.github.koraktor.steamcondenser.community.XMLData; 12 | 13 | /** 14 | * This abstract class is a base class for weapons in Left4Dead and Left4Dead 2 15 | * as the weapon stats for both games are very similar 16 | * 17 | * @author Sebastian Staudt 18 | */ 19 | public abstract class AbtractL4DWeapon extends GameWeapon { 20 | 21 | protected float accuracy; 22 | 23 | protected float headshotPercentage; 24 | 25 | protected float killPercentage; 26 | 27 | protected String name; 28 | 29 | protected int shots; 30 | 31 | /** 32 | * Creates a new instance of weapon from the given XML data and parses 33 | * common data for both, L4DWeapon and L4D2Weapon 34 | * 35 | * @param weaponData The XML data for this weapon 36 | */ 37 | public AbtractL4DWeapon(XMLData weaponData) { 38 | super(weaponData); 39 | 40 | this.accuracy = Float.parseFloat(weaponData 41 | .getString("accuracy").replace("%", "")) * 0.01f; 42 | this.headshotPercentage = Float.parseFloat(weaponData 43 | .getString("headshots").replace("%", "")) * 0.01f; 44 | this.name = weaponData.getName(); 45 | this.shots = weaponData.getInteger("shots"); 46 | } 47 | 48 | /** 49 | * Returns the overall accuracy of the player with this weapon 50 | * 51 | * @return The accuracy of the player with this weapon 52 | */ 53 | public float getAccuracy() { 54 | return this.accuracy; 55 | } 56 | 57 | /** 58 | * Returns the percentage of kills with this weapon that have been 59 | * headshots 60 | * 61 | * @return The percentage of headshots with this weapon 62 | */ 63 | public float getHeadshotPercentage() { 64 | return this.headshotPercentage; 65 | } 66 | 67 | /** 68 | * Returns the percentage of overall kills of the player that have been 69 | * achieved with this weapon 70 | * 71 | * @return The percentage of kills with this weapon 72 | */ 73 | public float getKillPercentage() { 74 | return this.killPercentage; 75 | } 76 | 77 | /** 78 | * Returns the name of the weapon 79 | * 80 | * @return The name of the weapon 81 | */ 82 | public String getName() { 83 | return this.name; 84 | } 85 | 86 | /** 87 | * Returns the number of shots the player has fired with this weapon 88 | * 89 | * @return The number of shots with this weapon 90 | */ 91 | public int getShots() { 92 | return this.shots; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/dods/DoDSStats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.dods; 9 | 10 | import java.util.HashMap; 11 | 12 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 13 | import com.github.koraktor.steamcondenser.community.GameStats; 14 | import com.github.koraktor.steamcondenser.community.XMLData; 15 | 16 | /** 17 | * The is class represents the game statistics for a single user in Day of 18 | * Defeat: Source 19 | * 20 | * @author Sebastian Staudt 21 | */ 22 | public class DoDSStats extends GameStats { 23 | 24 | private HashMap classStats; 25 | private HashMap weaponStats; 26 | 27 | /** 28 | * Creates a DoDSStats instance by calling the super 29 | * constructor with the game name "DoD:S" 30 | * 31 | * @param steamId The custom URL or 64bit Steam ID of the user 32 | * @throws SteamCondenserException if an error occurs while fetching the 33 | * stats data 34 | */ 35 | public DoDSStats(Object steamId) throws SteamCondenserException { 36 | super(steamId, "dod:s"); 37 | } 38 | 39 | /** 40 | * Returns a map of DoDSClass for this user containing all 41 | * DoD:S classes. 42 | *

43 | * If the classes haven't been parsed already, parsing is done now. 44 | * 45 | * @return The class statistics for this user 46 | */ 47 | public HashMap getClassStats() { 48 | if(!this.isPublic()) { 49 | return null; 50 | } 51 | 52 | if(this.classStats == null) { 53 | this.classStats = new HashMap<>(); 54 | for(XMLData classData : this.xmlData.getElements("classes", "class")) { 55 | this.classStats.put(classData.getAttribute("key"), 56 | new DoDSClass(classData)); 57 | } 58 | } 59 | 60 | return this.classStats; 61 | } 62 | 63 | /** 64 | * Returns a map of DoDSWeapon for this user containing all 65 | * DoD:S weapons. 66 | *

67 | * If the weapons haven't been parsed already, parsing is done now. 68 | * 69 | * @return The weapon statistics for this user 70 | */ 71 | public HashMap getWeaponStats() { 72 | if(!this.isPublic()) { 73 | return null; 74 | } 75 | 76 | if(this.weaponStats == null) { 77 | this.weaponStats = new HashMap<>(); 78 | for(XMLData weaponData : this.xmlData.getChildren("weapons")) { 79 | this.weaponStats.put(weaponData.getAttribute("key"), 80 | new DoDSWeapon(weaponData)); 81 | } 82 | } 83 | 84 | return this.weaponStats; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/SteamPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import com.github.koraktor.steamcondenser.PacketBuffer; 11 | 12 | /** 13 | * This module implements the basic functionality used by most of the packets 14 | * used in communication with master, Source or GoldSrc servers. 15 | * 16 | * @author Sebastian Staudt 17 | * @see SteamPacketFactory 18 | */ 19 | abstract public class SteamPacket { 20 | 21 | public static final byte A2S_INFO_HEADER = 0x54; 22 | public static final byte S2A_INFO2_HEADER = 0x49; 23 | public static final byte S2A_INFO_DETAILED_HEADER = 0x6D; 24 | public static final byte A2S_PLAYER_HEADER = 0x55; 25 | public static final byte S2A_PLAYER_HEADER = 0x44; 26 | public static final byte A2S_RULES_HEADER = 0x56; 27 | public static final byte S2A_RULES_HEADER = 0x45; 28 | public static final byte A2S_SERVERQUERY_GETCHALLENGE_HEADER = 0x57; 29 | public static final byte S2C_CHALLENGE_HEADER = 0x41; 30 | public static final byte A2M_GET_SERVERS_BATCH2_HEADER = 0x31; 31 | public static final byte M2A_SERVER_BATCH_HEADER = 0x66; 32 | public static final byte RCON_GOLDSRC_CHALLENGE_HEADER = 0x63; 33 | public static final byte RCON_GOLDSRC_NO_CHALLENGE_HEADER = 0x39; 34 | public static final byte RCON_GOLDSRC_RESPONSE_HEADER = 0x6c; 35 | 36 | /** 37 | * This variable stores the content of the package 38 | */ 39 | protected PacketBuffer contentData; 40 | 41 | /** 42 | * This byte stores the type of the packet 43 | */ 44 | protected byte headerData; 45 | 46 | 47 | /** 48 | * Creates a new packet object based on the given data 49 | * 50 | * @param headerData The packet header 51 | */ 52 | protected SteamPacket(byte headerData) { 53 | this(headerData, new byte[0]); 54 | } 55 | 56 | /** 57 | * Creates a new packet object based on the given data 58 | * 59 | * @param headerData The packet header 60 | * @param contentBytes The raw data of the packet 61 | */ 62 | protected SteamPacket(byte headerData, byte[] contentBytes) { 63 | this.contentData = new PacketBuffer(contentBytes); 64 | this.headerData = headerData; 65 | } 66 | 67 | /** 68 | * Returns the raw data representing this packet 69 | * 70 | * @return A byte array containing the raw data of this request packet 71 | */ 72 | public byte[] getBytes() { 73 | byte[] bytes = new byte[this.contentData.getLength() + 5]; 74 | bytes[0] = (byte) 0xFF; 75 | bytes[1] = (byte) 0xFF; 76 | bytes[2] = (byte) 0xFF; 77 | bytes[3] = (byte) 0xFF; 78 | bytes[4] = this.headerData; 79 | System.arraycopy(this.contentData.array(), 0, bytes, 5, bytes.length - 5); 80 | return bytes; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/l4d/L4DMap.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.l4d; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * This class holds statistical information about a map played by a player in 14 | * Survival mode of Left4Dead 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class L4DMap { 19 | 20 | public static int GOLD = 1; 21 | public static int SILVER = 2; 22 | public static int BRONZE = 3; 23 | public static int NONE = 0; 24 | 25 | protected float bestTime; 26 | 27 | protected String id; 28 | 29 | protected int medal; 30 | 31 | protected String name; 32 | 33 | private int timesPlayed; 34 | 35 | public L4DMap() {} 36 | 37 | /** 38 | * Creates a new instance of a Left4Dead Survival map based on the given 39 | * XML data 40 | * 41 | * @param mapData The XML data for this map 42 | */ 43 | public L4DMap(XMLData mapData) { 44 | this.bestTime = mapData.getFloat("besttimeseconds"); 45 | this.id = mapData.getName(); 46 | this.name = mapData.getString("name"); 47 | this.timesPlayed = mapData.getInteger("timesplayed"); 48 | 49 | String medal = mapData.getString("medal"); 50 | if(medal.equals("gold")) { 51 | this.medal = GOLD; 52 | } else if(medal.equals("silver")) { 53 | this.medal = SILVER; 54 | } else if(medal.equals("bronze")) { 55 | this.medal = BRONZE; 56 | } else { 57 | this.medal = NONE; 58 | } 59 | } 60 | 61 | /** 62 | * Returns the best survival time of this player on this map 63 | * 64 | * @return The best survival time of this player on this map 65 | */ 66 | public float getBestTime() { 67 | return this.bestTime; 68 | } 69 | 70 | /** 71 | * Returns the ID of this map 72 | * 73 | * @return The ID of this map 74 | */ 75 | public String getId() { 76 | return this.id; 77 | } 78 | 79 | /** 80 | * Returns the highest medal this player has won on this map 81 | * 82 | * @return The highest medal won by this player on this map 83 | */ 84 | public int getMedal() { 85 | return this.medal; 86 | } 87 | 88 | /** 89 | * Returns the name of the map 90 | * 91 | * @return The name of the map 92 | */ 93 | public String getName() { 94 | return this.name; 95 | } 96 | 97 | /** 98 | * Returns the number of times this map has been played by this player 99 | * 100 | * @return The number of times this map has been played 101 | */ 102 | public int getTimesPlayed() { 103 | return this.timesPlayed; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/S2A_INFO_DETAILED_Packet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import java.util.HashMap; 11 | 12 | /** 13 | * This class represents a S2A_INFO_DETAILED response packet sent by a GoldSrc 14 | * server 15 | * 16 | * @author Sebastian Staudt 17 | * @deprecated Only outdated GoldSrc servers (before 10/24/2008) use this 18 | * format. Newer ones use the same format as Source servers now 19 | * (see {@link S2A_INFO2_Packet}). 20 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updateServerInfo 21 | */ 22 | public class S2A_INFO_DETAILED_Packet extends S2A_INFO_BasePacket { 23 | 24 | /** 25 | * Creates a new S2A_INFO_DETAILED response object based on the given data 26 | * 27 | * @param dataBytes The raw packet data replied from the server 28 | */ 29 | public S2A_INFO_DETAILED_Packet(byte[] dataBytes) { 30 | super(SteamPacket.S2A_INFO_DETAILED_HEADER, dataBytes); 31 | 32 | this.info.put("serverIp", this.contentData.getString()); 33 | this.info.put("serverName", this.contentData.getString()); 34 | this.info.put("mapName", this.contentData.getString()); 35 | this.info.put("gameDir", this.contentData.getString()); 36 | this.info.put("gameDescription", this.contentData.getString()); 37 | this.info.put("numberOfPlayers", this.contentData.getByte()); 38 | this.info.put("maxPlayers", this.contentData.getByte()); 39 | this.info.put("networkVersion", this.contentData.getByte()); 40 | this.info.put("dedicated", this.contentData.getByte()); 41 | this.info.put("operatingSystem", this.contentData.getByte()); 42 | this.info.put("passwordProtected", this.contentData.getByte() == 1); 43 | boolean isMod = this.contentData.getByte() == 1; 44 | this.info.put("isMod", isMod); 45 | 46 | if(isMod) { 47 | HashMap modInfo = new HashMap<>(6); 48 | modInfo.put("urlInfo", this.contentData.getString()); 49 | modInfo.put("urlDl", this.contentData.getString()); 50 | this.contentData.getByte(); 51 | if(this.contentData.remaining() == 12) { 52 | modInfo.put("modVersion", Integer.reverseBytes(this.contentData.getInt())); 53 | modInfo.put("modSize", Integer.reverseBytes(this.contentData.getInt())); 54 | modInfo.put("svOnly", this.contentData.getByte() == 1); 55 | modInfo.put("clDll", this.contentData.getByte() == 1); 56 | this.info.put("secure", this.contentData.getByte() == 1); 57 | this.info.put("numberOfBots", this.contentData.getByte()); 58 | } 59 | this.info.put("modInfo", modInfo); 60 | } else { 61 | this.info.put("secure", this.contentData.getByte() == 1); 62 | this.info.put("numberOfBots", this.contentData.getByte()); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/A2M_GET_SERVERS_BATCH2_Packet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | import com.github.koraktor.steamcondenser.servers.MasterServer; 11 | 12 | /** 13 | * This packet class represents a A2M_GET_SERVERS_BATCH2 request sent to a 14 | * master server 15 | *

16 | * It is used to receive a list of game servers matching the specified filters. 17 | *

18 | * Filtering: 19 | *

20 | * Instead of filtering the results sent by the master server locally, you 21 | * should at least use the following filters to narrow down the results sent by 22 | * the master server. Receiving all servers from the master server is taking 23 | * quite some time. 24 | *

25 | * Available filters: 26 | *

36 | * 37 | * @author Sebastian Staudt 38 | * @see MasterServer#getServers(byte, String) 39 | */ 40 | public class A2M_GET_SERVERS_BATCH2_Packet extends SteamPacket { 41 | 42 | private String filter; 43 | private byte regionCode; 44 | private String startIp; 45 | 46 | /** 47 | * Creates a new A2M_GET_SERVERS_BATCH2 request object, filtering by the 48 | * given paramters 49 | * 50 | * @param regionCode The region code to filter servers by region. 51 | * @param startIp This should be the last IP received from the master 52 | * server or 0.0.0.0 53 | * @param filter The filters to apply in the form ("\filtername\value...") 54 | */ 55 | public A2M_GET_SERVERS_BATCH2_Packet(byte regionCode, String startIp, String filter) { 56 | super(SteamPacket.A2M_GET_SERVERS_BATCH2_HEADER); 57 | 58 | this.filter = filter; 59 | this.regionCode = regionCode; 60 | this.startIp = startIp; 61 | } 62 | 63 | /** 64 | * Returns the raw data representing this packet 65 | * 66 | * @return A byte array containing the raw data of this request packet 67 | */ 68 | @Override 69 | public byte[] getBytes() { 70 | byte[] bytes, filterBytes, startIpBytes; 71 | 72 | filterBytes = (this.filter + "\0").getBytes(); 73 | startIpBytes = (this.startIp + "\0").getBytes(); 74 | bytes = new byte[2 + startIpBytes.length + filterBytes.length]; 75 | 76 | bytes[0] = this.headerData; 77 | bytes[1] = this.regionCode; 78 | System.arraycopy(startIpBytes, 0, bytes, 2, startIpBytes.length); 79 | System.arraycopy(filterBytes, 0, bytes, startIpBytes.length + 2, filterBytes.length); 80 | 81 | return bytes; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/rcon/RCONPacket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets.rcon; 9 | 10 | import com.github.koraktor.steamcondenser.Helper; 11 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 12 | 13 | /** 14 | * This module is included by all classes representing a packet used by 15 | * Source's RCON protocol 16 | *

17 | * It provides a basic implementation for initializing and serializing such a 18 | * packet. 19 | * 20 | * @author Sebastian Staudt 21 | * @see RCONPacketFactory 22 | */ 23 | abstract public class RCONPacket extends SteamPacket { 24 | 25 | /** 26 | * Header for authentication requests 27 | */ 28 | public static final byte SERVERDATA_AUTH = 3; 29 | 30 | /** 31 | * Header for replies to authentication attempts 32 | */ 33 | public static final byte SERVERDATA_AUTH_RESPONSE = 2; 34 | 35 | /** 36 | * Header for command execution requests 37 | */ 38 | public static final byte SERVERDATA_EXECCOMMAND = 2; 39 | 40 | /** 41 | * Header for packets with the output of a command execution 42 | */ 43 | public static final byte SERVERDATA_RESPONSE_VALUE = 0; 44 | 45 | /** 46 | * The packet header specifying the packet type 47 | */ 48 | protected int header; 49 | 50 | /** 51 | * The request ID used to identify the RCON communication 52 | */ 53 | protected int requestId; 54 | 55 | /** 56 | * Creates a new RCON packet object with the given request ID, type and 57 | * content data 58 | * 59 | * @param requestId The request ID for the current RCON communication 60 | * @param rconHeader The header for the packet type 61 | * @param rconData The raw packet data 62 | */ 63 | protected RCONPacket(int requestId, int rconHeader, String rconData) { 64 | super((byte) 0, (rconData + "\0\0").getBytes()); 65 | 66 | this.header = rconHeader; 67 | this.requestId = requestId; 68 | } 69 | 70 | /** 71 | * Returns the raw data representing this packet 72 | * 73 | * @return A byte array containing the raw data of this RCON packet 74 | */ 75 | public byte[] getBytes() { 76 | byte[] bytes = new byte[this.contentData.getLength() + 12]; 77 | 78 | System.arraycopy(Helper.byteArrayFromInteger(Integer.reverseBytes(bytes.length - 4)), 0, bytes, 0, 4); 79 | System.arraycopy(Helper.byteArrayFromInteger(Integer.reverseBytes(this.requestId)), 0, bytes, 4, 4); 80 | System.arraycopy(Helper.byteArrayFromInteger(Integer.reverseBytes(this.header)), 0, bytes, 8, 4); 81 | System.arraycopy(this.contentData.array(), 0, bytes, 12, bytes.length - 12); 82 | 83 | return bytes; 84 | } 85 | 86 | /** 87 | * Returns the request ID used to identify the RCON communication 88 | * 89 | * @return The request ID used to identify the RCON communication 90 | */ 91 | public int getRequestId() { 92 | return this.requestId; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/servers/sockets/MasterServerSocketTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.net.InetAddress; 11 | import java.nio.ByteBuffer; 12 | 13 | import org.junit.Before; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.junit.rules.ExpectedException; 17 | import org.junit.runner.RunWith; 18 | 19 | import org.mockito.invocation.InvocationOnMock; 20 | import org.mockito.stubbing.Answer; 21 | import org.powermock.core.classloader.annotations.PrepareForTest; 22 | import org.powermock.modules.junit4.PowerMockRunner; 23 | 24 | import com.github.koraktor.steamcondenser.exceptions.PacketFormatException; 25 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 26 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacketFactory; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.mockito.Mockito.doReturn; 30 | import static org.mockito.Mockito.mock; 31 | import static org.mockito.Mockito.spy; 32 | import static org.powermock.api.mockito.PowerMockito.doAnswer; 33 | import static org.powermock.api.mockito.PowerMockito.mockStatic; 34 | import static org.powermock.api.mockito.PowerMockito.when; 35 | 36 | /** 37 | * @author Sebastian Staudt 38 | */ 39 | @RunWith(PowerMockRunner.class) 40 | public class MasterServerSocketTest { 41 | 42 | @Rule 43 | public ExpectedException exception = ExpectedException.none(); 44 | 45 | private MasterServerSocket socket; 46 | 47 | @Before 48 | public void setup() throws Exception { 49 | this.socket = new MasterServerSocket(InetAddress.getLocalHost(), 27015); 50 | } 51 | 52 | @Test 53 | public void testIncorrectPacket() throws Exception { 54 | this.exception.expect(PacketFormatException.class); 55 | this.exception.expectMessage("Master query response has wrong packet header."); 56 | 57 | MasterServerSocket socket = spy(this.socket); 58 | doReturn(1).when(socket).receivePacket(1500); 59 | socket.buffer = mock(ByteBuffer.class); 60 | when(socket.buffer.getInt()).thenReturn(1); 61 | 62 | socket.getReply(); 63 | } 64 | 65 | @Test 66 | @PrepareForTest(SteamPacketFactory.class) 67 | public void testCorrectPacket() throws Exception { 68 | SteamPacket packet = mock(SteamPacket.class); 69 | mockStatic(SteamPacketFactory.class); 70 | when(SteamPacketFactory.getPacketFromData("test".getBytes())).thenReturn(packet); 71 | 72 | MasterServerSocket socket = spy(this.socket); 73 | doAnswer(new Answer() { 74 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 75 | MasterServerSocket socket = (MasterServerSocket) invocationOnMock.getMock(); 76 | socket.buffer = ByteBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 't', 'e', 's', 't' }); 77 | return 8; 78 | } 79 | }).when(socket).receivePacket(1500); 80 | 81 | assertEquals(packet, socket.getReply()); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/css/CSSWeapon.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.css; 9 | 10 | import com.github.koraktor.steamcondenser.community.XMLData; 11 | 12 | /** 13 | * Represents the stats for a Counter-Strike: Source weapon for a specific user 14 | * 15 | * @author Sebastian Staudt 16 | */ 17 | public class CSSWeapon { 18 | 19 | private boolean favorite; 20 | 21 | private int hits; 22 | 23 | private int kills; 24 | 25 | private String name; 26 | 27 | private int shots; 28 | 29 | /** 30 | * Creates a new instance of a Counter-Strike: Source weapon based on the 31 | * given XML data 32 | * 33 | * @param weaponName The name of the weapon 34 | * @param weaponsData The XML data of all weapons 35 | */ 36 | public CSSWeapon(String weaponName, XMLData weaponsData) { 37 | this.name = weaponName; 38 | 39 | this.favorite = weaponsData.getString("favorite").equals(this.name); 40 | this.kills = weaponsData.getInteger(this.name + "_kills"); 41 | 42 | if(!this.name.equals("grenade") && !this.name.equals("knife")) { 43 | this.hits = weaponsData.getInteger(this.name + "_hits"); 44 | this.shots = weaponsData.getInteger(this.name + "_shots"); 45 | } 46 | } 47 | 48 | /** 49 | * Returns whether this weapon is the favorite weapon of this player 50 | * 51 | * @return true if this is the favorite weapon 52 | */ 53 | public boolean isFavorite() { 54 | return this.favorite; 55 | } 56 | 57 | /** 58 | * Returns the accuracy of this player with this weapon 59 | * 60 | * @return The accuracy with this weapon 61 | */ 62 | public float getAccuracy() { 63 | return (this.shots > 0) ? this.hits / this.shots : 0; 64 | } 65 | 66 | /** 67 | * Returns the number of hits achieved with this weapon 68 | * 69 | * @return The number of hits achieved 70 | */ 71 | public int getHits() { 72 | return this.hits; 73 | } 74 | 75 | /** 76 | * Returns the number of kills achieved with this weapon 77 | * 78 | * @return The number of kills achieved 79 | */ 80 | public int getKills() { 81 | return this.kills; 82 | } 83 | 84 | /** 85 | * Returns the kill-shot-ratio of this player with this weapon 86 | * 87 | * @return The kill-shot-ratio 88 | */ 89 | public float getKsRatio() { 90 | return (this.shots > 0) ? this.kills / this.shots : 0; 91 | } 92 | 93 | /** 94 | * Returns the name of this weapon 95 | * 96 | * @return The name of this weapon 97 | */ 98 | public String getName() { 99 | return this.name; 100 | } 101 | 102 | /** 103 | * Returns the number of shots fired with this weapon 104 | * 105 | * @return The number of shots fired 106 | */ 107 | public int getShots() { 108 | return this.shots; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/l4d/L4DStats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.l4d; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 14 | import com.github.koraktor.steamcondenser.community.GameWeapon; 15 | import com.github.koraktor.steamcondenser.community.XMLData; 16 | 17 | /** 18 | * This class represents the game statistics for a single user in Left4Dead 19 | * 20 | * @author Sebastian Staudt 21 | */ 22 | public class L4DStats extends AbstractL4DStats { 23 | 24 | /** 25 | * Creates a L4DStats object by calling the super constructor 26 | * with the game name "l4d" 27 | * 28 | * @param steamId The custom URL or 64bit Steam ID of the user 29 | * @throws SteamCondenserException if an error occurs while fetching the 30 | * stats data 31 | */ 32 | public L4DStats(Object steamId) throws SteamCondenserException { 33 | super(steamId, "l4d"); 34 | } 35 | 36 | /** 37 | * Returns a map of Survival statistics for this user like revived 38 | * teammates 39 | *

40 | * If the Survival statistics haven't been parsed already, parsing is done 41 | * now. 42 | * 43 | * @return The stats for the Survival mode 44 | */ 45 | public Map getSurvivalStats() 46 | throws SteamCondenserException { 47 | if(!this.isPublic()) { 48 | return null; 49 | } 50 | 51 | if(this.survivalStats == null) { 52 | super.getSurvivalStats(); 53 | HashMap mapsHash = new HashMap<>(); 54 | for(XMLData mapData : this.xmlData.getElements("stats", "survival", "maps")) { 55 | mapsHash.put(mapData.getName(), new L4DMap(mapData)); 56 | } 57 | this.survivalStats.put("maps", mapsHash); 58 | } 59 | 60 | return this.survivalStats; 61 | } 62 | 63 | /** 64 | * Returns a map of L4DWeapon for this user containing all 65 | * Left4Dead weapons 66 | *

67 | * If the weapons haven't been parsed already, parsing is done now. 68 | * 69 | * @return The weapon statistics 70 | */ 71 | public Map getWeaponStats() { 72 | if(!this.isPublic()) { 73 | return null; 74 | } 75 | 76 | if(this.weaponStats == null) { 77 | this.weaponStats = new HashMap<>(); 78 | for(XMLData weaponData : this.xmlData.getChildren("stats", "weapons")) { 79 | String weaponName = weaponData.getName(); 80 | GameWeapon weapon; 81 | if(!weaponName.equals("molotov") && !weaponName.equals("pipes")) { 82 | weapon = new L4DWeapon(weaponData); 83 | } 84 | else { 85 | weapon = new L4DExplosive(weaponData); 86 | } 87 | this.weaponStats.put(weaponName, weapon); 88 | } 89 | } 90 | 91 | return this.weaponStats; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/sockets/QuerySocket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.io.IOException; 11 | import java.net.InetAddress; 12 | import java.nio.ByteBuffer; 13 | import java.nio.channels.DatagramChannel; 14 | import java.util.concurrent.TimeoutException; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 19 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 20 | 21 | /** 22 | * This class implements basic functionality for UDP based sockets 23 | * 24 | * @author Sebastian Staudt 25 | */ 26 | public abstract class QuerySocket extends SteamSocket { 27 | 28 | protected static final Logger LOG = LoggerFactory.getLogger(QuerySocket.class); 29 | 30 | /** 31 | * Creates a new socket to communicate with the server on the given IP 32 | * address and port 33 | * 34 | * @param ipAddress Either the IP address or the DNS name of the server 35 | * @param portNumber The port the server is listening on 36 | * @throws SteamCondenserException if the socket cannot be opened 37 | */ 38 | protected QuerySocket(InetAddress ipAddress, int portNumber) 39 | throws SteamCondenserException { 40 | super(ipAddress, portNumber); 41 | 42 | try { 43 | this.channel = DatagramChannel.open(); 44 | this.channel.configureBlocking(false); 45 | ((DatagramChannel) this.channel).connect(this.remoteSocket); 46 | } catch(IOException e) { 47 | throw new SteamCondenserException(e.getMessage(), e); 48 | } 49 | } 50 | 51 | /** 52 | * Returns whether a packet in the buffer is split 53 | * 54 | * @return true if the packet is split 55 | */ 56 | protected boolean packetIsSplit() { 57 | return (Integer.reverseBytes(this.buffer.getInt()) == 0xFFFFFFFE); 58 | } 59 | 60 | /** 61 | * Reads an UDP packet into the buffer 62 | * 63 | * @return The number of bytes received 64 | * @throws SteamCondenserException if an error occurs while reading from 65 | * the socket 66 | * @throws TimeoutException if no UDP packet was received 67 | */ 68 | protected int receivePacket() 69 | throws SteamCondenserException, TimeoutException { 70 | return this.receivePacket(0); 71 | } 72 | 73 | /** 74 | * Sends the given packet to the server 75 | * 76 | * @param dataPacket The packet to send to the server 77 | * @throws SteamCondenserException if an error occurs while writing to the 78 | * socket 79 | */ 80 | public void send(SteamPacket dataPacket) 81 | throws SteamCondenserException { 82 | LOG.info("Sending data packet of type \"" + dataPacket.getClass().getSimpleName() + "\""); 83 | 84 | try { 85 | this.buffer = ByteBuffer.wrap(dataPacket.getBytes()); 86 | ((DatagramChannel) this.channel).send(this.buffer, this.remoteSocket); 87 | this.buffer.flip(); 88 | } catch(IOException e) { 89 | throw new SteamCondenserException(e.getMessage(), e); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/packets/S2A_INFO2_Packet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.packets; 9 | 10 | /** 11 | * This class represents a S2A_INFO_DETAILED response packet sent by a Source 12 | * or GoldSrc server 13 | *

14 | * Out-of-date (before 10/24/2008) GoldSrc servers use an older format (see 15 | * {@link S2A_INFO_DETAILED_Packet}). 16 | * 17 | * @author Sebastian Staudt 18 | * @see com.github.koraktor.steamcondenser.servers.GameServer#updateServerInfo 19 | */ 20 | public class S2A_INFO2_Packet extends S2A_INFO_BasePacket { 21 | 22 | private static byte EDF_GAME_ID = (byte) 0x01; 23 | private static byte EDF_GAME_PORT = (byte) 0x80; 24 | private static byte EDF_SERVER_ID = (byte) 0x10; 25 | private static byte EDF_SERVER_TAGS = (byte) 0x20; 26 | private static byte EDF_SOURCE_TV = (byte) 0x40; 27 | 28 | /** 29 | * Creates a new S2A_INFO2 response object based on the given data 30 | * 31 | * @param dataBytes The raw packet data replied from the server 32 | */ 33 | public S2A_INFO2_Packet(byte[] dataBytes) { 34 | super(SteamPacket.S2A_INFO2_HEADER, dataBytes); 35 | 36 | this.info.put("networkVersion", this.contentData.getByte()); 37 | this.info.put("serverName", this.contentData.getString()); 38 | this.info.put("mapName", this.contentData.getString()); 39 | this.info.put("gameDir", this.contentData.getString()); 40 | this.info.put("gameDescription", this.contentData.getString()); 41 | this.info.put("appId", Short.reverseBytes(this.contentData.getShort())); 42 | this.info.put("numberOfPlayers", this.contentData.getByte()); 43 | this.info.put("maxPlayers", this.contentData.getByte()); 44 | this.info.put("numberOfBots", this.contentData.getByte()); 45 | this.info.put("dedicated", this.contentData.getByte()); 46 | this.info.put("operatingSystem", this.contentData.getByte()); 47 | this.info.put("passwordProtected", this.contentData.getByte() == 1); 48 | this.info.put("secure", this.contentData.getByte() == 1); 49 | this.info.put("gameVersion", this.contentData.getString()); 50 | 51 | if(this.contentData.remaining() > 0) { 52 | byte extraDataFlag = this.contentData.getByte(); 53 | 54 | if ((extraDataFlag & EDF_GAME_PORT) != 0) { 55 | this.info.put("serverPort", Short.reverseBytes(this.contentData.getShort())); 56 | } 57 | 58 | if ((extraDataFlag & EDF_SERVER_ID) != 0) { 59 | this.info.put("serverId", Long.reverseBytes((this.contentData.getInt() << 32) | this.contentData.getInt())); 60 | } 61 | 62 | if ((extraDataFlag & EDF_SOURCE_TV) != 0) { 63 | this.info.put("tvPort", Short.reverseBytes(this.contentData.getShort())); 64 | this.info.put("tvName", this.contentData.getString()); 65 | } 66 | 67 | if ((extraDataFlag & EDF_SERVER_TAGS) != 0) { 68 | this.info.put("serverTags", this.contentData.getString()); 69 | } 70 | 71 | if ((extraDataFlag & EDF_GAME_ID) != 0) { 72 | this.info.put("gameId", Long.reverseBytes((this.contentData.getInt() << 32) | this.contentData.getInt())); 73 | } 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/servers/sockets/QuerySocketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2020, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.net.InetAddress; 11 | import java.net.InetSocketAddress; 12 | import java.net.UnknownHostException; 13 | import java.nio.ByteBuffer; 14 | import java.nio.channels.DatagramChannel; 15 | import java.util.Arrays; 16 | 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | 20 | import org.mockito.ArgumentMatcher; 21 | 22 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 23 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | import static org.mockito.ArgumentMatchers.argThat; 27 | import static org.mockito.Mockito.doReturn; 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.spy; 30 | import static org.mockito.Mockito.verify; 31 | import static org.mockito.Mockito.when; 32 | 33 | /** 34 | * @author Sebastian Staudt 35 | */ 36 | public class QuerySocketTest { 37 | 38 | private DatagramChannel channel; 39 | 40 | private QuerySocket socket; 41 | 42 | @Before 43 | public void setup() throws Exception { 44 | this.socket = new GenericQuerySocket(); 45 | 46 | this.channel = mock(DatagramChannel.class); 47 | this.socket.channel = this.channel; 48 | } 49 | 50 | @Test 51 | public void testPacketIsSplit() { 52 | this.socket.buffer = mock(ByteBuffer.class); 53 | when(this.socket.buffer.getInt()).thenReturn(0xFFFFFFFF).thenReturn(0xFEFFFFFF); 54 | 55 | assertEquals(false, this.socket.packetIsSplit()); 56 | assertEquals(true, this.socket.packetIsSplit()); 57 | } 58 | 59 | @Test 60 | public void testReceivePacket() throws Exception { 61 | QuerySocket socket = spy(this.socket); 62 | doReturn(0).when(socket).receivePacket(0); 63 | 64 | socket.receivePacket(); 65 | 66 | verify(socket).receivePacket(0); 67 | } 68 | 69 | @Test 70 | public void testSend() throws Exception { 71 | SteamPacket packet = mock(SteamPacket.class); 72 | when(packet.getBytes()).thenReturn(new byte[] { 0x1, 0x2, 0x3, 0x4 } ); 73 | 74 | this.socket.send(packet); 75 | 76 | ArgumentMatcher bufferMatcher = new ArgumentMatcher() { 77 | public boolean matches(ByteBuffer buffer) { 78 | return Arrays.equals(buffer.array(), new byte[] { 0x1, 0x2, 0x3, 0x4 }); 79 | } 80 | }; 81 | 82 | ArgumentMatcher socketMatcher = new ArgumentMatcher() { 83 | public boolean matches(InetSocketAddress socketAddress) { 84 | return socketAddress.getAddress().isLoopbackAddress() && 85 | socketAddress.getPort() == 27015; 86 | } 87 | }; 88 | 89 | verify(this.channel).send(argThat(bufferMatcher), argThat(socketMatcher)); 90 | } 91 | 92 | class GenericQuerySocket extends QuerySocket { 93 | 94 | public GenericQuerySocket() throws SteamCondenserException, UnknownHostException { 95 | super(InetAddress.getByName("127.0.0.1"), 27015); 96 | } 97 | 98 | public SteamPacket getReply() { 99 | return null; 100 | } 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/servers/GoldSrcServerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers; 9 | 10 | import java.net.InetAddress; 11 | 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import org.powermock.core.classloader.annotations.PrepareForTest; 17 | import org.powermock.modules.junit4.PowerMockRunner; 18 | 19 | import com.github.koraktor.steamcondenser.servers.sockets.GoldSrcSocket; 20 | import com.github.koraktor.steamcondenser.exceptions.RCONNoAuthException; 21 | 22 | import static org.hamcrest.core.Is.is; 23 | import static org.hamcrest.core.IsEqual.equalTo; 24 | import static org.hamcrest.core.IsNull.nullValue; 25 | import static org.junit.Assert.assertThat; 26 | import static org.junit.Assert.assertTrue; 27 | import static org.mockito.Mockito.mock; 28 | import static org.mockito.Mockito.when; 29 | import static org.powermock.api.mockito.PowerMockito.spy; 30 | import static org.powermock.api.mockito.PowerMockito.whenNew; 31 | 32 | /** 33 | * 34 | * 35 | * @author Sebastian Staudt 36 | */ 37 | @RunWith(PowerMockRunner.class) 38 | @PrepareForTest(GoldSrcServer.class) 39 | public class GoldSrcServerTest { 40 | 41 | private static InetAddress LOCALHOST; 42 | 43 | private GoldSrcServer server; 44 | 45 | private GoldSrcSocket socket; 46 | 47 | @Before 48 | public void setup() throws Exception { 49 | LOCALHOST = InetAddress.getByAddress(new byte[] { 0x7f, 0x0, 0x0, 0x1 }); 50 | this.server = spy(new GoldSrcServer(LOCALHOST, 27015)); 51 | this.socket = mock(GoldSrcSocket.class); 52 | this.server.socket = this.socket; 53 | } 54 | 55 | @Test 56 | public void testGetMaster() throws Exception { 57 | MasterServer master = mock(MasterServer.class); 58 | whenNew(MasterServer.class).withArguments(MasterServer.GOLDSRC_MASTER_SERVER).thenReturn(master); 59 | 60 | assertThat(GoldSrcServer.getMaster(), is(equalTo(master))); 61 | } 62 | 63 | @Test 64 | public void testInitSocket() throws Exception { 65 | GoldSrcSocket socket = mock(GoldSrcSocket.class); 66 | whenNew(GoldSrcSocket.class).withArguments(LOCALHOST, 27015, false).thenReturn(socket); 67 | 68 | this.server.initSocket(); 69 | 70 | assertThat((GoldSrcSocket) this.server.socket, is(equalTo(socket))); 71 | } 72 | 73 | @Test 74 | public void testRconAuthFailed() throws Exception { 75 | when(this.socket.rconExec("password", "")). 76 | thenThrow(new RCONNoAuthException()); 77 | 78 | assertThat(this.server.rconAuth("password"), is(false)); 79 | assertThat(this.server.rconPassword, is(nullValue())); 80 | } 81 | 82 | @Test 83 | public void testRconAuthSuccessful() throws Exception { 84 | when(this.socket.rconExec("password", "")).thenReturn(""); 85 | 86 | assertTrue(this.server.rconAuth("password")); 87 | assertThat(this.server.rconPassword, is(equalTo("password"))); 88 | } 89 | 90 | @Test 91 | public void testRconExec() throws Exception { 92 | this.server.rconAuthenticated = true; 93 | when(this.socket.rconExec("password", "command")).thenReturn("test"); 94 | 95 | this.server.rconPassword = "password"; 96 | 97 | assertThat(this.server.rconExec("command"), is(equalTo("test"))); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/PacketBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser; 9 | 10 | import java.nio.ByteBuffer; 11 | 12 | import org.apache.commons.lang3.ArrayUtils; 13 | 14 | /** 15 | * A convenience class wrapping around {@link ByteBuffer} used for easy 16 | * retrieval of string values 17 | * 18 | * @author Sebastian Staudt 19 | */ 20 | public class PacketBuffer { 21 | 22 | private ByteBuffer byteBuffer; 23 | 24 | /** 25 | * Creates a new packet buffer from the given byte array 26 | * 27 | * @param data The data wrap into the underlying byte buffer 28 | */ 29 | public PacketBuffer(byte[] data) { 30 | this.byteBuffer = ByteBuffer.wrap(data); 31 | } 32 | 33 | /** 34 | * Returns the backing byte array of the underlying byte buffer 35 | * 36 | * @return The backing byte array 37 | */ 38 | public byte[] array() { 39 | return this.byteBuffer.array(); 40 | } 41 | 42 | /** 43 | * Returns the next byte at the buffer's current position 44 | * 45 | * @return A byte 46 | */ 47 | public byte getByte() { 48 | return this.byteBuffer.get(); 49 | } 50 | 51 | /** 52 | * Returns an integer value from the buffer's current position 53 | * 54 | * @return An integer value 55 | */ 56 | public int getInt() { 57 | return this.byteBuffer.getInt(); 58 | } 59 | 60 | /** 61 | * Returns the length of this buffer 62 | * 63 | * @return The length of this buffer 64 | */ 65 | public int getLength() { 66 | return this.byteBuffer.capacity(); 67 | } 68 | 69 | /** 70 | * Returns a short integer value from the buffer's current position 71 | * 72 | * @return A short integer value 73 | */ 74 | public short getShort() { 75 | return this.byteBuffer.getShort(); 76 | } 77 | 78 | /** 79 | * Returns a string value from the buffer's current position 80 | *

81 | * This reads the bytes up to the first zero-byte of the underlying byte 82 | * buffer into a String 83 | * 84 | * @return A string value 85 | */ 86 | public String getString() { 87 | byte[] remainingBytes = new byte[this.byteBuffer.remaining()]; 88 | this.byteBuffer.slice().get(remainingBytes); 89 | int zeroPosition = ArrayUtils.indexOf(remainingBytes, (byte) 0); 90 | 91 | if (zeroPosition == ArrayUtils.INDEX_NOT_FOUND) { 92 | return null; 93 | } else { 94 | byte[] stringBytes = new byte[zeroPosition]; 95 | System.arraycopy(remainingBytes, 0, stringBytes, 0, zeroPosition); 96 | this.byteBuffer.position(this.byteBuffer.position() + zeroPosition + 1); 97 | 98 | return new String(stringBytes); 99 | } 100 | } 101 | 102 | /** 103 | * Returns the number of bytes remaining in the underlying byte buffer from 104 | * the current position up to the end 105 | * 106 | * @return The number of bytes remaining in this buffer 107 | */ 108 | public int remaining() { 109 | return this.byteBuffer.remaining(); 110 | } 111 | 112 | /** 113 | * Returns whether there is more data available in this buffer after the 114 | * current position 115 | * 116 | * @return true if there's at least one byte left remaining 117 | */ 118 | public boolean hasRemaining() { 119 | return this.byteBuffer.hasRemaining(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2GoldenWrench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import java.util.Date; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | import org.json.JSONArray; 15 | import org.json.JSONException; 16 | import org.json.JSONObject; 17 | 18 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 19 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 20 | import com.github.koraktor.steamcondenser.community.SteamId; 21 | import com.github.koraktor.steamcondenser.community.WebApi; 22 | 23 | /** 24 | * Represents the special Team Fortress 2 item Golden Wrench. It includes the 25 | * ID of the item, the serial number of the wrench, a reference to the SteamID 26 | * of the owner and the date this player crafted the wrench 27 | * 28 | * @author Sebastian Staudt 29 | */ 30 | public class TF2GoldenWrench { 31 | 32 | private static Set goldenWrenches = null; 33 | 34 | private Date date; 35 | 36 | private int id; 37 | 38 | private int number; 39 | 40 | private SteamId owner; 41 | 42 | /** 43 | * Returns all Golden Wrenches 44 | * 45 | * @return All Golden Wrenches 46 | * @throws WebApiException if an error occurs querying the Web API 47 | * @throws SteamCondenserException if an error occurs querying the Steam 48 | * Community 49 | */ 50 | public static Set getGoldenWrenches() 51 | throws SteamCondenserException { 52 | if(goldenWrenches == null) { 53 | try { 54 | goldenWrenches = new HashSet<>(); 55 | 56 | JSONObject data = new JSONObject(WebApi.getJSON("ITFItems_440", "GetGoldenWrenches", 2)); 57 | JSONArray wrenches = data.getJSONObject("results").getJSONArray("wrenches"); 58 | for(int i = 0; i < wrenches.length(); i ++) { 59 | goldenWrenches.add(new TF2GoldenWrench(wrenches.getJSONObject(i))); 60 | } 61 | } catch(JSONException e) { 62 | throw new WebApiException("Could not parse the JSON data.", e); 63 | } 64 | } 65 | 66 | return goldenWrenches; 67 | } 68 | 69 | /** 70 | * Creates a new instance of a Golden Wrench with the given data 71 | * 72 | * @param wrenchData The JSON data for this wrench 73 | * @throws WebApiException If some attribute is missing from the JSON data 74 | * @throws SteamCondenserException If the SteamId for the owner of the 75 | * wrench cannot be created 76 | */ 77 | private TF2GoldenWrench(JSONObject wrenchData) 78 | throws SteamCondenserException { 79 | try { 80 | this.date = new Date(wrenchData.getLong("timestamp")); 81 | this.id = wrenchData.getInt("itemID"); 82 | this.number = wrenchData.getInt("wrenchNumber"); 83 | this.owner = SteamId.create(wrenchData.getLong("steamID"), false); 84 | } catch(JSONException e) { 85 | throw new WebApiException("Could not parse JSON data.", e); 86 | } 87 | } 88 | 89 | /** 90 | * Returns the date this Golden Wrench has been crafted 91 | * 92 | * @return The crafting date of this wrench 93 | */ 94 | public Date getDate() { 95 | return this.date; 96 | } 97 | 98 | /** 99 | * Returns the unique item ID of this Golden Wrench 100 | * 101 | * @return The ID of this wrench 102 | */ 103 | public int getId() { 104 | return this.id; 105 | } 106 | 107 | /** 108 | * Returns the serial number of this Golden Wrench 109 | * 110 | * @return The serial of this wrench 111 | */ 112 | public int getNumber() { 113 | return this.number; 114 | } 115 | 116 | /** 117 | * Returns the SteamID of the owner of this Golden Wrench 118 | * 119 | * @return The owner of this wrench 120 | */ 121 | public SteamId getOwner() { 122 | return this.owner; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/l4d/L4D2Map.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.l4d; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | 13 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 14 | import com.github.koraktor.steamcondenser.community.SteamId; 15 | import com.github.koraktor.steamcondenser.community.XMLData; 16 | 17 | /** 18 | * This class holds statistical information about a map played by a player in 19 | * Survival mode of Left4Dead 2 20 | *

21 | * The basic information provided is more or less the same for Left4Dead and 22 | * Left4Dead 2, but parsing has to be done differently. 23 | * 24 | * @author Sebastian Staudt 25 | */ 26 | public class L4D2Map extends L4DMap { 27 | 28 | private static final String[] INFECTED = { "boomer", "charger", "common", "hunter", "jockey", "smoker", "spitter", "tank" }; 29 | 30 | private static final String[] ITEMS = { "adrenaline", "defibs", "medkits", "pills"}; 31 | 32 | private HashMap items; 33 | 34 | private HashMap kills; 35 | 36 | private boolean played; 37 | 38 | private ArrayList teammates; 39 | 40 | /** 41 | * Creates a new instance of a map based on the given XML data 42 | *

43 | * The map statistics for the Survival mode of Left4Dead 2 hold much more 44 | * information than those for Left4Dead, e.g. the teammates and items are 45 | * listed. 46 | * 47 | * @param mapData The XML data for this map 48 | */ 49 | public L4D2Map(XMLData mapData) 50 | throws SteamCondenserException { 51 | String imgUrl = mapData.getString("img"); 52 | this.id = imgUrl.substring(imgUrl.lastIndexOf('/'), -4); 53 | this.name = mapData.getString("name"); 54 | this.played = mapData.getString("hasPlayed").equals("1"); 55 | 56 | if(this.played) { 57 | this.bestTime = mapData.getFloat("besttimemilliseconds") / 1000; 58 | 59 | this.items = new HashMap<>(); 60 | for(String item : ITEMS) { 61 | this.items.put(item, mapData.getInteger("items_" + item)); 62 | } 63 | 64 | this.kills = new HashMap<>(); 65 | for(String infected : INFECTED) { 66 | this.items.put(infected, mapData.getInteger("kills_" + infected)); 67 | } 68 | 69 | this.teammates = new ArrayList<>(); 70 | for(XMLData teammateData : mapData.getChildren("teammates")) { 71 | this.teammates.add(SteamId.create(teammateData.getLong())); 72 | } 73 | 74 | String medal = mapData.getString("medal"); 75 | if(medal.equals("gold")) { 76 | this.medal = GOLD; 77 | } else if(medal.equals("silver")) { 78 | this.medal = SILVER; 79 | } else if(medal.equals("bronze")) { 80 | this.medal = BRONZE; 81 | } else { 82 | this.medal = NONE; 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * Returns statistics about the items used by the player on this map 89 | * 90 | * @return array The items used by the player 91 | */ 92 | public HashMap getItems() { 93 | return this.items; 94 | } 95 | 96 | /** 97 | * Returns the number of special infected killed by the player grouped by 98 | * the names of the special infected 99 | * 100 | * @return array The special infected killed by the player 101 | */ 102 | public HashMap getKills() { 103 | return this.kills; 104 | } 105 | 106 | /** 107 | * Returns the SteamIDs of the teammates of the player in his best game on 108 | * this map 109 | * 110 | * @return array The SteamIDs of the teammates in the best game 111 | */ 112 | public ArrayList getTeammates() { 113 | return this.teammates; 114 | } 115 | 116 | /** 117 | * Returns whether the player has already played this map 118 | * 119 | * @return bool true if the player has already played this map 120 | */ 121 | public boolean hasPlayed() { 122 | return this.played; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Stats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import java.util.ArrayList; 11 | 12 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 13 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 14 | import com.github.koraktor.steamcondenser.community.GameInventory; 15 | import com.github.koraktor.steamcondenser.community.GameStats; 16 | import com.github.koraktor.steamcondenser.community.XMLData; 17 | 18 | /** 19 | * This class represents the game statistics for a single user in Team Fortress 20 | * 2 21 | * 22 | * @author Sebastian Staudt 23 | */ 24 | public class TF2Stats extends GameStats { 25 | 26 | private int accumulatedPoints; 27 | 28 | private ArrayList classStats; 29 | 30 | private GameInventory inventory; 31 | 32 | private int totalPlayTime; 33 | 34 | /** 35 | * Creates a new TF2Stats instance by calling the super 36 | * constructor with the game name "tf2" 37 | * 38 | * @param steamId The custom URL or 64bit Steam ID of the user 39 | * @throws SteamCondenserException if an error occurs while fetching the 40 | * stats data 41 | */ 42 | public TF2Stats(Object steamId) throws SteamCondenserException { 43 | this(steamId, false); 44 | } 45 | 46 | /** 47 | * Creates a new TF2Stats instance by calling the super 48 | * constructor with the game name "tf2" (or "520" 49 | * for the beta) 50 | * 51 | * @param steamId The custom URL or 64bit Steam ID of the user 52 | * @param beta If true, creates stats for the public TF2 beta 53 | * @throws SteamCondenserException if an error occurs while fetching the 54 | * stats data 55 | */ 56 | public TF2Stats(Object steamId, boolean beta) throws SteamCondenserException { 57 | super(steamId, (beta ? "520" : "tf2")); 58 | 59 | if(this.isPublic()) { 60 | this.accumulatedPoints = this.xmlData.getInteger("stats", "accumulatedPoints"); 61 | this.totalPlayTime = this.xmlData.getInteger("stats", "secondsPlayedAllClassesLifetime"); 62 | } 63 | } 64 | 65 | /** 66 | * Returns the total points this player has achieved in his career 67 | * 68 | * @return This player's accumulated points 69 | */ 70 | public int getAccumulatedPoints() { 71 | return this.accumulatedPoints; 72 | } 73 | 74 | /** 75 | * Returns the accumulated number of seconds this player has spent playing as a TF2 class 76 | * 77 | * @return total seconds played as a TF2 class 78 | */ 79 | public int getTotalPlayTime() { 80 | return this.totalPlayTime; 81 | } 82 | 83 | /** 84 | * Returns the statistics for all Team Fortress 2 classes for this user 85 | *

86 | * If the classes haven't been parsed already, parsing is done now. 87 | * 88 | * @return An array storing individual stats for each Team Fortress 2 class 89 | */ 90 | public ArrayList getClassStats() { 91 | if(!this.isPublic()) { 92 | return null; 93 | } 94 | 95 | if(this.classStats == null) { 96 | this.classStats = new ArrayList<>(); 97 | for(XMLData classData : this.xmlData.getElements("stats", "classData")) { 98 | this.classStats.add(TF2ClassFactory.getTF2Class(classData)); 99 | } 100 | } 101 | 102 | return this.classStats; 103 | } 104 | 105 | /** 106 | * Returns the current Team Fortress 2 inventory (a.k.a. backpack) of this 107 | * player 108 | * 109 | * @return This player's TF2 backpack 110 | * @throws WebApiException If an error occured while querying Steam's Web 111 | * API 112 | */ 113 | public GameInventory getInventory() throws SteamCondenserException { 114 | if(!this.isPublic()) { 115 | return null; 116 | } 117 | 118 | if(this.inventory == null) { 119 | this.inventory = GameInventory.create(this.getGame().getAppId(), this.user.getSteamId64()); 120 | } 121 | 122 | return this.inventory; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/servers/sockets/RCONSocketTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.net.InetAddress; 11 | import java.nio.ByteBuffer; 12 | import java.nio.channels.SocketChannel; 13 | 14 | import org.junit.Before; 15 | import org.junit.Rule; 16 | import org.junit.Test; 17 | import org.junit.rules.ExpectedException; 18 | import org.junit.runner.RunWith; 19 | 20 | import org.mockito.invocation.InvocationOnMock; 21 | import org.mockito.stubbing.Answer; 22 | import org.powermock.core.classloader.annotations.PrepareForTest; 23 | import org.powermock.modules.junit4.PowerMockRunner; 24 | 25 | import com.github.koraktor.steamcondenser.exceptions.ConnectionResetException; 26 | import com.github.koraktor.steamcondenser.exceptions.RCONBanException; 27 | import com.github.koraktor.steamcondenser.servers.packets.rcon.RCONPacket; 28 | import com.github.koraktor.steamcondenser.servers.packets.rcon.RCONPacketFactory; 29 | 30 | import static org.hamcrest.Matchers.is; 31 | import static org.hamcrest.Matchers.nullValue; 32 | import static org.junit.Assert.assertEquals; 33 | import static org.junit.Assert.assertNull; 34 | import static org.junit.Assert.assertThat; 35 | import static org.mockito.Mockito.doAnswer; 36 | import static org.mockito.Mockito.doReturn; 37 | import static org.mockito.Mockito.doThrow; 38 | import static org.mockito.Mockito.mock; 39 | import static org.mockito.Mockito.spy; 40 | import static org.powermock.api.mockito.PowerMockito.mockStatic; 41 | import static org.powermock.api.mockito.PowerMockito.when; 42 | 43 | /** 44 | * @author Sebastian Staudt 45 | */ 46 | @RunWith(PowerMockRunner.class) 47 | @PrepareForTest(RCONPacketFactory.class) 48 | public class RCONSocketTest { 49 | 50 | @Rule 51 | private ExpectedException exception = ExpectedException.none(); 52 | 53 | private RCONSocket socket; 54 | 55 | @Before 56 | public void setup() throws Exception { 57 | this.socket = spy(new RCONSocket(InetAddress.getLocalHost(), 27015)); 58 | } 59 | 60 | @Test 61 | public void testNewSocket() throws Exception { 62 | assertEquals(InetAddress.getLocalHost(), this.socket.remoteSocket.getAddress()); 63 | assertEquals(27015, this.socket.remoteSocket.getPort()); 64 | assertNull(this.socket.channel); 65 | } 66 | 67 | @Test 68 | public void testReceiveReply() throws Exception { 69 | RCONPacket packet = mock(RCONPacket.class); 70 | mockStatic(RCONPacketFactory.class); 71 | when(RCONPacketFactory.getPacketFromData("test test".getBytes())).thenReturn(packet); 72 | 73 | doAnswer(new Answer() { 74 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 75 | RCONSocket socket = (RCONSocket) invocationOnMock.getMock(); 76 | socket.buffer = ByteBuffer.wrap(new byte[]{ (byte) 0x9, 0x0, 0x0, 0x0 }); 77 | return 4; 78 | } 79 | }).when(this.socket).receivePacket(4); 80 | doAnswer(new Answer() { 81 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 82 | RCONSocket socket = (RCONSocket) invocationOnMock.getMock(); 83 | socket.buffer = ByteBuffer.wrap("test".getBytes()); 84 | return 4; 85 | } 86 | }).when(this.socket).receivePacket(9); 87 | doAnswer(new Answer() { 88 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 89 | RCONSocket socket = (RCONSocket) invocationOnMock.getMock(); 90 | socket.buffer = ByteBuffer.wrap(" test".getBytes()); 91 | return 5; 92 | } 93 | }).when(this.socket).receivePacket(5); 94 | 95 | assertEquals(packet, this.socket.getReply()); 96 | } 97 | 98 | @Test 99 | public void testConnectionDropped() throws Exception { 100 | this.socket.channel = SocketChannel.open(); 101 | doReturn(0).when(this.socket).receivePacket(4); 102 | 103 | assertThat(this.socket.getReply(), is(nullValue())); 104 | } 105 | 106 | @Test 107 | public void testConnectionReset() throws Exception { 108 | this.socket.channel = SocketChannel.open(); 109 | doThrow(new ConnectionResetException()).when(this.socket).receivePacket(4); 110 | 111 | assertThat(this.socket.getReply(), is(nullValue())); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/dods/DoDSClass.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2009-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.dods; 9 | 10 | import com.github.koraktor.steamcondenser.community.GameClass; 11 | import com.github.koraktor.steamcondenser.community.XMLData; 12 | 13 | /** 14 | * Represents the stats for a Day of Defeat: Source class for a specific user 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class DoDSClass extends GameClass { 19 | 20 | private int blocks; 21 | 22 | private int bombsDefused; 23 | 24 | private int bombsPlanted; 25 | 26 | private int captures; 27 | 28 | private int deaths; 29 | 30 | private int dominations; 31 | 32 | private String key; 33 | 34 | private int kills; 35 | 36 | private int roundsLost; 37 | 38 | private int roundsWon; 39 | 40 | private int revenges; 41 | 42 | /** 43 | * Creates a new instance of a Day of Defeat: Source class based on the 44 | * given XML data 45 | * 46 | * @param classData The XML data of the class 47 | */ 48 | public DoDSClass(XMLData classData) { 49 | this.blocks = classData.getInteger("blocks"); 50 | this.bombsDefused = classData.getInteger("bombsdefused"); 51 | this.bombsPlanted = classData.getInteger("bombsplanted"); 52 | this.captures = classData.getInteger("captures"); 53 | this.deaths = classData.getInteger("deaths"); 54 | this.dominations = classData.getInteger("dominations"); 55 | this.key = classData.getAttribute("key"); 56 | this.kills = classData.getInteger("kills"); 57 | this.name = classData.getString("name"); 58 | this.playTime = classData.getInteger("playtime"); 59 | this.roundsLost = classData.getInteger("roundslost"); 60 | this.roundsWon = classData.getInteger("roundswon"); 61 | this.revenges = classData.getInteger("revenges"); 62 | } 63 | 64 | /** 65 | * Returns the blocks achieved by the player with this class 66 | * 67 | * @return The blocks achieved by the player 68 | */ 69 | public int getBlocks() { 70 | return this.blocks; 71 | } 72 | 73 | /** 74 | * Returns the bombs defused by the player with this class 75 | * 76 | * @return The bombs defused by the player 77 | */ 78 | public int getBombsDefuse() { 79 | return this.bombsDefused; 80 | } 81 | 82 | /** 83 | * Returns the bombs planted by the player with this class 84 | * 85 | * @return the bombs planted by the player 86 | */ 87 | public int getBombsPlanted() { 88 | return this.bombsPlanted; 89 | } 90 | 91 | /** 92 | * Returns the number of points captured by the player with this class 93 | * 94 | * @return The number of points captured by the player 95 | */ 96 | public int getCaptures() { 97 | return this.captures; 98 | } 99 | 100 | /** 101 | * Returns the number of times the player died with this class 102 | * 103 | * @return The number of deaths by the player 104 | */ 105 | public int getDeaths() { 106 | return this.deaths; 107 | } 108 | 109 | /** 110 | * Returns the dominations achieved by the player with this class 111 | * 112 | * @return The dominations achieved by the player 113 | */ 114 | public int getDominations() { 115 | return this.dominations; 116 | } 117 | 118 | /** 119 | * Returns the ID of this class 120 | * 121 | * @return The ID of this class 122 | */ 123 | public String getKey() { 124 | return this.key; 125 | } 126 | 127 | /** 128 | * Returns the number of enemies killed by the player with this class 129 | * 130 | * @return The number of enemies killed by the player 131 | */ 132 | public int getKills() { 133 | return this.kills; 134 | } 135 | 136 | /** 137 | * Returns the revenges achieved by the player with this class 138 | * 139 | * @return The revenges achieved by the player 140 | */ 141 | public int getRevenges() { 142 | return this.revenges; 143 | } 144 | 145 | /** 146 | * Returns the number of rounds lost with this class 147 | * 148 | * @return The number of rounds lost with this class 149 | */ 150 | public int getRoundsLost() { 151 | return this.roundsLost; 152 | } 153 | 154 | /** 155 | * Returns the number of rounds won with this class 156 | * 157 | * @return The number of rounds won with this class 158 | */ 159 | public int getRoundsWon() { 160 | return this.roundsWon; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/sockets/SourceSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.net.InetAddress; 11 | import java.util.ArrayList; 12 | import java.util.concurrent.TimeoutException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 17 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 18 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacketFactory; 19 | 20 | /** 21 | * This class represents a socket used to communicate with game servers based 22 | * on the Source engine (e.g. Team Fortress 2, Counter-Strike: Source) 23 | * 24 | * @author Sebastian Staudt 25 | */ 26 | public class SourceSocket extends QuerySocket { 27 | 28 | protected static final Logger LOG = LoggerFactory.getLogger(SourceSocket.class); 29 | 30 | /** 31 | * Creates a new socket to communicate with the server on the given IP 32 | * address and port 33 | * 34 | * @param ipAddress Either the IP address or the DNS name of the server 35 | * @param portNumber The port the server is listening on 36 | * @throws SteamCondenserException if the socket cannot be opened 37 | */ 38 | public SourceSocket(InetAddress ipAddress, int portNumber) 39 | throws SteamCondenserException { 40 | super(ipAddress, portNumber); 41 | } 42 | 43 | /** 44 | * Reads a packet from the socket 45 | *

46 | * The Source query protocol specifies a maximum packet size of 1,400 47 | * bytes. Bigger packets will be split over several UDP packets. This 48 | * method reassembles split packets into single packet objects. 49 | * Additionally Source may compress big packets using bzip2. Those packets 50 | * will be compressed. 51 | * 52 | * @return SteamPacket The packet replied from the server 53 | * @throws SteamCondenserException if an error occurs while communicating 54 | * with the server 55 | * @throws TimeoutException if the request times out 56 | */ 57 | public SteamPacket getReply() 58 | throws SteamCondenserException, TimeoutException { 59 | int bytesRead; 60 | boolean isCompressed = false; 61 | SteamPacket packet; 62 | 63 | bytesRead = this.receivePacket(1400); 64 | 65 | if(this.packetIsSplit()) { 66 | byte[] splitData; 67 | int packetCount, packetNumber, requestId, splitSize; 68 | int packetChecksum = 0; 69 | ArrayList splitPackets = new ArrayList<>(); 70 | 71 | do { 72 | requestId = Integer.reverseBytes(this.buffer.getInt()); 73 | isCompressed = ((requestId & 0x80000000) != 0); 74 | packetCount = this.buffer.get(); 75 | packetNumber = this.buffer.get() + 1; 76 | 77 | if(isCompressed) { 78 | splitSize = Integer.reverseBytes(this.buffer.getInt()); 79 | packetChecksum = Integer.reverseBytes(this.buffer.getInt()); 80 | } else { 81 | splitSize = Short.reverseBytes(this.buffer.getShort()); 82 | } 83 | 84 | splitData = new byte[Math.min(splitSize, this.buffer.remaining())]; 85 | this.buffer.get(splitData); 86 | splitPackets.ensureCapacity(packetCount); 87 | splitPackets.add(packetNumber - 1, splitData); 88 | 89 | if(splitPackets.size() < packetCount) { 90 | try { 91 | bytesRead = this.receivePacket(); 92 | } catch(TimeoutException e) { 93 | bytesRead = 0; 94 | } 95 | } else { 96 | bytesRead = 0; 97 | } 98 | 99 | LOG.info("Received packet #" + packetNumber + " of " + packetCount + " for request ID " + requestId + "."); 100 | } while(bytesRead > 0 && this.packetIsSplit()); 101 | 102 | if(isCompressed) { 103 | packet = SteamPacketFactory.reassemblePacket(splitPackets, true, splitSize, packetChecksum); 104 | } else { 105 | packet = SteamPacketFactory.reassemblePacket(splitPackets); 106 | } 107 | } else { 108 | packet = this.getPacketFromData(); 109 | } 110 | 111 | this.buffer.flip(); 112 | 113 | if(isCompressed) { 114 | LOG.info("Received compressed reply of type \"" + packet.getClass().getSimpleName() + "\""); 115 | } else { 116 | LOG.info("Received reply of type \"" + packet.getClass().getSimpleName() + "\""); 117 | } 118 | 119 | return packet; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Class.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.community.GameClass; 11 | import com.github.koraktor.steamcondenser.community.XMLData; 12 | 13 | /** 14 | * Represents the stats for a Team Fortress 2 class for a specific user 15 | * 16 | * @author Sebastian Staudt 17 | */ 18 | public class TF2Class extends GameClass { 19 | 20 | protected int maxBuildingsDestroyed; 21 | protected int maxCaptures; 22 | protected int maxDamage; 23 | protected int maxDefenses; 24 | protected int maxDominations; 25 | protected int maxKillAssists; 26 | protected int maxKills; 27 | protected int maxRevenges; 28 | protected int maxScore; 29 | protected int maxTimeAlive; 30 | 31 | /** 32 | * Creates a new TF2 class instance based on the assigned XML data 33 | * 34 | * @param classData The XML data for this class 35 | */ 36 | public TF2Class(XMLData classData) { 37 | this.name = classData.getString("className"); 38 | this.maxBuildingsDestroyed = classData.getInteger("ibuildingsdestroyed"); 39 | this.maxCaptures = classData.getInteger("ipointcaptures"); 40 | this.maxDamage = classData.getInteger("idamagedealt"); 41 | this.maxDefenses = classData.getInteger("ipointdefenses"); 42 | this.maxDominations = classData.getInteger("idominations"); 43 | this.maxKillAssists = classData.getInteger("ikillassists"); 44 | this.maxKills = classData.getInteger("inumberofkills"); 45 | this.maxRevenges = classData.getInteger("irevenge"); 46 | this.maxScore = classData.getInteger("ipointsscored"); 47 | this.maxTimeAlive = classData.getInteger("iplaytime"); 48 | this.playTime = classData.getInteger("playtimeSeconds"); 49 | } 50 | 51 | /** 52 | * Returns the maximum number of buildings the player has destroyed in a 53 | * single life with this class 54 | * 55 | * @return Maximum number of buildings destroyed 56 | */ 57 | public int getMaxBuildingsDestroyed() { 58 | return this.maxBuildingsDestroyed; 59 | } 60 | 61 | /** 62 | * Returns the maximum number of points captured by the player in a single 63 | * life with this class 64 | * 65 | * @return Maximum number of points captured 66 | */ 67 | public int getMaxCaptures() { 68 | return this.maxCaptures; 69 | } 70 | 71 | /** 72 | * Returns the maximum damage dealt by the player in a single life with 73 | * this class 74 | * 75 | * @return Maximum damage dealt 76 | */ 77 | public int getMaxDamage() { 78 | return this.maxDamage; 79 | } 80 | 81 | /** 82 | * Returns the maximum number of defenses by the player in a single life 83 | * with this class 84 | * 85 | * @return Maximum number of defenses 86 | */ 87 | public int getMaxDefenses() { 88 | return this.maxDefenses; 89 | } 90 | 91 | /** 92 | * Returns the maximum number of dominations by the player in a single life 93 | * with this class 94 | * 95 | * @return Maximum number of dominations 96 | */ 97 | public int getMaxDominations() { 98 | return this.maxDominations; 99 | } 100 | 101 | /** 102 | * Returns the maximum number of times the the player assisted a teammate 103 | * with killing an enemy in a single life with this class 104 | * 105 | * @return Maximum number of kill assists 106 | */ 107 | public int getMaxKillAssists() { 108 | return this.maxKillAssists; 109 | } 110 | 111 | /** 112 | * Returns the maximum number of enemies killed by the player in a single 113 | * life with this class 114 | * 115 | * @return Maximum number of kills 116 | */ 117 | public int getMaxKills() { 118 | return this.maxKills; 119 | } 120 | 121 | /** 122 | * Returns the maximum number of revenges by the player in a single life 123 | * with this class 124 | * 125 | * @return Maximum number of revenges 126 | */ 127 | public int getMaxRevenges() { 128 | return this.maxRevenges; 129 | } 130 | 131 | /** 132 | * Returns the maximum number score achieved by the player in a single life 133 | * with this class 134 | * 135 | * @return Maximum score 136 | */ 137 | public int getMaxScore() { 138 | return this.maxScore; 139 | } 140 | 141 | /** 142 | * Returns the maximum lifetime by the player in a single life with this 143 | * class 144 | * 145 | * @return Maximum lifetime 146 | */ 147 | public int getMaxTimeAlive() { 148 | return this.maxTimeAlive; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/servers/sockets/RCONSocket.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2008-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.io.IOException; 11 | import java.net.InetAddress; 12 | import java.nio.ByteBuffer; 13 | import java.nio.channels.SocketChannel; 14 | import java.util.concurrent.TimeoutException; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.github.koraktor.steamcondenser.exceptions.ConnectionResetException; 19 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 20 | import com.github.koraktor.steamcondenser.servers.packets.rcon.RCONPacket; 21 | import com.github.koraktor.steamcondenser.servers.packets.rcon.RCONPacketFactory; 22 | 23 | /** 24 | * This class represents a socket used for RCON communication with game servers 25 | * based on the Source engine (e.g. Team Fortress 2, Counter-Strike: Source) 26 | *

27 | * The Source engine uses a stateful TCP connection for RCON communication and 28 | * uses an additional socket of this type to handle RCON requests. 29 | * 30 | * @author Sebastian Staudt 31 | */ 32 | public class RCONSocket extends SteamSocket { 33 | 34 | protected final static Logger LOG = LoggerFactory.getLogger(RCONSocket.class.getName()); 35 | 36 | /** 37 | * Creates a new TCP socket to communicate with the server on the given IP 38 | * address and port 39 | * 40 | * @param ipAddress Either the IP address or the DNS name of the server 41 | * @param portNumber The port the server is listening on 42 | */ 43 | public RCONSocket(InetAddress ipAddress, int portNumber) { 44 | super(ipAddress, portNumber); 45 | } 46 | 47 | /** 48 | * Closes the underlying TCP socket if it is connected 49 | * 50 | * @see SteamSocket#close 51 | */ 52 | @Override 53 | public void close() { 54 | if (this.channel != null && 55 | ((SocketChannel) this.channel).isConnected()) { 56 | super.close(); 57 | } 58 | } 59 | 60 | /** 61 | * Sends the given RCON packet to the server 62 | * 63 | * @param dataPacket The RCON packet to send to the server 64 | * @throws SteamCondenserException if an error occurs while writing to the 65 | * socket 66 | */ 67 | public void send(RCONPacket dataPacket) 68 | throws SteamCondenserException { 69 | try { 70 | if (this.channel == null || 71 | !((SocketChannel)this.channel).isConnected()) { 72 | this.channel = SocketChannel.open(); 73 | ((SocketChannel) this.channel).socket().connect(this.remoteSocket, SteamSocket.timeout); 74 | this.channel.configureBlocking(false); 75 | } 76 | 77 | this.buffer = ByteBuffer.wrap(dataPacket.getBytes()); 78 | ((SocketChannel)this.channel).write(this.buffer); 79 | } catch(IOException e) { 80 | throw new SteamCondenserException(e.getMessage(), e); 81 | } 82 | } 83 | 84 | /** 85 | * Reads a packet from the socket 86 | *

87 | * The Source RCON protocol allows packets of an arbitrary sice transmitted 88 | * using multiple TCP packets. The data is received in chunks and 89 | * concatenated into a single response packet. 90 | * 91 | * @return The packet replied from the server or null if the 92 | * connection has been closed by the server 93 | * @throws SteamCondenserException if an error occurs while communicating 94 | * with the server 95 | * @throws TimeoutException if the request times out 96 | */ 97 | public RCONPacket getReply() 98 | throws SteamCondenserException, TimeoutException { 99 | try { 100 | if (this.receivePacket(4) == 0) { 101 | try { 102 | this.channel.close(); 103 | } catch (IOException ignored) {} 104 | return null; 105 | } 106 | } catch (ConnectionResetException e) { 107 | try { 108 | this.channel.close(); 109 | } catch (IOException ignored) {} 110 | return null; 111 | } 112 | 113 | int packetSize = Integer.reverseBytes(this.buffer.getInt()); 114 | int remainingBytes = packetSize; 115 | 116 | byte[] packetData = new byte[packetSize]; 117 | int receivedBytes; 118 | do { 119 | receivedBytes = this.receivePacket(remainingBytes); 120 | System.arraycopy(this.buffer.array(), 0, packetData, packetSize - remainingBytes, receivedBytes); 121 | remainingBytes -= receivedBytes; 122 | } while(remainingBytes > 0); 123 | 124 | RCONPacket packet = RCONPacketFactory.getPacketFromData(packetData); 125 | 126 | LOG.info("Received packet of type \"" + packet.getClass() + "\"."); 127 | 128 | return packet; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/servers/sockets/SteamSocketTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011-2013, Sebastian Staudt 6 | */ 7 | package com.github.koraktor.steamcondenser.servers.sockets; 8 | 9 | import java.io.IOException; 10 | import java.net.InetAddress; 11 | import java.net.UnknownHostException; 12 | import java.nio.ByteBuffer; 13 | import java.nio.channels.DatagramChannel; 14 | import java.nio.channels.SelectionKey; 15 | import java.nio.channels.Selector; 16 | import java.util.concurrent.TimeoutException; 17 | 18 | import org.junit.Before; 19 | import org.junit.Rule; 20 | import org.junit.Test; 21 | import org.junit.rules.ExpectedException; 22 | import org.junit.runner.RunWith; 23 | 24 | import org.mockito.invocation.InvocationOnMock; 25 | import org.mockito.stubbing.Answer; 26 | import org.powermock.core.classloader.annotations.PrepareForTest; 27 | import org.powermock.modules.junit4.PowerMockRunner; 28 | 29 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | import static org.mockito.Matchers.any; 33 | import static org.mockito.Mockito.verify; 34 | import static org.mockito.Mockito.when; 35 | import static org.powermock.api.mockito.PowerMockito.mock; 36 | import static org.powermock.api.mockito.PowerMockito.mockStatic; 37 | 38 | /** 39 | * @author Sebastian Staudt 40 | */ 41 | @RunWith(PowerMockRunner.class) 42 | @PrepareForTest(SteamSocket.class) 43 | public class SteamSocketTest { 44 | 45 | @Rule 46 | private ExpectedException exception = ExpectedException.none(); 47 | 48 | private DatagramChannel channel; 49 | 50 | private SteamSocket socket; 51 | 52 | @Before 53 | public void setup() throws Exception { 54 | this.socket = new GenericSteamSocket(); 55 | 56 | this.channel = mock(DatagramChannel.class); 57 | this.socket.channel = this.channel; 58 | } 59 | 60 | @Test 61 | public void testClose() throws IOException { 62 | when(this.channel.isOpen()).thenReturn(true); 63 | 64 | this.socket.close(); 65 | 66 | verify(this.channel).close(); 67 | } 68 | 69 | @Test 70 | public void testReceiveIntoNewBuffer() throws Exception { 71 | Selector selector = mock(Selector.class); 72 | when(selector.select(SteamSocket.timeout)).thenReturn(1); 73 | mockStatic(Selector.class); 74 | when(Selector.open()).thenReturn(selector); 75 | when(this.channel.register(selector, SelectionKey.OP_READ)).thenReturn(null); 76 | 77 | final SteamSocket socket = this.socket; 78 | when(this.channel.read(any(ByteBuffer.class))).thenAnswer(new Answer() { 79 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 80 | socket.buffer.put("test".getBytes()); 81 | return 4; 82 | } 83 | }); 84 | 85 | assertEquals(4, this.socket.receivePacket(4)); 86 | 87 | ByteBuffer buffer = this.socket.buffer; 88 | assertEquals(0, buffer.position()); 89 | assertEquals(4, buffer.capacity()); 90 | assertEquals("test", new String(buffer.array())); 91 | } 92 | 93 | @Test 94 | public void testReceiveIntoExistingBuffer() throws Exception { 95 | Selector selector = mock(Selector.class); 96 | when(selector.select(SteamSocket.timeout)).thenReturn(1); 97 | mockStatic(Selector.class); 98 | when(Selector.open()).thenReturn(selector); 99 | when(this.channel.register(selector, SelectionKey.OP_READ)).thenReturn(null); 100 | 101 | this.socket.buffer = ByteBuffer.allocate(10); 102 | 103 | final SteamSocket socket = this.socket; 104 | when(this.channel.read(any(ByteBuffer.class))).thenAnswer(new Answer() { 105 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 106 | socket.buffer.put("test".getBytes()); 107 | return 4; 108 | } 109 | }); 110 | 111 | assertEquals(4, this.socket.receivePacket(4)); 112 | 113 | ByteBuffer buffer = this.socket.buffer; 114 | assertEquals(0, buffer.position()); 115 | assertEquals(4, buffer.capacity()); 116 | assertEquals("test", new String(buffer.array())); 117 | } 118 | 119 | @Test 120 | public void testSetTimeout() { 121 | SteamSocket.setTimeout(2000); 122 | 123 | assertEquals(2000, SteamSocket.timeout); 124 | } 125 | 126 | @Test 127 | public void testTimeout() throws Exception { 128 | this.exception.expect(TimeoutException.class); 129 | 130 | Selector selector = mock(Selector.class); 131 | when(selector.select(SteamSocket.timeout)).thenReturn(0); 132 | mockStatic(Selector.class); 133 | when(Selector.open()).thenReturn(selector); 134 | when(this.channel.register(selector, SelectionKey.OP_READ)).thenReturn(null); 135 | 136 | this.socket.receivePacket(4); 137 | } 138 | 139 | class GenericSteamSocket extends SteamSocket { 140 | 141 | public GenericSteamSocket() throws UnknownHostException { 142 | super(InetAddress.getLocalHost(), 27015); 143 | } 144 | 145 | public SteamPacket getReply() { 146 | return null; 147 | } 148 | 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/test/resources/com/github/koraktor/steamcondenser/community/koraktor-friends.json: -------------------------------------------------------------------------------- 1 | { 2 | "friendslist": { 3 | "friends": [{ 4 | "steamid": "76561197960299812", 5 | "relationship": "friend", 6 | "friend_since": 0 7 | }, { 8 | "steamid": "76561197960377091", 9 | "relationship": "friend", 10 | "friend_since": 0 11 | }, { 12 | "steamid": "76561197960811440", 13 | "relationship": "friend", 14 | "friend_since": 0 15 | }, { 16 | "steamid": "76561197961445668", 17 | "relationship": "friend", 18 | "friend_since": 0 19 | }, { 20 | "steamid": "76561197962380601", 21 | "relationship": "friend", 22 | "friend_since": 0 23 | }, { 24 | "steamid": "76561197964238198", 25 | "relationship": "friend", 26 | "friend_since": 0 27 | }, { 28 | "steamid": "76561197965401776", 29 | "relationship": "friend", 30 | "friend_since": 0 31 | }, { 32 | "steamid": "76561197967234643", 33 | "relationship": "friend", 34 | "friend_since": 0 35 | }, { 36 | "steamid": "76561197968567369", 37 | "relationship": "friend", 38 | "friend_since": 0 39 | }, { 40 | "steamid": "76561197968573709", 41 | "relationship": "friend", 42 | "friend_since": 1330018231 43 | }, { 44 | "steamid": "76561197968608003", 45 | "relationship": "friend", 46 | "friend_since": 0 47 | }, { 48 | "steamid": "76561197968747750", 49 | "relationship": "friend", 50 | "friend_since": 1279986266 51 | }, { 52 | "steamid": "76561197969318941", 53 | "relationship": "friend", 54 | "friend_since": 0 55 | }, { 56 | "steamid": "76561197969355234", 57 | "relationship": "friend", 58 | "friend_since": 0 59 | }, { 60 | "steamid": "76561197969802605", 61 | "relationship": "friend", 62 | "friend_since": 0 63 | }, { 64 | "steamid": "76561197970043247", 65 | "relationship": "friend", 66 | "friend_since": 0 67 | }, { 68 | "steamid": "76561197970084764", 69 | "relationship": "friend", 70 | "friend_since": 0 71 | }, { 72 | "steamid": "76561197970367346", 73 | "relationship": "friend", 74 | "friend_since": 0 75 | }, { 76 | "steamid": "76561197970371661", 77 | "relationship": "friend", 78 | "friend_since": 0 79 | }, { 80 | "steamid": "76561197970557916", 81 | "relationship": "friend", 82 | "friend_since": 0 83 | }, { 84 | "steamid": "76561197970610839", 85 | "relationship": "friend", 86 | "friend_since": 0 87 | }, { 88 | "steamid": "76561197970732484", 89 | "relationship": "friend", 90 | "friend_since": 0 91 | }, { 92 | "steamid": "76561197971785868", 93 | "relationship": "friend", 94 | "friend_since": 0 95 | }, { 96 | "steamid": "76561197975302059", 97 | "relationship": "friend", 98 | "friend_since": 0 99 | }, { 100 | "steamid": "76561197977072501", 101 | "relationship": "friend", 102 | "friend_since": 0 103 | }, { 104 | "steamid": "76561197977586822", 105 | "relationship": "friend", 106 | "friend_since": 1290718284 107 | }, { 108 | "steamid": "76561197978628218", 109 | "relationship": "friend", 110 | "friend_since": 1345215624 111 | }, { 112 | "steamid": "76561197979277905", 113 | "relationship": "friend", 114 | "friend_since": 1292607952 115 | }, { 116 | "steamid": "76561197980055115", 117 | "relationship": "friend", 118 | "friend_since": 1330719963 119 | }, { 120 | "steamid": "76561197980238112", 121 | "relationship": "friend", 122 | "friend_since": 0 123 | }, { 124 | "steamid": "76561197983311154", 125 | "relationship": "friend", 126 | "friend_since": 1279932515 127 | }, { 128 | "steamid": "76561197984496388", 129 | "relationship": "friend", 130 | "friend_since": 0 131 | }, { 132 | "steamid": "76561197986233889", 133 | "relationship": "friend", 134 | "friend_since": 1290718281 135 | }, { 136 | "steamid": "76561197989761999", 137 | "relationship": "friend", 138 | "friend_since": 1346751372 139 | }, { 140 | "steamid": "76561197990484599", 141 | "relationship": "friend", 142 | "friend_since": 1298211895 143 | }, { 144 | "steamid": "76561197992422064", 145 | "relationship": "friend", 146 | "friend_since": 1327745215 147 | }, { 148 | "steamid": "76561197993236014", 149 | "relationship": "friend", 150 | "friend_since": 1332898411 151 | }, { 152 | "steamid": "76561197994013896", 153 | "relationship": "friend", 154 | "friend_since": 1325179388 155 | }, { 156 | "steamid": "76561197998540387", 157 | "relationship": "friend", 158 | "friend_since": 0 159 | }, { 160 | "steamid": "76561198000503301", 161 | "relationship": "friend", 162 | "friend_since": 1309703220 163 | }, { 164 | "steamid": "76561198005170109", 165 | "relationship": "friend", 166 | "friend_since": 1345664691 167 | }, { 168 | "steamid": "76561198005731671", 169 | "relationship": "friend", 170 | "friend_since": 1286378684 171 | }, { 172 | "steamid": "76561198037444970", 173 | "relationship": "friend", 174 | "friend_since": 1335979528 175 | }] 176 | 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/test/java/com/github/koraktor/steamcondenser/servers/sockets/SourceSocketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2018, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.servers.sockets; 9 | 10 | import java.net.InetAddress; 11 | import java.nio.ByteBuffer; 12 | import java.util.ArrayList; 13 | 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | import org.mockito.invocation.InvocationOnMock; 19 | import org.mockito.stubbing.Answer; 20 | import org.powermock.core.classloader.annotations.PrepareForTest; 21 | import org.powermock.modules.junit4.PowerMockRunner; 22 | 23 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacket; 24 | import com.github.koraktor.steamcondenser.servers.packets.SteamPacketFactory; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.mockito.Matchers.eq; 28 | import static org.mockito.Matchers.refEq; 29 | import static org.mockito.Mockito.mock; 30 | import static org.mockito.Mockito.spy; 31 | import static org.powermock.api.mockito.PowerMockito.doAnswer; 32 | import static org.powermock.api.mockito.PowerMockito.mockStatic; 33 | import static org.powermock.api.mockito.PowerMockito.when; 34 | 35 | /** 36 | * @author Sebastian Staudt 37 | */ 38 | @RunWith(PowerMockRunner.class) 39 | @PrepareForTest(SteamPacketFactory.class) 40 | public class SourceSocketTest { 41 | 42 | private SteamPacket packet; 43 | 44 | private SourceSocket socket; 45 | 46 | @Before 47 | public void setup() throws Exception { 48 | this.packet = mock(SteamPacket.class); 49 | this.socket = spy(new SourceSocket(InetAddress.getLocalHost(), 27015)); 50 | 51 | mockStatic(SteamPacketFactory.class); 52 | } 53 | 54 | @Test 55 | public void testSinglePacketReply() throws Exception { 56 | when(SteamPacketFactory.getPacketFromData("test".getBytes())).thenReturn(this.packet); 57 | 58 | doAnswer(new Answer() { 59 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 60 | SourceSocket socket = (SourceSocket) invocationOnMock.getMock(); 61 | socket.buffer = ByteBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 't', 'e', 's', 't' }); 62 | return 8; 63 | } 64 | }).when(this.socket).receivePacket(1400); 65 | 66 | assertEquals(this.packet, this.socket.getReply()); 67 | } 68 | 69 | @Test 70 | public void testSplitPacketReply() throws Exception { 71 | ArrayList packets = new ArrayList<>(); 72 | packets.add("test".getBytes()); 73 | packets.add("test".getBytes()); 74 | when(SteamPacketFactory.reassemblePacket(refEq(packets))).thenReturn(this.packet); 75 | 76 | doAnswer(new Answer() { 77 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 78 | SourceSocket socket = (SourceSocket) invocationOnMock.getMock(); 79 | socket.buffer = ByteBuffer.wrap(new byte[] { (byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xD2, 0x4, 0x0, 0x0, 0x2, 0x0, 0x4, 0x0, 't', 'e', 's', 't' }); 80 | return 1400; 81 | } 82 | }).when(this.socket).receivePacket(1400); 83 | doAnswer(new Answer() { 84 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 85 | SourceSocket socket = (SourceSocket) invocationOnMock.getMock(); 86 | socket.buffer = ByteBuffer.wrap(new byte[]{(byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xD2, 0x4, 0x0, 0x0, 0x2, 0x1, 0x4, 0x0, 't', 'e', 's', 't'}); 87 | return 1400; 88 | } 89 | }).when(this.socket).receivePacket(); 90 | 91 | assertEquals(this.packet, this.socket.getReply()); 92 | } 93 | 94 | @Test 95 | public void testCompressedReply() throws Exception { 96 | ArrayList packets = new ArrayList<>(); 97 | packets.add("test".getBytes()); 98 | packets.add("test".getBytes()); 99 | when(SteamPacketFactory.reassemblePacket(refEq(packets), eq(true), eq(8), eq(1337))).thenReturn(this.packet); 100 | 101 | doAnswer(new Answer() { 102 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 103 | SourceSocket socket = (SourceSocket) invocationOnMock.getMock(); 104 | socket.buffer = ByteBuffer.wrap(new byte[] { (byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xD2, 0x4, 0x0, (byte) 0x80, 0x2, 0x0, 0x8, 0x0, 0x0, 0x0, 0x39, 0x5, 0x0, 0x0, 't', 'e', 's', 't' }); 105 | return 1400; 106 | } 107 | }).when(this.socket).receivePacket(1400); 108 | doAnswer(new Answer() { 109 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 110 | SourceSocket socket = (SourceSocket) invocationOnMock.getMock(); 111 | socket.buffer = ByteBuffer.wrap(new byte[]{(byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xD2, 0x4, 0x0, (byte) 0x80, 0x2, 0x1, 0x8, 0x0, 0x0, 0x0, 0x39, 0x5, 0x0, 0x0, 't', 'e', 's', 't'}); 112 | return 1400; 113 | } 114 | }).when(this.socket).receivePacket(); 115 | 116 | assertEquals(this.packet, this.socket.getReply()); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/dota2/Dota2Inventory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.dota2; 9 | 10 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 11 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 12 | import com.github.koraktor.steamcondenser.community.GameInventory; 13 | import com.github.koraktor.steamcondenser.community.GameItem; 14 | 15 | /** 16 | * Represents the inventory of a player of DotA 2 17 | * 18 | * @author Sebastian Staudt 19 | */ 20 | public class Dota2Inventory extends GameInventory { 21 | 22 | public static final int APP_ID = 570; 23 | 24 | /** 25 | * This checks the cache for an existing inventory. If it exists it is 26 | * returned. Otherwise a new inventory is created. 27 | * 28 | * @param vanityUrl The vanity URL of the user 29 | * @return The DotA 2 inventory for the given user 30 | * @throws SteamCondenserException if creating the inventory fails 31 | */ 32 | public static Dota2Inventory create(String vanityUrl) 33 | throws SteamCondenserException { 34 | return (Dota2Inventory) create(APP_ID, vanityUrl, true, false); 35 | } 36 | 37 | /** 38 | * This checks the cache for an existing inventory. If it exists it is 39 | * returned. Otherwise a new inventory is created. 40 | * 41 | * @param steamId64 The 64bit Steam ID of the user 42 | * @return The DotA 2 inventory for the given user 43 | * @throws SteamCondenserException if creating the inventory fails 44 | */ 45 | public static Dota2Inventory create(long steamId64) 46 | throws SteamCondenserException { 47 | return (Dota2Inventory) create(APP_ID, steamId64, true, false); 48 | } 49 | 50 | /** 51 | * This checks the cache for an existing inventory. If it exists it is 52 | * returned. Otherwise a new inventory is created. 53 | * 54 | * @param vanityUrl The vanity URL of the user 55 | * @param fetchNow Whether the data should be fetched now 56 | * @return The DotA 2 inventory for the given user 57 | * @throws SteamCondenserException if creating the inventory fails 58 | */ 59 | public static Dota2Inventory create(String vanityUrl, boolean fetchNow) 60 | throws SteamCondenserException { 61 | return (Dota2Inventory) create(APP_ID, vanityUrl, fetchNow, false); 62 | } 63 | 64 | /** 65 | * This checks the cache for an existing inventory. If it exists it is 66 | * returned. Otherwise a new inventory is created. 67 | * 68 | * @param steamId64 The 64bit Steam ID of the user 69 | * @param fetchNow Whether the data should be fetched now 70 | * @return The DotA 2 inventory for the given user 71 | * @throws SteamCondenserException if creating the inventory fails 72 | */ 73 | public static Dota2Inventory create(long steamId64, boolean fetchNow) 74 | throws SteamCondenserException { 75 | return (Dota2Inventory) create(APP_ID, steamId64, fetchNow, false); 76 | } 77 | 78 | /** 79 | * This checks the cache for an existing inventory. If it exists it is 80 | * returned. Otherwise a new inventory is created. 81 | * 82 | * @param vanityUrl The vanity URL of the user 83 | * @param fetchNow Whether the data should be fetched now 84 | * @param bypassCache Whether the cache should be bypassed 85 | * @return The DotA 2 inventory for the given user 86 | * @throws SteamCondenserException if creating the inventory fails 87 | */ 88 | public static Dota2Inventory create(String vanityUrl, boolean fetchNow, boolean bypassCache) 89 | throws SteamCondenserException { 90 | return (Dota2Inventory) create(APP_ID, vanityUrl, fetchNow, bypassCache); 91 | } 92 | 93 | /** 94 | * This checks the cache for an existing inventory. If it exists it is 95 | * returned. Otherwise a new inventory is created. 96 | * 97 | * @param steamId64 The 64bit Steam ID of the user 98 | * @param fetchNow Whether the data should be fetched now 99 | * @param bypassCache Whether the cache should be bypassed 100 | * @return The DotA 2 inventory for the given user 101 | * @throws SteamCondenserException if creating the inventory fails 102 | */ 103 | public static Dota2Inventory create(long steamId64, boolean fetchNow, boolean bypassCache) 104 | throws SteamCondenserException { 105 | return (Dota2Inventory) create(APP_ID, steamId64, fetchNow, bypassCache); 106 | } 107 | 108 | /** 109 | * Creates a new inventory instance for the player with the given Steam ID 110 | * 111 | * @param steamId64 The 64bit Steam ID of the user 112 | * @param fetchNow Whether the data should be fetched now 113 | * @see GameInventory#create 114 | * @throws WebApiException on Web API errors 115 | */ 116 | public Dota2Inventory(long steamId64, boolean fetchNow) 117 | throws SteamCondenserException { 118 | super(APP_ID, steamId64, fetchNow); 119 | } 120 | 121 | /** 122 | * Returns the item class for DotA 2 123 | * 124 | * @return The item class for DotA 2 is Dota2Item 125 | * @see Dota2Item 126 | */ 127 | protected Class getItemClass() { 128 | return Dota2Item.class; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2Inventory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2010-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 11 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 12 | import com.github.koraktor.steamcondenser.community.GameInventory; 13 | import com.github.koraktor.steamcondenser.community.GameItem; 14 | 15 | /** 16 | * Represents the inventory (aka. Backpack) of a Team Fortress 2 player 17 | * 18 | * @author Sebastian Staudt 19 | */ 20 | public class TF2Inventory extends GameInventory { 21 | 22 | public static final int APP_ID = 440; 23 | 24 | /** 25 | * This checks the cache for an existing inventory. If it exists it is 26 | * returned. Otherwise a new inventory is created. 27 | * 28 | * @param vanityUrl The vanity URL of the user 29 | * @return The Team Fortress 2 inventory for the given user 30 | * @throws SteamCondenserException if creating the inventory fails 31 | */ 32 | public static TF2Inventory create(String vanityUrl) 33 | throws SteamCondenserException { 34 | return (TF2Inventory) create(APP_ID, vanityUrl, true, false); 35 | } 36 | 37 | /** 38 | * This checks the cache for an existing inventory. If it exists it is 39 | * returned. Otherwise a new inventory is created. 40 | * 41 | * @param steamId64 The 64bit Steam ID of the user 42 | * @return The Team Fortress 2 inventory for the given user 43 | * @throws SteamCondenserException if creating the inventory fails 44 | */ 45 | public static TF2Inventory create(long steamId64) 46 | throws SteamCondenserException { 47 | return (TF2Inventory) create(APP_ID, steamId64, true, false); 48 | } 49 | 50 | /** 51 | * This checks the cache for an existing inventory. If it exists it is 52 | * returned. Otherwise a new inventory is created. 53 | * 54 | * @param vanityUrl The vanity URL of the user 55 | * @param fetchNow Whether the data should be fetched now 56 | * @return The Team Fortress 2 inventory for the given user 57 | * @throws SteamCondenserException if creating the inventory fails 58 | */ 59 | public static TF2Inventory create(String vanityUrl, boolean fetchNow) 60 | throws SteamCondenserException { 61 | return (TF2Inventory) create(APP_ID, vanityUrl, fetchNow, false); 62 | } 63 | 64 | /** 65 | * This checks the cache for an existing inventory. If it exists it is 66 | * returned. Otherwise a new inventory is created. 67 | * 68 | * @param steamId64 The 64bit Steam ID of the user 69 | * @param fetchNow Whether the data should be fetched now 70 | * @return The Team Fortress 2 inventory for the given user 71 | * @throws SteamCondenserException if creating the inventory fails 72 | */ 73 | public static TF2Inventory create(long steamId64, boolean fetchNow) 74 | throws SteamCondenserException { 75 | return (TF2Inventory) create(APP_ID, steamId64, fetchNow, false); 76 | } 77 | 78 | /** 79 | * This checks the cache for an existing inventory. If it exists it is 80 | * returned. Otherwise a new inventory is created. 81 | * 82 | * @param vanityUrl The vanity URL of the user 83 | * @param fetchNow Whether the data should be fetched now 84 | * @param bypassCache Whether the cache should be bypassed 85 | * @return The Team Fortress 2 inventory for the given user 86 | * @throws SteamCondenserException if creating the inventory fails 87 | */ 88 | public static TF2Inventory create(String vanityUrl, boolean fetchNow, boolean bypassCache) 89 | throws SteamCondenserException { 90 | return (TF2Inventory) create(APP_ID, vanityUrl, fetchNow, bypassCache); 91 | } 92 | 93 | /** 94 | * This checks the cache for an existing inventory. If it exists it is 95 | * returned. Otherwise a new inventory is created. 96 | * 97 | * @param steamId64 The 64bit Steam ID of the user 98 | * @param fetchNow Whether the data should be fetched now 99 | * @param bypassCache Whether the cache should be bypassed 100 | * @return The Team Fortress 2 inventory for the given user 101 | * @throws SteamCondenserException if creating the inventory fails 102 | */ 103 | public static TF2Inventory create(long steamId64, boolean fetchNow, boolean bypassCache) 104 | throws SteamCondenserException { 105 | return (TF2Inventory) create(APP_ID, steamId64, fetchNow, bypassCache); 106 | } 107 | 108 | /** 109 | * Creates a new inventory instance for the player with the given Steam ID 110 | * 111 | * @param steamId64 The 64bit Steam ID of the user 112 | * @param fetchNow Whether the data should be fetched now 113 | * @see GameInventory#create 114 | * @throws WebApiException on Web API errors 115 | */ 116 | public TF2Inventory(long steamId64, boolean fetchNow) 117 | throws SteamCondenserException { 118 | super(APP_ID, steamId64, fetchNow); 119 | } 120 | 121 | /** 122 | * Returns the item class for Team Fortress 2 123 | * 124 | * @return The item class for Team Fortress 2 is TF2Item 125 | * @see TF2Item 126 | */ 127 | protected Class getItemClass() { 128 | return TF2Item.class; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/portal2/Portal2Inventory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.portal2; 9 | 10 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 11 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 12 | import com.github.koraktor.steamcondenser.community.GameInventory; 13 | import com.github.koraktor.steamcondenser.community.GameItem; 14 | 15 | /** 16 | * Represents the inventory (aka. Robot Enrichment) of a Portal 2 player 17 | * 18 | * @author Sebastian Staudt 19 | */ 20 | public class Portal2Inventory extends GameInventory { 21 | 22 | public static final int APP_ID = 620; 23 | 24 | /** 25 | * This checks the cache for an existing inventory. If it exists it is 26 | * returned. Otherwise a new inventory is created. 27 | * 28 | * @param vanityUrl The vanity URL of the user 29 | * @return The Portal 2 inventory for the given user 30 | * @throws SteamCondenserException if creating the inventory fails 31 | */ 32 | public static Portal2Inventory create(String vanityUrl) 33 | throws SteamCondenserException { 34 | return (Portal2Inventory) create(APP_ID, vanityUrl, true, false); 35 | } 36 | 37 | /** 38 | * This checks the cache for an existing inventory. If it exists it is 39 | * returned. Otherwise a new inventory is created. 40 | * 41 | * @param steamId64 The 64bit Steam ID of the user 42 | * @return The Portal 2 inventory for the given user 43 | * @throws SteamCondenserException if creating the inventory fails 44 | */ 45 | public static Portal2Inventory create(long steamId64) 46 | throws SteamCondenserException { 47 | return (Portal2Inventory) create(APP_ID, steamId64, true, false); 48 | } 49 | 50 | /** 51 | * This checks the cache for an existing inventory. If it exists it is 52 | * returned. Otherwise a new inventory is created. 53 | * 54 | * @param vanityUrl The vanity URL of the user 55 | * @param fetchNow Whether the data should be fetched now 56 | * @return The Portal 2 inventory for the given user 57 | * @throws SteamCondenserException if creating the inventory fails 58 | */ 59 | public static Portal2Inventory create(String vanityUrl, boolean fetchNow) 60 | throws SteamCondenserException { 61 | return (Portal2Inventory) create(APP_ID, vanityUrl, fetchNow, false); 62 | } 63 | 64 | /** 65 | * This checks the cache for an existing inventory. If it exists it is 66 | * returned. Otherwise a new inventory is created. 67 | * 68 | * @param steamId64 The 64bit Steam ID of the user 69 | * @param fetchNow Whether the data should be fetched now 70 | * @return The Portal 2 inventory for the given user 71 | * @throws SteamCondenserException if creating the inventory fails 72 | */ 73 | public static Portal2Inventory create(long steamId64, boolean fetchNow) 74 | throws SteamCondenserException { 75 | return (Portal2Inventory) create(APP_ID, steamId64, fetchNow, false); 76 | } 77 | 78 | /** 79 | * This checks the cache for an existing inventory. If it exists it is 80 | * returned. Otherwise a new inventory is created. 81 | * 82 | * @param vanityUrl The vanity URL of the user 83 | * @param fetchNow Whether the data should be fetched now 84 | * @param bypassCache Whether the cache should be bypassed 85 | * @return The Portal 2 inventory for the given user 86 | * @throws SteamCondenserException if creating the inventory fails 87 | */ 88 | public static Portal2Inventory create(String vanityUrl, boolean fetchNow, boolean bypassCache) 89 | throws SteamCondenserException { 90 | return (Portal2Inventory) create(APP_ID, vanityUrl, fetchNow, bypassCache); 91 | } 92 | 93 | /** 94 | * This checks the cache for an existing inventory. If it exists it is 95 | * returned. Otherwise a new inventory is created. 96 | * 97 | * @param steamId64 The 64bit Steam ID of the user 98 | * @param fetchNow Whether the data should be fetched now 99 | * @param bypassCache Whether the cache should be bypassed 100 | * @return The Portal 2 inventory for the given user 101 | * @throws SteamCondenserException if creating the inventory fails 102 | */ 103 | public static Portal2Inventory create(long steamId64, boolean fetchNow, boolean bypassCache) 104 | throws SteamCondenserException { 105 | return (Portal2Inventory) create(APP_ID, steamId64, fetchNow, bypassCache); 106 | } 107 | 108 | /** 109 | * Creates a new inventory instance for the player with the given Steam ID 110 | * 111 | * @param steamId64 The 64bit Steam ID of the user 112 | * @param fetchNow Whether the data should be fetched now 113 | * @see GameInventory#create 114 | * @throws WebApiException on Web API errors 115 | */ 116 | public Portal2Inventory(long steamId64, boolean fetchNow) 117 | throws SteamCondenserException { 118 | super(APP_ID, steamId64, fetchNow); 119 | } 120 | 121 | /** 122 | * Returns the item class for Portal 2 123 | * 124 | * @return The item class for Portal 2 is Portal2Item 125 | * @see Portal2Item 126 | */ 127 | protected Class getItemClass() { 128 | return Portal2Item.class; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/dota2/Dota2BetaInventory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2012-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.dota2; 9 | 10 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 11 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 12 | import com.github.koraktor.steamcondenser.community.GameInventory; 13 | import com.github.koraktor.steamcondenser.community.GameItem; 14 | 15 | /** 16 | * Represents the inventory of a player of the DotA 2 beta 17 | * 18 | * @author Sebastian Staudt 19 | */ 20 | public class Dota2BetaInventory extends GameInventory { 21 | 22 | public static final int APP_ID = 205790; 23 | 24 | /** 25 | * This checks the cache for an existing inventory. If it exists it is 26 | * returned. Otherwise a new inventory is created. 27 | * 28 | * @param vanityUrl The vanity URL of the user 29 | * @return The DotA 2 Beta inventory for the given user 30 | * @throws SteamCondenserException if creating the inventory fails 31 | */ 32 | public static Dota2BetaInventory create(String vanityUrl) 33 | throws SteamCondenserException { 34 | return (Dota2BetaInventory) create(APP_ID, vanityUrl, true, false); 35 | } 36 | 37 | /** 38 | * This checks the cache for an existing inventory. If it exists it is 39 | * returned. Otherwise a new inventory is created. 40 | * 41 | * @param steamId64 The 64bit Steam ID of the user 42 | * @return The DotA 2 Beta inventory for the given user 43 | * @throws SteamCondenserException if creating the inventory fails 44 | */ 45 | public static Dota2BetaInventory create(long steamId64) 46 | throws SteamCondenserException { 47 | return (Dota2BetaInventory) create(APP_ID, steamId64, true, false); 48 | } 49 | 50 | /** 51 | * This checks the cache for an existing inventory. If it exists it is 52 | * returned. Otherwise a new inventory is created. 53 | * 54 | * @param vanityUrl The vanity URL of the user 55 | * @param fetchNow Whether the data should be fetched now 56 | * @return The DotA 2 Beta inventory for the given user 57 | * @throws SteamCondenserException if creating the inventory fails 58 | */ 59 | public static Dota2BetaInventory create(String vanityUrl, boolean fetchNow) 60 | throws SteamCondenserException { 61 | return (Dota2BetaInventory) create(APP_ID, vanityUrl, fetchNow, false); 62 | } 63 | 64 | /** 65 | * This checks the cache for an existing inventory. If it exists it is 66 | * returned. Otherwise a new inventory is created. 67 | * 68 | * @param steamId64 The 64bit Steam ID of the user 69 | * @param fetchNow Whether the data should be fetched now 70 | * @return The DotA 2 Beta inventory for the given user 71 | * @throws SteamCondenserException if creating the inventory fails 72 | */ 73 | public static Dota2BetaInventory create(long steamId64, boolean fetchNow) 74 | throws SteamCondenserException { 75 | return (Dota2BetaInventory) create(APP_ID, steamId64, fetchNow, false); 76 | } 77 | 78 | /** 79 | * This checks the cache for an existing inventory. If it exists it is 80 | * returned. Otherwise a new inventory is created. 81 | * 82 | * @param vanityUrl The vanity URL of the user 83 | * @param fetchNow Whether the data should be fetched now 84 | * @param bypassCache Whether the cache should be bypassed 85 | * @return The DotA 2 Beta inventory for the given user 86 | * @throws SteamCondenserException if creating the inventory fails 87 | */ 88 | public static Dota2BetaInventory create(String vanityUrl, boolean fetchNow, boolean bypassCache) 89 | throws SteamCondenserException { 90 | return (Dota2BetaInventory) create(APP_ID, vanityUrl, fetchNow, bypassCache); 91 | } 92 | 93 | /** 94 | * This checks the cache for an existing inventory. If it exists it is 95 | * returned. Otherwise a new inventory is created. 96 | * 97 | * @param steamId64 The 64bit Steam ID of the user 98 | * @param fetchNow Whether the data should be fetched now 99 | * @param bypassCache Whether the cache should be bypassed 100 | * @return The DotA 2 Beta inventory for the given user 101 | * @throws SteamCondenserException if creating the inventory fails 102 | */ 103 | public static Dota2BetaInventory create(long steamId64, boolean fetchNow, boolean bypassCache) 104 | throws SteamCondenserException { 105 | return (Dota2BetaInventory) create(APP_ID, steamId64, fetchNow, bypassCache); 106 | } 107 | 108 | /** 109 | * Creates a new inventory instance for the player with the given Steam ID 110 | * 111 | * @param steamId64 The 64bit Steam ID of the user 112 | * @param fetchNow Whether the data should be fetched now 113 | * @see GameInventory#create 114 | * @throws WebApiException on Web API errors 115 | */ 116 | public Dota2BetaInventory(long steamId64, boolean fetchNow) 117 | throws SteamCondenserException { 118 | super(APP_ID, steamId64, fetchNow); 119 | } 120 | 121 | /** 122 | * Returns the item class for DotA 2 123 | * 124 | * @return The item class for DotA 2 is Dota2Item 125 | * @see Dota2Item 126 | */ 127 | protected Class getItemClass() { 128 | return Dota2Item.class; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/github/koraktor/steamcondenser/community/tf2/TF2BetaInventory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is free software; you can redistribute it and/or modify it under 3 | * the terms of the new BSD License. 4 | * 5 | * Copyright (c) 2011-2013, Sebastian Staudt 6 | */ 7 | 8 | package com.github.koraktor.steamcondenser.community.tf2; 9 | 10 | import com.github.koraktor.steamcondenser.exceptions.SteamCondenserException; 11 | import com.github.koraktor.steamcondenser.exceptions.WebApiException; 12 | import com.github.koraktor.steamcondenser.community.GameInventory; 13 | import com.github.koraktor.steamcondenser.community.GameItem; 14 | 15 | /** 16 | * Represents the inventory (aka. Backpack) of a player of the public Team 17 | * Fortress 2 beta 18 | * 19 | * @author Sebastian Staudt 20 | */ 21 | public class TF2BetaInventory extends GameInventory { 22 | 23 | public static final int APP_ID = 520; 24 | 25 | /** 26 | * This checks the cache for an existing inventory. If it exists it is 27 | * returned. Otherwise a new inventory is created. 28 | * 29 | * @param vanityUrl The vanity URL of the user 30 | * @return The Team Fortress 2 Beta inventory for the given user 31 | * @throws SteamCondenserException if creating the inventory fails 32 | */ 33 | public static TF2BetaInventory create(String vanityUrl) 34 | throws SteamCondenserException { 35 | return (TF2BetaInventory) create(APP_ID, vanityUrl, true, false); 36 | } 37 | 38 | /** 39 | * This checks the cache for an existing inventory. If it exists it is 40 | * returned. Otherwise a new inventory is created. 41 | * 42 | * @param steamId64 The 64bit Steam ID of the user 43 | * @return The Team Fortress 2 Beta inventory for the given user 44 | * @throws SteamCondenserException if creating the inventory fails 45 | */ 46 | public static TF2BetaInventory create(long steamId64) 47 | throws SteamCondenserException { 48 | return (TF2BetaInventory) create(APP_ID, steamId64, true, false); 49 | } 50 | 51 | /** 52 | * This checks the cache for an existing inventory. If it exists it is 53 | * returned. Otherwise a new inventory is created. 54 | * 55 | * @param vanityUrl The vanity URL of the user 56 | * @param fetchNow Whether the data should be fetched now 57 | * @return The Team Fortress 2 Beta inventory for the given user 58 | * @throws SteamCondenserException if creating the inventory fails 59 | */ 60 | public static TF2BetaInventory create(String vanityUrl, boolean fetchNow) 61 | throws SteamCondenserException { 62 | return (TF2BetaInventory) create(APP_ID, vanityUrl, fetchNow, false); 63 | } 64 | 65 | /** 66 | * This checks the cache for an existing inventory. If it exists it is 67 | * returned. Otherwise a new inventory is created. 68 | * 69 | * @param steamId64 The 64bit Steam ID of the user 70 | * @param fetchNow Whether the data should be fetched now 71 | * @return The Team Fortress 2 Beta inventory for the given user 72 | * @throws SteamCondenserException if creating the inventory fails 73 | */ 74 | public static TF2BetaInventory create(long steamId64, boolean fetchNow) 75 | throws SteamCondenserException { 76 | return (TF2BetaInventory) create(APP_ID, steamId64, fetchNow, false); 77 | } 78 | 79 | /** 80 | * This checks the cache for an existing inventory. If it exists it is 81 | * returned. Otherwise a new inventory is created. 82 | * 83 | * @param vanityUrl The vanity URL of the user 84 | * @param fetchNow Whether the data should be fetched now 85 | * @param bypassCache Whether the cache should be bypassed 86 | * @return The Team Fortress 2 Beta inventory for the given user 87 | * @throws SteamCondenserException if creating the inventory fails 88 | */ 89 | public static TF2BetaInventory create(String vanityUrl, boolean fetchNow, boolean bypassCache) 90 | throws SteamCondenserException { 91 | return (TF2BetaInventory) create(APP_ID, vanityUrl, fetchNow, bypassCache); 92 | } 93 | 94 | /** 95 | * This checks the cache for an existing inventory. If it exists it is 96 | * returned. Otherwise a new inventory is created. 97 | * 98 | * @param steamId64 The 64bit Steam ID of the user 99 | * @param fetchNow Whether the data should be fetched now 100 | * @param bypassCache Whether the cache should be bypassed 101 | * @return The Team Fortress 2 Beta inventory for the given user 102 | * @throws SteamCondenserException if creating the inventory fails 103 | */ 104 | public static TF2BetaInventory create(long steamId64, boolean fetchNow, boolean bypassCache) 105 | throws SteamCondenserException { 106 | return (TF2BetaInventory) create(APP_ID, steamId64, fetchNow, bypassCache); 107 | } 108 | 109 | /** 110 | * Creates a new inventory instance for the player with the given Steam ID 111 | * 112 | * @param steamId64 The 64bit Steam ID of the user 113 | * @param fetchNow Whether the data should be fetched now 114 | * @see GameInventory#create 115 | * @throws WebApiException on Web API errors 116 | */ 117 | public TF2BetaInventory(long steamId64, boolean fetchNow) 118 | throws SteamCondenserException { 119 | super(APP_ID, steamId64, fetchNow); 120 | } 121 | 122 | /** 123 | * Returns the item class for Team Fortress 2 124 | * 125 | * @return The item class for Team Fortress 2 is TF2Item 126 | * @see TF2Item 127 | */ 128 | protected Class getItemClass() { 129 | return TF2Item.class; 130 | } 131 | 132 | } 133 | --------------------------------------------------------------------------------