Compare commits

...

192 Commits

Author SHA1 Message Date
OMGeeky
4c2113c58c Implement checking for savegame edits with a hash 2024-01-26 17:19:03 +01:00
Nut.andor
3fbc7cf65f authors + translation% 2023-05-01 20:57:16 +02:00
Nut.andor
dc18289d18 POTC 2023-05-01 03:03:36 +02:00
Nut.andor
6473e231e4 Mikhail basket 2023-05-01 02:56:49 +02:00
Nut.andor
33a61b7a57 Merge branch 'v0.8.5' 2023-05-01 02:09:07 +02:00
Nut.andor
c070a39f7c Ratdom content 2023-05-01 02:00:00 +02:00
Nut.andor
995ec0f697 active devs 2023-05-01 01:58:18 +02:00
Nut.andor
6cba6872c9 version name 2023-05-01 01:17:20 +02:00
Nut.andor
8812f664e7 authors 2023-05-01 01:15:09 +02:00
Nut.andor
d9113b2dab Merge branch 'master' into v0.8.5 2023-05-01 01:03:25 +02:00
Nut.andor
ad0025ba03 Merge remote-tracking branch 'hosted.weblate/master' 2023-05-01 00:56:15 +02:00
Nut.andor
984aa17cc0 Merge branch 'development' into v0.8.5 2023-05-01 00:45:33 +02:00
Seven
4cad05ce6b Translated using Weblate (Spanish (Argentina))
Currently translated at 0.3% (49 of 15108 strings)
2023-04-29 05:05:40 +02:00
Jiri Zizkin Zizka
197ab946be Translated using Weblate (Czech)
Currently translated at 94.5% (14291 of 15109 strings)
2023-04-29 05:05:37 +02:00
Daniel Stasiak
e4cdbc1840 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-04-29 05:05:34 +02:00
Seven
04f00553ae Translated using Weblate (Spanish)
Currently translated at 84.4% (12763 of 15109 strings)
2023-04-29 05:05:30 +02:00
Daniel Stasiak
0c02902ceb Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-04-26 11:24:53 +02:00
Seven
aba93fb15b Translated using Weblate (Spanish)
Currently translated at 84.3% (12740 of 15109 strings)
2023-04-26 11:24:50 +02:00
Seven
1c8330b99a Translated using Weblate (Spanish)
Currently translated at 100.0% (610 of 610 strings)
2023-04-25 19:50:01 +02:00
Seven
a8de7c2860 Translated using Weblate (Spanish)
Currently translated at 84.2% (12727 of 15109 strings)
2023-04-25 19:50:00 +02:00
Seven
4ca8bbfc12 Translated using Weblate (Spanish)
Currently translated at 84.0% (12700 of 15109 strings)
2023-04-25 18:53:44 +02:00
Daniel Stasiak
a5c004e4d4 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-04-25 18:19:25 +02:00
Seven
e957e409e0 Translated using Weblate (Spanish)
Currently translated at 83.9% (12684 of 15109 strings)
2023-04-25 18:19:21 +02:00
Jiri Zizkin Zizka
70d6340c11 Translated using Weblate (Czech)
Currently translated at 94.5% (14280 of 15109 strings)
2023-04-22 22:53:01 +02:00
Jiri Zizkin Zizka
bbbb69b175 Translated using Weblate (Czech)
Currently translated at 94.4% (14273 of 15109 strings)
2023-04-21 20:37:11 +02:00
ilya
4d5b7d8667 Translated using Weblate (Russian)
Currently translated at 100.0% (15109 of 15109 strings)
2023-04-21 20:37:08 +02:00
Daniel Stasiak
43da08f57d Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-04-21 20:37:04 +02:00
Jiri Zizkin Zizka
7b97367bdd Translated using Weblate (Czech)
Currently translated at 94.4% (14272 of 15109 strings)
2023-04-19 21:55:23 +02:00
Daniel Stasiak
e6fb647b99 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-04-19 21:55:21 +02:00
Daniel Stasiak
5e4d7aba18 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-04-18 10:35:09 +02:00
Daniel Stasiak
a96086b9af Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-04-17 21:14:23 +02:00
Joan Josep
0566cf899c Translated using Weblate (Catalan)
Currently translated at 100.0% (610 of 610 strings)
2023-04-17 12:49:15 +02:00
Jiri Zizkin Zizka
c8e87fb3d0 Translated using Weblate (Czech)
Currently translated at 94.4% (14265 of 15109 strings)
2023-04-17 12:49:14 +02:00
Nut.andor
0a56a0ba86 Credits for external products 2023-04-16 19:58:58 +02:00
Nut.andor
026ccde2ff Credits for external products 2023-04-16 10:33:28 +02:00
Nut.andor
7813994b81 Release Notes 2023-04-16 09:52:04 +02:00
Nut.andor
d3b070844f set high quality filters default to off 2023-04-16 09:18:45 +02:00
Emre Kaynak
7a879f99b1 Translated using Weblate (Turkish)
Currently translated at 97.5% (595 of 610 strings)
2023-04-16 06:51:16 +02:00
Jiri Zizkin Zizka
e810cdb3cb Translated using Weblate (Czech)
Currently translated at 94.3% (14257 of 15109 strings)
2023-04-16 06:51:15 +02:00
Emre Kaynak
398be07c31 Translated using Weblate (Turkish)
Currently translated at 36.1% (5455 of 15109 strings)
2023-04-16 06:51:13 +02:00
sefa mert katiç
6db4c7bb3c Translated using Weblate (Turkish)
Currently translated at 36.0% (5449 of 15109 strings)
2023-04-14 23:53:02 +02:00
Sarah Chiandotto
c402824641 Translated using Weblate (Italian)
Currently translated at 83.4% (12602 of 15109 strings)
2023-04-14 23:53:00 +02:00
Martin
abc020e8f5 Translated using Weblate (German)
Currently translated at 93.6% (14148 of 15109 strings)
2023-04-14 23:52:57 +02:00
sefa mert katiç
82caf260ce Translated using Weblate (Turkish)
Currently translated at 35.8% (5417 of 15109 strings)
2023-04-12 00:53:21 +02:00
Renald Afrenzia
cbcf5ff8ca Translated using Weblate (Indonesian)
Currently translated at 96.0% (586 of 610 strings)
2023-04-11 01:47:48 +02:00
Jiri Zizkin Zizka
39d07bf8a8 Translated using Weblate (Czech)
Currently translated at 94.2% (14245 of 15109 strings)
2023-04-11 01:47:47 +02:00
Lucas Araujo
93174f0d65 Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.7% (14166 of 15109 strings)
2023-04-09 12:53:09 +02:00
Joan Josep
6afbeb9a2a Translated using Weblate (Catalan)
Currently translated at 100.0% (610 of 610 strings)
2023-04-08 11:52:57 +02:00
Lucas Araujo
a7cbd644f3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.7% (14164 of 15109 strings)
2023-04-08 11:52:56 +02:00
Joan Josep
f674aff07c Translated using Weblate (Catalan)
Currently translated at 100.0% (610 of 610 strings)
2023-04-07 20:53:14 +02:00
Joan Josep
07b89a288c Translated using Weblate (Catalan)
Currently translated at 16.5% (2493 of 15109 strings)
2023-04-07 20:53:12 +02:00
Joan Josep
0264c418ca Translated using Weblate (Catalan)
Currently translated at 100.0% (610 of 610 strings)
2023-04-05 20:53:10 +02:00
Jiri Zizkin Zizka
494b9c47cc Translated using Weblate (Czech)
Currently translated at 94.1% (14225 of 15109 strings)
2023-04-05 20:53:09 +02:00
Pablo Jeldres
6ff6c6dd6c Translated using Weblate (Spanish)
Currently translated at 83.8% (12670 of 15109 strings)
2023-04-05 20:53:06 +02:00
Jiri Zizkin Zizka
00ab587265 Translated using Weblate (Czech)
Currently translated at 94.1% (14219 of 15109 strings)
2023-04-03 20:41:57 +02:00
Hong Quan
67fa480805 Translated using Weblate (Vietnamese)
Currently translated at 12.4% (76 of 610 strings)
2023-04-02 17:41:39 +02:00
Lucas Araujo
91db56e7a7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.7% (14164 of 15109 strings)
2023-04-02 17:41:38 +02:00
Cicy Chen
36d52bedad Added translation using Weblate (Chinese (Literary)) 2023-03-31 06:27:20 +02:00
Hong Quan
c36b676720 Translated using Weblate (Vietnamese)
Currently translated at 1.4% (9 of 610 strings)
2023-03-29 15:40:40 +02:00
Lustheart Faezeiros
5e0a999249 Translated using Weblate (Thai)
Currently translated at 12.7% (78 of 610 strings)
2023-03-29 15:40:40 +02:00
Jiri Zizkin Zizka
8fff4aff60 Translated using Weblate (Czech)
Currently translated at 94.1% (14218 of 15109 strings)
2023-03-29 15:40:39 +02:00
Lustheart Faezeiros
8c5344ff37 Translated using Weblate (Thai)
Currently translated at 1.2% (195 of 15109 strings)
2023-03-29 15:40:36 +02:00
ssantos
91e49b6c4a Translated using Weblate (Portuguese)
Currently translated at 76.8% (11618 of 15109 strings)
2023-03-29 15:40:34 +02:00
Hong Quan
52cfb37b21 Added translation using Weblate (Vietnamese) 2023-03-27 16:12:56 +02:00
DragonChen TW
87886f08bb Translated using Weblate (Chinese (Traditional))
Currently translated at 6.6% (998 of 15109 strings)
2023-03-26 11:32:21 +02:00
Jiri Zizkin Zizka
a31d7f9589 Translated using Weblate (Czech)
Currently translated at 93.9% (14198 of 15109 strings)
2023-03-26 11:32:19 +02:00
DragonChen TW
5600957f32 Translated using Weblate (Chinese (Traditional))
Currently translated at 6.5% (996 of 15109 strings)
2023-03-25 11:39:22 +01:00
ilya
d6840a83c9 Translated using Weblate (Russian)
Currently translated at 100.0% (610 of 610 strings)
2023-03-25 11:39:20 +01:00
DragonChen TW
06fdd39648 Translated using Weblate (Chinese (Traditional))
Currently translated at 6.5% (990 of 15109 strings)
2023-03-23 22:42:13 +01:00
ilya
b5763e7f76 Translated using Weblate (Russian)
Currently translated at 100.0% (610 of 610 strings)
2023-03-23 22:42:10 +01:00
DragonChen TW
75f01f20b9 Translated using Weblate (Chinese (Traditional))
Currently translated at 6.4% (979 of 15109 strings)
2023-03-22 14:12:10 +01:00
Hong Quan
dd366ab69e Translated using Weblate (Vietnamese)
Currently translated at 0.1% (3 of 15108 strings)
2023-03-21 21:24:23 +01:00
DragonChen TW
6ab443ec2c Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (610 of 610 strings)
2023-03-21 21:24:21 +01:00
DragonChen TW
e0056cbf5c Translated using Weblate (Chinese (Traditional))
Currently translated at 6.4% (978 of 15109 strings)
2023-03-21 21:24:20 +01:00
ilya
9f9397e1f3 Translated using Weblate (Russian)
Currently translated at 100.0% (610 of 610 strings)
2023-03-21 21:24:18 +01:00
Hong Quan
d5a43cc7e1 Added translation using Weblate (Vietnamese) 2023-03-20 17:34:05 +01:00
DragonChen TW
408d8aa8b8 Translated using Weblate (Chinese (Traditional))
Currently translated at 97.5% (595 of 610 strings)
2023-03-19 08:40:41 +01:00
DragonChen TW
1e663069e8 Translated using Weblate (Chinese (Traditional))
Currently translated at 6.4% (977 of 15109 strings)
2023-03-19 08:40:40 +01:00
Nut.andor
dbcec1a199 James' PR 54 to master - redirected to branch development
Changed XP death penalty to 20%
Give the player 9 BC by default
2023-03-18 18:53:53 +01:00
Noza
04b4193548 Translated using Weblate (Indonesian)
Currently translated at 17.0% (2580 of 15109 strings)
2023-03-14 19:44:12 +01:00
Jiri Zizkin Zizka
058803184f Translated using Weblate (Czech)
Currently translated at 93.7% (14170 of 15109 strings)
2023-03-10 22:41:53 +01:00
Jiri Zizkin Zizka
5b5912c5bc Translated using Weblate (Czech)
Currently translated at 93.7% (14170 of 15109 strings)
2023-03-09 21:42:07 +01:00
Lacrom
759e632c07 Translated using Weblate (French)
Currently translated at 84.3% (12739 of 15109 strings)
2023-03-08 16:42:04 +01:00
Johny The Pvp God
6e311ecb64 Translated using Weblate (Romanian)
Currently translated at 56.2% (343 of 610 strings)
2023-03-07 00:41:35 +01:00
Jiri Zizkin Zizka
b480fd9173 Translated using Weblate (Czech)
Currently translated at 93.7% (14170 of 15109 strings)
2023-03-07 00:41:35 +01:00
Nut.andor
226459ddb8 skill improve text clarified 2023-03-05 18:05:46 +01:00
BE Kharel
92de033bc8 Translated using Weblate (Arabic)
Currently translated at 9.6% (1459 of 15108 strings)
2023-03-04 11:41:54 +01:00
BE Kharel
faf0dd061c Translated using Weblate (Bulgarian)
Currently translated at 0.5% (89 of 15109 strings)
2023-03-04 11:41:52 +01:00
Jiri Zizkin Zizka
9eaf9ea2ed Translated using Weblate (Czech)
Currently translated at 93.7% (14168 of 15109 strings)
2023-03-04 11:41:50 +01:00
ilya
00c4f77251 Translated using Weblate (Russian)
Currently translated at 100.0% (15109 of 15109 strings)
2023-03-04 11:41:47 +01:00
Nut.andor
96ef9eedbb Content format reference 2023-02-26 01:35:28 +01:00
Nut.andor
a5d94c03e2 Migration of links to git repo AndorsTrailRelease 2023-02-26 01:34:35 +01:00
Nut.andor
9d15d380a3 Migration of links to git repo AndorsTrailRelease 2023-02-26 01:27:10 +01:00
Nut.andor
bff780c837 reformat for better reading 2023-02-26 01:11:40 +01:00
Nut.andor
0c493952c8 Migration of links to git repo AndorsTrailRelease 2023-02-25 23:47:10 +01:00
Jiri Zizkin Zizka
a1f6e1d29c Translated using Weblate (Czech)
Currently translated at 93.5% (14135 of 15109 strings)
2023-02-25 05:40:05 +01:00
aircqsj
fc71f43755 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.9% (15105 of 15109 strings)
2023-02-25 05:40:02 +01:00
Jiri Zizkin Zizka
16afcf4886 Translated using Weblate (Czech)
Currently translated at 93.4% (14114 of 15109 strings)
2023-02-23 02:46:02 +01:00
Daniel Stasiak
b64a3c2109 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-23 02:45:59 +01:00
Casey Malessa
5c0dc66183 Translated using Weblate (Japanese)
Currently translated at 95.4% (582 of 610 strings)
2023-02-23 02:45:56 +01:00
Nut.andor
3047f653bd prep beta 2023-02-21 21:57:48 +01:00
Nut.andor
393a478d13 prep beta 2023-02-21 21:31:14 +01:00
Michael Bæk
560cb326f9 Added translation using Weblate (Danish) 2023-02-20 20:45:04 +01:00
Jiri Zizkin Zizka
3fe2d7260f Translated using Weblate (Czech)
Currently translated at 93.1% (14074 of 15109 strings)
2023-02-20 20:41:50 +01:00
Jiri Zizkin Zizka
2cd4bc7ee5 Translated using Weblate (Czech)
Currently translated at 92.6% (13997 of 15109 strings)
2023-02-15 23:40:12 +01:00
Daniel Stasiak
19468b591d Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-15 23:40:09 +01:00
ssantos
903117d32f Translated using Weblate (Portuguese)
Currently translated at 76.4% (11555 of 15109 strings)
2023-02-12 16:40:07 +01:00
Jiri Zizkin Zizka
f5bfc98c62 Translated using Weblate (Czech)
Currently translated at 92.6% (13995 of 15109 strings)
2023-02-10 11:39:33 +01:00
Daniel Stasiak
3274feb1e9 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-10 11:39:30 +01:00
Daniel Stasiak
392b4d8577 Translated using Weblate (Polish)
Currently translated at 100.0% (610 of 610 strings)
2023-02-10 11:39:28 +01:00
Yan Chen
92e4afde4b Translated using Weblate (Esperanto)
Currently translated at 17.5% (107 of 610 strings)
2023-02-08 23:40:01 +01:00
Jiri Zizkin Zizka
0c7724fe33 Translated using Weblate (Czech)
Currently translated at 100.0% (610 of 610 strings)
2023-02-08 23:40:00 +01:00
Jiri Zizkin Zizka
ca5be34fc2 Translated using Weblate (Czech)
Currently translated at 92.5% (13986 of 15109 strings)
2023-02-08 23:40:00 +01:00
Daniel Stasiak
28c3c7a0ec Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-08 23:39:57 +01:00
Daniel Stasiak
7f39cf1a6f Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-07 15:40:44 +01:00
gallegonovato
365839571a Translated using Weblate (Spanish)
Currently translated at 100.0% (610 of 610 strings)
2023-02-04 20:39:45 +01:00
Daniel Stasiak
5c3a04ca77 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-04 20:39:45 +01:00
Nut.andor
758a580063 changed png 2023-02-04 01:28:09 +01:00
Daniel Stasiak
ad5dbc9f9d Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-03 12:00:45 +01:00
Casey Malessa
4a86680928 Translated using Weblate (Japanese)
Currently translated at 92.2% (13933 of 15109 strings)
2023-02-03 12:00:42 +01:00
ilya
ece63cf442 Translated using Weblate (Russian)
Currently translated at 100.0% (15109 of 15109 strings)
2023-02-01 22:45:44 +01:00
Daniel Stasiak
b487140a1b Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-02-01 22:45:42 +01:00
Casey Malessa
ad72bf8895 Translated using Weblate (Japanese)
Currently translated at 92.1% (13928 of 15109 strings)
2023-02-01 22:45:40 +01:00
Nut.andor
5919c429d2 Next version 2023-01-31 22:14:49 +01:00
Nut.andor
0b6580e7c9 whatsNew 69 v0.8.4.1 2023-01-31 21:14:03 +01:00
Nut.andor
6efb2b3860 unneeded files bloats the apk 2023-01-31 21:14:03 +01:00
ilya
7ae084073f Translated using Weblate (Russian)
Currently translated at 100.0% (15109 of 15109 strings)
2023-01-31 19:42:48 +01:00
Daniel Stasiak
36df0b1732 Translated using Weblate (Polish)
Currently translated at 91.8% (13884 of 15109 strings)
2023-01-31 19:42:45 +01:00
ilya
b8fb100bd5 Translated using Weblate (Russian)
Currently translated at 100.0% (610 of 610 strings)
2023-01-31 19:42:44 +01:00
Nut.andor
82b904d176 target 31 needed for Google Play + export 2023-01-30 22:04:10 +01:00
Nut.andor
111aca868c hotfix 2023-01-29 22:40:51 +01:00
Nut.andor
2171c16d47 whatsNew 2023-01-29 18:38:37 +01:00
Nut.andor
acd4d84783 potc 2023-01-29 18:25:36 +01:00
Nut.andor
cc25cc97f1 Translations 2023-01-29 18:10:15 +01:00
Nut.andor
93ecebc448 Merge branch 'V0.8.4'
# Conflicts:
#	AndorsTrail/app/src/main/AndroidManifest.xml
#	AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java
2023-01-29 17:58:55 +01:00
Nut.andor
f5713a3eb2 Merge remote-tracking branch 'hosted.weblate/master' 2023-01-29 17:51:49 +01:00
Jiri Zizkin Zizka
90001bb4d6 Translated using Weblate (Czech)
Currently translated at 93.0% (13958 of 14998 strings)
2023-01-29 17:48:01 +01:00
Daniel Stasiak
357cab9b1e Translated using Weblate (Polish)
Currently translated at 92.5% (13884 of 14998 strings)
2023-01-29 17:47:58 +01:00
Jiri Zizkin Zizka
5ea272f286 Translated using Weblate (Czech)
Currently translated at 100.0% (602 of 602 strings)
2023-01-28 00:57:24 +01:00
Jiri Zizkin Zizka
0c876bd1c5 Translated using Weblate (Czech)
Currently translated at 92.9% (13934 of 14998 strings)
2023-01-28 00:57:24 +01:00
Daniel Stasiak
0ac2999694 Translated using Weblate (Polish)
Currently translated at 92.5% (13884 of 14998 strings)
2023-01-28 00:57:22 +01:00
Nut.andor
49af3c4b6d some more map fixex 2023-01-24 21:30:37 +01:00
Nut.andor
2a54b31d31 beta version nbr 2023-01-24 21:29:05 +01:00
Nut.andor
64766ce50a potc 2023-01-24 20:47:39 +01:00
Nut.andor
311accfdd3 prep for beta 2023-01-24 20:21:38 +01:00
Nut.andor
2b5bf2fa4a Merge remote-tracking branch 'origin/V0.8.4' into V0.8.4 2023-01-21 13:32:42 +01:00
Nut
64342c96d7 Merge pull request #51 from OMGeeky/import-export-fix-1
Import/Export improvements
2023-01-21 13:31:28 +01:00
OMGeeky
f8649bb0a5 Cleanup 2023-01-21 12:55:16 +01:00
Nut.andor
fde6608c26 translations, copyright 2023-01-21 01:06:55 +01:00
Nut.andor
3f339c14a2 translations, copyright 2023-01-21 00:40:52 +01:00
Nut.andor
922db05c4d Merge remote-tracking branch 'hosted.weblate/master' into V0.8.4 2023-01-21 00:31:16 +01:00
Eduardo
b849de8b66 Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.4% (14159 of 14998 strings)
2023-01-21 00:21:55 +01:00
Eduardo
f798b29729 Translated using Weblate (Portuguese)
Currently translated at 76.4% (11470 of 14998 strings)
2023-01-21 00:21:51 +01:00
MARCO ACORTE
e58e7a2588 Translated using Weblate (Italian)
Currently translated at 83.9% (12598 of 14998 strings)
2023-01-21 00:21:50 +01:00
Eduardo
a1ef339831 Translated using Weblate (Portuguese)
Currently translated at 76.4% (11466 of 14998 strings)
2023-01-20 00:59:15 +01:00
gallegonovato
2002d51f6b Translated using Weblate (Spanish)
Currently translated at 100.0% (602 of 602 strings)
2023-01-15 19:50:17 +01:00
Nut.andor
025283b70b ratdom graphics 2023-01-14 00:57:13 +01:00
Predrag
577b1aa034 Translated using Weblate (Serbian)
Currently translated at 1.8% (275 of 14997 strings)
2023-01-13 10:52:03 +01:00
Ömer Faruk Çakmak
da637c0cc8 Translated using Weblate (Turkish)
Currently translated at 36.1% (5416 of 14998 strings)
2023-01-13 10:52:01 +01:00
Vincenzo De Concilio
d026ff690a Translated using Weblate (Italian)
Currently translated at 96.8% (583 of 602 strings)
2023-01-13 10:51:59 +01:00
Predrag
d14ed56669 Added translation using Weblate (Serbian) 2023-01-12 09:31:54 +01:00
Predrag
64cfacb7ff Translated using Weblate (Serbian)
Currently translated at 0.9% (144 of 14997 strings)
2023-01-11 16:55:04 +01:00
Ömer Faruk Çakmak
98a800e070 Translated using Weblate (Turkish)
Currently translated at 100.0% (602 of 602 strings)
2023-01-11 16:55:02 +01:00
Predrag
35219c8e88 Translated using Weblate (Serbian)
Currently translated at 0.5% (89 of 14997 strings)
2023-01-11 16:22:50 +01:00
Predrag
b636cad347 Added translation using Weblate (Serbian) 2023-01-11 15:35:23 +01:00
Ian Lethe (Windseeker)
733328cd29 Translated using Weblate (Korean)
Currently translated at 9.3% (1408 of 14998 strings)
2023-01-09 12:13:27 +01:00
Ian Lethe (Windseeker)
36db28f805 Translated using Weblate (Korean)
Currently translated at 9.3% (1404 of 14998 strings)
2023-01-09 08:52:00 +01:00
Szabó Péter
6d4c1c3c14 Translated using Weblate (Hungarian)
Currently translated at 89.0% (536 of 602 strings)
2023-01-08 13:52:07 +01:00
David Tamas Szabo
eadb95f8e8 Translated using Weblate (Hungarian)
Currently translated at 27.8% (4177 of 14998 strings)
2023-01-08 13:52:07 +01:00
Szabó Péter
014f67f270 Translated using Weblate (Hungarian)
Currently translated at 27.8% (4177 of 14998 strings)
2023-01-08 13:52:06 +01:00
Nut.andor
cd8657adb4 QuickLearner 5% -> 10% 2023-01-08 00:57:36 +01:00
gallegonovato
c007fc4d1c Translated using Weblate (Spanish)
Currently translated at 100.0% (602 of 602 strings)
2023-01-01 19:52:07 +01:00
gallegonovato
7e6c864ba3 Translated using Weblate (Spanish)
Currently translated at 84.3% (12658 of 14998 strings)
2023-01-01 19:52:06 +01:00
Kristoffer Grundström
f2da8f767a Translated using Weblate (Swedish)
Currently translated at 4.5% (689 of 14998 strings)
2022-12-29 13:27:04 +01:00
Nut.andor
bd856541d6 fix Burhczyd 2022-12-25 01:34:00 +01:00
Dan
bdde2176d9 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (602 of 602 strings)
2022-12-15 21:48:06 +01:00
Dan
171893a2c1 Translated using Weblate (Ukrainian)
Currently translated at 9.8% (1478 of 14998 strings)
2022-12-14 13:51:22 +01:00
rabituwu
c6c1d6d03d Translated using Weblate (Filipino)
Currently translated at 1.9% (295 of 14997 strings)
2022-12-12 14:51:24 +01:00
Nut.andor
ec8b114782 fix Burhczyd 2022-12-12 08:52:43 +01:00
Nut.andor
aaecb8ae4a Reformatting json for better reading 2022-12-11 03:56:15 +01:00
OMGeeky
4e2e3b370c formatting 2022-12-08 17:56:41 +01:00
OMGeeky
c14f8f84a0 Import/Export of the worldmap changed to zip files & some clarifications 2022-12-08 00:18:28 +01:00
Lucas Araujo
13c8012710 Translated using Weblate (Portuguese (Brazil))
Currently translated at 92.9% (13935 of 14998 strings)
2022-12-03 16:48:55 +01:00
عادل نصري
3806d77b26 Translated using Weblate (Arabic)
Currently translated at 9.7% (1457 of 14997 strings)
2022-11-30 14:48:56 +01:00
عادل نصري
af94220715 Translated using Weblate (Arabic)
Currently translated at 100.0% (602 of 602 strings)
2022-11-29 20:48:04 +01:00
عادل نصري
af07c47832 Translated using Weblate (Arabic)
Currently translated at 9.7% (1456 of 14997 strings)
2022-11-29 20:48:03 +01:00
Nut.andor
322007e5ac Next version number v0-8-4 68 2022-11-19 00:23:26 +01:00
aircqsj
af5cdb9d49 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (602 of 602 strings)
2022-11-16 13:48:29 +01:00
Nut.andor
3ada8ceaa7 V0.8.3 2022-11-13 00:54:22 +01:00
3raven
68fa0068dc Translated using Weblate (French)
Currently translated at 84.8% (12733 of 14998 strings)
2022-11-09 22:50:27 +01:00
ilya
5dfa750227 Translated using Weblate (Russian)
Currently translated at 100.0% (602 of 602 strings)
2022-11-08 19:03:26 +01:00
ilya
cbe0773310 Translated using Weblate (Russian)
Currently translated at 100.0% (602 of 602 strings)
2022-11-07 14:03:24 +01:00
400 changed files with 625419 additions and 13323 deletions

