internal void FollowPath(Vector2D weaponPos, IMovementPath path) { if (!IsWalkingEnabled) { return; } if (!currentWaypoint.HasValue || currentWaypoint.Value.DistanceTo(weaponPos) < WAYPOINT_RADIUS) { currentWaypoint = path.NextPoint; Api.Logger.Dev("Automaton: Next waypoint is " + currentWaypoint); } if (!currentWaypoint.HasValue) { Api.Logger.Error("Automaton: Failed to get next waypoint"); return; } Vector2D diff = currentWaypoint.Value - weaponPos; var moveModes = CharacterMoveModesHelper.CalculateMoveModes(diff) | CharacterMoveModes.ModifierRun; // Running will yield us more LP/minute (by miniscule amount, though) var command = new CharacterInputUpdate(moveModes, 0); // Ugh, too lazy to look for usages to understand whether `0` is "up" or "right". Probably "right", but I won't mess with trigonometry, forgive me. ((PlayerCharacter)CurrentCharacter.ProtoCharacter).ClientSetInput(command); }
/// <summary> /// Stop everything. /// </summary> public override void Stop() { if (attackInProgress) { attackInProgress = false; StopItemUse(); } rememberedPath = null; currentWaypoint = null; // ClientComponentPathRenderer.IsDrawing = false; }
private IMovementPath FindTarget() { ICharacter user = CurrentCharacter; double searchDistance = GetCurrentWeaponRange() * 25; if (rememberedPath != null && !rememberedPath.Target.IsDestroyed && rememberedPath.Target.PhysicsBody.Position.DistanceTo(user.Position) < searchDistance) { return(rememberedPath); } using var objectsVisible = this.CurrentCharacter.PhysicsBody.PhysicsSpace .TestCircle(position: user.Position, radius: searchDistance, // I don't know what units the game uses, so let's stick with the search radius ~25 times as far as the weapon can hit. // UPD: Apparently it uses units. So, 1 tile is unit. collisionGroup: CollisionGroups.HitboxMelee, sendDebugEvent: false); // I'd rather chain these methods, but it seems like `using` has some relation to IDisposable. I'm not sure of consequences not using it might cause, so I'll copy the code. It's not like I want to keep your memory free from leaks or anything. var sortedVisibleObjects = objectsVisible?.AsList() ?.Where(t => this.EnabledEntityList.Contains(t.PhysicsBody?.AssociatedWorldObject?.ProtoGameObject)) ?.Where(t => t.PhysicsBody?.AssociatedWorldObject is IStaticWorldObject) ?.Where(t => this.AdditionalValidation(t.PhysicsBody?.AssociatedWorldObject as IStaticWorldObject)) // ?.Where(t => this.CheckIsVisible(t.PhysicsBody, weaponPos)) ?.OrderBy(obj => obj.PhysicsBody.Position.DistanceTo(user.Position)) // Get closest ones ?.Take(10) // But take only 10 of them to reduce the load onto the pathfinder and eliminate the possibility of freezes. // ?.ToList() // Create a snapshot of the collection for the parallel executor // ?.AsParallel() // Faster paths calculation that uses more than 1 core. ?.Select(tgt => pathfinder.GetPath(user, tgt.PhysicsBody.AssociatedWorldObject)) // ?.AsSequential() ?.OrderBy(path => path.Length) ?.ToList(); if (sortedVisibleObjects == null || sortedVisibleObjects.Count == 0) { return(null); } rememberedPath = sortedVisibleObjects[0]; ClientComponentPathRenderer.Instance.SetPoints(sortedVisibleObjects[0].Points); return(rememberedPath); }
private void FindAndAttackTarget() { var fromPos = CurrentCharacter.Position; // or + GetWeaponOffset()? IMovementPath path = FindTarget(); if (path == null) { return; } bool canAlreadyHit = GeometryHelper.GetCenterPosition(path.Target.PhysicsBody).DistanceTo(fromPos) < this.GetCurrentWeaponRange() / 1.1; // Get a bit closer to the target than maximal range. if (!canAlreadyHit) { FollowPath(fromPos, path); return; // Can safely ignore code below, because if we have to move to the closest object to attack it, we definitely cannot hit it. } // Api.Logger.Dev("Automaton: Got to the target! " + path.Target); var targetPoint = GeometryHelper.GetCenterPosition(path.Target.PhysicsBody); if (this.CheckForObstacles(path.Target.PhysicsBody as IStaticWorldObject, targetPoint, GetCurrentWeaponRange())) // if (true) { // Api.Logger.Dev("Automaton: Attacking the target! " + path.Target); this.AttackTarget(path.Target.PhysicsBody as IStaticWorldObject, targetPoint); this.attackInProgress = true; ClientTimersSystem.AddAction(this.GetCurrentWeaponAttackDelay(), () => { if (this.attackInProgress) { this.attackInProgress = false; this.rememberedPath = null; this.StopItemUse(); this.FindAndAttackTarget(); } }); return; } }