├── .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 |
--------------------------------------------------------------------------------