/// <summary> /// Lists known skills /// </summary> public override CommandResult Execute(CommandEventArgs commandEventArgs) { try { base.Execute(commandEventArgs); } catch (CommandException ex) { return(ex.CommandResult); } EntityAnimate entity = commandEventArgs.Entity; List <ISkill> skills = new List <ISkill>(); foreach (var s in entity.Skills) { skills.Add(s); } OutputBuilder result = new OutputBuilder(); foreach (var s in skills) { result.Append($"{SkillMap.SkillToFriendlyName(s.GetType())} [{(int)s.SkillLevel}]\n"); } return(CommandResult.Success(result.Output)); }
/// <summary> /// Provides an entity's total modified attack rating for a particular weapon /// </summary> /// <param name="entity">The entity to calculate from</param> /// <param name="weapon">The weapon to use when calculating rating</param> /// <returns>The total attack rating modified by associated skills</returns> /// <remarks>If this is an unarmed attack with no unarmed weapons, provide 'null' for the weapon. /// Dual Wielding and Two Handed will presently not be included in modifying the attack rating. /// The skill rating is reduced by a factor of 4 before improving attack rating.</remarks> /// TODO: This could probably be refactored by putting calculated code inside the entity's ModifiedQualities.AttackRating property. public static int GetEntityAttackRatingFor(EntityAnimate entity, ItemWeapon weapon) { if (entity == null) { return(GetEntityAttackRating(entity)); } int baseAttackRating = GetEntityAttackRating(entity); double skill; if (weapon == null) { skill = SkillHelper.SkillOfWeapon(entity, WeaponType.Unarmed); } else { skill = SkillHelper.SkillOfWeapon(entity, weapon.WeaponType); } if (skill < 0) { skill = 0; } return(baseAttackRating + (int)(skill / 4)); }
/// <summary> /// Process a command from network input /// </summary> /// <param name="input">The string input to parse</param> /// <param name="entity">The entity invoking the command</param> /// <returns>The result of the command</returns> public static CommandResult ProcessCommand(string input, EntityAnimate entity, List <EntityState> validStates) { if (input == "\n") { return(CommandResult.Success("")); } var commandInput = ParseCommand(input); var argument = ParseArgument(input); var command = _commandMap.Where(kvp => kvp.Key.StartsWith(commandInput, StringComparison.InvariantCultureIgnoreCase) && !kvp.Value.RequiresFullMatch || kvp.Key.Equals(commandInput, StringComparison.InvariantCultureIgnoreCase) && kvp.Value.RequiresFullMatch) .FirstOrDefault() .Value; if (command == null) { return(CommandResult.NotFound(commandInput)); } // TODO: Move command failure messages based on state into properties and change the failure message here to use it from the command itself if (!command.ValidStates.Intersect(validStates).Any()) { return(CommandResult.Failure("State commands not found.")); } var args = new CommandEventArgs(argument, entity, null); return(command.Execute(args)); }
/// <summary> /// Process network input based on state /// </summary> /// <param name="input">The input data</param> /// <param name="entity">The data's corresponding entity</param> /// <returns></returns> public CommandResult ProcessInput(string input, EntityAnimate entity) { if (entity == null) { return(CommandResult.NullEntity()); } switch (State) { case EntityState.NameSelection: CommandResult result = HandleNameSelection(input, entity); if (result.ResultCode == ResultCode.SUCCESS) { var startingRoom = DataAccess.GetAll <World>(CacheType.Instance)[0].StartingLocation; State = EntityState.Active; if (startingRoom != null) { var args = new CommandEventArgs(startingRoom.ToString(), entity, null); entity.IOHandler?.QueueRawOutput(result.ResultMessage); result = new Goto().Execute(args); } } return(result); case EntityState.Active: return(CommandService.ProcessCommand(input, entity, new List <EntityState> { EntityState.Active })); case EntityState.Combat: var processCommands = CommandService.ProcessCommand(input, entity, new List <EntityState> { EntityState.Combat }); if (processCommands.ResultCode == ResultCode.FAIL) { return(HandleCombatInput(input, entity)); } else { return(processCommands); } default: return(CommandResult.Failure("Invalid entity state.")); } }
public override CommandResult Execute(CommandEventArgs commandEventArgs) { try { base.Execute(commandEventArgs); } catch (StateException) { if (commandEventArgs.Entity.State == EntityState.Combat) { return(CommandResult.Failure("You are already in combat!")); } else { var errMessage = $"Unexpected entity state: {commandEventArgs.Entity.State}."; Logger.Error(nameof(CommandService), nameof(Kill), errMessage); return(CommandResult.Failure(errMessage)); } } catch (CommandException ex) { return(ex.CommandResult); } var room = commandEventArgs.Entity.GetInstanceParentRoom(); var entities = room.Animates.GetAllEntities <EntityAnimate>(); EntityAnimate entity = commandEventArgs.Entity; uint? targetID = null; // Find first matching target targetID = Parse.MatchOnEntityNameByOrder(commandEventArgs.Argument, entities.Cast <IEntity>().ToList())?.Instance; if (targetID == entity?.Instance) { return(CommandResult.Failure("You cannot kill yourself.")); } if (targetID != null) { CombatHandler.Enter(commandEventArgs.Entity.Instance, targetID, false); } else { return(CommandResult.Failure("There is no such target.")); } var targetName = DataAccess.Get <EntityAnimate>(targetID, CacheType.Instance).ShortDescription; return(CommandResult.Success($"You attack {targetName}!")); }
/// <summary> /// Handles NameSelection state /// </summary> /// <returns>The result of the handled input</returns> private CommandResult HandleNameSelection(string input, EntityAnimate entity) { if (InputValidation.ValidPlayerName(input)) { entity.Name = input; var output = new OutputBuilder($"Welcome to HedronMUD, {entity.Name}!"); return(CommandResult.Success(output.Output)); } else { var output = new OutputBuilder("Please enter a valid player name containing only letters and between 3-12 characters."); output.Append("\nPlease enter your player name: "); return(CommandResult.InvalidSyntax(output.Output)); } }
/// <summary> /// Retrieves the skill level of an entity's weapon /// </summary> /// <param name="entity">The entity to check the skill of</param> /// <param name="weaponType">The weapon type to be checked</param> /// <returns>The associated weapon's skill level, or Constants.SKILL_UNLEARNED if not found.</returns> public static double SkillOfWeapon(EntityAnimate entity, WeaponType weaponType) { ISkill skill; switch (weaponType) { case WeaponType.Axe: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Axe)); break; case WeaponType.Bow: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Bow)); break; case WeaponType.Dagger: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Dagger)); break; case WeaponType.Mace: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Mace)); break; case WeaponType.Staff: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Staff)); break; case WeaponType.Sword: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Sword)); break; case WeaponType.Unarmed: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Unarmed)); break; case WeaponType.Wand: skill = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(Wand)); break; default: return(Constants.SKILL_UNLEARNED); } return(skill?.SkillLevel ?? Constants.SKILL_UNLEARNED); }
/// <summary> /// Learns a new skill /// </summary> public override CommandResult Execute(CommandEventArgs commandEventArgs) { try { base.Execute(commandEventArgs); } catch (CommandException ex) { return(ex.CommandResult); } EntityAnimate entity = commandEventArgs.Entity; string skillName = SkillMap.SkillToFriendlyName(typeof(Dodge)); if (commandEventArgs.Argument.ToLower() != skillName) { return(CommandResult.InvalidSyntax(nameof(Learn), new List <string> { skillName })); } return(CommandResult.Success(entity.ImproveSkill(skillName, 0).ImprovedMessage)); }
/// <summary> /// Provides an entity's base attack rating excluding weapons and skills /// </summary> /// <param name="entity">The entity to calculate attack rating for</param> /// <returns>The entity's base attack rating, or 0 if the entity was null.</returns> public static int GetEntityAttackRating(EntityAnimate entity) => entity != null ? (int)entity.ModifiedQualities.AttackRating + ((int)entity.ModifiedAttributes.Finesse * 2) : 0;
/// <summary> /// Calculates the entity's maximum possible number of attacks /// </summary> /// <param name="entity">The entity to calculate attacks for</param> /// <returns>The maximum number of possible attacks</returns> public static int CalcEntityMaxNumAttacks(EntityAnimate entity) { List <ItemWeapon> weapons = entity.GetItemsEquippedAt(ItemSlot.OneHandedWeapon, ItemSlot.TwoHandedWeapon).Cast <ItemWeapon>().ToList(); // The total base attack rating to start with int attackRating = GetEntityAttackRating(entity); // Calculate skill bonuses if (weapons.Count > 1) { // if dual wielding, take the better of the two weapons' skill ratings as the bonus, and half the dual rating of the dual wield skill int highestAttackRating = 0; foreach (var w in weapons) { double skill = SkillHelper.SkillOfWeapon(entity, w.WeaponType); if (skill > highestAttackRating) { highestAttackRating = (int)skill; } } attackRating += highestAttackRating; var dualWield = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(DualWield)); if (dualWield != null) { attackRating += (int)dualWield.SkillLevel / 2; } } else if (weapons.Count == 1) { // If single wielding, take the weapon's skill rating as the bonus, and if applicable, half the two-handed rating var weapon = weapons[0]; double skill = SkillHelper.SkillOfWeapon(entity, weapon.WeaponType); if (skill > 0) { attackRating += (int)skill; } if (weapon.Slot == ItemSlot.TwoHandedWeapon) { var twoHanded = entity.Skills.FirstOrDefault(s => s.GetType() == typeof(TwoHanded)); if (twoHanded != null) { attackRating += (int)twoHanded.SkillLevel / 2; } } } else { // Take the unarmed skill rating as the bonus double skill = SkillHelper.SkillOfWeapon(entity, WeaponType.Unarmed); // TODO: Add a skill that allows for a similar effect as two-handed or dual wield for unarmed multiple attacks if (skill > 0) { attackRating += (int)skill; } } // The total possible number of attacks, minimum 1 return((int)Math.Ceiling((double)attackRating / MULT_ATTACK_THRESHOLD)); }
/// <summary> /// Process an entity's auto attack /// </summary> /// <param name="numAttacks">The number of attacks to process</param> /// <param name="source">The attacking entity</param> /// <param name="target">The target entity</param> public static void ProcessEntityAutoAttack(EntityAnimate source, EntityAnimate target) { if (source == null || target == null) { return; } var rand = World.Random; var attackRating = 0; var weapons = source.GetItemsEquippedAt(ItemSlot.OneHandedWeapon, ItemSlot.TwoHandedWeapon).Cast <ItemWeapon>().ToList(); var skillResults = new List <ImproveSkillResult>(); string supportSkillToImprove = ""; // Calculate the number of attacks var numAttacks = CombatHelper.CalcEntityMaxNumAttacks(source); var attackChanceList = new List <double>(); // Protect unknown circumstances if (numAttacks < 1) { Logger.Info(nameof(CombatHandler), nameof(ProcessEntityAutoAttack), $"Number of attacks for {source.Name}: ID {source.Prototype} was less than 1."); string sourceShort = source.ShortDescription ?? ""; string targetShort = target.ShortDescription.FirstLetterToUpperCaseOrConvertNullToEmptyString(); source.IOHandler?.QueueRawOutput($"You struggle to attack {targetShort}, but can't!"); target.IOHandler?.QueueRawOutput($"{sourceShort} struggles to attack you, but can't!"); return; } // Get support skill for later improvement if (source.GetType() == typeof(Player)) { if (weapons.Count == 0) { // Unarmed supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(Unarmed)); } else if (weapons.Count == 1) { // Two handed? if (weapons[0].Slot == ItemSlot.TwoHandedWeapon) { supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(TwoHanded)); } else { supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(OneHanded)); } } else { // Dual wielding supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(DualWield)); } } // Build attack chance list for (int i = 0; i < numAttacks; i++) { attackChanceList.Add(CombatHelper.CalculateAttackChance(attackRating, i)); } // Iterate attack chance list and process attacks for (int i = 0; i < attackChanceList.Count; i++) { int damage = 0; bool didHit = false; // Improve support skills if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(supportSkillToImprove, attackChanceList[i])); } // Process the attack if it succeeds and handle skill improvement if (rand.NextDouble() <= attackChanceList[i]) { didHit = true; if (weapons.Count == 0) { // Unarmed damage damage = (source.Tier.Level * 2) + (int)source.ModifiedAttributes.Might * 3 / 5; damage = rand.Next((int)Math.Floor(damage * 0.9), (int)Math.Ceiling(damage * 1.1)); if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(SkillMap.SkillToFriendlyName(typeof(Unarmed)), 1)); } } else if (weapons.Count == 1) { // Single weapon damage if (weapons[0].Slot == ItemSlot.TwoHandedWeapon) { damage = rand.Next(weapons[0].MinDamage, weapons[0].MaxDamage + 1) + ((int)source.ModifiedAttributes.Might / 3); } else { damage = rand.Next(weapons[0].MinDamage, weapons[0].MaxDamage + 1) + ((int)source.ModifiedAttributes.Might / 5); } if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(SkillMap.WeaponTypeToSkillName(weapons[0].WeaponType), 1)); } } else { // Dual wield; alternate weapons for each attack int weaponIndex = i % 2; damage = rand.Next(weapons[weaponIndex].MinDamage, weapons[0].MaxDamage + 1) + ((int)source.ModifiedAttributes.Might / 5); if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(SkillMap.WeaponTypeToSkillName(weapons[weaponIndex].WeaponType), 1)); } } } if (didHit) { if (damage < 1) { damage = 1; } var crit = rand.NextDouble() * 100 <= source.ModifiedQualities.CriticalHit; if (crit) { damage = (int)(damage + (damage * source.ModifiedQualities.CriticalDamage / 100)); } var(hitPoints, died) = target.ModifyCurrentHealth(0 - damage, true); source.IOHandler?.QueueRawOutput($"You{(crit ? " critically" : "")} hit {target.ShortDescription} for {damage} damage{(crit ? "!" : ".")} ({target.CurrentHitPoints}/{target.ModifiedPools.HitPoints})"); target.IOHandler?.QueueRawOutput($"{source.ShortDescription}{(crit ? " critically" : "")} hits you for {damage}{(crit ? "!" : ".")}"); // Handle target death if (died) { Exit(target.Instance); if (target.GetType() == typeof(Mob)) { DataAccess.Remove <Mob>(target.Instance, CacheType.Instance); } if (target.GetType() == typeof(Player)) { target.IOHandler?.QueueRawOutput("You have died!"); var args = new Commands.CommandEventArgs( DataAccess.GetAll <World>(CacheType.Instance)?[0].Instance.ToString(), target, null); new Goto().Execute(args); } source.IOHandler?.QueueRawOutput($"You have slain {target.ShortDescription}!"); if (target.Currency.HasAnyValue()) { source.Currency += target.Currency; source.IOHandler?.QueueRawOutput($"You gain {target.Currency}."); } break; } } } // Handle skill improvement messages // TODO: Add configuration options to allow for suppressions skill improvement messages. foreach (var result in skillResults) { source.IOHandler?.QueueRawOutput(result.ImprovedMessage); } }
/// <summary> /// Default constructor /// </summary> /// <param name="argument">The argument for the command</param> /// <param name="entity">The entity executing the command</param> /// <param name="privilegeOverride">The privilege level to use as an override for execution</param> public CommandEventArgs(string argument, EntityAnimate entity, PrivilegeLevel?privilegeOverride) { Argument = argument; Entity = entity; PrivilegeOverride = privilegeOverride; }
/// <summary> /// Provides a long description for a living entity /// </summary> /// <param name="entity">The entity to provide a description for</param> /// <returns>The description</returns> public static string LongFor(EntityAnimate entity) { return(entity.LongDescription); }
/// <summary> /// Provides a short description for a living entity /// </summary> /// <param name="entity">The entity to provide a description for</param> /// <returns>The description</returns> public static string ShortFor(EntityAnimate entity) { return(entity.ShortDescription); }
/// <summary> /// Handles Combat state /// </summary> /// <returns>The result of the handled input</returns> private CommandResult HandleCombatInput(string input, EntityAnimate entity) { return(CommandResult.Failure("You must stop fighting first.")); }