Skip to content

Commit

Permalink
Water birds, revamped spawn system, ability to toggle binoculars brea…
Browse files Browse the repository at this point in the history
…king/jamming, and more
  • Loading branch information
greyivy committed Nov 12, 2023
1 parent 1d623e7 commit c14cafb
Show file tree
Hide file tree
Showing 24 changed files with 586 additions and 406 deletions.
10 changes: 9 additions & 1 deletion OrnithologistsGuild.CP/content.json
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,14 @@
"HasFlag": "Ivy_OrinothlogistsGuild_PowerUp"
},
"Update": "OnLocationChange"
}
},
// CustomNPCExclusions
{
"Action": "EditData",
"Target": "Data/CustomNPCExclusions",
"Entries": {
"OrinothlogistsGuild_Kyle": "WinterStar"
}
}
]
}
5 changes: 4 additions & 1 deletion OrnithologistsGuild.CP/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"Name": "[CP] Ornithologist's Guild",
"Author": "Ivy",
"Version": "1.4.3",
"Version": "1.5.0",
"Description": "Bird houses, binoculars, and a new building to explore! Ornithologist's Guild brings a totally new birding experience to Stardew Valley, complete with updated bird mechanics, seasonal birds, a progression system, a new shop, an NPC with a tragic past, and much more!",
"UniqueID": "Ivy.OrnithologistsGuild.CP",
"MinimumApiVersion": "3.0.0",
Expand All @@ -10,6 +10,9 @@
{
"UniqueID": "Pathoschild.ContentPatcher"
},
{
"UniqueID": "Esca.CustomNPCExclusions"
},
{
"UniqueID": "Ivy.OrnithologistsGuild"
}
Expand Down
2 changes: 1 addition & 1 deletion OrnithologistsGuild.STF/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"Name": "[STF] Ornithologist's Guild",
"Author": "Ivy",
"Version": "1.4.3",
"Version": "1.5.0",
"Description": "Bird houses, binoculars, and a new building to explore! Ornithologist's Guild brings a totally new birding experience to Stardew Valley, complete with updated bird mechanics, seasonal birds, a progression system, a new shop, an NPC with a tragic past, and much more!",
"UniqueID": "Ivy.OrnithologistsGuild.STF",
"MinimumApiVersion": "3.0.0",
Expand Down
96 changes: 29 additions & 67 deletions OrnithologistsGuild/Game/BetterBirdieSpawner.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DynamicGameAssets.Game;
using Microsoft.Xna.Framework;
Expand All @@ -7,7 +8,6 @@
using OrnithologistsGuild.Game.Critters;
using OrnithologistsGuild.Models;
using StardewValley;
using StardewValley.BellsAndWhistles;

namespace OrnithologistsGuild
{
Expand Down Expand Up @@ -87,47 +87,13 @@ private static void AddRandomBirdies(GameLocation location, double chance, bool
flockBirdieDef = ModEntry.debug_AlwaysSpawn == null ? GetRandomBirdieDef() : ModEntry.debug_AlwaysSpawn;
if (flockBirdieDef == null) return;

int flockSize = Game1.random.Next(1, flockBirdieDef.MaxFlockSize + 1);

// Try 50 times to find an empty patch within the location
for (int trial = 0; trial < 50; trial++)
{
// Get a random tile on the map
var randomTile = location.getRandomTile();

if (Utility.isOnScreen(randomTile * Game1.tileSize, Game1.tileSize) != onScreen) continue;
if (!BetterBirdie.CanSpawnAtOrRelocateTo(location, randomTile, flockBirdieDef)) continue;

ModEntry.Instance.Monitor.Log($"Found clear location at {randomTile}, adding flock of {flockSize} {flockBirdieDef.ID}");

// Spawn birdies
for (int index = 0; index < flockSize; ++index)
{
// 5% chance to spawn bird perched
Perch perch = null;
if (Game1.random.NextDouble() < 0.05)
{
perch = Perch.GetRandomAvailablePerch(location, flockBirdieDef);
// Ensure perch is/isn't onscreen
if (perch != null && Utility.isOnScreen(Utilities.XY(perch.Position), Game1.tileSize) != onScreen) perch = null;
}

if (perch == null) {
var tile = Utility.getTranslatedVector2(randomTile, Game1.random.Next(4), 1f);
location.addCritter((Critter)new BetterBirdie(flockBirdieDef, tile));
} else {
location.addCritter(new BetterBirdie(flockBirdieDef, Vector2.Zero, perch));
}
}

flocksAdded++;

break;
}
var spawnLocations = BetterBirdie.GetRandomPositionsOrPerchesFor(location, flockBirdieDef, mustBeOffscreen: !onScreen);
SpawnBirdies(location, flockBirdieDef, spawnLocations);
if (spawnLocations.Any()) flocksAdded++;
}
}

