public override IEnumerable <IActionEffect> Execute() { float chanceToSucceed = ActorData.Accuracy; bool success = _rng.Check(chanceToSucceed); yield return(new BumpEffect(ActorData, _targetEnemy.LogicalPosition)); if (success) { _targetEnemy.Energy -= 1.2f; --ActorData.Swords; Vector2Int direction = _targetEnemy.LogicalPosition - ActorData.LogicalPosition; Assert.IsTrue(Vector2IntUtilities.IsOneStep(direction)); Vector2Int previousPosition = _targetEnemy.LogicalPosition; _targetEnemy.LogicalPosition += direction; yield return(new MoveEffect(_targetEnemy, previousPosition, _gridInfoProvider, _entityDetector, _uiConfig)); } }
private IGameAction ResolveActionForAggresion(ActorData actorData) { if (_rng.Check(0.04f)) { if (_gameContext.PlayerActor.ActorData.Health <= 0) { if (_rng.Check(0.07f)) { string text = _rng.Choice(new[] { "Ha, ha!", "I got him!", "I know my strength!", "Got what he deserved!", "Guess what we'll cook for dinner..." }); _textEffectPresenter.ShowTextEffect(actorData.LogicalPosition, text); } } else if (actorData.ActorType == ActorType.Dog && actorData.Team != Team.Beasts) { string text = _rng.Choice(new[] { "Woof", "Wrrrr!" }); _textEffectPresenter.ShowTextEffect(actorData.LogicalPosition, text); } else if (actorData.ActorType == ActorType.LastMonster) { var potential = new[] { "Whshsh!", "Rrrruv!" }.ToList(); if (!actorData.Entity.IsVisible) { potential.AddRange(new[] { "[THUD!]", "[THUD!]", "[THUD!]" }); } string text = _rng.Choice(potential); _textEffectPresenter.ShowTextEffect(actorData.LogicalPosition, text, text == "[THUD!]" ? Color.white : Color.magenta, true); } else if (actorData.ActorType == ActorType.Friend || actorData.ActorType == ActorType.Buddy) { string text = _rng.Choice(new[] { "Ma-uluh, ruv!", "Suku bgeve lir...", "Alir tak rettenekopast!" }); _textEffectPresenter.ShowTextEffect(actorData.LogicalPosition, text, new Color(0.7f, 0.8f, 1f)); } else if (actorData.ActorType != ActorType.Basher && actorData.ActorType != ActorType.LastMonster) { string text = _rng.Choice(new[] { "Back to your ward!", "Squeak!", "You're mine!", "Comrades, help me!", "Aah!" }); _textEffectPresenter.ShowTextEffect(actorData.LogicalPosition, text); } } List <ActorData> enemiesClose = _entityDetector.DetectActors(actorData.LogicalPosition, actorData.VisionRayLength) .Where(a => a.Team != actorData.Team && a.Entity.IsVisible) .ToList(); enemiesClose.Sort((first, second) => Vector2IntUtilities.WalkDistance(first.LogicalPosition, actorData.LogicalPosition) .CompareTo(Vector2IntUtilities.WalkDistance(second.LogicalPosition, actorData.LogicalPosition)) ); ActorData closestEnemy = enemiesClose.FirstOrDefault(); if (closestEnemy != null) { Vector2Int toEnemy = closestEnemy.LogicalPosition - actorData.LogicalPosition; // mam nadzieje, ze zadziala if (Vector2IntUtilities.WalkDistance(closestEnemy.LogicalPosition, actorData.LogicalPosition) == 2) { // move towards player if possible and desired bool possible = false; Vector2Int?legalMove = null; Vector2Int directionToEnemy = Vector2IntUtilities.Normalized(toEnemy); IEnumerable <Vector2Int> candidateMovesToEnemy = Vector2IntUtilities.GetCone(directionToEnemy) .Select(coneVector => coneVector - directionToEnemy) .Where(fixedConeVector => fixedConeVector != actorData.LogicalPosition); IList <Vector2Int> candidateMovesShuffled = _rng.Shuffle(candidateMovesToEnemy); foreach (var cand in candidateMovesShuffled) { if (!_entityDetector.DetectActors(actorData.LogicalPosition + cand).Any() && _gridInfoProvider.IsWalkable(actorData.LogicalPosition + cand)) { legalMove = cand; break; } } if (legalMove.HasValue) { int closeCombatAdvantage = actorData.WeaponWeld.WeaponDefinition.CloseCombatModifier - closestEnemy.WeaponWeld.WeaponDefinition.CloseCombatModifier; if (closeCombatAdvantage < 0) { closeCombatAdvantage = 0; } float chanceToMove = .1f + 0.2f * closeCombatAdvantage; if (_rng.Check(chanceToMove)) { return(_actionFactory.CreateMoveAction(actorData, legalMove.Value)); } } } if (Vector2IntUtilities.IsOneStep(toEnemy) || (actorData.WeaponWeld.WeaponDefinition.AllowsFarCombat && Vector2IntUtilities.IsOneOrTwoSteps(toEnemy) && _clearWayBetweenTwoPointsDetector.ClearWayExists(actorData.LogicalPosition, closestEnemy.LogicalPosition))) { IGameAction actionToPerform; bool pushingIsPossible = actorData.AiTraits.Contains(AiTrait.Pusher) && Vector2IntUtilities.IsOneStep(toEnemy) && _gridInfoProvider.IsWalkable(closestEnemy.LogicalPosition + toEnemy); bool pushingIsDesired = false; if (pushingIsPossible) { float pushingChanceScore = 0.08f; if (actorData.WeaponWeld.WeaponDefinition.CloseCombatModifier < closestEnemy.WeaponWeld.WeaponDefinition.CloseCombatModifier) { pushingChanceScore += .25f; } if (!_gridInfoProvider.IsWalkable(closestEnemy.LogicalPosition + toEnemy + toEnemy)) { pushingChanceScore += .2f; } if (_rng.Check(pushingChanceScore)) { pushingIsDesired = true; } } if (pushingIsPossible && pushingIsDesired) { actionToPerform = _actionFactory.CreatePushAction(actorData, closestEnemy); } else { bool isInGoodPosition = Vector2IntUtilities.IsOneStep(toEnemy) && actorData.WeaponWeld.WeaponDefinition.CloseCombatModifier > closestEnemy.WeaponWeld.WeaponDefinition.CloseCombatModifier; if (Vector2IntUtilities.IsOneOrTwoSteps(toEnemy) && !isInGoodPosition) // && actorData.AiTraits.Contains(AiTrait.Careful))) { float chanceToStepBack = 0f; float healthFactor = (1 - actorData.HealthProgress) * .15f; float swordsFactor = (closestEnemy.Swords - actorData.Swords) * .15f; chanceToStepBack = healthFactor + swordsFactor; if (_rng.Check(chanceToStepBack)) { Vector2Int directionFromEnemy = Vector2IntUtilities.Normalized(toEnemy) * -1; IEnumerable <Vector2Int> positionsToStepBack = Vector2IntUtilities.GetCone(directionFromEnemy) .Select(coneVector => actorData.LogicalPosition + coneVector - directionFromEnemy) .Where(position => position != actorData.LogicalPosition); foreach (var conePosition in positionsToStepBack) { if (!_gridInfoProvider.IsWalkable(conePosition) || _entityDetector.DetectEntities(conePosition).Any()) { continue; } Vector2Int stepBackMoveVector = conePosition - actorData.LogicalPosition; return(_actionFactory.CreateMoveAction(actorData, stepBackMoveVector)); } } } bool isDaringBlow = false; if (actorData.Traits.Contains(Trait.DaringBlow) && actorData.Swords >= 2) { float daringBlowChance = actorData.AiTraits.Contains(AiTrait.Aggressive) ? .5f : .2f; if (actorData.ActorType == ActorType.Basher) { daringBlowChance += .2f; } if (_rng.Check(daringBlowChance)) { isDaringBlow = true; } } actionToPerform = _actionFactory.CreateAttackAction(actorData, closestEnemy, isDaringBlow); } if (DateTime.UtcNow < closestEnemy.BlockedUntil) { actorData.StoredAction = actionToPerform; actorData.BlockedUntil = closestEnemy.BlockedUntil; return(null); } return(actionToPerform); } int moveX = toEnemy.x.CompareTo(0); int moveY = toEnemy.y.CompareTo(0); Vector2Int moveVector = new Vector2Int(moveX, moveY); Func <Vector2Int, bool> isWalkableAndFree = position => _gridInfoProvider.IsWalkable(position) && !_entityDetector.DetectActors(position).Any(); if (!isWalkableAndFree(actorData.LogicalPosition + moveVector)) { Vector2Int?finalMoveVector = null; Vector2Int alternativeMoveVector1; Vector2Int alternativeMoveVector2; // trying to find best alternative vectors to move if (moveVector.x == 0) { alternativeMoveVector1 = new Vector2Int(+1, moveVector.y); alternativeMoveVector2 = new Vector2Int(-1, moveVector.y); } else if (moveVector.y == 0) { alternativeMoveVector1 = new Vector2Int(moveVector.x, -1); alternativeMoveVector2 = new Vector2Int(moveVector.x, +1); } else { alternativeMoveVector1 = new Vector2Int(moveVector.x, 0); alternativeMoveVector2 = new Vector2Int(0, moveVector.y); } if (isWalkableAndFree(actorData.LogicalPosition + alternativeMoveVector1)) { finalMoveVector = alternativeMoveVector1; } else if (isWalkableAndFree(actorData.LogicalPosition + alternativeMoveVector2)) { finalMoveVector = alternativeMoveVector2; } if (finalMoveVector.HasValue) { return(_actionFactory.CreateMoveAction(actorData, finalMoveVector.Value)); } return(_actionFactory.CreatePassAction(actorData)); } return(_actionFactory.CreateMoveAction(actorData, moveVector)); } else if (actorData.Team == Team.Beasts) { ActorData playerClose = _entityDetector.DetectActors(actorData.LogicalPosition, actorData.VisionRayLength).FirstOrDefault( f => f != actorData && f.ActorType == ActorType.Player); if (playerClose != null) { Vector2Int toFriend = playerClose.LogicalPosition - actorData.LogicalPosition; Vector2Int directionToFriend = Vector2IntUtilities.Normalized(toFriend); Vector2Int legalMove = new Vector2Int(); IEnumerable <Vector2Int> candidateMovesToFriend = Vector2IntUtilities.GetCone(directionToFriend) .Select(coneVector => coneVector - directionToFriend) .Where(fixedConeVector => fixedConeVector != actorData.LogicalPosition); IList <Vector2Int> candidateMovesShuffled = _rng.Shuffle(candidateMovesToFriend); foreach (var cand in candidateMovesShuffled) { if (!_entityDetector.DetectActors(actorData.LogicalPosition + cand).Any() && _gridInfoProvider.IsWalkable(actorData.LogicalPosition + cand)) { legalMove = cand; break; } } Vector2Int moveVector = legalMove; if (!_entityDetector.DetectActors(actorData.LogicalPosition + moveVector).Any()) { return(_actionFactory.CreateMoveAction(actorData, moveVector)); } } return(_actionFactory.CreatePassAction(actorData)); } if (Vector2IntUtilities.WalkDistance(actorData.LogicalPosition, _gameContext.PlayerActor.ActorData.LogicalPosition) < 15 && _gameContext.PlayerActor.ActorData.Health > 0 && actorData.ActorType != ActorType.Basher) { Vector2Int?farReachablePoint = GetFarReachablePoint(actorData); if (farReachablePoint.HasValue) { Vector2Int moveVector = Vector2IntUtilities.Normalized(farReachablePoint.Value - actorData.LogicalPosition); return(_actionFactory.CreateMoveAction(actorData, moveVector)); } } return(_actionFactory.CreatePassAction(actorData)); }
public virtual void Process() { bool visibleEnemiesClose = ActorData.ControlledByPlayer && _entityDetector.DetectActors(ActorData.LogicalPosition, 3) .Count(a => a.Team != Team.Beasts && a.Entity.IsVisible) > 0; if (visibleEnemiesClose) { DateTime potentialBlockedUntil = DateTime.UtcNow + TimeSpan.FromMilliseconds(150); ActorData.BlockedUntil = potentialBlockedUntil > ActorData.BlockedUntil ? potentialBlockedUntil : ActorData.BlockedUntil; } IGameEntity entity = ActorData.Entity; IEntityAnimator entityAnimator = entity.EntityAnimator; IEnumerable <ActorData> enemiesNearby = _entityDetector.DetectActors(ActorData.LogicalPosition, 3) .Where(e => ActorData.Team != e.Team) .Where(e => Vector2IntUtilities.IsOneTwoOrThreeSteps(e.LogicalPosition, ActorData.LogicalPosition)); // this code orientates the actor to face the closest danger, even if it means stepping backwards List <Vector2Int> directionsToOneStepEnemiesNearby = enemiesNearby.Select(e => e.LogicalPosition - ActorData.LogicalPosition) .Where(direction => Vector2IntUtilities.IsOneStep(direction)) .ToList(); List <Vector2Int> directionsToAllEnemiesNearby = enemiesNearby.Select(e => e.LogicalPosition - ActorData.LogicalPosition).ToList(); List <Vector2Int> directionsToRelevantEnemiesNearby = directionsToOneStepEnemiesNearby.Any() ? directionsToOneStepEnemiesNearby : directionsToAllEnemiesNearby; bool thereAreSomeEnemiesOnOneSide = directionsToRelevantEnemiesNearby.Any() && (directionsToRelevantEnemiesNearby.All(direction => direction.x < 0) || directionsToRelevantEnemiesNearby.All(direction => direction.x > 0)); if (thereAreSomeEnemiesOnOneSide) { _actorAligner.AlignActorToDirection(ActorData.Entity, directionsToRelevantEnemiesNearby.First().x); } else { _actorAligner.AlignActorToDirection(ActorData.Entity, ActorData.LogicalPosition.x - PreviousPosition.x); } if (entity.IsVisible) { entityAnimator.MoveTo(PreviousPosition, ActorData.LogicalPosition); } else { Vector3 animationTargetPosition = _gridInfoProvider.GetCellCenterWorld(ActorData.LogicalPosition); // The next line makes this class kind of untestable, because ActorData.Entity is a MonoBehaviour. I believe it must be so, // so that we can see and assign the reference to it in the inspector window. If this happens more often in other effects, // we could maybe extract some kind of proxy class to keep the Entity, so that we can test with fake proxy. entity.Position = animationTargetPosition; } if (!ActorData.ControlledByPlayer) { return; } ItemData itemOnTheGround = _entityDetector.DetectItems(ActorData.LogicalPosition).FirstOrDefault(); if (itemOnTheGround != null) { _uiConfig.TooltipPresenter.Present(itemOnTheGround.ItemDefinition, false); } }
public void Heartbeat(ActorData actorData) { ++actorData.RoundsCount; BoundsInt lastLevelBounds = new BoundsInt(-18, -103, 0, 49, 72, 1); BoundsInt almostLastLevelBounds = new BoundsInt(-16, -101, 0, 45, 68, 1); if (actorData.ActorType == ActorType.Player && _gameContext.CurrentDungeonIndex >= _gameContext.Dungeons.Count && !almostLastLevelBounds.Contains(actorData.LogicalPosition.ToVector3Int())) { _textEffectPresenter.ShowTextEffect(actorData.LogicalPosition, "Almost free!", Color.yellow); } if (actorData.ActorType == ActorType.Player && _gameContext.CurrentDungeonIndex >= _gameContext.Dungeons.Count && !lastLevelBounds.Contains(actorData.LogicalPosition.ToVector3Int())) { var finisher = _uiConfig.GameFinisher; finisher.gameObject.SetActive(true); finisher.Initialize(actorData); } //if (actorData.Health < actorData.MaxHealth) // ++actorData.Health; IEnumerable <ActorData> enemiesNearby = _entityDetector.DetectActors(actorData.LogicalPosition, 2) .Where(a => a.Team != actorData.Team); if (enemiesNearby.Any(a => Vector2IntUtilities.IsOneStep(a.LogicalPosition, actorData.LogicalPosition))) { actorData.IsInCloseCombat = true; } else { actorData.IsInCloseCombat = false; } actorData.HasLittleSpace = _playerSpaceResolver.ResolveIfPlayerHasLittleSpace(actorData); int maxSwords = _maxSwordCalculator.Calculate(actorData); actorData.MaxSwords = maxSwords; if (actorData.Swords > maxSwords) { actorData.Swords = maxSwords; } if (actorData.Swords < maxSwords) { if (actorData.WeaponWeld.WeaponDefinition.RecoveryTime == RecoveryTime.ThreePerSeven) { int roundNumber = actorData.RoundsCount % 21; if (new[] { 0, 3, 5, 8, 10, 13, 15, 18, 20 }.Contains(roundNumber)) { ++actorData.Swords; } } else if (actorData.WeaponWeld.WeaponDefinition.RecoveryTime == RecoveryTime.OnePerTwo && actorData.RoundsCount % 2 == 0) { ++actorData.Swords; } else if (actorData.WeaponWeld.WeaponDefinition.RecoveryTime == RecoveryTime.ThreePerFive) { int roundNumber = actorData.RoundsCount % 15; if (new[] { 0, 2, 5, 7, 10, 12 }.Contains(roundNumber)) { ++actorData.Swords; } } } if (actorData.Entity.transform.GetComponentInChildren <SwordsIndicator>() == null /*inactive*/) { } else { actorData.Entity.transform.GetComponentInChildren <SwordsIndicator>().UpdateActiveSwords(actorData.Swords); // kind of ugly workaround, but seems to help } if (actorData.ControlledByPlayer) { if (actorData.Xp >= _gameConfig.XpForLevels[actorData.Level + 1]) { ++actorData.Level; _uiConfig.AdvanceManager.gameObject.SetActive(true); GameObject.Find("PlayerLevelIndicator").GetComponent <Text>().text = "Player level: " + actorData.Level; } } }