View File

@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.gpl.rpg.AndorsTrail"
minSdkVersion 14
targetSdkVersion 30
targetSdkVersion 31
}
buildTypes {

View File

@@ -3,15 +3,11 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gpl.rpg.AndorsTrail"
android:versionCode="67"
android:versionName="0.8.3dev"
android:versionCode="70"
android:versionName="0.8.5"
android:installLocation="auto"
>
<uses-sdk
android:minSdkVersion="4"
android:targetSdkVersion="30"
/>
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
@@ -37,6 +33,7 @@
>
<activity
android:name="com.gpl.rpg.AndorsTrail.activity.StartScreenActivity"
android:exported="true"
android:clearTaskOnLaunch="true"
>
<intent-filter>

View File

@@ -28,11 +28,11 @@ public final class AndorsTrailApplication extends Application {
public static final boolean DEVELOPMENT_FASTSPEED = false;
public static final boolean DEVELOPMENT_VALIDATEDATA = false;
public static final boolean DEVELOPMENT_DEBUGMESSAGES = false;
public static final String CURRENT_VERSION_DISPLAY = "0.8.3dev";
public static final String CURRENT_VERSION_DISPLAY = "0.8.5";
public static final boolean IS_RELEASE_VERSION = !CURRENT_VERSION_DISPLAY.matches(".*[a-d].*");
public static final boolean DEVELOPMENT_INCOMPATIBLE_SAVEGAMES = DEVELOPMENT_DEBUGRESOURCES || DEVELOPMENT_DEBUGBUTTONS || DEVELOPMENT_FASTSPEED || !IS_RELEASE_VERSION;
public static final int DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION = 999;
public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION : 67;
public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION : 71;
private final AndorsTrailPreferences preferences = new AndorsTrailPreferences();
private WorldContext world = new WorldContext();

View File

@@ -78,7 +78,7 @@ public final class AndorsTrailPreferences {
dest.dpadTransparency = Integer.parseInt(prefs.getString("dpadtransparency", Integer.toString(DPAD_TRANSPARENCY_50_PERCENT)));
dest.dpadMinimizeable = prefs.getBoolean("dpadMinimizeable", true);
dest.optimizedDrawing = prefs.getBoolean("optimized_drawing", false);
dest.highQualityFilters = prefs.getBoolean("high_quality_filters", true);
dest.highQualityFilters = prefs.getBoolean("high_quality_filters", false);
dest.enableUiAnimations = prefs.getBoolean("enableUiAnimations", true);
dest.displayOverwriteSavegame = Integer.parseInt(prefs.getString("display_overwrite_savegame", Integer.toString(CONFIRM_OVERWRITE_SAVEGAME_ALWAYS)));
dest.quickslotsPosition = Integer.parseInt(prefs.getString("quickslots_placement", Integer.toString(QUICKSLOTS_POSITION_HORIZONTAL_CENTER_BOTTOM)));

View File

@@ -134,8 +134,9 @@ public final class WorldSetup {
onSceneLoadedListener = null;
if (o == null) return;
if (loadResult == Savegames.LoadSavegameResult.success) {
o.onSceneLoaded();
if (loadResult == Savegames.LoadSavegameResult.success
|| loadResult == Savegames.LoadSavegameResult.editDetected) {
o.onSceneLoaded(loadResult);
} else {
o.onSceneLoadFailed(loadResult);
}
@@ -161,7 +162,7 @@ public final class WorldSetup {
public static interface OnSceneLoadedListener {
void onSceneLoaded();
void onSceneLoaded(Savegames.LoadSavegameResult loadResult);
void onSceneLoadFailed(Savegames.LoadSavegameResult loadResult);
}
public interface OnResourcesLoadedListener {

View File

@@ -11,9 +11,7 @@ import java.util.List;
import java.util.Objects;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
@@ -122,8 +120,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
boolean hasSavegames = !Savegames.getUsedSavegameSlots(this).isEmpty();
exportSaves.setEnabled(hasSavegames);
}
else{
} else {
exportImportContainer.setVisibility(View.GONE);
}
}
@@ -133,12 +130,17 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 2;
private void checkAndRequestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, READ_EXTERNAL_STORAGE_REQUEST);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
READ_EXTERNAL_STORAGE_REQUEST);
}
if (getApplicationContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_REQUEST);
if (getApplicationContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
WRITE_EXTERNAL_STORAGE_REQUEST);
}
}
}
@@ -152,11 +154,15 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}
}
private void addSavegameSlotButtons(ViewGroup parent, LayoutParams params, List<Integer> usedSavegameSlots) {
private void addSavegameSlotButtons(ViewGroup parent,
LayoutParams params,
List<Integer> usedSavegameSlots) {
int unused = 1;
for (int slot : usedSavegameSlots) {
final FileHeader header = Savegames.quickload(this, slot);
if (header == null) continue;
if (header == null) {
continue;
}
while (unused < slot) {
Button b = new Button(this);
@@ -180,65 +186,76 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}
}
private void cancelLoadSaveActivity(int slot){
private void cancelLoadSaveActivity(int slot) {
completeLoadSaveActivity(slot, false);
}
private void completeLoadSaveActivity(int slot) {
completeLoadSaveActivity(slot, true);
}
private void completeLoadSaveActivity(int slot, boolean success) {
Intent i = new Intent();
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) {
slot = getFirstFreeSlot();
} else if (slot == SLOT_NUMBER_EXPORT_SAVEGAMES
|| slot == SLOT_NUMBER_IMPORT_SAVEGAMES
|| slot == SLOT_NUMBER_IMPORT_WORLDMAP) {
|| slot == SLOT_NUMBER_IMPORT_SAVEGAMES
|| slot == SLOT_NUMBER_IMPORT_WORLDMAP) {
i.putExtra("import_export", true);
if(slot == SLOT_NUMBER_IMPORT_WORLDMAP){
if (slot == SLOT_NUMBER_IMPORT_WORLDMAP) {
i.putExtra("import_worldmap", true);
}
if(slot == SLOT_NUMBER_IMPORT_SAVEGAMES){
if (slot == SLOT_NUMBER_IMPORT_SAVEGAMES) {
i.putExtra("import_savegames", true);
}
if(slot == SLOT_NUMBER_EXPORT_SAVEGAMES){
if (slot == SLOT_NUMBER_EXPORT_SAVEGAMES) {
i.putExtra("export", true);
}
} else if (slot < SLOT_NUMBER_FIRST_SLOT)
} else if (slot < SLOT_NUMBER_FIRST_SLOT) {
slot = SLOT_NUMBER_FIRST_SLOT;
}
i.putExtra("slot", slot);
if(success) setResult(Activity.RESULT_OK, i);
else setResult(Activity.RESULT_CANCELED, i);
if (success) {
setResult(Activity.RESULT_OK, i);
} else {
setResult(Activity.RESULT_CANCELED, i);
}
LoadSaveActivity.this.finish();
}
private int getFirstFreeSlot() {
int slot;
List<Integer> usedSlots = Savegames.getUsedSavegameSlots(this);
if (usedSlots.isEmpty())
if (usedSlots.isEmpty()) {
slot = SLOT_NUMBER_FIRST_SLOT;
else slot = Collections.max(usedSlots) + 1;
} else {
slot = Collections.max(usedSlots) + 1;
}
return slot;
}
private String getConfirmOverwriteQuestion(int slot) {
if (isLoading)
if (isLoading) {
return null;
}
return getConfirmOverwriteQuestionIgnoringLoading(slot);
}
private String getConfirmOverwriteQuestionIgnoringLoading(int slot) {
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT)
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) {
return null;//creating a new savegame
}
if (!Savegames.getSlotFile(slot, this).exists())
if (!Savegames.getSlotFile(slot, this).exists()) {
return null;//nothing in slot to overwrite
}
if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) {
if (preferences.displayOverwriteSavegame
== AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) {
return getString(R.string.loadsave_save_overwrite_confirmation_all);
}
if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_NEVER) {
@@ -247,10 +264,14 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
final String currentPlayerName = model.player.getName();
final FileHeader header = Savegames.quickload(this, slot);
if (header == null) return null;
if (header == null) {
return null;
}
final String savedPlayerName = header.playerName;
if (currentPlayerName.equals(savedPlayerName)) return null; //if the names match
if (currentPlayerName.equals(savedPlayerName)) {
return null; //if the names match
}
return getString(R.string.loadsave_save_overwrite_confirmation, savedPlayerName, currentPlayerName);
}
@@ -259,7 +280,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
public void onClick(View view) {
final int slot = (Integer) view.getTag();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
switch (slot) {
case SLOT_NUMBER_IMPORT_WORLDMAP:
clickImportWorldmap();
@@ -273,8 +294,9 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}
}
if (!isLoading
&& slot != SLOT_NUMBER_CREATE_NEW_SLOT
&& AndorsTrailApplication.CURRENT_VERSION == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
&& slot != SLOT_NUMBER_CREATE_NEW_SLOT
&& AndorsTrailApplication.CURRENT_VERSION
== AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
if (!isOverwriteTargetInIncompatibleVersion(slot)) {
saveOrOverwriteSavegame(slot);
}
@@ -288,7 +310,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private void saveOrOverwriteSavegame(int slot) {
final String message = getConfirmOverwriteQuestion(slot);
if (message != null) {
showConfirmoverwriteQuestion(slot, message);
showConfirmOverwriteQuestion(slot, message);
} else {
completeLoadSaveActivity(slot);
}
@@ -296,8 +318,11 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private boolean isOverwriteTargetInIncompatibleVersion(int slot) {
final FileHeader header = Savegames.quickload(this, slot);
if (header != null && header.fileversion != AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this, "Overwriting not allowed", "You are currently using a development version of Andor's trail. Overwriting a regular savegame is not allowed in development mode.");
if (header != null
&& header.fileversion != AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
"Overwriting not allowed",
"You are currently using a development version of Andor's trail. Overwriting a regular savegame is not allowed in development mode.");
CustomDialogFactory.show(d);
return true;
}
@@ -306,28 +331,32 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
//region Imports/Exports
//region Export
@RequiresApi(api = Build.VERSION_CODES.P)
private void exportSaveGames(Intent data) {
Uri uri = data.getData();
Context context = getApplicationContext();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this)
.getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile source = DocumentFile.fromFile(storageDir);
File storageDir = AndroidStorage.getStorageDirectory(context,
Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile target = DocumentFile.fromTreeUri(context, uri);
if (target == null) {
return;
}
DocumentFile[] files = source.listFiles();
File[] files = storageDir.listFiles();
if (files == null) {
showErrorExportingSaveGamesUnknown();
return;
}
boolean hasExistingFiles = false;
for (DocumentFile file :
files) {
for (File file : files) {
String fileName = file.getName();
if (fileName == null)
continue;
DocumentFile existingFile = target.findFile(fileName);
if (existingFile != null) {
@@ -339,45 +368,70 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
if (hasExistingFiles) {
showConfirmOverwriteByExportQuestion(resolver, target, files);
} else {
exportSaveGamesFolderContentToFolder(resolver, target, files);
exportSaveGamesFolderContentToFolder(target, files);
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
private void exportSaveGamesFolderContentToFolder(ContentResolver resolver, DocumentFile target, DocumentFile[] files) {
private void exportSaveGamesFolderContentToFolder(DocumentFile target, File[] files) {
DocumentFile[] sourceFiles = new DocumentFile[files.length];
DocumentFile[] worldmapFiles = null;
File[] worldmapFiles = null;
for (int i = 0; i < files.length; i++) {
DocumentFile file = files[i];
File file = files[i];
if (file.isFile()) {
sourceFiles[i] = file;
} else if (file.isDirectory() && Objects.equals(file.getName(), Constants.FILENAME_WORLDMAP_DIRECTORY)) {
sourceFiles[i] = DocumentFile.fromFile(file);
} else if (file.isDirectory() && Objects.equals(file.getName(),
Constants.FILENAME_WORLDMAP_DIRECTORY)) {
worldmapFiles = file.listFiles();
}
}
Context context =this;
DocumentFile[] finalWorldmapFiles = worldmapFiles;
AndroidStorage.copyDocumentFilesToDirAsync(sourceFiles,
context,
target,
(sucess) -> {
if (sucess) {
DocumentFile worldmapFolder = target.createDirectory(Constants.FILENAME_WORLDMAP_DIRECTORY);
AndroidStorage.copyDocumentFilesToDirAsync(finalWorldmapFiles,
context,
worldmapFolder,
(sucessWorldmap) -> completeLoadSaveActivity(SLOT_NUMBER_EXPORT_SAVEGAMES, sucessWorldmap));
} else {
completeLoadSaveActivity(SLOT_NUMBER_EXPORT_SAVEGAMES, false);
}
});
Context context = this;
File[] finalWorldmapFiles = worldmapFiles;
CopyFilesToExternalFolder(target, sourceFiles, context, finalWorldmapFiles);
}
@RequiresApi(api = Build.VERSION_CODES.P)
private void CopyFilesToExternalFolder(DocumentFile target,
DocumentFile[] sourceFiles,
Context context,
File[] finalWorldmapFiles) {
AndroidStorage.copyDocumentFilesToDirAsync(sourceFiles,
context,
target,
getString(R.string.loadsave_exporting_savegames),
(success) -> {
if (success) {
CopyWorldmapFilesAsZip(target,
context,
finalWorldmapFiles);
} else {
completeLoadSaveActivity(
SLOT_NUMBER_EXPORT_SAVEGAMES,
false);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.N)
@RequiresApi(api = Build.VERSION_CODES.P)
private void CopyWorldmapFilesAsZip(DocumentFile target,
Context context,
File[] finalWorldmapFiles) {
AndroidStorage.createZipDocumentFileFromFilesAsync(finalWorldmapFiles,
context,
target,
Constants.FILENAME_WORLDMAP_DIRECTORY,
getString(R.string.loadsave_exporting_worldmap),
(successWorldmap) -> completeLoadSaveActivity(
SLOT_NUMBER_EXPORT_SAVEGAMES,
successWorldmap));
}
//endregion
@RequiresApi(api = Build.VERSION_CODES.P)
private void importSaveGames(Intent data) {
Uri uri = data.getData();
ClipData uris = data.getClipData();
@@ -388,7 +442,8 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}
Context context = getApplicationContext();
ContentResolver resolver = context.getContentResolver();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this)
.getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile appSavegameFolder = DocumentFile.fromFile(storageDir);
@@ -397,14 +452,18 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
if (uri != null) {
uriList.add(uri);
} else {
for (int i = 0; i < uris.getItemCount(); i++)
for (int i = 0; i < uris.getItemCount(); i++) {
uriList.add(uris.getItemAt(i).getUri());
}
}
importSaveGamesFromUris(context, resolver, appSavegameFolder, uriList);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void importSaveGamesFromUris(Context context, ContentResolver resolver, DocumentFile appSavegameFolder, List<Uri> uriList) {
@RequiresApi(api = Build.VERSION_CODES.P)
private void importSaveGamesFromUris(Context context,
ContentResolver resolver,
DocumentFile appSavegameFolder,
List<Uri> uriList) {
int count = uriList.size();
ArrayList<DocumentFile> alreadyExistingFiles = new ArrayList<>();
@@ -414,10 +473,11 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
Uri item = uriList.get(i);
DocumentFile itemFile = DocumentFile.fromSingleUri(context, item);
boolean fileAlreadyExists = getExistsSavegameInOwnFiles(itemFile, appSavegameFolder);
if (fileAlreadyExists)
if (fileAlreadyExists) {
alreadyExistingFiles.add(itemFile);
else
} else {
newFiles.add(itemFile);
}
}
if (alreadyExistingFiles.size() > 0) {
@@ -427,7 +487,10 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}
}
private void importSaveGames(ContentResolver resolver, DocumentFile appSavegameFolder, List<DocumentFile> saveFiles) {
@RequiresApi(api = Build.VERSION_CODES.P)
private void importSaveGames(ContentResolver resolver,
DocumentFile appSavegameFolder,
List<DocumentFile> saveFiles) {
int size = saveFiles.size();
DocumentFile[] sources = new DocumentFile[size];
DocumentFile[] targets = new DocumentFile[size];
@@ -457,21 +520,18 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}
AndroidStorage.copyDocumentFilesFromToAsync(sources,
this,
targets,
(sucess) -> completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES, sucess));
}
private void completeSavegameImportAndCheckIfDone(List<Integer> importsNeedingConfirmation, int slot) {
importsNeedingConfirmation.remove((Object) slot);
if (importsNeedingConfirmation.isEmpty()) {
completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES);
}
this,
targets,
getString(R.string.loadsave_importing_savegames),
(sucess) -> completeLoadSaveActivity(
SLOT_NUMBER_IMPORT_SAVEGAMES,
sucess));
}
private boolean getExistsSavegameInOwnFiles(DocumentFile savegameFile, DocumentFile appSavegameFolder) {
if (savegameFile == null)
if (savegameFile == null) {
return false;
}
DocumentFile foundFile = appSavegameFolder.findFile(Objects.requireNonNull(savegameFile.getName()));
return foundFile != null && foundFile.exists();
@@ -479,7 +539,6 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private int getSlotFromSavegameFileName(String fileName) {
if (fileName == null || !fileName.startsWith(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX)) {
//TODO: Maybe output a message that the file didn't have the right name?
return -1;
}
String slotStr = fileName.substring(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX.length());
@@ -489,32 +548,16 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
slot = Integer.parseInt(slotStr);
return slot;
} catch (NumberFormatException e) {
//TODO: Maybe output a message that the file didn't have the right name?
return -1;
}
}
private void importSaveGameFile(ContentResolver resolver, DocumentFile appSavegameFolder, DocumentFile itemFile, int slot) {
String targetName = Savegames.getSlotFileName(slot);
DocumentFile targetFile = getOrCreateDocumentFile(appSavegameFolder, targetName);
if (targetFile == null || !targetName.equals(targetFile.getName())) {
showErrorImportingSaveGameUnknown();//TODO: maybe replace with a more specific error message
return;
}
try {
AndroidStorage.copyDocumentFile(itemFile, resolver, targetFile);
} catch (IOException e) {
showErrorImportingSaveGameUnknown();
e.printStackTrace();
}
}
private DocumentFile getOrCreateDocumentFile(DocumentFile folder, String targetName) {
DocumentFile targetFile = folder.findFile(targetName);//try finding the file
if (targetFile == null)//no file found, creating new one
{
targetFile = folder.createFile(Constants.NO_FILE_EXTENSION_MIME_TYPE, targetName);
}
return targetFile;
}
@@ -522,72 +565,87 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private void importWorldmap(Intent data) {
Uri uri = data.getData();
Context context = getApplicationContext();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver();
Context context = AndorsTrailApplication.getApplicationFromActivity(this).getApplicationContext();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile storageFolder = DocumentFile.fromFile(storageDir);
DocumentFile ownWorldmapFolder = storageFolder.findFile(Constants.FILENAME_WORLDMAP_DIRECTORY);
if (ownWorldmapFolder == null) {
ownWorldmapFolder = storageFolder.createDirectory(Constants.FILENAME_WORLDMAP_DIRECTORY);
}
DocumentFile chosenFolder = DocumentFile.fromTreeUri(context, uri);
if (chosenFolder == null || !chosenFolder.isDirectory()) {
DocumentFile chosenZip = DocumentFile.fromSingleUri(context, uri);
if (chosenZip == null || !chosenZip.isFile()) {
showErrorImportingWorldmapWrongDirectory();
return;
}
if (!Constants.FILENAME_WORLDMAP_DIRECTORY.equals(chosenFolder.getName())) {
//user did not select the worldmap folder directly
DocumentFile file = chosenFolder.findFile(Constants.FILENAME_WORLDMAP_DIRECTORY);
if (file == null || !file.isDirectory() || !Constants.FILENAME_WORLDMAP_DIRECTORY.equals(file.getName())) {
//could not find a worldmap folder in the users selection
showErrorImportingWorldmapWrongDirectory();
return;
}
chosenFolder = file;
String chosenZipName = chosenZip.getName();
if (!chosenZipName.startsWith(Constants.FILENAME_WORLDMAP_DIRECTORY)) {
showErrorImportingWorldmapWrongDirectory();
return;
}
AndroidStorage.copyDocumentFilesToDirAsync(chosenFolder.listFiles(),
this,
ownWorldmapFolder,
(success) -> completeLoadSaveActivity(SLOT_NUMBER_IMPORT_WORLDMAP, success));
File ownWorldmapFolder = getOwnWorldmapFolder(context);
AndroidStorage.unzipDocumentFileToDirectoryAsync(chosenZip,
this,
ownWorldmapFolder,
false,
getString(R.string.loadsave_importing_worldmap),
(success) -> completeLoadSaveActivity(
SLOT_NUMBER_IMPORT_WORLDMAP,
success));
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private File getOwnWorldmapFolder(Context context) {
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
File ownWorldmapFolder = null;
for (File f : storageDir.listFiles()) {
if (f.getName().equals(Constants.FILENAME_WORLDMAP_DIRECTORY)) {
ownWorldmapFolder = f;
break;
}
}
if (ownWorldmapFolder == null) {
ownWorldmapFolder = new File(storageDir, Constants.FILENAME_WORLDMAP_DIRECTORY);
ownWorldmapFolder.mkdir();
}
return ownWorldmapFolder;
}
@RequiresApi(api = Build.VERSION_CODES.P)
private void clickExportSaveGames() {
startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_EXPORT_SAVEGAMES);
showStartExportInfo(view -> startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(),
-SLOT_NUMBER_EXPORT_SAVEGAMES));
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@RequiresApi(api = Build.VERSION_CODES.P)
private void clickImportSaveGames() {
startActivityForResult(AndroidStorage.getNewSelectMultipleSavegameFilesIntent(), -SLOT_NUMBER_IMPORT_SAVEGAMES);
showStartImportSavesInfo(view -> startActivityForResult(AndroidStorage.getNewSelectMultipleSavegameFilesIntent(),
-SLOT_NUMBER_IMPORT_SAVEGAMES));
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@RequiresApi(api = Build.VERSION_CODES.P)
private void clickImportWorldmap() {
startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_IMPORT_WORLDMAP);
showStartImportWorldmapInfo(view -> startActivityForResult(AndroidStorage.getNewSelectZipIntent(),
-SLOT_NUMBER_IMPORT_WORLDMAP));
}
@RequiresApi(api = Build.VERSION_CODES.P)
private void showConfirmOverwriteByExportQuestion(ContentResolver resolver, DocumentFile targetFolder, DocumentFile[] files) {
private void showConfirmOverwriteByExportQuestion(ContentResolver resolver,
DocumentFile targetFolder,
File[] files) {
final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_export_overwrite_confirmation_title),
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
getString(R.string.loadsave_export_overwrite_confirmation),
null,
true);
getString(R.string.loadsave_export_overwrite_confirmation_title),
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
getString(R.string.loadsave_export_overwrite_confirmation),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, v -> exportSaveGamesFolderContentToFolder(resolver, targetFolder, files));
CustomDialogFactory.addButton(d,
android.R.string.yes,
v -> exportSaveGamesFolderContentToFolder(targetFolder, files));
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@RequiresApi(api = Build.VERSION_CODES.P)
private void showConfirmOverwriteByImportQuestion(ContentResolver resolver,
DocumentFile appSavegameFolder,
List<DocumentFile> alreadyExistingFiles,
@@ -601,9 +659,9 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
Context context = AndorsTrailApplication.getApplicationFromActivity(this).getApplicationContext();
ArrayList<CustomDialog> dialogs = new ArrayList<CustomDialog>(amount) ;
ArrayList<CustomDialog> dialogs = new ArrayList<>(amount);
for (int i = 0; i < amount ; i++) {
for (int i = 0; i < amount; i++) {
DocumentFile alreadyExistingFile = alreadyExistingFiles.get(i);
int slot = getSlotFromSavegameFileName(alreadyExistingFile.getName());
FileHeader existingFileHeader = Savegames.quickload(context, slot);
@@ -620,20 +678,26 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}
StringBuilder messageSb = new StringBuilder();
String existingFileDescription = getString(R.string.loadsave_import_existing_description, slot, existingFileHeader.describe());
String importedFileDescription = getString(R.string.loadsave_import_imported_description, slot, importedFileHeader.describe());
messageSb.append(getString(R.string.loadsave_import_file_exists_question, existingFileDescription, importedFileDescription));
String existingFileDescription = getString(R.string.loadsave_import_existing_description,
Integer.toString(slot),
existingFileHeader.describe());
String importedFileDescription = getString(R.string.loadsave_import_imported_description,
Integer.toString(slot),
importedFileHeader.describe());
messageSb.append(getString(R.string.loadsave_import_file_exists_question,
existingFileDescription,
importedFileDescription));
String m = messageSb.toString();
CustomDialog dialog = CustomDialogFactory.createDialog(this,
title,
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
m,
null,
true,
false,
true);
title,
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
m,
null,
true,
false,
true);
CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_keep_existing, v -> {
//do nothing
@@ -646,7 +710,8 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
});
CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_add_as_new, v -> {
newFiles.add(null);//add a null element as marker to know later if the next file should be imported as new or overwrite the existing one
newFiles.add(null);//add a null element as marker to know later if the next file
// should be imported as new or overwrite the existing one
newFiles.add(alreadyExistingFile);
GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs);
});
@@ -662,13 +727,15 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void GoToNextConflictOrFinish(ContentResolver resolver, DocumentFile appSavegameFolder, List<DocumentFile> newFiles, ArrayList<CustomDialog> dialogs) {
if(dialogs.stream().count() > 0){
@RequiresApi(api = Build.VERSION_CODES.P)
private void GoToNextConflictOrFinish(ContentResolver resolver,
DocumentFile appSavegameFolder,
List<DocumentFile> newFiles,
ArrayList<CustomDialog> dialogs) {
if (dialogs.stream().count() > 0) {
CustomDialog d = dialogs.remove(0);
CustomDialogFactory.show(d);
}
else{
} else {
importSaveGames(resolver, appSavegameFolder, newFiles);
}
}
@@ -677,8 +744,9 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK)
if (resultCode != Activity.RESULT_OK) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
switch (-requestCode) {
@@ -713,48 +781,90 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
//region show Dialogs
private void showErrorImportingWorldmapWrongDirectory() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_import_worldmap_unsuccessfull),
getString(R.string.loadsave_import_worldmap_wrong_directory));
//region Import/Export
@RequiresApi(api = Build.VERSION_CODES.P)
private void showStartExportInfo(OnClickListener onOk) {
final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_export),
getResources().getDrawable(android.R.drawable.ic_dialog_info),
getString(R.string.loadsave_export_info),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, onOk);
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d);
}
private void showErrorImportingSaveGameUnknown() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_import_save_unsuccessfull),
getString(R.string.loadsave_import_save_error_unknown));
@RequiresApi(api = Build.VERSION_CODES.P)
private void showStartImportSavesInfo(OnClickListener onOk) {
final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_import_save),
getResources().getDrawable(android.R.drawable.ic_dialog_info),
getString(R.string.loadsave_import_save_info),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, onOk);
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d);
}
@RequiresApi(api = Build.VERSION_CODES.P)
private void showStartImportWorldmapInfo(OnClickListener onOk) {
final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_import_worldmap),
getResources().getDrawable(android.R.drawable.ic_dialog_info),
getString(R.string.loadsave_import_worldmap_info),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, onOk);
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d);
}
private void showErrorImportingWorldmapWrongDirectory() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_import_worldmap_unsuccessfull),
getString(R.string.loadsave_import_worldmap_wrong_file));
CustomDialogFactory.show(d);
}
private void showErrorExportingSaveGamesUnknown() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_export_unsuccessfull),
getString(R.string.loadsave_export_error_unknown));
CustomDialogFactory.show(d);
}
//endregion
private void showErrorLoadingEmptySlot() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.startscreen_error_loading_game),
getString(R.string.startscreen_error_loading_empty_slot));
getString(R.string.startscreen_error_loading_game),
getString(R.string.startscreen_error_loading_empty_slot));
CustomDialogFactory.show(d);
}
private void showSlotGetsDeletedOnLoadWarning(final int slot) {
final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.startscreen_attention_slot_gets_delete_on_load),
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
getString(R.string.startscreen_attention_message_slot_gets_delete_on_load),
null,
true);
getString(R.string.startscreen_attention_slot_gets_delete_on_load),
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
getString(R.string.startscreen_attention_message_slot_gets_delete_on_load),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.ok, v -> completeLoadSaveActivity(slot));
CustomDialogFactory.show(d);
}
private void showConfirmoverwriteQuestion(final int slot, String message) {
final String title =
getString(R.string.loadsave_save_overwrite_confirmation_title) + ' '
+ getString(R.string.loadsave_save_overwrite_confirmation_slot, slot);
private void showConfirmOverwriteQuestion(final int slot, String message) {
final String title = getString(R.string.loadsave_save_overwrite_confirmation_title) + ' '
+ getString(R.string.loadsave_save_overwrite_confirmation_slot, slot);
final CustomDialog d = CustomDialogFactory.createDialog(this,
title,
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
message,
null,
true);
title,
getResources().getDrawable(android.R.drawable.ic_dialog_alert),
message,
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, v -> completeLoadSaveActivity(slot));
CustomDialogFactory.addDismissButton(d, android.R.string.no);

View File

@@ -141,13 +141,18 @@ public final class LoadingActivity extends AndorsTrailBaseActivity implements On
@Override
public void onSceneLoaded() {
public void onSceneLoaded(Savegames.LoadSavegameResult loadResult) {
synchronized (semaphore) {
if (progressDialog != null) progressDialog.dismiss();
loaded =true;
}
startActivity(new Intent(this, MainActivity.class));
this.finish();
if (loadResult == Savegames.LoadSavegameResult.editDetected) {
showLoadingWarnDialog(R.string.dialog_loading_warning_edit);
}else{
startActivity(new Intent(this, MainActivity.class));
this.finish();
}
}
@Override
@@ -165,6 +170,19 @@ public final class LoadingActivity extends AndorsTrailBaseActivity implements On
}
}
private void showLoadingWarnDialog(int messageResourceID) {
final CustomDialog d = CustomDialogFactory.createDialog(this, getResources().getString(R.string.dialog_loading_warning_title), null, getResources().getString(messageResourceID), null, true);
CustomDialogFactory.addDismissButton(d, android.R.string.ok);
CustomDialogFactory.setDismissListener(d, new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
startActivity(new Intent(LoadingActivity.this, MainActivity.class));
LoadingActivity.this.finish();
}
});
CustomDialogFactory.show(d);
}
private void showLoadingFailedDialog(int messageResourceID) {
final CustomDialog d = CustomDialogFactory.createDialog(this, getResources().getString(R.string.dialog_loading_failed_title), null, getResources().getString(messageResourceID), null, true);
CustomDialogFactory.addDismissButton(d, android.R.string.ok);

View File

@@ -1,5 +1,8 @@
package com.gpl.rpg.AndorsTrail.context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.gpl.rpg.AndorsTrail.model.ModelContainer;
import com.gpl.rpg.AndorsTrail.model.ability.ActorConditionTypeCollection;
import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection;
@@ -12,6 +15,10 @@ import com.gpl.rpg.AndorsTrail.model.quest.QuestCollection;
import com.gpl.rpg.AndorsTrail.resource.ConversationLoader;
import com.gpl.rpg.AndorsTrail.resource.VisualEffectCollection;
import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class WorldContext {
//Objectcollections
@@ -62,4 +69,13 @@ public final class WorldContext {
public void resetForNewGame() {
maps.resetForNewGame();
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public String createHash() throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
this.maps.createHash(digest);
this.model.createHash(digest);
byte[] hash = digest.digest();
return ByteUtils.toHexString(hash);
}
}

View File

@@ -7,7 +7,7 @@ import com.gpl.rpg.AndorsTrail.util.ConstRange;
import com.gpl.rpg.AndorsTrail.util.Range;
public final class Constants {
public static final int PERCENT_EXP_LOST_WHEN_DIED = 30;
public static final int PERCENT_EXP_LOST_WHEN_DIED = 20;
public static final int LEVELUP_EFFECT_HEALTH = 5;
public static final int LEVELUP_EFFECT_ATK_CH = 5;
public static final int LEVELUP_EFFECT_ATK_DMG = 1;

View File

@@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.model;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -12,6 +13,8 @@ import java.util.Map.Entry;
import java.util.Set;
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.gpl.rpg.AndorsTrail.R;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
@@ -19,6 +22,7 @@ import com.gpl.rpg.AndorsTrail.model.actor.MonsterType;
import com.gpl.rpg.AndorsTrail.model.item.ItemType;
import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap;
import com.gpl.rpg.AndorsTrail.model.quest.Quest;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import com.gpl.rpg.AndorsTrail.util.HashMapHelper;
public final class GameStatistics {
@@ -214,4 +218,21 @@ public final class GameStatistics {
dest.writeInt(startLives);
dest.writeBoolean(unlimitedSaves);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(deaths));
for (Entry<String, Integer> e : killedMonstersByTypeID.entrySet()) {
digest.update(ByteUtils.toBytes(e.getKey()));
digest.update(ByteUtils.toBytes(e.getValue()));
}
for (Entry<String, Integer> e : usedItems.entrySet()) {
digest.update(ByteUtils.toBytes(e.getKey()));
digest.update(ByteUtils.toBytes(e.getValue()));
}
digest.update(ByteUtils.toBytes(spentGold));
digest.update(ByteUtils.toBytes(startLives));
digest.update(ByteUtils.toBytes(unlimitedSaves));
}
}

View File

@@ -3,8 +3,10 @@ package com.gpl.rpg.AndorsTrail.model;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import com.gpl.rpg.AndorsTrail.util.Coord;
public final class InterfaceData {
@@ -51,4 +53,12 @@ public final class InterfaceData {
}
dest.writeUTF(selectedTabHeroInfo);
}
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(isMainActivityVisible));
digest.update(ByteUtils.toBytes(isInCombat));
if(selectedPosition != null){
selectedPosition.createHash(digest);
}
}
}

