Skip to content

Commit

Permalink
Merge pull request #96 from jonathan-robertson/dev
Browse files Browse the repository at this point in the history
Fix Critical Issue with Respec
  • Loading branch information
jonathan-robertson authored Jul 8, 2023
2 parents 5f25386 + a5ab1a8 commit 4658eb3
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 56 deletions.
Binary file modified Amnesia.dll
Binary file not shown.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.3] - 2023-07-08

- add mechanic to detect/fix too many skill points
- fix purchase log to clarify total & change
- fix respec to no longer give extra skill points
- update support invite link

## [2.0.2] - 2023-07-04

- improve logging, simplify logic for purchase
Expand Down
9 changes: 9 additions & 0 deletions Config/XUi/windows.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<config>
<!-- dialog shop -->
<append xpath="/windows">
<window name="amnesiaDialogShopCannotAfford" width="603" height="363" pos="-300, 150" controller="TipWindow" anchor="CenterCenter" pivot="center" depth="15" cursor_area="true">
<tip_header text="Memory Services" />
Expand All @@ -17,4 +18,12 @@
<tip_body text="Your therapy is now complete and [00ff80]your skill points can be reassigned[-]. Please come again!" />
</window>
</append>

<!-- skill point integrity check -->
<append xpath="/windows">
<window name="amnesiaSkillPointIntegrityCheckReduced" width="603" height="363" pos="-300, 150" controller="TipWindow" anchor="CenterCenter" pivot="center" depth="15" cursor_area="true">
<tip_header text="Amnesia Skill Point Integrity Check" />
<tip_body text="You were found to have too many skill points due to a bug in a previous version of the Amnesia Mod; [decea3]these skill points have been removed, along with any skills purchased with these points[-]. Please reach out to the admin if you have any questions." />
</window>
</append>
</config>
3 changes: 3 additions & 0 deletions Config/XUi/xui.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
<window_group name="amnesiaDialogShopTherapyCompleteWindowGroup">
<window name="amnesiaDialogShopTherapyComplete" />
</window_group>
<window_group name="amnesiaSkillPointIntegrityCheckReducedWindowGroup">
<window name="amnesiaSkillPointIntegrityCheckReduced" />
</window_group>
</append>
</config>
12 changes: 12 additions & 0 deletions Config/buffs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@
<triggered_effect trigger="onSelfEnteredGame" action="RemoveBuff" buff="buffAmnesiaRequestChangeCallback" />
</effect_group>
</buff>

<buff name="buffAmnesiaRespec" hidden="true" remove_on_death="false">
<stack_type value="ignore" />
<duration value="2" />
<effect_group tiered="false" name="respec">
<triggered_effect trigger="onSelfBuffStart" action="ResetProgression" reset_skills="true" />
<triggered_effect trigger="onSelfBuffStart" action="ModifyStats" stat="Health" operation="subtract" value="1" />
<triggered_effect trigger="onSelfBuffStart" action="ModifyStats" stat="Health" operation="add" value="1" />
<triggered_effect trigger="onSelfBuffStart" action="AddBuff" buff="buffPerkAbilityUpdate" />
<triggered_effect trigger="onSelfBuffStart" action="PlaySound" sound="twitch_timechange" play_in_head="true" />
</effect_group>
</buff>
</append>
<!-- TODO: explore how onCombatEntered works: perhaps a screen effect could be added when on last life onCombatEntered as a reminder -->
</config>
2 changes: 1 addition & 1 deletion ModInfo.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<xml>
<Name value="kanaverum-amnesia" />
<DisplayName value="Amnesia" />
<Version value="2.0.2" />
<Version value="2.0.3" />
<Description value="Reset player progress after dying under a configurable set of circumstances" />
<Author value="Jonathan Robertson (Kanaverum)" />
<Website value="https://github.com/jonathan-robertson/amnesia" />
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ As you may have guessed, this mod adds a RogueLike element to 7 Days to Die in a
### Support

