matt daa0ba8f72 Complete Service Layer Implementation - Core Business Logic
Major service implementations completed:
 PlayerService.cs - Player progression, VIP management, castle operations, teleportation
 CombatService.cs - Field interception system, battle resolution, march mechanics
 AllianceService.cs - Coalition mechanics, research systems, territory management
 KingdomService.cs - KvK events, democratic leadership, population management
 PurchaseService.cs - Anti-pay-to-win monitoring, ethical monetization

Core innovations fully implemented:
- Field interception combat system (primary differentiator)
- Alliance coalition system preserving individual identity
- Anti-pay-to-win balance with skill-based alternatives
- Democratic kingdom politics and leadership
- Ethical monetization with player protection systems

Architecture features:
- Consistent dependency injection patterns across all services
- Comprehensive error handling and logging
- Kingdom-scoped security enforcement
- Transaction management via UnitOfWork
- Production-ready business logic implementation

Service layer complete - ready for API controller development
2025-10-19 15:30:14 -05:00

802 lines
38 KiB
C#

/*
* File: D:\shadowed-realms-mobile\ShadowedRealmsMobile\src\server\ShadowedRealms.API\Services\PurchaseService.cs
* Created: 2025-10-19
* Last Modified: 2025-10-19
* Description: Concrete implementation of IPurchaseService providing comprehensive purchase and monetization business logic operations including anti-pay-to-win monitoring, ethical VIP progression, spending balance validation, and skill-based alternative systems with player protection
* Last Edit Notes: Initial creation with complete business logic implementation focusing on ethical monetization
*/
using Microsoft.Extensions.Logging;
using ShadowedRealms.Core.Interfaces;
using ShadowedRealms.Core.Interfaces.Repositories;
using ShadowedRealms.Core.Interfaces.Services;
using ShadowedRealms.Core.Models;
using ShadowedRealms.Core.Models.Player;
namespace ShadowedRealms.API.Services
{
/// <summary>
/// Concrete implementation of purchase service providing ethical monetization and anti-pay-to-win business logic
/// </summary>
public class PurchaseService : IPurchaseService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IPurchaseLogRepository _purchaseLogRepository;
private readonly IPlayerRepository _playerRepository;
private readonly IAllianceRepository _allianceRepository;
private readonly IKingdomRepository _kingdomRepository;
private readonly ICombatLogRepository _combatLogRepository;
private readonly ILogger<PurchaseService> _logger;
// Anti-pay-to-win constants for balance
private const double MAX_SPENDING_VICTORY_INFLUENCE = 0.3; // Max 30% victory influence from spending
private const double MIN_FREE_PLAYER_EFFECTIVENESS = 0.7; // Min 70% effectiveness for skilled free players
private const double CHARGEBACK_RISK_THRESHOLD = 0.15; // 15% chargeback risk threshold
private const int VIP_SECRET_TIER_THRESHOLD = 16; // VIP tiers 16+ are secret
private const decimal HEALTHY_SPENDING_DAILY_LIMIT = 100m; // Daily spending limit for player protection
private const int FRAUD_DETECTION_LOOKBACK_DAYS = 30; // Days to analyze for fraud patterns
public PurchaseService(
IUnitOfWork unitOfWork,
IPurchaseLogRepository purchaseLogRepository,
IPlayerRepository playerRepository,
IAllianceRepository allianceRepository,
IKingdomRepository kingdomRepository,
ICombatLogRepository combatLogRepository,
ILogger<PurchaseService> logger)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
_purchaseLogRepository = purchaseLogRepository ?? throw new ArgumentNullException(nameof(purchaseLogRepository));
_playerRepository = playerRepository ?? throw new ArgumentNullException(nameof(playerRepository));
_allianceRepository = allianceRepository ?? throw new ArgumentNullException(nameof(allianceRepository));
_kingdomRepository = kingdomRepository ?? throw new ArgumentNullException(nameof(kingdomRepository));
_combatLogRepository = combatLogRepository ?? throw new ArgumentNullException(nameof(combatLogRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
#region Purchase Processing & Validation
public async Task<(bool Success, string TransactionId, Dictionary<string, object> AppliedBenefits,
Dictionary<string, object> BalanceValidation)>
ProcessPurchaseAsync(int playerId, int kingdomId, Dictionary<string, object> purchaseDetails,
Dictionary<string, object> paymentMethod)
{
_logger.LogInformation("Processing purchase: Player {PlayerId}, Amount: {Amount}, Items: {ItemCount}",
playerId, purchaseDetails.GetValueOrDefault("Amount", 0),
((List<object>)purchaseDetails.GetValueOrDefault("Items", new List<object>())).Count);
return await _unitOfWork.ExecuteInTransactionAsync(async () =>
{
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
throw new ArgumentException($"Player {playerId} not found in kingdom {kingdomId}");
// Fraud detection
var (isFraudulent, riskScore, fraudIndicators, preventionMeasures) =
await DetectPurchaseFraudAsync(playerId, kingdomId, purchaseDetails, paymentMethod);
if (isFraudulent)
{
return (false, "", new Dictionary<string, object>(), new Dictionary<string, object>
{
["Error"] = "Purchase blocked due to fraud detection",
["RiskScore"] = riskScore,
["FraudIndicators"] = fraudIndicators
});
}
// Balance validation
var (isValid, validationWarnings, balanceImpact, alternativeOptions) =
await ValidatePurchaseBalanceAsync(playerId, kingdomId, purchaseDetails);
if (!isValid && balanceImpact.ContainsKey("BlockPurchase") && (bool)balanceImpact["BlockPurchase"])
{
return (false, "", new Dictionary<string, object>(), new Dictionary<string, object>
{
["Error"] = "Purchase would create unfair competitive advantage",
["ValidationWarnings"] = validationWarnings,
["AlternativeOptions"] = alternativeOptions
});
}
// Generate transaction ID
var transactionId = $"TXN_{playerId}_{DateTime.UtcNow.Ticks}";
// Process payment (integrate with payment provider)
var paymentResult = await ProcessPaymentTransaction(transactionId, purchaseDetails, paymentMethod);
if (!paymentResult.Success)
{
return (false, transactionId, new Dictionary<string, object>(), new Dictionary<string, object>
{
["Error"] = "Payment processing failed",
["PaymentError"] = paymentResult.ErrorMessage
});
}
// Apply purchase benefits
var appliedBenefits = await ApplyPurchaseBenefits(playerId, kingdomId, purchaseDetails, transactionId);
// Create purchase log
var purchaseLog = new PurchaseLog
{
PlayerId = playerId,
KingdomId = kingdomId,
TransactionId = transactionId,
Amount = Convert.ToDecimal(purchaseDetails["Amount"]),
Currency = (string)purchaseDetails.GetValueOrDefault("Currency", "USD"),
PurchaseType = (string)purchaseDetails["PurchaseType"],
Items = purchaseDetails["Items"].ToString(),
PaymentMethod = paymentMethod["Type"].ToString(),
PurchaseDate = DateTime.UtcNow,
IsRefunded = false
};
await _purchaseLogRepository.CreateAsync(purchaseLog);
// Update player spending statistics
await UpdatePlayerSpendingStats(playerId, kingdomId, purchaseLog.Amount);
// Create audit trail
await CreateTransactionAuditTrailAsync(transactionId, new Dictionary<string, object>
{
["PlayerId"] = playerId,
["KingdomId"] = kingdomId,
["PurchaseDetails"] = purchaseDetails,
["AppliedBenefits"] = appliedBenefits,
["BalanceValidation"] = balanceImpact,
["FraudScore"] = riskScore
});
_logger.LogInformation("Purchase processed successfully: {TransactionId}, Player {PlayerId}, Amount: {Amount}",
transactionId, playerId, purchaseLog.Amount);
return (true, transactionId, appliedBenefits, balanceImpact);
});
}
public async Task<(bool IsValid, List<string> ValidationWarnings, Dictionary<string, object> BalanceImpact,
Dictionary<string, object> AlternativeOptions)>
ValidatePurchaseBalanceAsync(int playerId, int kingdomId, Dictionary<string, object> purchaseDetails)
{
var validationWarnings = new List<string>();
var balanceImpact = new Dictionary<string, object>();
var alternativeOptions = new Dictionary<string, object>();
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
return (false, new List<string> { "Player not found" }, balanceImpact, alternativeOptions);
var purchaseAmount = Convert.ToDecimal(purchaseDetails["Amount"]);
var purchaseType = (string)purchaseDetails["PurchaseType"];
// Analyze current spending patterns
var currentSpending = await _purchaseLogRepository.GetPlayerPurchaseSummaryAsync(playerId, kingdomId, 30);
var monthlySpending = (decimal)currentSpending["TotalSpent"];
// Calculate potential competitive impact
var competitiveImpact = await CalculatePurchaseCompetitiveImpact(playerId, kingdomId, purchaseDetails);
balanceImpact["CompetitiveImpact"] = competitiveImpact;
// Check spending dominance patterns
var spendingDominance = await AnalyzeSpendingDominanceRisk(playerId, kingdomId, purchaseAmount);
balanceImpact["SpendingDominance"] = spendingDominance;
// Validate against anti-pay-to-win thresholds
var antiP2WValidation = ValidateAntiPayToWinThresholds(competitiveImpact, spendingDominance);
balanceImpact["AntiPayToWinValidation"] = antiP2WValidation;
// Check if purchase would exceed healthy limits
if (monthlySpending + purchaseAmount > HEALTHY_SPENDING_DAILY_LIMIT * 30)
{
validationWarnings.Add("Purchase exceeds recommended monthly spending limits");
balanceImpact["HealthySpendingConcern"] = true;
}
// Generate skill-based alternatives
alternativeOptions = await ProvideSkillBasedAlternativesAsync(playerId, kingdomId, purchaseType);
// Determine if purchase should be blocked
var blockPurchase = (double)antiP2WValidation["VictoryInfluenceRisk"] > MAX_SPENDING_VICTORY_INFLUENCE ||
(double)spendingDominance["DominanceRisk"] > 0.8;
balanceImpact["BlockPurchase"] = blockPurchase;
if (blockPurchase)
{
validationWarnings.Add("Purchase would create unfair competitive advantage");
validationWarnings.Add("Consider skill-based alternatives provided");
}
var isValid = !blockPurchase;
return (isValid, validationWarnings, balanceImpact, alternativeOptions);
}
public async Task<(bool Success, Dictionary<string, object> StateAdjustments, Dictionary<string, object> FraudPrevention)>
ProcessRefundAsync(int playerId, int kingdomId, string transactionId, string refundReason, string refundType)
{
_logger.LogInformation("Processing refund: Player {PlayerId}, Transaction {TransactionId}, Type: {RefundType}, Reason: {RefundReason}",
playerId, transactionId, refundType, refundReason);
return await _unitOfWork.ExecuteInTransactionAsync(async () =>
{
// Get original purchase
var purchaseLog = await _purchaseLogRepository.GetByTransactionIdAsync(transactionId, kingdomId);
if (purchaseLog == null)
{
return (false, new Dictionary<string, object>
{
["Error"] = "Original transaction not found"
}, new Dictionary<string, object>());
}
if (purchaseLog.IsRefunded)
{
return (false, new Dictionary<string, object>
{
["Error"] = "Transaction already refunded"
}, new Dictionary<string, object>());
}
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
throw new ArgumentException($"Player {playerId} not found");
var stateAdjustments = new Dictionary<string, object>();
var fraudPrevention = new Dictionary<string, object>();
// Calculate refund impact on game state
var refundImpact = await CalculateRefundGameStateImpact(purchaseLog, player);
// Reverse VIP benefits if applicable
if (purchaseLog.PurchaseType == "VIP" || purchaseLog.PurchaseType.Contains("VIP"))
{
var vipAdjustments = await ReverseVipBenefits(playerId, kingdomId, purchaseLog);
stateAdjustments["VipAdjustments"] = vipAdjustments;
}
// Reverse premium item benefits
var itemAdjustments = await ReversePremiumItemBenefits(playerId, kingdomId, purchaseLog);
stateAdjustments["ItemAdjustments"] = itemAdjustments;
// Implement fraud prevention measures for chargeback/dispute refunds
if (refundType == "Chargeback" || refundType == "Dispute")
{
fraudPrevention = await ImplementChargebackFraudPrevention(playerId, kingdomId, purchaseLog);
}
// Mark purchase as refunded
purchaseLog.IsRefunded = true;
purchaseLog.RefundDate = DateTime.UtcNow;
purchaseLog.RefundReason = refundReason;
await _purchaseLogRepository.UpdateAsync(purchaseLog);
// Update player spending statistics
await UpdatePlayerSpendingStats(playerId, kingdomId, -purchaseLog.Amount);
// Create refund audit trail
await CreateTransactionAuditTrailAsync($"REFUND_{transactionId}", new Dictionary<string, object>
{
["OriginalTransactionId"] = transactionId,
["RefundType"] = refundType,
["RefundReason"] = refundReason,
["StateAdjustments"] = stateAdjustments,
["FraudPrevention"] = fraudPrevention,
["RefundAmount"] = purchaseLog.Amount
});
_logger.LogInformation("Refund processed: Transaction {TransactionId}, Amount: {Amount}, Type: {RefundType}",
transactionId, purchaseLog.Amount, refundType);
return (true, stateAdjustments, fraudPrevention);
});
}
public async Task<(bool IsFraudulent, double RiskScore, List<string> FraudIndicators, Dictionary<string, object> PreventionMeasures)>
DetectPurchaseFraudAsync(int playerId, int kingdomId, Dictionary<string, object> purchaseDetails,
Dictionary<string, object> paymentMethod)
{
var fraudIndicators = new List<string>();
var preventionMeasures = new Dictionary<string, object>();
var riskScore = 0.0;
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
return (true, 1.0, new List<string> { "Player not found" }, preventionMeasures);
// Analyze purchase patterns
var recentPurchases = await _purchaseLogRepository.GetPlayerPurchasesAsync(playerId, kingdomId,
TimeSpan.FromDays(FRAUD_DETECTION_LOOKBACK_DAYS));
// Check for rapid successive purchases (velocity check)
var recentPurchaseCount = recentPurchases.Count(p => p.PurchaseDate > DateTime.UtcNow.AddHours(-1));
if (recentPurchaseCount > 5)
{
fraudIndicators.Add("High purchase velocity detected");
riskScore += 0.3;
}
// Check for unusual spending amounts
var avgPurchaseAmount = recentPurchases.Any() ? recentPurchases.Average(p => p.Amount) : 0m;
var currentAmount = Convert.ToDecimal(purchaseDetails["Amount"]);
if (avgPurchaseAmount > 0 && currentAmount > avgPurchaseAmount * 10)
{
fraudIndicators.Add("Purchase amount significantly higher than historical pattern");
riskScore += 0.2;
}
// Check payment method consistency
var paymentMethodChanges = AnalyzePaymentMethodChanges(recentPurchases, paymentMethod);
if (paymentMethodChanges.IsUnusual)
{
fraudIndicators.Add("Unusual payment method change detected");
riskScore += 0.15;
}
// Check account age vs spending pattern
var accountAge = DateTime.UtcNow - player.CreatedAt;
var totalSpent = recentPurchases.Sum(p => p.Amount);
if (accountAge.TotalDays < 7 && totalSpent > 500m)
{
fraudIndicators.Add("High spending on new account");
riskScore += 0.25;
}
// Check for chargeback history
var chargebackHistory = recentPurchases.Count(p => p.RefundReason?.Contains("Chargeback") == true);
if (chargebackHistory > 0)
{
fraudIndicators.Add("Previous chargeback activity detected");
riskScore += 0.4;
}
// Check device/location consistency (if available in payment method)
if (paymentMethod.ContainsKey("DeviceFingerprint") && paymentMethod.ContainsKey("Location"))
{
var deviceConsistency = await ValidateDeviceConsistency(playerId, paymentMethod);
if (!deviceConsistency.IsConsistent)
{
fraudIndicators.Add("Inconsistent device or location pattern");
riskScore += 0.2;
}
}
// Implement prevention measures based on risk score
if (riskScore > 0.5)
{
preventionMeasures["RequireAdditionalVerification"] = true;
preventionMeasures["DelayBenefitApplication"] = TimeSpan.FromHours(24);
}
if (riskScore > 0.7)
{
preventionMeasures["RequireManualReview"] = true;
preventionMeasures["TemporarilyRestrictPurchases"] = true;
}
var isFraudulent = riskScore > 0.8;
if (isFraudulent)
{
_logger.LogWarning("Fraudulent purchase detected: Player {PlayerId}, Risk score: {RiskScore}, Indicators: {Indicators}",
playerId, riskScore, string.Join(", ", fraudIndicators));
}
return (isFraudulent, riskScore, fraudIndicators, preventionMeasures);
}
#endregion
#region Anti-Pay-to-Win Monitoring
public async Task<Dictionary<string, object>> MonitorPayToWinDominanceAsync(int playerId, int kingdomId,
TimeSpan monitoringPeriod)
{
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
throw new ArgumentException($"Player {playerId} not found");
var monitoring = new Dictionary<string, object>
{
["PlayerId"] = playerId,
["MonitoringPeriod"] = monitoringPeriod,
["MonitoringTimestamp"] = DateTime.UtcNow
};
// Get spending data for monitoring period
var spendingData = await _purchaseLogRepository.GetPlayerPurchaseSummaryAsync(playerId, kingdomId,
(int)monitoringPeriod.TotalDays);
var totalSpent = (decimal)spendingData["TotalSpent"];
monitoring["TotalSpending"] = totalSpent;
// Analyze combat outcomes vs spending
var combatAnalysis = await AnalyzeCombatOutcomesBySpending(playerId, kingdomId, monitoringPeriod);
monitoring["CombatAnalysis"] = combatAnalysis;
// Calculate spending dominance metrics
var dominanceMetrics = await CalculateSpendingDominanceMetrics(playerId, kingdomId, totalSpent, monitoringPeriod);
monitoring["DominanceMetrics"] = dominanceMetrics;
// Assess competitive balance impact
var balanceImpact = await AssessCompetitiveBalanceImpact(playerId, kingdomId, combatAnalysis, dominanceMetrics);
monitoring["BalanceImpact"] = balanceImpact;
// Generate balance correction recommendations
var corrections = new List<string>();
var victoryInfluence = (double)balanceImpact["VictoryInfluenceFromSpending"];
if (victoryInfluence > MAX_SPENDING_VICTORY_INFLUENCE)
{
corrections.Add("Victory outcomes too heavily influenced by spending");
corrections.Add("Recommend implementing skill-based balance adjustments");
corrections.Add("Consider offering enhanced strategic alternatives to opponents");
}
var playerEffectiveness = (double)balanceImpact["OpponentEffectivenessVsSpender"];
if (playerEffectiveness < MIN_FREE_PLAYER_EFFECTIVENESS)
{
corrections.Add("Free players achieving less than 70% effectiveness against spenders");
corrections.Add("Recommend enhancing skill-based bonuses and strategic options");
}
monitoring["BalanceCorrections"] = corrections;
monitoring["RequiresIntervention"] = corrections.Any();
return monitoring;
}
public async Task<Dictionary<string, object>> CalculateCompetitiveEffectivenessAsync(int playerId, int kingdomId,
List<int> comparisonGroup)
{
var effectiveness = new Dictionary<string, object>
{
["PlayerId"] = playerId,
["ComparisonGroupSize"] = comparisonGroup.Count,
["AnalysisTimestamp"] = DateTime.UtcNow
};
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
throw new ArgumentException($"Player {playerId} not found");
// Get spending data
var playerSpending = await _purchaseLogRepository.GetPlayerPurchaseSummaryAsync(playerId, kingdomId, 30);
var playerTotalSpent = (decimal)playerSpending["TotalSpent"];
// Classify player spending tier
var spendingTier = ClassifySpendingTier(playerTotalSpent);
effectiveness["PlayerSpendingTier"] = spendingTier;
effectiveness["PlayerTotalSpent"] = playerTotalSpent;
// Calculate effectiveness metrics
var playerMetrics = await GetPlayerEffectivenessMetrics(playerId, kingdomId);
effectiveness["PlayerMetrics"] = playerMetrics;
// Compare against similar spending tiers
var tierComparison = await CompareAgainstSpendingTier(playerId, kingdomId, comparisonGroup, spendingTier);
effectiveness["TierComparison"] = tierComparison;
// Calculate skill vs spending contribution
var skillVsSpendingBreakdown = CalculateSkillVsSpendingContribution(playerMetrics, playerTotalSpent);
effectiveness["SkillVsSpendingBreakdown"] = skillVsSpendingBreakdown;
// Validate 70% effectiveness threshold for free players
if (spendingTier == "Free" || spendingTier == "Light")
{
var freePlayerEffectiveness = (double)tierComparison["RelativeEffectiveness"];
var meetsThreshold = freePlayerEffectiveness >= MIN_FREE_PLAYER_EFFECTIVENESS;
effectiveness["MeetsEffectivenessThreshold"] = meetsThreshold;
effectiveness["EffectivenessGap"] = MIN_FREE_PLAYER_EFFECTIVENESS - freePlayerEffectiveness;
if (!meetsThreshold)
{
effectiveness["RecommendedSkillEnhancements"] = await GenerateSkillEnhancementRecommendations(
playerId, kingdomId, freePlayerEffectiveness);
}
}
return effectiveness;
}
public async Task<Dictionary<string, object>> ImplementBalanceAdjustmentsAsync(List<int> affectedPlayers, int kingdomId,
string balanceType, Dictionary<string, object> adjustmentParameters)
{
_logger.LogInformation("Implementing balance adjustments: {PlayerCount} players, Type: {BalanceType}",
affectedPlayers.Count, balanceType);
return await _unitOfWork.ExecuteInTransactionAsync(async () =>
{
var adjustmentResults = new Dictionary<string, object>
{
["BalanceType"] = balanceType,
["AffectedPlayerCount"] = affectedPlayers.Count,
["AdjustmentTimestamp"] = DateTime.UtcNow
};
var playerAdjustments = new Dictionary<string, object>();
foreach (var playerId in affectedPlayers)
{
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null) continue;
var playerSpending = await _purchaseLogRepository.GetPlayerPurchaseSummaryAsync(playerId, kingdomId, 30);
var spendingTier = ClassifySpendingTier((decimal)playerSpending["TotalSpent"]);
var adjustments = new Dictionary<string, object>();
switch (balanceType.ToLower())
{
case "skill_bonuses":
adjustments = await ApplySkillBasedBalanceBonuses(playerId, kingdomId, spendingTier, adjustmentParameters);
break;
case "strategic_advantages":
adjustments = await ApplyStrategicAdvantages(playerId, kingdomId, spendingTier, adjustmentParameters);
break;
case "coordination_bonuses":
adjustments = await ApplyCoordinationBonuses(playerId, kingdomId, spendingTier, adjustmentParameters);
break;
case "intelligence_bonuses":
adjustments = await ApplyIntelligenceBonuses(playerId, kingdomId, spendingTier, adjustmentParameters);
break;
default:
adjustments["Error"] = $"Unknown balance type: {balanceType}";
break;
}
playerAdjustments[$"Player_{playerId}"] = adjustments;
}
adjustmentResults["PlayerAdjustments"] = playerAdjustments;
// Validate adjustment effectiveness
var effectivenessValidation = await ValidateAdjustmentEffectiveness(affectedPlayers, kingdomId, balanceType);
adjustmentResults["EffectivenessValidation"] = effectivenessValidation;
return adjustmentResults;
});
}
public async Task<Dictionary<string, object>> ValidateVictoryOutcomeBalanceAsync(int kingdomId, TimeSpan analysisTimeframe,
string gameMode = null)
{
var validation = new Dictionary<string, object>
{
["KingdomId"] = kingdomId,
["AnalysisTimeframe"] = analysisTimeframe,
["GameMode"] = gameMode,
["AnalysisTimestamp"] = DateTime.UtcNow
};
// Get combat data for analysis period
var combatLogs = await _combatLogRepository.GetKingdomCombatLogsAsync(kingdomId, analysisTimeframe);
if (gameMode != null)
{
combatLogs = combatLogs.Where(c => c.BattleType?.Contains(gameMode) == true);
}
var totalBattles = combatLogs.Count();
validation["TotalBattlesAnalyzed"] = totalBattles;
if (totalBattles == 0)
{
validation["ValidationResult"] = "Insufficient battle data for analysis";
return validation;
}
// Analyze victory outcomes by spending patterns
var victoryAnalysis = new Dictionary<string, object>();
var spendingInfluencedVictories = 0;
var skillInfluencedVictories = 0;
var balancedVictories = 0;
foreach (var combat in combatLogs)
{
var outcomeAnalysis = await AnalyzeBattleOutcomeInfluence(combat, kingdomId);
var spendingInfluence = (double)outcomeAnalysis["SpendingInfluence"];
if (spendingInfluence > 0.7)
spendingInfluencedVictories++;
else if (spendingInfluence < 0.3)
skillInfluencedVictories++;
else
balancedVictories++;
}
var spendingInfluenceRate = (double)spendingInfluencedVictories / totalBattles;
var skillInfluenceRate = (double)skillInfluencedVictories / totalBattles;
var balancedRate = (double)balancedVictories / totalBattles;
victoryAnalysis["SpendingInfluencedRate"] = spendingInfluenceRate;
victoryAnalysis["SkillInfluencedRate"] = skillInfluenceRate;
victoryAnalysis["BalancedRate"] = balancedRate;
validation["VictoryAnalysis"] = victoryAnalysis;
// Validate against 30% threshold
var meetsThreshold = spendingInfluenceRate <= MAX_SPENDING_VICTORY_INFLUENCE;
validation["MeetsBalanceThreshold"] = meetsThreshold;
validation["ThresholdViolation"] = spendingInfluenceRate - MAX_SPENDING_VICTORY_INFLUENCE;
// Generate recommendations
var recommendations = new List<string>();
if (!meetsThreshold)
{
recommendations.Add($"Spending influence ({spendingInfluenceRate * 100:F1}%) exceeds 30% threshold");
recommendations.Add("Implement enhanced skill-based bonuses for lower spenders");
recommendations.Add("Provide additional strategic options and coordination tools");
recommendations.Add("Consider temporary balance adjustments for affected game modes");
}
else
{
recommendations.Add("Victory outcome balance within acceptable parameters");
recommendations.Add("Continue monitoring for emerging imbalance patterns");
}
validation["Recommendations"] = recommendations;
validation["OverallBalance"] = meetsThreshold ? "Balanced" : "Requires Correction";
return validation;
}
#endregion
#region VIP System Management
public async Task<(bool TierAdvanced, int NewVipTier, bool IsSecretTier, Dictionary<string, object> NewBenefits,
double ChargebackRisk, Dictionary<string, object> SkillAlternatives)>
ManageVipProgressionAsync(int playerId, int kingdomId, decimal purchaseAmount, string purchaseType)
{
_logger.LogInformation("Managing VIP progression: Player {PlayerId}, Purchase amount: {Amount}, Type: {PurchaseType}",
playerId, purchaseAmount, purchaseType);
return await _unitOfWork.ExecuteInTransactionAsync(async () =>
{
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
throw new ArgumentException($"Player {playerId} not found");
var oldVipTier = player.VipTier;
// Calculate chargeback risk
var chargebackRisk = await CalculateChargebackRisk(playerId, kingdomId, purchaseAmount, purchaseType);
// Update VIP progression (delegate to player repository for tier calculation)
var (tierUpdated, newVipTier, chargebackProtection) = await _playerRepository.UpdateVipTierAsync(
playerId, kingdomId, purchaseAmount);
var isSecretTier = newVipTier >= VIP_SECRET_TIER_THRESHOLD;
var newBenefits = new Dictionary<string, object>();
var skillAlternatives = new Dictionary<string, object>();
if (tierUpdated)
{
// Calculate new VIP benefits with balance considerations
newBenefits = await CalculateVipBenefitsWithAlternativesAsync(playerId, kingdomId, newVipTier);
// Generate skill-based alternatives for the same benefits
skillAlternatives = await GenerateVipSkillAlternatives(newVipTier, newBenefits);
// Apply chargeback protection for secret tiers
if (isSecretTier && chargebackRisk > CHARGEBACK_RISK_THRESHOLD)
{
var protection = await HandleVipChargebackProtectionAsync(playerId, kingdomId, new Dictionary<string, object>
{
["ChargebackRisk"] = chargebackRisk,
["VipTier"] = newVipTier,
["PurchaseAmount"] = purchaseAmount
});
newBenefits["ChargebackProtection"] = protection;
}
_logger.LogInformation("VIP tier advanced: Player {PlayerId}, Tier {OldTier} → {NewTier} (Secret: {IsSecret})",
playerId, oldVipTier, newVipTier, isSecretTier);
}
return (tierUpdated, newVipTier, isSecretTier, newBenefits, chargebackRisk, skillAlternatives);
});
}
public async Task<Dictionary<string, object>> CalculateVipBenefitsWithAlternativesAsync(int playerId, int kingdomId, int vipTier)
{
var benefits = new Dictionary<string, object>
{
["VipTier"] = vipTier,
["CalculationTimestamp"] = DateTime.UtcNow
};
// Calculate convenience benefits (primary VIP value)
var convenienceBenefits = new Dictionary<string, object>
{
["AutoResourceCollection"] = vipTier >= 5,
["QueueSlots"] = Math.Min(1 + (vipTier / 5), 4),
["InstantBuildingCompletion"] = Math.Min(vipTier / 10, 3), // Limited uses
["AdvancedIntelligence"] = vipTier >= 10,
["PremiumCustomization"] = vipTier >= 8,
["ExclusiveChat"] = vipTier >= 12
};
benefits["ConvenienceBenefits"] = convenienceBenefits;
// Calculate minor gameplay benefits (capped to prevent pay-to-win)
var gameplayBenefits = new Dictionary<string, object>
{
["ConstructionSpeedBonus"] = Math.Min(vipTier * 1.5, 15), // Max 15%
["ResearchSpeedBonus"] = Math.Min(vipTier * 1.5, 15), // Max 15%
["MarchSpeedBonus"] = Math.Min(vipTier * 1, 10), // Max 10%
["ResourceCapacityBonus"] = Math.Min(vipTier * 2, 20) // Max 20%
};
benefits["GameplayBenefits"] = gameplayBenefits;
// Secret tier benefits (16+) with chargeback protection
if (vipTier >= VIP_SECRET_TIER_THRESHOLD)
{
var secretBenefits = new Dictionary<string, object>
{
["SecretTierStatus"] = true,
["HiddenFromOthers"] = true,
["ExclusiveTerritoryAccess"] = true,
["PremiumSupportChannel"] = true,
["EarlyFeatureAccess"] = true
};
benefits["SecretTierBenefits"] = secretBenefits;
}
// Generate skill-based alternatives that provide similar value
var skillAlternatives = new Dictionary<string, object>
{
["AchievementBasedSpeedups"] = "Complete daily objectives for speed bonuses",
["StrategicCoordinationBonuses"] = "Alliance coordination provides equivalent bonuses",
["IntelligenceGatheringSkills"] = "Scouting mastery provides advanced intelligence",
["TerritorialMastery"] = "Territory control unlocks exclusive areas",
["CommunityLeadership"] = "Alliance roles provide exclusive communication channels"
};
benefits["SkillBasedAlternatives"] = skillAlternatives;
return benefits;
}
public async Task<(bool Success, Dictionary<string, object> AppliedBenefits, List<string> ValidationWarnings)>
ProcessVipBenefitClaimAsync(int playerId, int kingdomId, string benefitType,
Dictionary<string, object> claimParameters)
{
var player = await _playerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
return (false, new Dictionary<string, object>(), new List<string> { "Player not found" });
var validationWarnings = new List<string>();
var appliedBenefits = new Dictionary<string, object>();
// Validate VIP tier for benefit
var requiredTier = GetRequiredVipTierForBenefit(benefitType);
if (player.VipTier < requiredTier)
{
validationWarnings.Add($"VIP tier {requiredTier} required for {benefitType} (current: {player.VipTier})");
return (false, appliedBenefits, validationWarnings);
}
// Check usage limits and cooldowns
var usageLimits = await ValidateVipBenefitUsageLimits(playerId, kingdomId, benefitType);
if (!usageLimits.CanUse)
{
validationWarnings.AddRange(usageLimits.Restrictions);
return (false, appliedBenefits, validationWarnings);
}
// Process benefit claim
switch (benefitType.ToLower())
{
case "instant_completion":
appliedBenefits = await ProcessInstant