Jump to content

Script mod broke the happiness system in settlements, help me please


strusik

Recommended Posts

2 hours ago, 363rdChemicalCompany said:

One trick I use before O emable sricpt heavy mods thta I dont know if I iwll like them or not is this:

-Clone a profile of my exsiting Vortex profile

-Copy/paste the last 2 or 3 savegame files into that new profile's save game folder 

- Enable the new mod and play it.

-Now my previous playthrough and save files are completely protected from any shenanigans.

I also always tried to keep backups when adding new mods, but the thing is that this particular mod I played with from the beginning and it didn't cause any problems, and then in a later game I noticed that it conflicts with certain mechanics of Sim Settlements 2, and decided to remove it, besides the author on the mod page wrote how to do it. I did, and it ended up breaking something in the settlement happiness system

Link to comment
Share on other sites

Spoiler

PEX format v3.9 GameID: 2
Source   : F:\Spiele\Steam\SteamApps\common\Fallout 4\Data\Scripts\Source\User\workshopscript.psc
Modified : 2017-11-25 20:12:04
Compiled : 2017-11-25 20:12:10
User     : Stefan
Computer : STEFAN-PC
/;
ScriptName workshopscript extends ObjectReference conditional
{ script for Workshop reference }

;-- Structs -----------------------------------------
Struct DailyUpdateData
    int totalPopulation
    int robotPopulation
    int brahminPopulation
    int unassignedPopulation
    float vendorIncome
    float currentHappiness
    float damageMult
    float productivity
    int availableBeds
    int shelteredBeds
    int bonusHappiness
    int happinessModifier
    int safety
    int safetyDamage
    int foodProduction
    int waterProduction
    int availableFood
    int availableWater
    int safetyPerNPC
    float totalHappiness
EndStruct


;-- Properties --------------------------------------
Group Optional
    Faction Property SettlementOwnershipFaction Auto
    { optional - if the workshop settlement has an ownership faction, set this here so the player can be added to that faction when workshop becomes player-owned }
    bool Property UseOwnershipFaction = True Auto
    { set to false to not use the ownership faction }
    ActorBase Property CustomWorkshopNPC Auto Const
    { Patch 1.4: the actor that gets created when a settlement makes a successful recruitment roll - overrides properties on WorkshopParentScript }
    Message Property CustomUnownedMessage Auto Const
    { Patch 1.4: a custom unowned message, that overrides standard messages from WorkshopParentScript }
    bool Property AllowBrahminRecruitment = True Auto Const
    { Patch 1.6: set to false to prevent brahmin from being randomly recruited at this workshop settlement }
EndGroup

Group BuildingBudget
    int Property MaxTriangles Auto
    { if > 0, initialize WorkshopMaxTriangles to this value }
    int Property MaxDraws Auto
    { if > 0, initialize WorkshopMaxDraws to this value }
    int Property CurrentTriangles Auto
    { if > 0, initialize WorkshopCurrentTriangles to this value }
    int Property CurrentDraws Auto
    { if > 0, initialize WorkshopCurrentDraws to this value }
EndGroup

