Compare commits
48 Commits
c09f5e3a6e
...
android-up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4fbe20871 | ||
|
|
0d73ee10fa | ||
|
|
f20529df62 | ||
|
|
ffef76b28b | ||
|
|
cd49473e86 | ||
|
|
363a395169 | ||
|
|
a09176d344 | ||
|
|
f884af20b8 | ||
|
|
a2859fbc56 | ||
|
|
11d5966a51 | ||
|
|
fd169e9f40 | ||
|
|
149ad3ae1c | ||
|
|
6e74243ea8 | ||
|
|
9c4009034f | ||
|
|
0422beb855 | ||
|
|
2cc12b3530 | ||
|
|
80997ddc36 | ||
|
|
c244ba1390 | ||
|
|
8a74ffd2ef | ||
|
|
57f7a0dc91 | ||
|
|
78cf59c90c | ||
|
|
c4ac6b1046 | ||
|
|
a5ed53a763 | ||
|
|
d6b11d8242 | ||
|
|
d9e7509daa | ||
|
|
e15583e3c5 | ||
|
|
25b70ae818 | ||
|
|
1aa98de579 | ||
|
|
be027bb303 | ||
|
|
d2fe192466 | ||
|
|
469d2bfc46 | ||
|
|
ce399ffe1f | ||
|
|
054c6e5d23 | ||
|
|
e4633c0689 | ||
|
|
9b9d8a7c3a | ||
|
|
0cfa6a97c9 | ||
|
|
9729d5a34f | ||
|
|
c024474128 | ||
|
|
3100ffd717 | ||
|
|
e3c8c0ecfb | ||
|
|
cd35ad673f | ||
|
|
5f000ef210 | ||
|
|
2078eedb94 | ||
|
|
6f2ba0fe7a | ||
|
|
06f1a791e3 | ||
|
|
b341d7e6f1 | ||
|
|
31f25a963f | ||
|
|
30030031b2 |
@@ -2,12 +2,12 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion 34
|
||||
compileSdkVersion 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.gpl.rpg.AndorsTrail"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 34
|
||||
targetSdkVersion 35
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.gpl.rpg.AndorsTrail"
|
||||
android:versionCode="80"
|
||||
android:versionName="0.8.13"
|
||||
android:versionCode="81"
|
||||
android:versionName="0.8.14dev"
|
||||
android:installLocation="auto"
|
||||
>
|
||||
|
||||
|
||||
@@ -8,31 +8,39 @@ import com.gpl.rpg.AndorsTrail.context.ControllerContext;
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.controller.Constants;
|
||||
import com.gpl.rpg.AndorsTrail.util.AndroidStorage;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
|
||||
public final class AndorsTrailApplication extends Application {
|
||||
|
||||
public static final boolean DEVELOPMENT_DEBUGRESOURCES = false;
|
||||
public static final boolean DEVELOPMENT_FORCE_STARTNEWGAME = false;
|
||||
public static final boolean DEVELOPMENT_FORCE_CONTINUEGAME = false;
|
||||
public static final boolean DEVELOPMENT_DEBUGBUTTONS = false;
|
||||
public static final boolean DEVELOPMENT_DEBUGBUTTONS = true;
|
||||
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.13";
|
||||
public static final boolean DEVELOPMENT_DEBUGMESSAGES = true;
|
||||
public static final String CURRENT_VERSION_DISPLAY = "0.8.14dev";
|
||||
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 : 80;
|
||||
public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION : 81;
|
||||
|
||||
private final AndorsTrailPreferences preferences = new AndorsTrailPreferences();
|
||||
private WorldContext world = new WorldContext();
|
||||
@@ -57,13 +65,45 @@ public final class AndorsTrailApplication extends Application {
|
||||
|
||||
public void setWindowParameters(Activity activity) {
|
||||
activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
if (preferences.fullscreen) {
|
||||
activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
public void setFullscreenMode(Activity activity) {
|
||||
setFullscreenMode(preferences.fullscreen, activity.getWindow());
|
||||
}
|
||||
public static void setFullscreenMode(boolean fullscreen, Window window) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
|
||||
final WindowInsetsController insetsController = window.getInsetsController();
|
||||
if (insetsController != null) {
|
||||
insetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
int insetType = WindowInsets.Type.statusBars();
|
||||
if (fullscreen) {
|
||||
insetsController.hide(insetType);
|
||||
} else {
|
||||
insetsController.show(insetType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activity.getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
if (fullscreen) {
|
||||
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
} else {
|
||||
window.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
public int getUsableTouchAreaInsetMask(){
|
||||
int i = 0;
|
||||
i |= WindowInsets.Type.displayCutout();
|
||||
i |= WindowInsets.Type.navigationBars();
|
||||
if (!preferences.fullscreen) {
|
||||
i |= WindowInsets.Type.statusBars();
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
//Get default locale at startup, as somehow it seems that changing the app's
|
||||
//configured locale impacts the value returned by Locale.getDefault() nowadays.
|
||||
private final Locale defaultLocale = Locale.getDefault();
|
||||
@@ -166,4 +206,18 @@ public final class AndorsTrailApplication extends Application {
|
||||
controllers = new ControllerContext(this, world);
|
||||
setup = new WorldSetup(world, controllers, getApplicationContext());
|
||||
}
|
||||
|
||||
public void setUsablePadding(View root) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
root.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
Insets bars = insets.getInsets(getUsableTouchAreaInsetMask());
|
||||
int left = Math.max(bars.left, v.getPaddingLeft());
|
||||
int top = Math.max(bars.top, v.getPaddingTop());
|
||||
int right = Math.max(bars.right, v.getPaddingRight());
|
||||
int bottom = Math.max(bars.bottom, v.getPaddingBottom());
|
||||
v.setPadding(left, top, right, bottom);
|
||||
return WindowInsets.CONSUMED;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +346,6 @@ public final class Dialogs {
|
||||
CustomDialogFactory.show(d);
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
private static boolean hasPermissions(final Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (activity.getApplicationContext().checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
||||
|
||||
@@ -24,9 +24,7 @@ public final class AboutActivity extends AndorsTrailBaseActivity implements Imag
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
app.setWindowParameters(this);
|
||||
|
||||
setContentView(R.layout.about);
|
||||
initializeView(this, R.layout.about, R.id.about_root);
|
||||
final Resources res = getResources();
|
||||
|
||||
final TextView tv = (TextView) findViewById(R.id.about_contents);
|
||||
|
||||
@@ -30,9 +30,7 @@ public final class ActorConditionInfoActivity extends AndorsTrailBaseActivity {
|
||||
|
||||
String conditionTypeID = getIntent().getData().getLastPathSegment();
|
||||
ActorConditionType conditionType = world.actorConditionsTypes.getActorConditionType(conditionTypeID);
|
||||
|
||||
setContentView(R.layout.actorconditioninfo);
|
||||
|
||||
initializeView(this, R.layout.actorconditioninfo, R.id.actorconditioninfo_root);
|
||||
|
||||
TextView tv = (TextView) findViewById(R.id.actorconditioninfo_title);
|
||||
tv.setText(conditionType.name);
|
||||
|
||||
@@ -2,6 +2,10 @@ package com.gpl.rpg.AndorsTrail.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
|
||||
|
||||
@@ -19,5 +23,13 @@ public abstract class AndorsTrailBaseActivity extends Activity {
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
app.setLocale(this);
|
||||
}
|
||||
protected void initializeView(Activity activity, @LayoutRes int layoutId, @IdRes int rootViewId) {
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(activity);
|
||||
app.setWindowParameters(activity);
|
||||
activity.setContentView(layoutId);
|
||||
View root = activity.findViewById(rootViewId);
|
||||
app.setUsablePadding(root);
|
||||
app.setFullscreenMode(activity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.gpl.rpg.AndorsTrail.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
|
||||
@@ -19,4 +24,13 @@ public abstract class AndorsTrailBaseFragmentActivity extends FragmentActivity {
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
app.setLocale(this);
|
||||
}
|
||||
|
||||
protected void initializeView(Activity activity, @LayoutRes int layoutId, @IdRes int rootViewId) {
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(activity);
|
||||
app.setWindowParameters(activity);
|
||||
activity.setContentView(layoutId);
|
||||
View root = activity.findViewById(rootViewId);
|
||||
app.setUsablePadding(root);
|
||||
app.setFullscreenMode(activity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +89,7 @@ public final class BulkSelectionInterface extends AndorsTrailBaseActivity implem
|
||||
interfaceType = BulkInterfaceType.valueOf(params.getString("interfaceType"));
|
||||
|
||||
int intialSelection = 1;
|
||||
|
||||
setContentView(R.layout.bulkselection);
|
||||
initializeView(this, R.layout.bulkselection, R.id.bulkselection_root);
|
||||
|
||||
// initialize UI variables
|
||||
TextView bulkselection_action_type = (TextView)findViewById(R.id.bulkselection_action_type);
|
||||
|
||||
@@ -75,13 +75,11 @@ public final class ConversationActivity
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.conversation);
|
||||
initializeView(this, R.layout.conversation, R.id.conversation_root);
|
||||
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
setFinishOnTouchOutside(false);
|
||||
}
|
||||
setFinishOnTouchOutside(false);
|
||||
|
||||
replyGroup = new RadioGroup(this);
|
||||
replyGroup = new RadioGroup(this);
|
||||
replyGroup.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT, ListView.LayoutParams.WRAP_CONTENT));
|
||||
statementList = (ListView) findViewById(R.id.conversation_statements);
|
||||
statementList.addFooterView(replyGroup);
|
||||
|
||||
@@ -365,7 +365,7 @@ public final class DebugInterface {
|
||||
,new DebugButton("#2", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "crackshot_hideout3", "South", 0, 0);
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "waytolake12", "tower_door", 0, 0);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -373,21 +373,21 @@ public final class DebugInterface {
|
||||
,new DebugButton("#3", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "fallhaven_derelict2", "up", 0, 0);
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "galmore_cavea", "up", 0, 0);
|
||||
}
|
||||
})
|
||||
|
||||
,new DebugButton("#4", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "loneford4", "south", 0, 0);
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "galmore_35", "south", 11, 0);
|
||||
}
|
||||
})
|
||||
|
||||
,new DebugButton("#5", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "waytogalmore1", "entrance2", 0, 0);
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "way_to_sullengard_west_5", "east", 0, 5);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -408,7 +408,7 @@ public final class DebugInterface {
|
||||
,new DebugButton("#8", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "galmore_86", "west", 0, 5);
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "wayto_feygard_duleian_2", "south", 0, 5);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -422,7 +422,28 @@ public final class DebugInterface {
|
||||
,new DebugButton("#10", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "laerothprison3", "south", 2, 0);
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "moesforest_03", "north1", 0, 0);
|
||||
}
|
||||
})
|
||||
|
||||
,new DebugButton("#11", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "galmore_17", "outside", 0, 0);
|
||||
}
|
||||
})
|
||||
|
||||
,new DebugButton("#12", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "galmore_48", "west", 0, 10);
|
||||
}
|
||||
})
|
||||
|
||||
,new DebugButton("#13", new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
controllerContext.movementController.placePlayerAsyncAt(MapObject.MapObjectType.newmap, "wild18", "south", 6, 0);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -38,10 +38,7 @@ public final class DisplayWorldMapActivity extends AndorsTrailBaseActivity {
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
if (!app.isInitialized()) { finish(); return; }
|
||||
this.world = app.getWorld();
|
||||
|
||||
app.setWindowParameters(this);
|
||||
|
||||
setContentView(R.layout.displayworldmap);
|
||||
initializeView(this, R.layout.displayworldmap, R.id.worldmap_root);
|
||||
|
||||
displayworldmap_webview = (WebView) findViewById(R.id.displayworldmap_webview);
|
||||
displayworldmap_webview.setBackgroundColor(ThemeHelper.getThemeColor(this, R.attr.ui_theme_displayworldmap_bg_color));
|
||||
|
||||
@@ -29,10 +29,7 @@ public final class HeroinfoActivity extends AndorsTrailBaseFragmentActivity {
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
if (!app.isInitialized()) { finish(); return; }
|
||||
this.world = app.getWorld();
|
||||
|
||||
app.setWindowParameters(this);
|
||||
|
||||
setContentView(R.layout.tabbedlayout);
|
||||
initializeView(this, R.layout.tabbedlayout, android.R.id.tabhost);
|
||||
|
||||
Resources res = getResources();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.widget.TextView;
|
||||
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
|
||||
import com.gpl.rpg.AndorsTrail.R;
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.controller.Constants;
|
||||
import com.gpl.rpg.AndorsTrail.model.item.ItemType;
|
||||
import com.gpl.rpg.AndorsTrail.util.ThemeHelper;
|
||||
import com.gpl.rpg.AndorsTrail.view.ItemEffectsView;
|
||||
@@ -45,7 +46,7 @@ public final class ItemInfoActivity extends AndorsTrailBaseActivity {
|
||||
boolean buttonEnabled = params.getBoolean("buttonEnabled");
|
||||
boolean moreButtonEnabled = params.getBoolean("moreActions");
|
||||
|
||||
setContentView(R.layout.iteminfo);
|
||||
initializeView(this, R.layout.iteminfo, R.id.iteminfo_root);
|
||||
|
||||
TextView tv = (TextView) findViewById(R.id.iteminfo_title);
|
||||
tv.setText(itemType.getName(world.model.player));
|
||||
@@ -54,7 +55,7 @@ public final class ItemInfoActivity extends AndorsTrailBaseActivity {
|
||||
tv = (TextView) findViewById(R.id.iteminfo_description);
|
||||
String description = itemType.getDescription();
|
||||
if (description != null) {
|
||||
tv.setText(description);
|
||||
tv.setText(description.replace(Constants.PLACEHOLDER_PLAYERNAME, world.model.player.getName()));
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
tv.setVisibility(View.GONE);
|
||||
@@ -67,8 +68,10 @@ public final class ItemInfoActivity extends AndorsTrailBaseActivity {
|
||||
itemType.effects_equip,
|
||||
itemType.effects_use == null ? null : Collections.singletonList(itemType.effects_use),
|
||||
itemType.effects_hit == null ? null : Collections.singletonList(itemType.effects_hit),
|
||||
itemType.effects_miss == null ? null : Collections.singletonList(itemType.effects_miss),
|
||||
itemType.effects_kill == null ? null : Collections.singletonList(itemType.effects_kill),
|
||||
itemType.effects_hitReceived == null ? null : Collections.singletonList(itemType.effects_hitReceived),
|
||||
itemType.effects_missReceived == null ? null : Collections.singletonList(itemType.effects_missReceived),
|
||||
null,
|
||||
itemType.isWeapon()
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class LevelUpActivity extends AndorsTrailBaseActivity {
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.levelup);
|
||||
initializeView(this, R.layout.levelup, R.id.levelup_root);
|
||||
|
||||
levelup_title = (TextView) findViewById(R.id.levelup_title);
|
||||
levelup_description = (TextView) findViewById(R.id.levelup_description);
|
||||
|
||||
@@ -70,7 +70,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
|
||||
String loadsave = getIntent().getData().getLastPathSegment();
|
||||
isLoading = (loadsave.equalsIgnoreCase("load"));
|
||||
|
||||
setContentView(R.layout.loadsave);
|
||||
initializeView(this, R.layout.loadsave, R.id.loadsave_root);
|
||||
|
||||
TextView tv = (TextView) findViewById(R.id.loadsave_title);
|
||||
if (isLoading) {
|
||||
|
||||
@@ -36,8 +36,7 @@ public final class LoadingActivity extends AndorsTrailBaseActivity implements On
|
||||
setTheme(ThemeHelper.getBaseTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
app.setWindowParameters(this);
|
||||
setContentView(R.layout.startscreen);
|
||||
initializeView(this, R.layout.startscreen, R.id.startscreen_fragment_container);
|
||||
|
||||
TextView tv = (TextView) findViewById(R.id.startscreen_version);
|
||||
tv.setVisibility(View.GONE);
|
||||
|
||||
@@ -91,9 +91,9 @@ public final class MainActivity
|
||||
AndorsTrailPreferences preferences = app.getPreferences();
|
||||
this.world = app.getWorld();
|
||||
this.controllers = app.getControllerContext();
|
||||
app.setWindowParameters(this);
|
||||
|
||||
setContentView(R.layout.main);
|
||||
initializeView(this, R.layout.main, R.id.main_container);
|
||||
|
||||
mainview = (MainView) findViewById(R.id.main_mainview);
|
||||
statusview = (StatusView) findViewById(R.id.main_statusview);
|
||||
combatview = (CombatView) findViewById(R.id.main_combatview);
|
||||
|
||||
@@ -34,7 +34,7 @@ public final class MonsterEncounterActivity extends AndorsTrailBaseActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.monsterencounter);
|
||||
initializeView(this, R.layout.monsterencounter, R.id.monsterencounter_root);
|
||||
|
||||
CharSequence difficulty = getText(MonsterInfoActivity.getMonsterDifficultyResource(controllers, monster));
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public final class MonsterInfoActivity extends AndorsTrailBaseActivity {
|
||||
this.controllers = app.getControllerContext();
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.monsterinfo);
|
||||
initializeView(this, R.layout.monsterinfo, R.id.monsterinfo_root);
|
||||
|
||||
monsterinfo_title = (TextView) findViewById(R.id.monsterinfo_title);
|
||||
monsterinfo_difficulty = (TextView) findViewById(R.id.monsterinfo_difficulty);
|
||||
@@ -90,8 +90,10 @@ public final class MonsterInfoActivity extends AndorsTrailBaseActivity {
|
||||
null,
|
||||
null,
|
||||
monster.getOnHitEffectsAsList(),
|
||||
monster.getOnMissEffectsAsList(),
|
||||
null,
|
||||
monster.getOnHitReceivedEffectsAsList(),
|
||||
monster.getOnMissReceivedEffectsAsList(),
|
||||
monster.getOnDeathEffects(),
|
||||
false);
|
||||
hp.update(monster.getMaxHP(), monster.getCurrentHP());
|
||||
|
||||
@@ -13,17 +13,16 @@ public final class Preferences extends PreferenceActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(ThemeHelper.getBaseTheme());
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
super.onCreate(savedInstanceState);
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
if (app.getPreferences().fullscreen) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
app.setWindowParameters(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
app.setFullscreenMode(this);
|
||||
|
||||
|
||||
app.setLocale(this);
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,9 +23,8 @@ public final class ShopActivity extends AndorsTrailBaseFragmentActivity {
|
||||
|
||||
AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this);
|
||||
if (!app.isInitialized()) { finish(); return; }
|
||||
app.setWindowParameters(this);
|
||||
|
||||
setContentView(R.layout.tabbedlayout);
|
||||
initializeView(this, R.layout.tabbedlayout, android.R.id.tabhost);
|
||||
|
||||
final Resources res = getResources();
|
||||
|
||||
|
||||
@@ -31,9 +31,7 @@ public final class SkillInfoActivity extends AndorsTrailBaseActivity {
|
||||
final WorldContext world = app.getWorld();
|
||||
final Player player = world.model.player;
|
||||
|
||||
app.setWindowParameters(this);
|
||||
|
||||
setContentView(R.layout.skill_info_view);
|
||||
initializeView(this, R.layout.skill_info_view, R.id.skillinfo_root);
|
||||
|
||||
final Resources res = getResources();
|
||||
final Intent intent = getIntent();
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.AnimationDrawable;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
||||
import android.view.KeyEvent;
|
||||
@@ -49,9 +51,9 @@ public final class StartScreenActivity extends AndorsTrailBaseFragmentActivity i
|
||||
final Resources res = getResources();
|
||||
TileManager tileManager = app.getWorld().tileManager;
|
||||
tileManager.setDensity(res);
|
||||
app.setWindowParameters(this);
|
||||
|
||||
setContentView(R.layout.startscreen);
|
||||
initializeView(this, R.layout.startscreen, R.id.startscreen_fragment_container);
|
||||
app.setFullscreenMode(this);
|
||||
|
||||
if (findViewById(R.id.startscreen_fragment_container) != null) {
|
||||
StartScreenActivity_MainMenu mainMenu = new StartScreenActivity_MainMenu();
|
||||
@@ -67,9 +69,11 @@ public final class StartScreenActivity extends AndorsTrailBaseFragmentActivity i
|
||||
|
||||
|
||||
tv = (TextView) findViewById(R.id.startscreen_version);
|
||||
app.setUsablePadding(tv);
|
||||
tv.setText('v' + AndorsTrailApplication.CURRENT_VERSION_DISPLAY);
|
||||
|
||||
development_version = (TextView) findViewById(R.id.startscreen_dev_version);
|
||||
app.setUsablePadding((View) development_version.getParent());
|
||||
if (AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAMES) {
|
||||
development_version.setText(R.string.startscreen_incompatible_savegames);
|
||||
development_version.setVisibility(View.VISIBLE);
|
||||
@@ -96,6 +100,10 @@ public final class StartScreenActivity extends AndorsTrailBaseFragmentActivity i
|
||||
}
|
||||
});
|
||||
}
|
||||
View titleLogo = findViewById(R.id.title_logo);
|
||||
if (titleLogo != null) {
|
||||
app.setUsablePadding(titleLogo);
|
||||
}
|
||||
|
||||
if (development_version.getVisibility() == View.VISIBLE) {
|
||||
development_version.setText(development_version.getText()
|
||||
@@ -112,7 +120,7 @@ public final class StartScreenActivity extends AndorsTrailBaseFragmentActivity i
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
|
||||
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
final CustomDialog d = CustomDialogFactory.createDialog(this,
|
||||
|
||||
@@ -183,19 +183,25 @@ public final class HeroinfoActivity_Stats extends Fragment {
|
||||
);
|
||||
|
||||
ArrayList<ItemTraits_OnUse> effects_hit = new ArrayList<ItemTraits_OnUse>();
|
||||
ArrayList<ItemTraits_OnUse> effects_miss = new ArrayList<ItemTraits_OnUse>();
|
||||
ArrayList<ItemTraits_OnUse> effects_kill = new ArrayList<ItemTraits_OnUse>();
|
||||
ArrayList<ItemTraits_OnHitReceived> effects_hitReceived = new ArrayList<ItemTraits_OnHitReceived>();
|
||||
ArrayList<ItemTraits_OnHitReceived> effects_missReceived = new ArrayList<ItemTraits_OnHitReceived>();
|
||||
for (Inventory.WearSlot slot : Inventory.WearSlot.values()) {
|
||||
ItemType type = player.inventory.getItemTypeInWearSlot(slot);
|
||||
if (type == null) continue;
|
||||
if (type.effects_hit != null) effects_hit.add(type.effects_hit);
|
||||
if (type.effects_miss != null) effects_miss.add(type.effects_miss);
|
||||
if (type.effects_kill != null) effects_kill.add(type.effects_kill);
|
||||
if (type.effects_hitReceived != null) effects_hitReceived.add(type.effects_hitReceived);
|
||||
if (type.effects_missReceived != null) effects_missReceived.add(type.effects_missReceived);
|
||||
}
|
||||
if (effects_hit.isEmpty()) effects_hit = null;
|
||||
if (effects_miss.isEmpty()) effects_miss = null;
|
||||
if (effects_kill.isEmpty()) effects_kill = null;
|
||||
if (effects_hitReceived.isEmpty()) effects_hitReceived = null;
|
||||
actorinfo_onhiteffects.update(null, null, effects_hit, effects_kill, effects_hitReceived, null, false);
|
||||
if (effects_missReceived.isEmpty()) effects_missReceived = null;
|
||||
actorinfo_onhiteffects.update(null, null, effects_hit, effects_miss, effects_kill, effects_hitReceived,effects_missReceived, null, false);
|
||||
|
||||
|
||||
updateStatsTableRow(world.model.statistics.getNumberOfCompletedQuests(world), R.id.heroinfo_gamestats_quests, R.id.heroinfo_gamestats_quests_row);
|
||||
|
||||
@@ -59,7 +59,6 @@ public class StartScreenActivity_MainMenu extends Fragment {
|
||||
updatePreferences(false);
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
|
||||
if (container != null) {
|
||||
container.removeAllViews();
|
||||
}
|
||||
@@ -196,7 +195,6 @@ public class StartScreenActivity_MainMenu extends Fragment {
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(29)
|
||||
public void migrateDataOnDemand(final Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (activity.getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
@@ -232,7 +230,6 @@ public class StartScreenActivity_MainMenu extends Fragment {
|
||||
private static final int READ_EXTERNAL_STORAGE_REQUEST=1;
|
||||
private static final int WRITE_EXTERNAL_STORAGE_REQUEST=2;
|
||||
|
||||
@TargetApi(23)
|
||||
public static void checkAndRequestPermissions(final Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
if (activity.getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.activity.fragment;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -39,8 +40,7 @@ public class StartScreenActivity_NewGame extends Fragment {
|
||||
}
|
||||
|
||||
View root = inflater.inflate(R.layout.startscreen_newgame, container, false);
|
||||
|
||||
|
||||
|
||||
startscreen_enterheroname = (TextView) root.findViewById(R.id.startscreen_enterheroname);
|
||||
|
||||
new SpinnerEmulator(root, R.id.startscreen_mode_selector_button, R.array.startscreen_mode_selector, R.string.startscreen_game_mode) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gpl.rpg.AndorsTrail.context;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.model.ModelContainer;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.ActorConditionTypeCollection;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection;
|
||||
@@ -13,6 +14,8 @@ import com.gpl.rpg.AndorsTrail.resource.ConversationLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.VisualEffectCollection;
|
||||
import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager;
|
||||
|
||||
import java.security.DigestException;
|
||||
|
||||
public final class WorldContext {
|
||||
//Objectcollections
|
||||
public final ConversationLoader conversationLoader;
|
||||
@@ -62,4 +65,11 @@ public final class WorldContext {
|
||||
public void resetForNewGame() {
|
||||
maps.resetForNewGame();
|
||||
}
|
||||
|
||||
public byte[] getChecksum() throws DigestException {
|
||||
ChecksumBuilder checksumBuilder = new ChecksumBuilder();
|
||||
model.addToChecksum(checksumBuilder);
|
||||
maps.addToChecksum(checksumBuilder, this);
|
||||
return checksumBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +595,10 @@ public final class CombatController implements VisualEffectCompletedCallback {
|
||||
|
||||
private AttackResult attack(final Actor attacker, final Actor target) {
|
||||
int hitChance = getAttackHitChance(attacker, target);
|
||||
if (!Constants.roll100(hitChance)) return AttackResult.MISS;
|
||||
if (!Constants.roll100(hitChance)) {
|
||||
applyAttackMissStatusEffects(attacker, target);
|
||||
return AttackResult.MISS;
|
||||
}
|
||||
|
||||
int damage = Constants.rollValue(attacker.getDamagePotential());
|
||||
boolean isCriticalHit = false;
|
||||
@@ -629,6 +632,21 @@ public final class CombatController implements VisualEffectCompletedCallback {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyAttackMissStatusEffects(Actor attacker, Actor target) {
|
||||
ItemTraits_OnUse[] onMissEffects = attacker.getOnMissEffects();
|
||||
ItemTraits_OnHitReceived[] onMissReceivedEffects = target.getOnMissReceivedEffects();
|
||||
if (onMissEffects != null) {
|
||||
for (ItemTraits_OnUse e : onMissEffects) {
|
||||
controllers.actorStatsController.applyUseEffect(attacker, target, e);
|
||||
}
|
||||
}
|
||||
if (onMissReceivedEffects != null) {
|
||||
for (ItemTraits_OnHitReceived e : onMissReceivedEffects) {
|
||||
controllers.actorStatsController.applyHitReceivedEffect(target, attacker, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void endOfCombatRound() {
|
||||
world.model.worldData.tickWorldTime();
|
||||
controllers.gameRoundController.resetRoundTimers();
|
||||
|
||||
@@ -195,19 +195,27 @@ public final class ItemController {
|
||||
|
||||
public static void recalculateHitEffectsFromWornItems(Player player) {
|
||||
ArrayList<ItemTraits_OnUse> effects_onHit = null;
|
||||
ArrayList<ItemTraits_OnUse> effects_onMiss = null;
|
||||
ArrayList<ItemTraits_OnHitReceived> effects_onHitReceived = null;
|
||||
ArrayList<ItemTraits_OnHitReceived> effects_onMissReceived = null;
|
||||
for (Inventory.WearSlot slot : Inventory.WearSlot.values()) {
|
||||
ItemType type = player.inventory.getItemTypeInWearSlot(slot);
|
||||
if (type == null) continue;
|
||||
ItemTraits_OnUse eh = type.effects_hit;
|
||||
ItemTraits_OnHitReceived ehr = type.effects_hitReceived;
|
||||
if (eh == null && ehr == null) continue;
|
||||
ItemTraits_OnUse em = type.effects_miss;
|
||||
ItemTraits_OnHitReceived emr = type.effects_missReceived;
|
||||
if (eh == null && ehr == null && em == null && emr == null) continue;
|
||||
|
||||
if (effects_onHit == null) effects_onHit = new ArrayList<ItemTraits_OnUse>();
|
||||
if (eh != null) effects_onHit.add(eh);
|
||||
if (effects_onMiss == null) effects_onMiss = new ArrayList<ItemTraits_OnUse>();
|
||||
if (em != null) effects_onMiss.add(em);
|
||||
|
||||
if (effects_onHitReceived == null) effects_onHitReceived = new ArrayList<ItemTraits_OnHitReceived>();
|
||||
if (ehr != null) effects_onHitReceived.add(ehr);
|
||||
if (effects_onMissReceived == null) effects_onMissReceived = new ArrayList<ItemTraits_OnHitReceived>();
|
||||
if (emr != null) effects_onMissReceived.add(emr);
|
||||
}
|
||||
|
||||
if (effects_onHit != null) {
|
||||
@@ -217,6 +225,13 @@ public final class ItemController {
|
||||
} else {
|
||||
player.onHitEffects = null;
|
||||
}
|
||||
if (effects_onMiss != null) {
|
||||
ItemTraits_OnUse[] effects_ = new ItemTraits_OnUse[effects_onMiss.size()];
|
||||
effects_ = effects_onMiss.toArray(effects_);
|
||||
player.onMissEffects = effects_;
|
||||
} else {
|
||||
player.onMissEffects = null;
|
||||
}
|
||||
|
||||
if (effects_onHitReceived != null) {
|
||||
ItemTraits_OnHitReceived[] effects_ = new ItemTraits_OnHitReceived[effects_onHitReceived.size()];
|
||||
@@ -225,6 +240,13 @@ public final class ItemController {
|
||||
} else {
|
||||
player.onHitReceivedEffects = null;
|
||||
}
|
||||
if (effects_onMissReceived != null) {
|
||||
ItemTraits_OnHitReceived[] effects_ = new ItemTraits_OnHitReceived[effects_onMissReceived.size()];
|
||||
effects_ = effects_onMissReceived.toArray(effects_);
|
||||
player.onMissReceivedEffects = effects_;
|
||||
} else {
|
||||
player.onMissReceivedEffects = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void consumeNonItemLoot(Loot loot) {
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.gpl.rpg.AndorsTrail.model;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.util.L;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class ChecksumBuilder {
|
||||
|
||||
public static final int CHECKSUM_LENGTH = 32;// 256 bits (depends on the hash algorithm)
|
||||
public static final String CHECKSUM_ALGORITHM = "SHA-256"; //Should be available in all Android versions
|
||||
private ByteBuffer buffer;
|
||||
private final MessageDigest digest;
|
||||
|
||||
private ChecksumBuilder(int initialCapacity, ByteOrder byteOrder) {
|
||||
buffer = ByteBuffer.allocate(initialCapacity);
|
||||
buffer.order(byteOrder);
|
||||
try {
|
||||
digest = MessageDigest.getInstance(CHECKSUM_ALGORITHM); // Or SHA-512 for even stronger hash
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Hash algorithm not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ChecksumBuilder(int initialCapacity) {
|
||||
this(initialCapacity, ByteOrder.BIG_ENDIAN); // Default to big-endian
|
||||
}
|
||||
|
||||
public ChecksumBuilder() {
|
||||
this(1024*10); // A reasonable default initial capacity
|
||||
}
|
||||
|
||||
// --- Methods for adding different data types ---
|
||||
|
||||
public ChecksumBuilder add(String value) {
|
||||
if (value != null) {
|
||||
byte[] bytes;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
bytes = value.getBytes();
|
||||
}
|
||||
add(bytes);
|
||||
} else {
|
||||
add(-1); // Use -1 to represent a null string
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChecksumBuilder add(byte[] bytes) {
|
||||
add(bytes.length); // Add length prefix
|
||||
ensureCapacity(bytes.length + 4); // +4 for length prefix (int)
|
||||
buffer.put(bytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChecksumBuilder add(boolean value) {
|
||||
ensureCapacity(1);
|
||||
buffer.put(value ? (byte) 1 : (byte) 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChecksumBuilder add(long value) {
|
||||
ensureCapacity(8);
|
||||
buffer.putLong(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChecksumBuilder add(int value) {
|
||||
ensureCapacity(4);
|
||||
buffer.putInt(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChecksumBuilder add(float value) {
|
||||
ensureCapacity(8);
|
||||
buffer.putFloat(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChecksumBuilder add(double value) {
|
||||
ensureCapacity(4);
|
||||
buffer.putDouble(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
// --- Method to finalize and get the checksum ---
|
||||
|
||||
public byte[] build() throws DigestException {
|
||||
buffer.flip(); // Prepare for reading
|
||||
digest.update(buffer);// Only use the actually used part of the buffer
|
||||
buffer.flip(); // Prepare for further writing
|
||||
return digest.digest();
|
||||
}
|
||||
|
||||
|
||||
// --- Utility method to ensure sufficient capacity ---
|
||||
private void ensureCapacity(int required) {
|
||||
if (buffer.remaining() < required) {
|
||||
int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + required);
|
||||
ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity);
|
||||
newBuffer.order(buffer.order());
|
||||
buffer.flip(); // Prepare for reading
|
||||
newBuffer.put(buffer); // Copy existing data
|
||||
buffer = newBuffer; // Assign the new buffer to the field
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -22,6 +23,8 @@ import com.gpl.rpg.AndorsTrail.model.quest.Quest;
|
||||
import com.gpl.rpg.AndorsTrail.util.HashMapHelper;
|
||||
|
||||
public final class GameStatistics {
|
||||
private boolean isAlteredSavegame = false;
|
||||
private byte[] checksum = new byte[ChecksumBuilder.CHECKSUM_LENGTH];
|
||||
private int deaths = 0;
|
||||
private final HashMap<String, Integer> killedMonstersByTypeID = new HashMap<String, Integer>();
|
||||
private final HashMap<String, Integer> killedMonstersByName = new HashMap<String, Integer>();
|
||||
@@ -67,6 +70,7 @@ public final class GameStatistics {
|
||||
public boolean hasUnlimitedLives() { return startLives == -1; }
|
||||
|
||||
public int getStartLives() { return startLives; }
|
||||
public boolean getIsAlteredSavegame() { return isAlteredSavegame; }
|
||||
|
||||
public int getLivesLeft() { return hasUnlimitedLives() ? -1 : startLives - deaths; }
|
||||
|
||||
@@ -158,6 +162,18 @@ public final class GameStatistics {
|
||||
}
|
||||
};
|
||||
|
||||
public void setChecksum(byte[] checksum) {
|
||||
if (checksum.length != ChecksumBuilder.CHECKSUM_LENGTH) throw new IllegalArgumentException("Invalid checksum length.");
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
public boolean compareChecksum(byte[] checksum) {
|
||||
return this.checksum.length == checksum.length && MessageDigest.isEqual(this.checksum, checksum);
|
||||
}
|
||||
|
||||
public void markAsAlteredSavegame() {
|
||||
isAlteredSavegame = true;
|
||||
}
|
||||
|
||||
// ====== PARCELABLE ===================================================================
|
||||
|
||||
@@ -194,6 +210,11 @@ public final class GameStatistics {
|
||||
|
||||
this.startLives = src.readInt();
|
||||
this.unlimitedSaves = src.readBoolean();
|
||||
if (fileversion < 81) return;
|
||||
this.isAlteredSavegame = src.readBoolean();
|
||||
final int checksumLength = src.readInt();
|
||||
this.checksum = new byte[checksumLength];
|
||||
if( src.read(checksum) != checksumLength) throw new IOException("Failed to read full checksum.");
|
||||
}
|
||||
|
||||
public void writeToParcel(DataOutputStream dest) throws IOException {
|
||||
@@ -213,5 +234,29 @@ public final class GameStatistics {
|
||||
dest.writeInt(spentGold);
|
||||
dest.writeInt(startLives);
|
||||
dest.writeBoolean(unlimitedSaves);
|
||||
dest.writeBoolean(isAlteredSavegame);
|
||||
dest.writeInt(checksum.length);
|
||||
dest.write(checksum);
|
||||
}
|
||||
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(deaths);
|
||||
Set<Entry<String, Integer> > set = killedMonstersByTypeID.entrySet();
|
||||
builder.add(set.size());
|
||||
for (Entry<String, Integer> e : set) {
|
||||
builder.add(e.getKey());
|
||||
builder.add(e.getValue());
|
||||
}
|
||||
set = usedItems.entrySet();
|
||||
builder.add(set.size());
|
||||
for (Entry<String, Integer> e : set) {
|
||||
builder.add(e.getKey());
|
||||
builder.add(e.getValue());
|
||||
}
|
||||
builder.add(spentGold);
|
||||
builder.add(startLives);
|
||||
builder.add(unlimitedSaves);
|
||||
builder.add(isAlteredSavegame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,12 @@ public final class InterfaceData {
|
||||
}
|
||||
dest.writeUTF(selectedTabHeroInfo);
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(isMainActivityVisible);
|
||||
builder.add(isInCombat);
|
||||
builder.add(selectedPosition != null);
|
||||
if (selectedPosition != null) selectedPosition.addToChecksum(builder);
|
||||
builder.add(selectedTabHeroInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,11 @@ public final class ModelContainer {
|
||||
statistics.writeToParcel(dest);
|
||||
worldData.writeToParcel(dest);
|
||||
}
|
||||
public void addToChecksum(ChecksumBuilder builder){
|
||||
player.addToChecksum(builder);
|
||||
builder.add(currentMaps.map.name);
|
||||
uiSelections.addToChecksum(builder);
|
||||
statistics.addToChecksum(builder);
|
||||
worldData.addToChecksum(builder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,4 +114,13 @@ public final class WorldData {
|
||||
dest.writeLong(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(worldTime);
|
||||
builder.add(timers.size());
|
||||
for(Map.Entry<String, Long> e : timers.entrySet()) {
|
||||
builder.add(e.getKey());
|
||||
builder.add(e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
|
||||
public final class ActorCondition {
|
||||
public static final int MAGNITUDE_REMOVE_ALL = -99;
|
||||
@@ -51,4 +52,11 @@ public final class ActorCondition {
|
||||
dest.writeInt(magnitude);
|
||||
dest.writeInt(duration);
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(conditionType.conditionTypeID);
|
||||
builder.add(magnitude);
|
||||
builder.add(duration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ public class Actor {
|
||||
public int blockChance;
|
||||
public int damageResistance;
|
||||
public ItemTraits_OnUse[] onHitEffects;
|
||||
public ItemTraits_OnUse[] onMissEffects;
|
||||
public ItemTraits_OnHitReceived[] onHitReceivedEffects;
|
||||
public ItemTraits_OnHitReceived[] onMissReceivedEffects;
|
||||
public ItemTraits_OnUse onDeathEffects;
|
||||
public boolean hasVFXRunning = false;
|
||||
public long vfxStartTime = 0;
|
||||
@@ -69,8 +71,12 @@ public class Actor {
|
||||
public int getDamageResistance() { return damageResistance; }
|
||||
public ItemTraits_OnUse[] getOnHitEffects() { return onHitEffects; }
|
||||
public List<ItemTraits_OnUse> getOnHitEffectsAsList() { return onHitEffects == null ? null : Arrays.asList(onHitEffects); }
|
||||
public ItemTraits_OnUse[] getOnMissEffects() { return onMissEffects; }
|
||||
public List<ItemTraits_OnUse> getOnMissEffectsAsList() { return onMissEffects == null ? null : Arrays.asList(onMissEffects); }
|
||||
public ItemTraits_OnHitReceived[] getOnHitReceivedEffects() { return onHitReceivedEffects; }
|
||||
public List<ItemTraits_OnHitReceived> getOnHitReceivedEffectsAsList() { return onHitReceivedEffects == null ? null : Arrays.asList(onHitReceivedEffects); }
|
||||
public ItemTraits_OnHitReceived[] getOnMissReceivedEffects() { return onMissReceivedEffects; }
|
||||
public List<ItemTraits_OnHitReceived> getOnMissReceivedEffectsAsList() { return onMissReceivedEffects == null ? null : Arrays.asList(onMissReceivedEffects); }
|
||||
public ItemTraits_OnUse getOnDeathEffects() { return onDeathEffects; }
|
||||
|
||||
public boolean hasCriticalSkillEffect() { return getCriticalSkill() != 0; }
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.io.IOException;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.controller.Constants;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.ActorCondition;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection;
|
||||
import com.gpl.rpg.AndorsTrail.model.item.DropList;
|
||||
@@ -193,4 +194,43 @@ public final class Monster extends Actor {
|
||||
dest.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(getMonsterTypeID());
|
||||
if (attackCost == monsterType.attackCost
|
||||
&& attackChance == monsterType.attackChance
|
||||
&& criticalSkill == monsterType.criticalSkill
|
||||
&& criticalMultiplier == monsterType.criticalMultiplier
|
||||
&& damagePotential.equals(monsterType.damagePotential)
|
||||
&& blockChance == monsterType.blockChance
|
||||
&& damageResistance == monsterType.damageResistance
|
||||
) {
|
||||
builder.add(false);
|
||||
} else {
|
||||
builder.add(true);
|
||||
builder.add(attackCost);
|
||||
builder.add(attackChance);
|
||||
builder.add(criticalSkill);
|
||||
builder.add(criticalMultiplier);
|
||||
damagePotential.addToChecksum(builder);
|
||||
builder.add(blockChance);
|
||||
builder.add(damageResistance);
|
||||
}
|
||||
ap.addToChecksum(builder);
|
||||
health.addToChecksum(builder);
|
||||
position.addToChecksum(builder);
|
||||
builder.add(conditions.size());
|
||||
for (ActorCondition c : conditions) {
|
||||
c.addToChecksum(builder);
|
||||
}
|
||||
builder.add(moveCost);
|
||||
|
||||
builder.add(forceAggressive);
|
||||
if (shopItems != null) {
|
||||
builder.add(true);
|
||||
shopItems.addToChecksum(builder);
|
||||
} else {
|
||||
builder.add(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
|
||||
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.controller.Constants;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.ActorCondition;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection;
|
||||
import com.gpl.rpg.AndorsTrail.model.item.DropListCollection;
|
||||
@@ -475,5 +476,62 @@ public final class Player extends Actor {
|
||||
dest.writeUTF(id);
|
||||
dest.writeLong(savedVersion);
|
||||
}
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
//builder.add(baseTraits.iconID);// Do not add to checksum so that it can be changed without invalidating checksums
|
||||
builder.add(baseTraits.maxAP);
|
||||
builder.add(baseTraits.maxHP);
|
||||
//builder.add(name);// Do not add to checksum so that it can be changed without invalidating checksums
|
||||
builder.add(moveCost); // TODO: Should we really write this?
|
||||
builder.add(baseTraits.attackCost);
|
||||
builder.add(baseTraits.attackChance);
|
||||
builder.add(baseTraits.criticalSkill);
|
||||
builder.add(baseTraits.criticalMultiplier);
|
||||
baseTraits.damagePotential.addToChecksum(builder);
|
||||
builder.add(baseTraits.blockChance);
|
||||
builder.add(baseTraits.damageResistance);
|
||||
builder.add(baseTraits.moveCost);
|
||||
|
||||
ap.addToChecksum(builder);
|
||||
health.addToChecksum(builder);
|
||||
position.addToChecksum(builder);
|
||||
builder.add(conditions.size());
|
||||
for (ActorCondition c : conditions) {
|
||||
c.addToChecksum(builder);
|
||||
}
|
||||
builder.add(immunities.size());
|
||||
for (ActorCondition c : immunities) {
|
||||
c.addToChecksum(builder);
|
||||
}
|
||||
lastPosition.addToChecksum(builder);
|
||||
nextPosition.addToChecksum(builder);
|
||||
builder.add(level);
|
||||
builder.add(totalExperience);
|
||||
inventory.addToChecksum(builder);
|
||||
builder.add(baseTraits.useItemCost);
|
||||
builder.add(baseTraits.reequipCost);
|
||||
builder.add(skillLevels.size());
|
||||
for (int i = 0; i < skillLevels.size(); ++i) {
|
||||
builder.add(skillLevels.keyAt(i));
|
||||
builder.add(skillLevels.valueAt(i));
|
||||
}
|
||||
builder.add(spawnMap);
|
||||
builder.add(spawnPlace);
|
||||
builder.add(questProgress.size());
|
||||
for(Entry<String, LinkedHashSet<Integer> > e : questProgress.entrySet()) {
|
||||
builder.add(e.getKey());
|
||||
builder.add(e.getValue().size());
|
||||
for(int progress : e.getValue()) {
|
||||
builder.add(progress);
|
||||
}
|
||||
}
|
||||
builder.add(availableSkillIncreases);
|
||||
builder.add(alignments.size());
|
||||
for(Entry<String, Integer> e : alignments.entrySet()) {
|
||||
builder.add(e.getKey());
|
||||
builder.add(e.getValue());
|
||||
}
|
||||
builder.add(id);
|
||||
builder.add(savedVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForItemContainer;
|
||||
|
||||
public final class Inventory extends ItemContainer {
|
||||
@@ -208,4 +209,21 @@ public final class Inventory extends ItemContainer {
|
||||
}
|
||||
}
|
||||
}
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
super.addToChecksum(builder);
|
||||
builder.add(gold);
|
||||
builder.add(NUM_WORN_SLOTS);
|
||||
for(int i = 0; i < NUM_WORN_SLOTS; ++i) {
|
||||
if (wear[i] != null) {
|
||||
builder.add(wear[i].id);
|
||||
}
|
||||
}
|
||||
builder.add(NUM_QUICK_SLOTS);
|
||||
for(int i = 0; i < NUM_QUICK_SLOTS; ++i) {
|
||||
if (quickitem[i] != null) {
|
||||
builder.add(quickitem[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.Player;
|
||||
|
||||
public class ItemContainer {
|
||||
@@ -42,6 +43,10 @@ public class ItemContainer {
|
||||
dest.writeUTF(itemType.id);
|
||||
dest.writeInt(quantity);
|
||||
}
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(itemType.id);
|
||||
builder.add(quantity);
|
||||
}
|
||||
}
|
||||
|
||||
public void addItem(ItemType itemType, int quantity) {
|
||||
@@ -280,4 +285,10 @@ public class ItemContainer {
|
||||
e.writeToParcel(dest);
|
||||
}
|
||||
}
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(items.size());
|
||||
for (ItemEntry e : items) {
|
||||
e.addToChecksum(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,10 @@ public final class ItemType {
|
||||
public final ItemTraits_OnEquip effects_equip;
|
||||
public final ItemTraits_OnUse effects_use;
|
||||
public final ItemTraits_OnUse effects_hit;
|
||||
public final ItemTraits_OnUse effects_miss;
|
||||
public final ItemTraits_OnUse effects_kill;
|
||||
public final ItemTraits_OnHitReceived effects_hitReceived;
|
||||
public final ItemTraits_OnHitReceived effects_missReceived;
|
||||
|
||||
public ItemType(
|
||||
String id
|
||||
@@ -48,8 +50,10 @@ public final class ItemType {
|
||||
, ItemTraits_OnEquip effects_equip
|
||||
, ItemTraits_OnUse effects_use
|
||||
, ItemTraits_OnUse effects_hit
|
||||
, ItemTraits_OnUse effects_miss
|
||||
, ItemTraits_OnUse effects_kill
|
||||
, ItemTraits_OnHitReceived effects_hitReceived
|
||||
, ItemTraits_OnHitReceived effects_missReceived
|
||||
) {
|
||||
this.id = id;
|
||||
this.iconID = iconID;
|
||||
@@ -63,8 +67,10 @@ public final class ItemType {
|
||||
this.effects_equip = effects_equip;
|
||||
this.effects_use = effects_use;
|
||||
this.effects_hit = effects_hit;
|
||||
this.effects_miss = effects_miss;
|
||||
this.effects_kill = effects_kill;
|
||||
this.effects_hitReceived = effects_hitReceived;
|
||||
this.effects_missReceived = effects_missReceived;
|
||||
this.hasPersonalizedName = name.contains(Constants.PLACEHOLDER_PLAYERNAME);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForItemContainer;
|
||||
import com.gpl.rpg.AndorsTrail.util.Coord;
|
||||
|
||||
@@ -88,4 +89,12 @@ public final class Loot {
|
||||
position.writeToParcel(dest);
|
||||
dest.writeBoolean(isVisible);
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(exp);
|
||||
builder.add(gold);
|
||||
items.addToChecksum(builder);
|
||||
position.addToChecksum(builder);
|
||||
builder.add(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.List;
|
||||
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
|
||||
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForMap;
|
||||
import com.gpl.rpg.AndorsTrail.util.L;
|
||||
|
||||
@@ -99,4 +100,16 @@ public final class MapCollection {
|
||||
map.writeToParcel(dest, world);
|
||||
}
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder checksumBuilder, WorldContext world) {
|
||||
List<PredefinedMap> mapsToExport = new ArrayList<PredefinedMap>();
|
||||
for(PredefinedMap map : getAllMaps()) {
|
||||
if (shouldSaveMap(world, map)) mapsToExport.add(map);
|
||||
}
|
||||
checksumBuilder.add(mapsToExport.size());
|
||||
for(PredefinedMap map : mapsToExport) {
|
||||
checksumBuilder.add(map.name);
|
||||
map.addToChecksum(checksumBuilder, world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.controller.Constants;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.MonsterType;
|
||||
import com.gpl.rpg.AndorsTrail.util.Coord;
|
||||
@@ -140,4 +141,12 @@ public final class MonsterSpawnArea {
|
||||
m.writeToParcel(dest);
|
||||
}
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(isSpawning);
|
||||
builder.add(monsters.size());
|
||||
for (Monster m : monsters) {
|
||||
m.addToChecksum(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.gpl.rpg.AndorsTrail.context.ControllerContext;
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.controller.Constants;
|
||||
import com.gpl.rpg.AndorsTrail.controller.VisualEffectController.BloodSplatter;
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
|
||||
import com.gpl.rpg.AndorsTrail.model.item.ItemType;
|
||||
import com.gpl.rpg.AndorsTrail.model.item.Loot;
|
||||
@@ -398,4 +399,30 @@ public final class PredefinedMap {
|
||||
dest.writeBoolean(visited);
|
||||
dest.writeUTF(lastSeenLayoutHash);
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder, WorldContext world) {
|
||||
if (shouldSaveMapData(world)) {
|
||||
builder.add(true);
|
||||
builder.add(spawnAreas.length);
|
||||
for(MonsterSpawnArea a : spawnAreas) {
|
||||
builder.add(a.areaID);
|
||||
a.addToChecksum(builder);
|
||||
}
|
||||
builder.add(activeMapObjectGroups.size());
|
||||
for(String s : activeMapObjectGroups) {
|
||||
builder.add(s);
|
||||
}
|
||||
builder.add(groundBags.size());
|
||||
for(Loot l : groundBags) {
|
||||
l.addToChecksum(builder);
|
||||
}
|
||||
builder.add(currentColorFilter != null);
|
||||
if (currentColorFilter != null) builder.add(currentColorFilter);
|
||||
builder.add(lastVisitTime);
|
||||
} else {
|
||||
builder.add(false);
|
||||
}
|
||||
builder.add(visited);
|
||||
builder.add(lastSeenLayoutHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,6 +385,7 @@ public final class ResourceLoader {
|
||||
loader.prepareTileset(R.drawable.monsters_omi2, "monsters_omi2", sz8x3, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.monsters_phoenix01, "monsters_phoenix01", sz16x8, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.monsters_cats, "monsters_cats", new Size(10, 2), sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.monsters_1x2, "monsters_1x2", new Size(10, 5), sz1x2, mTileSize);
|
||||
/*INSERT_NPCS_TILESETS_HERE*/
|
||||
|
||||
loader.prepareTileset(R.drawable.map_bed_1, "map_bed_1", sz16x10, sz1x1, mTileSize);
|
||||
@@ -399,6 +400,7 @@ public final class ResourceLoader {
|
||||
loader.prepareTileset(R.drawable.map_cavewall_3, "map_cavewall_3", new Size(18, 6), sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_cavewall_4, "map_cavewall_4", new Size(18, 6), sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_cavewall_5, "map_cavewall_5", new Size(6, 6), sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_cavewall_6, "map_cavewall_6", new Size(18, 6), sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_chair_table_1, "map_chair_table_1", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_chair_table_2, "map_chair_table_2", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_crate_1, "map_crate_1", mapTileSize, sz1x1, mTileSize);
|
||||
@@ -427,6 +429,7 @@ public final class ResourceLoader {
|
||||
loader.prepareTileset(R.drawable.map_indoor_2, "map_indoor_2", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_kitchen_1, "map_kitchen_1", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_outdoor_1, "map_outdoor_1", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_outdoor_2, "map_outdoor_2", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_pillar_1, "map_pillar_1", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_pillar_2, "map_pillar_2", mapTileSize, sz1x1, mTileSize);
|
||||
loader.prepareTileset(R.drawable.map_pillar_3, "map_pillar_3", new Size(16, 10), sz1x1, mTileSize);
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.gpl.rpg.AndorsTrail.resource.DynamicTileLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.TranslationLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonCollectionParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonFieldNames;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
|
||||
public final class ActorConditionsTypeParser extends JsonCollectionParserFor<ActorConditionType> {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonArrayParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonCollectionParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonFieldNames;
|
||||
import com.gpl.rpg.AndorsTrail.util.L;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
|
||||
public final class ConversationListParser extends JsonCollectionParserFor<Phrase> {
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonArrayParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonCollectionParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonFieldNames;
|
||||
import com.gpl.rpg.AndorsTrail.util.L;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
|
||||
public final class DropListParser extends JsonCollectionParserFor<DropList> {
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.gpl.rpg.AndorsTrail.model.item.ItemCategory;
|
||||
import com.gpl.rpg.AndorsTrail.resource.TranslationLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonCollectionParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonFieldNames;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
|
||||
public final class ItemCategoryParser extends JsonCollectionParserFor<ItemCategory> {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.gpl.rpg.AndorsTrail.resource.DynamicTileLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.TranslationLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonCollectionParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonFieldNames;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
|
||||
public final class ItemTypeParser extends JsonCollectionParserFor<ItemType> {
|
||||
|
||||
@@ -41,9 +41,11 @@ public final class ItemTypeParser extends JsonCollectionParserFor<ItemType> {
|
||||
final ItemTraits_OnEquip equipEffect = itemTraitsParser.parseItemTraits_OnEquip(o.optJSONObject(JsonFieldNames.ItemType.equipEffect));
|
||||
final ItemTraits_OnUse useEffect = itemTraitsParser.parseItemTraits_OnUse(o.optJSONObject(JsonFieldNames.ItemType.useEffect));
|
||||
final ItemTraits_OnUse hitEffect = itemTraitsParser.parseItemTraits_OnUse(o.optJSONObject(JsonFieldNames.ItemType.hitEffect));
|
||||
final ItemTraits_OnUse missEffect = itemTraitsParser.parseItemTraits_OnUse(o.optJSONObject(JsonFieldNames.ItemType.missEffect));
|
||||
final ItemTraits_OnUse killEffect = itemTraitsParser.parseItemTraits_OnUse(o.optJSONObject(JsonFieldNames.ItemType.killEffect));
|
||||
final ItemTraits_OnHitReceived hitReceivedEffect = itemTraitsParser.parseItemTraits_OnHitReceived(o.optJSONObject(JsonFieldNames.ItemType.hitReceivedEffect));
|
||||
|
||||
final ItemTraits_OnHitReceived missReceivedEffect = itemTraitsParser.parseItemTraits_OnHitReceived(o.optJSONObject(JsonFieldNames.ItemType.missReceivedEffect));
|
||||
|
||||
final int baseMarketCost = o.optInt(JsonFieldNames.ItemType.baseMarketCost);
|
||||
final boolean hasManualPrice = o.optInt(JsonFieldNames.ItemType.hasManualPrice, 0) > 0;
|
||||
final ItemType itemType = new ItemType(
|
||||
@@ -58,8 +60,10 @@ public final class ItemTypeParser extends JsonCollectionParserFor<ItemType> {
|
||||
, equipEffect
|
||||
, useEffect
|
||||
, hitEffect
|
||||
, missEffect
|
||||
, killEffect
|
||||
, hitReceivedEffect
|
||||
, missReceivedEffect
|
||||
);
|
||||
return new Pair<String, ItemType>(id, itemType);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.gpl.rpg.AndorsTrail.resource.TranslationLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonCollectionParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonFieldNames;
|
||||
import com.gpl.rpg.AndorsTrail.util.ConstRange;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
import com.gpl.rpg.AndorsTrail.util.Size;
|
||||
|
||||
public final class MonsterTypeParser extends JsonCollectionParserFor<MonsterType> {
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.gpl.rpg.AndorsTrail.resource.TranslationLoader;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonArrayParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonCollectionParserFor;
|
||||
import com.gpl.rpg.AndorsTrail.resource.parsers.json.JsonFieldNames;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
|
||||
public final class QuestParser extends JsonCollectionParserFor<Quest> {
|
||||
private final TranslationLoader translationLoader;
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.gpl.rpg.AndorsTrail.model.map.WorldMapSegment.WorldMapSegmentMap;
|
||||
import com.gpl.rpg.AndorsTrail.resource.TranslationLoader;
|
||||
import com.gpl.rpg.AndorsTrail.util.Coord;
|
||||
import com.gpl.rpg.AndorsTrail.util.L;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
import com.gpl.rpg.AndorsTrail.util.XmlResourceParserUtils;
|
||||
|
||||
public final class WorldMapParser {
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.json.JSONException;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
|
||||
import com.gpl.rpg.AndorsTrail.util.L;
|
||||
import com.gpl.rpg.AndorsTrail.util.Pair;
|
||||
import android.util.Pair;
|
||||
|
||||
public abstract class JsonCollectionParserFor<T> extends JsonParserFor<Pair<String, T>> {
|
||||
public HashSet<String> parseRows(String input, HashMap<String, T> dest) {
|
||||
|
||||
@@ -167,8 +167,10 @@ public final class JsonFieldNames {
|
||||
public static final String equipEffect = "equipEffect";
|
||||
public static final String useEffect = "useEffect";
|
||||
public static final String hitEffect = "hitEffect";
|
||||
public static final String missEffect = "missEffect";
|
||||
public static final String killEffect = "killEffect";
|
||||
public static final String hitReceivedEffect = "hitReceivedEffect";
|
||||
public static final String missReceivedEffect = "missReceivedEffect";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.security.DigestException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -79,7 +80,7 @@ public final class Savegames {
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | DigestException e) {
|
||||
L.log("Error saving world: " + e.toString());
|
||||
return false;
|
||||
}
|
||||
@@ -87,7 +88,7 @@ public final class Savegames {
|
||||
|
||||
private static void writeBackup(Context androidContext, byte[] savegame, String playerId) throws IOException {
|
||||
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
|
||||
ensureDirExists(cheatDetectionFolder);
|
||||
ensureDirExists(cheatDetectionFolder);
|
||||
File backupFile = new File(cheatDetectionFolder, playerId + "X");
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(backupFile);
|
||||
fileOutputStream.write(savegame);
|
||||
@@ -108,15 +109,15 @@ public final class Savegames {
|
||||
LoadSavegameResult result = loadWorld(androidContext.getResources(), world, controllers, androidContext, fos, fh);
|
||||
fos.close();
|
||||
if (result == LoadSavegameResult.success && slot != SLOT_QUICKSAVE && !world.model.statistics.hasUnlimitedSaves()) {
|
||||
// save to the quicksave slot before deleting the file
|
||||
if (!saveWorld(world, androidContext, SLOT_QUICKSAVE)) {
|
||||
// save to the quicksave slot before deleting the file
|
||||
if (!saveWorld(world, androidContext, SLOT_QUICKSAVE)) {
|
||||
return LoadSavegameResult.unknownError;
|
||||
}
|
||||
getSlotFile(slot, androidContext).delete();
|
||||
writeCheatCheck(androidContext, DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED, fh.playerId);
|
||||
}
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | DigestException e) {
|
||||
if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) {
|
||||
L.log("Error loading world: " + e.toString());
|
||||
StringWriter sw = new StringWriter();
|
||||
@@ -128,33 +129,33 @@ public final class Savegames {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException {
|
||||
long savedVersionToCheck = 0;
|
||||
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
|
||||
ensureDirExists(cheatDetectionFolder);
|
||||
File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId);
|
||||
if (cheatDetectionFile.exists()) {
|
||||
FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile);
|
||||
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
||||
final CheatDetection cheatDetection = new CheatDetection(dataInputStream);
|
||||
savedVersionToCheck = cheatDetection.savedVersion;
|
||||
dataInputStream.close();
|
||||
fileInputStream.close();
|
||||
}
|
||||
private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException {
|
||||
long savedVersionToCheck = 0;
|
||||
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
|
||||
ensureDirExists(cheatDetectionFolder);
|
||||
File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId);
|
||||
if (cheatDetectionFile.exists()) {
|
||||
FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile);
|
||||
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
||||
final CheatDetection cheatDetection = new CheatDetection(dataInputStream);
|
||||
savedVersionToCheck = cheatDetection.savedVersion;
|
||||
dataInputStream.close();
|
||||
fileInputStream.close();
|
||||
}
|
||||
|
||||
if (savedVersionToCheck == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (androidContext.getFileStreamPath(fh.playerId).exists()) {
|
||||
FileInputStream fileInputStream = androidContext.openFileInput(fh.playerId);
|
||||
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
||||
final CheatDetection cheatDetection = new CheatDetection(dataInputStream);
|
||||
if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) {
|
||||
savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED;
|
||||
} else if (cheatDetection.savedVersion > savedVersionToCheck) {
|
||||
savedVersionToCheck = cheatDetection.savedVersion;
|
||||
}
|
||||
if (androidContext.getFileStreamPath(fh.playerId).exists()) {
|
||||
FileInputStream fileInputStream = androidContext.openFileInput(fh.playerId);
|
||||
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
||||
final CheatDetection cheatDetection = new CheatDetection(dataInputStream);
|
||||
if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) {
|
||||
savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED;
|
||||
} else if (cheatDetection.savedVersion > savedVersionToCheck) {
|
||||
savedVersionToCheck = cheatDetection.savedVersion;
|
||||
}
|
||||
|
||||
if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) {
|
||||
L.log("Internal cheatcheck file savedVersion: " + cheatDetection.savedVersion);
|
||||
@@ -167,83 +168,98 @@ public final class Savegames {
|
||||
return (savedVersionToCheck == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED || fh.savedVersion < savedVersionToCheck);
|
||||
}
|
||||
|
||||
private static FileOutputStream getOutputFile(Context androidContext, int slot) throws IOException {
|
||||
if (slot == SLOT_QUICKSAVE) {
|
||||
return androidContext.openFileOutput(Constants.FILENAME_SAVEGAME_QUICKSAVE, Context.MODE_PRIVATE);
|
||||
} else {
|
||||
ensureSavegameDirectoryExists(androidContext);
|
||||
return new FileOutputStream(getSlotFile(slot, androidContext));
|
||||
}
|
||||
}
|
||||
private static FileOutputStream getOutputFile(Context androidContext, int slot) throws IOException {
|
||||
if (slot == SLOT_QUICKSAVE) {
|
||||
return androidContext.openFileOutput(Constants.FILENAME_SAVEGAME_QUICKSAVE, Context.MODE_PRIVATE);
|
||||
} else {
|
||||
ensureSavegameDirectoryExists(androidContext);
|
||||
return new FileOutputStream(getSlotFile(slot, androidContext));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureSavegameDirectoryExists(Context context) {
|
||||
File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
|
||||
ensureDirExists(dir);
|
||||
}
|
||||
private static void ensureSavegameDirectoryExists(Context context) {
|
||||
File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
|
||||
ensureDirExists(dir);
|
||||
}
|
||||
|
||||
public static boolean ensureDirExists(File dir) {
|
||||
if (!dir.exists()) {
|
||||
boolean worked = dir.mkdir();
|
||||
return worked;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static boolean ensureDirExists(File dir) {
|
||||
if (!dir.exists()) {
|
||||
boolean worked = dir.mkdir();
|
||||
return worked;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException {
|
||||
if (slot == SLOT_QUICKSAVE) {
|
||||
return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE);
|
||||
} else {
|
||||
return new FileInputStream(getSlotFile(slot, androidContext));
|
||||
}
|
||||
}
|
||||
private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException {
|
||||
if (slot == SLOT_QUICKSAVE) {
|
||||
return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE);
|
||||
} else {
|
||||
return new FileInputStream(getSlotFile(slot, androidContext));
|
||||
}
|
||||
}
|
||||
|
||||
public static File getSlotFile(int slot, Context context) {
|
||||
File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
|
||||
return getSlotFile(slot, root);
|
||||
}
|
||||
public static File getSlotFile(int slot, Context context) {
|
||||
File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
|
||||
return getSlotFile(slot, root);
|
||||
}
|
||||
|
||||
public static File getSlotFile(int slot, File directory) {
|
||||
return new File(directory, getSlotFileName(slot));
|
||||
}
|
||||
public static File getSlotFile(int slot, File directory) {
|
||||
return new File(directory, getSlotFileName(slot));
|
||||
}
|
||||
|
||||
public static String getSlotFileName(int slot) {
|
||||
return Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot;
|
||||
}
|
||||
public static String getSlotFileName(int slot) {
|
||||
return Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot;
|
||||
}
|
||||
|
||||
|
||||
public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException {
|
||||
public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException, DigestException {
|
||||
DataOutputStream dest = new DataOutputStream(outStream);
|
||||
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.statistics.getIsAlteredSavegame());
|
||||
|
||||
byte[] checksum = world.getChecksum();
|
||||
world.model.statistics.setChecksum(checksum);
|
||||
|
||||
world.maps.writeToParcel(dest, world);
|
||||
world.model.writeToParcel(dest);
|
||||
dest.close();
|
||||
}
|
||||
|
||||
public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, Context androidContext, InputStream inState, FileHeader fh) throws IOException {
|
||||
DataInputStream src = new DataInputStream(inState);
|
||||
final FileHeader header = new FileHeader(src, fh.skipIcon);
|
||||
if (header.fileversion > AndorsTrailApplication.CURRENT_VERSION)
|
||||
return LoadSavegameResult.savegameIsFromAFutureVersion;
|
||||
public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, Context androidContext, InputStream inState, FileHeader fh) throws IOException, DigestException {
|
||||
DataInputStream src = new DataInputStream(inState);
|
||||
final FileHeader header = new FileHeader(src, fh.skipIcon);
|
||||
if (header.fileversion > AndorsTrailApplication.CURRENT_VERSION)
|
||||
return LoadSavegameResult.savegameIsFromAFutureVersion;
|
||||
|
||||
world.maps.readFromParcel(src, world, controllers, header.fileversion);
|
||||
world.model = new ModelContainer(src, world, controllers, header.fileversion);
|
||||
WorldMapController.populateWorldMap(androidContext, world, controllers.getResources());
|
||||
src.close();
|
||||
|
||||
if (header.fileversion >= 81) {
|
||||
checkChecksum(world);
|
||||
}
|
||||
WorldMapController.populateWorldMap(androidContext, world, controllers.getResources());
|
||||
|
||||
if (header.fileversion < 45) {
|
||||
LegacySavegamesContentAdaptations.adaptToNewContentForVersion45(world, controllers, res);
|
||||
}
|
||||
|
||||
|
||||
onWorldLoaded(res, world, controllers);
|
||||
|
||||
return LoadSavegameResult.success;
|
||||
}
|
||||
|
||||
private static void checkChecksum(WorldContext world) throws DigestException {
|
||||
byte[] checksum = world.getChecksum();
|
||||
if (!world.model.statistics.compareChecksum(checksum)) {
|
||||
world.model.statistics.markAsAlteredSavegame();
|
||||
}
|
||||
}
|
||||
|
||||
private static void onWorldLoaded(Resources res, WorldContext world, ControllerContext controllers) {
|
||||
controllers.actorStatsController.recalculatePlayerStats(world.model.player);
|
||||
controllers.mapController.resetMapsNotRecentlyVisited();
|
||||
@@ -268,15 +284,15 @@ public final class Savegames {
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException {
|
||||
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
|
||||
ensureDirExists(cheatDetectionFolder);
|
||||
File cheatDetectionFile = new File(cheatDetectionFolder, playerId);
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
|
||||
CheatDetection.writeToParcel(dataOutputStream, savedVersion);
|
||||
dataOutputStream.close();
|
||||
fileOutputStream.close();
|
||||
private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException {
|
||||
File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER);
|
||||
ensureDirExists(cheatDetectionFolder);
|
||||
File cheatDetectionFile = new File(cheatDetectionFolder, playerId);
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
|
||||
CheatDetection.writeToParcel(dataOutputStream, savedVersion);
|
||||
dataOutputStream.close();
|
||||
fileOutputStream.close();
|
||||
|
||||
fileOutputStream = androidContext.openFileOutput(playerId, Context.MODE_PRIVATE);
|
||||
dataOutputStream = new DataOutputStream(fileOutputStream);
|
||||
@@ -287,26 +303,26 @@ public final class Savegames {
|
||||
|
||||
private static final Pattern savegameFilenamePattern = Pattern.compile(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + "(\\d+)");
|
||||
|
||||
public static List<Integer> getUsedSavegameSlots(Context context) {
|
||||
try {
|
||||
final List<Integer> result = new ArrayList<Integer>();
|
||||
AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY).listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File f, String filename) {
|
||||
Matcher m = savegameFilenamePattern.matcher(filename);
|
||||
if (m != null && m.matches()) {
|
||||
result.add(Integer.parseInt(m.group(1)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
return new ArrayList<Integer>();
|
||||
}
|
||||
}
|
||||
public static List<Integer> getUsedSavegameSlots(Context context) {
|
||||
try {
|
||||
final List<Integer> result = new ArrayList<Integer>();
|
||||
AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY).listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File f, String filename) {
|
||||
Matcher m = savegameFilenamePattern.matcher(filename);
|
||||
if (m != null && m.matches()) {
|
||||
result.add(Integer.parseInt(m.group(1)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
return new ArrayList<Integer>();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CheatDetection {
|
||||
public final int fileversion;
|
||||
@@ -326,16 +342,17 @@ public final class Savegames {
|
||||
}
|
||||
|
||||
|
||||
public static final class FileHeader {
|
||||
public final int fileversion;
|
||||
public final String playerName;
|
||||
public final String displayInfo;
|
||||
public final int iconID;
|
||||
public boolean skipIcon = false;
|
||||
public final boolean isDead;
|
||||
public final boolean hasUnlimitedSaves;
|
||||
public final String playerId;
|
||||
public final long savedVersion;
|
||||
public static final class FileHeader {
|
||||
public final int fileversion;
|
||||
public final String playerName;
|
||||
public final String displayInfo;
|
||||
public final int iconID;
|
||||
public final boolean isAlteredSavegame;
|
||||
public boolean skipIcon = false;
|
||||
public final boolean isDead;
|
||||
public final boolean hasUnlimitedSaves;
|
||||
public final String playerId;
|
||||
public final long savedVersion;
|
||||
|
||||
public String describe() {
|
||||
return (fileversion == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION ? "(D) " : "") + playerName + ", " + displayInfo;
|
||||
@@ -344,18 +361,18 @@ public final class Savegames {
|
||||
|
||||
// ====== PARCELABLE ===================================================================
|
||||
|
||||
public FileHeader(DataInputStream src, boolean skipIcon) throws IOException {
|
||||
int fileversion = src.readInt();
|
||||
if (fileversion == 11)
|
||||
fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11.
|
||||
this.fileversion = fileversion;
|
||||
if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header.
|
||||
this.playerName = src.readUTF();
|
||||
this.displayInfo = src.readUTF();
|
||||
} else {
|
||||
this.playerName = null;
|
||||
this.displayInfo = null;
|
||||
}
|
||||
public FileHeader(DataInputStream src, boolean skipIcon) throws IOException {
|
||||
int fileversion = src.readInt();
|
||||
if (fileversion == 11)
|
||||
fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11.
|
||||
this.fileversion = fileversion;
|
||||
if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header.
|
||||
this.playerName = src.readUTF();
|
||||
this.displayInfo = src.readUTF();
|
||||
} else {
|
||||
this.playerName = null;
|
||||
this.displayInfo = null;
|
||||
}
|
||||
|
||||
if (fileversion >= 43) {
|
||||
int id = src.readInt();
|
||||
@@ -380,9 +397,14 @@ public final class Savegames {
|
||||
this.playerId = "";
|
||||
this.savedVersion = 0;
|
||||
}
|
||||
if(fileversion >= 81){
|
||||
this.isAlteredSavegame = src.readBoolean();
|
||||
}else{
|
||||
this.isAlteredSavegame = 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 isAlteredSavegame) throws IOException {
|
||||
dest.writeInt(AndorsTrailApplication.CURRENT_VERSION);
|
||||
dest.writeUTF(playerName);
|
||||
dest.writeUTF(displayInfo);
|
||||
@@ -391,6 +413,7 @@ public final class Savegames {
|
||||
dest.writeBoolean(hasUnlimitedSaves);
|
||||
dest.writeUTF(playerId);
|
||||
dest.writeLong(savedVersion);
|
||||
dest.writeBoolean(isAlteredSavegame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gpl.rpg.AndorsTrail.util;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -47,4 +49,9 @@ public final class Coord {
|
||||
dest.writeInt(x);
|
||||
dest.writeInt(y);
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(x);
|
||||
builder.add(y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.gpl.rpg.AndorsTrail.util;
|
||||
|
||||
// Should really use android.util.Pair<> instead, but it is not available for API level 4 (Android 1.6).
|
||||
public final class Pair<T1, T2> {
|
||||
public final T1 first;
|
||||
public final T2 second;
|
||||
public Pair(T1 a, T2 b) {
|
||||
this.first = a;
|
||||
this.second = b;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gpl.rpg.AndorsTrail.util;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -99,4 +101,9 @@ public final class Range {
|
||||
dest.writeInt(max);
|
||||
dest.writeInt(current);
|
||||
}
|
||||
|
||||
public void addToChecksum(ChecksumBuilder builder) {
|
||||
builder.add(max);
|
||||
builder.add(current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -76,11 +75,8 @@ public class CustomDialogFactory {
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
dialog.setContentView(R.layout.custom_dialog_title_icon);
|
||||
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||
if (((AndorsTrailApplication)context.getApplicationContext()).getPreferences().fullscreen) {
|
||||
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
dialog.getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
boolean fullscreen = ((AndorsTrailApplication) context.getApplicationContext()).getPreferences().fullscreen;
|
||||
AndorsTrailApplication.setFullscreenMode(fullscreen, dialog.getWindow());
|
||||
|
||||
setTitle(dialog, title, icon);
|
||||
|
||||
|
||||
@@ -20,13 +20,17 @@ public final class ItemEffectsView extends LinearLayout {
|
||||
private final ActorConditionEffectList itemeffect_onequip_conditions;
|
||||
private final ItemEffectsView_OnUse itemeffect_onuse;
|
||||
private final ItemEffectsView_OnUse itemeffect_onhit;
|
||||
private final ItemEffectsView_OnUse itemeffect_onmiss;
|
||||
private final ItemEffectsView_OnUse itemeffect_onkill;
|
||||
private final ItemEffectsView_OnHitReceived itemeffect_onhitreceived;
|
||||
private final ItemEffectsView_OnHitReceived itemeffect_onmissreceived;
|
||||
private final ItemEffectsView_OnDeath itemeffect_ondeath;
|
||||
private final TextView itemeffect_onuse_title;
|
||||
private final TextView itemeffect_onhit_title;
|
||||
private final TextView itemeffect_onmiss_title;
|
||||
private final TextView itemeffect_onkill_title;
|
||||
private final TextView itemeffect_onhitreceived_title;
|
||||
private final TextView itemeffect_onmissreceived_title;
|
||||
private final TextView itemeffect_ondeath_title;
|
||||
|
||||
public ItemEffectsView(Context context, AttributeSet attr) {
|
||||
@@ -41,13 +45,17 @@ public final class ItemEffectsView extends LinearLayout {
|
||||
|
||||
itemeffect_onuse = (ItemEffectsView_OnUse) findViewById(R.id.itemeffect_onuse);
|
||||
itemeffect_onhit = (ItemEffectsView_OnUse) findViewById(R.id.itemeffect_onhit);
|
||||
itemeffect_onmiss = (ItemEffectsView_OnUse) findViewById(R.id.itemeffect_onmiss);
|
||||
itemeffect_onkill = (ItemEffectsView_OnUse) findViewById(R.id.itemeffect_onkill);
|
||||
itemeffect_onhitreceived = (ItemEffectsView_OnHitReceived) findViewById(R.id.itemeffect_onhitreceived);
|
||||
itemeffect_onmissreceived = (ItemEffectsView_OnHitReceived) findViewById(R.id.itemeffect_onmissreceived);
|
||||
itemeffect_ondeath = (ItemEffectsView_OnDeath) findViewById(R.id.itemeffect_ondeath);
|
||||
itemeffect_onuse_title = (TextView) findViewById(R.id.itemeffect_onuse_title);
|
||||
itemeffect_onhit_title = (TextView) findViewById(R.id.itemeffect_onhit_title);
|
||||
itemeffect_onmiss_title = (TextView) findViewById(R.id.itemeffect_onmiss_title);
|
||||
itemeffect_onkill_title = (TextView) findViewById(R.id.itemeffect_onkill_title);
|
||||
itemeffect_onhitreceived_title = (TextView) findViewById(R.id.itemeffect_onhitreceived_title);
|
||||
itemeffect_onmissreceived_title = (TextView) findViewById(R.id.itemeffect_onmissreceived_title);
|
||||
itemeffect_ondeath_title = (TextView) findViewById(R.id.itemeffect_ondeath_title);
|
||||
}
|
||||
|
||||
@@ -55,8 +63,10 @@ public final class ItemEffectsView extends LinearLayout {
|
||||
ItemTraits_OnEquip effects_equip,
|
||||
Collection<ItemTraits_OnUse> effects_use,
|
||||
Collection<ItemTraits_OnUse> effects_hit,
|
||||
Collection<ItemTraits_OnUse> effects_miss,
|
||||
Collection<ItemTraits_OnUse> effects_kill,
|
||||
Collection<ItemTraits_OnHitReceived> effects_hitreceived,
|
||||
Collection<ItemTraits_OnHitReceived> effects_missreceived,
|
||||
ItemTraits_OnUse effects_death,
|
||||
boolean isWeapon
|
||||
) {
|
||||
@@ -90,6 +100,12 @@ public final class ItemEffectsView extends LinearLayout {
|
||||
} else {
|
||||
itemeffect_onhit_title.setVisibility(View.GONE);
|
||||
}
|
||||
itemeffect_onmiss.update(effects_miss);
|
||||
if (effects_miss != null) {
|
||||
itemeffect_onmiss_title.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
itemeffect_onmiss_title.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
itemeffect_onkill.update(effects_kill);
|
||||
if (effects_kill != null) {
|
||||
@@ -104,6 +120,12 @@ public final class ItemEffectsView extends LinearLayout {
|
||||
} else {
|
||||
itemeffect_onhitreceived_title.setVisibility(View.GONE);
|
||||
}
|
||||
itemeffect_onmissreceived.update(effects_missreceived);
|
||||
if (effects_missreceived != null) {
|
||||
itemeffect_onmissreceived_title.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
itemeffect_onmissreceived_title.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
itemeffect_ondeath.update(effects_death);
|
||||
if (effects_death != null) {
|
||||
|
||||
@@ -1,6 +1,68 @@
|
||||
I put both (release notes + forum announcement) into this source, so it will be easier to maintain them parallel:
|
||||
|
||||
|
||||
APK 81 (0.8.14)
|
||||
|
||||
Release notes
|
||||
=============
|
||||
* New quests:
|
||||
- "Darkness in the Daylight" and "Shadows" (from RaptorNXT)
|
||||
- "A familiar shadow"
|
||||
- "The exploded star"
|
||||
- "The swamp healer"
|
||||
- "Restless in the grave"
|
||||
|
||||
* 90 new maps, most of them in the new area "Mt.Galmore" south of Stoutford
|
||||
* Many minor map fixes, typos and other little things
|
||||
* Translations
|
||||
|
||||
|
||||
Forum announcement //2025-07-20
|
||||
==================
|
||||
Hello, fellow adventurers!
|
||||
|
||||
Today we have the beta of one of the largest and most highly anticipated releases for you - Mt. Galmore and its devastated wastelands. Come take the next step in our journey and one step closer to finding the elusive heartstone.
|
||||
|
||||
This project was a collaborative effort between some great members of the community and the development team as once again we got contributions from RaptorNXT on multiple quests, Kutsu was instrumental in the construction of a lot of other content and quest ideas.
|
||||
|
||||
Travel south of Stoutford and west of the Sutdover River to explore and discover three quests and continue your epic search for Andor.
|
||||
Another quest starts on the walls of Stoutford castle or in the Sullengard church - what you prefer.
|
||||
Oh, and go exploring the Crossroads Guard shack and the southern regions of the Duleian Road and you will be rewarded or punished for some of your choices made in the past.
|
||||
These quests and many little things along the way can be discovered.
|
||||
You will be surprised.
|
||||
|
||||
[list]
|
||||
6 New Quests
|
||||
[list]"Darkness in the Daylight" and "Shadows"
|
||||
(You must have completed Beer Bootlegging before as well as a few beginners quests.)[/list]
|
||||
[list]"A familiar shadow"[/list]
|
||||
[list]"The exploded star"
|
||||
(Sullengard must have been visited)[/list]
|
||||
[list]"The swamp healer"[/list]
|
||||
[list]"Restless in the grave"[/list]
|
||||
[/list]
|
||||
[list]Continue "Search for Andor" and "Lost Treasure"[/list]
|
||||
[list]Explore 90 new maps![/list]
|
||||
[list]Bug Fixes and current translations[/list]
|
||||
|
||||
|
||||
|
||||
And now try it out, enjoy the new quests! And pay attention to inconsistencies, errors or other opportunities for improvement.
|
||||
Please post your comments here in this thread or on Discord in the Beta testing channel.
|
||||
This helps us to create again a release version in good quality.
|
||||
|
||||
This download will create a separate beta APK, so that you are independent of your productive game. Your saved games will remain untouched there.
|
||||
Tip: You can export a saved game and import it into the beta (but never the other way around).
|
||||
The first start might take long, maybe even minutes. Be patient, world map files are generated - but only on first start.
|
||||
|
||||
|
||||
Here is is the link on our server: [url]https://andorstrail.com/static/AndorsTrail_v0.8.14_beta.apk[/url]
|
||||
Don't forget to give us feedback about mistakes, what is good and what you don't like.
|
||||
|
||||
Happy playing! :D
|
||||
|
||||
|
||||
|
||||
APK 80 (0.8.13)
|
||||
|
||||
Release notes
|
||||
@@ -18,7 +80,7 @@ Hey fellow adventurers - we have a new release now!
|
||||
There have been many requests for a sequel to quests in the Thieves' World. Now here it is!
|
||||
This is essentially a co-production between RaptorNXT and us. RaptorNXT created and formulated the quest log and dialogue, and we translated it into the game. It's a good division of labor, since a good story requires a surprising amount of detail work.
|
||||
|
||||
The quest starts in the Thieves' Guild. You will find it easily when you have completed all Thieves’ Guild quests up to, and including, The Ruthless Crackshot and Wanted Men.
|
||||
The quest starts in the Thieves' Guild. You will find it easily when you have completed all Thieves' Guild quests up to, and including, The Ruthless Crackshot and Wanted Men.
|
||||
|
||||
[list]A new quest "Troubling Times"[/list]
|
||||
[list]3 new maps (2 of which don't even have any connection to the new quest)[/list]
|
||||
@@ -30,6 +92,8 @@ Google Play, F-droid, Github and Itch will follow soon.
|
||||
|
||||
Note: The first start might take long, maybe even minutes. Be patient, world map files are generated. This is only on first start.
|
||||
|
||||
Nevertheless please check this beta and try if it is working as expected: [url]https://andorstrail.com/static/AndorsTrail_v0.8.12.1beta.apk[/url]
|
||||
|
||||
And now: Happy playing! :D
|
||||
|
||||
|
||||
@@ -153,7 +217,7 @@ Tip: You can export a saved game and import it into the beta (but never the othe
|
||||
|
||||
|
||||
Here is is the link on our server: [url]https://andorstrail.com/static/AndorsTrail_v0.8.12_beta.apk[/url]
|
||||
And don’t forget to give us feedback about mistakes, what is good and what you don’t like.
|
||||
And don't forget to give us feedback about mistakes, what is good and what you don't like.
|
||||
|
||||
Happy playing!
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
AndorsTrail/res/drawable/map_cavewall_6.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
AndorsTrail/res/drawable/map_outdoor_2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
AndorsTrail/res/drawable/monsters_1x2.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
@@ -5,6 +5,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/dialog_margin"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/about_root"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/actorconditioninfo_root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actorconditioninfo_title"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical" android:id="@+id/bulkselection_root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bulkselection_itemname"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/conversation_root"
|
||||
>
|
||||
|
||||
<ListView
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/worldmap_root">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -12,19 +12,19 @@
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
<com.gpl.rpg.AndorsTrail.view.AbilityModifierInfoView
|
||||
android:id="@+id/itemeffect_onequip_abilitymodifierinfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.AbilityModifierInfoView
|
||||
android:id="@+id/itemeffect_onequip_abilitymodifierinfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<com.gpl.rpg.AndorsTrail.view.ActorConditionEffectList
|
||||
android:id="@+id/itemeffect_onequip_conditions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ActorConditionEffectList
|
||||
android:id="@+id/itemeffect_onequip_conditions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:text="@string/iteminfo_effect_works_when_used"
|
||||
@@ -32,12 +32,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnUse
|
||||
android:id="@+id/itemeffect_onuse"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnUse
|
||||
android:id="@+id/itemeffect_onuse"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:text="@string/iteminfo_effect_works_when_hitting_target"
|
||||
@@ -45,12 +45,25 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnUse
|
||||
android:id="@+id/itemeffect_onhit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnUse
|
||||
android:id="@+id/itemeffect_onhit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:text="@string/iteminfo_effect_works_when_missing_target"
|
||||
android:id="@+id/itemeffect_onmiss_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnUse
|
||||
android:id="@+id/itemeffect_onmiss"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:text="@string/iteminfo_effect_works_when_killing_target"
|
||||
@@ -58,12 +71,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnUse
|
||||
android:id="@+id/itemeffect_onkill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnUse
|
||||
android:id="@+id/itemeffect_onkill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:text="@string/iteminfo_effect_works_when_hit_by_attacker"
|
||||
@@ -71,23 +84,36 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnHitReceived
|
||||
android:id="@+id/itemeffect_onhitreceived"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnHitReceived
|
||||
android:id="@+id/itemeffect_onhitreceived"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:text="@string/iteminfo_effect_works_when_missed_by_attacker"
|
||||
android:id="@+id/itemeffect_onmissreceived_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnHitReceived
|
||||
android:id="@+id/itemeffect_onmissreceived"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:text="@string/iteminfo_effect_works_when_killed_by_attacker"
|
||||
android:id="@+id/itemeffect_ondeath_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnDeath
|
||||
android:id="@+id/itemeffect_ondeath"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
<com.gpl.rpg.AndorsTrail.view.ItemEffectsView_OnDeath
|
||||
android:id="@+id/itemeffect_ondeath"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10sp"
|
||||
/>
|
||||
</merge>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/iteminfo_root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/iteminfo_title"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/dialog_margin" >
|
||||
android:padding="@dimen/dialog_margin"
|
||||
android:id="@+id/levelup_root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/levelup_title"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/dialog_margin"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/loadsave_root"
|
||||
>
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/monsterencounter_root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/monsterencounter_title"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/monsterinfo_root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/monsterinfo_title"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/skillinfo_root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/skillinfo_title"
|
||||
|
||||
@@ -348,10 +348,12 @@
|
||||
<string name="iteminfo_effect_works_on_target">On target</string>
|
||||
<string name="iteminfo_effect_works_on_attacker">On attacker</string>
|
||||
<string name="iteminfo_effect_works_when_hitting_target">When hitting target</string>
|
||||
<string name="iteminfo_effect_works_when_missing_target">When missing target</string>
|
||||
<string name="iteminfo_effect_works_when_killing_target">On every kill</string>
|
||||
<string name="iteminfo_effect_works_when_used">When used</string>
|
||||
<string name="iteminfo_effect_works_when_equipped">When equipped</string>
|
||||
<string name="iteminfo_effect_works_when_hit_by_attacker">When hit by attacker</string>
|
||||
<string name="iteminfo_effect_works_when_missed_by_attacker">When missed by attacker</string>
|
||||
<string name="iteminfo_effect_works_when_killed_by_attacker">When killed by attacker</string>
|
||||
|
||||
<string name="iteminfo_effect_decrease_current_hp">Drains %1$s HP</string>
|
||||
|
||||
258
AndorsTrail/tools/check_format_specifiers.py
Normal file
@@ -0,0 +1,258 @@
|
||||
import re
|
||||
import os
|
||||
import argparse
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
def get_string_value_and_specifiers(filepath, key_name):
|
||||
"""
|
||||
Parses an XML strings file to find a specific key and its format specifiers.
|
||||
|
||||
Args:
|
||||
filepath (str): Path to the strings.xml file.
|
||||
key_name (str): The name of the string resource to find.
|
||||
|
||||
Returns:
|
||||
tuple: (string_value, set_of_specifiers) or (None, None) if key not found.
|
||||
Specifiers are returned as a set, e.g., {"%1$s", "%2$d"}.
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
for string_tag in root.findall('string'):
|
||||
if string_tag.get('name') == key_name:
|
||||
value = string_tag.text if string_tag.text else ""
|
||||
# Regex to find format specifiers like %s, %d, %1$s, %2$d, etc.
|
||||
# It handles optional positional arguments (e.g., 1$) and type characters.
|
||||
specifiers = set(re.findall(r'%(?:(?:\d+\$)?(?:[sdfeoxXgGaAbhHc]|(?:\.\d[fd])))', value))
|
||||
return value, specifiers
|
||||
except ET.ParseError:
|
||||
print(f"Warning: Could not parse XML file: {filepath}")
|
||||
except FileNotFoundError:
|
||||
print(f"Warning: File not found: {filepath}")
|
||||
return None, None
|
||||
|
||||
def find_res_directories(project_root):
|
||||
"""
|
||||
Finds all 'res' directories within a project, typically in module roots.
|
||||
"""
|
||||
res_dirs = []
|
||||
for root_dir, dirs, _ in os.walk(project_root):
|
||||
if 'res' in dirs:
|
||||
res_dirs.append(os.path.join(root_dir, 'res'))
|
||||
if not res_dirs and os.path.basename(project_root) == 'res': # If project_root itself is a res dir
|
||||
res_dirs.append(project_root)
|
||||
return res_dirs
|
||||
|
||||
|
||||
def find_strings_files(res_dir_path, base_filename="strings.xml"):
|
||||
"""
|
||||
Finds all strings.xml files (or variants like strings-es.xml)
|
||||
within a given 'res' directory.
|
||||
"""
|
||||
strings_files = {} # lang_code -> filepath
|
||||
for dirpath, _, filenames in os.walk(res_dir_path):
|
||||
if "values" in os.path.basename(dirpath).lower(): # e.g., values, values-es, values-en-rGB
|
||||
for filename in filenames:
|
||||
if filename.startswith(os.path.splitext(base_filename)[0]) and filename.endswith(".xml"):
|
||||
full_path = os.path.join(dirpath, filename)
|
||||
# Determine language code from directory name (e.g., "values-es" -> "es")
|
||||
# or default if it's just "values"
|
||||
dir_name_parts = os.path.basename(dirpath).split('-')
|
||||
lang_code = "default" # For the base "values" folder
|
||||
if len(dir_name_parts) > 1:
|
||||
lang_code = "-".join(dir_name_parts[1:]) # Handles values-en-rUS correctly
|
||||
strings_files[lang_code] = full_path
|
||||
return strings_files
|
||||
|
||||
def find_non_escaped_percent(value):
|
||||
"""
|
||||
Finds non-escaped % characters in a string (not part of %% or a valid format specifier).
|
||||
Returns a list of indices where such % occur.
|
||||
"""
|
||||
# Find all % positions
|
||||
percent_indices = [m.start() for m in re.finditer(r'%', value)]
|
||||
# Find all valid format specifiers and %% positions
|
||||
valid_specifier_pattern = r'%(?:%|(?:\d+\$)?(?:[sdfeoxXgGaAbhHc]|(?:\.\d[fd])))'
|
||||
valid_matches = [m.span() for m in re.finditer(valid_specifier_pattern, value)]
|
||||
# Mark all indices covered by valid specifiers or %%
|
||||
covered_indices = set()
|
||||
for start, end in valid_matches:
|
||||
covered_indices.update(range(start, end))
|
||||
# Only report % indices not covered by valid specifiers or %%
|
||||
return [idx for idx in percent_indices if idx not in covered_indices]
|
||||
|
||||
def get_all_keys(filepath):
|
||||
"""
|
||||
Returns a list of all string resource keys in the given XML file.
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
return [string_tag.get('name') for string_tag in root.findall('string') if string_tag.get('name')]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def find_used_keys_in_java(project_root):
|
||||
"""
|
||||
Scans all .java files under project_root for usages of string resource keys.
|
||||
Returns a set of keys found.
|
||||
"""
|
||||
key_pattern = re.compile(r'R\.string\.([a-zA-Z0-9_]+)')
|
||||
used_keys = set()
|
||||
for root, _, files in os.walk(project_root):
|
||||
for fname in files:
|
||||
if fname.endswith('.java'):
|
||||
try:
|
||||
with open(os.path.join(root, fname), encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
used_keys.update(key_pattern.findall(content))
|
||||
except Exception:
|
||||
pass
|
||||
return used_keys
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check format specifier consistency across language files for a given string key."
|
||||
)
|
||||
parser.add_argument(
|
||||
"project_root",
|
||||
help="Path to the Android project's root directory (or a specific module's root directory)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"res_root",
|
||||
help="Path to the some 'res' directory."
|
||||
)
|
||||
parser.add_argument(
|
||||
"key_name",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="The name of the string resource to check (e.g., 'skill_longdescription_evasion'). If omitted, checks all keys in the base language file."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--base_lang",
|
||||
default="default",
|
||||
help="The language code for the base/reference strings file (e.g., 'en', 'default' for values/strings.xml). Default is 'default'."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--strings_filename",
|
||||
default="strings.xml",
|
||||
help="The base name of your strings files (default: strings.xml)."
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"Using base language: '{args.base_lang}' from file '{args.strings_filename}'")
|
||||
print(f"Project root path: {args.project_root}\n")
|
||||
print(f"Project res path: {args.res_root}\n")
|
||||
|
||||
res_directories = find_res_directories(args.res_root)
|
||||
if not res_directories:
|
||||
print(f"Error: No 'res' directory found under {args.res_root}")
|
||||
return
|
||||
|
||||
all_strings_files = {}
|
||||
for res_dir in res_directories:
|
||||
all_strings_files.update(find_strings_files(res_dir, args.strings_filename))
|
||||
|
||||
if not all_strings_files:
|
||||
print(f"Error: No '{args.strings_filename}' files found in any 'res/values-*' directories under {args.res_root}.")
|
||||
return
|
||||
|
||||
base_file_path = all_strings_files.get(args.base_lang)
|
||||
if not base_file_path:
|
||||
if args.base_lang != "default" and all_strings_files.get("default"):
|
||||
print(f"Warning: Base language '{args.base_lang}' not found. Using 'default' (values/{args.strings_filename}) as base.")
|
||||
base_file_path = all_strings_files.get("default")
|
||||
args.base_lang = "default"
|
||||
else:
|
||||
print(f"Error: Base strings file for language '{args.base_lang}' not found.")
|
||||
print(f"Available language files found: {list(all_strings_files.keys())}")
|
||||
return
|
||||
|
||||
# If no key_name is provided, check only keys used in .java files
|
||||
if args.key_name is None:
|
||||
used_keys = find_used_keys_in_java(args.project_root)
|
||||
all_keys = set(get_all_keys(base_file_path))
|
||||
keys_to_check = sorted(list(all_keys & used_keys))
|
||||
if not keys_to_check:
|
||||
print('no keys to check')
|
||||
return
|
||||
else:
|
||||
keys_to_check = [args.key_name]
|
||||
|
||||
total_issues_found = 0
|
||||
file_error_counts = {}
|
||||
file_warning_counts = {}
|
||||
|
||||
for key_name in keys_to_check:
|
||||
base_value, base_specifiers = get_string_value_and_specifiers(base_file_path, key_name)
|
||||
if base_specifiers is None:
|
||||
continue
|
||||
|
||||
for lang_code, file_path in all_strings_files.items():
|
||||
if lang_code == args.base_lang:
|
||||
continue
|
||||
|
||||
current_value, current_specifiers = get_string_value_and_specifiers(file_path, key_name)
|
||||
if current_specifiers is None:
|
||||
continue
|
||||
|
||||
error_count = 0
|
||||
warning_count = 0
|
||||
|
||||
non_escaped_percent_indices = find_non_escaped_percent(current_value)
|
||||
if non_escaped_percent_indices:
|
||||
error_count += 1
|
||||
print(f"--- Language: {lang_code} (NON-ESCAPED % FOUND) ---")
|
||||
print(f"Key: {key_name}")
|
||||
print(f"File: {file_path}")
|
||||
print(f"Value: \"{current_value}\"")
|
||||
print(f"Non-escaped % at positions: {non_escaped_percent_indices}")
|
||||
print("-" * 20)
|
||||
|
||||
if current_specifiers != base_specifiers:
|
||||
missing_in_current = base_specifiers - current_specifiers
|
||||
extra_in_current = current_specifiers - base_specifiers
|
||||
|
||||
if extra_in_current:
|
||||
error_count += 1
|
||||
print(f"--- Language: {lang_code} (ISSUE FOUND) ---")
|
||||
print(f"Key: {key_name}")
|
||||
print(f"File: {file_path}")
|
||||
print(f"Value: \"{current_value}\"")
|
||||
print(f"Specifiers: {sorted(list(current_specifiers)) if current_specifiers else 'None'}")
|
||||
print(f"Expected specifiers (from base): {sorted(list(base_specifiers)) if base_specifiers else 'None'}")
|
||||
print(f" EXTRA in '{lang_code}': {sorted(list(extra_in_current))}")
|
||||
print("-" * 20)
|
||||
if missing_in_current:
|
||||
warning_count += 1
|
||||
print(f"--- Language: {lang_code} (WARNING: MISSING SPECIFIERS) ---")
|
||||
print(f"Key: {key_name}")
|
||||
print(f"File: {file_path}")
|
||||
print(f"Value: \"{current_value}\"")
|
||||
print(f"Specifiers: {sorted(list(current_specifiers)) if current_specifiers else 'None'}")
|
||||
print(f"Expected specifiers (from base): {sorted(list(base_specifiers)) if base_specifiers else 'None'}")
|
||||
print(f" MISSING in '{lang_code}': {sorted(list(missing_in_current))}")
|
||||
print("-" * 20)
|
||||
|
||||
if error_count:
|
||||
file_error_counts[file_path] = file_error_counts.get(file_path, 0) + error_count
|
||||
if warning_count:
|
||||
file_warning_counts[file_path] = file_warning_counts.get(file_path, 0) + warning_count
|
||||
|
||||
if file_error_counts or file_warning_counts:
|
||||
print("\nSummary of errors and warnings per file:")
|
||||
for file_path in sorted(set(list(file_error_counts.keys()) + list(file_warning_counts.keys()))):
|
||||
error_str = f"{file_error_counts.get(file_path, 0)} error(s)"
|
||||
warning_str = f"{file_warning_counts.get(file_path, 0)} warning(s)"
|
||||
print(f"{file_path}:\t {error_str:>5}, {warning_str:>5}")
|
||||
|
||||
total_errors = sum(file_error_counts.values())
|
||||
total_warnings = sum(file_warning_counts.values())
|
||||
print(f"\nTOTAL: {total_errors} error(s), {total_warnings} warning(s)")
|
||||
|
||||
if file_error_counts:
|
||||
exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5
useful_links.html
Normal file
@@ -0,0 +1,5 @@
|
||||
[b][color=#0080FF]v0.8.13[/color] (Troubling Times) active on Google Play[/b]
|
||||
|
||||
[b]Useful links[/b]
|
||||
[size=80] [url=https://github.com/AndorsTrailRelease/andors-trail][b]Source code of the game[/b][/url] - [url=https://andorstrail.com/viewtopic.php?f=6&t=4560][b]Contribution guide[/b][/url] - [url=https://andorstrail.com/viewtopic.php?f=6&t=4806][b]ATCS Editor[/b][/url] - [url=https://hosted.weblate.org/projects/andors-trail/][b]Translate the game on Weblate[/b][/url] - [url=https://andorstrail.com/viewtopic.php?f=12&t=4583][b]Example walkthrough[/b][/url] - [url=https://andorstrail-directory.github.io/][b]Andor's Trail Directory[/b][/url] - [url=https://discord.gg/FgwXdy6][b]Join the Discord[/b][/url][/size]
|
||||
[size=80][b]Get the game (v0.8.13) from[/b] [url=https://andorstrail.app]Google[/url], [url=https://github.com/AndorsTrailRelease/andors-trail/releases]Github[/url], [url=https://f-droid.org/packages/com.gpl.rpg.AndorsTrail/]F-Droid[/url], [url=https://andorstrail.com/static/AndorsTrail_v0.8.13.apk]our server[/url], [b]or [/b][url=https://andorstrailteam.itch.io/andors-trail] itch.io[/url]
|
||||