View File

@@ -1,12 +1,17 @@
package com.gpl.rpg.AndorsTrail.model;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.model.actor.Player;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
public final class ModelContainer {
@@ -49,4 +54,14 @@ public final class ModelContainer {
statistics.writeToParcel(dest);
worldData.writeToParcel(dest);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
player.createHash(digest);
digest.update(ByteUtils.toBytes(currentMaps.map.name));
uiSelections.createHash(digest);
statistics.createHash(digest);
worldData.createHash(digest);
}
}

View File

@@ -1,8 +1,14 @@
package com.gpl.rpg.AndorsTrail.model;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
@@ -56,4 +62,13 @@ public final class WorldData {
dest.writeLong(e.getValue());
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(worldTime));
for (Map.Entry<String, Long> e: timers.entrySet() ) {
digest.update(ByteUtils.toBytes(e.getKey()));
digest.update(ByteUtils.toBytes(e.getValue()));
}
}
}

View File

@@ -1,10 +1,15 @@
package com.gpl.rpg.AndorsTrail.model.ability;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
public final class ActorCondition {
public static final int MAGNITUDE_REMOVE_ALL = -99;
@@ -45,4 +50,12 @@ public final class ActorCondition {
dest.writeInt(magnitude);
dest.writeInt(duration);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(conditionType.conditionTypeID));
digest.update(ByteUtils.toBytes(magnitude));
digest.update(ByteUtils.toBytes(duration));
}
}