private static void AddBirdiesNearFeeder(GameLocation location, CustomBigCraftable feeder, Models.FeederDef feederDef, Models.FoodDef food, double chance, bool onScreen)
private static void AddBirdiesNearFeeder(GameLocation location, CustomBigCraftable feeder, FeederDef feederDef, FoodDef food, double chance, bool onScreen)
{
ModEntry.Instance.Monitor.Log("AddBirdiesNearFeeder");

Expand All @@ -144,53 +110,49 @@ private static void AddBirdiesNearFeeder(GameLocation location, CustomBigCraftab
flockBirdieDef = GetRandomFeederBirdieDef(feederDef, food);
if (flockBirdieDef == null) return;

int flockSize = Game1.random.Next(1, flockBirdieDef.MaxFlockSize + 1);

var shouldAddBirdToFeeder = flocksAdded == 0 && Game1.random.NextDouble() < 0.65;
// Ensure feeder is/isn't onscreen
if (Utility.isOnScreen(feeder.TileLocation * Game1.tileSize, Game1.tileSize) != onScreen) shouldAddBirdToFeeder = false;
if (shouldAddBirdToFeeder) flockSize -= 1;

// Try 50 times to find an empty patch within the feeder range
for (int trial = 0; trial < 50; trial++)
{
// Get a random tile within the feeder range
var randomTile = Utility.getRandomPositionInThisRectangle(feederRect, Game1.random);

if (Utility.isOnScreen(randomTile * Game1.tileSize, Game1.tileSize) != onScreen) continue;
if (!BetterBirdie.CanSpawnAtOrRelocateTo(location, randomTile, flockBirdieDef)) continue;

ModEntry.Instance.Monitor.Log($"Found clear location at {randomTile}, adding flock of {flockSize} {flockBirdieDef.ID}");

// Spawn birdies
for (int index = 0; index < flockSize; ++index)
{
var tile = Utility.getTranslatedVector2(randomTile, Game1.random.Next(4), 1f);
location.addCritter((Critter)new BetterBirdie(flockBirdieDef, tile));
}

flocksAdded++;
break;
}
var spawnLocations = BetterBirdie.GetRandomPositionsOrPerchesFor(location, flockBirdieDef, mustBeOffscreen: true, tileAreaBound: feederRect, spawnType: SpawnType.Land);
SpawnBirdies(location, flockBirdieDef, shouldAddBirdToFeeder ? spawnLocations.Skip(1) : spawnLocations);
if (spawnLocations.Any()) flocksAdded++;

var perch = new Perch(feeder);
if (shouldAddBirdToFeeder && perch.GetOccupant(location) == null)
{
location.addCritter((Critter)new BetterBirdie(flockBirdieDef, Vector2.Zero, perch));
location.addCritter(new BetterBirdie(flockBirdieDef, Vector2.Zero, perch));
}
}
}

private static void SpawnBirdies(GameLocation location, BirdieDef birdieDef, IEnumerable<Tuple<Vector3, Perch>> spawnLocations)
{
foreach (var spawnLocation in spawnLocations)
{
if (spawnLocation.Item2 != null)
{
// Add perched bird
location.addCritter(new BetterBirdie(birdieDef, Vector2.Zero, spawnLocation.Item2));
}
else
{
var tile = Utilities.XY(spawnLocation.Item1) / Game1.tileSize;
location.addCritter(new BetterBirdie(birdieDef, tile));
}
}
}

