diff --git a/Enderal Credits.txt b/Enderal Credits.txt index eeb5a7ad..fd990b2e 100644 --- a/Enderal Credits.txt +++ b/Enderal Credits.txt @@ -763,6 +763,7 @@ XP32 Maximum Skeleton Extended - XPMSE by Groovtama Vanilla Script (micro)Optimizations by subhuman0100 Unnecessarily Fixed Fixed Dragon Stalking Fix by tarlazo and KirbonatedBeverage Precision Creatures by NickaNak (werewolf behaviors) +Butterflies Unchained by runesick Japanese Fonts "Corporate Mingcho" made by LOGOTYPE.JP Jimukyoku diff --git a/interface/credits.txt b/interface/credits.txt index ef21667d..5469d4ff 100644 --- a/interface/credits.txt +++ b/interface/credits.txt @@ -759,6 +759,7 @@ XP32 Maximum Skeleton Extended - XPMSE by Groovtama Vanilla Script (micro)Optimizations by subhuman0100 Unnecessarily Fixed Fixed Dragon Stalking Fix by tarlazo and KirbonatedBeverage Precision Creatures by NickaNak (werewolf behaviors) +Butterflies Unchained by runesick Japanese Fonts "Corporate Mingcho" made by LOGOTYPE.JP Jimukyoku diff --git a/interface/credits_plru.txt b/interface/credits_plru.txt index ef21667d..5469d4ff 100644 --- a/interface/credits_plru.txt +++ b/interface/credits_plru.txt @@ -759,6 +759,7 @@ XP32 Maximum Skeleton Extended - XPMSE by Groovtama Vanilla Script (micro)Optimizations by subhuman0100 Unnecessarily Fixed Fixed Dragon Stalking Fix by tarlazo and KirbonatedBeverage Precision Creatures by NickaNak (werewolf behaviors) +Butterflies Unchained by runesick Japanese Fonts "Corporate Mingcho" made by LOGOTYPE.JP Jimukyoku diff --git a/source/scripts/critter.psc b/source/scripts/critter.psc index 6c6181f5..914cc2fb 100644 --- a/source/scripts/critter.psc +++ b/source/scripts/critter.psc @@ -310,6 +310,13 @@ Function SetSpawnerProperties() PlayerRef = Game.GetPlayer() endFunction +Function SetPositionVariance(float xVar, float yVar, float minZVar, float maxZVar) + fPositionVarianceX = xVar + fPositionVarianceY = yVar + fPositionVarianceZMin = minZVar + fPositionVarianceZMax = maxZVar +EndFunction + ; Checks that the critter is all initialized, and if so, kick off the behavior Function CheckStateAndStart() ; ; Debug.TraceConditional("Critter " + self + "bDefaultPropertiesInitialized=" + bDefaultPropertiesInitialized + ", bSpawnerVariablesInitialized=" + bSpawnerVariablesInitialized, bCritterDebug) diff --git a/source/scripts/crittermoth.psc b/source/scripts/crittermoth.psc index 85d8fe12..ef865b92 100644 --- a/source/scripts/crittermoth.psc +++ b/source/scripts/crittermoth.psc @@ -8,11 +8,20 @@ import debug ; Properties (set through the editor) FormList property PlantTypes auto { The list of plant types this moth can be attracted to} +FormList property IgnoreHarvestedPlants auto +{ plants we want to ignore when they're harvested } +FormList property GroundPlants auto ; jazbay +FormList property VeryShortPlants auto ; cabbage, leek, nirnroot, canis root, hanging moss 3, spiky grass +FormList property ShortPlants auto ; mountain flowers, cotton, dragon's tongue, hanging moss 2 +FormList property TallPlants auto ; snowberry +FormList property VeryTallPlants auto ; juniper + +ObjectReference fLastPlant ; Constants float Property fTimeAtPlantMin = 5.0 auto {The Minimum time a Moth stays at a plant} -float Property fTimeAtPlantMax = 10.0 auto +float Property fTimeAtPlantMax = 15.0 auto {The Maximum time a Moth stays at a plant} float Property fActorDetectionDistance = 300.0 auto {The Distance at which an actor will trigger a flee behavior} @@ -23,7 +32,7 @@ float Property fTranslationSpeedVariance = 50.0 auto float Property fFleeTranslationSpeed = 300.0 auto {The movement speed when fleeing from the player} float Property fBellShapePathHeight = 150.0 auto -{The height of the bell shaped path} +{The maximum height of the bell shaped path} float Property fFlockPlayerXYDist = 100.0 auto {When flocking the player, the XY random value to add to its location} float Property fFlockPlayerZDistMin = 50.0 auto @@ -44,8 +53,20 @@ string property LandingMarkerPrefix = "LandingSmall0" auto {Prefix of landing markers on plants, default="LandingSmall0"} float property fMaxRotationSpeed = 90.0 auto {Max rotation speed while mocing, default = 90 deg/s} +float property fLeashMultiplier = 4.0 auto +{Increase the spawning range from the intial spawn point} +float property fSearchDistance = 250.0 auto +{Butterflies will search this distance for their next perch, increasing by fSearchIncrease each time it fails to find a plant.} +float property fSearchIncrease = 250.0 auto +float property fMinWander = 500.0 auto +float property fMaxWander = 1500.0 auto +{ How far away will butterflies wander from their current perch before searching for a perch} ; Variables +; Averages of x, y, z positions to track butterfly movement and keep them moving from old areas +float avgX = 0.0 +float avgY = 0.0 +float avgZ = 0.0 int iPlantTypeCount = 0 Actor closestActor = none @@ -55,6 +76,8 @@ float fWaitingToDieTimer = 10.0 ; Called by the spawner to kick off the processing on this Moth Event OnStart() + LoadPlantLists() + ; Pick a plant type that we're attracted to iPlantTypeCount = PlantTypes.GetSize() @@ -82,6 +105,14 @@ Event OnStart() endIf endEvent +Function LoadPlantLists() + ;Form cabbage = Game.GetFormFromFile(0x000BCF48, "Skyrim.esm") + ;if !VeryShortPlants.HasForm(cabbage) + ; debug.trace("[Butterflies Unchained] Loading cabbage form into form list..") + ; VeryShortPlants.AddForm(cabbage) + ;endif +endFunction + ; The Current plant object ObjectReference currentPlant = none @@ -241,8 +272,111 @@ Function FlockToPlayer() SplineTranslateTo(ftargetX, ftargetY, ftargetZ, ftargetAngleX, 0.0, ftargetAngleZ, fpathCurve, fFlockTranslationSpeed, fMaxRotationSpeed) endFunction -; Finds a new plant to fly to +ObjectReference Function PickPlant(float x, float y, float z, float searchDistance, float searchIncrease) + ; re-check viability [USKP 2.0.1] + if !(PlayerRef ;/&& CheckCellAttached(self) && Spawner && CheckCellAttached(Spawner)/;) + return none + endif + + ; Look for a random plant within the radius of the Spawner + int isafetyCheck = 10; was 5, match FireFly [USKP 2.0.1] + int distanceScale = 1 + ObjectReference newPlant = CurrentPlant + + while PlayerRef && isafetyCheck > 0; [USKP 2.0.1b] + + ; Grab a random plant from the list of valid plant types + newPlant = Game.FindRandomReferenceOfAnyTypeInList(PlantTypes, x, y, z, searchDistance + (searchIncrease * distanceScale)) + + ; Check whether the new plant is valid (different from current) + ; and 3D check because critters can attempt to pick disabled Nirnroots [USKP 2.0.1] + ; and not too close to an actor + if (newPlant != none && newPlant != currentPlant && !newPlant.IsDisabled() \ + && Game.FindClosestActorFromRef(newPlant, fActorDetectionDistance) == none \ + && CheckCellAttached(newPlant) && CheckFor3D(newPlant) && newPlant != fLastPlant) + float plantX = newPlant.GetPositionX() + float plantY = newPlant.GetPositionY() + float plantZ = newPlant.GetPositionZ() + float distanceFromAvg = Math.sqrt(Math.pow(plantX - avgX, 2) + Math.pow(plantY - avgY, 2) + Math.pow(plantZ - avgZ, 2)) + + ; Enforce distance from average position, unless we are on our last safety check. + ; In which case we allow it to pass to stop the butterfly from despawning + if (distanceFromAvg >= fMinWander || isafetyCheck == 1) + Form plantForm + + ; Fixes butterfly hovering by setting the height according to the plant + if (GroundPlants == none || VeryShortPlants == none \ + || ShortPlants == none || TallPlants == none \ + || VeryTallPlants == none) + Debug.Trace("[Butterflies Unchained] One or more of your mods adds a butterfly that requires a compatibility patch for optimal performance. You may encounter hovering butterflies in the meantime.") + else + plantForm = newPlant.GetBaseObject() + if (GroundPlants.Find(plantForm) != -1) + SetPositionVariance(10.0, 10.0, 5.0, 10.0) + elseif (VeryShortPlants.Find(plantForm) != -1) + SetPositionVariance(10.0, 10.0, 20.0, 30.0) + elseif (ShortPlants.Find(plantForm) != -1) + SetPositionVariance(15.0, 15.0, 35.0, 50.0) + elseif (TallPlants.Find(plantForm) != -1) + SetPositionVariance(15.0, 15.0, 50.0, 90.0) + elseif (VeryTallPlants.Find(plantForm) != -1) + SetPositionVariance(15.0, 15.0, 115.0, 140.0) + else + SetPositionVariance(15.0, 15.0, 50.0, 100.0) + endif + endif + + if (!newPlant.IsHarvested() || IgnoreHarvestedPlants.Find(plantForm) == -1) + return newPlant; [USKP 2.0.1b] + endif + endif + endIf + + if ((fSearchDistance + (100 * distanceScale)) < fMaxPlayerDistance) + distanceScale += 1 + endif + + ; Safety counter + isafetyCheck -= 1 + endWhile + ; [USKP 2.0.1] + ;Debug.Trace("Moth ( " + fSpawnerX + ", " + fSpawnerY + ", " + fSpawnerZ + ") " + self + " couldn't find a valid plant to go to", 1) + return none +endFunction + +; Finds a new plant to fly to ObjectReference Function PickNextPlant() + float wanderRangeX = 0 + float wanderRangeY = 0 + + float posX = self.GetPositionX() + float posY = self.GetPositionY() + float posZ = self.GetPositionZ() + + ; create a vector in the direction of the butterfly's movement to encourage exploration + if (fLastPlant != none) + float lastPlantX = fLastPlant.GetPositionX() + float lastPlantY = fLastPlant.GetPositionY() + float distanceTraveledX = posX - lastPlantX + float distanceTraveledY = posY - lastPlantY + + float distanceTraveled = Math.sqrt(Math.pow(distanceTraveledX, 2) + Math.pow(distanceTraveledY, 2)) + + wanderRangeX = RandomFloat(fMinWander, fMaxWander) * Math.cos(distanceTraveledX / distanceTraveled) + if (distanceTraveledX < 0) ; more efficient than calling math.abs + wanderRangeX *= -1 + endif + wanderRangeY = RandomFloat(fMinWander, fMaxWander) * Math.sin(distanceTraveledY / distanceTraveled) + else + wanderRangeX = RandomFloat(fMinWander, fMaxWander) * RandomInt(-1, 1) + wanderRangeY = RandomFloat(fMinWander, fMaxWander) * RandomInt(-1, 1) + endif + + return PickPlant(posX + wanderRangeX, posY + wanderRangeY, posZ, fSearchDistance, fSearchIncrease) +endFunction + +;; To maintain optimal compatibility with other mods, new function was created rather than having parameters added +ObjectReference Function PickSpawnPlant() ; re-check viability [USKP 2.0.1] if !(PlayerRef ;/&& CheckCellAttached(self) && Spawner && CheckCellAttached(Spawner)/;) return none @@ -255,7 +389,7 @@ ObjectReference Function PickNextPlant() while PlayerRef && isafetyCheck > 0; [USKP 2.0.1b] ; Grab a random plant from the list of valid plant types - newPlant = Game.FindRandomReferenceOfAnyTypeInList(PlantTypes, fSpawnerX, fSpawnerY, fSpawnerZ, fLeashLength) + newPlant = Game.FindRandomReferenceOfAnyTypeInList(PlantTypes, fSpawnerX, fSpawnerY, fSpawnerZ, fLeashLength * fLeashMultiplier) ; Check whether the new plant is valid (different from current) ; and 3D check because critters can attempt to pick disabled Nirnroots [USKP 2.0.1] @@ -280,22 +414,51 @@ Function GoToNewPlant(float afSpeed) ObjectReference newPlant = PickNextPlant() if (newPlant != none) + ; Update the last plant to the current one + fLastPlant = currentPlant + ; Update the current plant to the new one currentPlant = newPlant + + float newPlantX = newPlant.GetPositionX() + float newPlantY = newPlant.GetPositionY() + float newPlantZ = newPlant.GetPositionZ() + + ; Update avg coord vals + if (avgX == 0) + avgX = newPlantX + else + avgX = (avgX + newPlantX) / 2 + endif + + if (avgY == 0) + avgY = newPlantY + else + avgY = (avgY + newPlantY) / 2 + endif + + if (avgZ == 0) + avgZ = newPlantZ + else + avgZ = (avgX + newPlantZ) / 2 + endif ; Pick random landing node ; And start moving towards it string landingMarkerName = LandingMarkerPrefix + RandomInt(1, 3) + float newPlantDistance = Math.sqrt(Math.pow(newPlantX - self.GetPositionX(), 2) + Math.pow(newPlantY - self.GetPositionY(), 2) + Math.pow(newPlantZ - self.GetPositionZ(), 2)) + float adjustedBellShapePathHeight = fBellShapePathHeight - (fBellShapePathHeight * (Math.pow(0.995, newPlantDistance))) if (newPlant.HasNode(landingMarkerName)) - BellShapeTranslateToRefNodeAtSpeedAndGotoState(CurrentPlant, landingMarkerName, fBellShapePathHeight, afSpeed, fMaxRotationSpeed, "AtPlant") + BellShapeTranslateToRefNodeAtSpeedAndGotoState(CurrentPlant, landingMarkerName, adjustedBellShapePathHeight, afSpeed, fMaxRotationSpeed, "AtPlant") else ; traceConditional(self + " could not find landing marker " + landingMarkerName + " on plant " + newPlant, bCritterDebug) + string firstMarkerName = LandingMarkerPrefix + 1 if (newPlant.HasNode(firstMarkerName)) - BellShapeTranslateToRefNodeAtSpeedAndGotoState(CurrentPlant, firstMarkerName, fBellShapePathHeight, afSpeed, fMaxRotationSpeed, "AtPlant") + BellShapeTranslateToRefNodeAtSpeedAndGotoState(CurrentPlant, firstMarkerName, adjustedBellShapePathHeight, afSpeed, fMaxRotationSpeed, "AtPlant") else ; traceConditional(self + " could not find landing marker " + firstMarkerName + " on plant " + newPlant, bCritterDebug) - BellShapeTranslateToRefAtSpeedAndGotoState(CurrentPlant, fBellShapePathHeight, afSpeed, fMaxRotationSpeed, "AtPlant") + BellShapeTranslateToRefAtSpeedAndGotoState(CurrentPlant, adjustedBellShapePathHeight, afSpeed, fMaxRotationSpeed, "AtPlant") endIf endIf else @@ -308,7 +471,7 @@ endFunction Function WarpToNewPlant() ; Find a plant reference, trying to pick a different one than the current one - ObjectReference newPlant = PickNextPlant() + ObjectReference newPlant = PickSpawnPlant() if (newPlant != none) ; Update the current plant to the new one