private void ProcessDamage(IAttackTarget target, ITacticalAct tacticalAct, IActor actor, ActorViewModel actorViewModel) { var targetActorViewModel = ActorViewModels.SingleOrDefault(x => ReferenceEquals(x.Item, target)); var targetStaticObjectViewModel = _staticObjectViewModels.SingleOrDefault(x => ReferenceEquals(x.Item, target)); var canBeHitViewModel = (ICanBeHitSectorObject)targetActorViewModel ?? targetStaticObjectViewModel; if (canBeHitViewModel is null) { return; } actorViewModel.GraphicRoot.ProcessHit(canBeHitViewModel.Position); var sfx = Instantiate(HitSfx, transform); canBeHitViewModel.AddHitEffect(sfx); // Проверяем, стрелковое оружие или удар ближнего боя if (tacticalAct.Stats.Range?.Max > 1) { sfx.EffectSpriteRenderer.sprite = sfx.ShootSprite; // Создаём снараяд CreateBullet(actor, target); } }
public void UseOn(IActor actor, IAttackTarget target, ITacticalAct act) { //TODO реализовать возможность действовать на себя некоторыми скиллами. if (actor == target) { throw new ArgumentException("Актёр не может атаковать сам себя", nameof(target)); } var currentCubePos = ((HexNode)actor.Node).CubeCoords; var targetCubePos = ((HexNode)target.Node).CubeCoords; var isInDistance = act.CheckDistance(currentCubePos, targetCubePos); if (!isInDistance) { throw new InvalidOperationException("Попытка атаковать цель, находящуюся за пределами атаки."); } var tacticalActRoll = GetActEfficient(act); if (target is IActor targetActor) { UseOnActor(actor, targetActor, tacticalActRoll); } else { UseOnChest(target, tacticalActRoll); } }
public void SetUp() { var actUsageRandomSourceMock = new Mock <ITacticalActUsageRandomSource>(); _actUsageRandomSource = actUsageRandomSourceMock.Object; _perkResolverMock = new Mock <IPerkResolver>(); _perkResolver = _perkResolverMock.Object; var personMock = new Mock <IPerson>(); _person = personMock.Object; var evolutionDataMock = new Mock <IEvolutionData>(); var evolutionData = evolutionDataMock.Object; personMock.SetupGet(x => x.EvolutionData).Returns(evolutionData); var actMock = new Mock <ITacticalAct>(); actMock.SetupGet(x => x.Stats).Returns(new TacticalActStatsSubScheme { Range = new Range <int>(1, 1) }); _act = actMock.Object; }
public IPerson Create(IMonsterScheme monsterScheme) { var monsterPerson = new MonsterPerson(monsterScheme); var Acts = new ITacticalAct[] { new MonsterTacticalAct(monsterScheme.PrimaryAct) }; var combaActModule = new MonsterCombatActModule(Acts); monsterPerson.AddModule(combaActModule); var defenses = monsterScheme.Defense?.Defenses? .Select(x => new PersonDefenceItem(x.Type, x.Level)) .ToArray(); var defenceStats = new PersonDefenceStats( defenses ?? Array.Empty <PersonDefenceItem>(), Array.Empty <PersonArmorItem>()); var combatStatsModule = new MonsterCombatStatsModule(defenceStats); monsterPerson.AddModule(combatStatsModule); var survivalModule = new MonsterSurvivalModule(monsterScheme); monsterPerson.AddModule(survivalModule); var diseaseModule = new MonsterDiseaseModule(); monsterPerson.AddModule(diseaseModule); return(monsterPerson); }
private static void RemovePropResource(IActor actor, ITacticalAct act) { var propResources = from prop in actor.Person.GetModule <IInventoryModule>().CalcActualItems() where prop is Resource where prop.Scheme.Bullet?.Caliber == act.Constrains.PropResourceType select prop; if (propResources.FirstOrDefault() is Resource propResource) { if (propResource.Count >= act.Constrains.PropResourceCount) { var usedResource = new Resource(propResource.Scheme, act.Constrains.PropResourceCount.Value); actor.Person.GetModule <IInventoryModule>().Remove(usedResource); } else { throw new InvalidOperationException( $"Не хватает ресурса {propResource} для использования действия {act}."); } } else { throw new InvalidOperationException( $"Не хватает ресурса {act.Constrains?.PropResourceType} для использования действия {act}."); } }
private void CountTargetActorAttack(IActor actor, IActor targetActor, ITacticalAct tacticalAct) { if (actor.Person is MonsterPerson) { // Монстры не могут прокачиваться. return; } if (actor.Person == null) { // Это может происходить в тестах, // если в моках не определили персонажа. //TODO Поискать решение, как всегда быть уверенным, что персонаж указан в боевых условиях, и может быть null в тестах. //TODO Эта же проверка нужна в CountActorDefeat (учёт убиства актёра). return; } var evolutionData = actor.Person.EvolutionData; //TODO Такую же проверку добавить в CountActorDefeat (учёт убиства актёра). if (evolutionData == null) { return; } var progress = new AttackActorJobProgress(targetActor, tacticalAct); _perkResolver.ApplyProgress(progress, evolutionData); }
/// <summary> /// Возвращает показатель поглощения брони цели. /// Это величина, на которую будет снижен урон. /// </summary> /// <param name="targetActor"> Целевой актёр, для которого проверяется поглощение урона. </param> /// <param name="usedTacticalAct"> Действие, которое будет использовано для нанесения урона. </param> /// <returns> Возвращает показатель поглощения брони цели. </returns> private static int GetArmorAbsorbtion(IActor targetActor, ITacticalAct usedTacticalAct) { var actorArmors = targetActor.Person.CombatStats.DefenceStats.Armors; var actImpact = usedTacticalAct.Stats.Offence.Impact; var preferredArmor = actorArmors.FirstOrDefault(x => x.Impact == actImpact); if (preferredArmor == null) { return(0); } switch (preferredArmor.AbsorbtionLevel) { case PersonRuleLevel.None: return(0); case PersonRuleLevel.Lesser: return(1); case PersonRuleLevel.Normal: return(2); case PersonRuleLevel.Grand: return(5); case PersonRuleLevel.Absolute: return(10); default: throw new InvalidOperationException($"Неизвестный уровень поглощения брони {preferredArmor.AbsorbtionLevel}."); } }
/// <summary> /// Проверяет, допустимая ли дистанция для совершения действия. /// </summary> /// <param name="act"> Проверяемое действие. </param> /// <param name="currentNode"> Узел, из которого совершается действие. </param> /// <param name="targetNode"> Целевой узел. </param> /// <returns>Возвращает true, если дистанция допустима.</returns> public static bool CheckDistance(this ITacticalAct act, IGraphNode currentNode, IGraphNode targetNode, ISectorMap map) { if (act is null) { throw new System.ArgumentNullException(nameof(act)); } if (currentNode is null) { throw new System.ArgumentNullException(nameof(currentNode)); } if (targetNode is null) { throw new System.ArgumentNullException(nameof(targetNode)); } if (map is null) { throw new System.ArgumentNullException(nameof(map)); } var range = act.Stats.Range; var distance = map.DistanceBetween(currentNode, targetNode); var isInDistance = range.Contains(distance); return(isInDistance); }
private void ProcessDamage(IGraphNode targetNode, ITacticalAct tacticalAct, IActor actor, ActorViewModel actorViewModel) { var targetActorViewModel = ActorViewModels.SingleOrDefault(x => x.Actor.Node == targetNode); var targetStaticObjectViewModel = _staticObjectViewModels.SingleOrDefault(x => x.StaticObject.Node == targetNode); var canBeHitViewModel = (ICanBeHitSectorObject)targetActorViewModel ?? targetStaticObjectViewModel; if (canBeHitViewModel is null) { return; } actorViewModel.GraphicRoot.ProcessHit(canBeHitViewModel.Position); var sfxObj = _container.InstantiatePrefab(HitSfx, transform); var sfx = sfxObj.GetComponent <HitSfx>(); canBeHitViewModel.AddHitEffect(sfx); // Проверяем, стрелковое оружие или удар ближнего боя if (tacticalAct.Stats.Range?.Max > 1) { sfx.EffectSpriteRenderer.sprite = sfx.ShootSprite; // Создаём снаряд CreateBullet(actor, targetNode); } }
/// <summary> /// Возвращает показатель поглощения брони цели. /// Это величина, на которую будет снижен урон. /// </summary> /// <param name="targetActor"> Целевой актёр, для которого проверяется поглощение урона. </param> /// <param name="usedTacticalAct"> Действие, которое будет использовано для нанесения урона. </param> /// <returns> Возвращает показатель поглощения брони цели. </returns> private static int GetArmorAbsorbtion(IActor targetActor, ITacticalAct usedTacticalAct) { var actorArmors = targetActor.Person.GetModule <ICombatStatsModule>().DefenceStats.Armors; var offence = usedTacticalAct.Stats.Offence; if (offence is null) { throw new InvalidOperationException(); } var actImpact = offence.Impact; var preferredArmor = actorArmors.FirstOrDefault(x => x.Impact == actImpact); if (preferredArmor == null) { return(0); } return(preferredArmor.AbsorbtionLevel switch { PersonRuleLevel.None => 0, PersonRuleLevel.Lesser => 1, PersonRuleLevel.Normal => 2, PersonRuleLevel.Grand => 5, PersonRuleLevel.Absolute => 10, _ => throw new InvalidOperationException( $"Unknown armor absorbtion level: {preferredArmor.AbsorbtionLevel}.") });
public void Init(ITacticalAct act) { Act = act; var icon = CalcIcon(act.Scheme, act.Equipment); IconImage.sprite = icon; }
/// <summary> /// Возвращает случайное значение эффективность действия. /// </summary> /// <param name="act"> Соверщённое действие. </param> /// <returns> Возвращает выпавшее значение эффективности. </returns> private TacticalActRoll GetActEfficient(ITacticalAct act) { var rolledEfficient = _actUsageRandomSource.RollEfficient(act.Efficient); var roll = new TacticalActRoll(act, rolledEfficient); return(roll); }
public void SelectBestAct_HasResourceRequireActAndHasNoResource_SelectsDefault() { // ARRANGE var acts = new ITacticalAct[] { new TacticalAct(new TestTacticalActScheme { Sid = "default", Stats = new TestTacticalActStatsSubScheme { Effect = Core.Schemes.TacticalActEffectType.Damage, Efficient = new Core.Common.Roll(3, 1), Offence = new TestTacticalActOffenceSubScheme { ApRank = 1, Impact = Core.Common.ImpactType.Kinetic, Type = Core.Components.OffenseType.Tactical } } }, new Core.Common.Roll(3, 1), new Core.Common.Roll(3, 3), equipment: null ), new TacticalAct(new TestTacticalActScheme { Sid = "resource-required", Stats = new TestTacticalActStatsSubScheme { Effect = Core.Schemes.TacticalActEffectType.Damage, Efficient = new Core.Common.Roll(3, 3), Offence = new TestTacticalActOffenceSubScheme { ApRank = 1, Impact = Core.Common.ImpactType.Kinetic, Type = Core.Components.OffenseType.Tactical } }, Constrains = new TestTacticalActConstrainsSubScheme { PropResourceType = "resource", PropResourceCount = 1 } }, new Core.Common.Roll(3, 1), new Core.Common.Roll(3, 3), equipment: null ), }; var inventoryMock = new Mock <IPropStore>(); inventoryMock.Setup(x => x.CalcActualItems()).Returns(Array.Empty <IProp>()); var inventory = inventoryMock.Object; // ACT var factAct = SelectActHelper.SelectBestAct(acts, inventory); // ASSERT factAct.Scheme.Sid.Should().Be("default"); }
public void SelectBestAct_HasResourceRequireActAndHasNoInventory_SelectsDefault() { // ARRANGE var acts = new ITacticalAct[] { new TacticalAct(new TestTacticalActScheme { Sid = "default", Stats = new TestTacticalActStatsSubScheme { Effect = TacticalActEffectType.Damage, Efficient = new Roll(3, 1), Offence = new TestTacticalActOffenceSubScheme { ApRank = 1, Impact = ImpactType.Kinetic, Type = OffenseType.Tactical } } }, new Roll(3, 1), new Roll(3, 3), null ), new TacticalAct(new TestTacticalActScheme { Sid = "resource-required", Stats = new TestTacticalActStatsSubScheme { Effect = TacticalActEffectType.Damage, Efficient = new Roll(3, 3), Offence = new TestTacticalActOffenceSubScheme { ApRank = 1, Impact = ImpactType.Kinetic, Type = OffenseType.Tactical } }, Constrains = new TestTacticalActConstrainsSubScheme { PropResourceType = "resource", PropResourceCount = 1 } }, new Roll(3, 1), new Roll(3, 3), null ) }; // ACT var factAct = SelectActHelper.SelectBestAct(acts, null); // ASSERT factAct.Scheme.Sid.Should().Be("default"); }
/// <summary> /// Проверяет, допустимая ли дистанция для совершения действия. /// </summary> /// <param name="act"> Проверяемое действие. </param> /// <param name="currentCubePos"> Узел, из которого совершается действие. </param> /// <param name="targetCubePos"> Целевой узел. </param> /// <returns>Возвращает true, если дистанция допустима.</returns> public static bool CheckDistance(this ITacticalAct act, CubeCoords currentCubePos, CubeCoords targetCubePos) { var range = act.Stats.Range; var distance = currentCubePos.DistanceTo(targetCubePos); var isInDistance = range.Contains(distance); return(isInDistance); }
/// <summary> /// Возвращает случайное значение эффективность действия. /// </summary> /// <param name="act"> Соверщённое действие. </param> /// <returns> Возвращает выпавшее значение эффективности. </returns> private TacticalActRoll GetActEfficient(ITacticalAct act) { var minEfficient = act.MinEfficient; var maxEfficient = act.MaxEfficient; var rolledEfficient = _actUsageRandomSource.SelectEfficient(minEfficient, maxEfficient); var roll = new TacticalActRoll(act, rolledEfficient); return(roll); }
private void UseAct(IActor actor, IAttackTarget target, ITacticalAct act) { bool isInDistance; if ((act.Stats.Targets & TacticalActTargets.Self) > 0 && actor == target) { isInDistance = true; } else { isInDistance = IsInDistance(actor, target, act); } if (!isInDistance) { // Это может произойти, если цель, в процессе применения действия // успела выйти из радиуса применения. // TODO Лучше сделать, чтобы этот метод возвращал объект с результатом выполнения действия. // А внешний код пусть либо считает исход допустимым, либо выбрасывает исключения типа UsageOutOfDistanceException return; } var targetNode = target.Node; var targetIsOnLine = _sectorManager.CurrentSector.Map.TargetIsOnLine( actor.Node, targetNode); if (!targetIsOnLine) { throw new UsageThroughtWallException("Задачу на атаку нельзя выполнить сквозь стены."); } actor.UseAct(target, act); var tacticalActRoll = GetActEfficient(act); // Изъятие патронов if (act.Constrains?.PropResourceType != null) { RemovePropResource(actor, act); } var actHandler = _actUsageHandlerSelector.GetHandler(target); actHandler.ProcessActUsage(actor, target, tacticalActRoll); if (act.Equipment != null) { EquipmentDurableService?.UpdateByUse(act.Equipment, actor.Person); } // Сброс КД, если он есть. act.StartCooldownIfItIs(); }
private void UseAct(IActor actor, ActTargetInfo target, ITacticalAct act, ISectorMap map) { bool isInDistance; if ((act.Stats.Targets & TacticalActTargets.Self) > 0 && actor == target) { isInDistance = true; } else { isInDistance = IsInDistance(actor, target.TargetNode, act, map); } if (!isInDistance) { // Это может произойти, если цель, в процессе применения действия // успела выйти из радиуса применения. // TODO Лучше сделать, чтобы этот метод возвращал объект с результатом выполнения действия. // А внешний код пусть либо считает исход допустимым, либо выбрасывает исключения типа UsageOutOfDistanceException return; } var targetNode = target.TargetNode; var targetIsOnLine = map.TargetIsOnLine( actor.Node, targetNode); if (targetIsOnLine) { actor.UseAct(targetNode, act); var tacticalActRoll = GetActEfficient(act); var actHandler = _actUsageHandlerSelector.GetHandler(target.TargetObject); actHandler.ProcessActUsage(actor, target.TargetObject, tacticalActRoll); UseActResources(actor, act); } else { // Ситация, когда цель за стеной может произойти по следующим причинам: // 1. В момент начала применения действия цель была доступна. К моменту выполнения дейтвия цель скрылась. // В этом случае изымаем патроны и начинаем КД по действию, так как фактически ресурсы на него потрачены. Но на цель не воздействуем. // 2. Ошибка во внешнем коде, когда не провели предварительную проверку. Это проверяется сравнением ячейки в которую целились // на момент начала действия и текущую ячейку цели. if (target.TargetNode == target.TargetObject.Node) { throw new UsageThroughtWallException(); } UseActResources(actor, act); } }
/// <summary> /// Проверяет, допустимая ли дистанция для совершения действия. /// </summary> /// <param name="act"> Проверяемое действие. </param> /// <param name="currentNode"> Узел, из которого совершается действие. </param> /// <param name="targetNode"> Целевой узел. </param> /// <returns>Возвращает true, если дистанция допустима.</returns> public static bool CheckDistance(this ITacticalAct act, IMapNode currentNode, IMapNode targetNode, ISectorMap map) { var range = act.Stats.Range; var distance = map.DistanceBetween(currentNode, targetNode); var isInDistance = range.Contains(distance); return(isInDistance); }
public AttackTask(IActor actor, IAttackTarget target, ITacticalAct tacticalAct, ITacticalActUsageService actService) : base(actor) { _actService = actService; Target = target; TacticalAct = tacticalAct; }
/// <summary> /// Рассчитывает успешный спас-бросок за броню цели. /// </summary> /// <param name="targetActor"> Целевой актёр, для которого проверяется спас-бросок за броню. </param> /// <param name="usedTacticalAct"> Действие, для которого будет проверятся спас-бросок за броню. </param> /// <returns> Величина успешного спас-броска за броню. </returns> /// <remarks> /// При равных рангах броня пробивается на 4+. /// За каждые два ранга превосходства действия над бронёй - увеличение на 1. /// </remarks> private static int GetSuccessArmorSave(IActor targetActor, ITacticalAct usedTacticalAct) { var actorArmors = targetActor.Person.CombatStats.DefenceStats.Armors; var actImpact = usedTacticalAct.Stats.Offence.Impact; var preferredArmor = actorArmors.FirstOrDefault(x => x.Impact == actImpact); if (preferredArmor == null) { throw new InvalidOperationException($"Не найдена защита {actImpact}."); } var apRankDiff = usedTacticalAct.Stats.Offence.ApRank - preferredArmor.ArmorRank; switch (apRankDiff) { case 1: case 0: case -1: return(4); case 2: case 3: return(5); case 4: case 5: case 6: return(6); case -2: case -3: return(3); case -4: case -5: case -6: return(2); default: if (apRankDiff >= 7) { return(7); } else if (apRankDiff <= -7) { return(1); } else { throw new InvalidOperationException(); } } }
public CombatActButton(Texture2D texture, Texture2D icon, Texture2D selectedMarkerTexture, CombatActButtonGroup buttonGroup, ITacticalAct tacticalAct, Rectangle rect) : base(texture, rect) { _icon = icon; _selectedMarker = selectedMarkerTexture; _buttonGroup = buttonGroup; TacticalAct = tacticalAct; }
public IPerson Create(IMonsterScheme monsterScheme) { var monsterPerson = new MonsterPerson(monsterScheme); if (MonsterIdentifierGenerator != null) { monsterPerson.Id = MonsterIdentifierGenerator.GetNewId(); } var movingModule = new MonsterMovingModule(monsterScheme); monsterPerson.AddModule(movingModule); if (monsterScheme?.PrimaryAct is null) { throw new InvalidOperationException(); } var Acts = new ITacticalAct[] { new MonsterTacticalAct(monsterScheme.PrimaryAct) }; var combaActModule = new MonsterCombatActModule(Acts); monsterPerson.AddModule(combaActModule); var defenses = monsterScheme.Defense?.Defenses? .Where(x => x != null) .Select(x => x !) .Select(x => new PersonDefenceItem(x.Type, x.Level)) .ToArray(); var defenceStats = new PersonDefenceStats( defenses ?? Array.Empty <PersonDefenceItem>(), Array.Empty <PersonArmorItem>()); var combatStatsModule = new MonsterCombatStatsModule(defenceStats); monsterPerson.AddModule(combatStatsModule); var survivalModule = new MonsterSurvivalModule(monsterScheme); monsterPerson.AddModule(survivalModule); var diseaseModule = new MonsterDiseaseModule(); monsterPerson.AddModule(diseaseModule); return(monsterPerson); }
private string GetPropDisplayName(ITacticalAct act) { var lang = _uiSettingService.CurrentLanguage; var scheme = act.Scheme; if (scheme.IsMimicFor != null) { scheme = _schemeService.GetScheme <ITacticalActScheme>(scheme.IsMimicFor); } var name = scheme.Name; return(LocalizationHelper.GetValueOrDefaultNoname(lang, name)); }
private static bool WeaponHasTag(string tag, ITacticalAct _tacticalAct) { if (_tacticalAct.Equipment == null) { return(false); } if (_tacticalAct.Equipment.Scheme.Tags != null) { return(false); } return(_tacticalAct.Equipment.Scheme.Tags.Contains(tag)); }
public AttackTask(IActor actor, IActorTaskContext context, IAttackTarget target, ITacticalAct tacticalAct, ITacticalActUsageService actService) : base(actor, context) { _actService = actService; TargetObject = target ?? throw new ArgumentNullException(nameof(target)); TacticalAct = tacticalAct ?? throw new ArgumentNullException(nameof(tacticalAct)); TargetNode = target.Node; }
public async Task SetUpAsync() { var actUsageRandomSourceMock = new Mock <ITacticalActUsageRandomSource>(); actUsageRandomSourceMock.Setup(x => x.RollToHit(It.IsAny <Roll>())).Returns(6); actUsageRandomSourceMock.Setup(x => x.RollEfficient(It.IsAny <Roll>())).Returns(1); _actUsageRandomSource = actUsageRandomSourceMock.Object; _perkResolverMock = new Mock <IPerkResolver>(); _perkResolver = _perkResolverMock.Object; var personMock = new Mock <IPerson>(); _person = personMock.Object; var evolutionDataMock = new Mock <IEvolutionData>(); var evolutionData = evolutionDataMock.Object; personMock.SetupGet(x => x.EvolutionData).Returns(evolutionData); var actScheme = new TestTacticalActStatsSubScheme { Offence = new TestTacticalActOffenceSubScheme { Type = OffenseType.Tactical, Impact = ImpactType.Kinetic, ApRank = 10 } }; var actMock = new Mock <ITacticalAct>(); actMock.SetupGet(x => x.Stats).Returns(actScheme); _act = actMock.Object; var sectorManagerMock = new Mock <ISectorManager>(); var sectorManager = sectorManagerMock.Object; var map = await SquareMapFactory.CreateAsync(3); var sectorMock = new Mock <ISector>(); sectorMock.SetupGet(x => x.Map).Returns(map); var sector = sectorMock.Object; sectorManagerMock.SetupGet(x => x.CurrentSector).Returns(sector); _sectorManager = sectorManager; }