some refactoring, and moved NPCs persist on load

This commit is contained in:
Lilian Jónsdóttir 2020-08-15 17:17:21 -07:00
parent 293babb69a
commit 4f9d4635e2

View file

@ -15,13 +15,14 @@ local waistworks = {"^[Vv]ivec,?.*[Ww]aist", "[Cc]analworks", "[Ww]aistworks"}
local updateTimer local updateTimer
-- NPC homes -- NPC homes
local homedNPCS = {}
local publicHouses = {} local publicHouses = {}
local homes = {byName = {}, byCell = {}}
-- city name if cell.name is nil -- city name if cell.name is nil
local wilderness = "Wilderness" local wilderness = "Wilderness"
-- maybe this shouldn't be hardcoded -- maybe this shouldn't be hardcoded
local publicHouseTypes = {inns = "Inns", guildhalls = "Guildhalls", temples = "Temples", houses = "Houses"} local publicHouseTypes = {inns = "Inns", guildhalls = "Guildhalls", temples = "Temples", houses = "Houses"}
-- local movedNPCs = {} local movedNPCs = {}
-- build a list of followers on cellChange -- build a list of followers on cellChange
local followers = {} local followers = {}
@ -115,7 +116,7 @@ end
-- {{{ housing -- {{{ housing
-- ? I honestly don't know if there are any wandering NPCs that "live" in close-by manors, but I wrote this anyway -- ? I honestly don't know if there are any wandering NPCs that "live" in close-by manors, but I wrote this anyway
local function checkIfManor(cellName, npcName) local function checkManor(cellName, npcName)
if not cellName or (cellName and not string.find(cellName, "Manor")) then return end if not cellName or (cellName and not string.find(cellName, "Manor")) then return end
local splitName = common.split(npcName) local splitName = common.split(npcName)
@ -134,8 +135,8 @@ local function pickPublicHouseType(cellName)
return publicHouseTypes.guildhalls return publicHouseTypes.guildhalls
elseif cellName:match("Temple") then elseif cellName:match("Temple") then
return publicHouseTypes.temples return publicHouseTypes.temples
elseif cellName:match("House") then -- elseif cellName:match("House") then
return publicHouseTypes.houses -- return publicHouseTypes.houses
else else
return publicHouseTypes.inns return publicHouseTypes.inns
end end
@ -184,81 +185,93 @@ local function pickPublicHouseForNPC(npc, city)
return pickInnForNPC(npc, city) return pickInnForNPC(npc, city)
end end
local function createHomedNPCTableEntry(npc, home, startingPlace, isHome) local function createHomedNPCTableEntry(npc, home, startingPlace, isHome, position, orientation)
if npc.object and (npc.object.name == nil or npc.object.name == "") then return end if npc.object and (npc.object.name == nil or npc.object.name == "") then return end
log(common.logLevels.medium, "Found home for %s: %s... adding it to in memory table...", npc.object.name, home.id) log(common.logLevels.medium, "Found %s for %s: %s... adding it to in memory table...",
isHome and "home" or "public house", npc.object.name, home.id)
local pickedPosition, pickedOrientation, p, o local pickedPosition, pickedOrientation, pos, ori
-- mod support for different positions in cells -- mod support for different positions in cells
local id = checkModdedCell(home.id) local id = checkModdedCell(home.id)
if isHome and positions.npcs[npc.object.name] then if isHome and positions.npcs[npc.object.name] then
p = positions.npcs[npc.object.name].position pos = positions.npcs[npc.object.name].position
o = positions.npcs[npc.object.name].orientation ori = positions.npcs[npc.object.name].orientation
pickedPosition = positions.npcs[npc.object.name] and tes3vector3.new(p[1], p[2], p[3]) or zeroVector:copy() -- pickedPosition = positions.npcs[npc.object.name] and tes3vector3.new(p[1], p[2], p[3]) or zeroVector:copy()
pickedOrientation = positions.npcs[npc.object.name] and tes3vector3.new(o[1], o[2], o[3]) or zeroVector:copy() -- pickedOrientation = positions.npcs[npc.object.name] and tes3vector3.new(o[1], o[2], o[3]) or zeroVector:copy()
elseif positions.cells[id] then elseif positions.cells[id] then
p = table.choice(positions.cells[id]).position pos = table.choice(positions.cells[id]).position
o = table.choice(positions.cells[id]).orientation ori = table.choice(positions.cells[id]).orientation
pickedPosition = positions.cells[id] and tes3vector3.new(p[1], p[2], p[3]) or zeroVector:copy() -- pickedPosition = positions.cells[id] and tes3vector3.new(p[1], p[2], p[3]) or zeroVector:copy()
pickedOrientation = positions.cells[id] and tes3vector3.new(o[1], o[2], o[3]) or zeroVector:copy() -- pickedOrientation = positions.cells[id] and tes3vector3.new(o[1], o[2], o[3]) or zeroVector:copy()
-- pickedPosition = tes3vector3.new(p[1], p[2], p[3])
-- pickedOrientation = tes3vector3.new(o[1], o[2], o[3])
else else
pickedPosition = zeroVector:copy() pos = {0,0,0}
pickedOrientation = zeroVector:copy() ori = {0,0,0}
-- pickedPosition = zeroVector:copy()
-- pickedOrientation = zeroVector:copy()
end end
pickedPosition = tes3vector3.new(pos[1], pos[2], pos[3])
pickedOrientation = tes3vector3.new(ori[1], ori[2], ori[3])
local ogPosition = position and
(tes3vector3.new(position.x, position.y, position.z)) or
(npc.position and npc.position:copy() or zeroVector:copy())
local ogOrientation = orientation and
(tes3vector3.new(orientation.x, orientation.y, orientation.z)) or
(npc.orientation and npc.orientation:copy() or zeroVector:copy())
local this = { local this = {
name = npc.object.name, name = npc.object.name,
npc = npc, npc = npc, -- tes3npc
isHome = isHome, isHome = isHome, -- bool
home = home, home = home, -- tes3cell
homeName = home.id, homeName = home.id,
ogPlace = startingPlace, ogPlace = startingPlace, -- tes3cell
ogPlaceName = startingPlace.id, ogPlaceName = startingPlace.id,
ogPosition = npc.position and npc.position:copy() or zeroVector:copy(), ogPosition = ogPosition,
ogOrientation = npc.orientation and npc.orientation:copy() or zeroVector:copy(), ogOrientation = ogOrientation,
homePosition = pickedPosition, homePosition = pickedPosition, -- tes3vector3
homeOrientation = pickedOrientation, homeOrientation = pickedOrientation, -- tes3vector3
worth = calculateNPCWorth(npc) worth = calculateNPCWorth(npc) -- int
} }
homedNPCS[home.id] = this homes.byName[npc.object.name] = this
if isHome then homes.byCell[home.id] = this end
interop.setHomedNPCTable(homedNPCS) interop.setHomedNPCTable(homes.byName)
return this return this
end end
local function createPublicHouseTableEntry(publicCell, proprietor) local function createPublicHouseTableEntry(publicCell, proprietor, city, name)
local city, publicHouseName local typeOfPub = pickPublicHouseType(publicCell.name)
if publicCell.name and string.match(publicCell.name, ",") then
city = common.split(publicCell.name, ",")[1]
publicHouseName = common.split(publicCell.name, ",")[2]:gsub("^%s", "")
else
city = wilderness
publicHouseName = publicCell.id
end
local type = pickPublicHouseType(publicCell.name)
local worth = 0 local worth = 0
-- for houses, worth is equal to NPC who lives there -- for houses, worth is equal to NPC who lives there
if type == publicHouseTypes.houses then -- if typeOfPub == publicHouseTypes.houses then
worth = calculateNPCWorth(proprietor) -- worth = calculateNPCWorth(proprietor)
else -- else
-- for other types, worth is combined worth of all NPCs -- for other types, worth is combined worth of all NPCs
for innard in publicCell:iterateReferences(tes3.objectType.npc) do for innard in publicCell:iterateReferences(tes3.objectType.npc) do
if innard == proprietor then
worth = worth + calculateNPCWorth(innard, publicCell)
else
worth = worth + calculateNPCWorth(innard) worth = worth + calculateNPCWorth(innard)
end end
end end
-- end
if not publicHouses[city] then publicHouses[city] = {} end if not publicHouses[city] then publicHouses[city] = {} end
if not publicHouses[city][type] then publicHouses[city][type] = {} end if not publicHouses[city][typeOfPub] then publicHouses[city][typeOfPub] = {} end
publicHouses[city][type][publicCell.name] = { publicHouses[city][typeOfPub][publicCell.id] = {
name = publicHouseName, name = name,
city = city, city = city,
cell = publicCell, cell = publicCell,
proprietor = proprietor, proprietor = proprietor,
@ -266,7 +279,7 @@ local function createPublicHouseTableEntry(publicCell, proprietor)
worth = worth worth = worth
} }
interop.setInnTable(publicHouses) interop.setPublicHouseTable(publicHouses)
end end
-- looks through doors to find a cell that matches a wandering NPCs name -- looks through doors to find a cell that matches a wandering NPCs name
@ -282,11 +295,17 @@ local function pickHomeForNPC(cell, npc)
for door in cell:iterateReferences(tes3.objectType.door) do for door in cell:iterateReferences(tes3.objectType.door) do
if door.destination then if door.destination then
local dest = door.destination.cell local dest = door.destination.cell
if dest.id:match(name) or checkIfManor(dest.name, name) then
-- essentially, if npc full name, or surname matches the cell name
if dest.id:match(name) or checkManor(dest.name, name) then
if homes.byName[name] then -- already have a home, don't create the table entry again
return homes.byName[name]
else
return createHomedNPCTableEntry(npc, dest, cell, true) return createHomedNPCTableEntry(npc, dest, cell, true)
end end
end end
end end
end
-- haven't found a home, so put them in an inn or guildhall -- haven't found a home, so put them in an inn or guildhall
if config.homelessWanderersToPublicHouses then if config.homelessWanderersToPublicHouses then
@ -304,11 +323,13 @@ end
-- {{{ checks -- {{{ checks
local function fargothCheck() local function fargothCheck()
local fargothJournal = tes3.getJournalIndex({ id = "MS_Lookout" }) local fargothJournal = tes3.getJournalIndex({id = "MS_Lookout"})
if not fargothJournal then return false end if not fargothJournal then return false end
-- only disable Fargoth before speaking to Hrisskar, and after observing Fargoth sneak -- only disable Fargoth before speaking to Hrisskar, and after observing Fargoth sneak
log(common.logLevels.large, "Fargoth journal check %s: %s", fargothJournal, fargothJournal > 10 and fargothJournal <= 30) log(common.logLevels.large, "Fargoth journal check %s: %s", fargothJournal,
fargothJournal > 10 and fargothJournal <= 30)
return fargothJournal > 10 and fargothJournal <= 30 return fargothJournal > 10 and fargothJournal <= 30
end end
@ -319,10 +340,15 @@ local function isIgnoredNPC(npc)
local isDead = false local isDead = false
local isHostile = false local isHostile = false
local isVampire = false local isVampire = false
if npc.mobile then if npc.mobile then
if npc.mobile.health.current <= 0 then isDead = true end if npc.mobile.health.current <= 0 or npc.mobile.isDead then isDead = true end
if npc.mobile.fight > 70 then isHostile = true end if npc.mobile.fight > 70 then isHostile = true end
isVampire = tes3.isAffectedBy({reference = npc, effect = tes3.effect.vampirism}) isVampire = tes3.isAffectedBy({reference = npc, effect = tes3.effect.vampirism})
else
-- local fight = getFightFromSpawnedReference(obj.id) -- ! calling this hundreds of times is bad for performance lol
-- if (fight or 0) > 70 then isHostile = true end
isVampire = obj.head.vampiric and true or false -- don't set a reference ... is bool even a reference type??
end end
local isFargothActive = obj.id == "fargoth" and fargothCheck() or false local isFargothActive = obj.id == "fargoth" and fargothCheck() or false
@ -332,15 +358,15 @@ local function isIgnoredNPC(npc)
-- local isVampire = mwscript.getSpellEffects({reference = npc, spell = "vampire sun damage"}) -- local isVampire = mwscript.getSpellEffects({reference = npc, spell = "vampire sun damage"})
-- this just keeps getting uglier but it's debug logging so whatever I don't care -- this just keeps getting uglier but it's debug logging so whatever I don't care
log(common.logLevels.large, ("Checking NPC:%s (%s or %s): id blocked:%s, mod blocked:%s " .. log(common.logLevels.large, ("Checking NPC:%s (%s or %s): id blocked:%s, %s blocked:%s " .. --
"guard:%s dead:%s vampire:%s werewolf:%s dreamer:%s follower:%s hostile:%s %s%s"), "guard:%s dead:%s vampire:%s werewolf:%s dreamer:%s follower:%s hostile:%s %s%s"), --
obj.name, npc.object.id, npc.object.baseObject and npc.object.baseObject.id or "nil", obj.name, npc.object.id, npc.object.baseObject and npc.object.baseObject.id or "nil", --
config.ignored[obj.id], config.ignored[obj.sourceMod], obj.isGuard, isDead, isVampire, config.ignored[string.lower(obj.id)], obj.sourceMod, config.ignored[string.lower(obj.sourceMod)], --
isWerewolf, (obj.class and obj.class.id == "Dreamers"), followers[obj.id], isHostile, obj.isGuard, isDead, isVampire, isWerewolf, (obj.class and obj.class.id == "Dreamers"), --
obj.id == "fargoth" and "fargoth:" or "", obj.id == "fargoth" and isFargothActive or "") followers[obj.id], isHostile, obj.id == "fargoth" and "fargoth:" or "", obj.id == "fargoth" and isFargothActive or "")
return config.ignored[obj.id] or -- return config.ignored[string.lower(obj.id)] or --
config.ignored[obj.sourceMod] or -- config.ignored[string.lower(obj.sourceMod)] or --
obj.isGuard or -- obj.isGuard or --
isFargothActive or -- isFargothActive or --
isDead or -- don't move dead NPCS isDead or -- don't move dead NPCS
@ -352,49 +378,65 @@ local function isIgnoredNPC(npc)
end end
-- checks NPC class and faction in cells for block list and adds to publicHouse list -- checks NPC class and faction in cells for block list and adds to publicHouse list
-- todo: rewrite this
local function isPublicHouse(cell) local function isPublicHouse(cell)
local typeOfPub = pickPublicHouseType(cell.name)
local city, publicHouseName
if cell.name and string.match(cell.name, ",") then
city = common.split(cell.name, ",")[1]
publicHouseName = common.split(cell.name, ",")[2]:gsub("^%s", "")
else
city = wilderness
publicHouseName = cell.id
end
-- don't iterate NPCs in the cell if we've already marked it public
if publicHouses[city] and (publicHouses[city][typeOfPub] and publicHouses[city][typeOfPub][cell.id]) then return true end
local npcs = {factions = {}, total = 0} local npcs = {factions = {}, total = 0}
for npc in cell:iterateReferences(tes3.objectType.npc) do for npc in cell:iterateReferences(tes3.objectType.npc) do
-- Check for NPCS of ignored classes first -- Check for NPCS of ignored classes first
if not isIgnoredNPC(npc) and (npc.object.class and config.ignored[npc.object.class.id]) then if not isIgnoredNPC(npc) then
if npc.object.class and config.ignored[npc.object.class.id] then
log(common.logLevels.medium, "NPC:\'%s\' of class:\'%s\' made %s public", npc.object.name, log(common.logLevels.medium, "NPC:\'%s\' of class:\'%s\' made %s public", npc.object.name,
npc.object.class and npc.object.class.id or "none", cell.name) npc.object.class and npc.object.class.id or "none", cell.name)
createPublicHouseTableEntry(cell, npc) createPublicHouseTableEntry(cell, npc, city, publicHouseName)
return true return true
end end
local faction = npc.object.faction-- and npc.object.faction.id local faction = npc.object.faction
if faction then if faction then
if not npcs.factions[faction] then npcs.factions[faction] = {total = 0, percentage = 0} end if not npcs.factions[faction] then npcs.factions[faction] = {total = 0, percentage = 0} end
if not npcs.factions[faction].master or npcs.factions[faction].master.object.factionIndex < npc.object.factionIndex then if not npcs.factions[faction].master or npcs.factions[faction].master.object.factionIndex <
npcs.factions[faction].master = npc npc.object.factionIndex then npcs.factions[faction].master = npc end
end
npcs.factions[faction].total = npcs.factions[faction].total + 1 npcs.factions[faction].total = npcs.factions[faction].total + 1
end end
npcs.total = npcs.total + 1 npcs.total = npcs.total + 1
end
end end
-- no NPCs of ignored classes, so let's check out factions -- no NPCs of ignored classes, so let's check out factions
for faction, info in pairs(npcs.factions) do for faction, info in pairs(npcs.factions) do
info.percentage = ( info.total / npcs.total ) * 100 info.percentage = (info.total / npcs.total) * 100
log(common.logLevels.large, log(common.logLevels.large,
"No NPCs of ignored class in %s, checking faction %s (ignored: %s, player joined: %s) with %s (%s%%) vs total %s", cell.name, "No NPCs of ignored class in %s, checking faction %s (ignored: %s, player joined: %s) with %s (%s%%) vs total %s",
faction, config.ignored[faction.id], faction.playerJoined, info.total, info.percentage, npcs.total) cell.name, faction, config.ignored[faction.id], faction.playerJoined, info.total, info.percentage,
npcs.total)
-- less than 3 NPCs can't possibly be a public house unless it's a Blades house -- less than 3 NPCs can't possibly be a public house unless it's a Blades house
if ( config.ignored[faction.id] or faction.playerJoined ) and (npcs.total >= config.minimumOccupancy or faction == "Blades") and if (config.ignored[faction.id] or faction.playerJoined) and
info.percentage >= config.factionIgnorePercentage then (npcs.total >= config.minimumOccupancy or faction == "Blades") and info.percentage >=
log(common.logLevels.medium, "%s is %s%% faction %s, marking public.", cell.name, info.percentage, config.factionIgnorePercentage then
faction) log(common.logLevels.medium, "%s is %s%% faction %s, marking public.", cell.name, info.percentage, faction)
createPublicHouseTableEntry(cell, npcs.factions[faction].master) createPublicHouseTableEntry(cell, npcs.factions[faction].master, city, publicHouseName)
return true return true
end end
end end
@ -411,13 +453,13 @@ local function isIgnoredDoor(door, homeCellId)
return true return true
end end
-- Only doors in cities and towns (cells whose names share the same first characters) -- Only doors in cities and towns (interior cells with names that contain the exterior cell)
-- todo: if destination cell name contains outside cell name local inCity = string.match(door.destination.cell.id, homeCellId)
local inCity = string.sub(homeCellId, 1, 4) == string.sub(door.destination.cell.id, 1, 4)
-- peek inside doors to look for guild halls, inns and clubs -- peek inside doors to look for guild halls, inns and clubs
local leadsToPublicCell = isPublicHouse(door.destination.cell) local leadsToPublicCell = isPublicHouse(door.destination.cell)
-- don't lock unoccupied cells
local hasOccupants = false local hasOccupants = false
for npc in door.destination.cell:iterateReferences(tes3.objectType.npc) do for npc in door.destination.cell:iterateReferences(tes3.objectType.npc) do
if not isIgnoredNPC(npc) then if not isIgnoredNPC(npc) then
@ -446,7 +488,7 @@ local function isIgnoredCell(cell)
return config.ignored[cell.id] or config.ignored[cell.sourceMod] -- or wilderness return config.ignored[cell.id] or config.ignored[cell.sourceMod] -- or wilderness
end end
local function checkInteriorCell(cell) local function isInteriorCell(cell)
if not cell then return end if not cell then return end
log(common.logLevels.large, "Cell: interior: %s, behaves as exterior: %s therefore returning %s", cell.isInterior, log(common.logLevels.large, "Cell: interior: %s, behaves as exterior: %s therefore returning %s", cell.isInterior,
@ -455,7 +497,7 @@ local function checkInteriorCell(cell)
return cell.isInterior and not cell.behavesAsExterior return cell.isInterior and not cell.behavesAsExterior
end end
local function checkCantonCell(cellName) local function isCantonCell(cellName)
for _, str in pairs(waistworks) do if cellName:match(str) then return true end end for _, str in pairs(waistworks) do if cellName:match(str) then return true end end
return false return false
end end
@ -478,7 +520,7 @@ local function checkWeather(cell)
end end
-- travel agents, their steeds, and argonians stick around -- travel agents, their steeds, and argonians stick around
local function badWeatherNPC(npc) local function isBadWeatherNPC(npc)
if not npc.object then return end if not npc.object then return end
log(common.logLevels.large, "NPC Inclement Weather: %s is %s, %s", npc.object.name, npc.object.class.name, log(common.logLevels.large, "NPC Inclement Weather: %s is %s, %s", npc.object.name, npc.object.class.name,
@ -520,79 +562,126 @@ end
-- }}} -- }}}
-- {{{ real meat and potatoes functions -- {{{ real meat and potatoes functions
local function moveNPC(data) local function moveNPC(homeData)
-- movedNPCs[#movedNPCs + 1] = data -- add to in memory table
-- table.insert(movedNPCs, data) table.insert(movedNPCs, homeData)
-- interop.setMovedNPCsTable(movedNPCs) interop.setMovedNPCTable(movedNPCs)
table.insert(tes3.player.data.NPCsGoHome.movedNPCs, data)
interop.setMovedNPCsTable(tes3.player.data.NPCsGoHome.movedNPCs) -- set npc data, so we can move NPCs back after a load
local npc = homeData.npc
npc.data.NPCsGoHome = {
position = {
x = npc.position.x,
y = npc.position.y,
z = npc.position.z,
},
orientation = {
x = npc.orientation.x,
y = npc.orientation.y,
z = npc.orientation.z,
},
cell = homeData.ogPlaceName
}
tes3.positionCell({ tes3.positionCell({
cell = data.home, cell = homeData.home,
reference = data.npc, reference = homeData.npc,
position = data.homePosition, position = homeData.homePosition,
orientation = data.homeOrientation orientation = homeData.homeOrientation
}) })
log(common.logLevels.small, "Moving %s to home %s (%s, %s, %s)", data.npc.object.name, data.home.id, log(common.logLevels.medium, "Moving %s to home %s (%s, %s, %s)", homeData.npc.object.name, homeData.home.id,
data.homePosition.x, data.homePosition.y, data.homePosition.z) homeData.homePosition.x, homeData.homePosition.y, homeData.homePosition.z)
end end
local function putNPCsBack() local function putNPCsBack()
-- for i = #movedNPCs, 1, -1 do for i = #movedNPCs, 1, -1 do
for i = #tes3.player.data.NPCsGoHome.movedNPCs, 1, -1 do local data = table.remove(movedNPCs, i)
-- local data = table.remove(movedNPCs, i)
local data = table.remove(tes3.player.data.NPCsGoHome.movedNPCs, i)
log(common.logLevels.medium, "Moving %s back outside to %s (%s, %s, %s)", data.npc.object.name, data.ogPlace.id, log(common.logLevels.medium, "Moving %s back outside to %s (%s, %s, %s)", data.npc.object.name, data.ogPlace.id,
data.ogPosition.x, data.ogPosition.y, data.ogPosition.z) data.ogPosition.x, data.ogPosition.y, data.ogPosition.z)
-- unset NPC data so we don't try to move them on load
data.npc.data.NPCsGoHome = nil
-- and put them back
tes3.positionCell({ tes3.positionCell({
cell = data.ogPlace, cell = data.ogPlace,
reference = data.npc, reference = data.npc,
position = data.ogPosition, position = data.ogPosition,
orientation = data.ogPlace orientation = data.ogPlace
}) })
-- interop.setMovedNPCsTable(movedNPCs) end
interop.setMovedNPCsTable(tes3.player.data.NPCsGoHome.movedNPCs) interop.setMovedNPCTable(movedNPCs)
end
-- search in a specific cell for moved NPCs
local function checkForMovedNPCs(cell)
log(common.logLevels.medium, "Looking for moved NPCs in cell %s", cell.id)
for npc in cell:iterateReferences(tes3.objectType.npc) do
if npc.data and npc.data.NPCsGoHome then
createHomedNPCTableEntry(npc, cell, tes3.getCell(npc.data.NPCsGoHome.cell), true, npc.data.NPCsGoHome.position, npc.data.NPCsGoHome.orientation)
end
end end
end end
-- todo: rename to toggleNPCs(cell, state = true|false) local function searchCellsForNPCs()
-- todo: using tes3.setEnabled({ enabled = state }) for _, cell in pairs(tes3.getActiveCells()) do
local function disableNPCs(cell) -- check active cells
checkForMovedNPCs(cell)
for door in cell:iterateReferences(tes3.objectType.door) do
if door.destination then
-- then check cells attached to active cells
checkForMovedNPCs(door.destination.cell)
end
end
end
end
local function processNPCs(cell)
-- todo: move this check somewhere else, so that disabled NPCs will be re-enabled even if the option is off
if not config.disableNPCs then return end if not config.disableNPCs then return end
log(common.logLevels.small, "Applying changes to NPCs in %s", cell.id)
-- iterate NPCs in the cell, move them to their homes, and keep track of moved NPCs so we can move them back later -- iterate NPCs in the cell, move them to their homes, and keep track of moved NPCs so we can move them back later
for npc in cell:iterateReferences(tes3.objectType.npc) do for npc in cell:iterateReferences(tes3.objectType.npc) do
-- for npc, _ in pairs(cellsInMemory[cell].npcs) do -- for npc, _ in pairs(cellsInMemory[cell].npcs) do
if not isIgnoredNPC(npc) then if not isIgnoredNPC(npc) then
log(common.logLevels.large, "People change") log(common.logLevels.large, "People change")
-- if not npc.data.NPCsGoHome then npc.data.NPCsGoHome = {} end
-- find NPC homes -- find NPC homes
local npcHome = config.moveNPCs and pickHomeForNPC(cell, npc) or nil local npcHome = config.moveNPCs and pickHomeForNPC(cell, npc) or nil
local tmpLogLevelNPCHome = npcHome and common.logLevels.small or common.logLevels.medium local tmpLogLevelNPCHome = npcHome and common.logLevels.medium or common.logLevels.large
log(tmpLogLevelNPCHome, "%s %s %s%s", npc.object.name, log(tmpLogLevelNPCHome, "%s %s %s%s", npc.object.name,
npcHome and (npcHome.isHome and "lives in" or "goes to") or "lives", npcHome and (npcHome.isHome and "lives in" or "goes to") or "lives",
npcHome and npcHome.home or "nowhere", npcHome and (npcHome.isHome and "." or " at night.")) npcHome and npcHome.home or "nowhere", npcHome and (npcHome.isHome and "." or " at night.") or ".")
-- disable or move NPCs -- disable or move NPCs
if (checkTime() or if (checkTime() or
(checkWeather(cell) and (checkWeather(cell) and
(not badWeatherNPC(npc) or (badWeatherNPC(npc) and not config.keepBadWeatherNPCs)))) then (not isBadWeatherNPC(npc) or (isBadWeatherNPC(npc) and not config.keepBadWeatherNPCs)))) then
if npcHome then if npcHome then
moveNPC(npcHome) moveNPC(npcHome)
else -- elseif not npc.data.NPCsGoHome.modified then
elseif not npc.disabled then
log(common.logLevels.medium, "Disabling homeless %s", npc.object.name) log(common.logLevels.medium, "Disabling homeless %s", npc.object.name)
-- npc:disable() -- ! this one sometimes causes crashes -- npc:disable() -- ! this one sometimes causes crashes
mwscript.disable({reference = npc}) -- ! this one is deprecated mwscript.disable({reference = npc}) -- ! this one is deprecated
-- tes3.setEnabled({reference = npc, enabled = false}) -- ! but this one causes crashes too -- tes3.setEnabled({reference = npc, enabled = false}) -- ! but this one causes crashes too
-- npc.data.NPCsGoHome.modified = true
else
log(common.logLevels.medium, "Didn't do anything with %s", npc.object.name)
end end
else else
if not npcHome then -- if not npcHome and npc.data.modified then
if not npcHome and npc.disabled then
log(common.logLevels.medium, "Enabling homeless %s", npc.object.name) log(common.logLevels.medium, "Enabling homeless %s", npc.object.name)
-- npc:enable() -- npc:enable()
mwscript.enable({reference = npc}) mwscript.enable({reference = npc})
-- tes3.setEnabled({reference = npc, enabled = true}) -- tes3.setEnabled({reference = npc, enabled = true})
-- npc.data.NPCsGoHome.modified = false
end end
end end
end end
@ -600,22 +689,26 @@ local function disableNPCs(cell)
-- now put NPCs back -- now put NPCs back
-- if not (checkTime() or checkWeather(cell)) and #movedNPCs > 0 then putNPCsBack() end -- if not (checkTime() or checkWeather(cell)) and #movedNPCs > 0 then putNPCsBack() end
if not (checkTime() or checkWeather(cell)) and #tes3.player.data.NPCsGoHome.movedNPCs > 0 then putNPCsBack() end if not (checkTime() or checkWeather(cell)) then putNPCsBack() end
end end
local function disableSiltStriders(cell) -- todo: deal with trader's guars, and other npc linked creatures/whatever
local function processSiltStriders(cell)
if not config.disableNPCs then return end if not config.disableNPCs then return end
log(common.logLevels.large, "Looking for silt striders") log(common.logLevels.small, "Applying changes to silt striders in %s", cell.name)
for activator in cell:iterateReferences(tes3.objectType.activator) do for activator in cell:iterateReferences(tes3.objectType.activator) do
log(common.logLevels.large, "Is %s a silt strider??", activator.object.id) log(common.logLevels.large, "Is %s a silt strider??", activator.object.id)
if activator.object.id:match("siltstrider") then if activator.object.id:match("siltstrider") then
if checkTime() or (checkWeather(cell) and not config.keepBadWeatherNPCs) then if checkTime() or (checkWeather(cell) and not config.keepBadWeatherNPCs) then
if not activator.disabled then
log(common.logLevels.medium, "Disabling silt strider %s!", activator.object.name) log(common.logLevels.medium, "Disabling silt strider %s!", activator.object.name)
mwscript.disable({reference = activator}) mwscript.disable({reference = activator})
-- activator:disable() -- activator:disable()
-- tes3.setEnabled({reference = activator, enabled = false}) -- tes3.setEnabled({reference = activator, enabled = false})
end
else else
if activator.disabled then
log(common.logLevels.medium, "Enabling silt strider %s!", activator.object.name) log(common.logLevels.medium, "Enabling silt strider %s!", activator.object.name)
mwscript.enable({reference = activator}) mwscript.enable({reference = activator})
-- activator:enable() -- activator:enable()
@ -623,48 +716,48 @@ local function disableSiltStriders(cell)
end end
end end
end end
end
log(common.logLevels.large, "Done with silt striders") log(common.logLevels.large, "Done with silt striders")
end end
local function processDoors(cell) local function processDoors(cell)
if not config.lockDoors then return end if not config.lockDoors then return end
log(common.logLevels.large, "Checking out doors") log(common.logLevels.small, "Applying changes to doors in %s", cell.id)
for door in cell:iterateReferences(tes3.objectType.door) do for door in cell:iterateReferences(tes3.objectType.door) do
if not door.data.NPCsGoHome then door.data.NPCsGoHome = {} end if not door.data.NPCsGoHome then door.data.NPCsGoHome = {} end
log(common.logLevels.large, "Door has destination: %s", door.destination and door.destination.cell.id or "none")
if not isIgnoredDoor(door, cell.id) then if not isIgnoredDoor(door, cell.id) then
log(common.logLevels.large, "It knows there's a door") -- don't mess around with doors that are already locked
if door.data.NPCsGoHome.alreadyLocked == nil then
door.data.NPCsGoHome.alreadyLocked = tes3.getLocked({reference = door})
end
local alreadyLocked = tes3.getLocked({reference = door}) log(common.logLevels.large, "Found %slocked %s with destination %s",
door.data.NPCsGoHome.alreadyLocked = alreadyLocked door.data.NPCsGoHome.alreadyLocked and "" or "un", door.id, door.destination.cell.id)
log(common.logLevels.large, "Locked Status: %s", alreadyLocked)
if checkTime() then if checkTime() then
if not door.data.NPCsGoHome.alreadyLocked then if not door.data.NPCsGoHome.alreadyLocked then
log(common.logLevels.large, "It should lock now") log(common.logLevels.medium, "locking: %s to %s", door.object.name, door.destination.cell.id)
log(common.logLevels.large, "What door is this anyway: %s to %s", door.object.name,
door.destination.cell.id)
local lockLevel = math.random(25, 100) local lockLevel = math.random(25, 100)
tes3.lock({reference = door, level = lockLevel}) tes3.lock({reference = door, level = lockLevel})
door.data.NPCsGoHome.modified = true door.data.NPCsGoHome.modified = true
end end
else else
-- only unlock doors that we locked before
if door.data.NPCsGoHome and door.data.NPCsGoHome.modified then if door.data.NPCsGoHome and door.data.NPCsGoHome.modified then
door.data.NPCsGoHome.modified = false door.data.NPCsGoHome.modified = false
tes3.setLockLevel({reference = door, level = 0}) tes3.setLockLevel({reference = door, level = 0})
tes3.unlock({reference = door}) tes3.unlock({reference = door})
log(common.logLevels.large, "It should unlock now") log(common.logLevels.medium, "unlocking: %s to %s", door.object.name, door.destination.cell.id)
log(common.logLevels.large, "What unlocked door is this anyway: %s to %s", door.object.name,
door.destination.cell.id)
end end
end end
log(common.logLevels.large, "Now Locked Status: %s", tes3.getLocked({reference = door})) log(common.logLevels.large, "Now locked Status: %s", tes3.getLocked({reference = door}))
end end
end end
log(common.logLevels.large, "Done with doors") log(common.logLevels.large, "Done with doors")
@ -691,11 +784,11 @@ local function applyChanges(cell)
if isIgnoredCell(cell) then return end if isIgnoredCell(cell) then return end
-- Interior cell, except Waistworks, don't do anything -- Interior cell, except Waistworks, don't do anything
if checkInteriorCell(cell) and not (config.waistWorks and checkCantonCell(cell.name)) then return end if isInteriorCell(cell) and not (config.waistWorks and isCantonCell(cell.name)) then return end
-- Disable NPCs in cell -- Deal with NPCs and mounts in cell
disableNPCs(cell) processNPCs(cell)
disableSiltStriders(cell) processSiltStriders(cell)
-- check doors in cell, locking those that aren't inns/clubs -- check doors in cell, locking those that aren't inns/clubs
processDoors(cell) processDoors(cell)
@ -710,10 +803,12 @@ local function updateCells()
end end
end end
local function updatePlayerTrespass(cell) local function updatePlayerTrespass(cell, previousCell)
cell = cell or tes3.getPlayerCell() cell = cell or tes3.getPlayerCell()
if checkInteriorCell(cell) and not isIgnoredCell(cell) and not isPublicHouse(cell) then local inCity = previousCell and (previousCell.id:match(cell.id) or cell.id:match(previousCell.id))
if isInteriorCell(cell) and not isIgnoredCell(cell) and not isPublicHouse(cell) and inCity then
if checkTime() then if checkTime() then
tes3.player.data.NPCsGoHome.intruding = true tes3.player.data.NPCsGoHome.intruding = true
else else
@ -729,9 +824,7 @@ end
-- {{{ event functions -- {{{ event functions
local function onActivated(e) local function onActivated(e)
if e.activator ~= tes3.player or if e.activator ~= tes3.player or e.target.object.objectType ~= tes3.objectType.npc or not config.disableInteraction then
e.target.object.objectType ~= tes3.objectType.npc or
not config.disableInteraction then
return return
end end
@ -742,9 +835,10 @@ local function onActivated(e)
end end
local function onLoaded() local function onLoaded()
if not tes3.player.data.NPCsGoHome then tes3.player.data.NPCsGoHome = {} end tes3.player.data.NPCsGoHome = tes3.player.data.NPCsGoHome or {}
if not tes3.player.data.NPCsGoHome.movedNPCs then tes3.player.data.NPCsGoHome.movedNPCs = {} end -- tes3.player.data.NPCsGoHome.movedNPCs = tes3.player.data.NPCsGoHome.movedNPCs or {}
-- movedNPCs = {} -- movedNPCs = tes3.player.data.NPCsGoHome.movedNPCs or {}
if tes3.player.cell then searchCellsForNPCs() end
if not updateTimer or (updateTimer and updateTimer.state ~= timer.active) then if not updateTimer or (updateTimer and updateTimer.state ~= timer.active) then
updateTimer = timer.start({ updateTimer = timer.start({