private static BirdieDef GetRandomBirdieDef()
{
return Utilities.WeightedRandom<BirdieDef>(ContentPackManager.BirdieDefs.Values, birdieDef => birdieDef.GetContextualWeight(true));
return Utilities.WeightedRandom(ContentPackManager.BirdieDefs.Values, birdieDef => birdieDef.GetContextualWeight(true));
}

private static BirdieDef GetRandomFeederBirdieDef(Models.FeederDef feederDef, Models.FoodDef foodDef)
private static BirdieDef GetRandomFeederBirdieDef(FeederDef feederDef, FoodDef foodDef)
{
var usualSuspects = ContentPackManager.BirdieDefs.Values.Where(birdieDef => birdieDef.CanPerchAt(feederDef) && birdieDef.CanEat(foodDef));

return Utilities.WeightedRandom<BirdieDef>(usualSuspects, birdieDef => birdieDef.GetContextualWeight(true, feederDef, foodDef));
return Utilities.WeightedRandom(usualSuspects, birdieDef => birdieDef.GetContextualWeight(true, feederDef, foodDef));
}
}
}
Expand Down
22 changes: 14 additions & 8 deletions OrnithologistsGuild/Game/Critters/BetterBirdie.Behavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,30 @@ public partial class BetterBirdie : StardewValley.BellsAndWhistles.Critter
{
public List<BetterBirdieBehavior> GetContextualBehavior()
{
// TODO if (Type == BirdType.Default) {

if (IsRoosting)
{
return new List<BetterBirdieBehavior> {
new BetterBirdieBehavior(1, () => BetterBirdieTrigger.Sleep, true)
};
}

if (IsBathing || Environment.isWaterTile((int)TileLocation.X, (int)TileLocation.Y))
if (IsInBath)
{
return new List<BetterBirdieBehavior> {
new BetterBirdieBehavior(1, () => BetterBirdieTrigger.Bathe, true)
};
}

if (IsInWater)
{
return new List<BetterBirdieBehavior> {
new BetterBirdieBehavior(1000, () => BetterBirdieTrigger.Swim, false),
new BetterBirdieBehavior(100, () => BetterBirdieTrigger.Bathe, false),
new BetterBirdieBehavior(25, () => BetterBirdieTrigger.Relocate, false),
new BetterBirdieBehavior(5, () => BetterBirdieTrigger.FlyAway, false)
};
}

if (IsPerched)
{
return new List<BetterBirdieBehavior> {
Expand All @@ -55,13 +63,11 @@ public List<BetterBirdieBehavior> GetContextualBehavior()
new BetterBirdieBehavior(200, () => BetterBirdieTrigger.Walk, false),
new BetterBirdieBehavior(200, () => BetterBirdieTrigger.Hop, false),
new BetterBirdieBehavior(100, () => BetterBirdieTrigger.Peck, false),
new BetterBirdieBehavior(50, () => BetterBirdieTrigger.Sleep, false),
new BetterBirdieBehavior(25, () => BetterBirdieTrigger.Relocate, false),
new BetterBirdieBehavior(5, () => BetterBirdieTrigger.FlyAway, false)
new BetterBirdieBehavior(5, () => BetterBirdieTrigger.FlyAway, false),
// Birds who cannot perch can sleep on the group
new BetterBirdieBehavior(BirdieDef.PerchPreference > 0 ? 0 : 50, () => BetterBirdieTrigger.Sleep, false)
};

// TODO Relocate and RelocateToWater? Or a way to specify per birdie so water birds are more likely to be in the water
// (I think we can use a WaterPreference that is a double for likelihood to spawn/relocate to water?)
}
}
}
Expand Down
Loading

0 comments on commit c14cafb

Please sign in to comment.