├── .dockerignore ├── .gitattributes ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── Vagrantfile ├── cacert.pem ├── cheat.bat ├── cheat.php ├── cheat.py ├── downloadphp.ps1 ├── downloadpython.ps1 └── python-cheat.bat /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .gitattributes 4 | 5 | LICENSE 6 | README.md 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | token.txt 2 | *.swp 3 | php/ 4 | php.zip 5 | python/ 6 | python.zip 7 | get-pip.py 8 | .vagrant 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.2-cli-stretch 2 | 3 | WORKDIR /app 4 | COPY . . 5 | 6 | CMD [ "php", "/app/cheat.php" ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Steam Database 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to use this 2 | 3 | ## First steps 4 | 5 | 1. Join https://steamcommunity.com/groups/SteamDB (needed to represent captures) 6 | 2. Open https://steamcommunity.com/saliengame/gettoken and save it (Ctrl+S) as `token.txt` in the same folder as `cheat.php` 7 | 8 | ## PHP 9 | 10 | ### Windows 11 | 12 | 1. [Download this script](https://github.com/SteamDatabase/SalienCheat/archive/master.zip) 13 | 2. Extract it into a new folder 14 | 3. Click `cheat.bat` and follow instructions 15 | 16 | If that fails for any reason, or you still have questions, [check out this Google doc for commonly asked questions](https://docs.google.com/document/d/1DQx6K-SmfkF_fsy4sS-vMkUlqGrABolOpOtvAYaU3nU/preview). 17 | 18 | ### Mac 19 | 20 | 0. (optional) Launch the App Store and download any updates for macOS. Newer versions of macOS have php and curl included by default 21 | 1. Extract the contents of this script to the Downloads folder 22 | 2. Launch Terminal and run the script: `php downloads/cheat.php` 23 | 24 | You can also provide token directly in CLI, to ease running multiple accounts: 25 | ```bash 26 | php cheat.php token1 accountid1 27 | php cheat.php token2 accountid2 28 | ``` 29 | 30 | ### Linux 31 | 32 | 1. Install `php-curl` and enable it in `php.ini` 33 | 2. You know what you are doing. 🐧 34 | 35 | ## Python 36 | 37 | ⚠ **Python version currently does not support Boss battles, so you should choose the PHP version.** ⚠ 38 | 39 | ### Windows 40 | 41 | 1. [Download this script](https://github.com/SteamDatabase/SalienCheat/archive/master.zip) 42 | 2. Extract it into a new folder 43 | 3. Click `python-cheat.bat` and follow instructions 44 | 45 | ### Linux/Cygwin 46 | 47 | 0. (optional) Setup virtual env: `virtualenv env && source env/bin/activate` 48 | 1. `pip install requests tqdm` 49 | 2. Run the script: `python cheat.py [token]` 50 | 51 | ### Mac 52 | 53 | 0. (optional) Launch the App Store and download any updates for macOS. Newer versions of macOS have Python 2.7.10 included by default. 54 | 1. Extract the contents of this script to the Downloads folder. 55 | 2. Launch Terminal and run the following scripts: 56 | 1. `sudo easy_install pip` 57 | 2. `pip install requests tqdm` 58 | 3. `python downloads/cheat.py [token]` 59 | 60 | ## Vagrant 61 | 62 | 1. Install [vagrant](https://www.vagrantup.com/downloads.html) and [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 63 | 2. Run `vagrant up` to setup VM 64 | 3. Run cheat 65 | * For PHP `vagrant ssh -c 'php cheat.php [token]` 66 | * For Python `vagrant ssh -c 'python3 cheat.py [token]` 67 | 68 | ## Docker 69 | 1. Extract contents of this script somewhere. 70 | 2. To build: `docker build . -t steamdb/saliencheat` 71 | 3. To run: `docker run -it --init --rm -e TOKEN=<32 character token from gettoken url> steamdb/saliencheat` 72 | 4. To stop running, Ctrl+C 73 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.define :ubuntu do |box| 6 | box.vm.box = "bento/ubuntu-18.04" 7 | box.vm.host_name = 'salien-cheat-box' 8 | box.vm.synced_folder ".", "/home/vagrant/salien" 9 | 10 | box.vm.provision "shell", inline: <<-SHELL 11 | set -o xtrace 12 | apt-get update 13 | apt-get install -y php-cli php-curl python-pip python3-pip 14 | pip2 install requests tqdm 15 | pip3 install requests tqdm 16 | SHELL 17 | 18 | box.vm.provision "shell", privileged: false, inline: <<-SHELL 19 | ln -s /home/vagrant/salien/cheat.php 20 | ln -s /home/vagrant/salien/cheat.py 21 | ln -s /home/vagrant/salien/token.txt 22 | SHELL 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /cheat.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd %~dp0 3 | setlocal enabledelayedexpansion 4 | 5 | if not exist php\php.exe ( 6 | echo PHP wasn't detected; we'll download and install it for you. 7 | PowerShell -ExecutionPolicy Unrestricted -File "downloadphp.ps1" 8 | ) 9 | 10 | if not exist php\php.exe ( 11 | echo Failed to setup php, try doing it manually 12 | pause 13 | exit 14 | ) 15 | 16 | if not exist token.txt ( 17 | echo( 18 | echo Go to https://steamcommunity.com/saliengame/gettoken and save that page as token.txt file 19 | echo( 20 | pause 21 | ) 22 | 23 | echo The script can be terminated at any time by pressing Ctrl-C 24 | 25 | php\php.exe -f cheat.php 26 | pause 27 | -------------------------------------------------------------------------------- /cheat.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 1 ) 28 | { 29 | $Token = $argv[ 1 ]; 30 | 31 | if( $argc > 2 ) 32 | { 33 | $AccountID = $argv[ 2 ]; 34 | } 35 | } 36 | else if( isset( $_SERVER[ 'TOKEN' ] ) ) 37 | { 38 | // if the token was provided as an env var, use it 39 | $Token = $_SERVER[ 'TOKEN' ]; 40 | } 41 | else 42 | { 43 | // otherwise, read it from disk 44 | $Token = trim( file_get_contents( __DIR__ . '/token.txt' ) ); 45 | $ParsedToken = json_decode( $Token, true ); 46 | 47 | if( is_string( $ParsedToken ) ) 48 | { 49 | $Token = $ParsedToken; 50 | } 51 | else if( isset( $ParsedToken[ 'token' ] ) ) 52 | { 53 | $Token = $ParsedToken[ 'token' ]; 54 | $AccountID = GetAccountID( $ParsedToken[ 'steamid' ] ); 55 | 56 | Msg( 'Your SteamID is {teal}' . $ParsedToken[ 'steamid' ] . '{normal} - AccountID is {teal}' . $AccountID ); 57 | 58 | if( $AccountID == 0 && $ParsedToken[ 'steamid' ] > 0 ) 59 | { 60 | Msg( '{lightred}Looks like you are using 32bit PHP. Try enabling "gmp" module for correct accountid calculation.' ); 61 | } 62 | } 63 | 64 | unset( $ParsedToken ); 65 | } 66 | 67 | if( strlen( $Token ) !== 32 ) 68 | { 69 | Msg( 'Failed to find your token. Verify token.txt' ); 70 | exit( 1 ); 71 | } 72 | 73 | $LocalScriptHash = sha1( trim( file_get_contents( __FILE__ ) ) ); 74 | Msg( '{teal}File hash is ' . substr( $LocalScriptHash, 0, 8 ) ); 75 | 76 | if( isset( $_SERVER[ 'IGNORE_UPDATES' ] ) && (bool)$_SERVER[ 'IGNORE_UPDATES' ] ) 77 | { 78 | $UpdateCheck = false; 79 | } 80 | else 81 | { 82 | $UpdateCheck = true; 83 | $RepositoryScriptETag = ''; 84 | $RepositoryScriptHash = GetRepositoryScriptHash( $RepositoryScriptETag, $LocalScriptHash ); 85 | } 86 | 87 | // 10/10 code 88 | $DisableColors = !( 89 | ( function_exists( 'sapi_windows_vt100_support' ) && sapi_windows_vt100_support( STDOUT ) ) || 90 | ( function_exists( 'stream_isatty' ) && stream_isatty( STDOUT ) ) || 91 | ( function_exists( 'posix_isatty' ) && posix_isatty( STDOUT ) ) || 92 | ( false !== getenv( 'ANSICON' ) ) || 93 | ( 'ON' === getenv( 'ConEmuANSI' ) ) || 94 | ( substr( getenv( 'TERM' ), 0, 5 ) === 'xterm' ) 95 | ); 96 | 97 | if( isset( $_SERVER[ 'DISABLE_COLORS' ] ) ) 98 | { 99 | $DisableColors = (bool)$_SERVER[ 'DISABLE_COLORS' ]; 100 | } 101 | 102 | $GameVersion = 2; 103 | $WaitTime = 110; 104 | $FailSleep = 3; 105 | $OldScore = 0; 106 | $LastKnownPlanet = 0; 107 | $BestPlanetAndZone = 0; 108 | 109 | if( ini_get( 'precision' ) < 18 ) 110 | { 111 | Msg( '{teal}Fixed php float precision (was ' . ini_get( 'precision' ) . ')' ); 112 | ini_set( 'precision', '18' ); 113 | } 114 | 115 | do 116 | { 117 | $Data = SendPOST( 'ITerritoryControlMinigameService/GetPlayerInfo', 'access_token=' . $Token ); 118 | 119 | if( isset( $Data[ 'response' ][ 'score' ] ) ) 120 | { 121 | $OldScore = $Data[ 'response' ][ 'score' ]; 122 | 123 | if( !isset( $Data[ 'response' ][ 'clan_info' ][ 'accountid' ] ) ) 124 | { 125 | Msg( '{green}-- You are currently not representing any clan, so you are now part of SteamDB' ); 126 | Msg( '{green}-- Make sure to join{yellow} https://steamcommunity.com/groups/steamdb {green}on Steam' ); 127 | 128 | SendPOST( 'ITerritoryControlMinigameService/RepresentClan', 'clanid=4777282&access_token=' . $Token ); 129 | } 130 | else if( $Data[ 'response' ][ 'clan_info' ][ 'accountid' ] != 4777282 ) 131 | { 132 | Msg( '{green}-- If you want to support us, join our group' ); 133 | Msg( '{green}--{yellow} https://steamcommunity.com/groups/steamdb' ); 134 | Msg( '{green}-- and set us as your clan on' ); 135 | Msg( '{green}--{yellow} https://steamcommunity.com/saliengame/play' ); 136 | Msg( '{green}-- Happy farming!' ); 137 | } 138 | } 139 | } 140 | while( !isset( $Data[ 'response' ][ 'score' ] ) && sleep( $FailSleep ) === 0 ); 141 | 142 | do 143 | { 144 | if( !$BestPlanetAndZone ) 145 | { 146 | do 147 | { 148 | $BestPlanetAndZone = GetBestPlanetAndZone( $WaitTime, $FailSleep ); 149 | } 150 | while( !$BestPlanetAndZone && sleep( $FailSleep ) === 0 ); 151 | } 152 | 153 | echo PHP_EOL; 154 | 155 | // Only get player info and leave current planet if it changed 156 | if( $LastKnownPlanet !== $BestPlanetAndZone[ 'id' ] ) 157 | { 158 | do 159 | { 160 | // Leave current game before trying to switch planets (it will report InvalidState otherwise) 161 | $SteamThinksPlanet = LeaveCurrentGame( $Token, $FailSleep, $BestPlanetAndZone[ 'id' ] ); 162 | 163 | if( $BestPlanetAndZone[ 'id' ] !== $SteamThinksPlanet ) 164 | { 165 | SendPOST( 'ITerritoryControlMinigameService/JoinPlanet', 'id=' . $BestPlanetAndZone[ 'id' ] . '&access_token=' . $Token ); 166 | 167 | $SteamThinksPlanet = LeaveCurrentGame( $Token, $FailSleep ); 168 | } 169 | } 170 | while( $BestPlanetAndZone[ 'id' ] !== $SteamThinksPlanet && sleep( $FailSleep ) === 0 ); 171 | 172 | $LastKnownPlanet = $BestPlanetAndZone[ 'id' ]; 173 | } 174 | 175 | if( $BestPlanetAndZone[ 'best_zone' ][ 'boss_active' ] ) 176 | { 177 | $Zone = SendPOST( 'ITerritoryControlMinigameService/JoinBossZone', 'zone_position=' . $BestPlanetAndZone[ 'best_zone' ][ 'zone_position' ] . '&access_token=' . $Token ); 178 | 179 | if( $Zone[ 'eresult' ] != 1 ) 180 | { 181 | Msg( '{lightred}!! Failed to join boss zone, rescanning and restarting...' ); 182 | 183 | $BestPlanetAndZone = 0; 184 | 185 | sleep( $FailSleep ); 186 | 187 | continue; 188 | } 189 | 190 | // Avoid first time not sync error 191 | sleep( 4 ); 192 | 193 | $BossFailsAllowed = 10; 194 | $NextHeal = PHP_INT_MAX; 195 | $WaitingForPlayers = true; 196 | $MyScoreInBoss = 0; 197 | $BossEstimate = 198 | [ 199 | 'InitHP' => 0, 200 | 'PrevHP' => 0, 201 | 'PrevXP' => 0, 202 | 'DeltHP' => [], 203 | 'DeltXP' => [] 204 | ]; 205 | 206 | do 207 | { 208 | $Time = microtime( true ); 209 | $UseHeal = 0; 210 | $DamageToBoss = $WaitingForPlayers ? 0 : 1; 211 | 212 | $DamageTaken = 0; 213 | 214 | if( $Time >= $NextHeal ) 215 | { 216 | $UseHeal = 1; 217 | $NextHeal = $Time + 120; 218 | } 219 | 220 | $Data = SendPOST( 'ITerritoryControlMinigameService/ReportBossDamage', 'access_token=' . $Token . '&use_heal_ability=' . $UseHeal . '&damage_to_boss=' . $DamageToBoss . '&damage_taken=' . $DamageTaken ); 221 | 222 | if( $Data[ 'eresult' ] == 11 ) 223 | { 224 | Msg( '{green}@@ Got invalid state, restarting...' ); 225 | 226 | break; 227 | } 228 | 229 | if( $Data[ 'eresult' ] != 1 && $BossFailsAllowed-- < 1 ) 230 | { 231 | Msg( '{green}@@ Boss battle errored too much, restarting...' ); 232 | 233 | break; 234 | } 235 | 236 | if( empty( $Data[ 'response' ][ 'boss_status' ] ) ) 237 | { 238 | Msg( '{green}@@ Waiting...' ); 239 | continue; 240 | } 241 | 242 | if( $Data[ 'response' ][ 'waiting_for_players' ] ) 243 | { 244 | $WaitingForPlayers = true; 245 | Msg( '{green}@@ Waiting for players...' ); 246 | continue; 247 | } 248 | else if( $WaitingForPlayers ) 249 | { 250 | $WaitingForPlayers = false; 251 | $NextHeal = $Time + random_int( 0, 120 ); 252 | } 253 | 254 | // Strip names down to basic ASCII. 255 | $RegMask = '/[\x00-\x1F\x7F-\xFF]/'; 256 | 257 | usort( $Data[ 'response' ][ 'boss_status' ][ 'boss_players' ], function( $a, $b ) use( $AccountID ) 258 | { 259 | if( $a[ 'accountid' ] == $AccountID ) 260 | { 261 | return 1; 262 | } 263 | else if( $b[ 'accountid' ] == $AccountID ) 264 | { 265 | return -1; 266 | } 267 | 268 | return $b[ 'accountid' ] - $a[ 'accountid' ]; 269 | } ); 270 | 271 | $MyPlayer = null; 272 | 273 | foreach( $Data[ 'response' ][ 'boss_status' ][ 'boss_players' ] as $Player ) 274 | { 275 | $IsThisMe = $Player[ 'accountid' ] == $AccountID; 276 | $DefaultColor = $IsThisMe ? '{green}' : '{normal}'; 277 | 278 | if( $IsThisMe ) 279 | { 280 | $MyPlayer = $Player; 281 | } 282 | 283 | $Name = trim( preg_replace( $RegMask, '', $Player[ 'name' ] ) ); 284 | 285 | Msg( 286 | ( $IsThisMe ? '{green}@@' : ' ' ) . 287 | ' %-20s - HP: {yellow}%6s' . $DefaultColor . ' / %6s - XP Gained: {yellow}%10s' . $DefaultColor, 288 | PHP_EOL, 289 | [ 290 | empty( $Name ) ? ( '[U:1:' . $Player[ 'accountid' ] . ']' ) : substr( $Name, 0, 20 ), 291 | $Player[ 'hp' ], 292 | $Player[ 'max_hp' ], 293 | number_format( $Player[ 'xp_earned' ] ) 294 | ] 295 | ); 296 | } 297 | 298 | if( $MyPlayer !== null ) 299 | { 300 | $MyScoreInBoss = $MyPlayer[ 'score_on_join' ] + $MyPlayer[ 'xp_earned' ]; 301 | 302 | Msg( '@@ Started XP: ' . number_format( $MyPlayer[ 'score_on_join' ] ) . ' {teal}(L' . $MyPlayer[ 'level_on_join' ] . '){normal} - Current XP: {yellow}' . number_format( $MyScoreInBoss ) . ' ' . ( $MyPlayer[ 'level_on_join' ] != $MyPlayer[ 'new_level' ] ? '{green}' : '{teal}' ) . '(L' . $MyPlayer[ 'new_level' ] . ')' ); 303 | 304 | if( $MyPlayer[ 'hp' ] <= 0 ) 305 | { 306 | Msg( '{lightred}!! You died, restarting...' ); 307 | 308 | break; 309 | } 310 | } 311 | 312 | if( $Data[ 'response' ][ 'game_over' ] ) 313 | { 314 | Msg( '{green}@@ Boss battle is over.' ); 315 | 316 | break; 317 | } 318 | 319 | // Boss XP, DPS and Time Estimation 320 | if( $BossEstimate[ 'PrevXP' ] > 0 ) 321 | { 322 | // Calculate HP and XP change per game tick 323 | $BossEstimate[ 'DeltHP' ][] = abs( $BossEstimate[ 'PrevHP' ] - $Data[ 'response' ][ 'boss_status' ][ 'boss_hp' ] ); 324 | $BossEstimate[ 'DeltXP' ][] = ( $MyPlayer !== null ? abs( $BossEstimate[ 'PrevXP' ] - $MyPlayer[ 'xp_earned' ] ) : 1 ); 325 | 326 | // Calculate XP rate, Boss damage per game tick (2500xp/tick fallback for players without $AccountID) and game ticks Remaining 327 | $EstXPRate = ( $MyPlayer !== null ? array_sum( $BossEstimate[ 'DeltXP' ] ) / count( $BossEstimate[ 'DeltXP' ] ) : 2500 ); 328 | $EstBossDPT = array_sum( $BossEstimate[ 'DeltHP' ] ) / count( $BossEstimate[ 'DeltHP' ] ); 329 | $EstTickRemain = $Data[ 'response' ][ 'boss_status' ][ 'boss_hp' ] / $EstBossDPT; 330 | 331 | // Calculate Total XP Reward for Boss 332 | $EstXPTotal = ( $MyPlayer !== null ? $MyPlayer[ 'xp_earned' ] + ( $EstTickRemain * $EstXPRate ) : ( $BossEstimate[ 'InitHP' ] / $EstBossDPT ) * $EstXPRate ); 333 | 334 | // Display Estimated XP and DPS 335 | Msg( '@@ Estimated Final XP: {lightred}' . number_format( $EstXPTotal ) . "{normal} ({yellow}+" . number_format( $EstXPRate ) . "{normal}/tick excl. bonuses) - Damage per Second: {green}" . number_format( $EstBossDPT / 5 ) ); 336 | 337 | // Display Estimated Time Remaining 338 | Msg( '@@ Estimated Time Remaining: {teal}' . gmdate( 'H:i:s', $EstTickRemain * 5 ) ); 339 | 340 | // Only keep the last 1 minute of game time (12 ticks) in BossEstimate 341 | if( count( $BossEstimate[ 'DeltHP' ] ) >= 12 ) 342 | { 343 | array_shift( $BossEstimate[ 'DeltHP' ] ); 344 | array_shift( $BossEstimate[ 'DeltXP' ] ); 345 | } 346 | } 347 | 348 | // Set Initial HP Once, Log HP and XP every tick 349 | $BossEstimate[ 'InitHP' ] = ( $BossEstimate[ 'InitHP' ] ?: $Data[ 'response' ][ 'boss_status' ][ 'boss_hp' ] ); 350 | $BossEstimate[ 'PrevHP' ] = $Data[ 'response' ][ 'boss_status' ][ 'boss_hp' ]; 351 | $BossEstimate[ 'PrevXP' ] = ( $MyPlayer !== null ? $MyPlayer[ 'xp_earned' ] : 1 ); 352 | 353 | Msg( '@@ Boss HP: {green}' . number_format( $Data[ 'response' ][ 'boss_status' ][ 'boss_hp' ] ) . '{normal} / {lightred}' . number_format( $Data[ 'response' ][ 'boss_status' ][ 'boss_max_hp' ] ) . '{normal} - Lasers: {yellow}' . $Data[ 'response' ][ 'num_laser_uses' ] . '{normal} - Team Heals: {green}' . $Data[ 'response' ][ 'num_team_heals' ] ); 354 | 355 | Msg( '{normal}@@ Damage sent: {green}' . $DamageToBoss . '{normal} - ' . ( $UseHeal ? '{green}Used heal ability!' : 'Next heal in {green}' . round( $NextHeal - $Time ) . '{normal} seconds' ) ); 356 | 357 | echo PHP_EOL; 358 | } 359 | while( BossSleep( $c ) ); 360 | 361 | // Boss battle is over, reset state and scan again 362 | $BestPlanetAndZone = 0; 363 | $LastKnownPlanet = 0; 364 | 365 | unset( $BossEstimate ); 366 | 367 | if( $MyScoreInBoss > 0 ) 368 | { 369 | Msg( 370 | '++ Your Score after Boss battle: {lightred}' . number_format( $MyScoreInBoss ) . 371 | '{yellow} (+' . number_format( $MyScoreInBoss - $OldScore ) . ')' 372 | ); 373 | 374 | $OldScore = $MyScoreInBoss; 375 | } 376 | 377 | continue; 378 | } 379 | 380 | $Zone = SendPOST( 'ITerritoryControlMinigameService/JoinZone', 'zone_position=' . $BestPlanetAndZone[ 'best_zone' ][ 'zone_position' ] . '&access_token=' . $Token ); 381 | $PlanetCheckTime = microtime( true ); 382 | 383 | // Rescan planets if joining failed 384 | if( empty( $Zone[ 'response' ][ 'zone_info' ] ) ) 385 | { 386 | Msg( '{lightred}!! Failed to join a zone, rescanning and restarting...' ); 387 | 388 | $BestPlanetAndZone = 0; 389 | 390 | sleep( $FailSleep ); 391 | 392 | continue; 393 | } 394 | 395 | $Zone = $Zone[ 'response' ][ 'zone_info' ]; 396 | 397 | Msg( 398 | '++ Joined Zone {yellow}' . $Zone[ 'zone_position' ] . 399 | '{normal} on Planet {green}' . $BestPlanetAndZone[ 'id' ] . 400 | '{normal} - Captured: {yellow}' . number_format( empty( $Zone[ 'capture_progress' ] ) ? 0.0 : ( $Zone[ 'capture_progress' ] * 100 ), 2 ) . '%' . 401 | '{normal} - Difficulty: {yellow}' . GetNameForDifficulty( $Zone ) 402 | ); 403 | 404 | $SkippedLagTime = curl_getinfo( $c, CURLINFO_TOTAL_TIME ) - curl_getinfo( $c, CURLINFO_STARTTRANSFER_TIME ); 405 | $SkippedLagTime -= fmod( $SkippedLagTime, 0.1 ); 406 | $LagAdjustedWaitTime = $WaitTime - $SkippedLagTime; 407 | $WaitTimeBeforeFirstScan = $WaitTime - $SkippedLagTime - 10; 408 | 409 | if( $UpdateCheck ) 410 | { 411 | if( $LocalScriptHash === $RepositoryScriptHash ) 412 | { 413 | $RepositoryScriptHash = GetRepositoryScriptHash( $RepositoryScriptETag, $LocalScriptHash ); 414 | } 415 | 416 | if( $LocalScriptHash !== $RepositoryScriptHash ) 417 | { 418 | Msg( '-- {lightred}Script has been updated on GitHub since you started this script, please make sure to update.' ); 419 | } 420 | } 421 | 422 | Msg( ' {teal}Waiting ' . number_format( $WaitTimeBeforeFirstScan, 3 ) . ' (+' . number_format( $SkippedLagTime, 3 ) . ' second lag) seconds before rescanning planets...' ); 423 | 424 | usleep( $WaitTimeBeforeFirstScan * 1000000 ); 425 | 426 | do 427 | { 428 | $BestPlanetAndZone = GetBestPlanetAndZone( $WaitTime, $FailSleep ); 429 | } 430 | while( !$BestPlanetAndZone && sleep( $FailSleep ) === 0 ); 431 | 432 | if( $BestPlanetAndZone[ 'best_zone' ][ 'boss_active' ] ) 433 | { 434 | Msg( '{green}Boss detected, abandoning current zone and joining boss...' ); 435 | 436 | $LastKnownPlanet = 0; 437 | 438 | continue; 439 | } 440 | 441 | $LagAdjustedWaitTime -= microtime( true ) - $PlanetCheckTime; 442 | 443 | if( $LagAdjustedWaitTime > 0 ) 444 | { 445 | Msg( ' {teal}Waiting ' . number_format( $LagAdjustedWaitTime, 3 ) . ' remaining seconds before submitting score...' ); 446 | 447 | usleep( $LagAdjustedWaitTime * 1000000 ); 448 | } 449 | 450 | $Data = SendPOST( 'ITerritoryControlMinigameService/ReportScore', 'access_token=' . $Token . '&score=' . GetScoreForZone( $Zone ) . '&language=english' ); 451 | 452 | if( empty( $Data[ 'response' ][ 'new_score' ] ) ) 453 | { 454 | $LagAdjustedWaitTime = max( 1, min( 10, round( $SkippedLagTime ) ) ); 455 | 456 | Msg( '{lightred}-- Report score failed, trying again in ' . $LagAdjustedWaitTime . ' seconds...' ); 457 | 458 | sleep( $LagAdjustedWaitTime ); 459 | 460 | $Data = SendPOST( 'ITerritoryControlMinigameService/ReportScore', 'access_token=' . $Token . '&score=' . GetScoreForZone( $Zone ) . '&language=english' ); 461 | } 462 | 463 | if( isset( $Data[ 'response' ][ 'new_score' ] ) ) 464 | { 465 | $Data = $Data[ 'response' ]; 466 | 467 | echo PHP_EOL; 468 | 469 | // Store our own old score because the API may increment score while giving an error (e.g. a timeout) 470 | if( !$OldScore ) 471 | { 472 | $OldScore = $Data[ 'old_score' ]; 473 | } 474 | 475 | Msg( 476 | '++ Your Score: {lightred}' . number_format( $Data[ 'new_score' ] ) . 477 | '{yellow} (+' . number_format( $Data[ 'new_score' ] - $OldScore ) . ')' . 478 | '{normal} - Current Level: {green}' . $Data[ 'new_level' ] . 479 | '{normal} (' . number_format( GetNextLevelProgress( $Data ) * 100, 2 ) . '%)' 480 | ); 481 | 482 | $OldScore = $Data[ 'new_score' ]; 483 | 484 | if( isset( $Data[ 'next_level_score' ] ) ) 485 | { 486 | Msg( 487 | '>> Next Level: {yellow}' . number_format( $Data[ 'next_level_score' ] ) . 488 | '{normal} - Remaining: {yellow}' . number_format( $Data[ 'next_level_score' ] - $Data[ 'new_score' ] ) 489 | ); 490 | } 491 | } 492 | } 493 | while( true ); 494 | 495 | function BossSleep( $c ) 496 | { 497 | $SkippedLagTime = curl_getinfo( $c, CURLINFO_TOTAL_TIME ) - curl_getinfo( $c, CURLINFO_STARTTRANSFER_TIME ); 498 | $SkippedLagTime -= fmod( $SkippedLagTime, 0.1 ); 499 | $LagAdjustedWaitTime = 5 - $SkippedLagTime; 500 | 501 | if( $LagAdjustedWaitTime > 0 ) 502 | { 503 | usleep( $LagAdjustedWaitTime * 1000000 ); 504 | } 505 | 506 | return true; 507 | } 508 | 509 | function CheckGameVersion( $Data ) 510 | { 511 | global $GameVersion; 512 | 513 | if( !isset( $Data[ 'response' ][ 'game_version' ] ) || $GameVersion >= $Data[ 'response' ][ 'game_version' ] ) 514 | { 515 | return; 516 | } 517 | 518 | Msg( '{lightred}!! Game version changed to ' . $Data[ 'response' ][ 'game_version' ] ); 519 | } 520 | 521 | function GetNextLevelProgress( $Data ) 522 | { 523 | if( !isset( $Data[ 'next_level_score' ] ) ) 524 | { 525 | return 1; 526 | } 527 | 528 | $ScoreTable = 529 | [ 530 | 0, // Level 1 531 | 1200, // Level 2 532 | 2400, // Level 3 533 | 4800, // Level 4 534 | 12000, // Level 5 535 | 30000, // Level 6 536 | 72000, // Level 7 537 | 180000, // Level 8 538 | 450000, // Level 9 539 | 1200000, // Level 10 540 | 2400000, // Level 11 541 | 3600000, // Level 12 542 | 4800000, // Level 13 543 | 6000000, // Level 14 544 | 7200000, // Level 15 545 | 8400000, // Level 16 546 | 9600000, // Level 17 547 | 10800000, // Level 18 548 | 12000000, // Level 19 549 | 14400000, // Level 20 550 | 16800000, // Level 21 551 | 19200000, // Level 22 552 | 21600000, // Level 23 553 | 24000000, // Level 24 554 | 26400000, // Level 25 555 | ]; 556 | 557 | $PreviousLevel = $Data[ 'new_level' ] - 1; 558 | 559 | if( !isset( $ScoreTable[ $PreviousLevel ] ) ) 560 | { 561 | Msg( '{lightred}!! Score for next level is unknown, you probably should update the script.' ); 562 | return 0; 563 | } 564 | 565 | return ( $Data[ 'new_score' ] - $ScoreTable[ $PreviousLevel ] ) / ( $Data[ 'next_level_score' ] - $ScoreTable[ $PreviousLevel ] ); 566 | } 567 | 568 | function GetScoreForZone( $Zone ) 569 | { 570 | switch( $Zone[ 'difficulty' ] ) 571 | { 572 | case 1: $Score = 5; break; 573 | case 2: $Score = 10; break; 574 | case 3: $Score = 20; break; 575 | 576 | // Set fallback score equal to high zone score to avoid uninitialized 577 | // variable if new zone difficulty is introduced (e.g., for boss zones) 578 | default: $Score = 20; 579 | } 580 | 581 | return $Score * 120; 582 | } 583 | 584 | function GetNameForDifficulty( $Zone ) 585 | { 586 | $Boss = $Zone[ 'type' ] == 4 ? 'BOSS - ' : ''; 587 | $Difficulty = $Zone[ 'difficulty' ]; 588 | 589 | switch( $Zone[ 'difficulty' ] ) 590 | { 591 | case 3: $Difficulty = 'High'; break; 592 | case 2: $Difficulty = 'Medium'; break; 593 | case 1: $Difficulty = 'Low'; break; 594 | } 595 | 596 | return $Boss . $Difficulty; 597 | } 598 | 599 | function GetPlanetState( $Planet, $WaitTime ) 600 | { 601 | $Zones = SendGET( 'ITerritoryControlMinigameService/GetPlanet', 'id=' . $Planet . '&language=english' ); 602 | 603 | if( empty( $Zones[ 'response' ][ 'planets' ][ 0 ][ 'zones' ] ) ) 604 | { 605 | return null; 606 | } 607 | 608 | $Zones = $Zones[ 'response' ][ 'planets' ][ 0 ][ 'zones' ]; 609 | $CleanZones = []; 610 | $HighZones = 0; 611 | $MediumZones = 0; 612 | $LowZones = 0; 613 | $BossZones = []; 614 | 615 | foreach( $Zones as &$Zone ) 616 | { 617 | if( empty( $Zone[ 'capture_progress' ] ) ) 618 | { 619 | $Zone[ 'capture_progress' ] = 0.0; 620 | } 621 | 622 | if( !isset( $Zone[ 'boss_active' ] ) ) 623 | { 624 | $Zone[ 'boss_active' ] = false; 625 | } 626 | 627 | if( $Zone[ 'captured' ] ) 628 | { 629 | continue; 630 | } 631 | 632 | // Store boss zone separately to ensure it has priority later 633 | if( $Zone[ 'type' ] == 4 && $Zone[ 'boss_active' ] ) 634 | { 635 | $BossZones[] = $Zone; 636 | } 637 | 638 | // If a zone is close to completion, skip it because we want to avoid joining a completed zone 639 | // Valve now rewards points, if the zone is completed before submission 640 | if( $Zone[ 'capture_progress' ] >= 0.98 ) 641 | { 642 | continue; 643 | } 644 | 645 | switch( $Zone[ 'difficulty' ] ) 646 | { 647 | case 3: $HighZones++; break; 648 | case 2: $MediumZones++; break; 649 | case 1: $LowZones++; break; 650 | } 651 | 652 | $CleanZones[] = $Zone; 653 | } 654 | 655 | unset( $Zone ); 656 | 657 | if( !empty( $BossZones ) ) 658 | { 659 | $CleanZones = $BossZones; 660 | } 661 | else if( count( $CleanZones ) < 2 ) 662 | { 663 | return false; 664 | } 665 | 666 | usort( $CleanZones, function( $a, $b ) 667 | { 668 | if( $b[ 'difficulty' ] === $a[ 'difficulty' ] ) 669 | { 670 | return $b[ 'zone_position' ] - $a[ 'zone_position' ]; 671 | } 672 | 673 | return $b[ 'difficulty' ] - $a[ 'difficulty' ]; 674 | } ); 675 | 676 | return [ 677 | 'high_zones' => $HighZones, 678 | 'medium_zones' => $MediumZones, 679 | 'low_zones' => $LowZones, 680 | 'best_zone' => $CleanZones[ 0 ], 681 | ]; 682 | } 683 | 684 | function GetBestPlanetAndZone( $WaitTime, $FailSleep ) 685 | { 686 | $Planets = SendGET( 'ITerritoryControlMinigameService/GetPlanets', 'active_only=1&language=english' ); 687 | 688 | CheckGameVersion( $Planets ); 689 | 690 | if( empty( $Planets[ 'response' ][ 'planets' ] ) ) 691 | { 692 | if( isset( $Planets[ 'response' ][ 'game_version' ] ) ) 693 | { 694 | Msg( '{green}There are no active planets left! Good game!' ); 695 | exit( 0 ); 696 | } 697 | 698 | return null; 699 | } 700 | 701 | $Planets = $Planets[ 'response' ][ 'planets' ]; 702 | 703 | usort( $Planets, function( $a, $b ) 704 | { 705 | $a = isset( $a[ 'state' ][ 'boss_zone_position' ] ) ? 1000 : $a[ 'id' ]; 706 | $b = isset( $b[ 'state' ][ 'boss_zone_position' ] ) ? 1000 : $b[ 'id' ]; 707 | 708 | return $b - $a; 709 | } ); 710 | 711 | foreach( $Planets as &$Planet ) 712 | { 713 | $Planet[ 'sort_key' ] = 0; 714 | 715 | if( empty( $Planet[ 'state' ][ 'capture_progress' ] ) ) 716 | { 717 | $Planet[ 'state' ][ 'capture_progress' ] = 0.0; 718 | } 719 | 720 | if( empty( $Planet[ 'state' ][ 'current_players' ] ) ) 721 | { 722 | $Planet[ 'state' ][ 'current_players' ] = 0; 723 | } 724 | 725 | do 726 | { 727 | $Zone = GetPlanetState( $Planet[ 'id' ], $WaitTime ); 728 | } 729 | while( $Zone === null && sleep( $FailSleep ) === 0 ); 730 | 731 | if( $Zone === false ) 732 | { 733 | $Planet[ 'high_zones' ] = 0; 734 | $Planet[ 'medium_zones' ] = 0; 735 | $Planet[ 'low_zones' ] = 0; 736 | } 737 | else 738 | { 739 | $Planet[ 'high_zones' ] = $Zone[ 'high_zones' ]; 740 | $Planet[ 'medium_zones' ] = $Zone[ 'medium_zones' ]; 741 | $Planet[ 'low_zones' ] = $Zone[ 'low_zones' ]; 742 | $Planet[ 'best_zone' ] = $Zone[ 'best_zone' ]; 743 | } 744 | 745 | Msg( 746 | '>> Planet {green}%3d{normal} - Captured: {green}%5s%%{normal} - High: {yellow}%2d{normal} - Medium: {yellow}%2d{normal} - Low: {yellow}%2d{normal} - Players: {yellow}%7s {green}(%s)', 747 | PHP_EOL, 748 | [ 749 | $Planet[ 'id' ], 750 | number_format( $Planet[ 'state' ][ 'capture_progress' ] * 100, 2 ), 751 | $Planet[ 'high_zones' ], 752 | $Planet[ 'medium_zones' ], 753 | $Planet[ 'low_zones' ], 754 | number_format( $Planet[ 'state' ][ 'current_players' ] ), 755 | $Planet[ 'state' ][ 'name' ], 756 | ] 757 | ); 758 | 759 | if( $Zone !== false ) 760 | { 761 | if( $Zone[ 'best_zone' ][ 'type' ] == 4 ) 762 | { 763 | Msg( '{green}>> This planet has an uncaptured boss, selecting this planet...' ); 764 | 765 | return $Planet; 766 | } 767 | 768 | if( $Planet[ 'low_zones' ] > 0 ) 769 | { 770 | $Planet[ 'sort_key' ] += 99 - $Planet[ 'low_zones' ]; 771 | } 772 | 773 | if( $Planet[ 'medium_zones' ] > 0 ) 774 | { 775 | $Planet[ 'sort_key' ] += pow( 10, 2 ) * ( 99 - $Planet[ 'medium_zones' ] ); 776 | } 777 | 778 | if( $Planet[ 'high_zones' ] > 0 ) 779 | { 780 | $Planet[ 'sort_key' ] += pow( 10, 4 ) * ( 99 - $Planet[ 'high_zones' ] ); 781 | } 782 | } 783 | } 784 | 785 | usort( $Planets, function( $a, $b ) 786 | { 787 | return $b[ 'sort_key' ] - $a[ 'sort_key' ]; 788 | } ); 789 | 790 | $Planet = $Planets[ 0 ]; 791 | 792 | Msg( 793 | '>> Next Zone is {yellow}' . $Planet[ 'best_zone' ][ 'zone_position' ] . 794 | '{normal} (Captured: {yellow}' . number_format( $Planet[ 'best_zone' ][ 'capture_progress' ] * 100, 2 ) . '%' . 795 | '{normal} - Difficulty: {yellow}' . GetNameForDifficulty( $Planet[ 'best_zone' ] ) . 796 | '{normal}) on Planet {green}' . $Planet[ 'id' ] . 797 | ' (' . $Planet[ 'state' ][ 'name' ] . ')' 798 | ); 799 | 800 | return $Planet; 801 | } 802 | 803 | function LeaveCurrentGame( $Token, $FailSleep, $LeaveCurrentPlanet = 0 ) 804 | { 805 | do 806 | { 807 | $Data = SendPOST( 'ITerritoryControlMinigameService/GetPlayerInfo', 'access_token=' . $Token ); 808 | 809 | if( isset( $Data[ 'response' ][ 'active_zone_game' ] ) ) 810 | { 811 | SendPOST( 'IMiniGameService/LeaveGame', 'access_token=' . $Token . '&gameid=' . $Data[ 'response' ][ 'active_zone_game' ] ); 812 | } 813 | 814 | if( isset( $Data[ 'response' ][ 'active_boss_game' ] ) ) 815 | { 816 | SendPOST( 'IMiniGameService/LeaveGame', 'access_token=' . $Token . '&gameid=' . $Data[ 'response' ][ 'active_boss_game' ] ); 817 | } 818 | } 819 | while( !isset( $Data[ 'response' ][ 'score' ] ) && sleep( $FailSleep ) === 0 ); 820 | 821 | if( !isset( $Data[ 'response' ][ 'active_planet' ] ) ) 822 | { 823 | return 0; 824 | } 825 | 826 | $ActivePlanet = $Data[ 'response' ][ 'active_planet' ]; 827 | 828 | if( $LeaveCurrentPlanet > 0 && $LeaveCurrentPlanet !== $ActivePlanet ) 829 | { 830 | Msg( ' Leaving planet {green}' . $ActivePlanet . '{normal} because we want to be on {green}' . $LeaveCurrentPlanet ); 831 | Msg( ' Time accumulated on planet {green}' . $ActivePlanet . '{normal}: {yellow}' . gmdate( 'H\h i\m s\s', $Data[ 'response' ][ 'time_on_planet' ] ) ); 832 | 833 | echo PHP_EOL; 834 | 835 | SendPOST( 'IMiniGameService/LeaveGame', 'access_token=' . $Token . '&gameid=' . $ActivePlanet ); 836 | } 837 | 838 | return $ActivePlanet; 839 | } 840 | 841 | function SendPOST( $Method, $Data ) 842 | { 843 | return ExecuteRequest( $Method, 'https://community.steam-api.com/' . $Method . '/v0001/', $Data ); 844 | } 845 | 846 | function SendGET( $Method, $Data ) 847 | { 848 | return ExecuteRequest( $Method, 'https://community.steam-api.com/' . $Method . '/v0001/?' . $Data ); 849 | } 850 | 851 | function GetCurl( ) 852 | { 853 | global $c; 854 | 855 | if( isset( $c ) ) 856 | { 857 | return $c; 858 | } 859 | 860 | $c = curl_init( ); 861 | 862 | curl_setopt_array( $c, [ 863 | CURLOPT_USERAGENT => 'SalienCheat (https://github.com/SteamDatabase/SalienCheat/)', 864 | CURLOPT_RETURNTRANSFER => true, 865 | CURLOPT_ENCODING => '', // Let curl decide best encoding on its own 866 | CURLOPT_TIMEOUT => 30, 867 | CURLOPT_CONNECTTIMEOUT => 10, 868 | CURLOPT_HEADER => 1, 869 | CURLOPT_CAINFO => __DIR__ . '/cacert.pem', 870 | CURLOPT_HTTPHEADER => 871 | [ 872 | 'Connection: Keep-Alive', 873 | 'Keep-Alive: timeout=300' 874 | ], 875 | ] ); 876 | 877 | if ( !empty( $_SERVER[ 'LOCAL_ADDRESS' ] ) ) 878 | { 879 | curl_setopt( $c, CURLOPT_INTERFACE, $_SERVER[ 'LOCAL_ADDRESS' ] ); 880 | } 881 | 882 | if( defined( 'CURL_HTTP_VERSION_2_0' ) ) 883 | { 884 | curl_setopt( $c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0 ); 885 | } 886 | 887 | return $c; 888 | } 889 | 890 | function ExecuteRequest( $Method, $URL, $Data = [] ) 891 | { 892 | $c = GetCurl( ); 893 | 894 | curl_setopt( $c, CURLOPT_URL, $URL ); 895 | 896 | if( !empty( $Data ) ) 897 | { 898 | curl_setopt( $c, CURLOPT_POST, 1 ); 899 | curl_setopt( $c, CURLOPT_POSTFIELDS, $Data ); 900 | } 901 | else 902 | { 903 | curl_setopt( $c, CURLOPT_HTTPGET, 1 ); 904 | } 905 | 906 | do 907 | { 908 | $Data = curl_exec( $c ); 909 | 910 | $HeaderSize = curl_getinfo( $c, CURLINFO_HEADER_SIZE ); 911 | $Header = substr( $Data, 0, $HeaderSize ); 912 | $Data = substr( $Data, $HeaderSize ); 913 | 914 | preg_match( '/[Xx]-eresult: ([0-9]+)/', $Header, $EResult ) === 1 ? $EResult = (int)$EResult[ 1 ] : $EResult = 0; 915 | 916 | if( $EResult !== 1 ) 917 | { 918 | Msg( '{lightred}!! ' . $Method . ' failed - EResult: ' . $EResult . ' - ' . $Data ); 919 | 920 | if( preg_match( '/^[Xx]-error_message: (?:.+)$/m', $Header, $ErrorMessage ) === 1 ) 921 | { 922 | Msg( '{lightred}!! API failed - ' . $ErrorMessage[ 0 ] ); 923 | } 924 | 925 | if( $EResult === 15 && $Method === 'ITerritoryControlMinigameService/RepresentClan' ) // EResult.AccessDenied 926 | { 927 | echo PHP_EOL; 928 | 929 | Msg( '{green}This script was designed for SteamDB' ); 930 | Msg( '{green}If you want to support it, join the group and represent it in game:' ); 931 | Msg( '{yellow}https://steamcommunity.com/groups/SteamDB' ); 932 | 933 | sleep( 10 ); 934 | } 935 | else if( $EResult === 11 || $EResult === 27 ) // EResult.InvalidState || EResult.Expired 936 | { 937 | global $LastKnownPlanet; 938 | $LastKnownPlanet = 0; 939 | } 940 | else if( $EResult === 0 ) // timeout 941 | { 942 | Msg( '{lightred}-- This problem should resolve itself, wait for a couple of minutes' ); 943 | } 944 | else if( $EResult === 10 ) // EResult.Busy 945 | { 946 | $Data = '{}'; // Retry this exact request 947 | 948 | Msg( '{lightred}-- EResult 10 means Steam is busy' ); 949 | 950 | sleep( 5 ); 951 | } 952 | } 953 | 954 | $Data = json_decode( $Data, true ); 955 | $Data[ 'eresult' ] = $EResult; 956 | } 957 | while( !isset( $Data[ 'response' ] ) && sleep( 2 ) === 0 ); 958 | 959 | return $Data; 960 | } 961 | 962 | function GetRepositoryScriptHash( &$RepositoryScriptETag, $LocalScriptHash ) 963 | { 964 | $c_r = curl_init( ); 965 | 966 | $Time = time(); 967 | $Time = $Time - ( $Time % 10 ); 968 | 969 | curl_setopt_array( $c_r, [ 970 | CURLOPT_URL => 'https://raw.githubusercontent.com/SteamDatabase/SalienCheat/master/cheat.php?_=' . $Time, 971 | CURLOPT_USERAGENT => 'SalienCheat (https://github.com/SteamDatabase/SalienCheat/)', 972 | CURLOPT_RETURNTRANSFER => true, 973 | CURLOPT_ENCODING => 'gzip', 974 | CURLOPT_TIMEOUT => 5, 975 | CURLOPT_CONNECTTIMEOUT => 5, 976 | CURLOPT_CAINFO => __DIR__ . '/cacert.pem', 977 | CURLOPT_HEADER => 1, 978 | CURLOPT_HTTPHEADER => 979 | [ 980 | 'If-None-Match: "' . $RepositoryScriptETag . '"' 981 | ] 982 | ] ); 983 | 984 | $Data = curl_exec( $c_r ); 985 | 986 | $HeaderSize = curl_getinfo( $c_r, CURLINFO_HEADER_SIZE ); 987 | $Header = substr( $Data, 0, $HeaderSize ); 988 | $Data = substr( $Data, $HeaderSize ); 989 | 990 | curl_close( $c_r ); 991 | 992 | if( preg_match( '/ETag: "([a-z0-9]+)"/', $Header, $ETag ) === 1 ) 993 | { 994 | $RepositoryScriptETag = $ETag[ 1 ]; 995 | } 996 | 997 | return strlen( $Data ) > 0 ? sha1( trim( $Data ) ) : $LocalScriptHash; 998 | } 999 | 1000 | function GetAccountID( $SteamID ) 1001 | { 1002 | if( PHP_INT_SIZE === 8 ) 1003 | { 1004 | return $SteamID & 0xFFFFFFFF; 1005 | } 1006 | else if( function_exists( 'gmp_and' ) ) 1007 | { 1008 | return gmp_and( $SteamID, '0xFFFFFFFF' ); 1009 | } 1010 | 1011 | return 0; 1012 | } 1013 | 1014 | function Msg( $Message, $EOL = PHP_EOL, $printf = [] ) 1015 | { 1016 | global $DisableColors; 1017 | 1018 | $Message = str_replace( 1019 | [ 1020 | '{normal}', 1021 | '{green}', 1022 | '{yellow}', 1023 | '{lightred}', 1024 | '{teal}', 1025 | '{background-blue}', 1026 | ], 1027 | $DisableColors ? '' : [ 1028 | "\033[0m", 1029 | "\033[0;32m", 1030 | "\033[1;33m", 1031 | "\033[1;31m", 1032 | "\033[0;36m", 1033 | "\033[37;44m", 1034 | ], 1035 | $Message, $Count ); 1036 | 1037 | if( $Count > 0 && !$DisableColors ) 1038 | { 1039 | $Message .= "\033[0m"; 1040 | } 1041 | 1042 | $Message = '[' . date( 'H:i:s' ) . '] ' . $Message . $EOL; 1043 | 1044 | if( !empty( $printf ) ) 1045 | { 1046 | array_unshift( $printf, $Message ); 1047 | call_user_func_array( 'printf', $printf ); 1048 | } 1049 | else 1050 | { 1051 | echo $Message; 1052 | } 1053 | } 1054 | -------------------------------------------------------------------------------- /cheat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Plays SALIEN for you 3 | 4 | pip install requests tqdm 5 | """ 6 | 7 | import os 8 | import re 9 | import sys 10 | import json 11 | from io import open 12 | from time import sleep, time 13 | from itertools import count 14 | from datetime import datetime 15 | 16 | import requests 17 | from tqdm import tqdm 18 | 19 | # determine input func 20 | try: 21 | _input = raw_input 22 | except: 23 | _input = input 24 | 25 | 26 | def get_access_token(force_input=False): 27 | token_re = re.compile("^[a-z0-9]{32}$") 28 | token_path = './token.txt' 29 | token = '' 30 | 31 | if not force_input: 32 | if token_re.match(sys.argv[-1]): 33 | token = sys.argv[-1] 34 | else: 35 | if os.path.isfile(token_path): 36 | data = open(token_path, 'r', encoding='utf-8').read() 37 | 38 | try: 39 | token = json.loads(data)['token'] 40 | except: 41 | token = data.strip() 42 | 43 | if not token_re.match(token): 44 | token = '' 45 | 46 | if not token: 47 | token = _input("Login to steamcommunity.com\n" 48 | "Visit https://steamcommunity.com/saliengame/gettoken\n" 49 | "Copy the token value and paste here.\n" 50 | "--\n" 51 | "Token: " 52 | ).strip() 53 | 54 | while not token_re.match(token): 55 | token = _input("Enter valid token: ").strip() 56 | 57 | with open(token_path, 'w', encoding='utf-8') as fp: 58 | if sys.version_info < (3,): 59 | token = token.decode('utf-8') 60 | fp.write(token) 61 | 62 | return token 63 | 64 | 65 | class Saliens(requests.Session): 66 | api_url = 'https://community.steam-api.com/%s/v0001/' 67 | player_info = None 68 | planet = None 69 | zone_id = None 70 | zone_capture_rate = 0 71 | colors = ( 72 | ('^NOR', '\033[0m'), 73 | ('^GRN', '\033[0;32m'), 74 | ('^YEL', '\033[0;33m'), 75 | ('^RED', '\033[0;31m'), 76 | ('^GRY', '\033[0;36m'), 77 | ) 78 | 79 | def __init__(self, access_token): 80 | super(Saliens, self).__init__() 81 | self.access_token = access_token 82 | self.headers['User-Agent'] = ('SalienCheat (https://github.com/SteamDatabase/SalienCheat/') 83 | self.pbar_init() 84 | 85 | def spost(self, endpoint, form_fields=None, retry=False): 86 | if not form_fields: 87 | form_fields = {} 88 | form_fields['access_token'] = self.access_token 89 | 90 | data = None 91 | resp = None 92 | deadline = time() + 30 93 | 94 | while not data: 95 | try: 96 | resp = self.post(self.api_url % endpoint, data=form_fields) 97 | 98 | eresult = int(resp.headers.get('X-eresult', -1)) 99 | 100 | if resp.status_code != 200: 101 | raise Exception("HTTP %s EResult %s\n%s" % (resp.status_code, eresult, resp.text)) 102 | 103 | rdata = resp.json() 104 | if 'response' not in rdata: 105 | raise Exception("NoJSON EResult %s" % eresult) 106 | except Exception as exp: 107 | self.log("^RED-- POST %-46s %s", endpoint, str(exp)) 108 | 109 | if resp is None or resp.status_code >= 500: 110 | sleep(2) 111 | continue 112 | else: 113 | self.log("^GRY POST %-46s HTTP %s EResult %s", endpoint, resp.status_code, eresult) 114 | 115 | if eresult == 93 and time() < deadline: 116 | sleep(3) 117 | continue 118 | 119 | data = rdata['response'] 120 | 121 | if not retry: 122 | break 123 | 124 | if not data: 125 | sleep(1) 126 | 127 | return data 128 | 129 | def sget(self, endpoint, query_params=None, retry=False, timeout=15): 130 | data = None 131 | resp = None 132 | 133 | while not data: 134 | try: 135 | resp = self.get(self.api_url % endpoint, params=query_params, timeout=timeout) 136 | 137 | eresult = resp.headers.get('X-eresult', -1) 138 | if resp.status_code != 200: 139 | raise Exception("HTTP %s EResult %s\n%s" % (resp.status_code, eresult, resp.text)) 140 | 141 | rdata = resp.json() 142 | if 'response' not in rdata: 143 | raise Exception("NoJSON EResult %s" % eresult) 144 | except Exception as exp: 145 | self.log("^RED-- GET %-46s %s", endpoint, str(exp)) 146 | 147 | if (resp is None and retry) or (resp and resp.status_code >= 500): 148 | sleep(2) 149 | continue 150 | else: 151 | self.log("^GRY GET %-46s HTTP %s EResult %s", endpoint, resp.status_code, eresult) 152 | data = rdata['response'] 153 | 154 | if not retry: 155 | break 156 | 157 | if not data: 158 | sleep(1) 159 | 160 | return data 161 | 162 | def is_access_token_valid(self): 163 | if not self.access_token: 164 | return False 165 | 166 | while True: 167 | resp = self.post(self.api_url % 'ITerritoryControlMinigameService/GetPlayerInfo', 168 | data={'access_token': self.access_token} 169 | ) 170 | 171 | if resp.status_code == 200: 172 | return True 173 | elif resp.status_code == 401: 174 | return False 175 | 176 | sleep(2) 177 | 178 | def refresh_player_info(self): 179 | self.player_info = self.spost('ITerritoryControlMinigameService/GetPlayerInfo', retry=True) 180 | return self.player_info 181 | 182 | def refresh_planet_info(self, retry=True, timeout=15): 183 | if 'active_planet' in self.player_info: 184 | planet = self.get_planet(self.player_info['active_planet'], retry=retry, timeout=timeout) 185 | 186 | if planet is not None: 187 | self.planet = planet 188 | else: 189 | self.planet = {} 190 | 191 | self.pbar_refresh() 192 | return self.planet 193 | 194 | def get_planet(self, pid, retry=True, timeout=15): 195 | data = self.sget('ITerritoryControlMinigameService/GetPlanet', 196 | {'id': pid, '_': int(time())}, 197 | retry=retry, 198 | timeout=timeout, 199 | ) 200 | if data is None: 201 | return 202 | else: 203 | planet = data.get('planets', [{}])[0] 204 | 205 | if planet: 206 | planet['easy_zones'] = sorted((z for z in planet['zones'] 207 | if (not z['captured'] 208 | and z['difficulty'] == 1)), 209 | reverse=True, 210 | key=lambda x: x['zone_position']) 211 | 212 | planet['medium_zones'] = sorted((z for z in planet['zones'] 213 | if (not z['captured'] 214 | and z['difficulty'] == 2)), 215 | # and z.get('capture_progress', 0) < 0.90)), 216 | reverse=True, 217 | key=lambda x: x['zone_position']) 218 | 219 | planet['hard_zones'] = sorted((z for z in planet['zones'] 220 | if (not z['captured'] 221 | and z['difficulty'] == 3)), 222 | # and z.get('capture_progress', 0) < 0.95)), 223 | reverse=True, 224 | key=lambda x: x['zone_position']) 225 | planet['boss_zones'] = sorted((z for z in planet['zones'] 226 | if not z['captured'] and z['type'] == 4), 227 | reverse=True, 228 | key=lambda x: x['zone_position']) 229 | 230 | # Example ordering (easy/med/hard): 231 | # 20/5/1 > 20/5/5 > 20/1/0 > 1/20/0 232 | # This should result in prefering planets that are nearing completion, but 233 | # still prioritize ones that have high difficulty zone to maximize score gain 234 | sort_key = 0 235 | 236 | if len(planet['easy_zones']): 237 | sort_key += 99 - len(planet['easy_zones']) 238 | if len(planet['medium_zones']): 239 | sort_key += 10**2 * (99 - len(planet['medium_zones'])) 240 | if len(planet['hard_zones']): 241 | sort_key += 10**4 * (99 - len(planet['hard_zones'])) 242 | # if len(planet['boss_zones']): 243 | # sort_key += 10**6 * (99 - len(planet['boss_zones'])) 244 | 245 | planet['sort_key'] = sort_key 246 | 247 | return planet 248 | 249 | def get_planets(self): 250 | return self.sget('ITerritoryControlMinigameService/GetPlanets', 251 | {'active_only': 1}, 252 | retry=True, 253 | ).get('planets', []) 254 | 255 | def get_uncaptured_planets(self): 256 | planets = self.get_planets() 257 | return sorted((game.get_planet(p['id']) for p in planets if not p['state']['captured']), 258 | reverse=True, 259 | key=lambda x: x['sort_key'], 260 | ) 261 | 262 | def represent_clan(self, clan_id): 263 | return self.spost('ITerritoryControlMinigameService/RepresentClan', {'clanid': clan_id}) 264 | 265 | def report_score(self, score): 266 | return self.spost('ITerritoryControlMinigameService/ReportScore', {'score': score}) 267 | 268 | def join_planet(self, pid): 269 | return self.spost('ITerritoryControlMinigameService/JoinPlanet', {'id': pid}) 270 | 271 | def join_zone(self, pos): 272 | self.zone_id = pos 273 | return self.spost('ITerritoryControlMinigameService/JoinZone', {'zone_position': pos}) 274 | 275 | def leave_zone(self, clear_rate=True): 276 | if 'active_zone_game' in self.player_info: 277 | self.spost('IMiniGameService/LeaveGame', 278 | {'gameid': self.player_info['active_zone_game']}, 279 | retry=False) 280 | self.zone_id = None 281 | 282 | if clear_rate: 283 | self.zone_capture_rate = 0 284 | 285 | def leave_planet(self): 286 | if 'active_planet' in self.player_info: 287 | self.spost('IMiniGameService/LeaveGame', 288 | {'gameid': self.player_info['active_planet']}, 289 | retry=False) 290 | 291 | def pbar_init(self): 292 | self.level_pbar = tqdm(ascii=True, 293 | dynamic_ncols=True, 294 | desc="Player Level", 295 | total=0, 296 | initial=0, 297 | bar_format='{desc:<18} {percentage:3.0f}% |{bar}| {n_fmt}/{total_fmt} | {remaining:>9}', 298 | ) 299 | self.level_pbar.rate_psec = 0 300 | self.planet_pbar = tqdm(ascii=True, 301 | dynamic_ncols=True, 302 | desc="Planet progress", 303 | total=0, 304 | initial=0, 305 | smoothing=0.3, 306 | bar_format='{desc:<18} {percentage:3.0f}%% |{bar}|%s {remaining:>9}', 307 | ) 308 | self.planet_pbar.bar_format_tmpl = self.planet_pbar.bar_format 309 | self.planet_pbar.rate_psec = 0 310 | self.zone_pbar = tqdm(ascii=True, 311 | dynamic_ncols=True, 312 | desc="Zone progress", 313 | total=0, 314 | initial=0, 315 | smoothing=0.3, 316 | bar_format='{desc:<18} {percentage:3.0f}%% |{bar}|%s {remaining:>9}', 317 | ) 318 | self.zone_pbar.bar_format_tmpl = self.zone_pbar.bar_format 319 | self.zone_pbar.rate_psec = 0 320 | 321 | def end(self): 322 | self.level_pbar.close() 323 | self.planet_pbar.close() 324 | self.zone_pbar.close() 325 | 326 | def pbar_refresh(self): 327 | dmap = { 328 | 1: 'Easy', 329 | 2: 'Medium', 330 | 3: 'Hard', 331 | } 332 | 333 | if not self.player_info: 334 | return 335 | 336 | player_info = self.player_info 337 | 338 | def avg_time(pbar, n): 339 | curr_t = pbar._time() 340 | rate_psec = 0 341 | 342 | if pbar.n == 0: 343 | pbar.avg_time = 0 344 | pbar.last_print_t = curr_t 345 | else: 346 | delta_n = n - pbar.n 347 | delta_t = curr_t - pbar.last_print_t 348 | 349 | if delta_n and delta_t: 350 | curr_avg_time = delta_t / delta_n 351 | 352 | if (delta_n / delta_t) >= 0: 353 | rate_psec = (pbar.smoothing * (delta_n / delta_t) 354 | + (1-pbar.smoothing) * pbar.rate_psec) 355 | 356 | pbar.avg_time = (pbar.smoothing * curr_avg_time 357 | + (1-pbar.smoothing) * (pbar.avg_time 358 | if pbar.avg_time 359 | else curr_avg_time)) 360 | pbar.last_print_t = curr_t 361 | 362 | if pbar.n and rate_psec and getattr(pbar, 'bar_format_tmpl', None): 363 | pbar.rate_psec = rate_psec 364 | rate = ' +{:.2f}% |'.format(rate_psec * 110 * 100) 365 | pbar.bar_format = pbar.bar_format_tmpl % rate 366 | 367 | pbar.n = n 368 | 369 | # level progress bar 370 | self.level_pbar.desc = "Level {level}".format(**player_info) 371 | self.level_pbar.total = int(player_info['next_level_score']) 372 | avg_time(self.level_pbar, int(player_info['score'])) 373 | self.level_pbar.refresh() 374 | 375 | # planet capture progress bar 376 | if self.planet: 377 | planet = self.planet 378 | state = planet['state'] 379 | planet_progress = (1.0 if state['captured'] 380 | else state.get('capture_progress', 0)) 381 | self.planet_pbar.desc = "Planet #{}".format(planet['id']) 382 | self.planet_pbar.total = 1.0 383 | avg_time(self.planet_pbar, planet_progress) 384 | else: 385 | self.planet_pbar.desc = "Planet" 386 | self.planet_pbar.n = 0 387 | self.planet_pbar.total = 0 388 | self.planet_pbar.rate_psec = 0 389 | self.planet_pbar.last_print_t = time() 390 | self.planet_pbar.bar_format = self.planet_pbar.bar_format_tmpl % '' 391 | 392 | self.planet_pbar.refresh() 393 | 394 | # zone capture progress bar 395 | if self.planet and self.zone_id is not None: 396 | zone = self.planet['zones'][self.zone_id] 397 | zone_progress = (1.0 if zone['captured'] 398 | else zone.get('capture_progress', 0)) 399 | self.zone_pbar.desc = "Zone #{} - {}".format(zone['zone_position'], 400 | dmap.get(zone['difficulty'], 401 | zone['difficulty'])) 402 | self.zone_pbar.total = 1.0 403 | avg_time(self.zone_pbar, zone_progress) 404 | self.zone_capture_rate = self.zone_pbar.rate_psec * 110 405 | else: 406 | self.zone_pbar.desc = "Zone" 407 | self.zone_pbar.n = 0 408 | self.zone_pbar.total = 0 409 | self.zone_pbar.last_print_t = time() 410 | self.zone_pbar.bar_format = self.zone_pbar.bar_format_tmpl % '' 411 | 412 | self.zone_pbar.refresh() 413 | 414 | _plog_c = 0 415 | _plog_text = None 416 | 417 | def log(self, text, *args): 418 | text = text % args 419 | text += "^NOR" 420 | 421 | for k, v in self.colors: 422 | text = text.replace(k, v) 423 | 424 | max_collapsed = 10 425 | 426 | if text == self._plog_text: 427 | self._plog_c += 1 428 | 429 | if ((text == self._plog_text and self._plog_c >= max_collapsed) 430 | or (text != self._plog_text and self._plog_c > 0)): 431 | ptext = self._plog_text + " x" + str(self._plog_c) 432 | self.level_pbar.write(datetime.now().strftime("%H:%M:%S") + " | " + ptext) 433 | self._plog_c = 0 434 | 435 | if text != self._plog_text: 436 | self.level_pbar.write(datetime.now().strftime("%H:%M:%S") + " | " + text) 437 | self._plog_c = 0 438 | 439 | self._plog_text = text 440 | self.pbar_refresh() 441 | 442 | def print_planet(self, planet): 443 | planet_id = planet['id'] 444 | planet_name = planet['state']['name'].split('Planet', 1)[1].replace('_', ' ') 445 | curr_players = planet['state'].get('current_players', 0) 446 | n_boss = len(planet['boss_zones']) 447 | n_hard = len(planet['hard_zones']) 448 | n_med = len(planet['medium_zones']) 449 | n_easy = len(planet['easy_zones']) 450 | 451 | status = ('yes' if planet['state']['captured'] 452 | else "{:>5.2f}%%".format(planet['state'].get('capture_progress', 0) * 100)) 453 | 454 | game.log("^YEL>>^NOR Planet ^GRN#{:>3}^NOR - ^YEL{:>2}^NOR / ^YEL{:>2}^NOR / ^YEL{:>2}^NOR " 455 | "/ ^YEL{:>2}^NOR B/H/M/E - Captured: ^YEL{}^NOR Players: ^YEL{:>7,}^NOR ^GRN({})" 456 | "".format(planet_id, 457 | n_boss, n_hard, n_med, n_easy, 458 | status, 459 | curr_players, 460 | planet_name, 461 | ) 462 | ) 463 | 464 | 465 | # ----- MAIN ------- 466 | 467 | 468 | access_token = get_access_token() 469 | game = Saliens(access_token) 470 | 471 | # display current stats 472 | game.log("^GRN++^NOR Getting player info...") 473 | game.refresh_player_info() 474 | 475 | # fair play 476 | game.log("^GRN-- Welcome to SalienCheat for SteamDB") 477 | 478 | if 'clan_info' not in game.player_info: 479 | game.log("^GRN-- You are currently not representing any clan, so you are now part of SteamDB") 480 | game.log("^GRN-- Make sure to join ^YELhttps://steamcommunity.com/groups/steamdb^GRN on Steam") 481 | game.represent_clan(4777282) 482 | 483 | elif game.player_info['clan_info']['accountid'] != 4777282: 484 | game.log("^GRN-- If you want to support us, join our group") 485 | game.log("^GRN-- ^YELhttps://steamcommunity.com/groups/steamdb") 486 | game.log("^GRN-- and set us as your clan on") 487 | game.log("^GRN-- ^YELhttps://steamcommunity.com/saliengame/play/") 488 | game.log("^GRN-- Happy farming!") 489 | 490 | game.log("^GRN++^NOR Scanning for planets...") 491 | game.refresh_planet_info() 492 | 493 | # show planet info 494 | planets = game.get_uncaptured_planets() 495 | game.log("^GRN++^NOR Found %s uncaptured planets: %s", 496 | len(planets), 497 | [int(x['id']) for x in planets]) 498 | 499 | for planet in planets: 500 | game.print_planet(planet) 501 | 502 | # join battle 503 | try: 504 | while True: 505 | if not planets: 506 | game.log("^GRN++ No planets left. Hmm? Gonna keep checkin...") 507 | sleep(10) 508 | planets = game.get_uncaptured_planets() 509 | continue 510 | 511 | planet_id = planets[0]['id'] 512 | # ensures we are not stuck in a zone 513 | game.leave_zone() 514 | 515 | # determine which planet to join 516 | if not game.planet or game.planet['id'] != planet_id: 517 | game.log("^GRN++^NOR Joining toughest planet ^GRN%s^NOR..", planets[0]['id']) 518 | 519 | # join planet and confirm it was success, otherwise retry 520 | for i in range(3): 521 | game.join_planet(planet_id) 522 | sleep(1) 523 | game.refresh_player_info() 524 | 525 | if game.player_info.get('active_planet') == planet_id: 526 | break 527 | 528 | game.log("^RED-- Failed to join planet. Retrying...") 529 | game.leave_planet() 530 | 531 | if i >= 2 and game.player_info.get('active_planet') != planet_id: 532 | continue 533 | 534 | else: 535 | game.log("^GRN++^NOR Remaining on current planet") 536 | 537 | game.refresh_planet_info() 538 | 539 | # show planet info 540 | giveaway_appds = game.planet.get('giveaway_apps', []) 541 | top_clans = [c['clan_info']['url'] for c in game.planet.get('top_clans', []) if 'url' in c.get('clan_info', {})][:5] 542 | 543 | game.print_planet(game.planet) 544 | game.log("^YEL>>^NOR Giveaway AppIDs: %s", giveaway_appds) 545 | if top_clans: 546 | game.log("^YEL>>^NOR Top clans: %s", ', '.join(top_clans)) 547 | if 'clan_info' not in game.player_info or game.player_info['clan_info']['accountid'] != 0O022162502: 548 | game.log("^YEL>>^NOR Join SteamDB: https://steamcommunity.com/groups/SteamDB") 549 | 550 | # selecting zone 551 | while game.planet and game.planet['id'] == planets[0]['id']: 552 | # retry represent on free agents 553 | if 'clan_info' not in game.player_info: 554 | game.represent_clan(4777282) 555 | 556 | zones = (game.planet['hard_zones'] 557 | + game.planet['medium_zones'] 558 | + game.planet['easy_zones']) 559 | 560 | # # filter out zones that are very close to getting captured 561 | # while (zones 562 | # and zones[0]['difficulty'] > 1 563 | # and (zones[0].get('capture_progress', 0) 564 | # + min(game.zone_capture_rate, 0.2) >= 1)): 565 | # zones.pop(0) 566 | 567 | if not zones: 568 | game.log("^GRN++^NOR No open zones left on planet") 569 | game.player_info.pop('active_planet') 570 | break 571 | 572 | # choose highest priority zone 573 | zone_id = zones[0]['zone_position'] 574 | difficulty = zones[0]['difficulty'] 575 | 576 | deadline = time() + 60 * 10 # rescan planets every 10min 577 | 578 | dmap = { 579 | 1: 'easy', 580 | 2: 'medium', 581 | 3: 'hard', 582 | } 583 | 584 | game.log("^GRN++^NOR Selecting %szone ^YEL%s^NOR (%s)....", 585 | '^REDboss^NOR ' if game.planet['zones'][zone_id]['type'] == 4 else '', 586 | zone_id, 587 | dmap.get(difficulty, difficulty), 588 | ) 589 | 590 | # fight in the zone 591 | while (game.planet 592 | and time() < deadline 593 | and not game.planet['zones'][zone_id]['captured'] 594 | ): 595 | 596 | # # skip if zone is likely to get captured while we wait, except easy zones 597 | # if (game.planet['zones'][zone_id]['difficulty'] > 1 598 | # and (game.planet['zones'][zone_id].get('capture_progress', 0) 599 | # + min(game.zone_capture_rate, 0.2) >= 1)): 600 | # game.log("^GRN++^NOR Zone likely to complete early. Moving on...") 601 | # break 602 | 603 | game.log("^GRN++^NOR Fighting in ^YEL%szone^NOR %s (^YEL%s^NOR) for ^YEL110sec", 604 | 'boss ' if game.planet['zones'][zone_id]['type'] == 4 else '', 605 | zone_id, 606 | dmap.get(difficulty, difficulty)) 607 | 608 | game.join_zone(zone_id) 609 | stoptime = time() + 109.6 610 | game.refresh_player_info() 611 | 612 | # refresh progress bars while in battle 613 | for i in count(start=1): 614 | # stop when battle is finished or zone was captured 615 | if time() >= stoptime: # or game.planet['zones'][zone_id]['captured']: 616 | break 617 | 618 | sleep(1) 619 | 620 | if (i % 11) == 0: 621 | game.refresh_planet_info(retry=False, timeout=max(0, stoptime - time())) 622 | game.pbar_refresh() 623 | 624 | # if game.planet['zones'][zone_id]['captured']: 625 | # game.log("^RED-- Zone was captured before we could submit score") 626 | # else: 627 | score = 120 * (5 * (2**(difficulty - 1))) 628 | game.log("^GRN++^NOR Submitting score of ^GRN%s^NOR...", score) 629 | game.report_score(score) 630 | game.refresh_player_info() 631 | game.refresh_planet_info() 632 | 633 | # incase user gets stuck 634 | game.leave_zone(False) 635 | 636 | # Rescan planets after zone is finished 637 | game.log("^GRN++^NOR Rescanning planets...") 638 | planets = game.get_uncaptured_planets() 639 | 640 | for planet in planets: 641 | game.print_planet(planet) 642 | 643 | game.refresh_planet_info() 644 | 645 | except KeyboardInterrupt: 646 | game.close() 647 | sys.exit() 648 | 649 | # end game 650 | game.log("^GRN++^NOR No uncaptured planets left. We done!") 651 | game.close() 652 | -------------------------------------------------------------------------------- /downloadphp.ps1: -------------------------------------------------------------------------------- 1 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 2 | $workingdir = (Get-Location).Path 3 | $arch = (Get-WmiObject win32_operatingsystem | select osarchitecture).osarchitecture.Substring(0, 2).replace('32', '86') 4 | if($PSVersiontable.PSVersion.Major -lt 3) 5 | { 6 | Write-Warning "Please download php and place it in the same folder as this script." 7 | Write-Output "Download from: https://windows.php.net/downloads/releases/php-7.2.7-nts-Win32-VC15-x$arch.zip" 8 | Write-Output ("Save to this directory: $workingdir `nand rename it to php.zip" -f (Get-Location).Path) 9 | Read-Host "Press Enter when you're done!" 10 | } 11 | else 12 | { 13 | Invoke-WebRequest -Uri https://windows.php.net/downloads/releases/php-7.2.7-nts-Win32-VC15-x$arch.zip -OutFile php.zip 14 | } 15 | if($PSVersiontable.PSVersion.Major -lt 5) 16 | { 17 | if((Test-Path "$workingdir\php") -eq $False) 18 | { 19 | Add-Type -AssemblyName System.IO.Compression.FileSystem 20 | [IO.Compression.ZipFile]::ExtractToDirectory('php.zip', 'php\') 21 | } 22 | else 23 | { 24 | Write-Warning "Directory $work\php already found. Delete if necessary!" 25 | } 26 | } 27 | else 28 | { 29 | Expand-Archive -LiteralPath php.zip -DestinationPath php\ -Force 30 | } 31 | Copy-Item -Path php\php.ini-production -Destination php\php.ini 32 | ((Get-Content php\php.ini)) -Replace ";extension=curl", ("extension=" + (Get-Item -Path ".\php") + "\ext\php_curl.dll") | Set-Content php\php.ini 33 | -------------------------------------------------------------------------------- /downloadpython.ps1: -------------------------------------------------------------------------------- 1 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 2 | $workingdir = (Get-Location).Path 3 | if($PSVersiontable.PSVersion.Major -lt 3) 4 | { 5 | Write-Warning "Please download Python and place it in the same folder as this script." 6 | Write-Output "Download from: https://www.python.org/ftp/python/3.6.5/python-3.6.5-embed-amd64.zip" 7 | Write-Output ("Save to this directory: $workingdir `nand rename it to python.zip" -f (Get-Location).Path) 8 | Write-Output "Download from: https://bootstrap.pypa.io/get-pip.py" 9 | Write-Output ("Save to this directory: $workingdir" -f (Get-Location).Path) 10 | Read-Host "Press Enter when you're done!" 11 | } 12 | else 13 | { 14 | Invoke-WebRequest -Uri https://www.python.org/ftp/python/3.6.5/python-3.6.5-embed-amd64.zip -OutFile python.zip 15 | Invoke-WebRequest -Uri https://bootstrap.pypa.io/get-pip.py -OutFile get-pip.py 16 | } 17 | if($PSVersiontable.PSVersion.Major -lt 5) 18 | { 19 | if((Test-Path "$workingdir\python") -eq $False) 20 | { 21 | Add-Type -AssemblyName System.IO.Compression.FileSystem 22 | [IO.Compression.ZipFile]::ExtractToDirectory('python.zip', 'python\') 23 | } 24 | else 25 | { 26 | Write-Warning "Directory $work\python already found. Delete if necessary!" 27 | } 28 | } 29 | else 30 | { 31 | Expand-Archive -LiteralPath python.zip -DestinationPath python\ -Force 32 | } 33 | 34 | ((Get-Content python\python36._pth)) -Replace "#import", "import" | Set-Content python\python36._pth 35 | 36 | python\python.exe get-pip.py 37 | python\python.exe -m pip install requests tqdm colorama 38 | 39 | del python.zip 40 | del get-pip.py 41 | -------------------------------------------------------------------------------- /python-cheat.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | if not exist python\python.exe ( 5 | echo Python portable wasn't detected; we'll download and install it for you. 6 | PowerShell -ExecutionPolicy Unrestricted -File "downloadpython.ps1" 7 | ) 8 | 9 | cls 10 | echo The script can be terminated at any time by pressing Ctrl-C or clicking X 11 | echo ------------------------------------------------------------------------- 12 | 13 | :start 14 | python\python.exe cheat.py 15 | goto start 16 | --------------------------------------------------------------------------------