diff --git a/AndorsTrail/res/layout/startscreen_newgame.xml b/AndorsTrail/res/layout/startscreen_newgame.xml
index ceef63423..9191998b8 100644
--- a/AndorsTrail/res/layout/startscreen_newgame.xml
+++ b/AndorsTrail/res/layout/startscreen_newgame.xml
@@ -80,6 +80,38 @@
android:layout_height="wrap_content"
android:inputType="textPersonName" />
+
+
+
+
+
+
+
@string/questlog_includecompleted_onlycompleted
+
+
+ - Unlimited saves and lives
+ - Unlimited lives
+ - 50 lives
+ - 10 lives
+ - 3 lives
+ - 1 life (hardcore mode)
+
+
- @string/inventory_category_all
diff --git a/AndorsTrail/res/values/strings.xml b/AndorsTrail/res/values/strings.xml
index 31f55ce7e..702e6f9ae 100644
--- a/AndorsTrail/res/values/strings.xml
+++ b/AndorsTrail/res/values/strings.xml
@@ -19,11 +19,16 @@
Load Failed
Andor\'s Trail was unable to load the savegame file.\n\n:(\n\nThe file may be damaged or incomplete.
Andor\'s Trail was unable to load the savegame file. This savegame file is created with a newer version than what is currently running.
+ Andor\'s Trail was unable to load the savegame file. This savegame file has already been continued.
Recenter
Close
More
+ Game over
+ You take your last breath and die.
+ (RIP)
+
Encounter
Do you want to attack?\nDifficulty: %1$s
Info
@@ -50,6 +55,13 @@
Experience points (XP):
Action points (AP):
Quests
+ unlimited lives and saves
+ unlimited lives
+ 1 life (hardcore mode)
+ %1$d/%2$d lives left
+ , unlimited saves
+
+
Attack (%1$d AP)
Move (%1$d AP)
@@ -143,6 +155,13 @@
Choose your hero
Enter hero name
Load
+ Mode
+ Load game
+ The current game is unsaved and you will loose your character.
+ Error loading game
+ Can\'t load from an empty slot.
+ Attention
+ Loading this game deletes its save slot. You will have to save again before switching to another game.
[You gained %1$d experience]
@@ -448,10 +467,10 @@
To level up this skill, you need at least level %1$d of the %2$s skill.
To level up this skill, you need at least experience level %1$d.
To level up this skill, you need at least %1$d %2$s (base stats).
- You may select one skill to improve.
- You may select %1$d skills to improve.
- This level also gives you a new skill point to spend!
-
+ You may select one skill to improve.
+ You may select %1$d skills to improve.
+ This level also gives you a new skill point to spend!
+
Create new savegame slot
Overwrite savegame?
This savegame contains a different player name (%1$s) than your current player name (%2$s). Are you sure you want to overwrite this savegame?
@@ -470,7 +489,9 @@
Max HP:
Max AP:
- Cannot save the game while in combat.
+ Cannot save the game while in combat.
+ Switch character
+ Saving allows you to switch to another character and later continue the current game. Do you want to save and exit the current game?
Optimized drawing
Disable this if you see graphical artifacts. Enabling this option will make the game only redraw changed parts of the screen every frame.
@@ -548,6 +569,8 @@
Are you sure you want to overwrite this savegame?
(slot %1$d)
+ %1$d.<empty>
+
Always show confirmation dialog box
Only show when overwriting a different playername
Never display confirmation dialog box
@@ -604,7 +627,7 @@
For each skill level, increases attack chance of rapiers, longswords and broadswords by %1$d %% of the item\'s base attack chance, increases block chance by %2$d %% of the item\'s base block chance, and increases critical skill by %3$d %% of the item\'s base critical skill.
For each skill level, increases attack chance of two-handed swords by %1$d %% of the item\'s base attack chance, increases block chance by %2$d %% of the item\'s base block chance, and increases critical skill by %3$d %% of the item\'s base critical skill.
For each skill level, increases attack chance of axes and greataxes by %1$d %% of the item\'s base attack chance, increases block chance by %2$d %% of the item\'s base block chance, and increases critical skill by %3$d %% of the item\'s base critical skill.
- For each skill level, increases attack chance of bludgeoning weapons by %1$d %% of the item\'s base attack chance, increases block chance by %2$d %% of the item\'s base block chance, and increases critical skill by %3$d %% of the item\'s base critical skill. This includes clubs, quarterstaves, maces, scepters, war hammers and giant hammers.
+ For each skill level, increases attack chance of bludgeoning weapons by %1$d %% of the item\'s base attack chance, increases block chance by %2$d %% of the item\'s base block chance, and increases critical skill by %3$d %% of the item\'s base critical skill. This includes clubs, quarterstaves, maces, scepters, war hammers and giant hammers.
When fighting without a weapon and shield, gain %1$d attack chance, %2$d damage potential and %3$d block chance per skill level.
Increase damage resistance by %1$d per skill level while having a shield equipped.
While fighting without having any piece of armor equipped, gain %1$d block chance per skill level. Items made of cloth are not considered as being armor.
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java
index 961e8f8d5..9c5402798 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java
@@ -25,10 +25,10 @@ public final class AndorsTrailApplication extends Application {
public static final boolean DEVELOPMENT_FORCE_CONTINUEGAME = 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 boolean DEVELOPMENT_VALIDATEDATA = true;
+ public static final boolean DEVELOPMENT_DEBUGMESSAGES = true;
public static final boolean DEVELOPMENT_INCOMPATIBLE_SAVEGAMES = DEVELOPMENT_DEBUGRESOURCES || DEVELOPMENT_DEBUGBUTTONS || DEVELOPMENT_FASTSPEED;
- public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? 999 : 47;
+ public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? 999 : 48;
public static final String CURRENT_VERSION_DISPLAY = "0.7.7";
public static final boolean IS_RELEASE_VERSION = !CURRENT_VERSION_DISPLAY.matches(".*[a-d].*");
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/Dialogs.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/Dialogs.java
index cfee5d906..216b4ad19 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/Dialogs.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/Dialogs.java
@@ -244,6 +244,25 @@ public final class Dialogs {
});
}
+ public static void showHeroDied(final MainActivity mainActivity, final ControllerContext controllers) {
+ final Dialog d = CustomDialogFactory.createDialog(mainActivity,
+ mainActivity.getResources().getString(R.string.dialog_game_over_title),
+ mainActivity.getResources().getDrawable(R.drawable.ui_icon_combat),
+ mainActivity.getResources().getString(R.string.dialog_game_over_text),
+ null,
+ true);
+
+ CustomDialogFactory.addDismissButton(d, android.R.string.ok);
+
+ showDialogAndPause(d, controllers, new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface arg0) {
+ mainActivity.finish();
+ }
+ });
+ }
+
+
public static Intent getIntentForItemInfo(final Context ctx, String itemTypeID, ItemInfoActivity.ItemInfoAction actionType, String buttonText, boolean buttonEnabled, Inventory.WearSlot inventorySlot) {
Intent intent = new Intent(ctx, ItemInfoActivity.class);
intent.putExtra("buttonText", buttonText);
@@ -324,11 +343,33 @@ public final class Dialogs {
Toast.makeText(mainActivity, R.string.menu_save_saving_not_allowed_in_combat, Toast.LENGTH_SHORT).show();
return false;
}
- controllerContext.gameRoundController.pause();
- Intent intent = new Intent(mainActivity, LoadSaveActivity.class);
- intent.setData(Uri.parse("content://com.gpl.rpg.AndorsTrail/save"));
- mainActivity.startActivityForResult(intent, MainActivity.INTENTREQUEST_SAVEGAME);
- return true;
+
+ if (!world.model.statistics.hasUnlimitedSaves()) {
+ final Dialog d = CustomDialogFactory.createDialog(mainActivity,
+ mainActivity.getResources().getString(R.string.menu_save_switch_character_title),
+ null,
+ mainActivity.getResources().getString(R.string.menu_save_switch_character),
+ null,
+ true);
+ CustomDialogFactory.addButton(d, android.R.string.ok, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ controllerContext.gameRoundController.pause();
+ Intent intent = new Intent(mainActivity, LoadSaveActivity.class);
+ intent.setData(Uri.parse("content://com.gpl.rpg.AndorsTrail/save"));
+ mainActivity.startActivityForResult(intent, MainActivity.INTENTREQUEST_SAVEGAME);
+ }
+ });
+ CustomDialogFactory.addDismissButton(d, android.R.string.cancel);
+ CustomDialogFactory.show(d);
+ return false;
+ } else {
+ controllerContext.gameRoundController.pause();
+ Intent intent = new Intent(mainActivity, LoadSaveActivity.class);
+ intent.setData(Uri.parse("content://com.gpl.rpg.AndorsTrail/save"));
+ mainActivity.startActivityForResult(intent, MainActivity.INTENTREQUEST_SAVEGAME);
+ return true;
+ }
}
public static void showLoad(final Activity currentActivity) {
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/WorldSetup.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/WorldSetup.java
index ff89272e4..4490de782 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/WorldSetup.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/WorldSetup.java
@@ -28,6 +28,8 @@ public final class WorldSetup {
public boolean isSceneReady = false;
public String newHeroName;
public int newHeroIcon;
+ public int newHeroStartLives;
+ public boolean newHeroUnlimitedSaves;
private Savegames.LoadSavegameResult loadResult;
public WorldSetup(WorldContext world, ControllerContext controllers, Context androidContext) {
@@ -149,7 +151,7 @@ public final class WorldSetup {
private void createNewWorld() {
Context ctx = androidContext.get();
- world.model = new ModelContainer();
+ world.model = new ModelContainer(newHeroStartLives, newHeroUnlimitedSaves);
world.model.player.initializeNewPlayer(world.dropLists, newHeroName, newHeroIcon);
controllers.actorStatsController.recalculatePlayerStats(world.model.player);
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java
index 89ef0591a..9145aba92 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java
@@ -108,10 +108,23 @@ public final class LoadSaveActivity extends Activity implements OnClickListener
}
private void addSavegameSlotButtons(ViewGroup parent, LayoutParams params, List usedSavegameSlots) {
+ int unused = 1;
for (int slot : usedSavegameSlots) {
final FileHeader header = Savegames.quickload(this, slot);
if (header == null) continue;
+ while (unused < slot){
+ Button b = new Button(this);
+ b.setLayoutParams(params);
+ b.setTag(unused);
+ b.setOnClickListener(this);
+ b.setText(getString(R.string.loadsave_empty_slot, unused));
+ tileManager.setImageViewTileForPlayer(getResources(), b, header.iconID);
+ parent.addView(b, params);
+ unused++;
+ }
+ unused++;
+
Button b = new Button(this);
b.setLayoutParams(params);
b.setTag(slot);
@@ -139,6 +152,7 @@ public final class LoadSaveActivity extends Activity implements OnClickListener
private String getConfirmOverwriteQuestion(int slot) {
if (isLoading) return null;
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) return null; // if we're creating a new slot
+ if (!Savegames.getSlotFile(slot).exists()) return null;
if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) {
return getString(R.string.loadsave_save_overwrite_confirmation_all);
@@ -160,42 +174,73 @@ public final class LoadSaveActivity extends Activity implements OnClickListener
@Override
public void onClick(View view) {
final int slot = (Integer) view.getTag();
- final String message = getConfirmOverwriteQuestion(slot);
- if (message != null) {
- final String title =
- getString(R.string.loadsave_save_overwrite_confirmation_title) + ' '
- + getString(R.string.loadsave_save_overwrite_confirmation_slot, slot);
-// new AlertDialog.Builder(this)
-// .setIcon(android.R.drawable.ic_dialog_alert)
-// .setTitle(title)
-// .setMessage(message)
-// .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
-// @Override
-// public void onClick(DialogInterface dialog, int which) {
-// loadsave(slot);
-// }
-// })
-// .setNegativeButton(android.R.string.no, null)
-// .show();
- final Dialog d = CustomDialogFactory.createDialog(this,
- title,
- getResources().getDrawable(android.R.drawable.ic_dialog_alert),
- message,
- null,
- true);
-
- CustomDialogFactory.addButton(d, android.R.string.yes, new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- loadsave(slot);
- }
- });
- CustomDialogFactory.addDismissButton(d, android.R.string.no);
-
- CustomDialogFactory.show(d);
+ if (isLoading) {
+ if(!Savegames.getSlotFile(slot).exists()) {
+ showErrorLoadingEmptySlot();
+ } else {
+ final FileHeader header = Savegames.quickload(this, slot);
+ if (header != null && !header.hasUnlimitedSaves) {
+ showSlotGetsDeletedOnLoadWarning(slot);
+ } else {
+ loadsave(slot);
+ }
+ }
} else {
- loadsave(slot);
+ final String message = getConfirmOverwriteQuestion(slot);
+ if (message != null) {
+ showConfirmoverwriteQuestion(slot, message);
+ } else {
+ loadsave(slot);
+ }
}
}
+
+ private void showErrorLoadingEmptySlot() {
+ final Dialog d = CustomDialogFactory.createDialog(this,
+ getString(R.string.startscreen_error_loading_game),
+ getResources().getDrawable(android.R.drawable.ic_dialog_alert),
+ getString(R.string.startscreen_error_loading_empty_slot),
+ null,
+ true);
+ CustomDialogFactory.addDismissButton(d, android.R.string.ok);
+ CustomDialogFactory.show(d);
+ }
+
+ private void showSlotGetsDeletedOnLoadWarning(int slot) {
+ final Dialog d = CustomDialogFactory.createDialog(this,
+ getString(R.string.startscreen_attention_slot_gets_delete_on_load),
+ getResources().getDrawable(android.R.drawable.ic_dialog_alert),
+ getString(R.string.startscreen_attention_message_slot_gets_delete_on_load),
+ null,
+ true);
+ CustomDialogFactory.addButton(d, android.R.string.ok, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ loadsave(slot);
+ }
+ });
+ CustomDialogFactory.show(d);
+ }
+
+ private void showConfirmoverwriteQuestion(int slot, String message) {
+ final String title =
+ getString(R.string.loadsave_save_overwrite_confirmation_title) + ' '
+ + getString(R.string.loadsave_save_overwrite_confirmation_slot, slot);
+ final Dialog d = CustomDialogFactory.createDialog(this,
+ title,
+ getResources().getDrawable(android.R.drawable.ic_dialog_alert),
+ message,
+ null,
+ true);
+
+ CustomDialogFactory.addButton(d, android.R.string.yes, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ loadsave(slot);
+ }
+ });
+ CustomDialogFactory.addDismissButton(d, android.R.string.no);
+ CustomDialogFactory.show(d);
+ }
}
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java
index c0d36bae2..ab0df06f2 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java
@@ -28,7 +28,7 @@ public final class LoadingActivity extends Activity implements OnResourcesLoaded
private Dialog progressDialog;
private CloudsAnimatorView clouds_back, clouds_mid, clouds_front;
boolean loaded = false;
-
+
private Object semaphore = new Object();
@Override
@@ -158,6 +158,8 @@ public final class LoadingActivity extends Activity implements OnResourcesLoaded
}
if (loadResult == Savegames.LoadSavegameResult.savegameIsFromAFutureVersion) {
showLoadingFailedDialog(R.string.dialog_loading_failed_incorrectversion);
+ } else if (loadResult == Savegames.LoadSavegameResult.cheatingDetected) {
+ showLoadingFailedDialog(R.string.dialog_loading_failed_cheat);
} else {
showLoadingFailedDialog(R.string.dialog_loading_failed_message);
}
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java
index 38b812119..9e0fbbd25 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java
@@ -149,6 +149,9 @@ public final class MainActivity
final int slot = data.getIntExtra("slot", 1);
if (save(slot)) {
Toast.makeText(this, getResources().getString(R.string.menu_save_gamesaved, slot), Toast.LENGTH_SHORT).show();
+ if (!world.model.statistics.hasUnlimitedSaves()) {
+ finish();
+ }
} else {
Toast.makeText(this, R.string.menu_save_failed, Toast.LENGTH_LONG).show();
}
@@ -188,9 +191,11 @@ public final class MainActivity
super.onResume();
if (!AndorsTrailApplication.getApplicationFromActivity(this).getWorldSetup().isSceneReady) return;
- controllers.gameRoundController.resume();
-
- updateStatus();
+ if (world.model.statistics.isDead()) this.finish();
+ else {
+ controllers.gameRoundController.resume();
+ updateStatus();
+ }
}
private void unsubscribeFromModel() {
@@ -452,7 +457,11 @@ public final class MainActivity
@Override
public void onPlayerDied(int lostExp) {
- message(getString(R.string.combat_hero_dies, lostExp));
+ if (!world.model.statistics.isDead()) {
+ message(getString(R.string.combat_hero_dies, lostExp));
+ } else {
+ Dialogs.showHeroDied(this, controllers);
+ }
}
@Override
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/HeroinfoActivity_Stats.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/HeroinfoActivity_Stats.java
index 3068e181f..cb811e214 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/HeroinfoActivity_Stats.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/HeroinfoActivity_Stats.java
@@ -19,6 +19,7 @@ import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
import com.gpl.rpg.AndorsTrail.Dialogs;
import com.gpl.rpg.AndorsTrail.R;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
+import com.gpl.rpg.AndorsTrail.model.GameStatistics;
import com.gpl.rpg.AndorsTrail.model.actor.HeroCollection;
import com.gpl.rpg.AndorsTrail.model.actor.Player;
import com.gpl.rpg.AndorsTrail.model.item.Inventory;
@@ -35,6 +36,7 @@ public final class HeroinfoActivity_Stats extends Fragment {
private WorldContext world;
private Player player;
+ private GameStatistics statistics;
private View view;
private Button levelUpButton;
@@ -53,6 +55,7 @@ public final class HeroinfoActivity_Stats extends Fragment {
private TableLayout heroinfo_basestats_table;
private ViewGroup heroinfo_container;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -60,6 +63,7 @@ public final class HeroinfoActivity_Stats extends Fragment {
if (!app.isInitialized()) return;
this.world = app.getWorld();
this.player = world.model.player;
+ this.statistics = world.model.statistics;
}
@Override
@@ -72,7 +76,23 @@ public final class HeroinfoActivity_Stats extends Fragment {
TextView tv = (TextView) v.findViewById(R.id.heroinfo_title);
if (tv != null) {
- tv.setText(player.getName());
+ String description = player.getName() + "\n";
+ final Resources res = getResources();
+ if (statistics.hasUnlimitedLives() && statistics.hasUnlimitedSaves()) {
+ description += res.getString(R.string.heroinfo_unlimited_lives_and_saves);
+ } else if (statistics.hasUnlimitedLives()) {
+ description += res.getString(R.string.heroinfo_unlimited_lives);
+ } else {
+ if (statistics.getStartLives() == 1 && !statistics.hasUnlimitedSaves()) {
+ description += res.getString(R.string.heroinfo_hardcore_mode);
+ } else {
+ description += res.getString(R.string.heroinfo_lives_left, statistics.getLivesLeft(), statistics.getStartLives());
+ if (statistics.hasUnlimitedSaves()) {
+ description += res.getString(R.string.heroinfo_unlimited_saves);
+ }
+ }
+ }
+ tv.setText(description);
tv.setCompoundDrawablesWithIntrinsicBounds(HeroCollection.getHeroLargeSprite(player.iconID), 0, 0, 0);
}
heroinfo_container = (ViewGroup) v.findViewById(R.id.heroinfo_container);
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java
index fbe85cc04..72761c541 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java
@@ -106,7 +106,27 @@ public class StartScreenActivity_MainMenu extends Fragment {
startscreen_load.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
- Dialogs.showLoad(StartScreenActivity_MainMenu.this);
+ AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(getActivity());
+ if (hasExistingGame && app != null && app.getWorld() != null && app.getWorld().model != null
+ && app.getWorld().model.statistics != null && !app.getWorld().model.statistics.hasUnlimitedSaves()) {
+ final Dialog d = CustomDialogFactory.createDialog(getActivity(),
+ getString(R.string.startscreen_load_game),
+ getResources().getDrawable(android.R.drawable.ic_delete),
+ getString(R.string.startscreen_load_game_confirm),
+ null,
+ true);
+ CustomDialogFactory.addButton(d, android.R.string.ok, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Dialogs.showLoad(StartScreenActivity_MainMenu.this);
+ }
+ });
+ CustomDialogFactory.addDismissButton(d, android.R.string.cancel);
+ CustomDialogFactory.show(d);
+
+ } else {
+ Dialogs.showLoad(StartScreenActivity_MainMenu.this);
+ }
}
});
@@ -131,12 +151,14 @@ public class StartScreenActivity_MainMenu extends Fragment {
String playerName;
String displayInfo = null;
int iconID = TileManager.CHAR_HERO;
+ boolean isDead = false;
FileHeader header = Savegames.quickload(getActivity(), Savegames.SLOT_QUICKSAVE);
if (header != null && header.playerName != null) {
playerName = header.playerName;
displayInfo = header.displayInfo;
iconID = header.iconID;
+ isDead = header.isDead;
} else {
// Before fileversion 14 (v0.6.7), quicksave was stored in Shared preferences
SharedPreferences p = getActivity().getSharedPreferences("quicksave", Activity.MODE_PRIVATE);
@@ -146,7 +168,7 @@ public class StartScreenActivity_MainMenu extends Fragment {
}
}
hasExistingGame = (playerName != null);
- setButtonState(playerName, displayInfo, iconID);
+ setButtonState(playerName, displayInfo, iconID, isDead);
if (isNewVersion()) {
Dialogs.showNewVersion(getActivity());
@@ -168,13 +190,13 @@ public class StartScreenActivity_MainMenu extends Fragment {
listener = null;
}
- private void setButtonState(final String playerName, final String displayInfo, int iconID) {
- startscreen_continue.setEnabled(hasExistingGame);
+ private void setButtonState(final String playerName, final String displayInfo, int iconID, boolean isDead) {
+ startscreen_continue.setEnabled(hasExistingGame && !isDead);
startscreen_newgame.setEnabled(true);
if (hasExistingGame) {
TileManager tm = AndorsTrailApplication.getApplicationFromActivity(getActivity()).getWorld().tileManager;
tm.setImageViewTileForPlayer(getResources(), save_preview_hero_icon, iconID);
- save_preview_hero_desc.setText(playerName + ", " + displayInfo);
+ save_preview_hero_desc.setText((isDead ? getString(R.string.rip_startscreen) : "") + playerName + ", " + displayInfo);
save_preview_holder.setVisibility(View.VISIBLE);
} else {
save_preview_holder.setVisibility(View.GONE);
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_NewGame.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_NewGame.java
index e01aa06d1..a401572bc 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_NewGame.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_NewGame.java
@@ -20,12 +20,15 @@ import com.gpl.rpg.AndorsTrail.R;
import com.gpl.rpg.AndorsTrail.WorldSetup;
import com.gpl.rpg.AndorsTrail.activity.LoadingActivity;
import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager;
+import com.gpl.rpg.AndorsTrail.view.SpinnerEmulator;
public class StartScreenActivity_NewGame extends Fragment {
private TextView startscreen_enterheroname;
private int selectedIconID = TileManager.CHAR_HERO;
+ private int startLives = -1;
+ private boolean unlimitedSaves = true;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -39,7 +42,40 @@ public class StartScreenActivity_NewGame extends Fragment {
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) {
+ @Override
+ public void setValue(int value) {
+ if (value == 0) {
+ startLives = -1;
+ unlimitedSaves = true;
+ } else {
+ unlimitedSaves = false;
+ if (value == 1) {
+ startLives = -1;
+ } else if (value == 2) {
+ startLives = 50;
+ } else if (value == 3) {
+ startLives = 10;
+ } else if (value == 4) {
+ startLives = 3;
+ } else {
+ startLives = 1;
+ }
+ }
+ }
+
+ @Override
+ public void selectionChanged(int value) {
+
+ }
+
+ @Override
+ public int getValue() {
+ return 0;
+ }
+ };
+
final RadioGroup group = (RadioGroup) root.findViewById(R.id.newgame_spritegroup);
group.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@@ -114,6 +150,8 @@ public class StartScreenActivity_NewGame extends Fragment {
setup.loadFromSlot = loadFromSlot;
setup.newHeroName = name;
setup.newHeroIcon = selectedIconID;
+ setup.newHeroStartLives = startLives;
+ setup.newHeroUnlimitedSaves = unlimitedSaves;
gameCreationOver();
startActivity(new Intent(getActivity(), LoadingActivity.class));
}
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/Constants.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/Constants.java
index cf214f2c4..b3871a997 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/Constants.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/Constants.java
@@ -47,6 +47,7 @@ public final class Constants {
public static final String FILENAME_WORLDMAP_HTMLFILE_SUFFIX = ".html";
public static final String FILENAME_SAVEGAME_FILENAME_PREFIX = "savegame";
public static final String PLACEHOLDER_PLAYERNAME = "$playername";
+ public static final String CHEAT_DETECTION_FOLDER = "dEAGyGE3YojqXjI3x4x7";
public static final Random rnd = new Random();
public static int rollValue(final ConstRange r) { return rollValue(r.max, r.current); }
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java
index 2ddeb1a09..47ea75a32 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java
@@ -129,8 +129,11 @@ public final class MapController {
if (lostExp < 0) lostExp = 0;
controllers.actorStatsController.addExperience(-lostExp);
world.model.statistics.addPlayerDeath(lostExp);
- controllers.movementController.respawnPlayerAsync();
- lotsOfTimePassed();
+
+ if (!world.model.statistics.isDead()) {
+ controllers.movementController.respawnPlayerAsync();
+ lotsOfTimePassed();
+ }
worldEventListeners.onPlayerDied(lostExp);
}
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/GameStatistics.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/GameStatistics.java
index fa2605ecb..07e404e18 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/GameStatistics.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/GameStatistics.java
@@ -25,8 +25,14 @@ public final class GameStatistics {
private final HashMap killedMonsters = new HashMap();
private final HashMap usedItems = new HashMap();
private int spentGold = 0;
+ private boolean unlimitedSaves = true;
+ private int startLives = -1; // -1 --> unlimited
+
+ public GameStatistics(boolean unlimitedSaves, int startLives) {
+ this.unlimitedSaves = unlimitedSaves;
+ this.startLives = startLives;
+ }
- public GameStatistics() { }
public void addMonsterKill(String monsterTypeID) {
if (!killedMonsters.containsKey(monsterTypeID)) killedMonsters.put(monsterTypeID, 1);
else killedMonsters.put(monsterTypeID, killedMonsters.get(monsterTypeID) + 1);
@@ -51,6 +57,16 @@ public final class GameStatistics {
return spentGold;
}
+ public boolean hasUnlimitedSaves() { return unlimitedSaves; }
+
+ public boolean hasUnlimitedLives() { return startLives == -1; }
+
+ public int getStartLives() { return startLives; }
+
+ public int getLivesLeft() { return hasUnlimitedLives() ? -1 : startLives - deaths; }
+
+ public boolean isDead() { return !hasUnlimitedLives() && getLivesLeft() < 1; }
+
public int getNumberOfKillsForMonsterType(String monsterTypeID) {
Integer v = killedMonsters.get(monsterTypeID);
if (v == null) return 0;
@@ -167,6 +183,11 @@ public final class GameStatistics {
this.usedItems.put(name, value);
}
this.spentGold = src.readInt();
+
+ if (fileversion < 48) return;
+
+ this.startLives = src.readInt();
+ this.unlimitedSaves = src.readBoolean();
}
public void writeToParcel(DataOutputStream dest) throws IOException {
@@ -184,5 +205,7 @@ public final class GameStatistics {
dest.writeInt(e.getValue());
}
dest.writeInt(spentGold);
+ dest.writeInt(startLives);
+ dest.writeBoolean(unlimitedSaves);
}
}
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/ModelContainer.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/ModelContainer.java
index 628776afc..4a768bf80 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/ModelContainer.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/ModelContainer.java
@@ -20,10 +20,10 @@ public final class ModelContainer {
public PredefinedMap currentMap;
public LayeredTileMap currentTileMap;
- public ModelContainer() {
+ public ModelContainer(int startLives, boolean unlimitedSaves) {
player = new Player();
uiSelections = new InterfaceData();
- statistics = new GameStatistics();
+ statistics = new GameStatistics(unlimitedSaves, startLives);
worldData = new WorldData();
}
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/actor/Player.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/actor/Player.java
index b8a466634..db1426860 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/actor/Player.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/actor/Player.java
@@ -6,6 +6,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
+import java.util.UUID;
import android.util.SparseIntArray;
@@ -44,6 +45,8 @@ public final class Player extends Actor {
private String spawnMap;
private String spawnPlace;
private final HashMap alignments = new HashMap();
+ public String id = UUID.randomUUID().toString();
+ public long savedVersion = 1; // the version get's increased for cheat detection everytime a player with limited saves is saved
// Unequipped stats
public static final class PlayerBaseTraits {
@@ -365,6 +368,11 @@ public final class Player extends Actor {
this.alignments.put(faction, alignment);
}
}
+
+ if (fileversion >= 48) {
+ this.id = src.readUTF();
+ this.savedVersion = src.readLong();
+ }
}
public void writeToParcel(DataOutputStream dest) throws IOException {
@@ -421,6 +429,8 @@ public final class Player extends Actor {
dest.writeUTF(e.getKey());
dest.writeInt(e.getValue());
}
+ dest.writeUTF(id);
+ dest.writeLong(savedVersion);
}
}
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/savegames/Savegames.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/savegames/Savegames.java
index b4e5e1619..6111f66b9 100644
--- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/savegames/Savegames.java
+++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/savegames/Savegames.java
@@ -5,6 +5,7 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
@@ -21,6 +22,7 @@ import java.util.regex.Pattern;
import android.content.Context;
import android.content.res.Resources;
import android.os.Environment;
+import android.os.SystemClock;
import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
@@ -32,15 +34,25 @@ import com.gpl.rpg.AndorsTrail.util.L;
public final class Savegames {
public static final int SLOT_QUICKSAVE = 0;
+ public static final long DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED = -1;
+
+ private static long lastBackup = 0;
public static enum LoadSavegameResult {
success
, unknownError
, savegameIsFromAFutureVersion
+ , cheatingDetected
}
public static boolean saveWorld(WorldContext world, Context androidContext, int slot, String displayInfo) {
try {
+ if (slot != SLOT_QUICKSAVE && !world.model.statistics.hasUnlimitedSaves()) {
+ world.model.player.savedVersion++;
+ }
+ String id = world.model.player.id;
+ long savedVersion = world.model.player.savedVersion;
+
// Create the savegame in a temporary memorystream first to ensure that the savegame can
// be created correctly. We don't want to trash the user's file unneccessarily if there is an error.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -51,18 +63,51 @@ public final class Savegames {
FileOutputStream fos = getOutputFile(androidContext, slot);
fos.write(savegame);
fos.close();
+
+ if (!world.model.statistics.hasUnlimitedSaves()) {
+ if (slot != SLOT_QUICKSAVE) {
+ androidContext.deleteFile(Constants.FILENAME_SAVEGAME_QUICKSAVE);
+ writeCheatCheck(androidContext, savedVersion, id);
+ } else if (SystemClock.uptimeMillis() > lastBackup + 120000) {
+ writeBackup(savegame, id);
+ lastBackup = SystemClock.uptimeMillis();
+ }
+ }
+
return true;
} catch (IOException e) {
L.log("Error saving world: " + e.toString());
return false;
}
}
+
+ private static void writeBackup(byte[] savegame, String playerId) throws IOException {
+ File root = Environment.getExternalStorageDirectory();
+ File cheatDetectionFolder = new File(root, Constants.CHEAT_DETECTION_FOLDER);
+ if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir();
+ File backupFile = new File(cheatDetectionFolder, playerId + "X");
+ FileOutputStream fileOutputStream = new FileOutputStream(backupFile);
+ fileOutputStream.write(savegame);
+ fileOutputStream.close();
+ }
+
public static LoadSavegameResult loadWorld(WorldContext world, ControllerContext controllers, Context androidContext, int slot) {
try {
FileHeader fh = quickload(androidContext, slot);
+ if(fh == null) {
+ return LoadSavegameResult.unknownError;
+ }
+ if (!fh.hasUnlimitedSaves && slot != SLOT_QUICKSAVE && triedToCheat(androidContext, fh)) {
+ return LoadSavegameResult.cheatingDetected;
+ }
+
FileInputStream fos = getInputFile(androidContext, slot);
LoadSavegameResult result = loadWorld(androidContext.getResources(), world, controllers, fos, fh);
fos.close();
+ if (result == LoadSavegameResult.success && slot != SLOT_QUICKSAVE && !world.model.statistics.hasUnlimitedSaves()) {
+ getSlotFile(slot).delete();
+ writeCheatCheck(androidContext, DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED, fh.playerId);
+ }
return result;
} catch (IOException e) {
if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) {
@@ -76,6 +121,47 @@ public final class Savegames {
}
}
+ private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException {
+ long savedVersionToCheck = 0;
+ File root = Environment.getExternalStorageDirectory();
+ File cheatDetectionFolder = new File(root, Constants.CHEAT_DETECTION_FOLDER);
+ if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir();
+ 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 (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) {
+ L.log("Internal cheatcheck file savedVersion: " + cheatDetection.savedVersion);
+ }
+
+ dataInputStream.close();
+ fileInputStream.close();
+ }
+
+ 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);
@@ -96,7 +182,8 @@ public final class Savegames {
return new FileInputStream(getSlotFile(slot));
}
}
- private static File getSlotFile(int slot) {
+
+ public static File getSlotFile(int slot) {
File root = getSavegameDirectory();
return new File(root, Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot);
}
@@ -108,7 +195,12 @@ public final class Savegames {
public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException {
DataOutputStream dest = new DataOutputStream(outStream);
- FileHeader.writeToParcel(dest, world.model.player.getName(), displayInfo, world.model.player.iconID);
+ 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.maps.writeToParcel(dest, world);
world.model.writeToParcel(dest);
dest.close();
@@ -156,6 +248,24 @@ public final class Savegames {
}
}
+ private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException {
+ File root = Environment.getExternalStorageDirectory();
+ File cheatDetectionFolder = new File(root, Constants.CHEAT_DETECTION_FOLDER);
+ if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir();
+ 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);
+ CheatDetection.writeToParcel(dataOutputStream, savedVersion);
+ dataOutputStream.close();
+ fileOutputStream.close();
+ }
+
private static final Pattern savegameFilenamePattern = Pattern.compile(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + "(\\d+)");
public static List getUsedSavegameSlots() {
try {
@@ -178,13 +288,36 @@ public final class Savegames {
}
}
+ private static final class CheatDetection {
+ public final int fileversion;
+ public final long savedVersion;
+
+ // ====== PARCELABLE ===================================================================
+
+ public CheatDetection(DataInputStream src) throws IOException {
+ this.fileversion = src.readInt();
+ this.savedVersion = src.readLong();
+ }
+
+ public static void writeToParcel(DataOutputStream dest, long savedVersion) throws IOException {
+ dest.writeInt(AndorsTrailApplication.CURRENT_VERSION);
+ dest.writeLong(savedVersion);
+ }
+ }
+
+
+
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 String describe() {
return playerName + ", " + displayInfo;
}
@@ -203,9 +336,10 @@ public final class Savegames {
this.playerName = null;
this.displayInfo = null;
}
- if (fileversion >= 43 && !skipIcon) {
+
+ if (fileversion >= 43) {
int id = src.readInt();
- if (id > TileManager.LAST_HERO) {
+ if (skipIcon || id > TileManager.LAST_HERO) {
this.iconID = TileManager.CHAR_HERO_0;
this.skipIcon = true;
} else {
@@ -214,13 +348,29 @@ public final class Savegames {
} else {
this.iconID = TileManager.CHAR_HERO_0;
}
+
+ if (fileversion >= 48) {
+ this.isDead = src.readBoolean();
+ this.hasUnlimitedSaves = src.readBoolean();
+ this.playerId = src.readUTF();
+ this.savedVersion = src.readLong();
+ } else {
+ this.isDead = false;
+ this.hasUnlimitedSaves = true;
+ this.playerId = "";
+ this.savedVersion = 0;
+ }
}
- public static void writeToParcel(DataOutputStream dest, String playerName, String displayInfo, int iconID) throws IOException {
+ public static void writeToParcel(DataOutputStream dest, String playerName, String displayInfo, int iconID, boolean isDead, boolean hasUnlimitedSaves, String playerId, long savedVersion) throws IOException {
dest.writeInt(AndorsTrailApplication.CURRENT_VERSION);
dest.writeUTF(playerName);
dest.writeUTF(displayInfo);
dest.writeInt(iconID);
+ dest.writeBoolean(isDead);
+ dest.writeBoolean(hasUnlimitedSaves);
+ dest.writeUTF(playerId);
+ dest.writeLong(savedVersion);
}
}
}