🗪 If you would like support for this mod, please feel free to reach out via [Discord](https://discord.gg/tRJHSB9Uk7).
🗪 If you would like support for this mod, please feel free to reach out via [Discord](https://discord.gg/hYa2sNHXya).

## Features

Expand Down
36 changes: 19 additions & 17 deletions src/Data/PlayerRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,26 @@ internal class PlayerRecord
public int Level { get; private set; } = 0;
public List<(string, int)> Changes { get; private set; } = new List<(string, int)>();

public static void Load(ClientInfo clientInfo)
public static bool TryLoad(ClientInfo clientInfo, out PlayerRecord playerRecord, EntityPlayer player = null)
{
var entityId = ClientInfoHelper.SafelyGetEntityIdFor(clientInfo);
var userIdentifier = ClientInfoHelper.GetUserIdentifier(clientInfo);
var playerShortReference = $"{entityId} / {userIdentifier.CombinedString}";
if (Entries.ContainsKey(entityId))
{
_log.Error($"Player Record is already loaded for player {entityId} / {userIdentifier.CombinedString} and this is NOT expected.");
return;
_log.Error($"Player Record is already loaded for player {playerShortReference} and this is NOT expected.");
playerRecord = default;
return false;
}
if (!GameManager.Instance.World.Players.dict.TryGetValue(entityId, out var player))
if (player == null && !GameManager.Instance.World.Players.dict.TryGetValue(entityId, out player))
{
_log.Error($"Could not find player at {entityId} / {userIdentifier.CombinedString} even though one is logging in with this info.");
return;
_log.Error($"Could not find player at {playerShortReference} even though one is logging in with this info.");
playerRecord = default;
return false;
}
var playerRecord = new PlayerRecord(entityId, userIdentifier, player.Progression.Level, player.Progression.SkillPoints);
var playerLongReference = $"{entityId} ({player.GetDebugName()} | {userIdentifier.CombinedString})";

playerRecord = new PlayerRecord(entityId, userIdentifier, player.Progression.Level, player.Progression.SkillPoints);
var filename = Path.Combine(GameIO.GetPlayerDataDir(), $"{userIdentifier}.apr");
try
{
Expand All @@ -55,30 +60,31 @@ public static void Load(ClientInfo clientInfo)
var level = int.Parse(changes[i].Attributes[LEVEL].Value);
playerRecord.Changes.Add((name, level));
}
_log.Info($"Successfully loaded {filename}");
_log.Info($"Successfully loaded record data for player {playerLongReference} from {filename}");
}
catch (FileNotFoundException)
{
_log.Info($"No player record file found for player {entityId}; creating a new one with defaults under {filename}");
_log.Info($"No player record file found for player {playerLongReference} at {filename}; creating a new one with defaults under {filename}");
playerRecord.Save();
}
catch (Exception e)
{
_log.Error($"Failed to load player record file {filename}; attempting to recover from backup.", e);
_log.Error($"Failed to load record data for player {playerLongReference} from {filename}; attempting to recover from backup.", e);

// TODO: try to recover from backup

// TODO: if backup recovery failed, store broken file under different filename for future reference
var failureFilename = filename + ".failure";

// otherwise, create default
_log.Info($"Unable to recover player record for player {entityId}; creating a new one with defaults under {filename}; admin can attempt to inspect backup file {failureFilename}");
_log.Info($"Unable to recover record data for player {playerLongReference}; creating a new one with defaults under {filename}; admin can attempt to inspect backup file {failureFilename}");
playerRecord.Save();
}
finally
{
Entries.Add(entityId, playerRecord);
}
return true;
}

public static void Unload(ClientInfo clientInfo)
Expand Down Expand Up @@ -168,15 +174,11 @@ public void PurchaseSkill(string name, int level, int cost)
/// Respec player, returning/unassigning all skill points but leaving level the same.
/// </summary>
/// <param name="player">Player to respec.</param>
public void Respec(ClientInfo clientInfo, EntityPlayer player)
public void Respec(EntityPlayer player)
{
player.Progression.ResetProgression(true);
_ = player.Buffs.AddBuff(Values.BuffRespec);
Changes.Clear();
Save();
player.Progression.bProgressionStatsChanged = true;
player.bPlayerStatsChanged = true;
ConnectionManager.Instance.SendPackage(NetPackageManager.GetPackage<NetPackagePlayerStats>().Setup(player), false, player.entityId);

}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Data/Values.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal class Values
public static string BuffTryBuyTreatment { get; private set; } = "buffAmnesiaTryBuyTreatment";
public static string BuffTryBuyTherapy { get; private set; } = "buffAmnesiaTryBuyTherapy";
public static string BuffRequestChangeCallback { get; private set; } = "buffAmnesiaRequestChangeCallback";
public static string BuffRespec { get; private set; } = "buffAmnesiaRespec";

// game_events
public static string GameEventPayFromBag { get; private set; } = "amnesia_pay_from_bag";
Expand All @@ -37,6 +38,7 @@ internal class Values
public static string WindowShopTreatmentUnnecessary { get; private set; } = "amnesiaDialogShopTreatmentUnnecessaryWindowGroup";
public static string WindowShopTreatmentComplete { get; private set; } = "amnesiaDialogShopTreatmentCompleteWindowGroup";
public static string WindowShopTherapyComplete { get; private set; } = "amnesiaDialogShopTherapyCompleteWindowGroup";
public static string WindowSkillPointIntegrityCheckReduced { get; private set; } = "amnesiaSkillPointIntegrityCheckReducedWindowGroup";

// names
public static string NameLongTermMemoryLevel { get; private set; } = "LongTermMemoryLevel";
Expand Down
7 changes: 5 additions & 2 deletions src/Handlers/PlayerSpawnedInWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static void Handle(ClientInfo clientInfo, RespawnType respawnType, Vector
HandleStandardRespawnSteps(player);
DialogShop.UpdatePrices(player);
DialogShop.UpdateMoneyTracker(player.entityId, player.inventory.GetSlots(), player.bag.GetSlots());
PlayerRecord.Load(clientInfo);
_ = PlayerRecord.TryLoad(clientInfo, out _, player);
break;
// TODO: case RespawnType.LoadedGame: // local player loading existing game
case RespawnType.JoinMultiplayer: // existing player rejoining
Expand All @@ -41,7 +41,10 @@ public static void Handle(ClientInfo clientInfo, RespawnType respawnType, Vector
HandleStandardRespawnSteps(player);
DialogShop.UpdatePrices(player);
DialogShop.UpdateMoneyTracker(player.entityId, player.inventory.GetSlots(), player.bag.GetSlots());
PlayerRecord.Load(clientInfo);
if (PlayerRecord.TryLoad(clientInfo, out var record, player))
{
PlayerHelper.SkillPointIntegrityCheck(clientInfo, player, clientInfo.latestPlayerData, record);
}
break;
case RespawnType.Died: // existing player returned from death
_ = PlayerHelper.AddPositiveOutlookTime(player, Config.PositiveOutlookTimeOnMemoryLoss);
Expand Down
46 changes: 19 additions & 27 deletions src/Utilities/DialogShop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,30 +93,22 @@ public static void TryBuyTreatment(EntityAlive entity)
{
player.Buffs.RemoveBuff(Values.BuffTryBuyTreatment);

var playerName = player.GetDebugName();
var userId = ClientInfoHelper.GetUserIdentifier(clientInfo);

if (!player.Buffs.HasBuff(Values.BuffFragileMemory))
{
_log.Info($"player {player.entityId} ({playerName} | {userId}) requested Treatment from trader but doesn't have a Fragile Memory to heal.");
_log.Info($"player {player.entityId} ({player.GetDebugName()} | {ClientInfoHelper.GetUserIdentifier(clientInfo)}) requested Treatment from trader but doesn't have a Fragile Memory to heal.");
PlayerHelper.OpenWindow(clientInfo, Values.WindowShopTreatmentUnnecessary);
PlayerHelper.TriggerGameEvent(clientInfo, player, Values.GameEventShopUnnecessary);
return;
}

_ = BltMoney.TryGetValue(player.entityId, out var blt);
_ = BagMoney.TryGetValue(player.entityId, out var bag);
var cost = GetCost(player.Progression.Level, Product.Treatment);
if (TryPurchase(clientInfo, player, blt, bag, cost, out var change))
if (TryPurchase(clientInfo, player, Product.Treatment))
{
_log.Info($"player {player.entityId} ({playerName} | {userId}) purchased Treatment from trader: {blt}+{bag} = {blt + bag} - {cost} = {change}");
player.Buffs.RemoveBuff(Values.BuffFragileMemory);
PlayerHelper.OpenWindow(clientInfo, Values.WindowShopTreatmentComplete);
PlayerHelper.TriggerGameEvent(clientInfo, player, Values.GameEventShopPurchased);
}
else
{
_log.Info($"player {player.entityId} ({playerName} | {userId}) requested Treatment from trader but doesn't have enough money: {blt}+{bag} = {blt + bag} < {cost}");
PlayerHelper.OpenWindow(clientInfo, Values.WindowShopCannotAfford);
PlayerHelper.TriggerGameEvent(clientInfo, player, Values.GameEventShopCannotAfford);
}
Expand All @@ -130,23 +122,16 @@ public static void TryBuyTherapy(EntityAlive entity)
if (PlayerHelper.TryGetClientInfoAndEntityPlayer(entity.world, entity.entityId, out var clientInfo, out var player))
{
player.Buffs.RemoveBuff(Values.BuffTryBuyTherapy);
player.Buffs.RemoveBuff(Values.BuffRespec);

var playerName = player.GetDebugName();
var userId = ClientInfoHelper.GetUserIdentifier(clientInfo);

_ = BltMoney.TryGetValue(player.entityId, out var blt);
_ = BagMoney.TryGetValue(player.entityId, out var bag);
var cost = GetCost(player.Progression.Level, Product.Therapy);
if (TryPurchase(clientInfo, player, blt, bag, cost, out var change))
if (TryPurchase(clientInfo, player, Product.Therapy))
{
_log.Info($"player {player.entityId} ({playerName} | {userId}) purchased Therapy from trader: {blt}+{bag} = {blt + bag} - {cost} = {change}");
PlayerHelper.Respec(player);
PlayerHelper.OpenWindow(clientInfo, Values.WindowShopTherapyComplete);
PlayerHelper.TriggerGameEvent(clientInfo, player, Values.GameEventShopPurchased);
}
else
{
_log.Info($"player {player.entityId} ({playerName} | {userId}) requested Therapy from trader but doesn't have enough money: {blt}+{bag} = {blt + bag} < {cost}");
PlayerHelper.OpenWindow(clientInfo, Values.WindowShopCannotAfford);
PlayerHelper.TriggerGameEvent(clientInfo, player, Values.GameEventShopCannotAfford);
}
Expand Down Expand Up @@ -182,25 +167,32 @@ private static int GetCost(int level, Product product)
return -1;
}

private static bool TryPurchase(ClientInfo clientInfo, EntityPlayer player, int _blt, int _bag, int _cost, out int change)
private static bool TryPurchase(ClientInfo clientInfo, EntityPlayer player, Product product)
{
if (!CanAfford(player.entityId, _cost))
var playerName = player.GetDebugName();
var userId = ClientInfoHelper.GetUserIdentifier(clientInfo);
_ = BltMoney.TryGetValue(player.entityId, out var blt);
_ = BagMoney.TryGetValue(player.entityId, out var bag);
var cost = GetCost(player.Progression.Level, product);

if (!CanAfford(player.entityId, cost))
{
_log.Trace($"player {player.GetDebugName()} could NOT afford {_cost}");
change = -1;
_log.Info($"player {player.entityId} ({playerName} | {userId}) requested Therapy from trader but doesn't have enough money: {blt}+{bag} = {blt + bag} < {cost}");
return false;
}

_log.Trace($"player {player.GetDebugName()} could afford {_cost}");
int change;
string eventName;
if (_bag >= _cost)
if (bag >= cost)
{
change = _bag - _cost;
change = bag - cost;
_log.Info($"player {player.entityId} ({playerName} | {userId}) purchased {product} from trader with coins from bag: {bag} - {cost} = {change} in change due");
eventName = Values.GameEventPayFromBag;
}
else
{
change = _bag + _blt - _cost;
change = bag + blt - cost;
_log.Info($"player {player.entityId} ({playerName} | {userId}) purchased {product} from trader with coins from bag and belt: {blt}+{bag} = {blt + bag} - {cost} = {change} in change due");
eventName = Values.GameEventPayFromAll;
}

Expand Down
Loading

0 comments on commit 4658eb3

Please sign in to comment.