View File

@@ -83,7 +83,7 @@ public final class SkillCollection {
public static final int PER_SKILLPOINT_INCREASE_COINFINDER_CHANCE_PERCENT = 30;
public static final int PER_SKILLPOINT_INCREASE_MAGICFINDER_CHANCE_PERCENT = 50;
public static final int PER_SKILLPOINT_INCREASE_COINFINDER_QUANTITY_PERCENT = 50;
public static final int PER_SKILLPOINT_INCREASE_MORE_EXP_PERCENT = 5;
public static final int PER_SKILLPOINT_INCREASE_MORE_EXP_PERCENT = 10;
public static final int PER_SKILLPOINT_INCREASE_CLEAVE_AP = 3;
public static final int PER_SKILLPOINT_INCREASE_EATER_HEALTH = 1;
public static final int PER_SKILLPOINT_INCREASE_FORTITUDE_HEALTH = 1;

View File

@@ -1,8 +1,12 @@
package com.gpl.rpg.AndorsTrail.model.actor;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.controller.Constants;
@@ -13,6 +17,7 @@ import com.gpl.rpg.AndorsTrail.model.item.ItemContainer;
import com.gpl.rpg.AndorsTrail.model.item.Loot;
import com.gpl.rpg.AndorsTrail.model.map.MonsterSpawnArea;
import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForMonster;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.util.CoordRect;
import com.gpl.rpg.AndorsTrail.util.Range;
@@ -193,4 +198,28 @@ public final class Monster extends Actor {
dest.writeBoolean(false);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(getMonsterTypeID()));
digest.update(ByteUtils.toBytes(attackCost));
digest.update(ByteUtils.toBytes(attackChance));
digest.update(ByteUtils.toBytes(criticalSkill));
digest.update(ByteUtils.toBytes(criticalMultiplier));
damagePotential.createHash(digest);
digest.update(ByteUtils.toBytes(blockChance));
digest.update(ByteUtils.toBytes(damageResistance));
ap.createHash(digest);
health.createHash(digest);
position.createHash(digest);
for (ActorCondition c: conditions){
c.createHash(digest);
}
digest.update(ByteUtils.toBytes(moveCost));
digest.update(ByteUtils.toBytes(forceAggressive));
if (shopItems != null) {
shopItems.createHash(digest);
}
}
}

