CPos?FindDropLocation(CPos targetCell, WDist maxSearchDistance) { // The easy case if (positionable.CanEnterCell(targetCell)) { return(targetCell); } var cellRange = (maxSearchDistance.Length + 1023) / 1024; var centerPosition = self.World.Map.CenterOfCell(targetCell); foreach (var c in self.World.Map.FindTilesInCircle(targetCell, cellRange)) { if (!positionable.CanEnterCell(c)) { continue; } var delta = self.World.Map.CenterOfCell(c) - centerPosition; if (delta.LengthSquared < maxSearchDistance.LengthSquared) { return(c); } } return(null); }
// Find a suitable location to drop our carryable CPos GetLocationToDrop(CPos requestedPosition) { if (positionable.CanEnterCell(requestedPosition)) { return(requestedPosition); } var candidateCells = Util.AdjacentCells(self.World, Target.FromCell(self.World, requestedPosition)); // TODO: This will behave badly if there is no suitable drop point nearby do { foreach (var c in candidateCells) { if (positionable.CanEnterCell(c)) { return(c); } } // Expanding dropable cells search area // TODO: This also includes all of the cells we have just checked candidateCells = Util.ExpandFootprint(candidateCells, true); } while (true); }
void INotifyParachuteLanded.OnLanded(Actor ignore) { if (!info.KilledOnImpassableTerrain) { return; } if (positionable.CanEnterCell(self.Location, self)) { return; } if (ignore != null && self.World.ActorMap.GetActorsAt(self.Location).Any(a => a != ignore)) { return; } var terrain = self.World.Map.GetTerrainInfo(self.Location); var sound = terrain.IsWater ? info.WaterImpactSound : info.GroundImpactSound; Game.Sound.Play(sound, self.CenterPosition); var sequence = terrain.IsWater ? info.WaterCorpseSequence : info.GroundCorpseSequence; var palette = terrain.IsWater ? info.WaterCorpsePalette : info.GroundCorpsePalette; if (sequence != null && palette != null) { self.World.AddFrameEndTask(w => w.Add(new SpriteEffect(self.OccupiesSpace.CenterPosition, w, info.Image, sequence, palette))); } self.Kill(self); }
public void OnLanded() { if (!info.KilledOnImpassableTerrain) { return; } if (positionable.CanEnterCell(self.Location)) { return; } var terrain = self.World.Map.GetTerrainInfo(self.Location); var sound = terrain.IsWater ? info.WaterImpactSound : info.GroundImpactSound; Sound.Play(sound, self.CenterPosition); var sequence = terrain.IsWater ? info.WaterCorpseSequence : info.GroundCorpseSequence; var palette = terrain.IsWater ? info.WaterCorpsePalette : info.GroundCorpsePalette; self.World.AddFrameEndTask(w => w.Add(new Explosion(w, self.OccupiesSpace.CenterPosition, sequence, palette))); self.Kill(self); }
protected virtual Activity InnerTick(Actor self, AttackBase attack) { if (IsCanceled) { return(NextActivity); } var type = Target.Type; if (!Target.IsValidFor(self) || type == TargetType.FrozenActor) { return(NextActivity); } if (attack.Info.AttackRequiresEnteringCell && !positionable.CanEnterCell(Target.Actor.Location, null, false)) { return(NextActivity); } // Drop the target if it moves under the shroud / fog. // HACK: This would otherwise break targeting frozen actors // The problem is that Shroud.IsTargetable returns false (as it should) for // frozen actors, but we do want to explicitly target the underlying actor here. if (!attack.Info.IgnoresVisibility && type == TargetType.Actor && !Target.Actor.Info.HasTraitInfo <FrozenUnderFogInfo>() && !self.Owner.CanTargetActor(Target.Actor)) { return(NextActivity); } // Drop the target once none of the weapons are effective against it var armaments = attack.ChooseArmamentsForTarget(Target, forceAttack); if (!armaments.Any()) { return(NextActivity); } // Update ranges minRange = armaments.Max(a => a.Weapon.MinRange); maxRange = armaments.Min(a => a.MaxRange()); // Try to move within range if (move != null && (!Target.IsInRange(self.CenterPosition, maxRange) || Target.IsInRange(self.CenterPosition, minRange))) { return(Util.SequenceActivities(move.MoveWithinRange(Target, minRange, maxRange), this)); } var desiredFacing = Util.GetFacing(Target.CenterPosition - self.CenterPosition, 0); if (facing.Facing != desiredFacing) { return(Util.SequenceActivities(new Turn(self, desiredFacing), this)); } attack.DoAttack(self, Target, armaments); return(this); }
public override Activity Tick(Actor self) { if (countdown > 0) { countdown--; return(this); } // Wait for the worm to get back underground if (stance == AttackState.ReturningUnderground) { sandworm.IsAttacking = false; // There is a chance that the worm would just go away after attacking if (self.World.SharedRandom.Next() % 100 <= sandworm.Info.ChanceToDisappear) { self.CancelActivity(); self.World.AddFrameEndTask(w => self.Kill(self)); } else { renderUnit.DefaultAnimation.ReplaceAnim("idle"); } return(NextActivity); } // Wait for the worm to get in position if (stance == AttackState.Burrowed) { // This is so that the worm cancels an attack against a target that has reached solid rock if (!positionable.CanEnterCell(target.Actor.Location, null, false)) { return(NextActivity); } if (!WormAttack(self)) { renderUnit.DefaultAnimation.ReplaceAnim("idle"); return(NextActivity); } countdown = swallow.Info.ReturnTime; stance = AttackState.ReturningUnderground; } return(this); }
void INotifyParachute.OnLanded(Actor self) { IsInAir = false; if (parachutingToken != Actor.InvalidConditionToken) { parachutingToken = self.RevokeCondition(parachutingToken); } if (!info.KilledOnImpassableTerrain) { return; } var cell = self.Location; if (positionable.CanEnterCell(cell, self)) { return; } if (IgnoreActor != null && !self.World.ActorMap.GetActorsAt(cell) .Any(a => a != IgnoreActor && a != self && self.World.Map.DistanceAboveTerrain(a.CenterPosition) == WDist.Zero)) { return; } var onWater = info.WaterTerrainTypes.Contains(self.World.Map.GetTerrainInfo(cell).Type); var sound = onWater ? info.WaterImpactSound : info.GroundImpactSound; Game.Sound.Play(SoundType.World, sound, self.CenterPosition); var sequence = onWater ? info.WaterCorpseSequence : info.GroundCorpseSequence; var palette = onWater ? info.WaterCorpsePalette : info.GroundCorpsePalette; if (!string.IsNullOrEmpty(info.Image) && !string.IsNullOrEmpty(sequence) && palette != null) { self.World.AddFrameEndTask(w => w.Add(new SpriteEffect(self.OccupiesSpace.CenterPosition, w, info.Image, sequence, palette))); } self.Kill(self, info.DamageTypes); }
void INotifyParachute.OnLanded(Actor self, Actor ignore) { IsInAir = false; if (parachutingToken != ConditionManager.InvalidConditionToken) { parachutingToken = conditionManager.RevokeCondition(self, parachutingToken); } if (!info.KilledOnImpassableTerrain) { return; } var cell = self.Location; if (positionable.CanEnterCell(cell, self)) { return; } if (ignore != null && self.World.ActorMap.GetActorsAt(cell).Any(a => a != ignore)) { return; } var onWater = info.WaterTerrainTypes.Contains(self.World.Map.GetTerrainInfo(cell).Type); var sound = onWater ? info.WaterImpactSound : info.GroundImpactSound; Game.Sound.Play(SoundType.World, sound, self.CenterPosition); var sequence = onWater ? info.WaterCorpseSequence : info.GroundCorpseSequence; var palette = onWater ? info.WaterCorpsePalette : info.GroundCorpsePalette; if (sequence != null && palette != null) { self.World.AddFrameEndTask(w => w.Add(new SpriteEffect(self.OccupiesSpace.CenterPosition, w, info.Image, sequence, palette))); } self.Kill(self); }
// Can't be used in synced code, except with ignoreVis. public virtual bool CanChronoshiftTo(Actor self, CPos targetLocation) { // TODO: Allow enemy units to be chronoshifted into bad terrain to kill them return(!IsTraitDisabled && iPositionable != null && iPositionable.CanEnterCell(targetLocation)); }
public override bool Tick(Actor self) { switch (stance) { case AttackState.Uninitialized: stance = AttackState.Burrowed; countdown = swallow.Info.AttackDelay; burrowLocation = self.Location; if (conditionManager != null && attackingToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(swallow.Info.AttackingCondition)) { attackingToken = conditionManager.GrantCondition(self, swallow.Info.AttackingCondition); } break; case AttackState.Burrowed: if (--countdown > 0) { return(false); } var targetLocation = target.Actor.Location; // The target has moved too far away if ((burrowLocation - targetLocation).Length > NearEnough) { RevokeCondition(self); return(true); } // The target reached solid ground if (!positionable.CanEnterCell(targetLocation, null, false)) { RevokeCondition(self); return(true); } var targets = self.World.ActorMap.GetActorsAt(targetLocation) .Where(t => !t.Equals(self) && weapon.IsValidAgainst(t, self)); if (!targets.Any()) { RevokeCondition(self); return(true); } stance = AttackState.Attacking; countdown = swallow.Info.ReturnDelay; sandworm.IsAttacking = true; AttackTargets(self, targets); break; case AttackState.Attacking: if (--countdown > 0) { return(false); } sandworm.IsAttacking = false; // There is a chance that the worm would just go away after attacking if (self.World.SharedRandom.Next(100) <= sandworm.WormInfo.ChanceToDisappear) { self.CancelActivity(); self.World.AddFrameEndTask(w => self.Dispose()); } RevokeCondition(self); return(true); } return(false); }
protected virtual AttackStatus TickAttack(Actor self, AttackBase attack) { if (IsCanceled) { return(AttackStatus.UnableToAttack); } var type = Target.Type; if (!Target.IsValidFor(self) || type == TargetT.FrozenActor) { return(AttackStatus.UnableToAttack); } if (attack.Info.AttackRequiresEnteringCell && !positionable.CanEnterCell(Target.Actor.Location, null, false)) { return(AttackStatus.UnableToAttack); } //Drop the target if it moves under the shroud / fog. if (!attack.Info.IgnoresVisibility && type == TargetT.Actor && !Target.Actor.Info.HasTraitInfo <FrozenUnderFogInfo>() && !Target.Actor.CanBeViewedByPlayer(self.Owner)) { return(AttackStatus.UnableToAttack); } //Drop the target once none of the weapons are effective against it var armaments = attack.ChooseArmamentsForTarget(Target, forceAttack).ToList(); if (armaments.Count == 0) { return(AttackStatus.UnableToAttack); } //Update ranges minRange = armaments.Max(a => a.Weapon.MinRange); maxRange = armaments.Min(a => a.MaxRange()); var pos = self.CenterPosition; var mobile = move as Mobile; if (!Target.IsInRange(pos, maxRange) || (minRange.Length != 0 && Target.IsInRange(pos, minRange)) || (mobile != null && !mobile.CanInteractWithGroundLayer(self))) { //Try to move within range,drop the target otherwise. if (move == null) { return(AttackStatus.UnableToAttack); } attackStatus |= AttackStatus.NeedsToMove; moveActivity = ActivityUtils.SequenceActivities(move.MoveWithinRange(Target, minRange, maxRange), this); return(AttackStatus.NeedsToMove); } var targetedPosition = attack.GetTargetPosition(pos, Target); var desiredFacing = (targetedPosition - pos).Yaw.Facing; if (!Util.FacingWithinTolerance(facing.Facing, desiredFacing, facingTolerance)) { attackStatus |= AttackStatus.NeedsToTurn; turnActivity = ActivityUtils.SequenceActivities(new Turn(self, desiredFacing), this); return(AttackStatus.NeedsToTurn); } attackStatus |= AttackStatus.Attacking; attack.DoAttack(self, Target, armaments); return(AttackStatus.Attacking); }
protected virtual AttackStatus TickAttack(Actor self, AttackFrontal attack) { if (!target.IsValidFor(self)) { return(AttackStatus.UnableToAttack); } if (attack.Info.AttackRequiresEnteringCell && !positionable.CanEnterCell(target.Actor.Location, null, false)) { return(AttackStatus.UnableToAttack); } if (!attack.Info.TargetFrozenActors && !forceAttack && target.Type == TargetType.FrozenActor) { // Try to move within range, drop the target otherwise if (move == null) { return(AttackStatus.UnableToAttack); } var rs = revealsShroud .Where(Exts.IsTraitEnabled) .MaxByOrDefault(s => s.Range); // Default to 2 cells if there are no active traits var sightRange = rs != null ? rs.Range : WDist.FromCells(2); attackStatus |= AttackStatus.NeedsToMove; QueueChild(self, move.MoveWithinRange(target, sightRange, target.CenterPosition, Color.Red), true); return(AttackStatus.NeedsToMove); } // Drop the target once none of the weapons are effective against it var armaments = attack.ChooseArmamentsForTarget(target, forceAttack).ToList(); if (armaments.Count == 0) { return(AttackStatus.UnableToAttack); } // Update ranges minRange = armaments.Max(a => a.Weapon.MinRange); maxRange = armaments.Min(a => a.MaxRange()); var pos = self.CenterPosition; var mobile = move as Mobile; if (!target.IsInRange(pos, maxRange) || (minRange.Length != 0 && target.IsInRange(pos, minRange)) || (mobile != null && !mobile.CanInteractWithGroundLayer(self))) { // Try to move within range, drop the target otherwise if (move == null) { return(AttackStatus.UnableToAttack); } attackStatus |= AttackStatus.NeedsToMove; var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; QueueChild(self, move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, Color.Red), true); return(AttackStatus.NeedsToMove); } if (!attack.TargetInFiringArc(self, target, attack.Info.FacingTolerance)) { var desiredFacing = (attack.GetTargetPosition(pos, target) - pos).Yaw.Facing; attackStatus |= AttackStatus.NeedsToTurn; QueueChild(self, new Turn(self, desiredFacing), true); return(AttackStatus.NeedsToTurn); } attackStatus |= AttackStatus.Attacking; DoAttack(self, attack, armaments); return(AttackStatus.Attacking); }
protected virtual Activity InnerTick(Actor self, AttackBase attack) { if (IsCanceled) { return(NextActivity); } var type = Target.Type; if (!Target.IsValidFor(self) || type == TargetType.FrozenActor) { return(NextActivity); } if (attack.Info.AttackRequiresEnteringCell && !positionable.CanEnterCell(Target.Actor.Location, null, false)) { return(NextActivity); } // Drop the target if it moves under the shroud / fog. // HACK: This would otherwise break targeting frozen actors // The problem is that Shroud.IsTargetable returns false (as it should) for // frozen actors, but we do want to explicitly target the underlying actor here. if (!attack.Info.IgnoresVisibility && type == TargetType.Actor && !Target.Actor.Info.HasTraitInfo <FrozenUnderFogInfo>() && !self.Owner.CanTargetActor(Target.Actor)) { return(NextActivity); } // Drop the target once none of the weapons are effective against it var armaments = attack.ChooseArmamentsForTarget(Target, forceAttack).ToList(); if (armaments.Count == 0) { return(NextActivity); } // Update ranges minRange = armaments.Max(a => a.Weapon.MinRange); maxRange = armaments.Min(a => a.MaxRange()); var pos = self.CenterPosition; var mobile = move as Mobile; if (!Target.IsInRange(pos, maxRange) || (minRange.Length != 0 && Target.IsInRange(pos, minRange)) || (mobile != null && !mobile.CanInteractWithGroundLayer(self))) { // Try to move within range, drop the target otherwise if (move == null) { return(NextActivity); } attackStatus |= AttackStatus.NeedsToMove; moveActivity = ActivityUtils.SequenceActivities(move.MoveWithinRange(Target, minRange, maxRange), this); return(NextActivity); } var targetedPosition = attack.GetTargetPosition(pos, Target); var desiredFacing = (targetedPosition - pos).Yaw.Facing; if (facing.Facing != desiredFacing) { attackStatus |= AttackStatus.NeedsToTurn; turnActivity = ActivityUtils.SequenceActivities(new Turn(self, desiredFacing), this); return(NextActivity); } attackStatus |= AttackStatus.Attacking; attack.DoAttack(self, Target, armaments); return(this); }