Group Flags
    bool Property OwnedByPlayer = False Auto conditional
    { all workshops start "unowned" - activate after location is cleared to "own" }
    bool Property StartsHostile = False Auto conditional
    { set to true for workbench locations that start out with hostiles in control (to prevent it being counted as a valid Minuteman recruiting target) }
    bool Property EnableAutomaticPlayerOwnership = True Auto
    { TRUE = workshop will automatically become usable when the location is cleared (or if there are no bosses)
      FALSE = workshop won't be usable by player until SetOwnedByPlayer(true) is called on it }
    bool Property AllowUnownedFromLowHappiness = False Auto
    { TRUE = workshop can become unowned due to low happiness (<=minHappinessThreshold)
        FALSE (default) = workshop can never become unowned, no matter how low the happiness (for special cases like the Castle) }
    bool Property HappinessWarning Auto conditional hidden
    { set to true when happiness warning given; set back to false when happiness goes above the warning level 
      NOTE: only applies when AllowUnownedFromLowHappiness = true }
    int Property DaysSinceLastVisit Auto conditional hidden
    { this gets cleared when player visits, incremented by daily update }
    bool Property PlayerHasVisited Auto conditional hidden
    { this gets set to true the first time player visits - used by ResetWorkshop to initialize data on first visit }
    bool Property MinRecruitmentProhibitRandom = False Auto conditional
    { set to TRUE to prohibit random Minutemen recruitment quests from picking this workshop }
    bool Property MinRecruitmentAllowRandomAfterPlayerOwned = True Auto conditional
    { set to FALSE to prohibit random Minutemen quests from picking this workshop AFTER the player takes over (TRUE = random quests are allowed once owned by player) }
    bool Property AllowAttacksBeforeOwned = True Auto conditional
    { set to FALSE to prevent attacks when unowned
        NOTE: this always gets set to true when the player takes ownership for the first time }
    bool Property AllowAttacks = True Auto conditional
    { set to FALSE to prevent ALL random attacks (e.g. the Castle) }
    bool Property RadioBeaconFirstRecruit = False Auto conditional hidden
    { set to true after player first builds a radio beacon here and gets the first "quick" recruit }
    bool Property ShowedWorkshopMenuExitMessage = False Auto conditional hidden
    { set to true after player first exits the workshop menu here }
EndGroup

Group VendorData
    ObjectReference[] Property VendorContainersMisc Auto hidden
    { array of Misc vendor containers, indexed by vendor level }
    ObjectReference[] Property VendorContainersArmor Auto hidden
    ObjectReference[] Property VendorContainersWeapons Auto hidden
    ObjectReference[] Property VendorContainersBar Auto hidden
    ObjectReference[] Property VendorContainersClinic Auto hidden
    ObjectReference[] Property VendorContainersClothing Auto hidden
EndGroup

Group WorkshopRadioData
    ObjectReference Property WorkshopRadioRef Auto Const
    { if WorkshopRadioRef exists, it will override the default WorkshopRadioRef from WorkshopParent }
    float Property workshopRadioInnerRadius = 9000 Auto Const
    { override workshop parent values }
    float Property workshopRadioOuterRadius = 20000 Auto Const
    { override workshop parent values }
    Scene Property WorkshopRadioScene Auto Const
    { if WorkshopRadioRef exists, WorkshopRadioScene will be started instead of the default scene from WorkshopParent }
    bool Property bWorkshopRadioRefIsUnique = True Auto Const
    { TRUE: WorkshopRadioScene is unique to this workshop, so it should be stopped/disabled when radio is shut off (completely) }
EndGroup

workshopparentscript Property WorkshopParent Auto Const mandatory
{ parent quest - holds most general workshop properties }
Location Property myLocation Auto hidden
{ workshop's location (filled onInit)
 this is a property so the WorkshopParent script can access it }
ObjectReference Property myMapMarker Auto hidden
{ workshop's map marker (filled by WorkshopParent.InitializeLocation) }

;-- Variables ---------------------------------------
float vendorIncomeBaseMult = 2
float happinessBonusFood = 20 Const
float maxStoredWaterPerPopulation = 0.25 Const
int minHappinessWarningThreshold = 15 Const
float attackChanceBase = 0.05 Const
float damageDailyRepairBase = 5 Const
int minVendorIncomePopulation = 5
float attackChancePopulationMult = 0.02 Const
int maxStoredFoodPerPopulation = 1 Const
int iBaseMaxNPCs = 10 Const
float maxHappinessNoWater = 30 Const
int happinessBonusChangePerUpdate = 2 Const
int maxStoredFertilizerBase = 10
int maxBrahminFertilizerProduction = 3
float happinessChangeMult = 0.2 Const
int dailyUpdateTimerID = 1 Const
int buildWorkObjectTimerID = 0 Const
float attractNPCHappinessMult = 0.5 Const
float brahminProductionBoost = 0.5
bool bDailyUpdateInProgress = False
float maxHappinessNoFood = 30 Const
float minProductivity = 0.25 Const
float happinessBonusShelter = 10 Const
float happinessBonusWater = 20 Const
float maxVendorIncome = 50
float damageDailyPopulationMult = 0.2 Const
bool showVendorTraces = True
int maxStoredScavengeBase = 100 Const
float minDailyUpdateWaitHours = 0.2
float maxDailyUpdateWaitHours = 1
int maxProductionPerBrahmin = 10
float minDaysSinceLastAttack = 1 Const
int maxStoredFoodBase = 10 Const
float attackChanceSafetyMult = 0.01 Const
float attackChanceResourceMult = 0.05 Const
int iMaxBonusAttractChancePopulation = 5 Const
int iMaxSurplusNPCs = 5 Const
float vendorIncomePopulationMult = 0.03
float productivityHappinessMult = 0.75 Const
int maxStoredScavengePerPopulation = 5 Const
float happinessBonusBed = 10 Const
int WorkshopID = -1
int minHappinessClearWarningThreshold = 20 Const
float maxHappinessNoShelter = 60 Const
float happinessBonusSafety = 20 Const
int minHappinessChangePerUpdate = 1 Const
int minHappinessThreshold = 10 Const
float attractNPCDailyChance = 0.1 Const
int maxStoredWaterBase = 5 Const

;-- Functions ---------------------------------------

Event OnWorkshopObjectDestroyed(ObjectReference akReference)
    WorkshopParent.RemoveObjectPUBLIC(akReference, Self)
EndEvent

int Function GetWorkshopID()
    If (WorkshopID < 0)
        Self.InitWorkshopID(WorkshopParent.GetWorkshopID(Self))
    EndIf
    return WorkshopID
EndFunction

float Function GetProductivityMultiplier(workshopdatascript#workshopratingkeyword[] ratings)
    float currentHappiness = Self.GetValue(ratings[WorkshopParent.WorkshopRatingHappiness].resourceValue)
    return minProductivity + currentHappiness / 100 as float * (1 as float - minProductivity)
EndFunction

Function SetOwnedByPlayer(bool bIsOwned)
    If (!bIsOwned && OwnedByPlayer)
        OwnedByPlayer = bIsOwned
        WorkshopParent.DisplayMessage(WorkshopParent.WorkshopLosePlayerOwnership, None, myLocation)
        Self.SetValue(WorkshopParent.WorkshopPlayerLostControl, 1)
        ObjectReference[] WorkshopActors = WorkshopParent.GetWorkshopActors(Self)
        int I = 0
        While (I < WorkshopActors.length)
            workshopnpcscript theActor = (WorkshopActors as Actor) as workshopnpcscript
            If (theActor)
                theActor.RemoveFromFaction(WorkshopParent.FarmDiscountFaction)
                theActor.UpdatePlayerOwnership(Self)
            EndIf
            I += 1
        EndWhile
        WorkshopParent.ClearCaravansFromWorkshopPUBLIC(Self)
    ElseIf (bIsOwned && !OwnedByPlayer)
        OwnedByPlayer = bIsOwned
        If (!WorkshopParent.PlayerOwnsAWorkshop)
            WorkshopParent.PlayerOwnsAWorkshop = True
        EndIf
        float currentHappiness = Self.GetValue(WorkshopParent.WorkshopRatings[WorkshopParent.WorkshopRatingHappiness].resourceValue)
        float currentHappinessTarget = Self.GetValue(WorkshopParent.WorkshopRatings[WorkshopParent.WorkshopRatingHappinessTarget].resourceValue)
        If (currentHappiness < minHappinessClearWarningThreshold as float || currentHappinessTarget < minHappinessClearWarningThreshold as float)
            WorkshopParent.ModifyResourceData(WorkshopParent.WorkshopRatings[WorkshopParent.WorkshopRatingHappiness].resourceValue, Self, minHappinessClearWarningThreshold as float)
            WorkshopParent.ModifyResourceData(WorkshopParent.WorkshopRatings[WorkshopParent.WorkshopRatingHappinessTarget].resourceValue, Self, minHappinessClearWarningThreshold as float)
        EndIf
        WorkshopParent.DisplayMessage(WorkshopParent.WorkshopGainPlayerOwnership, None, myLocation)
        If (Self.GetValue(WorkshopParent.WorkshopPlayerLostControl) == 0 as float)
            Game.IncrementStat("Workshops Unlocked", 1)
        Else
            Self.SetValue(WorkshopParent.WorkshopPlayerLostControl, 0)
        EndIf
        If (MinRecruitmentAllowRandomAfterPlayerOwned)
            MinRecruitmentProhibitRandom = False
        EndIf
        AllowAttacksBeforeOwned = True
        ObjectReference[] workshopactors = WorkshopParent.GetWorkshopActors(Self)
        int i = 0
        While (i < workshopactors.length)
            workshopnpcscript theactor = (workshopactors as Actor) as workshopnpcscript
            If (theactor)
                theactor.UpdatePlayerOwnership(Self)
            EndIf
            i += 1
        EndWhile
    EndIf
    OwnedByPlayer = bIsOwned
    Self.BlockActivation(!OwnedByPlayer, False)
    Self.SetValue(WorkshopParent.WorkshopPlayerOwnership, bIsOwned as float)
    If (bIsOwned)
        Self.SetActorOwner(Game.GetPlayer().GetActorBase(), False)
        myLocation.SetCleared(False)
        If (SettlementOwnershipFaction as bool && UseOwnershipFaction)
            Game.GetPlayer().AddToFaction(SettlementOwnershipFaction)
        EndIf
    Else
        Self.SetActorOwner(None, False)
        If (SettlementOwnershipFaction as bool && UseOwnershipFaction)
            Game.GetPlayer().RemoveFromFaction(SettlementOwnershipFaction)
        EndIf
    EndIf
    WorkshopParent.SendPlayerOwnershipChangedEvent(Self)
EndFunction

float Function CheckActorHappiness(float currentHappiness, bool bFood, bool bWater, bool bBed, bool bShelter)
    If (!bWater && currentHappiness > maxHappinessNoWater)
        currentHappiness = maxHappinessNoWater
    EndIf
    If (!bFood && currentHappiness > maxHappinessNoFood)
        currentHappiness = maxHappinessNoFood
    EndIf
    If (!bShelter && currentHappiness > maxHappinessNoShelter)
        currentHappiness = maxHappinessNoShelter
    EndIf
    return currentHappiness
EndFunction

Function RepairDamageToResource(ActorValue resourceValue)
    ActorValue damageRating = WorkshopParent.GetDamageRatingValue(resourceValue)
    workshopdatascript#workshopratingkeyword[] ratings = WorkshopParent.WorkshopRatings
    bool bPopulationDamage = damageRating == ratings[WorkshopParent.WorkshopRatingDamagePopulation].resourceValue
    float currentDamage = 0
    If (bPopulationDamage)
        currentDamage = WorkshopParent.GetPopulationDamage(Self)
    Else
        currentDamage = Self.GetValue(damageRating)
    EndIf
    WorkshopParent.wsTrace(Self as string + "   RepairDamageToResource: damageRating=" + damageRating as string + " bPopulationDamage=" + bPopulationDamage as string, 0, False)
    int currentWorkshopID = WorkshopParent.WorkshopCurrentWorkshopID.GetValueInt()
    If (currentDamage > 0 as float)
        WorkshopParent.wsTrace(Self as string + "   RepairDamageToResource: " + currentDamage as string + " for " + " resourceValue=" + resourceValue as string, 0, False)
        float repairAmount = 1 as float
        bool bHealedActor = False
        If (damageRating != ratings[WorkshopParent.WorkshopRatingDamagePopulation].resourceValue)
            repairAmount = Self.CalculateRepairAmount(ratings)
            repairAmount = Math.max(repairAmount, 1)
            WorkshopParent.wsTrace("\t\trepair amount=" + repairAmount as string, 0, False)
        Else
            Location[] linkedLocations = myLocation.GetAllLinkedLocations(WorkshopParent.WorkshopCaravanKeyword)
            If (linkedLocations.length > 0)
                int index = 0
                While (index < WorkshopParent.CaravanActorAliases.GetCount())
                    workshopnpcscript caravanActor = WorkshopParent.CaravanActorAliases.GetAt(index) as workshopnpcscript
                    If (caravanActor as bool && caravanActor.GetWorkshopID() == WorkshopID && caravanActor.IsWounded())
                        bHealedActor = True
                        WorkshopParent.WoundActor(caravanActor, False)
                        return 
                    EndIf
                    index += 1
                EndWhile
            EndIf
            If (!bHealedActor)
                If (WorkshopID == currentWorkshopID)
                    int I = 0
                    ObjectReference[] WorkshopActors = WorkshopParent.GetWorkshopActors(Self)
                    While (I < WorkshopActors.length && !bHealedActor)
                        workshopnpcscript theActor = WorkshopActors as workshopnpcscript
                        If (theActor as bool && theActor.IsWounded())
                            bHealedActor = True
                            WorkshopParent.WoundActor(theActor, False)
                        EndIf
                        I += 1
                    EndWhile
                EndIf
            EndIf
        EndIf
        If (!bHealedActor)
            repairAmount = Math.min(repairAmount, currentDamage)
            WorkshopParent.ModifyResourceData(damageRating, Self, repairAmount * -1)
            WorkshopParent.wsTrace("\t\tworkshopID=" + WorkshopID as string + ", currentWorkshopID=" + currentWorkshopID as string, 0, False)
            If (WorkshopID == currentWorkshopID && damageRating != ratings[WorkshopParent.WorkshopRatingDamagePopulation].resourceValue)
                WorkshopParent.wsTrace("\t\tCurrent workshop - find item(s) to repair " + repairAmount as string + " damage...", 0, False)
                int i = 0
                ObjectReference[] ResourceObjects = Self.GetWorkshopResourceObjects(resourceValue, 1)
                While (i < ResourceObjects.length && repairAmount > 0 as float)
                    workshopobjectscript theObject = ResourceObjects as workshopobjectscript
                    float damage = theObject.GetResourceDamage(resourceValue)
                    WorkshopParent.wsTrace("\t\t" + theObject as string + "=" + damage as string + " damage", 0, False)
                    If (damage > 0 as float)
                        float modDamage = Math.min(repairAmount, damage) * -1
                        If (theObject.ModifyResourceDamage(resourceValue, modDamage))
                            repairAmount += modDamage
                        EndIf
                    EndIf
                    i += 1
                EndWhile
            EndIf
        EndIf
    EndIf
EndFunction

Function DailyUpdateAttractNewSettlers(workshopdatascript#workshopratingkeyword[] ratings, workshopscript#dailyupdatedata updateData)
    DaysSinceLastVisit += 1
    int radioRating = Self.GetValue(ratings[WorkshopParent.WorkshopRatingRadio].resourceValue) as int
    If (radioRating > 0 && Self.HasKeyword(WorkshopParent.WorkshopType02) == False && updateData.unassignedPopulation < iMaxSurplusNPCs && updateData.totalPopulation < Self.GetMaxWorkshopNPCs())
        float attractChance = attractNPCDailyChance + updateData.currentHappiness / 100 as float * attractNPCHappinessMult
        If (updateData.totalPopulation < iMaxBonusAttractChancePopulation)
            attractChance += (iMaxBonusAttractChancePopulation - updateData.totalPopulation) as float * attractNPCDailyChance
        EndIf
        float dieRoll = Utility.RandomFloat(0, 1)
        If (dieRoll <= attractChance)
            workshopnpcscript newWorkshopActor = WorkshopParent.CreateActor(Self, False, None, False)
            updateData.totalPopulation = updateData.totalPopulation + 1
            If (newWorkshopActor.GetValue(WorkshopParent.WorkshopGuardPreference) == 0 as float)
                If (Self.GetValue(ratings[WorkshopParent.WorkshopRatingBrahmin].resourceValue) == 0 && AllowBrahminRecruitment)
                    int brahminRoll = Utility.RandomInt(0, 100)
                    If (brahminRoll <= WorkshopParent.recruitmentBrahminChance)
                        Actor newBrahmin = WorkshopParent.CreateActor(Self, True, None, False) as Actor
                    EndIf
                EndIf
            EndIf
        EndIf
    EndIf
EndFunction

Event OnInit()
    If (MaxTriangles > 0)
        Self.SetValue(WorkshopParent.WorkshopMaxTriangles, MaxTriangles as float)
    EndIf
    If (MaxDraws > 0)
        Self.SetValue(WorkshopParent.WorkshopMaxDraws, MaxDraws as float)
    EndIf
    If (CurrentTriangles > 0)
        Self.SetValue(WorkshopParent.WorkshopCurrentTriangles, CurrentTriangles as float)
    EndIf
    If (CurrentDraws > 0)
        Self.SetValue(WorkshopParent.WorkshopCurrentDraws, CurrentDraws as float)
    EndIf
    Self.SetValue(WorkshopParent.WorkshopRatings[WorkshopParent.WorkshopRatingHappinessTarget].resourceValue, WorkshopParent.startingHappinessTarget)
EndEvent

Function DailyUpdateConsumeResources(workshopdatascript#workshopratingkeyword[] ratings, workshopscript#dailyupdatedata updateData, ObjectReference containerRef, bool bRealUpdate)
    If (updateData.totalPopulation == 0)
        return 
    EndIf
    float ActorHappiness = 0
    bool ActorBed = False
    bool ActorShelter = False
    bool ActorFood = False
    bool ActorWater = False
    int missingFood = 0
    int missingWater = 0
    int missingBeds = 0
    int missingShelter = 0
    int missingSafety = 0
    int I = 0
    While (I < updateData.totalPopulation - updateData.robotPopulation)
        ActorHappiness = 0 as float
        ActorFood = False
        ActorWater = False
        ActorBed = False
        ActorShelter = False
        WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: actor " + I as string, 0, False)
        If (updateData.availableFood > 0)
            ActorFood = True
            updateData.availableFood = updateData.availableFood - 1
            If (updateData.foodProduction > 0)
                updateData.foodProduction = updateData.foodProduction - 1
            ElseIf (bRealUpdate)
                containerRef.RemoveItem(WorkshopParent.WorkshopConsumeFood as Form, 1, False, None)
            EndIf
            ActorHappiness += happinessBonusFood
            WorkshopParent.wsTrace(Self as string + "   +FOOD - happiness=" + ActorHappiness as string, 0, False)
        Else
            missingFood += 1
        EndIf
        If (updateData.availableWater > 0)
            ActorWater = True
            updateData.availableWater = updateData.availableWater - 1
            If (updateData.waterProduction > 0)
                updateData.waterProduction = updateData.waterProduction - 1
            ElseIf (bRealUpdate)
                containerRef.RemoveItem(WorkshopParent.WorkshopConsumeWater as Form, 1, False, None)
            EndIf
            ActorHappiness += happinessBonusWater
            WorkshopParent.wsTrace(Self as string + "   +WATER - happiness=" + ActorHappiness as string, 0, False)
        Else
            missingWater += 1
        EndIf
        If (updateData.availableBeds > 0)
            ActorBed = True
            updateData.availableBeds = updateData.availableBeds - 1
            ActorHappiness += happinessBonusBed
            WorkshopParent.wsTrace(Self as string + "   +BED - happiness=" + ActorHappiness as string, 0, False)
        Else
            missingBeds += 1
        EndIf
        If (updateData.shelteredBeds > 0)
            ActorShelter = True
            updateData.shelteredBeds = updateData.shelteredBeds - 1
            ActorHappiness += happinessBonusShelter
            WorkshopParent.wsTrace(Self as string + "   +SHELTER - happiness=" + ActorHappiness as string, 0, False)
        EndIf
        If (updateData.safetyPerNPC > 0)
            ActorHappiness += happinessBonusSafety
        EndIf
        ActorHappiness = Self.CheckActorHappiness(ActorHappiness, ActorFood, ActorWater, ActorBed, ActorShelter)
        WorkshopParent.wsTrace(Self as string + "   Final actor happiness=" + ActorHappiness as string, 0, False)
        updateData.totalHappiness = updateData.totalHappiness + ActorHappiness
        I += 1
    EndWhile
    updateData.totalHappiness = updateData.totalHappiness + (50 * updateData.robotPopulation) as float
    WorkshopParent.SetResourceData(ratings[WorkshopParent.WorkshopRatingMissingBeds].resourceValue, Self, missingBeds as float)
    If (bRealUpdate)
        WorkshopParent.SetResourceData(ratings[WorkshopParent.WorkshopRatingMissingFood].resourceValue, Self, missingFood as float)
        WorkshopParent.SetResourceData(ratings[WorkshopParent.WorkshopRatingMissingWater].resourceValue, Self, missingWater as float)
    EndIf
    updateData.totalHappiness = updateData.totalHappiness + updateData.bonusHappiness as float
    WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: totalHappiness=" + updateData.totalHappiness as string + ", totalActors=" + updateData.totalPopulation as string + ", happiness modifier=" + updateData.happinessModifier as string, 0, False)
    updateData.totalHappiness = Math.max(updateData.totalHappiness / updateData.totalPopulation as float + updateData.happinessModifier as float, 0 as float)
    updateData.totalHappiness = Math.min(updateData.totalHappiness, 100 as float)
    WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: Happiness Target=" + updateData.totalHappiness as string, 0, False)
    WorkshopParent.SetResourceData(ratings[WorkshopParent.WorkshopRatingHappinessTarget].resourceValue, Self, updateData.totalHappiness)
    If (bRealUpdate)
        float deltaHappinessFloat = (updateData.totalHappiness - updateData.currentHappiness) * happinessChangeMult
        int deltaHappiness = 0
        If (deltaHappinessFloat < 0 as float)
            deltaHappiness = Math.floor(deltaHappinessFloat)
        Else
            deltaHappiness = Math.Ceiling(deltaHappinessFloat)
        EndIf
        If (deltaHappiness != 0 && Math.abs(deltaHappiness as float) < minHappinessChangePerUpdate as float)
            deltaHappiness = minHappinessChangePerUpdate * (deltaHappiness as float / Math.abs(deltaHappiness as float)) as int
        EndIf
        WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: happiness change=" + deltaHappiness as string, 0, False)
        WorkshopParent.ModifyResourceData(ratings[WorkshopParent.WorkshopRatingHappiness].resourceValue, Self, deltaHappiness as float)
        float finalHappiness = Self.GetValue(ratings[WorkshopParent.WorkshopRatingHappiness].resourceValue)
        WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: final happiness=" + finalHappiness as string, 0, False)
        If (finalHappiness >= WorkshopParent.HappinessAchievementValue as float)
            Game.AddAchievement(WorkshopParent.HappinessAchievementID)
        EndIf
        If (OwnedByPlayer && AllowUnownedFromLowHappiness)
            If (finalHappiness <= minHappinessWarningThreshold as float && HappinessWarning == False)
                HappinessWarning = True
                WorkshopParent.DisplayMessage(WorkshopParent.WorkshopUnhappinessWarning, None, myLocation)
            ElseIf (finalHappiness <= minHappinessThreshold as float)
                Self.SetOwnedByPlayer(False)
            EndIf
            If (finalHappiness > minHappinessClearWarningThreshold as float && HappinessWarning == True)
                HappinessWarning = False
            EndIf
        EndIf
        If (updateData.happinessModifier != 0)
            float modifierSign = (-1) as float * updateData.happinessModifier as float / Math.abs(updateData.happinessModifier as float)
            WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: modifierSign=" + modifierSign as string, 0, False)
            int deltaHappinessModifier = 0
            float deltaHappinessModifierFloat = Math.abs(updateData.happinessModifier as float) * modifierSign * happinessChangeMult
            WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: deltaHappinessModifierFloat=" + deltaHappinessModifierFloat as string, 0, False)
            If (deltaHappinessModifierFloat > 0 as float)
                deltaHappinessModifier = Math.floor(deltaHappinessModifierFloat)
            Else
                deltaHappinessModifier = Math.Ceiling(deltaHappinessModifierFloat)
            EndIf
            WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: deltaHappinessModifier=" + deltaHappinessModifier as string, 0, False)
            If (Math.abs(deltaHappinessModifier as float) < happinessBonusChangePerUpdate as float)
                deltaHappinessModifier = (modifierSign * happinessBonusChangePerUpdate as float) as int
            EndIf
            WorkshopParent.wsTrace(Self as string + "\tCalculateHappiness: FINAL deltaHappinessModifier=" + deltaHappinessModifier as string, 0, False)
            If (deltaHappinessModifier as float > Math.abs(updateData.happinessModifier as float))
                WorkshopParent.SetHappinessModifier(Self, 0 as float)
            Else
                WorkshopParent.ModifyHappinessModifier(Self, deltaHappinessModifier as float)
            EndIf
        EndIf
    EndIf
EndFunction

Function DailyUpdateSurplusResources(workshopdatascript#workshopratingkeyword[] ratings, workshopscript#dailyupdatedata updateData, ObjectReference containerRef)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    WorkshopParent.wsTrace(Self as string + "\tAdd surplus to workshop container: ", 0, False)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    int currentStoredFood = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeFood as Form)
    int currentStoredWater = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeWater as Form)
    int currentStoredScavenge = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeScavenge as Form)
    int currentStoredFertilizer = containerRef.GetItemCount(WorkshopParent.WorkshopProduceFertilizer as Form)
    WorkshopParent.wsTrace(Self as string + "\t\tCheck stored resources: food=" + currentStoredFood as string + ", water=" + currentStoredWater as string + ", scavenge=" + currentStoredScavenge as string, 0, False)
    bool bAllowFoodProduction = True
    If (currentStoredFood > maxStoredFoodBase + maxStoredFoodPerPopulation * updateData.totalPopulation)
        bAllowFoodProduction = False
    EndIf
    bool bAllowWaterProduction = True
    If (currentStoredWater > maxStoredWaterBase + Math.floor(maxStoredWaterPerPopulation * updateData.totalPopulation as float))
        bAllowWaterProduction = False
    EndIf
    bool bAllowScavengeProduction = True
    If (currentStoredScavenge > maxStoredScavengeBase + maxStoredScavengePerPopulation * updateData.totalPopulation)
        bAllowScavengeProduction = False
    EndIf
    bool bAllowFertilizerProduction = True
    If (currentStoredFertilizer > maxStoredFertilizerBase)
        bAllowFertilizerProduction = False
    EndIf
    WorkshopParent.wsTrace(Self as string + "\t\tAllow production? food: " + bAllowFoodProduction as string + ", water: " + bAllowWaterProduction as string + ", scavenge: " + bAllowScavengeProduction as string, 0, False)
    If (updateData.foodProduction > 0 && bAllowFoodProduction)
        updateData.foodProduction = Math.floor(updateData.foodProduction as float * updateData.productivity)
        WorkshopParent.wsTrace(Self as string + "\t\tFOOD SURPLUS: +" + updateData.foodProduction as string, 0, False)
        If (updateData.foodProduction > 0)
            WorkshopParent.ProduceFood(Self, updateData.foodProduction)
        EndIf
    EndIf
    If (updateData.waterProduction > 0 && bAllowWaterProduction)
        WorkshopParent.wsTrace(Self as string + "\t\tWATER SURPLUS: +" + updateData.waterProduction as string, 0, False)
        containerRef.AddItem(WorkshopParent.WorkshopProduceWater as Form, updateData.waterProduction, False)
    EndIf
    If (updateData.brahminPopulation > 0 && bAllowFertilizerProduction)
        int fertilizerProduction = Math.min(updateData.brahminPopulation as float, maxBrahminFertilizerProduction as float) as int
        WorkshopParent.wsTrace(Self as string + "\t\tFERTILIZER PRODUCTION: +" + fertilizerProduction as string, 0, False)
        containerRef.AddItem(WorkshopParent.WorkshopProduceFertilizer as Form, fertilizerProduction, False)
    EndIf
    int scavengePopulation = (updateData.unassignedPopulation as float - Self.GetValue(ratings[WorkshopParent.WorkshopRatingDamagePopulation].resourceValue)) as int
    int scavengeProductionGeneral = Self.GetValue(ratings[WorkshopParent.WorkshopRatingScavengeGeneral].resourceValue) as int
    int scavengeAmount = Math.Ceiling(scavengePopulation as float * updateData.productivity * updateData.damageMult + scavengeProductionGeneral as float * updateData.productivity)
    WorkshopParent.wsTrace(Self as string + "\t\tscavenge population: " + scavengePopulation as string + " unassigned, " + scavengeProductionGeneral as string + " dedicated scavengers", 0, False)
    If (scavengeAmount > 0 && bAllowScavengeProduction)
        WorkshopParent.wsTrace(Self as string + "\t\tSCAVENGING: +" + scavengeAmount as string, 0, False)
        containerRef.AddItem(WorkshopParent.WorkshopProduceScavenge as Form, scavengeAmount, False)
    EndIf
    If (updateData.vendorIncome > 0 as float)
        WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
        WorkshopParent.wsTrace(Self as string + "\tVendor income: ", 0, False)
        WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
        WorkshopParent.wsTrace(Self as string + "\t\tProductivity mult: +" + updateData.productivity as string, 0, showVendorTraces)
        int vendorIncomeFinal = 0
        float linkedPopulation = WorkshopParent.GetLinkedPopulation(Self, False)
        WorkshopParent.wsTrace(Self as string + "\t\tLinked population: +" + linkedPopulation as string, 0, showVendorTraces)
        float vendorPopulation = linkedPopulation + updateData.totalPopulation as float
        WorkshopParent.wsTrace(Self as string + "\t\tTotal population: +" + vendorPopulation as string, 0, showVendorTraces)
        If (vendorPopulation >= minVendorIncomePopulation as float)
            linkedPopulation = WorkshopParent.GetLinkedPopulation(Self, True)
            WorkshopParent.wsTrace(Self as string + "\t\tLinked population (productivity adjusted): +" + linkedPopulation as string, 0, showVendorTraces)
            vendorPopulation = updateData.totalPopulation as float * updateData.productivity + linkedPopulation
            WorkshopParent.wsTrace(Self as string + "\t\tTotal vendor population: " + vendorPopulation as string, 0, showVendorTraces)
            WorkshopParent.wsTrace(Self as string + "\t\tBase income: +" + updateData.vendorIncome as string, 0, showVendorTraces)
            float incomeBonus = updateData.vendorIncome * vendorIncomePopulationMult * vendorPopulation
            WorkshopParent.wsTrace(Self as string + "\t\tPopulation bonus: +" + incomeBonus as string, 0, showVendorTraces)
            updateData.vendorIncome = updateData.vendorIncome + incomeBonus
            vendorIncomeFinal = Math.Ceiling(updateData.vendorIncome)
            vendorIncomeFinal = Math.min(vendorIncomeFinal as float, maxVendorIncome) as int
            If (vendorIncomeFinal as float >= 1)
                containerRef.AddItem(WorkshopParent.WorkshopProduceVendorIncome as Form, vendorIncomeFinal, False)
            EndIf
        EndIf
        WorkshopParent.wsTrace(Self as string + "\t\tVENDOR INCOME: " + vendorIncomeFinal as string, 0, showVendorTraces)
    EndIf
EndFunction

Function RepairDamage()
    WorkshopParent.wsTrace("\tRepair damage: " + Self as string, 0, False)
    workshopdatascript#workshopratingkeyword[] ratings = WorkshopParent.WorkshopRatings
    Self.RepairDamageToResource(ratings[WorkshopParent.WorkshopRatingFood].resourceValue)
    Self.RepairDamageToResource(ratings[WorkshopParent.WorkshopRatingWater].resourceValue)
    Self.RepairDamageToResource(ratings[WorkshopParent.WorkshopRatingSafety].resourceValue)
    Self.RepairDamageToResource(ratings[WorkshopParent.WorkshopRatingPower].resourceValue)
    Self.RepairDamageToResource(ratings[WorkshopParent.WorkshopRatingPopulation].resourceValue)
    float currentDamage = Self.GetValue(ratings[WorkshopParent.WorkshopRatingDamageCurrent].resourceValue)
    If (currentDamage > 0 as float)
        WorkshopParent.UpdateCurrentDamage(Self)
    EndIf
EndFunction

Event OnWorkshopObjectRepaired(ObjectReference akReference)
    workshopobjectactorscript workshopObjectActor = akReference as workshopobjectactorscript
    If (workshopObjectActor)
        workshopobjectscript workshopObject = akReference as workshopobjectscript
        workshopObject.OnDestructionStageChanged(1, 0)
    EndIf
    workshopobjectscript workshopObjectRef = akReference as workshopobjectscript
    If (workshopObjectRef)
        var[] kargs = new var[2]
        kargs[0] = workshopObjectRef as var
        kargs[1] = Self as var
        WorkshopParent.SendCustomEvent("workshopparentscript_WorkshopObjectRepaired", kargs)
    EndIf
EndEvent

int Function GetMaxWorkshopNPCs()
    int iMaxNPCs = iBaseMaxNPCs + Game.GetPlayer().GetValue(WorkshopParent.Charisma) as int
    return iMaxNPCs
EndFunction

ObjectReference[] Function InitializeVendorChests(int vendorType)
    int containerArraySize = WorkshopParent.VendorTopLevel + 1
    ObjectReference[] vendorContainers = new ObjectReference[containerArraySize]
    FormList vendorContainerList = WorkshopParent.WorkshopVendorContainers[vendorType]
    int vendorLevel = 0
    While (vendorLevel <= WorkshopParent.VendorTopLevel)
        vendorContainers[vendorLevel] = WorkshopParent.WorkshopHoldingCellMarker.PlaceAtMe(vendorContainerList.GetAt(vendorLevel), 1, False, False, True)
        vendorLevel += 1
    EndWhile
    return vendorContainers
EndFunction

int Function GetTotalWaterRating(workshopdatascript#workshopratingkeyword[] ratings)
    int waterRating = Self.GetValue(ratings[WorkshopParent.WorkshopRatingWater].resourceValue) as int
    waterRating += Self.GetContainer().GetItemCount(WorkshopParent.WorkshopConsumeWater as Form)
    return waterRating
EndFunction

int Function GetTotalFoodRating(workshopdatascript#workshopratingkeyword[] ratings)
    int foodRating = Self.GetValue(ratings[WorkshopParent.WorkshopRatingFood].resourceValue) as int
    foodRating += Self.GetContainer().GetItemCount(WorkshopParent.WorkshopConsumeFood as Form)
    return foodRating
EndFunction

Event OnWorkshopObjectMoved(ObjectReference akReference)
    workshopobjectscript workshopObjectRef = akReference as workshopobjectscript
    If (workshopObjectRef)
        var[] kargs = new var[2]
        kargs[0] = workshopObjectRef as var
        kargs[1] = Self as var
        WorkshopParent.SendCustomEvent("workshopparentscript_WorkshopObjectMoved", kargs)
    EndIf
EndEvent

Function CheckForAttack(bool bForceAttack)
    WorkshopParent.wsTrace("------------------------------------------------------------------------------ ", 0, False)
    WorkshopParent.wsTrace("\tCheck for attack: " + Self as string, 0, False)
    WorkshopParent.wsTrace("------------------------------------------------------------------------------ ", 0, False)
    workshopdatascript#workshopratingkeyword[] ratings = WorkshopParent.WorkshopRatings
    WorkshopParent.ModifyResourceData(ratings[WorkshopParent.WorkshopRatingLastAttackDaysSince].resourceValue, Self, 1)
    If (AllowAttacks == False)
        WorkshopParent.wsTrace("\t\tattacks not allowed - no attack roll for " + Self as string, 0, False)
        return 
    EndIf
    If (AllowAttacksBeforeOwned == False && OwnedByPlayer == False && bForceAttack == False)
        WorkshopParent.wsTrace("\t\tattacks on unowned workshop not allowed - no attack roll for " + Self as string, 0, False)
        return 
    EndIf
    ObjectReference containerRef = Self.GetContainer()
    If (!containerRef)
        WorkshopParent.wsTrace(Self as string + " ERROR - no container linked to workshop " + Self as string + " with " + WorkshopParent.WorkshopLinkContainer as string, 2, False)
        return 
    EndIf
    int totalPopulation = Self.GetBaseValue(ratings[WorkshopParent.WorkshopRatingPopulation].resourceValue) as int
    int safety = Self.GetValue(ratings[WorkshopParent.WorkshopRatingSafety].resourceValue) as int
    int safetyPerNPC = 0
    If (totalPopulation > 0)
        safetyPerNPC = Math.Ceiling((safety / totalPopulation) as float)
    ElseIf (bForceAttack)
        safetyPerNPC = safety
    Else
        WorkshopParent.wsTrace("\t\t0 population - no attack roll", 0, False)
        return 
    EndIf
    int daysSinceLastAttack = Self.GetValue(ratings[WorkshopParent.WorkshopRatingLastAttackDaysSince].resourceValue) as int
    If (minDaysSinceLastAttack > daysSinceLastAttack as float && !bForceAttack)
        WorkshopParent.wsTrace("\t\t" + daysSinceLastAttack as string + " days since last attack - no attack roll", 0, False)
        return 
    EndIf
    int foodRating = Self.GetTotalFoodRating(ratings)
    int waterRating = Self.GetTotalWaterRating(ratings)
    WorkshopParent.wsTrace("\tStarting stats:", 0, False)
    WorkshopParent.wsTrace("\t\tpopulation=" + totalPopulation as string, 0, False)
    WorkshopParent.wsTrace("\t\tfood rating=" + foodRating as string, 0, False)
    WorkshopParent.wsTrace("\t\twater rating=" + waterRating as string, 0, False)
    WorkshopParent.wsTrace("\t\ttotal safety=" + safety as string, 0, False)
    WorkshopParent.wsTrace("\t\tsafety per NPC=" + safetyPerNPC as string, 0, False)
    WorkshopParent.wsTrace("\tAttack chance:", 0, False)
    WorkshopParent.wsTrace("\t\tbase chance=" + attackChanceBase as string, 0, False)
    WorkshopParent.wsTrace("\t\tresources=+" + (attackChanceResourceMult * (foodRating + waterRating) as float) as string, 0, False)
    WorkshopParent.wsTrace("\t\tsafety=-" + (attackChanceSafetyMult * safety as float) as string, 0, False)
    WorkshopParent.wsTrace("\t\tpopulation=-" + (attackChancePopulationMult * totalPopulation as float) as string, 0, False)
    float attackChance = attackChanceBase + attackChanceResourceMult * (foodRating + waterRating) as float - attackChanceSafetyMult * safety as float - attackChancePopulationMult * totalPopulation as float
    If (attackChance < attackChanceBase)
        attackChance = attackChanceBase
    EndIf
    WorkshopParent.wsTrace("\t\tTOTAL=" + attackChance as string, 0, False)
    float attackRoll = Utility.RandomFloat(0, 1)
    WorkshopParent.wsTrace("\tAttack roll = " + attackRoll as string, 0, False)
    If (attackRoll <= attackChance || bForceAttack)
        int attackStrength = WorkshopParent.CalculateAttackStrength(foodRating, waterRating)
        WorkshopParent.TriggerAttack(Self, attackStrength)
    EndIf
EndFunction

Event OnUnload()
    DaysSinceLastVisit = 0
EndEvent

Event OnTimerGameTime(int aiTimerID)
    If (aiTimerID == dailyUpdateTimerID)
        If (WorkshopParent.IsEditLocked() || WorkshopParent.DailyUpdateInProgress)
            float waitTime = Utility.RandomFloat(minDailyUpdateWaitHours, maxDailyUpdateWaitHours)
            WorkshopParent.wsTrace(Self as string + " DailyUpdate: system busy, try again in " + waitTime as string + " game hours.", 0, False)
            Self.StartTimerGameTime(waitTime, dailyUpdateTimerID)
        Else
            Self.DailyUpdate(True)
        EndIf
    EndIf
EndEvent

Function DailyUpdate(bool bRealUpdate)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    WorkshopParent.wsTrace(Self as string + " \tDAILY UPDATE: bRealUpdate=" + bRealUpdate as string, 0, True)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    If (bDailyUpdateInProgress)
        If (bRealUpdate)
            While (bDailyUpdateInProgress)
                WorkshopParent.wsTrace(Self as string + "\t\twaiting for update lock to clear...", 0, False)
                Utility.wait(0.5)
            EndWhile
        Else
            WorkshopParent.wsTrace(Self as string + "\t\tupdate already in progress - don't try again right now", 0, False)
            return 
        EndIf
    EndIf
    bDailyUpdateInProgress = True
    WorkshopParent.DailyUpdateInProgress = True
    workshopdatascript#workshopratingkeyword[] ratings = WorkshopParent.WorkshopRatings
    workshopscript#dailyupdatedata updateData = new workshopscript#dailyupdatedata
    updateData.totalPopulation = Self.GetBaseValue(ratings[WorkshopParent.WorkshopRatingPopulation].resourceValue) as int
    updateData.robotPopulation = Self.GetBaseValue(ratings[WorkshopParent.WorkshopRatingPopulationRobots].resourceValue) as int
    updateData.brahminPopulation = Self.GetBaseValue(ratings[WorkshopParent.WorkshopRatingBrahmin].resourceValue) as int
    updateData.unassignedPopulation = Self.GetBaseValue(ratings[WorkshopParent.WorkshopRatingPopulationUnassigned].resourceValue) as int
    updateData.vendorIncome = Self.GetValue(ratings[WorkshopParent.WorkshopRatingVendorIncome].resourceValue) * vendorIncomeBaseMult
    updateData.currentHappiness = Self.GetValue(ratings[WorkshopParent.WorkshopRatingHappiness].resourceValue)
    updateData.damageMult = 1 as float - Self.GetValue(ratings[WorkshopParent.WorkshopRatingDamageCurrent].resourceValue) / 100
    updateData.productivity = Self.GetProductivityMultiplier(ratings)
    updateData.availableBeds = Self.GetBaseValue(ratings[WorkshopParent.WorkshopRatingBeds].resourceValue) as int
    updateData.shelteredBeds = Self.GetValue(ratings[WorkshopParent.WorkshopRatingBeds].resourceValue) as int
    updateData.bonusHappiness = Self.GetValue(ratings[WorkshopParent.WorkshopRatingBonusHappiness].resourceValue) as int
    updateData.happinessModifier = Self.GetValue(ratings[WorkshopParent.WorkshopRatingHappinessModifier].resourceValue) as int
    updateData.safety = Self.GetValue(ratings[WorkshopParent.WorkshopRatingSafety].resourceValue) as int
    updateData.safetyDamage = Self.GetValue(WorkshopParent.GetDamageRatingValue(ratings[WorkshopParent.WorkshopRatingSafety].resourceValue)) as int
    updateData.totalHappiness = 0
    If (bRealUpdate)
        Self.DailyUpdateAttractNewSettlers(ratings, updateData)
    EndIf
    ObjectReference containerRef = Self.GetContainer()
    If (!containerRef)
        WorkshopParent.wsTrace(Self as string + " ERROR - no container linked to workshop " + Self as string + " with " + WorkshopParent.WorkshopLinkContainer as string, 2, False)
        bDailyUpdateInProgress = False
        WorkshopParent.DailyUpdateInProgress = False
        return 
    EndIf
    If (Self.GetWorkshopID() as float == WorkshopParent.WorkshopCurrentWorkshopID.GetValue())
        ObjectReference[] WorkshopActors = WorkshopParent.GetWorkshopActors(Self)
        int I = 0
        While (I < WorkshopActors.length)
            WorkshopParent.UpdateActorsWorkObjects(WorkshopActors as workshopnpcscript, Self, False)
            I += 1
        EndWhile
    EndIf
    Self.DailyUpdateProduceResources(ratings, updateData, containerRef, bRealUpdate)
    Self.DailyUpdateConsumeResources(ratings, updateData, containerRef, bRealUpdate)
    If (bRealUpdate)
        Self.DailyUpdateSurplusResources(ratings, updateData, containerRef)
        Self.RepairDamage()
        Self.RecalculateWorkshopResources(True)
        Self.CheckForAttack(False)
    EndIf
    If (updateData.totalPopulation >= WorkshopParent.TradeCaravanMinimumPopulation && Self.GetValue(ratings[WorkshopParent.WorkshopRatingCaravan].resourceValue) > 0 as float)
        WorkshopParent.TradeCaravanWorkshops.AddRef(Self as ObjectReference)
    Else
        WorkshopParent.TradeCaravanWorkshops.RemoveRef(Self as ObjectReference)
    EndIf
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    WorkshopParent.wsTrace(Self as string + "\tDAILY UPDATE - DONE - bRbRealUpdate=" + bRealUpdate as string, 0, True)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    bDailyUpdateInProgress = False
    WorkshopParent.DailyUpdateInProgress = False
EndFunction

Function CheckOwnership()
    If (myLocation.IsCleared() && !OwnedByPlayer && EnableAutomaticPlayerOwnership)
        Self.SetOwnedByPlayer(True)
    EndIf
    If (!OwnedByPlayer)
        If (CustomUnownedMessage)
            CustomUnownedMessage.Show(0, 0, 0, 0, 0, 0, 0, 0, 0)
        Else
            int totalPopulation = Self.GetBaseValue(WorkshopParent.WorkshopRatings[WorkshopParent.WorkshopRatingPopulation].resourceValue) as int
            If (totalPopulation > 0)
                WorkshopParent.WorkshopUnownedSettlementMessage.Show(0, 0, 0, 0, 0, 0, 0, 0, 0)
            ElseIf (myLocation.IsCleared() == False && EnableAutomaticPlayerOwnership)
                WorkshopParent.WorkshopUnownedHostileMessage.Show(0, 0, 0, 0, 0, 0, 0, 0, 0)
            Else
                WorkshopParent.WorkshopUnownedMessage.Show(0, 0, 0, 0, 0, 0, 0, 0, 0)
            EndIf
        EndIf
    EndIf
EndFunction

Event OnLoad()
    Self.BlockActivation(!OwnedByPlayer, False)
    If (Self.GetBaseObject() as Container)
        ObjectReference linkedContainer = Self.GetLinkedRef(WorkshopParent.WorkshopLinkContainer)
        If (linkedContainer)
            linkedContainer.RemoveAllItems(Self as ObjectReference, False)
        EndIf
        ObjectReference[] linkedContainers = Self.GetLinkedRefChildren(WorkshopParent.WorkshopLinkContainer)
        int I = 0
        While (I < linkedContainers.length)
            linkedContainer = linkedContainers
            If (linkedContainer)
                linkedContainer.RemoveAllItems(Self as ObjectReference, False)
            EndIf
            I += 1
        EndWhile
    EndIf
    If (!myLocation)
        myLocation = Self.GetCurrentLocation()
    EndIf
EndEvent

Event OnTimer(int aiTimerID)
    If (aiTimerID == buildWorkObjectTimerID)
        WorkshopParent.TryToAssignResourceObjectsPUBLIC(Self)
    EndIf
EndEvent

ObjectReference Function GetContainer()
    If (Self.GetBaseObject() as Container)
        return Self as ObjectReference
    Else
        return Self.GetLinkedRef(WorkshopParent.WorkshopLinkContainer)
    EndIf
EndFunction

Event OnWorkshopObjectPlaced(ObjectReference akReference)
    If (WorkshopParent.BuildObjectPUBLIC(akReference, Self))
        Self.StartTimer(3, buildWorkObjectTimerID)
    EndIf
EndEvent

Event OnWorkshopMode(bool aStart)
    If (aStart)
        If (OwnedByPlayer)
            WorkshopParent.SetCurrentWorkshop(Self)
        EndIf
        var[] kargs = new var[2]
        kargs[0] = None
        kargs[1] = Self as var
        WorkshopParent.SendCustomEvent("workshopparentscript_WorkshopEnterMenu", kargs)
    EndIf
    If (aStart && WorkshopParent.DogmeatAlias.GetRef() as bool)
        WorkshopParent.WorkshopDogmeatWhileBuildingScene.Start()
    Else
        WorkshopParent.WorkshopDogmeatWhileBuildingScene.Stop()
    EndIf
    If (aStart && WorkshopParent.CompanionAlias.GetRef() as bool)
        WorkshopParent.WorkshopCompanionWhileBuildingScene.Start()
    Else
        WorkshopParent.WorkshopCompanionWhileBuildingScene.Stop()
    EndIf
    If (!aStart)
        If (ShowedWorkshopMenuExitMessage == False)
            ShowedWorkshopMenuExitMessage = True
            WorkshopParent.WorkshopExitMenuMessage.ShowAsHelpMessage("WorkshopMenuExit", 10, 0 as float, 1, "NoMenu", 0)
        EndIf
        WorkshopParent.TryToAssignResourceObjectsPUBLIC(Self)
    EndIf
    Self.DailyUpdate(False)
EndEvent

Function DailyUpdateProduceResources(workshopdatascript#workshopratingkeyword[] ratings, workshopscript#dailyupdatedata updateData, ObjectReference containerRef, bool bRealUpdate)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    WorkshopParent.wsTrace(Self as string + "\tProduce resources: ", 0, False)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    updateData.foodProduction = Self.GetValue(ratings[WorkshopParent.WorkshopRatingFood].resourceValue) as int
    updateData.waterProduction = Self.GetValue(ratings[WorkshopParent.WorkshopRatingWater].resourceValue) as int
    WorkshopParent.wsTrace(Self as string + "\t\t\tBase food: " + updateData.foodProduction as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\t\tBase water: " + updateData.waterProduction as string, 0, False)
    int missingSafety = Math.max(0 as float, (updateData.totalPopulation - updateData.safety) as float) as int
    WorkshopParent.SetResourceData(ratings[WorkshopParent.WorkshopRatingMissingSafety].resourceValue, Self, missingSafety as float)
    updateData.foodProduction = Math.max(0 as float, (updateData.foodProduction - Self.GetValue(WorkshopParent.GetDamageRatingValue(ratings[WorkshopParent.WorkshopRatingFood].resourceValue)) as int) as float) as int
    updateData.waterProduction = Math.max(0 as float, (updateData.waterProduction - Self.GetValue(WorkshopParent.GetDamageRatingValue(ratings[WorkshopParent.WorkshopRatingWater].resourceValue)) as int) as float) as int
    If (updateData.brahminPopulation > 0)
        int brahminMaxFoodBoost = Math.min((updateData.brahminPopulation * maxProductionPerBrahmin) as float, updateData.foodProduction as float) as int
        int brahminFoodProduction = Math.Ceiling(brahminMaxFoodBoost as float * brahminProductionBoost)
        WorkshopParent.wsTrace(Self as string + "\t\t\tBrahmin food boost: " + brahminFoodProduction as string, 0, False)
        updateData.foodProduction = updateData.foodProduction + brahminFoodProduction
    EndIf
    WorkshopParent.SetResourceData(ratings[WorkshopParent.WorkshopRatingFoodActual].resourceValue, Self, updateData.foodProduction as float)
    WorkshopParent.wsTrace(Self as string + "\t\t\tActual production:", 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\t\t\tFood: " + updateData.foodProduction as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\t\t\tWater: " + updateData.waterProduction as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    WorkshopParent.wsTrace(Self as string + "\tConsume resources and calculate happiness: ", 0, False)
    WorkshopParent.wsTrace(Self as string + "------------------------------------------------------------------------------ ", 0, False)
    updateData.safety = Math.max((updateData.safety - updateData.safetyDamage) as float, 0 as float) as int
    updateData.safetyPerNPC = 0
    If (updateData.totalPopulation > 0)
        updateData.safetyPerNPC = Math.Ceiling((updateData.safety / updateData.totalPopulation) as float)
    EndIf
    updateData.availableFood = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeFood as Form)
    updateData.availableWater = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeWater as Form)
    WorkshopParent.wsTrace(Self as string + "\tStarting stats:", 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tpopulation=" + updateData.totalPopulation as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tfood=" + updateData.availableFood as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\twater=" + updateData.availableWater as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tbeds=" + updateData.availableBeds as string + " (" + updateData.shelteredBeds as string + " sheltered)", 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tbonus happiness=" + updateData.bonusHappiness as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\ttotal safety=" + updateData.safety as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tsafety per NPC=" + updateData.safetyPerNPC as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tunassignedPopulation=" + updateData.unassignedPopulation as string, 0, False)
    updateData.availableFood = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeFood as Form) + updateData.foodProduction
    updateData.availableWater = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeWater as Form) + updateData.waterProduction
    WorkshopParent.wsTrace(Self as string + "\tAfter production:", 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tfood=" + updateData.availableFood as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\twater=" + updateData.availableWater as string, 0, False)
    int neededFood = updateData.totalPopulation - updateData.robotPopulation - updateData.availableFood
    int neededWater = updateData.totalPopulation - updateData.robotPopulation - updateData.availableWater
    If (neededFood > 0 || neededWater > 0)
        WorkshopParent.wsTrace(Self as string + "\tResource shortage - try to get from linked workshops:", 0, False)
        WorkshopParent.wsTrace(Self as string + "\t\tfood=" + neededFood as string, 0, False)
        WorkshopParent.wsTrace(Self as string + "\t\twater=" + neededWater as string, 0, False)
        WorkshopParent.TransferResourcesFromLinkedWorkshops(Self, neededFood, neededWater)
    EndIf
    updateData.availableFood = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeFood as Form) + updateData.foodProduction
    updateData.availableWater = containerRef.GetItemCount(WorkshopParent.WorkshopConsumeWater as Form) + updateData.waterProduction
    WorkshopParent.wsTrace(Self as string + "\tFinal stats after transfers:", 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\tfood=" + updateData.availableFood as string, 0, False)
    WorkshopParent.wsTrace(Self as string + "\t\twater=" + updateData.availableWater as string, 0, False)
EndFunction

bool Function RecalculateWorkshopResources(bool bOnlyIfLocationLoaded)
    If (bOnlyIfLocationLoaded == False || myLocation.IsLoaded())
        Self.RecalculateResources()
        return True
    Else
        return False
    EndIf
EndFunction

Function InitWorkshopID(int newWorkshopID)
    If (WorkshopID < 0)
        WorkshopID = newWorkshopID
    EndIf
EndFunction

float Function CalculateRepairAmount(workshopdatascript#workshopratingkeyword[] ratings)
    float uninjuredPopulation = Self.GetValue(ratings[WorkshopParent.WorkshopRatingPopulation].resourceValue)
    float productivityMult = Self.GetProductivityMultiplier(ratings)
    float amountRepaired = Math.Ceiling(uninjuredPopulation * damageDailyPopulationMult * damageDailyRepairBase * productivityMult) as float
    return amountRepaired
EndFunction

ObjectReference[] Function GetVendorContainersByType(int vendorType)
    If (vendorType == 0)
        If (VendorContainersMisc == None)
            VendorContainersMisc = Self.InitializeVendorChests(vendorType)
        EndIf
        return VendorContainersMisc
    ElseIf (vendorType == 1)
        If (VendorContainersArmor == None)
            VendorContainersArmor = Self.InitializeVendorChests(vendorType)
        EndIf
        return VendorContainersArmor
    ElseIf (vendorType == 2)
        If (VendorContainersWeapons == None)
            VendorContainersWeapons = Self.InitializeVendorChests(vendorType)
        EndIf
        return VendorContainersWeapons
    ElseIf (vendorType == 3)
        If (VendorContainersBar == None)
            VendorContainersBar = Self.InitializeVendorChests(vendorType)
        EndIf
        return VendorContainersBar
    ElseIf (vendorType == 4)
        If (VendorContainersClinic == None)
            VendorContainersClinic = Self.InitializeVendorChests(vendorType)
        EndIf
        return VendorContainersClinic
    ElseIf (vendorType == 5)
        If (VendorContainersClothing == None)
            VendorContainersClothing = Self.InitializeVendorChests(vendorType)
        EndIf
        return VendorContainersClothing
    EndIf
EndFunction

Event OnActivate(ObjectReference akActionRef)
    If (akActionRef == Game.GetPlayer() as ObjectReference)
        Self.CheckOwnership()
        If (OwnedByPlayer)
            Self.StartWorkshop(True)
        EndIf
    EndIf
EndEvent

Event WorkshopParentScript.WorkshopDailyUpdate(workshopparentscript akSender, var[] akArgs)
    float waitTime = WorkshopParent.dailyUpdateIncrement * WorkshopID as float
    Self.StartTimerGameTime(waitTime, dailyUpdateTimerID)
EndEvent

By the way I was able to decompile the script and see its source code. Maybe that can tell something

Link to comment
Share on other sites

9 hours ago, strusik said:

The thing is, I wouldn't say the mod itself was breaking my game. Everything worked when it was installed, the problems started after I uninstalled it (I tracked it down through my save history), I have a theory that I might be able to get it working again if I can figure out which files in the root folder of the game are responsible for the workshop and replace them with vanilla files (my ideas on this might seem strange to you, but I think it should work), since I think the mod overwrote something and now it refuses to work without it

Well I did read a lot of posts that shared a whole number of problems and issues w/the mod in question ... again, sadly .... that does not speak well for the mod and it's likely reason.

As one person suggested: If you have a prior game before the mod was installed try it, ( though based on my reading the specific posts in the Mod Site in question ... ) I'm not sure that's going to work .. but, yes, give it a try.

I had one person tell me ... he had tried a mod that really had NOTHING to do with setting up settlement quests ... until this mod he tried. Screwed up so he only got three ( Settlement set up quests ) and the rest were locked out. He actually had to do a total clean install of FO 4 , which took him seriously out of his ".. happy place .."

 

Link to comment
Share on other sites

42 minutes ago, jjb54 said:

Well I did read a lot of posts that shared a whole number of problems and issues w/the mod in question ... again, sadly .... that does not speak well for the mod and it's likely reason.

As one person suggested: If you have a prior game before the mod was installed try it, ( though based on my reading the specific posts in the Mod Site in question ... ) I'm not sure that's going to work .. but, yes, give it a try.

I had one person tell me ... he had tried a mod that really had NOTHING to do with setting up settlement quests ... until this mod he tried. Screwed up so he only got three ( Settlement set up quests ) and the rest were locked out. He actually had to do a total clean install of FO 4 , which took him seriously out of his ".. happy place .."

 

I think I'm going to have to start my walkthrough all over again too. Actually, I always look and read carefully everything about a mod when I want to install it, and in general I have a very good collection of mods, with which there were no problems. But apparently, when I downloaded this s#*&#33;, I did it late at night and tired, and at this fateful moment did not pay attention. This "good" author ended up breaking another mod for me first, and after I tried to fix it, he broke my game as well 🙈 It's a pity that on nexus there are so many such mods, which are not moderated in any way, it would be great to have something like a rating system, where shitty mods that have low ratings would be removed from the site

Link to comment
Share on other sites

If you want to have more settlement attacks you can try this one:

https://www.mediafire.com/file/4ibjnbfpl4tv0uk/More_Attackers_-_Get_Off_My_Buildzone-27465-1-1-7.7z/file

It has MCM support and let's attackers spawn out of your buildzone. If you want to try please make a save before and perhaps a copy of it.

I use this mod since many years and never experienced any problem with it, but I do not use Sim Settlements.

Link to comment
Share on other sites

3 hours ago, subaverage said:

If you want to have more settlement attacks you can try this one:

https://www.mediafire.com/file/4ibjnbfpl4tv0uk/More_Attackers_-_Get_Off_My_Buildzone-27465-1-1-7.7z/file

It has MCM support and let's attackers spawn out of your buildzone. If you want to try please make a save before and perhaps a copy of it.

I use this mod since many years and never experienced any problem with it, but I do not use Sim Settlements.

Thanks for the recommendation, I've already had time to install another mod in the meantime: https://www.nexusmods.com/fallout4/mods/37393
It is also quite good and has compatibility with Sim Settlements 2 and allows you to create really huge raids on half a hundred opponents and even more (it can be customized in the mod itself)

Link to comment
Share on other sites

On 2/1/2024 at 4:37 PM, strusik said:

I think I'm going to have to start my walkthrough all over again too. Actually, I always look and read carefully everything about a mod when I want to install it, and in general I have a very good collection of mods, with which there were no problems. But apparently, when I downloaded this s#*&#33;, I did it late at night and tired, and at this fateful moment did not pay attention. This "good" author ended up breaking another mod for me first, and after I tried to fix it, he broke my game as well 🙈 It's a pity that on nexus there are so many such mods, which are not moderated in any way, it would be great to have something like a rating system, where shitty mods that have low ratings would be removed from the site

 Cannot tell you how many times I've done that very thing, only to kick myself in the butt for doing it! So I totally understand as I have "been there done that" also.

Link to comment
Share on other sites

9 hours ago, jjb54 said:

 Cannot tell you how many times I've done that very thing, only to kick myself in the butt for doing it! So I totally understand as I have "been there done that" also.

Well, nothing to do, these mods are like a drug, first installed one and then you can not stop, haha 😅 the process of installing and searching for mods for me bring more fun than the game itself. We're junkies and sometimes we get some bad shіt 😅🥴

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...