View File

@@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.model.actor;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -13,6 +14,8 @@ import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.SparseIntArray;
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
@@ -27,6 +30,7 @@ import com.gpl.rpg.AndorsTrail.model.item.Loot;
import com.gpl.rpg.AndorsTrail.model.quest.Quest;
import com.gpl.rpg.AndorsTrail.model.quest.QuestProgress;
import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForPlayer;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.util.Range;
import com.gpl.rpg.AndorsTrail.util.Size;
@@ -55,9 +59,11 @@ public final class Player extends Actor {
public String id = UUID.randomUUID().toString();
public long savedVersion = 1; // the version get's increased for cheat detection everytime a player with limited saves is saved
public boolean wasEditingDetected;
public String hash;
// Unequipped stats
// Unequipped stats
public static final class PlayerBaseTraits {
public int iconID;
public int maxAP;
@@ -111,7 +117,7 @@ public final class Player extends Actor {
baseTraits.criticalSkill = 0;
baseTraits.criticalMultiplier = 1;
baseTraits.damagePotential.set(1, 1);
baseTraits.blockChance = 0;
baseTraits.blockChance = 9;
baseTraits.damageResistance = 0;
baseTraits.useItemCost = 5;
baseTraits.reequipCost = 5;
@@ -416,6 +422,13 @@ public final class Player extends Actor {
this.id = src.readUTF();
this.savedVersion = src.readLong();
}
if (fileversion >= 71){
this.hash = src.readUTF();
this.wasEditingDetected = src.readBoolean();
}else{
this.hash = "";
this.wasEditingDetected = false;
}
}
public void writeToParcel(DataOutputStream dest) throws IOException {
@@ -474,6 +487,58 @@ public final class Player extends Actor {
}
dest.writeUTF(id);
dest.writeLong(savedVersion);
dest.writeUTF(hash);
dest.writeBoolean(wasEditingDetected);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
//skipping icon ID so that it can be changed without hash difference
digest.update(ByteUtils.toBytes(baseTraits.maxAP));
digest.update(ByteUtils.toBytes(baseTraits.maxHP));
//skipping name so that it can be changed without hash difference
digest.update(ByteUtils.toBytes(moveCost));
digest.update(ByteUtils.toBytes(baseTraits.attackCost));
digest.update(ByteUtils.toBytes(baseTraits.attackChance));
digest.update(ByteUtils.toBytes(baseTraits.criticalSkill));
digest.update(ByteUtils.toBytes(baseTraits.criticalMultiplier));
baseTraits.damagePotential.createHash(digest);
digest.update(ByteUtils.toBytes(baseTraits.blockChance));
digest.update(ByteUtils.toBytes(baseTraits.damageResistance));
digest.update(ByteUtils.toBytes(baseTraits.moveCost));
ap.createHash(digest);
health.createHash(digest);
position.createHash(digest);
for(ActorCondition c : conditions){
c.createHash(digest);
}
lastPosition.createHash(digest);
nextPosition.createHash(digest);
digest.update(ByteUtils.toBytes(level));
digest.update(ByteUtils.toBytes(totalExperience));
inventory.createHash(digest);
digest.update(ByteUtils.toBytes(baseTraits.useItemCost));
digest.update(ByteUtils.toBytes(baseTraits.reequipCost));
for (int i = 0; i<skillLevels.size(); i++){
digest.update(ByteUtils.toBytes(skillLevels.keyAt(i)));
digest.update(ByteUtils.toBytes(skillLevels.valueAt(i)));
}
digest.update(ByteUtils.toBytes(spawnMap));
digest.update(ByteUtils.toBytes(spawnPlace));
for (Entry<String, LinkedHashSet<Integer> > e:questProgress.entrySet() ) {
digest.update(ByteUtils.toBytes(e.getKey()));
for(int progress: e.getValue()){
digest.update(ByteUtils.toBytes(progress));
}
}
digest.update(ByteUtils.toBytes(availableSkillIncreases));
for (Entry<String, Integer> e:alignments.entrySet() ) {
digest.update(ByteUtils.toBytes(e.getKey()));
digest.update(ByteUtils.toBytes(e.getValue()));
}
digest.update(ByteUtils.toBytes(id));
// digest.update(ByteUtils.toBytes(savedVersion));
// digest.update(ByteUtils.toBytes(wasEditingDetected));
}
}

View File

@@ -1,14 +1,19 @@
package com.gpl.rpg.AndorsTrail.model.item;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.model.actor.Player;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
public class ItemContainer {
public final ArrayList<ItemEntry> items = new ArrayList<ItemEntry>();
@@ -23,7 +28,8 @@ public class ItemContainer {
return result;
}
public static final class ItemEntry {
public static final class ItemEntry {
public final ItemType itemType;
public int quantity;
public ItemEntry(ItemType itemType, int initialQuantity) {
@@ -42,6 +48,12 @@ public class ItemContainer {
dest.writeUTF(itemType.id);
dest.writeInt(quantity);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(itemType.id));
digest.update(ByteUtils.toBytes(quantity));
}
}
public void addItem(ItemType itemType, int quantity) {
@@ -280,4 +292,12 @@ public class ItemContainer {
e.writeToParcel(dest);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
for (ItemEntry e :
items) {
e.createHash(digest);
}
}
}

View File

@@ -1,11 +1,16 @@
package com.gpl.rpg.AndorsTrail.model.item;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForItemContainer;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import com.gpl.rpg.AndorsTrail.util.Coord;
public final class Loot {
@@ -88,4 +93,13 @@ public final class Loot {
position.writeToParcel(dest);
dest.writeBoolean(isVisible);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(exp));
digest.update(ByteUtils.toBytes(exp));
items.createHash(digest);
position.createHash(digest);
digest.update(ByteUtils.toBytes(isVisible));
}
}

View File

@@ -1,8 +1,12 @@
package com.gpl.rpg.AndorsTrail.model.map;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -88,15 +92,27 @@ public final class MapCollection {
return false;
}
public void writeToParcel(DataOutputStream dest, WorldContext world) throws IOException {
private List<PredefinedMap> getAllSavedMaps(WorldContext world) {
List<PredefinedMap> mapsToExport = new ArrayList<PredefinedMap>();
for(PredefinedMap map : getAllMaps()) {
if (shouldSaveMap(world, map)) mapsToExport.add(map);
}
return mapsToExport;
}
public void writeToParcel(DataOutputStream dest, WorldContext world) throws IOException {
List<PredefinedMap> mapsToExport = getAllSavedMaps(world);
dest.writeInt(mapsToExport.size());
for(PredefinedMap map : mapsToExport) {
dest.writeUTF(map.name);
map.writeToParcel(dest, world);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
for (PredefinedMap map : getAllMaps()) {
map.createHash(digest);
}
}
}

View File

@@ -1,8 +1,12 @@
package com.gpl.rpg.AndorsTrail.model.map;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -10,6 +14,7 @@ import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.controller.Constants;
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
import com.gpl.rpg.AndorsTrail.model.actor.MonsterType;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.util.CoordRect;
import com.gpl.rpg.AndorsTrail.util.Range;
@@ -140,4 +145,13 @@ public final class MonsterSpawnArea {
m.writeToParcel(dest);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(isSpawning));
for (Monster m :
monsters) {
m.createHash(digest);
}
}
}

View File

@@ -1,8 +1,12 @@
package com.gpl.rpg.AndorsTrail.model.map;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -16,6 +20,7 @@ import com.gpl.rpg.AndorsTrail.model.actor.Monster;
import com.gpl.rpg.AndorsTrail.model.item.ItemType;
import com.gpl.rpg.AndorsTrail.model.item.Loot;
import com.gpl.rpg.AndorsTrail.model.map.MapObject.MapObjectType;
import com.gpl.rpg.AndorsTrail.util.ByteUtils;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.util.CoordRect;
import com.gpl.rpg.AndorsTrail.util.L;
@@ -340,6 +345,7 @@ public final class PredefinedMap {
}
}
} else {
currentColorFilter = null;
activeMapObjectGroups.clear();
activeMapObjectGroups.addAll(initiallyActiveMapObjectGroups);
activateMapObjects();
@@ -360,16 +366,20 @@ public final class PredefinedMap {
}
public boolean shouldSaveMapData(WorldContext world) {
if (!hasResetTemporaryData()) return true;
if (this == world.model.currentMaps.map) return true;
return mapDataNeedsToBeSaved();
}
private boolean mapDataNeedsToBeSaved() {
if (!hasResetTemporaryData()) return true;
if (!groundBags.isEmpty()) return true;
for (MonsterSpawnArea a : spawnAreas) {
if (this.visited && a.isUnique) return true;
if (a.isSpawning != a.isSpawningForNewGame) return true;
}
if (!activeMapObjectGroups.containsAll(initiallyActiveMapObjectGroups)
|| !initiallyActiveMapObjectGroups.containsAll(activeMapObjectGroups)) return true;
if (currentColorFilter != null) return true;
if (!activeMapObjectGroups.containsAll(initiallyActiveMapObjectGroups)
|| !initiallyActiveMapObjectGroups.containsAll(activeMapObjectGroups)) return true;
if (currentColorFilter != null && !currentColorFilter.equals(initialColorFilter)) return true;
return false;
}
@@ -398,4 +408,23 @@ public final class PredefinedMap {
dest.writeBoolean(visited);
dest.writeUTF(lastSeenLayoutHash);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void createHash(MessageDigest digest) {
if (mapDataNeedsToBeSaved()) {
for (MonsterSpawnArea a : spawnAreas) {
a.createHash(digest);
}
for (String g : activeMapObjectGroups) {
digest.update(ByteUtils.toBytes(g));
}
for (Loot l : groundBags) {
l.createHash(digest);
}
digest.update(ByteUtils.toBytes(currentColorFilter));
//skip lastVisitTime since it is too volatile
}
digest.update( ByteUtils.toBytes(visited));
digest.update( ByteUtils.toBytes(lastSeenLayoutHash));
}
}

View File

@@ -12,6 +12,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -20,6 +21,7 @@ import java.util.regex.Pattern;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.SystemClock;
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
@@ -42,6 +44,7 @@ public final class Savegames {
public static enum LoadSavegameResult {
success
, editDetected
, unknownError
, savegameIsFromAFutureVersion
, cheatingDetected
@@ -78,7 +81,7 @@ public final class Savegames {
}
return true;
} catch (IOException e) {
} catch (IOException | NoSuchAlgorithmException e) {
L.log("Error saving world: " + e.toString());
return false;
}
@@ -210,14 +213,18 @@ public final class Savegames {
}
public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException {
public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException, NoSuchAlgorithmException {
DataOutputStream dest = new DataOutputStream(outStream);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
world.model.player.hash = world.createHash();
}
FileHeader.writeToParcel(dest, world.model.player.getName(),
displayInfo, world.model.player.iconID,
world.model.statistics.isDead(),
world.model.statistics.hasUnlimitedSaves(),
world.model.player.id,
world.model.player.savedVersion);
world.model.player.savedVersion,
world.model.player.wasEditingDetected);
world.maps.writeToParcel(dest, world);
world.model.writeToParcel(dest);
dest.close();
@@ -239,6 +246,15 @@ public final class Savegames {
onWorldLoaded(res, world, controllers);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
String worldHash = world.createHash();
if(!worldHash.equals(world.model.player.hash)){
world.model.player.wasEditingDetected = true;
return LoadSavegameResult.editDetected;
}
} catch (NoSuchAlgorithmException ignored) { }
}
return LoadSavegameResult.success;
}
@@ -334,6 +350,7 @@ public final class Savegames {
public final boolean hasUnlimitedSaves;
public final String playerId;
public final long savedVersion;
public final boolean wasEditingDetected;
public String describe() {
return (fileversion == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION ? "(D) " : "") + playerName + ", " + displayInfo;
@@ -378,9 +395,14 @@ public final class Savegames {
this.playerId = "";
this.savedVersion = 0;
}
if (fileversion >= 70){
this.wasEditingDetected = src.readBoolean();
}else{
this.wasEditingDetected = false;
}
}
public static void writeToParcel(DataOutputStream dest, String playerName, String displayInfo, int iconID, boolean isDead, boolean hasUnlimitedSaves, String playerId, long savedVersion) throws IOException {
public static void writeToParcel(DataOutputStream dest, String playerName, String displayInfo, int iconID, boolean isDead, boolean hasUnlimitedSaves, String playerId, long savedVersion, boolean wasEditingDetected) throws IOException {
dest.writeInt(AndorsTrailApplication.CURRENT_VERSION);
dest.writeUTF(playerName);
dest.writeUTF(displayInfo);
@@ -389,6 +411,7 @@ public final class Savegames {
dest.writeBoolean(hasUnlimitedSaves);
dest.writeUTF(playerId);
dest.writeLong(savedVersion);
dest.writeBoolean(wasEditingDetected);
}
}
}

View File

@@ -6,14 +6,12 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.content.FileProvider;
import android.support.v4.provider.DocumentFile;
import android.os.Handler;
import android.os.Looper;
import android.webkit.MimeTypeMap;
import com.gpl.rpg.AndorsTrail.R;
import com.gpl.rpg.AndorsTrail.controller.Constants;
@@ -22,19 +20,22 @@ import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public final class AndroidStorage {
public static File getStorageDirectory(Context context, String name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return context.getExternalFilesDir(name);
}
else {
} else {
File root = Environment.getExternalStorageDirectory();
return new File(root, name);
}
@@ -42,16 +43,16 @@ public final class AndroidStorage {
public static boolean shouldMigrateToInternalStorage(Context context) {
boolean ret = false;
File externalSaveGameDirectory = new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY);
File externalSaveGameDirectory = new File(Environment.getExternalStorageDirectory(),
Constants.FILENAME_SAVEGAME_DIRECTORY);
File internalSaveGameDirectory = getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
if (externalSaveGameDirectory.exists()
&& externalSaveGameDirectory.isDirectory()
&& externalSaveGameDirectory.listFiles().length > 0
&& (
!internalSaveGameDirectory.exists()
|| internalSaveGameDirectory.isDirectory() && internalSaveGameDirectory.listFiles().length < 2)
) {
&& externalSaveGameDirectory.isDirectory()
&& externalSaveGameDirectory.listFiles().length > 0
&& (!internalSaveGameDirectory.exists()
|| internalSaveGameDirectory.isDirectory()
&& internalSaveGameDirectory.listFiles().length < 2)) {
ret = true;
}
return ret;
@@ -60,11 +61,11 @@ public final class AndroidStorage {
public static boolean migrateToInternalStorage(Context context) {
try {
copy(new File(Environment.getExternalStorageDirectory(), Constants.CHEAT_DETECTION_FOLDER),
getStorageDirectory(context, Constants.CHEAT_DETECTION_FOLDER));
getStorageDirectory(context, Constants.CHEAT_DETECTION_FOLDER));
copy(new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY),
getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY));
getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY));
} catch (IOException e) {
L.log("Error migrating data: " + e.toString());
L.log("Error migrating data: " + e);
return false;
}
return true;
@@ -105,54 +106,174 @@ public final class AndroidStorage {
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void createZipDocumentFileFromFilesAsync(File[] files,
Context context,
DocumentFile targetDirectory,
String fileName,
String loadingMessage,
Consumer<Boolean> callback) {
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFolder) throws IOException {
copyDocumentFileToNewOrExistingFile(sourceFile, resolver, targetFolder, Constants.NO_FILE_EXTENSION_MIME_TYPE);
BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper());
worker.setTask(workerCallback -> {
try {
workerCallback.onInitialize();
//region create zip file
File zip = File.createTempFile("temp_worldmap", ".zip");
try (OutputStream out = new FileOutputStream(zip)) {
ZipOutputStream zipOut = new ZipOutputStream(out);
for (int i = 0; i < files.length; i++) {
File file = files[i];
try (FileInputStream fis = new FileInputStream(file)) {
workerCallback.onProgress((float) i / files.length);
zipOut.putNextEntry(new ZipEntry(file.getName()));
copyStream(fis, zipOut);
zipOut.closeEntry();
}
}
zipOut.close();
}
//endregion
DocumentFile worldmapZip = DocumentFile.fromFile(zip);
DocumentFile worldmapTarget = targetDirectory.createFile("application/zip", fileName);
if (worldmapTarget != null && worldmapTarget.exists()) {
AndroidStorage.copyDocumentFile(worldmapZip, resolver, worldmapTarget);
workerCallback.onComplete(true);
} else {
throw new FileNotFoundException("Could not create File");
}
} catch (NullPointerException e) {
if (worker.isCancelled()) {
workerCallback.onFailure(new CancellationException("Cancelled"));
} else {
workerCallback.onFailure(e);
}
} catch (Exception e) {
workerCallback.onFailure(e);
}
});
worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback));
worker.run();
}
public static void unzipToDirectory(File zipFile,
File targetDirectory,
boolean overwriteNotSkip) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
unzipStreamToDirectory(targetDirectory, overwriteNotSkip, zis);
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void unzipDocumentFileToDirectoryAsync(DocumentFile zipFile,
Context context,
File targetDirectory,
boolean overwriteNotSkip,
String loadingMessage,
Consumer<Boolean> callback) {
BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper());
worker.setTask(workerCallback -> {
try {
workerCallback.onInitialize();
workerCallback.onProgress(-1);//set dummy progress since we don't know the
// progress of the unzip
unzipDocumentFileToDirectory(zipFile, resolver, targetDirectory, overwriteNotSkip);
workerCallback.onComplete(true);
} catch (IOException e) {
workerCallback.onFailure(e);
}
});
worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback));
worker.run();
}
public static void unzipDocumentFileToDirectory(DocumentFile zipFile,
ContentResolver resolver,
File targetDirectory,
boolean overwriteNotSkip) throws IOException {
try (ZipInputStream zis = new ZipInputStream(resolver.openInputStream(zipFile.getUri()))) {
unzipStreamToDirectory(targetDirectory, overwriteNotSkip, zis);
}
}
private static void unzipStreamToDirectory(File targetDirectory,
boolean overwriteNotSkip,
ZipInputStream zis) throws IOException {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
File file = new File(targetDirectory, entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
file.getParentFile().mkdirs();
if (file.exists() && !overwriteNotSkip) {
continue;
}
try (FileOutputStream fos = new FileOutputStream(file)) {
copyStream(zis, fos);
}
}
}
}
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile,
ContentResolver resolver,
DocumentFile targetFolder) throws IOException {
copyDocumentFileToNewOrExistingFile(sourceFile,
resolver,
targetFolder,
Constants.NO_FILE_EXTENSION_MIME_TYPE);
}
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFolder, String mimeType) throws IOException {
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile,
ContentResolver resolver,
DocumentFile targetFolder,
String mimeType) throws IOException {
String fileName = sourceFile.getName();
DocumentFile file = targetFolder.findFile(fileName);
if (file == null)
if (file == null) {
file = targetFolder.createFile(mimeType, fileName);
if (file == null)
}
if (file == null) {
return;
}
AndroidStorage.copyDocumentFile(sourceFile, resolver, file);
}
public static void copyDocumentFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFile) throws IOException {
public static void copyDocumentFile(DocumentFile sourceFile,
ContentResolver resolver,
DocumentFile targetFile) throws IOException {
try (OutputStream outputStream = resolver.openOutputStream(targetFile.getUri());
InputStream inputStream = resolver.openInputStream(sourceFile.getUri())) {
copyStream(inputStream, outputStream);
}
}
/**
* Gets the MIME-Type for a file.<p/>
* Fallback value is '* / *' (without spaces) <p/>
* Mostly copied together from: <a href="https://stackoverflow.com/q/8589645/17292289">StackOverflow</a>
*/
@NonNull
public static String getMimeType(ContentResolver resolver, Uri uri) {
String type = null;
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
type = resolver.getType(uri);
return type;
}
final String extension = MimeTypeMap.getFileExtensionFromUrl(uri.getPath());
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
}
if (type == null) {
type = "*/*"; // fallback type.
}
return type;
}
public static String getUrlForFile(Context context, File worldmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
String applicationId = context.getPackageName();
@@ -163,11 +284,9 @@ public final class AndroidStorage {
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static Intent getNewOpenDirectoryIntent() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
return intent;
return new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@@ -179,50 +298,59 @@ public final class AndroidStorage {
return intent;
}
public static void copyDocumentFilesFromToAsync(DocumentFile[] sources, Context context, DocumentFile[] targets, Consumer<Boolean> callback) {
if(sources.length != targets.length)
{
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static Intent getNewSelectZipIntent() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/zip");
return intent;
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void copyDocumentFilesFromToAsync(DocumentFile[] sources,
Context context,
DocumentFile[] targets,
String loadingMessage,
Consumer<Boolean> callback) {
if (sources.length != targets.length) {
throw new IllegalArgumentException("Both arrays, target & source have to have the same size");
}
BackgroundWorker worker = new BackgroundWorker();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context);
BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper());
worker.setTask(new BackgroundWorker.worker() {
@Override
public void doWork(BackgroundWorkerCallback callback) {
try {
callback.onInitialize();
for (int i = 0; i < sources.length ; i++) {
if (worker.isCancelled()) {
callback.onFailure(new CancellationException("Cancelled"));
return;
}
DocumentFile source = sources[i];
DocumentFile target = targets[i];
if(source == null || target == null)
{
continue;
}
copyDocumentFile(source, resolver,target);
float progress = i /(float) sources.length;
callback.onProgress(progress);
}
callback.onComplete(true);
} catch (NullPointerException e) {
worker.setTask(workerCallback -> {
try {
workerCallback.onInitialize();
for (int i = 0; i < sources.length; i++) {
if (worker.isCancelled()) {
callback.onFailure(new CancellationException("Cancelled"));
workerCallback.onFailure(new CancellationException("Cancelled"));
return;
}
} catch (Exception e) {
callback.onFailure(e);
DocumentFile source = sources[i];
DocumentFile target = targets[i];
if (source == null || target == null) {
continue;
}
copyDocumentFile(source, resolver, target);
float progress = i / (float) sources.length;
workerCallback.onProgress(progress);
}
workerCallback.onComplete(true);
} catch (NullPointerException e) {
if (worker.isCancelled()) {
workerCallback.onFailure(new CancellationException("Cancelled"));
return;
}
} catch (Exception e) {
workerCallback.onFailure(e);
}
});
worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback));
@@ -233,9 +361,10 @@ public final class AndroidStorage {
public static void copyDocumentFilesToDirAsync(DocumentFile[] files,
Context context,
DocumentFile targetDirectory,
String loadingMessage,
Consumer<Boolean> callback) {
BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context);
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper());
@@ -249,11 +378,12 @@ public final class AndroidStorage {
return;
}
DocumentFile file = files[i];
if(file == null)
if (file == null) {
continue;
}
copyDocumentFileToNewOrExistingFile(file, resolver, targetDirectory);
float progress = i /(float) files.length;
float progress = i / (float) files.length;
workerCallback.onProgress(progress);
}
workerCallback.onComplete(true);
@@ -270,10 +400,11 @@ public final class AndroidStorage {
}
private static BackgroundWorkerCallback<Boolean> getDefaultBackgroundWorkerCallback(Handler handler,
CustomDialogFactory.CustomDialog progressDialog,
Consumer<Boolean> callback) {
CustomDialogFactory.CustomDialog progressDialog,
Consumer<Boolean> callback) {
return new BackgroundWorkerCallback<Boolean>() {
private int progress = -1;
private int progress = -1;
@Override
public void onInitialize() {
handler.post(() -> {
@@ -285,10 +416,17 @@ public final class AndroidStorage {
public void onProgress(float progress) {
handler.post(() -> {
int intProgress = (int) (progress * 100);
if(this.progress == intProgress)
if (this.progress == intProgress) {
return;
}
this.progress = intProgress;
if (progress == -1) {
CustomDialogFactory.setDesc(progressDialog, null);
return;
}
CustomDialogFactory.setDesc(progressDialog, intProgress + "%");
});
}
@@ -296,10 +434,7 @@ public final class AndroidStorage {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onFailure(Exception e) {
handler.post(() -> {
progressDialog.dismiss();
callback.accept(false);
});
this.onComplete(false);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@@ -307,20 +442,31 @@ public final class AndroidStorage {
public void onComplete(Boolean result) {
handler.post(() -> {
progressDialog.dismiss();
callback.accept(true);
callback.accept(result);
});
}
};
}
private static CustomDialogFactory.CustomDialog getLoadingDialog(Context context) {
return CustomDialogFactory.createDialog(context,
context.getResources().getString(R.string.dialog_loading_message),
context.getResources().getDrawable(R.drawable.loading_anim),
null,
null,
false,
false);
return getLoadingDialog(context, null);
}
private static CustomDialogFactory.CustomDialog getLoadingDialog(Context context, String message) {
if (message == null) {
message = context.getResources().getString(R.string.dialog_loading_message);
}
CustomDialogFactory.CustomDialog dialog = CustomDialogFactory.createDialog(context,
message,
context.getResources()
.getDrawable(R.drawable.loading_anim),
null,
null,
true,
false);
CustomDialogFactory.addCancelButton(dialog, android.R.string.no);
return dialog;
}
}

View File

@@ -4,14 +4,14 @@ import java.util.concurrent.Executors;
public final class BackgroundWorker<T> {
boolean cancelled = false;
worker task;
BackgroundWorkerCallback callback;
worker<T> task;
BackgroundWorkerCallback<T> callback;
public void setTask(worker task) {
public void setTask(worker<T> task) {
this.task = task;
}
public void setCallback(BackgroundWorkerCallback callback) {
public void setCallback(BackgroundWorkerCallback<T> callback) {
this.callback = callback;
}
@@ -20,7 +20,7 @@ public final class BackgroundWorker<T> {
}
interface worker<T> {
void doWork(BackgroundWorkerCallback callback);
void doWork(BackgroundWorkerCallback<T> callback);
}
interface BackgroundWorkerCallback<T> {

View File

@@ -1,6 +1,17 @@
package com.gpl.rpg.AndorsTrail.util;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public final class ByteUtils {
private static int bytes;
public static String toHexString(byte[] bytes) { return toHexString(bytes, bytes.length); }
public static String toHexString(byte[] bytes, int numBytes) {
if (bytes == null) return "";
@@ -21,4 +32,57 @@ public final class ByteUtils {
array[i] ^= mask[i];
}
}
public static byte[] toBytes(long l){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
bytes = Long.BYTES;
}else{
bytes = 8;
}
ByteBuffer buffer = ByteBuffer.allocate(bytes);
buffer.putLong(l);
return buffer.array();
}
public static byte[] toBytes(int i){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
bytes = Integer.BYTES;
}else{
bytes = 4;
}
ByteBuffer buffer = ByteBuffer.allocate(bytes);
buffer.putInt(i);
return buffer.array();
}
public static byte[] toBytes(float f) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
bytes = Float.BYTES;
}else{
bytes = 4;
}
ByteBuffer buffer = ByteBuffer.allocate(bytes);
buffer.putFloat(f);
return buffer.array();
}
public static byte[] toBytes(boolean bool){
byte b;
if(bool){
b = 0;
}else{
b = 1;
}
return new byte[] {b};
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static byte[] toBytes(String x) {
if (x == null) {
return new byte[]{};
}
return x.getBytes(StandardCharsets.UTF_8);
}
}

View File

@@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.util;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
public final class Coord {
public int x;
@@ -47,4 +48,9 @@ public final class Coord {
dest.writeInt(x);
dest.writeInt(y);
}
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(x));
digest.update(ByteUtils.toBytes(y));
}
}

View File

@@ -1,8 +1,11 @@
package com.gpl.rpg.AndorsTrail.util;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
public final class Range {
public int max;
@@ -99,4 +102,9 @@ public final class Range {
dest.writeInt(max);
dest.writeInt(current);
}
public void createHash(MessageDigest digest) {
digest.update(ByteUtils.toBytes(max));
digest.update(ByteUtils.toBytes(current));
}
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More