private void UpdateLocators() { this.Locators = new Dictionary <string, List <Locator> >(); foreach (var character in this.Characters) { if (character.currentLocation == null) { continue; } if (!this.Config.ShowHorses && character is Horse || this.Config.ShowFarmersOnly && (character is NPC && !(character is Horse))) { continue; } if (!this.SyncedLocationData.Locations.TryGetValue(character.Name, out var npcLoc) && character is NPC) { continue; } if (character is NPC npc && this.Config.ShowQuestsAndBirthdaysOnly) { bool isBirthday = false; bool hasQuest = false; // Check if gifted for birthday if (npc.isBirthday(Game1.currentSeason, Game1.dayOfMonth)) { isBirthday = Game1.player.friendshipData.ContainsKey(npc.Name) && Game1.player.friendshipData[npc.Name].GiftsToday == 0; } // Check for daily quests foreach (var quest in Game1.player.questLog) { if (quest.accepted.Value && quest.dailyQuest.Value && !quest.completed.Value) { hasQuest = quest.questType.Value switch { 3 => ((ItemDeliveryQuest)quest).target.Value == npc.Name, 4 => ((SlayMonsterQuest)quest).target.Value == npc.Name, 7 => ((FishingQuest)quest).target.Value == npc.Name, 10 => ((ResourceCollectionQuest)quest).target.Value == npc.Name, _ => hasQuest } } ; } if (!isBirthday && !hasQuest) { continue; } } string playerLocName = Game1.player.currentLocation.uniqueName.Value ?? Game1.player.currentLocation.Name; string charLocName = character is Farmer ? character.currentLocation.uniqueName.Value ?? character.currentLocation.Name : npcLoc.LocationName; bool isPlayerLocOutdoors = Game1.player.currentLocation.IsOutdoors; LocationContext playerLocCtx; LocationContext characterLocCtx; // Manually handle mines { string charMineName = this.LocationUtil.GetLocationNameFromLevel(charLocName); string playerMineName = this.LocationUtil.GetLocationNameFromLevel(playerLocName); if (isPlayerLocOutdoors) { // If inside a generated level, show characters as inside same general mine to player outside charLocName = charMineName ?? charLocName; } if (playerMineName != null && charMineName != null) { // Leave mine levels distinguished in name if player inside mine playerLocCtx = this.LocationUtil.TryGetContext(playerMineName, mapGeneratedLevels: false); characterLocCtx = this.LocationUtil.TryGetContext(charMineName, mapGeneratedLevels: false); } else { if (!this.LocationUtil.TryGetContext(playerLocName, out playerLocCtx)) { continue; } if (!this.LocationUtil.TryGetContext(charLocName, out characterLocCtx)) { continue; } } } if (this.Config.SameLocationOnly && characterLocCtx.Root != playerLocCtx.Root) { continue; } var characterPos = Vector2.Zero; var playerPos = new Vector2(Game1.player.position.X + Game1.player.FarmerSprite.SpriteWidth / 2 * Game1.pixelZoom, Game1.player.position.Y); bool isWarp = false; bool isOutdoors = false; bool isHorse = character is Horse; Vector2 charPosition; int charSpriteHeight; if (character is Farmer farmer) { charPosition = character.Position; charSpriteHeight = farmer.FarmerSprite.SpriteHeight; } else { charPosition = new Vector2(npcLoc.X, npcLoc.Y); charSpriteHeight = character.Sprite.SpriteHeight; } // Player and character in same location if (playerLocName == charLocName) { // Don't include locator if character is visible on screen if (Utility.isOnScreen(charPosition, Game1.tileSize / 4)) { continue; } characterPos = new Vector2(charPosition.X + charSpriteHeight / 2 * Game1.pixelZoom, charPosition.Y); } else { // Indoor locations // Intended behavior is for all characters in a building, including rooms within the building // to show up when the player is outside. So even if an character is not in the same location // ex. Maru in ScienceHouse, Sebastian in SebastianRoom, Sebastian will be placed in // ScienceHouse such that the player will know Sebastian is in that building. // Once the player is actually inside, Sebastian will be correctly placed in SebastianRoom. // Finds the upper-most indoor location that the player is in isWarp = true; string indoor = this.LocationUtil.GetBuilding(charLocName, curRecursionDepth: 1); if (this.Config.SameLocationOnly) { if (indoor == null) { continue; } if (playerLocName != characterLocCtx.Root && playerLocName != indoor) { continue; } } charLocName = isPlayerLocOutdoors || characterLocCtx.Type != LocationType.Room ? indoor : charLocName; // Neighboring outdoor warps if (!isPlayerLocOutdoors) { if (characterLocCtx.Root != playerLocCtx.Root || characterLocCtx.Parent == null) { continue; } // Doors that lead to connected rooms to character if (characterLocCtx.Parent == playerLocName) { characterPos = characterLocCtx.GetWarpPixelPosition(); } else { LocationContext characterParentContext = this.LocationUtil.TryGetContext(characterLocCtx.Parent); characterPos = characterParentContext.GetWarpPixelPosition(); } } else { if (characterLocCtx.Root == playerLocCtx.Root) { // Point locators to the neighboring outdoor warps and // doors of buildings including nested rooms LocationContext indoorContext = this.LocationUtil.TryGetContext(indoor); characterPos = indoorContext.GetWarpPixelPosition(); } else if (!this.Config.SameLocationOnly) { // Warps to other outdoor locations isOutdoors = true; if ((characterLocCtx.Root != null && playerLocCtx.Neighbors.TryGetValue(characterLocCtx.Root, out Vector2 warpPos)) || charLocName != null && playerLocCtx.Neighbors.TryGetValue(charLocName, out warpPos)) { charLocName = characterLocCtx.Root; characterPos = LocationContext.GetWarpPixelPosition(warpPos); } else { continue; } } } // Add character to the list of locators inside a building if (!this.ActiveWarpLocators.ContainsKey(charLocName)) { this.ActiveWarpLocators.Add(charLocName, new LocatorScroller() { Location = charLocName, Characters = new HashSet <string>() { character.Name }, LocatorRect = new Rectangle((int)(characterPos.X - 32), (int)(characterPos.Y - 32), 64, 64) }); } else { this.ActiveWarpLocators[charLocName].Characters.Add(character.Name); } } bool isOnScreen = Utility.isOnScreen(characterPos, Game1.tileSize / 4); var locator = new Locator { Name = character.Name, Farmer = character as Farmer, Marker = character is NPC ? character.Sprite.Texture : null, Proximity = this.GetDistance(playerPos, characterPos), IsWarp = isWarp, IsOutdoors = isOutdoors, IsOnScreen = isOnScreen, IsHorse = isHorse }; double angle = this.GetPlayerToTargetAngle(playerPos, characterPos); int quadrant = this.GetViewportQuadrant(angle, playerPos); var locatorPos = this.GetLocatorPosition(angle, quadrant, playerPos, characterPos, isWarp); locator.X = locatorPos.X; locator.Y = locatorPos.Y; locator.Angle = angle; locator.Quadrant = quadrant; if (this.Locators.TryGetValue(charLocName, out var warpLocators)) { if (!warpLocators.Contains(locator)) { warpLocators.Add(locator); } } else { warpLocators = new List <Locator> { locator }; } this.Locators[charLocName] = warpLocators; }