diff --git a/Source/ACE.Server/Entity/AllegianceNode.cs b/Source/ACE.Server/Entity/AllegianceNode.cs
index dea29a2db7..f833c4d88d 100644
--- a/Source/ACE.Server/Entity/AllegianceNode.cs
+++ b/Source/ACE.Server/Entity/AllegianceNode.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using ACE.Common;
using ACE.Entity;
using ACE.Server.Managers;
using ACE.Server.WorldObjects;
@@ -117,5 +118,67 @@ public void OnLevelUp()
vassal.Player.ExistedBeforeAllegianceXpChanges = true;
}
}
+
+ ///
+ /// Returns the amount of realtime sworn to patron
+ ///
+ public TimeSpan CalculateRealTime()
+ {
+ if (Patron == null)
+ return TimeSpan.Zero;
+
+ // TODO: figure out better handling for legacy players
+ // with AllegianceSwearTimestamp null
+ var now = Time.GetUnixTime();
+ var timestamp = Player.AllegianceSwearTimestamp ?? now;
+
+ return TimeSpan.FromSeconds(now - timestamp);
+ }
+
+ ///
+ /// Returns the amount of gametime sworn to patron
+ ///
+ public TimeSpan CalculateGameTime()
+ {
+ if (Patron == null)
+ return TimeSpan.Zero;
+
+ // TODO: figure out better handling for legacy players
+ // with AllegianceSwearAge null
+ var current_age = Player.Age ?? 0;
+ var swear_age = Player.AllegianceSwearAge ?? current_age;
+
+ return TimeSpan.FromSeconds(current_age - swear_age);
+ }
+
+ ///
+ /// Returns the average amount of realtime vassals sworn
+ ///
+ public TimeSpan CalculateRealTime_VassalAvg()
+ {
+ if (!HasVassals)
+ return TimeSpan.Zero;
+
+ var total = TimeSpan.Zero;
+ foreach (var vassal in Vassals.Values)
+ total += vassal.CalculateRealTime();
+
+ return total / Vassals.Count;
+ }
+
+ ///
+ /// Returns the average amount of gametime vassals sworn
+ ///
+ public TimeSpan CalculateGameTime_VassalAvg()
+ {
+ if (!HasVassals)
+ return TimeSpan.Zero;
+
+ var total = TimeSpan.Zero;
+ foreach (var vassal in Vassals.Values)
+ total += vassal.CalculateGameTime();
+
+ return total / Vassals.Count;
+ }
}
}
diff --git a/Source/ACE.Server/Entity/IPlayer.cs b/Source/ACE.Server/Entity/IPlayer.cs
index dbea99b50f..6fd02a1ba6 100644
--- a/Source/ACE.Server/Entity/IPlayer.cs
+++ b/Source/ACE.Server/Entity/IPlayer.cs
@@ -78,6 +78,12 @@ public interface IPlayer
int? HouseRentTimestamp { get; set; }
+ int? Age { get; set; }
+
+ int? AllegianceSwearAge { get; set; }
+
+ double? AllegianceSwearTimestamp { get; set; }
+
uint GetCurrentLoyalty();
diff --git a/Source/ACE.Server/Entity/OfflinePlayer.cs b/Source/ACE.Server/Entity/OfflinePlayer.cs
index 83ce5e8c0c..f284d0ebbd 100644
--- a/Source/ACE.Server/Entity/OfflinePlayer.cs
+++ b/Source/ACE.Server/Entity/OfflinePlayer.cs
@@ -327,6 +327,25 @@ public int? AllegianceOfficerRank
set { if (!value.HasValue) RemoveProperty(PropertyInt.AllegianceOfficerRank); else SetProperty(PropertyInt.AllegianceOfficerRank, value.Value); }
}
+ public int? Age
+ {
+ get => GetProperty(PropertyInt.Age);
+ set { if (!value.HasValue) RemoveProperty(PropertyInt.Age); else SetProperty(PropertyInt.Age, value.Value); }
+ }
+
+ public int? AllegianceSwearAge
+ {
+ get => GetProperty(PropertyInt.AllegianceSwearTimestamp);
+ set { if (!value.HasValue) RemoveProperty(PropertyInt.AllegianceSwearTimestamp); else SetProperty(PropertyInt.AllegianceSwearTimestamp, value.Value); }
+ }
+
+ public double? AllegianceSwearTimestamp
+ {
+ get => GetProperty(PropertyFloat.AllegianceSwearTimestamp);
+ set { if (!value.HasValue) RemoveProperty(PropertyFloat.AllegianceSwearTimestamp); else SetProperty(PropertyFloat.AllegianceSwearTimestamp, value.Value); }
+
+ }
+
///
/// This flag indicates if a player can pass up allegiance XP
///
diff --git a/Source/ACE.Server/Managers/AllegianceManager.cs b/Source/ACE.Server/Managers/AllegianceManager.cs
index f55f65767c..a05eb45f23 100644
--- a/Source/ACE.Server/Managers/AllegianceManager.cs
+++ b/Source/ACE.Server/Managers/AllegianceManager.cs
@@ -169,14 +169,16 @@ public static void RemoveCache(Allegiance allegiance)
public static float SkillCap = 291.0f;
///
- /// The maximum amount of realtime hours sworn to patron
+ /// The maximum amount of realtime sworn to patron
+ /// before reaching cap (retail default: 730 days / 2 years)
///
- public static float RealCap = 730.0f;
+ public static TimeSpan RealCap = TimeSpan.FromDays(730);
///
- /// The maximum amount of in-game hours sworn to patron
+ /// The maximum amount of gametime sworn to patron
+ /// before reaching cap (retail default: 720 hours / 1 month)
///
- public static float GameCap = 720.0f;
+ public static TimeSpan GameCap = TimeSpan.FromHours(720);
public static void PassXP(AllegianceNode vassalNode, ulong amount, bool direct)
{
@@ -246,19 +248,29 @@ public static void PassXP(AllegianceNode vassalNode, ulong amount, bool direct)
var loyalty = Math.Min(vassal.GetCurrentLoyalty(), SkillCap);
var leadership = Math.Min(patron.GetCurrentLeadership(), SkillCap);
- var timeReal = Math.Min(RealCap, RealCap);
- var timeGame = Math.Min(GameCap, GameCap);
+ //var realtime = vassalNode.CalculateRealTime();
+ //var gametime = vassalNode.CalculateGameTime();
+ var realtime = RealCap;
+ var gametime = GameCap;
- var timeRealAvg = Math.Min(RealCap, RealCap);
- var timeGameAvg = Math.Min(GameCap, GameCap);
+ var timeReal = Math.Min(realtime.TotalDays, RealCap.TotalDays);
+ var timeGame = Math.Min(gametime.TotalHours, GameCap.TotalHours);
+
+ //var realtime_vassal = patronNode.CalculateRealTime_VassalAvg();
+ //var gametime_vassal = patronNode.CalculateGameTime_VassalAvg();
+ var realtime_vassal = RealCap;
+ var gametime_vassal = GameCap;
+
+ var timeRealAvg = Math.Min(realtime_vassal.TotalDays, RealCap.TotalDays);
+ var timeGameAvg = Math.Min(gametime_vassal.TotalHours, GameCap.TotalHours);
var vassalFactor = Math.Min(0.25f * patronNode.TotalVassals, 1.0f);
var factor1 = direct ? 50.0f : 16.0f;
var factor2 = direct ? 22.5f : 8.0f;
- var generated = (factor1 + factor2 * (loyalty / SkillCap) * (1.0f + (timeReal / RealCap) * (timeGame / GameCap))) * 0.01f;
- var received = (factor1 + factor2 * (leadership / SkillCap) * (1.0f + vassalFactor * (timeRealAvg / RealCap) * (timeGameAvg / GameCap))) * 0.01f;
+ var generated = (factor1 + factor2 * (loyalty / SkillCap) * (1.0f + (timeReal / RealCap.TotalDays) * (timeGame / GameCap.TotalHours))) * 0.01f;
+ var received = (factor1 + factor2 * (leadership / SkillCap) * (1.0f + vassalFactor * (timeRealAvg / RealCap.TotalDays) * (timeGameAvg / GameCap.TotalHours))) * 0.01f;
var passup = generated * received;
var generatedAmount = (uint)(amount * generated);
diff --git a/Source/ACE.Server/WorldObjects/Player_Allegiance.cs b/Source/ACE.Server/WorldObjects/Player_Allegiance.cs
index 27af2d9d04..6e873a7e68 100644
--- a/Source/ACE.Server/WorldObjects/Player_Allegiance.cs
+++ b/Source/ACE.Server/WorldObjects/Player_Allegiance.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
+using ACE.Common;
using ACE.Database.Models.Shard;
using ACE.Entity;
using ACE.Entity.Enum;
@@ -60,6 +61,26 @@ public bool ExistedBeforeAllegianceXpChanges
set { if (value) RemoveProperty(PropertyBool.ExistedBeforeAllegianceXpChanges); else SetProperty(PropertyBool.ExistedBeforeAllegianceXpChanges, value); }
}
+ ///
+ /// The timestamp when the player swore allegiance
+ /// Used to calculate realtime sworn to patron
+ ///
+ public double? AllegianceSwearTimestamp
+ {
+ get => GetProperty(PropertyFloat.AllegianceSwearTimestamp);
+ set { if (!value.HasValue) RemoveProperty(PropertyFloat.AllegianceSwearTimestamp); else SetProperty(PropertyFloat.AllegianceSwearTimestamp, value.Value); }
+ }
+
+ ///
+ /// The player age when they swore allegiance
+ /// Used to calculate gametime sworn to patron
+ ///
+ public int? AllegianceSwearAge
+ {
+ get => GetProperty(PropertyInt.AllegianceSwearTimestamp);
+ set { if (!value.HasValue) RemoveProperty(PropertyInt.AllegianceSwearTimestamp); else SetProperty(PropertyInt.AllegianceSwearTimestamp, value.Value); }
+ }
+
///
/// Called when a player tries to Swear Allegiance to a target
///
@@ -125,6 +146,9 @@ public void SwearAllegiance(uint targetGuid, bool success, bool confirmed = fals
AllegianceXPGenerated = 0;
AllegianceOfficerRank = null;
+ AllegianceSwearTimestamp = Time.GetUnixTime();
+ AllegianceSwearAge = Age;
+
// refresh ui panel
Session.Network.EnqueueSend(new GameEventAllegianceUpdate(Session, Allegiance, AllegianceNode), new GameEventAllegianceAllegianceUpdateDone(Session));