public static void CheckAICounterattackRoutine(Player personAttacking, Player bot) { // person attacking is a boss and not a psychopath, so do nothing if (personAttacking.BotId < AIStatics.PsychopathBotId) { return; } // attacking the psychopath. Random chance the psychopath will set the attacker as their target. if (bot.BotId == AIStatics.PsychopathBotId) { if (personAttacking.BotId == AIStatics.ActivePlayerBotId) { var rand = new Random(); var numAttacks = NumPsychopathCounterAttacks(bot, rand); var(mySkills, weakenSkill, _) = GetPsychopathSkills(bot); if (!mySkills.IsEmpty()) { var complete = false; for (int i = 0; i < numAttacks && !complete; i++) { var skill = SelectPsychopathSkill(personAttacking, mySkills, weakenSkill, rand); (complete, _) = AttackProcedures.Attack(bot, personAttacking, skill); } if (complete) { EquipDefeatedPlayer(bot, personAttacking); } } } var directive = AIDirectiveProcedures.GetAIDirective(bot.Id); // no previous target, so set this player as the new one if (directive.TargetPlayerId == -1 || directive.State == "idle") { AIDirectiveProcedures.SetAIDirective_Attack(bot.Id, personAttacking.Id); } // random chance to see if the attacker becomes the new target else { var rand = new Random(); var roll = rand.NextDouble(); if (roll < .08) { AIDirectiveProcedures.SetAIDirective_Attack(bot.Id, personAttacking.Id); } } } // if the target is Donna, counterattack and set that player as her target immediately if (bot.BotId == AIStatics.DonnaBotId) { BossProcedures_Donna.DonnaCounterattack(personAttacking, bot); } // Valentine counterattack if (bot.BotId == AIStatics.ValentineBotId) { BossProcedures_Valentine.CounterAttack(personAttacking, bot); } // Bimbo boss counterattack else if (bot.BotId == AIStatics.BimboBossBotId) { BossProcedures_BimboBoss.CounterAttack(personAttacking, bot); } // rat thieves counterattack else if (bot.BotId == AIStatics.MaleRatBotId || bot.BotId == AIStatics.FemaleRatBotId) { AIProcedures.DealBossDamage(bot, personAttacking, true, 1); BossProcedures_Thieves.CounterAttack(personAttacking); } // fae boss counterattack else if (bot.BotId == AIStatics.FaebossBotId) { AIProcedures.DealBossDamage(bot, personAttacking, true, 1); BossProcedures_FaeBoss.CounterAttack(personAttacking); } // motocycle boss counterattack else if (bot.BotId == AIStatics.MotorcycleGangLeaderBotId) { AIProcedures.DealBossDamage(bot, personAttacking, true, 1); BossProcedures_MotorcycleGang.CounterAttack(personAttacking, bot); } // mouse sisters counterattack else if (bot.BotId == AIStatics.MouseNerdBotId || bot.BotId == AIStatics.MouseBimboBotId) { BossProcedures_Sisters.CounterAttack(personAttacking, bot); } // demon counterattack else if (bot.BotId == AIStatics.DemonBotId) { BossProcedures_DungeonDemon.CounterAttack(bot, personAttacking); } // miniboss counterattack else if (AIStatics.IsAMiniboss(bot.BotId)) { BossProcedures_Minibosses.CounterAttack(personAttacking, bot); } }
public static List <Exception> RunPsychopathActions(WorldDetail worldDetail) { var rand = new Random(DateTime.Now.Millisecond); var errors = new List <Exception>(); IPlayerRepository playerRepo = new EFPlayerRepository(); //spawn in more bots if there are less than the default var botCount = playerRepo.Players.Count(b => b.BotId == AIStatics.PsychopathBotId && b.Mobility == PvPStatics.MobilityFull); if (botCount < PvPStatics.PsychopathDefaultAmount) { SpawnAIPsychopaths(PvPStatics.PsychopathDefaultAmount - botCount); } var bots = playerRepo.Players.Where(p => p.BotId == AIStatics.PsychopathBotId && p.Mobility == PvPStatics.MobilityFull).ToList(); foreach (var bot in bots) { try { // if bot is no longer fully animate or is null, skip them if (bot == null || bot.Mobility != PvPStatics.MobilityFull) { continue; } bot.LastActionTimestamp = DateTime.UtcNow; if (!EffectProcedures.PlayerHasActiveEffect(bot.Id, JokeShopProcedures.PSYCHOTIC_EFFECT)) { #region drop excess items var botItems = DomainRegistry.Repository.Find(new GetItemsOwnedByPsychopath { OwnerId = bot.Id }).ToList(); string[] itemTypes = { PvPStatics.ItemType_Hat, PvPStatics.ItemType_Accessory, PvPStatics.ItemType_Pants, PvPStatics.ItemType_Pet, PvPStatics.ItemType_Shirt, PvPStatics.ItemType_Shoes, PvPStatics.ItemType_Underpants, PvPStatics.ItemType_Undershirt }; foreach (var typeToDrop in itemTypes) { if (botItems.Count(i => i.ItemSource.ItemType == typeToDrop) > 1) { var dropList = botItems.Where(i => i.ItemSource.ItemType == typeToDrop).Skip(1); foreach (var i in dropList) { ItemProcedures.DropItem(i.Id); var name = "a"; if (i.FormerPlayer != null) { name = "<b>" + i.FormerPlayer.FullName + "</b> the"; } if (i.ItemSource.ItemType == PvPStatics.ItemType_Pet) { LocationLogProcedures.AddLocationLog(bot.dbLocationName, "<b>" + bot.GetFullName() + "</b> released " + name + " pet <b>" + i.ItemSource.FriendlyName + "</b> here."); } else { LocationLogProcedures.AddLocationLog(bot.dbLocationName, "<b>" + bot.GetFullName() + "</b> dropped " + name + " <b>" + i.ItemSource.FriendlyName + "</b> here."); } } } } #endregion } var botbuffs = ItemProcedures.GetPlayerBuffs(bot); var meditates = 0; // meditate if needed if (bot.Mana < bot.MaxMana * .5M) { var manaroll = (int)Math.Floor(rand.NextDouble() * 4.0D); for (var i = 0; i < manaroll; i++) { DomainRegistry.Repository.Execute(new Meditate { PlayerId = bot.Id, Buffs = botbuffs, NoValidate = true }); meditates++; } } // cleanse if needed, less if psycho has cleansed lately if (bot.Health < bot.MaxHealth * .5M) { var healthroll = (int)Math.Floor(rand.NextDouble() * 4.0D); for (var i = meditates; i < healthroll; i++) { DomainRegistry.Repository.Execute(new Cleanse { PlayerId = bot.Id, Buffs = botbuffs, NoValidate = true }); } } var directive = AIDirectiveProcedures.GetAIDirective(bot.Id); // the bot has an attack target, so go chase it if (directive.State == "attack") { var myTarget = PlayerProcedures.GetPlayer(directive.TargetPlayerId); var(mySkills, weakenSkill, inanimateSkill) = GetPsychopathSkills(bot); // if the target is offline, no longer animate, in the dungeon, or in the same form as the spells' target, go into idle mode if (PlayerProcedures.PlayerIsOffline(myTarget) || myTarget.Mobility != PvPStatics.MobilityFull || mySkills.IsEmpty() || inanimateSkill == null || myTarget.FormSourceId == inanimateSkill.StaticSkill.FormSourceId || myTarget.IsInDungeon() || myTarget.InDuel > 0 || myTarget.InQuest > 0) { AIDirectiveProcedures.SetAIDirective_Idle(bot.Id); } // the target is okay for attacking else { // the bot must move to its target location. if (myTarget.dbLocationName != bot.dbLocationName) { if (botbuffs.MoveActionPointDiscount() > -100 && CanMove(worldDetail, myTarget)) { var maxSpaces = NumPsychopathMoveSpaces(bot); var newplace = MoveTo(bot, myTarget.dbLocationName, maxSpaces); bot.dbLocationName = newplace; } } // if the bot is now in the same place as the target, attack away, so long as the target is online and animate if (bot.dbLocationName == myTarget.dbLocationName && !PlayerProcedures.PlayerIsOffline(myTarget) && myTarget.Mobility == PvPStatics.MobilityFull && CanAttack(worldDetail, bot, myTarget) ) { playerRepo.SavePlayer(bot); var numAttacks = Math.Min(3, (int)(bot.Mana / PvPStatics.AttackManaCost)); var complete = false; for (var attackIndex = 0; attackIndex < numAttacks && !complete; ++attackIndex) { var skill = SelectPsychopathSkill(myTarget, mySkills, weakenSkill, rand); (complete, _) = AttackProcedures.Attack(bot, myTarget, skill); } if (complete) { EquipDefeatedPlayer(bot, myTarget); } } } } // the bot has no target, so wander and try to find new targets and attack them. else { if (botbuffs.MoveActionPointDiscount() > -100) { var newplace = MoveTo(bot, LocationsStatics.GetRandomLocationNotInDungeon(), 5); bot.dbLocationName = newplace; } // attack stage var playersHere = playerRepo.Players.Where (p => p.dbLocationName == bot.dbLocationName && p.Mobility == PvPStatics.MobilityFull && p.Id != bot.Id && p.BotId == AIStatics.PsychopathBotId && p.Level >= bot.Level).ToList(); // filter out offline players and Lindella var onlinePlayersHere = playersHere.Where(p => !PlayerProcedures.PlayerIsOffline(p)).ToList(); if (onlinePlayersHere.Count > 0) { var roll = Math.Floor(rand.NextDouble() * onlinePlayersHere.Count); var victim = onlinePlayersHere.ElementAt((int)roll); AIDirectiveProcedures.SetAIDirective_Attack(bot.Id, victim.Id); playerRepo.SavePlayer(bot); var(mySkills, weakenSkill, inanimateSkill) = GetPsychopathSkills(bot); if (!mySkills.IsEmpty()) { var numAttacks = Math.Min(3, (int)(bot.Mana / PvPStatics.AttackManaCost)); var complete = false; for (var attackIndex = 0; attackIndex < numAttacks && !complete; ++attackIndex) { var skill = SelectPsychopathSkill(victim, mySkills, weakenSkill, rand); (complete, _) = AttackProcedures.Attack(bot, victim, skill); } if (complete) { EquipDefeatedPlayer(bot, victim); } } } } playerRepo.SavePlayer(bot); } catch (Exception e) { errors.Add(e); } } return(errors); }
public static (bool, string) Attack(Player attackingPlayer, Player attackedPlayer, SkillViewModel skillBeingUsed, bool timestamp = true) { var result = ""; var attacker = PlayerProcedures.GetPlayer(attackingPlayer.Id); var victim = PlayerProcedures.GetPlayer(attackedPlayer.Id); if (victim.Mobility != PvPStatics.MobilityFull || attacker.Mobility != PvPStatics.MobilityFull || victim.GameMode == (int)GameModeStatics.GameModes.Invisible || attacker.GameMode == (int)GameModeStatics.GameModes.Invisible) { return(false, ""); } var complete = false; var logs = new LogBox(); // all of our checks seem to be okay. So let's lower the player's mana and action points PlayerProcedures.ChangePlayerActionMana(-PvPStatics.AttackCost, 0, -PvPStatics.AttackManaCost, attacker.Id, timestamp); PlayerProcedures.LogCombatTimestampsAndAddAttackCount(victim, attacker); var attackerFullName = attacker.GetFullName(); var victimFullName = victim.GetFullName(); // if the spell is a curse, give the effect and that's all if (skillBeingUsed.StaticSkill.GivesEffectSourceId != null) { var effectBeingGiven = EffectStatics.GetDbStaticEffect(skillBeingUsed.StaticSkill.GivesEffectSourceId.Value); EffectProcedures.GivePerkToPlayer(skillBeingUsed.StaticSkill.GivesEffectSourceId.Value, victim); if (attacker.Gender == PvPStatics.GenderMale && !effectBeingGiven.AttackerWhenHit_M.IsNullOrEmpty()) { logs.AttackerLog += effectBeingGiven.AttackerWhenHit_M; } else if (attacker.Gender == PvPStatics.GenderFemale && !effectBeingGiven.AttackerWhenHit_F.IsNullOrEmpty()) { logs.AttackerLog += effectBeingGiven.AttackerWhenHit_F; } else { logs.AttackerLog += effectBeingGiven.AttackerWhenHit; } if (!String.IsNullOrEmpty(logs.AttackerLog)) { logs.AttackerLog += "<br><br>"; } logs.LocationLog = "<span class='playerAttackNotification'>" + attackerFullName + " cursed " + victimFullName + " with " + skillBeingUsed.StaticSkill.FriendlyName + ".</span>"; logs.AttackerLog += "You cursed " + victimFullName + " with " + skillBeingUsed.StaticSkill.FriendlyName + "."; logs.AttackerLog += " (+1 XP) "; logs.AttackerLog += PlayerProcedures.GiveXP(attacker, 1); logs.VictimLog = effectBeingGiven.MessageWhenHit; logs.VictimLog += " <span class='playerAttackNotification'>" + attackerFullName + " cursed you with <b>" + skillBeingUsed.StaticSkill.FriendlyName + "</b>.</b></span> "; result = logs.AttackerLog; } // the spell is a regular attack else { logs.LocationLog = "<span class='playerAttackNotification'>" + attackerFullName + " cast " + skillBeingUsed.StaticSkill.FriendlyName + " against " + victimFullName + ".</span>"; logs.AttackerLog = "You cast " + skillBeingUsed.StaticSkill.FriendlyName + " against " + victimFullName + ". "; logs.VictimLog = "<span class='playerAttackNotification'>" + attackerFullName + " cast " + skillBeingUsed.StaticSkill.FriendlyName + " against you.</span> "; var meBuffs = ItemProcedures.GetPlayerBuffs(attacker); var targetedBuffs = ItemProcedures.GetPlayerBuffs(victim); var rand = new Random(Guid.NewGuid().GetHashCode()); var basehitChance = rand.NextDouble() * 100; var meDmgExtra = meBuffs.SpellExtraHealthDamagePercent(); var criticalMissPercentChance = PvPStatics.CriticalMissPercentChance - meBuffs.SpellMisfireChanceReduction(); var criticalPercentChance = meBuffs.ExtraSkillCriticalPercent() + PvPStatics.CriticalHitPercentChance; var evasionPercentChance = targetedBuffs.EvasionPercent() - meBuffs.EvasionNegationPercent(); var evasionUpgrade = false; var failedAttack = false; // clamp modifiedEvasion at 50% max if (evasionPercentChance > 50) { evasionPercentChance = 50; } // critical miss! damage caster instead if (basehitChance < (double)criticalMissPercentChance) { // check if there is a health damage aspect to this spell if (skillBeingUsed.StaticSkill.HealthDamageAmount > 0) { var amountToDamage = skillBeingUsed.StaticSkill.HealthDamageAmount * (1 + meDmgExtra / 100); PlayerProcedures.DamagePlayerHealth(attacker.Id, amountToDamage); logs.AttackerLog += $"Misfire! Your spell accidentally lowered your own willpower by {amountToDamage:N2}. "; logs.VictimLog += $"Misfire! {GetPronoun_HisHer(attacker.Gender)} spell accidentally lowered {GetPronoun_hisher(attacker.Gender)} own willpower by {amountToDamage:N2}."; result += logs.AttackerLog; } failedAttack = true; } // spell is evaded else if (basehitChance < (double)criticalMissPercentChance + (double)evasionPercentChance) { // Check for a crit to upgrade the miss to a hit var criticalHitChance = rand.NextDouble() * 100; if (criticalHitChance < (double)criticalPercentChance) { evasionUpgrade = true; } else { logs.AttackerLog += victimFullName + " managed to leap out of the way of your spell."; logs.VictimLog += "You managed to leap out of the way " + attackerFullName + "'s spell."; result = logs.AttackerLog; failedAttack = true; } } // not a miss, so let's deal some damage, possibly if (!failedAttack) { decimal criticalModifier = 1; if (evasionUpgrade) { logs.AttackerLog += "<b>Piercing hit!</b> "; logs.VictimLog += "<b>Piercing hit!</b> "; } else if (rand.NextDouble() * 100 < (double)criticalPercentChance) { criticalModifier = 2; logs.AttackerLog += "<b>Critical hit!</b> "; logs.VictimLog += "<b>Critical hit!</b> "; } var initialVictimHealth = victim.Health; // check if there is a health damage aspect to this spell if (skillBeingUsed.StaticSkill.HealthDamageAmount > 0) { var targetProt = targetedBuffs.SpellHealthDamageResistance(); // calculator the modifier as extra attack - defense. 15 - 20 = -5 modifier var willpowerDamageModifierFromBonuses = 1 + ((meDmgExtra - targetProt) / 100.0M); // cap the modifier at at 50 % IF the target is a human if (willpowerDamageModifierFromBonuses < .5M) { willpowerDamageModifierFromBonuses = .5M; } // cap the modifier at 200 % IF the target is a human if (willpowerDamageModifierFromBonuses > 2 && victim.BotId == AIStatics.ActivePlayerBotId) { willpowerDamageModifierFromBonuses = 2; } var totalHealthDamage = skillBeingUsed.StaticSkill.HealthDamageAmount * willpowerDamageModifierFromBonuses * criticalModifier; // make sure damage is never in the negatives (which would heal instead) if (totalHealthDamage < 0) { totalHealthDamage = 0; } PlayerProcedures.DamagePlayerHealth(victim.Id, totalHealthDamage); // even though it's been done in the db, change the player health here as well victim.Health -= totalHealthDamage; logs.AttackerLog += $"Your spell lowered {GetPronoun_hisher(victim.Gender)} willpower by {Math.Round(totalHealthDamage, 2)}. "; logs.VictimLog += $"{GetPronoun_HisHer(attacker.Gender)} spell lowered your willpower by {Math.Round(totalHealthDamage, 2)}. "; result += logs.AttackerLog; } // if this skill has any TF power, add energy and check for form change if (skillBeingUsed.StaticSkill.TFPointsAmount > 0) { var TFEnergyDmg = meBuffs.SpellExtraTFEnergyPercent(); var TFEnergyArmor = targetedBuffs.SpellTFEnergyDamageResistance(); // calculator the modifier as extra attack - defense. var tfEnergyDamageModifierFromBonuses = 1 + ((TFEnergyDmg - TFEnergyArmor) / 100.0M); // cap the modifier at at 50 % IF the target is a human if (tfEnergyDamageModifierFromBonuses < .5M) { tfEnergyDamageModifierFromBonuses = .5M; } // cap the modifier at at 200 % IF the target is a human if (tfEnergyDamageModifierFromBonuses > 2 && victim.BotId == AIStatics.ActivePlayerBotId) { tfEnergyDamageModifierFromBonuses = 2; } var totalTFEnergyModifier = criticalModifier * tfEnergyDamageModifierFromBonuses; LogBox tfEnergyResult; (complete, tfEnergyResult) = TFEnergyProcedures.AddTFEnergyToPlayer(victim, attacker, skillBeingUsed, totalTFEnergyModifier, initialVictimHealth); logs.Add(tfEnergyResult); result = logs.AttackerLog; } } } LocationLogProcedures.AddLocationLog(attacker.dbLocationName, logs.LocationLog); PlayerLogProcedures.AddPlayerLog(attacker.Id, logs.AttackerLog, false); PlayerLogProcedures.AddPlayerLog(victim.Id, logs.VictimLog, true); DomainRegistry.AttackNotificationBroker.Notify(victim.Id, logs.VictimLog); // if this is a psycho-on-psycho battle, have a chance for the victim bot to switch targets to the attacker bot if (attacker.BotId == AIStatics.PsychopathBotId && victim.BotId == AIStatics.PsychopathBotId) { var rand = new Random(Guid.NewGuid().GetHashCode()); var botAggroRoll = rand.NextDouble(); if (botAggroRoll < .08) { AIDirectiveProcedures.SetAIDirective_Attack(victim.Id, attacker.Id); } } return(complete, result); }
public static void UpdateWorld() { var worldStats = DomainRegistry.Repository.FindSingle(new GetWorld()); var turnNo = worldStats.TurnNumber; PvPStatics.LastGameTurn = turnNo; if (turnNo < PvPStatics.RoundDuration) { PvPStatics.AnimateUpdateInProgress = true; IServerLogRepository serverLogRepo = new EFServerLogRepository(); var log = new ServerLog { TurnNumber = turnNo, StartTimestamp = DateTime.UtcNow, FinishTimestamp = DateTime.UtcNow, Errors = 0, FullLog = "", Population = PlayerProcedures.GetWorldPlayerStats().CurrentOnlinePlayers, }; log.AddLog($"Started new log for turn {turnNo} at <b>{DateTime.UtcNow}</b>."); serverLogRepo.SaveServerLog(log); var updateTimer = new Stopwatch(); updateTimer.Start(); IPlayerRepository playerRepo = new EFPlayerRepository(); Player lindella = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.LindellaBotId); Player wuffie = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.WuffieBotId); #region spawn NPCS // make sure the NPCs have been spawned early turn if (turnNo <= 3) { if (lindella == null) { BossProcedures_Lindella.SpawnLindella(); lindella = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.LindellaBotId); } if (wuffie == null) { BossProcedures_PetMerchant.SpawnPetMerchant(); wuffie = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.WuffieBotId); } var fae = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.JewdewfaeBotId); if (fae == null) { BossProcedures_Jewdewfae.SpawnFae(); } var bartender = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.BartenderBotId); if (bartender == null) { BossProcedures_Bartender.SpawnBartender(); } var lorekeeper = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.LoremasterBotId); if (lorekeeper == null) { BossProcedures_Loremaster.SpawnLoremaster(); } var soulbinder = playerRepo.Players.FirstOrDefault(p => p.BotId == AIStatics.SoulbinderBotId); if (soulbinder == null) { var id = DomainRegistry.Repository.Execute(new CreatePlayer { FirstName = "Karin", LastName = "Kezesul-Adriz the Soulbinder", FormSourceId = 1000, Location = "stripclub_office", Level = 10, Mobility = PvPStatics.MobilityFull, Money = 0, Gender = PvPStatics.GenderFemale, Health = 9999, MaxHealth = 9999, Mana = 9999, MaxMana = 9999, OnlineActivityTimestamp = DateTime.UtcNow, BotId = AIStatics.SoulbinderBotId }); var newSoulbinder = playerRepo.Players.FirstOrDefault(p => p.Id == id); newSoulbinder.ReadjustMaxes(ItemProcedures.GetPlayerBuffs(newSoulbinder)); playerRepo.SavePlayer(newSoulbinder); } } #endregion log.AddLog(updateTimer.ElapsedMilliseconds + ": Started loading animate players"); IEffectRepository effectRepo = new EFEffectRepository(); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started loading effects"); var temporaryEffects = effectRepo.Effects.Where(e => !e.IsPermanent).ToList(); log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished loading effects"); var effectsToDelete = new List <Effect>(); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started updating effects"); var effectsExpiringThisTurn = temporaryEffects.Where(e => e.Duration == 1).ToList(); var activeOrExpiringEffects = temporaryEffects.Where(e => e.Duration > 0).ToList(); foreach (var e in temporaryEffects) { e.Duration--; e.Cooldown--; if (e.Duration < 0) { e.Duration = 0; } if (e.Cooldown <= 0) { effectsToDelete.Add(e); } else { effectRepo.SaveEffect(e); } } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished updating effects"); serverLogRepo.SaveServerLog(log); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started running effect-expiry actions"); try { JokeShopProcedures.RunEffectExpiryActions(effectsExpiringThisTurn); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, " ERROR running effect-expiry actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished running effect-expiry actions"); serverLogRepo.SaveServerLog(log); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started deleting expired effects"); foreach (var e in effectsToDelete) { effectRepo.DeleteEffect(e.Id); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished deleting expired effects"); serverLogRepo.SaveServerLog(log); #region playerExtra / protection cooldown loop IPlayerExtraRepository playerExtraRepo = new EFPlayerExtraRepository(); var extrasToIncrement = playerExtraRepo.PlayerExtras.ToList(); var extrasToIncrement_SaveList = new List <PlayerExtra>(); var extrasToIncrement_DeleteList = new List <PlayerExtra>(); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started updating protection change cooldown (" + extrasToIncrement.Count + ")"); foreach (var e in extrasToIncrement) { var owner = PlayerProcedures.GetPlayer(e.PlayerId); if (PlayerProcedures.PlayerIsOffline(owner)) { extrasToIncrement_SaveList.Add(e); } } foreach (var e in extrasToIncrement_SaveList) { if (e.ProtectionToggleTurnsRemaining > 0) { e.ProtectionToggleTurnsRemaining--; playerExtraRepo.SavePlayerExtra(e); } else if (e.ProtectionToggleTurnsRemaining <= 0) { extrasToIncrement_DeleteList.Add(e); } } foreach (var e in extrasToIncrement_DeleteList) { playerExtraRepo.DeletePlayerExtra(e.Id); } #endregion log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished updating protection change cooldown."); #region main player loop using (var context = new StatsContext()) { try { context.Database.ExecuteSqlCommand("UPDATE [dbo].[Players] SET TimesAttackingThisUpdate = 0, CleansesMeditatesThisRound = 0, ShoutsRemaining = 1, ActionPoints = ActionPoints + 10, ItemsUsedThisTurn = 0 WHERE Mobility='full'" + $"UPDATE [dbo].[Players] SET ActionPoints_Refill = ActionPoints_Refill + (ActionPoints % {TurnTimesStatics.GetActionPointLimit()} / 2) WHERE ActionPoints >= {TurnTimesStatics.GetActionPointLimit()} AND Mobility='full'" + $"UPDATE [dbo].[Players] SET ActionPoints = {TurnTimesStatics.GetActionPointLimit()} WHERE ActionPoints > {TurnTimesStatics.GetActionPointLimit()} AND Mobility='full'" + $"UPDATE [dbo].[Players] SET ActionPoints_Refill = {TurnTimesStatics.GetActionPointReserveLimit()} WHERE ActionPoints_Refill > {TurnTimesStatics.GetActionPointReserveLimit()} AND Mobility='full'" + $"UPDATE [dbo].[Players] SET ActionPoints = ActionPoints + 20, ActionPoints_Refill = ActionPoints_Refill - 20 WHERE ActionPoints <= {TurnTimesStatics.GetActionPointLimit()-20} AND ActionPoints_Refill >= 20 AND Mobility='full'"); if (PvPStatics.ChaosMode) { context.Database.ExecuteSqlCommand($"Update [dbo].[Players] SET ActionPoints = {TurnTimesStatics.GetActionPointLimit()}, ActionPoints_Refill = {TurnTimesStatics.GetActionPointReserveLimit()}, Mana = MaxMana"); } log.AddLog(updateTimer.ElapsedMilliseconds + ": ANIMATE SQL UPDATE SUCCEEDED!"); serverLogRepo.SaveServerLog(log); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, " ANIMATE SQL UPDATE FAILED", e)); serverLogRepo.SaveServerLog(log); } } var timeCutoff = DateTime.UtcNow.AddHours(-8); var playersToSave = playerRepo.Players .Where(p => p.Mobility == PvPStatics.MobilityFull && p.LastActionTimestamp > timeCutoff).ToList(); foreach (var player in playersToSave) { //Skip regen stuff for those in quests. if (player.InQuest > 0) { continue; } // extra AP condition checks if (player.Covenant > 0) { var playerCov = CovenantDictionary.IdNameFlagLookup.FirstOrDefault(c => c.Key == player.Covenant).Value; // give this player an extra AP refill if they are at their safeground, scaled up by level if (playerCov != null && !playerCov.HomeLocation.IsNullOrEmpty() && player.dbLocationName == playerCov.HomeLocation) { player.ActionPoints_Refill += .25M * playerCov.CovLevel; } // give this player an extra AP refill if they are on a location that their covenane has enchanted var currentLocation = LocationsStatics.LocationList.GetLocation.FirstOrDefault(l => l.dbName == player.dbLocationName); if (currentLocation != null && currentLocation.CovenantController == player.Covenant) { if (currentLocation.TakeoverAmount < 25) { player.ActionPoints_Refill += .05M; } else if (currentLocation.TakeoverAmount <= 50) { player.ActionPoints_Refill += .10M; } else if (currentLocation.TakeoverAmount <= 75) { player.ActionPoints_Refill += .15M; } else if (currentLocation.TakeoverAmount < 100) { player.ActionPoints_Refill += .20M; } else if (currentLocation.TakeoverAmount >= 100) { player.ActionPoints_Refill += .25M; } } // make sure AP reserve stays within maximum amount if (player.ActionPoints_Refill > TurnTimesStatics.GetActionPointReserveLimit()) { player.ActionPoints_Refill = TurnTimesStatics.GetActionPointReserveLimit(); } } var buffs = ItemProcedures.GetPlayerBuffs(player); player.Health += buffs.HealthRecoveryPerUpdate(); player.Mana += buffs.ManaRecoveryPerUpdate(); player.ReadjustMaxes(buffs); playerRepo.SavePlayer(player); } log.AddLog($"{updateTimer.ElapsedMilliseconds}: Finished updating animate players ({playersToSave.Count})"); serverLogRepo.SaveServerLog(log); #endregion main player loop log.AddLog(updateTimer.ElapsedMilliseconds + ": Started updating inanimate/animal players"); using (var context = new StatsContext()) { try { context.Database.ExecuteSqlCommand("UPDATE [dbo].[Players] SET TimesAttackingThisUpdate = 0, ItemsUsedThisTurn = 0 WHERE (Mobility = 'inanimate' OR Mobility = 'animal') AND BotId = 0"); log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished updating inanimate/animal players"); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, " ERROR UPDATING INANIMATE/ANIMAL PLAYERS", e)); } } serverLogRepo.SaveServerLog(log); #region decrement mind control timers log.AddLog(updateTimer.ElapsedMilliseconds + ": Started mind control cooldown."); using (var context = new StatsContext()) { try { context.Database.ExecuteSqlCommand("UPDATE [dbo].[MindControls] SET TurnsRemaining = TurnsRemaining - 1"); context.Database.ExecuteSqlCommand("DELETE FROM [dbo].[MindControls] WHERE TurnsRemaining <= 0"); context.Database.ExecuteSqlCommand("UPDATE [dbo].[MindControls] SET TimesUsedThisTurn = 0"); log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished mind control cooldown."); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "MIND CONTROL COOLDOWN UPDATE FAILED", e)); } } #endregion serverLogRepo.SaveServerLog(log); PvPStatics.AnimateUpdateInProgress = false; // bump down the timer on all items that are reuseable consumables log.AddLog(updateTimer.ElapsedMilliseconds + ": Started updating items on cooldown"); IItemRepository itemsRepo = new EFItemRepository(); var itemsToUpdate = itemsRepo.Items.Where(i => i.TurnsUntilUse > 0).ToList(); foreach (var item in itemsToUpdate) { item.TurnsUntilUse--; } foreach (var item in itemsToUpdate) { itemsRepo.SaveItem(item); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished updating items on cooldown"); serverLogRepo.SaveServerLog(log); // find the ids for the merchants Lindella and Skaldyr var skaldyr = PlayerProcedures.GetPlayerFromBotId(AIStatics.LoremasterBotId); var soulbinderId = PlayerProcedures.GetPlayerFromBotId(AIStatics.SoulbinderBotId).Id; // have abandoned items go to Lindella if (turnNo % 11 == 3 && lindella.Mobility == PvPStatics.MobilityFull) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started collecting all abandoned items for Lindella"); using (var context = new StatsContext()) { try { context.Database.ExecuteSqlCommand($"UPDATE [dbo].[Items] SET OwnerId = {lindella.Id}, dbLocationName = '', PvPEnabled = {(int)GameModeStatics.GameModes.Any}, TimeDropped = '{DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")}' " + $"FROM DbStaticItems WHERE dbLocationName <> '' AND dbLocationName IS NOT NULL AND TimeDropped < DATEADD(hour, -8, GETUTCDATE()) AND OwnerId IS NULL AND DbStaticItems.Id = Items.ItemSourceId AND ItemType != '{PvPStatics.ItemType_Pet}' AND ItemSourceId != {ItemStatics.ItemType_DungeonArtifactItemSourceId};" + $"UPDATE [dbo].[Players] SET dbLocationName = '' FROM Items WHERE Items.FormerPlayerId = Players.Id AND Items.OwnerId = {lindella.Id};"); log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished collecting all abandoned items for Lindella"); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR collecting all abandoned items for Lindella", e)); } } } // delete all consumable type items that have been sitting around on the ground for too long log.AddLog(updateTimer.ElapsedMilliseconds + ": Started deleting expired consumables"); DomainRegistry.Repository.Execute(new DeleteExpiredConsumablesOnGround()); DomainRegistry.Repository.Execute(new DeleteExpiredConsumablesOnMerchants { LindellaId = lindella.Id, LorekeeperId = skaldyr.Id }); log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished deleting expired consumables"); serverLogRepo.SaveServerLog(log); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started deleting expired runes"); try { DomainRegistry.Repository.Execute(new DeleteExpiredRunesOnMerchants()); } catch (Exception e) { log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR deleting expired runes", e)); log.Errors++; } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished deleting expired runes"); serverLogRepo.SaveServerLog(log); // allow all items that have been recently equipped to be taken back off log.AddLog(updateTimer.ElapsedMilliseconds + ": Started resetting items that have been recently equipped"); var recentlyEquipped = itemsRepo.Items.Where(i => i.EquippedThisTurn).ToList(); foreach (var item in recentlyEquipped) { item.EquippedThisTurn = false; itemsRepo.SaveItem(item); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished resetting items that have been recently equipped"); #region give covenants money based on territories if (turnNo % 6 == 0) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started giving covenants money from territories"); ICovenantRepository covRepo = new EFCovenantRepository(); var covs = covRepo.Covenants.Where(c => c.HomeLocation != null && c.HomeLocation != "").ToList(); foreach (var c in covs) { var locationControlledSum = CovenantProcedures.GetLocationControlCount(c); var moneyGain = (decimal)Math.Floor(Convert.ToDouble(locationControlledSum)); c.Money += moneyGain; if (moneyGain > 0) { CovenantProcedures.WriteCovenantLog("Your covenant collected " + moneyGain + " Arpeyjis from the locations you have enchanted.", c.Id, false); } covRepo.SaveCovenant(c); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished giving covenants money from territories"); } #endregion serverLogRepo.SaveServerLog(log); #region drop dungeon artifacts and spawn demons if needed try { if (turnNo % 7 == 2) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Starting dungeon item / demon spawning"); var dungeonArtifactCount = itemsRepo.Items.Count(i => i.ItemSourceId == ItemStatics.ItemType_DungeonArtifactItemSourceId); for (var x = 0; x < PvPStatics.DungeonArtifact_SpawnLimit - dungeonArtifactCount; x++) { var randDungeon = LocationsStatics.GetRandomLocation_InDungeon(); var cmd = new CreateItem { dbLocationName = randDungeon, OwnerId = null, EquippedThisTurn = false, IsPermanent = true, Level = 0, PvPEnabled = 2, IsEquipped = false, TurnsUntilUse = 0, ItemSourceId = ItemStatics.ItemType_DungeonArtifactItemSourceId }; DomainRegistry.Repository.Execute(cmd); } IEnumerable <Player> demons = playerRepo.Players.Where(i => i.FormSourceId == PvPStatics.DungeonDemonFormSourceId); var dungeonDemonCount = demons.Count(); var randLevel = new Random(Guid.NewGuid().GetHashCode()); var demonNames = XmlResourceLoader.Load <List <string> >("TT.Domain.XMLs.DungeonDemonNames.xml"); for (var x = 0; x < PvPStatics.DungeonDemon_Limit - dungeonDemonCount; x++) { var randDungeon = LocationsStatics.GetRandomLocation_InDungeon(); // pull a random last demon name double maxDemonNameCount = demonNames.Count(); var num = randLevel.NextDouble(); var demonIndex = Convert.ToInt32(Math.Floor(num * maxDemonNameCount)); var demonlastName = demonNames.ElementAt(demonIndex); // if there's already a demon with this last name, reroll and try again if (demons.FirstOrDefault(d => d.LastName == demonlastName) != null) { x--; continue; } var levelRoll = randLevel.NextDouble(); var level = (int)Math.Floor(levelRoll * 8 + 3); var cmd = new CreatePlayer { BotId = AIStatics.DemonBotId, FirstName = "Spirit of ", LastName = demonlastName, Mobility = PvPStatics.MobilityFull, FormSourceId = AIStatics.DungeonDemonFormId, Gender = PvPStatics.GenderFemale, GameMode = 2, Health = 10000, Mana = 10000, Covenant = -1, Location = randDungeon, Level = level, MaxHealth = 10000, MaxMana = 10000, }; var id = DomainRegistry.Repository.Execute(cmd); var newDemon = playerRepo.Players.FirstOrDefault(p => p.Id == id); if (cmd.Level <= 5) { ItemProcedures.GiveNewItemToPlayer(newDemon, ItemStatics.SpellbookMediumItemSourceId); } else if (cmd.Level <= 7) { ItemProcedures.GiveNewItemToPlayer(newDemon, ItemStatics.SpellbookLargeItemSourceId); } else if (cmd.Level > 7) { ItemProcedures.GiveNewItemToPlayer(newDemon, ItemStatics.SpellbookGiantItemSourceId); } newDemon.ReadjustMaxes(ItemProcedures.GetPlayerBuffs(newDemon)); playerRepo.SavePlayer(newDemon); } log.AddLog(updateTimer.ElapsedMilliseconds + ": FINISHED dungeon item / demon spawning"); } } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR running dungeon actions", e)); } #endregion serverLogRepo.SaveServerLog(log); #region forcibly terminate duels that have timed out log.AddLog(updateTimer.ElapsedMilliseconds + ": Started duel updates"); try { IDuelRepository duelRepo = new EFDuelRepository(); var duels = duelRepo.Duels.Where(d => d.Status == DuelProcedures.ACTIVE).ToList(); foreach (var d in duels) { // if the duel has timed out, end it forcibly if ((turnNo - d.StartTurn) >= PvPStatics.MaximumDuelTurnLength) { DuelProcedures.EndDuel(d.Id, DuelProcedures.TIMEOUT); } } log.AddLog(updateTimer.ElapsedMilliseconds + ": Successfully completed duel updates"); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR completing duel updates", e)); } #endregion duel updates log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Lindella actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_Lindella.RunActions(turnNo); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR running Lindella action", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Lindella actions"); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Wuffie actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_PetMerchant.RunPetMerchantActions(turnNo); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR running Wuffie actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Wuffie actions"); log.AddLog(updateTimer.ElapsedMilliseconds + ": Started running effect-related actions"); try { JokeShopProcedures.RunEffectActions(activeOrExpiringEffects); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, " ERROR running effect-related actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished running effect-related actions"); serverLogRepo.SaveServerLog(log); #region furniture if (turnNo % 6 == 0) { // move some furniture around on the market try { FurnitureProcedures.MoveFurnitureOnMarket(); } catch (Exception e) { log.Errors++; log.AddLog(updateTimer.ElapsedMilliseconds + "ERROR MOVING FURNITURE ON MARKET: " + e); } // move Jewdewfae to a new location if she has been in one place for more than 48 turns, 8 hours try { var fae = PlayerProcedures.GetPlayerFromBotId(-6); var faeAI = AIDirectiveProcedures.GetAIDirective(fae.Id); // if the turn since her last move has been long enough, relocate her if (turnNo - (int)faeAI.Var2 > 48) { log.AddLog(updateTimer.ElapsedMilliseconds + ": FORCED JEWDEWFAE TO MOVE."); BossProcedures_Jewdewfae.MoveToNewLocation(); } } catch (Exception e) { log.Errors++; log.AddLog(updateTimer.ElapsedMilliseconds + "ERROR TRYING TO MOVE JEWDEWFAE: " + e); } } #endregion furniture #region bosses // DONNA if (worldStats.Boss_Donna == AIStatics.ACTIVE) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Donna actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_Donna.RunDonnaActions(); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running Donna actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Donna actions"); } // VALENTINE if (worldStats.Boss_Valentine == AIStatics.ACTIVE || PlayerProcedures.GetAnimatePlayerFromBotId(AIStatics.ValentineBotId) != null) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Valentine actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_Valentine.RunValentineActions(); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running Valentine actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Valentine actions"); } // BIMBO if (worldStats.Boss_Bimbo == AIStatics.ACTIVE) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Bimbo actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_BimboBoss.RunActions(turnNo); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running Bimbo actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Bimbo actions"); } // THIEVES if (worldStats.Boss_Thief == AIStatics.ACTIVE) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Thieves actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_Thieves.RunThievesAction(turnNo); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running Thieves actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Thieves actions"); } // SISTERS if (worldStats.Boss_Sisters == AIStatics.ACTIVE) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Sisters actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_Sisters.RunSistersAction(); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running Sisters actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Sisters actions"); } // FAEBOSS if (worldStats.Boss_Faeboss == AIStatics.ACTIVE) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started Narcissa actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_FaeBoss.RunTurnLogic(); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running Narcissa actions", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished Narcissa actions"); } // BIKER GANG BOSS if (worldStats.Boss_MotorcycleGang == AIStatics.ACTIVE) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started BikerBoss actions"); serverLogRepo.SaveServerLog(log); try { BossProcedures_MotorcycleGang.RunTurnLogic(); } catch (Exception e) { log.Errors++; log.AddLog(updateTimer.ElapsedMilliseconds + ": BikerBoss ERROR: " + e); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished BikerBoss actions"); } #endregion bosses // psychopaths log.AddLog(updateTimer.ElapsedMilliseconds + ": Started psychopath actions"); serverLogRepo.SaveServerLog(log); var psychoExceptions = AIProcedures.RunPsychopathActions(worldStats); foreach (var e in psychoExceptions) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running pycho action", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished psychopath actions"); // minibosses log.AddLog(updateTimer.ElapsedMilliseconds + ": Started miniboss actions"); serverLogRepo.SaveServerLog(log); var minibossExceptions = BossProcedures_Minibosses.RunAll(worldStats.TurnNumber); foreach (var e in minibossExceptions) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Error running miniboss action", e)); } serverLogRepo.SaveServerLog(log); log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished miniboss actions"); log.AddLog(updateTimer.ElapsedMilliseconds + ": Starting setting update status to done"); try { PvPWorldStatProcedures.UpdateWorldTurnCounter_UpdateDone(); log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished setting update status to done"); } catch (Exception e) { log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR setting update status to done: ", e)); log.Errors++; } serverLogRepo.SaveServerLog(log); try { log.AddLog(updateTimer.ElapsedMilliseconds + ": Started stored procedure maintenance"); using (var context = new StatsContext()) { context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[LocationLogs] WHERE Timestamp < DATEADD(hour, -1, GETUTCDATE())"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[Messages] WHERE Timestamp < DATEADD(hour, -72, GETUTCDATE()) AND DoNotRecycleMe = 0"); context.Database.ExecuteSqlCommand("DELETE FROM [dbo].[TFEnergies] WHERE Amount < .5"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[PlayerLogs] WHERE Timestamp < DATEADD(hour, -72, GETUTCDATE())"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[ChatLogs] WHERE Timestamp < DATEADD(hour, -72, GETUTCDATE())"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[AIDirectives] WHERE Timestamp < DATEADD(hour, -72, GETUTCDATE()) AND DoNotRecycleMe = 0"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[CovenantLogs] WHERE Timestamp < DATEADD(hour, -72, GETUTCDATE())"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[RPClassifiedAds] WHERE RefreshTimestamp < DATEADD(hour, -72, GETUTCDATE())"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[TFEnergies] WHERE Timestamp < DATEADD(hour, -72, GETUTCDATE())"); context.Database.ExecuteSqlCommand( "DELETE FROM [dbo].[SelfRestoreEnergies] WHERE Timestamp < DATEADD(hour, -4, GETUTCDATE())"); // move soulbound items on the ground to the soulbinding NPC. context.Database.ExecuteSqlCommand( $"UPDATE[dbo].[Items] SET OwnerId = {soulbinderId}, dbLocationName = '' WHERE dbLocationName <> '' AND SoulboundToPlayerId IS NOT NULL;"); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished stored procedure maintenance"); } catch (Exception e) { log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR running stored procedure maintenance: ", e)); log.Errors++; } serverLogRepo.SaveServerLog(log); #region update joke shop log.AddLog(updateTimer.ElapsedMilliseconds + ": Updating joke shop started."); try { JokeShopProcedures.EjectOfflineCharacters(); if (new Random().Next(20) == 0) { LocationsStatics.MoveJokeShop(); } ChallengeProcedures.CheckChallenges(); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Updating joke shop FAILED", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Updating joke shop completed."); serverLogRepo.SaveServerLog(log); #endregion update joke shop #region regenerate dungeon if (turnNo % 30 == 7) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Dungeon generation started."); try { DungeonProcedures.GenerateDungeon(); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Dungeon generation FAILED", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Dungeon generation completed."); serverLogRepo.SaveServerLog(log); } #endregion #region move tomb quest // Just to keep some things consistent, following the update pattern of the dungeon regen. if (turnNo > 6665 && turnNo % 30 == 7) { log.AddLog(updateTimer.ElapsedMilliseconds + ": Updating tomb location started."); try { // Get the quest stuff to start with. int questId = 39; //Nephthyma's Calling quest ID. IQuestRepository repo = new EFQuestRepository(); var questStart = repo.QuestStarts.FirstOrDefault(q => q.Id == questId); if (questStart != null) { // Pick a random location. string[] locationList = { "mansion_mausoleum", "gym_laundry", "street_50e9th", "park_shrine" }; Random locationRandom = new Random(); int locationIndex = locationRandom.Next(locationList.Length); string location = locationList[locationIndex]; // Set it to the new location. questStart.Location = location; QuestWriterProcedures.SaveQuestStart(questStart); } } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "Updating tomb location FAILED", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Updating tomb location completed."); serverLogRepo.SaveServerLog(log); } #endregion log.AddLog(updateTimer.ElapsedMilliseconds + ": Started deleting unwanted psycho items/pets on Lindella/Wuffie"); try { DomainRegistry.Repository.Execute(new DeleteUnpurchasedPsychoItems()); } catch (Exception e) { log.Errors++; log.AddLog(FormatExceptionLog(updateTimer.ElapsedMilliseconds, "ERROR deleting unwanted psycho items", e)); } log.AddLog(updateTimer.ElapsedMilliseconds + ": Finished deleting unwanted psycho items/pets on Lindella/Wuffie"); log.FinishTimestamp = DateTime.UtcNow; serverLogRepo.SaveServerLog(log); } }
private static void PerformInanimateTransformation(Player victim, Player attacker, int skillSourceId, DbStaticForm targetForm, LogBox output) { SkillProcedures.UpdateFormSpecificSkillsToPlayer(victim, targetForm.Id); DomainRegistry.Repository.Execute(new ChangeForm { PlayerId = victim.Id, FormSourceId = targetForm.Id }); if (targetForm.MobilityType == PvPStatics.MobilityInanimate && victim.BotId != AIStatics.MinibossPlushAngelId) //No reward for monsters that hurt an innocent little plush friend. :( { StatsProcedures.AddStat(victim.MembershipId, StatsProcedures.Stat__TimesInanimateTFed, 1); StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__TimesInanimateTFing, 1); } else if (targetForm.MobilityType == PvPStatics.MobilityPet && victim.BotId != AIStatics.MinibossPlushAngelId) //No reward for monsters that hurt an innocent little plush friend. :( { StatsProcedures.AddStat(victim.MembershipId, StatsProcedures.Stat__TimesAnimalTFed, 1); StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__TimesAnimalTFing, 1); } if (targetForm.MobilityType == PvPStatics.MobilityPet || targetForm.MobilityType == PvPStatics.MobilityInanimate) { if (victim.BotId == AIStatics.PsychopathBotId) { StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__PsychopathsDefeated, 1); } if (victim.BotId == AIStatics.ActivePlayerBotId && attacker.GameMode == (int)GameModeStatics.GameModes.PvP && victim.GameMode == (int)GameModeStatics.GameModes.PvP) { StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__PvPPlayerNumberTakedowns, 1); StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__PvPPlayerLevelTakedowns, victim.Level); } } // extra log stuff for turning into item var extra = ItemProcedures.PlayerBecomesItem(victim, targetForm, attacker); output.AttackerLog += extra.AttackerLog; output.VictimLog += extra.VictimLog; output.LocationLog += extra.LocationLog; // give some of the victim's money to the attacker, the amount depending on what mode the victim is in var moneygain = victim.Money * .35M; PlayerProcedures.GiveMoneyToPlayer(attacker, moneygain); PlayerProcedures.GiveMoneyToPlayer(victim, -moneygain / 2); var levelDifference = attacker.Level - victim.Level; // only give the lump sum XP if the victim is not in the same covenant if (attacker.Covenant == null || attacker.Covenant != victim.Covenant) { var xpGain = 100 - (PvPStatics.XP__EndgameTFCompletionLevelBase * levelDifference); if (xpGain < 50) { xpGain = 50; } else if (xpGain > 200) { xpGain = 200; } // give the attacker a nice lump sum for having completed the transformation output.AttackerLog += $" <br>For having sealed your opponent into their new form, you gain an extra <b>{xpGain}</b> XP."; output.AttackerLog += PlayerProcedures.GiveXP(attacker, xpGain); } // exclude PvP score for bots if (victim.BotId == AIStatics.ActivePlayerBotId) { var score = PlayerProcedures.GetPvPScoreFromWin(attacker, victim); if (score > 0) { output.AttackerLog += PlayerProcedures.GivePlayerPvPScore(attacker, victim, score); output.VictimLog += PlayerProcedures.RemovePlayerPvPScore(victim, attacker, score); StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__DungeonPointsStolen, (float)score); } else { output.AttackerLog += $" {victim.GetFullName()} unfortunately did not have any dungeon points for you to steal for yourself."; } } // Call out a player for being the monster they are when they defeat the plush angel. if (victim.BotId == AIStatics.MinibossPlushAngelId) { output.AttackerLog += "<br><br>Why did you do that to the poor plush? They just wanted to be a friend!<br>"; output.LocationLog += $"<br><b>{attacker.GetFullName()}</b> went and bullied <b>{victim.GetFullName()}</b>, like some <b>monster</b>. The angelic plush left some flowers to the 'victor', in hope they would forgive it despite doing no wrong."; // Give the dummy a bit of madness for being a bully. EffectProcedures.GivePerkToPlayer(198, attacker); } // Heals the victorious player provided that the target was eligible if (attacker.BotId == AIStatics.ActivePlayerBotId) { // Provide no healing if the victim shared a coven with the attacker if (attacker.Covenant != null && attacker.Covenant == victim.Covenant) { output.AttackerLog += " <br>There is no glory to be had in this victory, your willpower & mana are not restored."; } else { // Figure out the modifier to be used double modifier = (levelDifference * 5) / 100; // Cap the modifier to prevent too much / too little healing. if (modifier > 0.3) { modifier = 0.3; } if (modifier < -0.55) { modifier = -0.55; } decimal healingPercent = (decimal)(0.6 + modifier); if (victim.BotId != AIStatics.ActivePlayerBotId) { // The victim is not a player, provide half of the healing. healingPercent /= 2; } // Heal the attacker and restore their Mana var healingTotal = attacker.MaxHealth * healingPercent; var manaRestoredTotal = attacker.MaxMana * healingPercent; PlayerProcedures.ChangePlayerActionMana(0, healingTotal, manaRestoredTotal, attacker.Id, false); // Remove any Self Restore entires. RemoveSelfRestore(victim); output.AttackerLog += $"<br />Invigorated by your victory and fuelled by the scattered essence that was once your foe, you are healed for {healingTotal:#} willpower and {manaRestoredTotal:#} mana."; } } output.AttackerLog += $" You collect {Math.Round(moneygain, 0)} Arpeyjis your victim dropped during the transformation."; // create inanimate XP for the victim InanimateXPProcedures.GetStruggleChance(victim, false); // if this victim is a bot, clear out some old stuff that is not needed anymore if (victim.BotId < AIStatics.ActivePlayerBotId) { AIDirectiveProcedures.DeleteAIDirectiveByPlayerId(victim.Id); PlayerLogProcedures.ClearPlayerLog(victim.Id); } TFEnergyProcedures.DeleteAllPlayerTFEnergiesOfFormSourceId(victim.Id, targetForm.Id); // if the attacker is a psycho, have them change to a new spell and equip whatever they just earned if (attacker.BotId == AIStatics.PsychopathBotId) { SkillProcedures.DeletePlayerSkill(attacker, skillSourceId); if (targetForm.MobilityType == PvPStatics.MobilityInanimate || targetForm.MobilityType == PvPStatics.MobilityPet) { if (attacker.MembershipId.IsNullOrEmpty()) { // give this bot a random replacement inanimate/pet skill var eligibleSkills = SkillStatics.GetLearnablePsychopathSkills().ToList(); var rand = new Random(); var skillToLearn = eligibleSkills.ElementAt(rand.Next(eligibleSkills.Count)); SkillProcedures.GiveSkillToPlayer(attacker.Id, skillToLearn.Id); } else { // Bot is being controlled by a player - re-add the original skill so only the ordering of skills changes SkillProcedures.GiveSkillToPlayer(attacker.Id, skillSourceId); } } } }