big heckin refactor/cleanup
This commit is contained in:
parent
9239aded68
commit
c26400b55b
|
@ -2,6 +2,7 @@ local inspect = require("inspect")
|
||||||
|
|
||||||
local this = {}
|
local this = {}
|
||||||
|
|
||||||
|
-- {{{ Variables and such
|
||||||
this.modName = "NPCs Go Home (At Night)"
|
this.modName = "NPCs Go Home (At Night)"
|
||||||
this.author = "OEA/Celediel"
|
this.author = "OEA/Celediel"
|
||||||
this.version = "0.0.1"
|
this.version = "0.0.1"
|
||||||
|
@ -10,7 +11,25 @@ this.modInfo = "Move NPCs to their homes, or public houses (or just disable them
|
||||||
this.configPath = "NPCSGOHOME"
|
this.configPath = "NPCSGOHOME"
|
||||||
|
|
||||||
this.logLevels = {none = 0, small = 1, medium = 2, large = 3}
|
this.logLevels = {none = 0, small = 1, medium = 2, large = 3}
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- {{{ Filled at runtime
|
||||||
|
this.runtimeData = {
|
||||||
|
-- cells marked as public
|
||||||
|
publicHouses = {},
|
||||||
|
-- homes picked for NPCs
|
||||||
|
homes = {
|
||||||
|
byName = {},
|
||||||
|
byCell = {}
|
||||||
|
},
|
||||||
|
-- NPCs who have been moved
|
||||||
|
movedNPCs = {},
|
||||||
|
-- player companions
|
||||||
|
followers = {}
|
||||||
|
}
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- {{{ Functions
|
||||||
this.split = function(input, sep)
|
this.split = function(input, sep)
|
||||||
if not input then return end
|
if not input then return end
|
||||||
if not sep then sep = "%s" end
|
if not sep then sep = "%s" end
|
||||||
|
@ -34,5 +53,6 @@ this.vowel = function(str)
|
||||||
|
|
||||||
return n
|
return n
|
||||||
end
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
|
319
MWSE/mods/celediel/NPCsGoHome/functions/checks.lua
Normal file
319
MWSE/mods/celediel/NPCsGoHome/functions/checks.lua
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
local common = require("celediel.NPCsGoHome.common")
|
||||||
|
local config = require("celediel.NPCsGoHome.config").getConfig()
|
||||||
|
local housing = require("celediel.NPCsGoHome.functions.housing")
|
||||||
|
|
||||||
|
-- {{{ local variables and such
|
||||||
|
-- Waistworks string match
|
||||||
|
local waistworks = {
|
||||||
|
"[Cc]analworks", -- These will match Vivec and Molag Mar
|
||||||
|
"[Ww]aistworks" -- and Almas Thirr from Tamriel Rebuilt
|
||||||
|
}
|
||||||
|
-- these are separate because doors to underworks should be ignored
|
||||||
|
-- but NPCs in underworks should not be disabled
|
||||||
|
local underworks = "[Uu]nderworks"
|
||||||
|
|
||||||
|
-- city name if cell.name is nil
|
||||||
|
local wilderness = "Wilderness"
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- {{{ local functions
|
||||||
|
local function log(level, ...) if config.logLevel >= level then common.log(...) end end
|
||||||
|
|
||||||
|
-- patented by Merlord
|
||||||
|
local yeet = function(reference)
|
||||||
|
-- tes3.positionCell({reference = reference, position = {0, 0, 10000}})
|
||||||
|
reference:disable()
|
||||||
|
timer.delayOneFrame(function() mwscript.setDelete({reference = reference}) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- very todd workaround
|
||||||
|
local function getFightFromSpawnedReference(id)
|
||||||
|
-- Spawn a reference of the given id in toddtest
|
||||||
|
local toddTest = tes3.getCell("toddtest")
|
||||||
|
|
||||||
|
log(common.logLevels.medium, "Spawning %s in %s", id, toddTest.id)
|
||||||
|
|
||||||
|
local ref = tes3.createReference({
|
||||||
|
object = id,
|
||||||
|
-- cell = toddTest,
|
||||||
|
cell = tes3.getPlayerCell(),
|
||||||
|
-- position = zeroVector,
|
||||||
|
position = {0, 0, 10000},
|
||||||
|
orientation = housing.zeroVector
|
||||||
|
})
|
||||||
|
|
||||||
|
local fight = ref.mobile.fight
|
||||||
|
|
||||||
|
log(common.logLevels.medium, "Got fight of %s, time to yeet %s", fight, id)
|
||||||
|
|
||||||
|
yeet(ref)
|
||||||
|
|
||||||
|
return fight
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
local this = {}
|
||||||
|
|
||||||
|
this.isInteriorCell = function(cell)
|
||||||
|
if not cell then return end
|
||||||
|
|
||||||
|
log(common.logLevels.large, "Cell %s: interior: %s, behaves as exterior: %s therefore returning %s",
|
||||||
|
cell.id, cell.isInterior, cell.behavesAsExterior, cell.isInterior and not cell.behavesAsExterior)
|
||||||
|
|
||||||
|
return cell.isInterior and not cell.behavesAsExterior
|
||||||
|
end
|
||||||
|
|
||||||
|
this.isCityCell = function(internalCellId, externalCellId)
|
||||||
|
-- easy mode
|
||||||
|
if string.match(internalCellId, externalCellId) then
|
||||||
|
log(common.logLevels.large, "easy mode city: %s in %s", internalCellId, externalCellId)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local cityMatch = "^(%w+), (.*)"
|
||||||
|
-- check for "advanced" cities
|
||||||
|
local _, _, internalCity = string.find(internalCellId, cityMatch)
|
||||||
|
local _, _, externalCity = string.find(externalCellId, cityMatch)
|
||||||
|
|
||||||
|
if externalCity and externalCity == internalCity then
|
||||||
|
log(common.logLevels.large, "hard mode city: %s in %s, %s == %s",
|
||||||
|
internalCellId, externalCellId, externalCity, internalCity)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
log(common.logLevels.large, "hard mode not city: %s not in %s, %s ~= %s or both are nil",
|
||||||
|
internalCellId, externalCellId, externalCity, internalCity)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
this.isIgnoredCell = function(cell)
|
||||||
|
log(common.logLevels.large, "%s is %s, %s is %s", cell.id, config.ignored[cell.id] and "ignored" or "not ignored",
|
||||||
|
cell.sourceMod, config.ignored[cell.sourceMod] and "ignored" or "not ignored")
|
||||||
|
|
||||||
|
-- don't do things in the wilderness
|
||||||
|
-- local wilderness = false
|
||||||
|
-- if not cell.name then wilderness = true end
|
||||||
|
|
||||||
|
return config.ignored[cell.id] or config.ignored[cell.sourceMod] -- or wilderness
|
||||||
|
end
|
||||||
|
|
||||||
|
this.isCantonCell = function(cellName)
|
||||||
|
for _, str in pairs(waistworks) do if cellName:match(str) then return true end end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ! this one depends on tes3 ! --
|
||||||
|
this.fargothCheck = function()
|
||||||
|
local fargothJournal = tes3.getJournalIndex({id = "MS_Lookout"})
|
||||||
|
if not fargothJournal then return false end
|
||||||
|
|
||||||
|
-- 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)
|
||||||
|
|
||||||
|
return fargothJournal > 10 and fargothJournal <= 30
|
||||||
|
end
|
||||||
|
|
||||||
|
this.isIgnoredNPC = function(npc)
|
||||||
|
local obj = npc.baseObject and npc.baseObject or npc.object
|
||||||
|
|
||||||
|
-- ignore dead, attack on sight NPCs, and vampires
|
||||||
|
local isDead = false
|
||||||
|
local isHostile = false
|
||||||
|
local isVampire = false
|
||||||
|
|
||||||
|
if npc.mobile then
|
||||||
|
if npc.mobile.health.current <= 0 or npc.mobile.isDead then isDead = true end
|
||||||
|
if npc.mobile.fight > 70 then isHostile = true end
|
||||||
|
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 and (obj.head.vampiric and true or false) or false
|
||||||
|
if obj.id:match("[Dd]ead") or obj.name:match("[Dd]ead") then isDead = true end
|
||||||
|
end
|
||||||
|
|
||||||
|
local isFargothActive = obj.id:match("fargoth") and this.fargothCheck() or false
|
||||||
|
|
||||||
|
-- todo: non mwscript version of this
|
||||||
|
local isWerewolf = mwscript.getSpellEffects({reference = npc, spell = "werewolf vision"})
|
||||||
|
-- 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
|
||||||
|
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"), --
|
||||||
|
obj.name, npc.object.id, npc.object.baseObject and npc.object.baseObject.id or "nil", --
|
||||||
|
config.ignored[obj.id:lower()], obj.sourceMod, config.ignored[obj.sourceMod:lower()], --
|
||||||
|
obj.isGuard, isDead, isVampire, isWerewolf, (obj.class and obj.class.id == "Dreamers"), --
|
||||||
|
common.runtimeData.followers[npc.object.id], isHostile, obj.id:match("fargoth") and "fargoth:" or "", --
|
||||||
|
obj.id:match("fargoth") and isFargothActive or "")
|
||||||
|
|
||||||
|
return config.ignored[obj.id:lower()] or --
|
||||||
|
config.ignored[obj.sourceMod:lower()] or --
|
||||||
|
obj.isGuard or --
|
||||||
|
isFargothActive or --
|
||||||
|
isDead or -- don't move dead NPCS
|
||||||
|
isHostile or --
|
||||||
|
common.runtimeData.followers[npc.object.id] or -- ignore followers
|
||||||
|
isVampire or --
|
||||||
|
isWerewolf or --
|
||||||
|
(obj.class and obj.class.id == "Dreamers") --
|
||||||
|
end
|
||||||
|
|
||||||
|
this.isNPCPet = function(creature)
|
||||||
|
local obj = creature.baseObject and creature.baseObject or creature.object
|
||||||
|
|
||||||
|
-- todo: more pets?
|
||||||
|
if obj.id:match("guar") and obj.mesh:match("pack") then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- checks NPC class and faction in cells for block list and adds to publicHouse list
|
||||||
|
-- todo: rewrite this
|
||||||
|
this.isPublicHouse = function(cell)
|
||||||
|
-- only interior cells are public "houses"
|
||||||
|
if not this.isInteriorCell(cell) then return false end
|
||||||
|
|
||||||
|
local typeOfPub = housing.pickPublicHouseType(cell)
|
||||||
|
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 common.runtimeData.publicHouses[city] and (common.runtimeData.publicHouses[city][typeOfPub] and common.runtimeData.publicHouses[city][typeOfPub][cell.id]) then return true end
|
||||||
|
|
||||||
|
local npcs = {factions = {}, total = 0}
|
||||||
|
for npc in cell:iterateReferences(tes3.objectType.npc) do
|
||||||
|
-- Check for NPCS of ignored classes first
|
||||||
|
if not this.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,
|
||||||
|
npc.object.class and npc.object.class.id or "none", cell.name)
|
||||||
|
|
||||||
|
housing.createPublicHouseTableEntry(cell, npc, city, publicHouseName)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local faction = npc.object.faction
|
||||||
|
|
||||||
|
if faction then
|
||||||
|
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 npcs.factions[faction].master = npc end
|
||||||
|
|
||||||
|
npcs.factions[faction].total = npcs.factions[faction].total + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
npcs.total = npcs.total + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- no NPCs of ignored classes, so let's check out factions
|
||||||
|
for faction, info in pairs(npcs.factions) do
|
||||||
|
info.percentage = (info.total / npcs.total) * 100
|
||||||
|
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, 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
|
||||||
|
if (config.ignored[faction.id] or faction.playerJoined) and
|
||||||
|
(npcs.total >= config.minimumOccupancy or faction == "Blades") and info.percentage >=
|
||||||
|
config.factionIgnorePercentage then
|
||||||
|
log(common.logLevels.medium, "%s is %s%% faction %s, marking public.", cell.name, info.percentage, faction)
|
||||||
|
|
||||||
|
housing.createPublicHouseTableEntry(cell, npcs.factions[faction].master, city, publicHouseName)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log(common.logLevels.large, "%s isn't public", cell.name)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- doors that lead to ignored, exterior, canton, unoccupied, or public cells, and doors that aren't in cities
|
||||||
|
this.isIgnoredDoor = function(door, homeCellId)
|
||||||
|
-- don't lock non-cell change doors
|
||||||
|
if not door.destination then
|
||||||
|
log(common.logLevels.large, "Non-Cell-change door %s, ignoring", door.id)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we use this a lot, so set a reference to it
|
||||||
|
local dest = door.destination.cell
|
||||||
|
|
||||||
|
-- Only doors in cities and towns (interior cells with names that contain the exterior cell)
|
||||||
|
local inCity = this.isCityCell(dest.id, homeCellId)
|
||||||
|
|
||||||
|
-- peek inside doors to look for guild halls, inns and clubs
|
||||||
|
local leadsToPublicCell = this.isPublicHouse(dest)
|
||||||
|
|
||||||
|
-- don't lock unoccupied cells
|
||||||
|
local hasOccupants = false
|
||||||
|
for npc in dest:iterateReferences(tes3.objectType.npc) do
|
||||||
|
if not this.isIgnoredNPC(npc) then
|
||||||
|
hasOccupants = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- don't lock doors to underworks in addition to other canton cells
|
||||||
|
local isCanton = this.isCantonCell(dest.id) or dest.id:match(underworks)
|
||||||
|
|
||||||
|
log(common.logLevels.large, "%s is %s, (%sin a city, is %spublic, %soccupied)", --
|
||||||
|
dest.id, this.isIgnoredCell(dest) and "ignored" or "not ignored", -- destination is ignored
|
||||||
|
inCity and "" or "not ", leadsToPublicCell and "" or "not ", hasOccupants and "" or "un") -- in a city, is public, is ocupado
|
||||||
|
|
||||||
|
return this.isIgnoredCell(dest) or
|
||||||
|
not this.isInteriorCell(dest) or
|
||||||
|
isCanton or
|
||||||
|
not inCity or
|
||||||
|
leadsToPublicCell or
|
||||||
|
not hasOccupants
|
||||||
|
end
|
||||||
|
|
||||||
|
-- AT NIGHT
|
||||||
|
this.checkTime = function()
|
||||||
|
log(common.logLevels.large, "Current time is %s, things are closed between %s and %s",
|
||||||
|
tes3.worldController.hour.value, config.closeTime, config.openTime)
|
||||||
|
return tes3.worldController.hour.value >= config.closeTime or tes3.worldController.hour.value <= config.openTime
|
||||||
|
end
|
||||||
|
|
||||||
|
-- inclement weather
|
||||||
|
this.checkWeather = function(cell)
|
||||||
|
if not cell.region then return end
|
||||||
|
|
||||||
|
log(common.logLevels.large, "Weather: %s >= %s == %s", cell.region.weather.index, config.worstWeather,
|
||||||
|
cell.region.weather.index >= config.worstWeather)
|
||||||
|
|
||||||
|
return cell.region.weather.index >= config.worstWeather
|
||||||
|
end
|
||||||
|
|
||||||
|
-- travel agents, their steeds, and argonians stick around
|
||||||
|
this.isBadWeatherNPC = function(npc)
|
||||||
|
local obj = npc.baseObject and npc.baseObject or npc.object
|
||||||
|
if not obj then return end
|
||||||
|
|
||||||
|
log(common.logLevels.large, "NPC Inclement Weather: %s is %s, %s", npc.object.name, npc.object.class.name,
|
||||||
|
npc.object.race.id)
|
||||||
|
|
||||||
|
-- todo: better detection of NPCs who offer travel services
|
||||||
|
-- found a rogue "shipmaster" in molag mar
|
||||||
|
return obj.class.name == "Caravaner" or
|
||||||
|
obj.class.name == "Gondolier" or
|
||||||
|
obj.class.name == "Shipmaster" or
|
||||||
|
obj.race.id == "Argonian"
|
||||||
|
end
|
||||||
|
|
||||||
|
return this
|
265
MWSE/mods/celediel/NPCsGoHome/functions/housing.lua
Normal file
265
MWSE/mods/celediel/NPCsGoHome/functions/housing.lua
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
local common = require("celediel.NPCsGoHome.common")
|
||||||
|
local config = require("celediel.NPCsGoHome.config").getConfig()
|
||||||
|
local interop = require("celediel.NPCsGoHome.interop")
|
||||||
|
local positions = require("celediel.NPCsGoHome.data.positions")
|
||||||
|
|
||||||
|
local function log(level, ...) if config.logLevel >= level then common.log(...) end end
|
||||||
|
|
||||||
|
local publicHouseTypes = {inns = "Inns", guildhalls = "Guildhalls", temples = "Temples", houses = "Houses"}
|
||||||
|
|
||||||
|
-- animated morrowind NPCs are contextual
|
||||||
|
local contextualNPCs = {"^AM_"}
|
||||||
|
|
||||||
|
local this = {}
|
||||||
|
|
||||||
|
this.zeroVector = tes3vector3.new(0, 0, 0)
|
||||||
|
|
||||||
|
this.checkModdedCell = function(cellId)
|
||||||
|
local id
|
||||||
|
|
||||||
|
if cellId == "Balmora, South Wall Cornerclub" and tes3.isModActive("South Wall.ESP") then
|
||||||
|
id = "Balmora, South Wall Den Of Iniquity"
|
||||||
|
elseif cellId == "Balmora, Eight Plates" and tes3.isModActive("Eight Plates.esp") then
|
||||||
|
id = "Balmora, Seedy Eight Plates"
|
||||||
|
elseif cellId == "Hla Oad, Fatleg's Drop Off" and tes3.isModActive("Clean DR115_TheDropoff_HlaOadDocks.ESP") then
|
||||||
|
id = "Hla Oad, The Drop Off"
|
||||||
|
else
|
||||||
|
id = cellId
|
||||||
|
end
|
||||||
|
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
-- {{{ npc evaluators
|
||||||
|
|
||||||
|
-- NPCs barter gold + value of all inventory items
|
||||||
|
this.calculateNPCWorth = function(npc, merchantCell)
|
||||||
|
local worth = npc.object.barterGold
|
||||||
|
local obj = npc.baseObject and npc.baseObject or npc.object
|
||||||
|
|
||||||
|
if npc.object.inventory then
|
||||||
|
for _, item in pairs(npc.object.inventory) do worth = worth + (item.object.value or 0) end
|
||||||
|
end
|
||||||
|
|
||||||
|
if merchantCell then -- if we pass a cell argument
|
||||||
|
for box in merchantCell:iterateReferences(tes3.objectType.container) do -- loop over each container
|
||||||
|
if box.inventory then -- if it's not empty
|
||||||
|
for item in tes3.iterate(box.inventory) do -- loop over its items
|
||||||
|
if obj:tradesItemType(item.objectType) then -- if the NPC sells that type
|
||||||
|
worth = worth + item.object.value -- add its value to the NPCs total value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return worth
|
||||||
|
end
|
||||||
|
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- todo: pick this better
|
||||||
|
this.pickPublicHouseType = function(cell)
|
||||||
|
if cell.id:match("Guild") then
|
||||||
|
return publicHouseTypes.guildhalls
|
||||||
|
elseif cell.id:match("Temple") then
|
||||||
|
return publicHouseTypes.temples
|
||||||
|
-- elseif cell.id:match("House") then
|
||||||
|
-- return publicHouseTypes.houses
|
||||||
|
else
|
||||||
|
return publicHouseTypes.inns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ? I honestly don't know if there are any wandering NPCs that "live" in close-by manors, but I wrote this anyway
|
||||||
|
this.livesInManor = function(cellName, npcName)
|
||||||
|
if not cellName or (cellName and not string.find(cellName, "Manor")) then return end
|
||||||
|
|
||||||
|
local splitName = common.split(npcName)
|
||||||
|
local given = splitName[1]
|
||||||
|
local sur = splitName[2]
|
||||||
|
|
||||||
|
-- surnameless peasants don't live in manors
|
||||||
|
if not sur then return end
|
||||||
|
|
||||||
|
log(common.logLevels.large, "Checking if %s %s lives in %s", given, sur, cellName)
|
||||||
|
return string.match(cellName, sur)
|
||||||
|
end
|
||||||
|
|
||||||
|
this.pickInnForNPC = function(npc, city)
|
||||||
|
-- todo: pick in Inn intelligently ?
|
||||||
|
-- high class inns for nobles and rich merchants and such
|
||||||
|
-- lower class inns for middle class npcs and merchants
|
||||||
|
-- temple for commoners and the poorest people
|
||||||
|
-- ? pick based on barterGold and value of equipment for merchants ?
|
||||||
|
-- ? for others, pick based on value of equipment
|
||||||
|
|
||||||
|
-- but for now pick one at random
|
||||||
|
if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][publicHouseTypes.inns] then
|
||||||
|
local choice = table.choice(common.runtimeData.publicHouses[city][publicHouseTypes.inns])
|
||||||
|
if not choice then return end
|
||||||
|
log(common.logLevels.medium, "Picking inn %s, %s for %s", choice.city, choice.name, npc.object.name)
|
||||||
|
return choice.cell
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
this.pickPublicHouseForNPC = function(npc, city)
|
||||||
|
-- look for wandering guild members
|
||||||
|
if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][publicHouseTypes.guildhalls] then
|
||||||
|
for _, data in pairs(common.runtimeData.publicHouses[city][publicHouseTypes.guildhalls]) do
|
||||||
|
-- if npc's faction and proprietor's faction match, pick that one
|
||||||
|
if npc.object.faction == data.proprietor.object.faction then
|
||||||
|
log(common.logLevels.medium, "Picking %s for %s based on faction", data.cell.id, npc.object.name)
|
||||||
|
return data.cell
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- temple members go to the temple
|
||||||
|
if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][publicHouseTypes.temples] then
|
||||||
|
for _, data in pairs(common.runtimeData.publicHouses[city][publicHouseTypes.temples]) do
|
||||||
|
if npc.object.faction == data.proprietor.object.faction then
|
||||||
|
log(common.logLevels.medium, "Picking temple %s for %s based on faction", data.cell.id, npc.object.name)
|
||||||
|
return data.cell
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- found nothing so pick an inn
|
||||||
|
return this.pickInnForNPC(npc, city)
|
||||||
|
end
|
||||||
|
|
||||||
|
this.createHomedNPCTableEntry = function(npc, home, startingPlace, isHome, position, orientation)
|
||||||
|
if npc.object and (npc.object.name == nil or npc.object.name == "") then return end
|
||||||
|
|
||||||
|
local pickedPosition, pickedOrientation, pos, ori
|
||||||
|
|
||||||
|
-- mod support for different positions in cells
|
||||||
|
local id = this.checkModdedCell(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, id)
|
||||||
|
|
||||||
|
if isHome and positions.npcs[npc.object.name] then
|
||||||
|
pos = positions.npcs[npc.object.name].position
|
||||||
|
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()
|
||||||
|
-- pickedOrientation = positions.npcs[npc.object.name] and tes3vector3.new(o[1], o[2], o[3]) or zeroVector:copy()
|
||||||
|
elseif positions.cells[id] then
|
||||||
|
pos = table.choice(positions.cells[id]).position
|
||||||
|
ori = table.choice(positions.cells[id]).orientation
|
||||||
|
-- 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()
|
||||||
|
-- pickedPosition = tes3vector3.new(p[1], p[2], p[3])
|
||||||
|
-- pickedOrientation = tes3vector3.new(o[1], o[2], o[3])
|
||||||
|
else
|
||||||
|
pos = {0,0,0}
|
||||||
|
ori = {0,0,0}
|
||||||
|
-- pickedPosition = zeroVector:copy()
|
||||||
|
-- pickedOrientation = zeroVector:copy()
|
||||||
|
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 = {
|
||||||
|
name = npc.object.name, -- string
|
||||||
|
npc = npc, -- tes3npc
|
||||||
|
isHome = isHome, -- bool
|
||||||
|
home = home, -- tes3cell
|
||||||
|
homeName = home.id, -- string
|
||||||
|
ogPlace = startingPlace, -- tes3cell
|
||||||
|
ogPlaceName = startingPlace.id,
|
||||||
|
ogPosition = ogPosition, -- tes3vector3
|
||||||
|
ogOrientation = ogOrientation, -- tes3vector3
|
||||||
|
homePosition = pickedPosition, -- tes3vector3
|
||||||
|
homeOrientation = pickedOrientation, -- tes3vector3
|
||||||
|
worth = this.calculateNPCWorth(npc) -- int
|
||||||
|
}
|
||||||
|
|
||||||
|
common.runtimeData.homes.byName[npc.object.name] = this
|
||||||
|
if isHome then common.runtimeData.homes.byCell[home.id] = this end
|
||||||
|
|
||||||
|
interop.setHomedNPCTable(common.runtimeData.homes.byName)
|
||||||
|
|
||||||
|
return this
|
||||||
|
end
|
||||||
|
|
||||||
|
this.createPublicHouseTableEntry = function(publicCell, proprietor, city, name)
|
||||||
|
local typeOfPub = this.pickPublicHouseType(publicCell)
|
||||||
|
|
||||||
|
local worth = 0
|
||||||
|
|
||||||
|
-- for houses, worth is equal to NPC who lives there
|
||||||
|
-- if typeOfPub == publicHouseTypes.houses then
|
||||||
|
-- worth = calculateNPCWorth(proprietor)
|
||||||
|
-- else
|
||||||
|
-- for other types, worth is combined worth of all NPCs
|
||||||
|
for innard in publicCell:iterateReferences(tes3.objectType.npc) do
|
||||||
|
if innard == proprietor then
|
||||||
|
worth = worth + this.calculateNPCWorth(innard, publicCell)
|
||||||
|
else
|
||||||
|
worth = worth + this.calculateNPCWorth(innard)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
if not common.runtimeData.publicHouses[city] then common.runtimeData.publicHouses[city] = {} end
|
||||||
|
if not common.runtimeData.publicHouses[city][typeOfPub] then common.runtimeData.publicHouses[city][typeOfPub] = {} end
|
||||||
|
|
||||||
|
common.runtimeData.publicHouses[city][typeOfPub][publicCell.id] = {
|
||||||
|
name = name,
|
||||||
|
city = city,
|
||||||
|
cell = publicCell,
|
||||||
|
proprietor = proprietor,
|
||||||
|
proprietorName = proprietor.object.name,
|
||||||
|
worth = worth
|
||||||
|
}
|
||||||
|
|
||||||
|
interop.setPublicHouseTable(common.runtimeData.publicHouses)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- looks through doors to find a cell that matches a wandering NPCs name
|
||||||
|
this.pickHomeForNPC = function(cell, npc)
|
||||||
|
-- wilderness cells don't have name
|
||||||
|
if not cell.name then return end
|
||||||
|
|
||||||
|
-- don't move contextual, such as Animated Morrowind, NPCs at all
|
||||||
|
for _, str in pairs(contextualNPCs) do if npc.object.id:match(str) then return end end
|
||||||
|
|
||||||
|
local name = npc.object.name
|
||||||
|
local city = common.split(cell.name, ",")[1]
|
||||||
|
for door in cell:iterateReferences(tes3.objectType.door) do
|
||||||
|
if door.destination then
|
||||||
|
local dest = door.destination.cell
|
||||||
|
|
||||||
|
-- essentially, if npc full name, or surname matches the cell name
|
||||||
|
if dest.id:match(name) or this.livesInManor(dest.name, name) then
|
||||||
|
if common.runtimeData.homes.byName[name] then -- already have a home, don't create the table entry again
|
||||||
|
return common.runtimeData.homes.byName[name]
|
||||||
|
else
|
||||||
|
return this.createHomedNPCTableEntry(npc, dest, cell, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- haven't found a home, so put them in an inn or guildhall
|
||||||
|
if config.homelessWanderersToPublicHouses then
|
||||||
|
log(common.logLevels.medium, "Didn't find a home for %s, trying inns", npc.object.name)
|
||||||
|
local dest = this.pickPublicHouseForNPC(npc, city)
|
||||||
|
-- return createHomedNPCTableEntry(npc, dest, door)
|
||||||
|
if dest then return this.createHomedNPCTableEntry(npc, dest, cell, false) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return this
|
239
MWSE/mods/celediel/NPCsGoHome/functions/processors.lua
Normal file
239
MWSE/mods/celediel/NPCsGoHome/functions/processors.lua
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
local common = require("celediel.NPCsGoHome.common")
|
||||||
|
local config = require("celediel.NPCsGoHome.config").getConfig()
|
||||||
|
local checks = require("celediel.NPCsGoHome.functions.checks")
|
||||||
|
local interop = require("celediel.NPCsGoHome.interop")
|
||||||
|
local housing = require("celediel.NPCsGoHome.functions.housing")
|
||||||
|
|
||||||
|
local function log(level, ...) if config.logLevel >= level then common.log(...) end end
|
||||||
|
|
||||||
|
local this = {}
|
||||||
|
|
||||||
|
-- search in a specific cell for moved NPCs
|
||||||
|
this.checkForMovedNPCs = function(cell)
|
||||||
|
-- NPCs don't get moved to exterior cells, so no need to check them for moved NPCs
|
||||||
|
if not checks.isInteriorCell(cell) then return end
|
||||||
|
|
||||||
|
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
|
||||||
|
housing.createHomedNPCTableEntry(npc, cell, tes3.getCell(npc.data.NPCsGoHome.cell), true, npc.data.NPCsGoHome.position, npc.data.NPCsGoHome.orientation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
this.searchCellsForNPCs = function()
|
||||||
|
for _, cell in pairs(tes3.getActiveCells()) do
|
||||||
|
-- check active cells
|
||||||
|
this.checkForMovedNPCs(cell)
|
||||||
|
for door in cell:iterateReferences(tes3.objectType.door) do
|
||||||
|
if door.destination then
|
||||||
|
-- then check cells attached to active cells
|
||||||
|
this.checkForMovedNPCs(door.destination.cell)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
this.moveNPC = function(homeData)
|
||||||
|
-- add to in memory table
|
||||||
|
table.insert(common.runtimeData.movedNPCs, homeData)
|
||||||
|
interop.setMovedNPCTable(common.runtimeData.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({
|
||||||
|
cell = homeData.home,
|
||||||
|
reference = homeData.npc,
|
||||||
|
position = homeData.homePosition,
|
||||||
|
orientation = homeData.homeOrientation
|
||||||
|
})
|
||||||
|
|
||||||
|
log(common.logLevels.medium, "Moving %s to home %s (%s, %s, %s)", homeData.npc.object.name, homeData.home.id,
|
||||||
|
homeData.homePosition.x, homeData.homePosition.y, homeData.homePosition.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
this.putNPCsBack = function()
|
||||||
|
for i = #common.runtimeData.movedNPCs, 1, -1 do
|
||||||
|
local data = table.remove(common.runtimeData.movedNPCs, i)
|
||||||
|
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)
|
||||||
|
|
||||||
|
-- unset NPC data so we don't try to move them on load
|
||||||
|
data.npc.data.NPCsGoHome = nil
|
||||||
|
|
||||||
|
-- and put them back
|
||||||
|
tes3.positionCell({
|
||||||
|
cell = data.ogPlace,
|
||||||
|
reference = data.npc,
|
||||||
|
position = data.ogPosition,
|
||||||
|
orientation = data.ogPlace
|
||||||
|
})
|
||||||
|
end
|
||||||
|
interop.setMovedNPCTable(common.runtimeData.movedNPCs)
|
||||||
|
end
|
||||||
|
|
||||||
|
this.processNPCs = function(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
|
||||||
|
|
||||||
|
log(common.logLevels.small, "Looking for NPCs to process in cell:%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
|
||||||
|
for npc in cell:iterateReferences(tes3.objectType.npc) do
|
||||||
|
-- for npc, _ in pairs(cellsInMemory[cell].npcs) do
|
||||||
|
if not checks.isIgnoredNPC(npc) then
|
||||||
|
log(common.logLevels.large, "People change")
|
||||||
|
-- if not npc.data.NPCsGoHome then npc.data.NPCsGoHome = {} end
|
||||||
|
|
||||||
|
-- find NPC homes
|
||||||
|
local npcHome = config.moveNPCs and housing.pickHomeForNPC(cell, npc) or nil
|
||||||
|
|
||||||
|
local tmpLogLevelNPCHome = npcHome and common.logLevels.medium or common.logLevels.large
|
||||||
|
log(tmpLogLevelNPCHome, "%s %s %s%s", npc.object.name,
|
||||||
|
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.") or ".")
|
||||||
|
|
||||||
|
-- disable or move NPCs
|
||||||
|
if (checks.checkTime() or
|
||||||
|
(checks.checkWeather(cell) and
|
||||||
|
(not checks.isBadWeatherNPC(npc) or (checks.isBadWeatherNPC(npc) and not config.keepBadWeatherNPCs)))) then
|
||||||
|
if npcHome then
|
||||||
|
this.moveNPC(npcHome)
|
||||||
|
-- elseif not npc.data.NPCsGoHome.modified then
|
||||||
|
elseif not npc.disabled then
|
||||||
|
log(common.logLevels.medium, "Disabling homeless %s", npc.object.name)
|
||||||
|
-- npc:disable() -- ! this one sometimes causes crashes
|
||||||
|
mwscript.disable({reference = npc}) -- ! this one is deprecated
|
||||||
|
-- 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
|
||||||
|
else
|
||||||
|
-- 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)
|
||||||
|
-- npc:enable()
|
||||||
|
mwscript.enable({reference = npc})
|
||||||
|
-- tes3.setEnabled({reference = npc, enabled = true})
|
||||||
|
-- npc.data.NPCsGoHome.modified = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now put NPCs back
|
||||||
|
-- if not (checks.checkTime() or checks.checkWeather(cell)) and #movedNPCs > 0 then putNPCsBack() end
|
||||||
|
if not (checks.checkTime() or checks.checkWeather(cell)) then this.putNPCsBack() end
|
||||||
|
end
|
||||||
|
|
||||||
|
this.processSiltStriders = function(cell)
|
||||||
|
if not config.disableNPCs then return end
|
||||||
|
|
||||||
|
log(common.logLevels.small, "Looking for silt striders to process in cell:%s", cell.name)
|
||||||
|
for activator in cell:iterateReferences(tes3.objectType.activator) do
|
||||||
|
log(common.logLevels.large, "Is %s a silt strider??", activator.object.id)
|
||||||
|
if activator.object.id:match("siltstrider") then
|
||||||
|
if checks.checkTime() or (checks.checkWeather(cell) and not config.keepBadWeatherNPCs) then
|
||||||
|
if not activator.disabled then
|
||||||
|
log(common.logLevels.medium, "Disabling silt strider %s!", activator.object.name)
|
||||||
|
mwscript.disable({reference = activator})
|
||||||
|
-- activator:disable()
|
||||||
|
-- tes3.setEnabled({reference = activator, enabled = false})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if activator.disabled then
|
||||||
|
log(common.logLevels.medium, "Enabling silt strider %s!", activator.object.name)
|
||||||
|
mwscript.enable({reference = activator})
|
||||||
|
-- activator:enable()
|
||||||
|
-- tes3.setEnabled({reference = activator, enabled = true})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log(common.logLevels.large, "Done with silt striders")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- deal with trader's guars, and other npc linked creatures/whatever
|
||||||
|
this.processPets = function(cell)
|
||||||
|
if not config.disableNPCs then return end
|
||||||
|
|
||||||
|
log(common.logLevels.small, "Looking for NPC pets to process in cell:%s", cell.name)
|
||||||
|
|
||||||
|
for creature in cell:iterateReferences(tes3.objectType.creature) do
|
||||||
|
if checks.isNPCPet(creature) then
|
||||||
|
if checks.checkTime() then
|
||||||
|
-- disable
|
||||||
|
if not creature.disabled then
|
||||||
|
log(common.logLevels.medium, "Disabling NPC Pet %s!", creature.object.id)
|
||||||
|
mwscript.disable({reference = creature })
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- enable
|
||||||
|
if creature.disabled then
|
||||||
|
log(common.logLevels.medium, "Enabling NPC Pet %s!", creature.object.id)
|
||||||
|
mwscript.enable({reference = creature })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
this.processDoors = function(cell)
|
||||||
|
if not config.lockDoors then return end
|
||||||
|
|
||||||
|
log(common.logLevels.small, "Looking for doors to process in cell:%s", cell.id)
|
||||||
|
|
||||||
|
for door in cell:iterateReferences(tes3.objectType.door) do
|
||||||
|
if not door.data.NPCsGoHome then door.data.NPCsGoHome = {} end
|
||||||
|
|
||||||
|
if not checks.isIgnoredDoor(door, cell.id) then
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
log(common.logLevels.large, "Found %slocked %s with destination %s",
|
||||||
|
door.data.NPCsGoHome.alreadyLocked and "" or "un", door.id, door.destination.cell.id)
|
||||||
|
|
||||||
|
if checks.checkTime() then
|
||||||
|
if not door.data.NPCsGoHome.alreadyLocked then
|
||||||
|
log(common.logLevels.medium, "locking: %s to %s", door.object.name, door.destination.cell.id)
|
||||||
|
|
||||||
|
local lockLevel = math.random(25, 100)
|
||||||
|
tes3.lock({reference = door, level = lockLevel})
|
||||||
|
door.data.NPCsGoHome.modified = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- only unlock doors that we locked before
|
||||||
|
if door.data.NPCsGoHome.modified then
|
||||||
|
door.data.NPCsGoHome.modified = false
|
||||||
|
|
||||||
|
tes3.setLockLevel({reference = door, level = 0})
|
||||||
|
tes3.unlock({reference = door})
|
||||||
|
|
||||||
|
log(common.logLevels.medium, "unlocking: %s to %s", door.object.name, door.destination.cell.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log(common.logLevels.large, "Now locked Status: %s", tes3.getLocked({reference = door}))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log(common.logLevels.large, "Done with doors")
|
||||||
|
end
|
||||||
|
|
||||||
|
return this
|
|
@ -2,43 +2,18 @@
|
||||||
-- ? could probably split this file out to others as well
|
-- ? could probably split this file out to others as well
|
||||||
local config = require("celediel.NPCsGoHome.config").getConfig()
|
local config = require("celediel.NPCsGoHome.config").getConfig()
|
||||||
local common = require("celediel.NPCsGoHome.common")
|
local common = require("celediel.NPCsGoHome.common")
|
||||||
local interop = require("celediel.NPCsGoHome.interop")
|
local checks = require("celediel.NPCsGoHome.functions.checks")
|
||||||
local positions = require("celediel.NPCsGoHome.positions")
|
local housing = require("celediel.NPCsGoHome.functions.housing")
|
||||||
|
local processors = require("celediel.NPCsGoHome.functions.processors")
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- {{{ variables and such
|
-- {{{ variables and such
|
||||||
-- Waistworks string match
|
|
||||||
local waistworks = {
|
|
||||||
"[Cc]analworks", -- These will match Vivec and Molag Mar
|
|
||||||
"[Ww]aistworks" -- and Almas Thirr from Tamriel Rebuilt
|
|
||||||
}
|
|
||||||
-- these are separate because doors to underworks should be ignored
|
|
||||||
-- but NPCs in underworks should not be disabled
|
|
||||||
local underworks = "[Uu]nderworks"
|
|
||||||
|
|
||||||
-- timers
|
-- timers
|
||||||
local updateTimer
|
local updateTimer
|
||||||
|
|
||||||
-- NPC homes
|
-- references to common.runtimeData
|
||||||
local publicHouses = {}
|
local publicHouses, homes, movedNPCs, followers
|
||||||
local homes = {
|
|
||||||
byName = {}, -- used to ensure duplicate homes are not created
|
|
||||||
byCell = {} -- used for cellChange events
|
|
||||||
}
|
|
||||||
|
|
||||||
-- city name if cell.name is nil
|
|
||||||
local wilderness = "Wilderness"
|
|
||||||
-- maybe this shouldn't be hardcoded
|
|
||||||
local publicHouseTypes = {inns = "Inns", guildhalls = "Guildhalls", temples = "Temples", houses = "Houses"}
|
|
||||||
local movedNPCs = {}
|
|
||||||
|
|
||||||
-- build a list of followers on cellChange
|
|
||||||
local followers = {}
|
|
||||||
|
|
||||||
local zeroVector = tes3vector3.new(0, 0, 0)
|
|
||||||
|
|
||||||
-- animated morrowind NPCs are contextual
|
|
||||||
local contextualNPCs = {"^AM_"}
|
|
||||||
|
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
|
@ -46,63 +21,7 @@ local contextualNPCs = {"^AM_"}
|
||||||
local function log(level, ...) if config.logLevel >= level then common.log(...) end end
|
local function log(level, ...) if config.logLevel >= level then common.log(...) end end
|
||||||
local function message(...) if config.showMessages then tes3.messageBox(...) end end
|
local function message(...) if config.showMessages then tes3.messageBox(...) end end
|
||||||
|
|
||||||
local function checkModdedCell(cellId)
|
-- build a list of followers on cellChange
|
||||||
local id
|
|
||||||
|
|
||||||
if cellId == "Balmora, South Wall Cornerclub" and tes3.isModActive("South Wall.ESP") then
|
|
||||||
id = "Balmora, South Wall Den Of Iniquity"
|
|
||||||
elseif cellId == "Balmora, Eight Plates" and tes3.isModActive("Eight Plates.esp") then
|
|
||||||
id = "Balmora, Seedy Eight Plates"
|
|
||||||
elseif cellId == "Hla Oad, Fatleg's Drop Off" and tes3.isModActive("Clean DR115_TheDropoff_HlaOadDocks.ESP") then
|
|
||||||
id = "Hla Oad, The Drop Off"
|
|
||||||
else
|
|
||||||
id = cellId
|
|
||||||
end
|
|
||||||
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isInteriorCell(cell)
|
|
||||||
if not cell then return end
|
|
||||||
|
|
||||||
log(common.logLevels.large, "Cell %s: interior: %s, behaves as exterior: %s therefore returning %s",
|
|
||||||
cell.id, cell.isInterior, cell.behavesAsExterior, cell.isInterior and not cell.behavesAsExterior)
|
|
||||||
|
|
||||||
return cell.isInterior and not cell.behavesAsExterior
|
|
||||||
end
|
|
||||||
|
|
||||||
-- patented by Merlord
|
|
||||||
local yeet = function(reference)
|
|
||||||
-- tes3.positionCell({reference = reference, position = {0, 0, 10000}})
|
|
||||||
reference:disable()
|
|
||||||
timer.delayOneFrame(function() mwscript.setDelete({reference = reference}) end)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- very todd workaround
|
|
||||||
local function getFightFromSpawnedReference(id)
|
|
||||||
-- Spawn a reference of the given id in toddtest
|
|
||||||
local toddTest = tes3.getCell("toddtest")
|
|
||||||
|
|
||||||
log(common.logLevels.medium, "Spawning %s in %s", id, toddTest.id)
|
|
||||||
|
|
||||||
local ref = tes3.createReference({
|
|
||||||
object = id,
|
|
||||||
-- cell = toddTest,
|
|
||||||
cell = tes3.getPlayerCell(),
|
|
||||||
-- position = zeroVector,
|
|
||||||
position = {0, 0, 10000},
|
|
||||||
orientation = zeroVector
|
|
||||||
})
|
|
||||||
|
|
||||||
local fight = ref.mobile.fight
|
|
||||||
|
|
||||||
log(common.logLevels.medium, "Got fight of %s, time to yeet %s", fight, id)
|
|
||||||
|
|
||||||
yeet(ref)
|
|
||||||
|
|
||||||
return fight
|
|
||||||
end
|
|
||||||
|
|
||||||
local function buildFollowerList()
|
local function buildFollowerList()
|
||||||
local f = {}
|
local f = {}
|
||||||
-- build our followers list
|
-- build our followers list
|
||||||
|
@ -115,499 +34,6 @@ local function buildFollowerList()
|
||||||
return f
|
return f
|
||||||
end
|
end
|
||||||
|
|
||||||
-- {{{ npc evaluators
|
|
||||||
|
|
||||||
-- NPCs barter gold + value of all inventory items
|
|
||||||
local function calculateNPCWorth(npc, merchantCell)
|
|
||||||
local worth = npc.object.barterGold
|
|
||||||
local obj = npc.baseObject and npc.baseObject or npc.object
|
|
||||||
|
|
||||||
if npc.object.inventory then
|
|
||||||
for _, item in pairs(npc.object.inventory) do worth = worth + (item.object.value or 0) end
|
|
||||||
end
|
|
||||||
|
|
||||||
if merchantCell then -- if we pass a cell argument
|
|
||||||
for box in merchantCell:iterateReferences(tes3.objectType.container) do -- loop over each container
|
|
||||||
if box.inventory then -- if it's not empty
|
|
||||||
for item in tes3.iterate(box.inventory) do -- loop over its items
|
|
||||||
if obj:tradesItemType(item.objectType) then -- if the NPC sells that type
|
|
||||||
worth = worth + item.object.value -- add its value to the NPCs total value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return worth
|
|
||||||
end
|
|
||||||
|
|
||||||
-- }}}
|
|
||||||
|
|
||||||
-- {{{ housing
|
|
||||||
|
|
||||||
-- ? I honestly don't know if there are any wandering NPCs that "live" in close-by manors, but I wrote this anyway
|
|
||||||
local function checkManor(cellName, npcName)
|
|
||||||
if not cellName or (cellName and not string.find(cellName, "Manor")) then return end
|
|
||||||
|
|
||||||
local splitName = common.split(npcName)
|
|
||||||
local given = splitName[1]
|
|
||||||
local sur = splitName[2]
|
|
||||||
|
|
||||||
-- surnameless peasants don't live in manors
|
|
||||||
if not sur then return end
|
|
||||||
|
|
||||||
log(common.logLevels.large, "Checking if %s %s lives in %s", given, sur, cellName)
|
|
||||||
return string.match(cellName, sur)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- todo: pick this better
|
|
||||||
local function pickPublicHouseType(cell)
|
|
||||||
if cell.id:match("Guild") then
|
|
||||||
return publicHouseTypes.guildhalls
|
|
||||||
elseif cell.id:match("Temple") then
|
|
||||||
return publicHouseTypes.temples
|
|
||||||
-- elseif cell.id:match("House") then
|
|
||||||
-- return publicHouseTypes.houses
|
|
||||||
else
|
|
||||||
return publicHouseTypes.inns
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pickInnForNPC(npc, city)
|
|
||||||
-- todo: pick in Inn intelligently ?
|
|
||||||
-- high class inns for nobles and rich merchants and such
|
|
||||||
-- lower class inns for middle class npcs and merchants
|
|
||||||
-- temple for commoners and the poorest people
|
|
||||||
-- ? pick based on barterGold and value of equipment for merchants ?
|
|
||||||
-- ? for others, pick based on value of equipment
|
|
||||||
|
|
||||||
-- but for now pick one at random
|
|
||||||
if publicHouses[city] and publicHouses[city][publicHouseTypes.inns] then
|
|
||||||
local choice = table.choice(publicHouses[city][publicHouseTypes.inns])
|
|
||||||
if not choice then return end
|
|
||||||
log(common.logLevels.medium, "Picking inn %s, %s for %s", choice.city, choice.name, npc.object.name)
|
|
||||||
return choice.cell
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pickPublicHouseForNPC(npc, city)
|
|
||||||
-- look for wandering guild members
|
|
||||||
if publicHouses[city] and publicHouses[city][publicHouseTypes.guildhalls] then
|
|
||||||
for _, data in pairs(publicHouses[city][publicHouseTypes.guildhalls]) do
|
|
||||||
-- if npc's faction and proprietor's faction match, pick that one
|
|
||||||
if npc.object.faction == data.proprietor.object.faction then
|
|
||||||
log(common.logLevels.medium, "Picking %s for %s based on faction", data.cell.id, npc.object.name)
|
|
||||||
return data.cell
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- temple members go to the temple
|
|
||||||
if publicHouses[city] and publicHouses[city][publicHouseTypes.temples] then
|
|
||||||
for _, data in pairs(publicHouses[city][publicHouseTypes.temples]) do
|
|
||||||
if npc.object.faction == data.proprietor.object.faction then
|
|
||||||
log(common.logLevels.medium, "Picking temple %s for %s based on faction", data.cell.id, npc.object.name)
|
|
||||||
return data.cell
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- found nothing so pick an inn
|
|
||||||
return pickInnForNPC(npc, city)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function createHomedNPCTableEntry(npc, home, startingPlace, isHome, position, orientation)
|
|
||||||
if npc.object and (npc.object.name == nil or npc.object.name == "") then return end
|
|
||||||
|
|
||||||
local pickedPosition, pickedOrientation, pos, ori
|
|
||||||
|
|
||||||
-- mod support for different positions in cells
|
|
||||||
local id = checkModdedCell(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, id)
|
|
||||||
|
|
||||||
if isHome and positions.npcs[npc.object.name] then
|
|
||||||
pos = positions.npcs[npc.object.name].position
|
|
||||||
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()
|
|
||||||
-- pickedOrientation = positions.npcs[npc.object.name] and tes3vector3.new(o[1], o[2], o[3]) or zeroVector:copy()
|
|
||||||
elseif positions.cells[id] then
|
|
||||||
pos = table.choice(positions.cells[id]).position
|
|
||||||
ori = table.choice(positions.cells[id]).orientation
|
|
||||||
-- 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()
|
|
||||||
-- pickedPosition = tes3vector3.new(p[1], p[2], p[3])
|
|
||||||
-- pickedOrientation = tes3vector3.new(o[1], o[2], o[3])
|
|
||||||
else
|
|
||||||
pos = {0,0,0}
|
|
||||||
ori = {0,0,0}
|
|
||||||
-- pickedPosition = zeroVector:copy()
|
|
||||||
-- pickedOrientation = zeroVector:copy()
|
|
||||||
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 = {
|
|
||||||
name = npc.object.name, -- string
|
|
||||||
npc = npc, -- tes3npc
|
|
||||||
isHome = isHome, -- bool
|
|
||||||
home = home, -- tes3cell
|
|
||||||
homeName = home.id, -- string
|
|
||||||
ogPlace = startingPlace, -- tes3cell
|
|
||||||
ogPlaceName = startingPlace.id,
|
|
||||||
ogPosition = ogPosition, -- tes3vector3
|
|
||||||
ogOrientation = ogOrientation, -- tes3vector3
|
|
||||||
homePosition = pickedPosition, -- tes3vector3
|
|
||||||
homeOrientation = pickedOrientation, -- tes3vector3
|
|
||||||
worth = calculateNPCWorth(npc) -- int
|
|
||||||
}
|
|
||||||
|
|
||||||
homes.byName[npc.object.name] = this
|
|
||||||
if isHome then homes.byCell[home.id] = this end
|
|
||||||
|
|
||||||
interop.setHomedNPCTable(homes.byName)
|
|
||||||
|
|
||||||
return this
|
|
||||||
end
|
|
||||||
|
|
||||||
local function createPublicHouseTableEntry(publicCell, proprietor, city, name)
|
|
||||||
local typeOfPub = pickPublicHouseType(publicCell)
|
|
||||||
|
|
||||||
local worth = 0
|
|
||||||
|
|
||||||
-- for houses, worth is equal to NPC who lives there
|
|
||||||
-- if typeOfPub == publicHouseTypes.houses then
|
|
||||||
-- worth = calculateNPCWorth(proprietor)
|
|
||||||
-- else
|
|
||||||
-- for other types, worth is combined worth of all NPCs
|
|
||||||
for innard in publicCell:iterateReferences(tes3.objectType.npc) do
|
|
||||||
if innard == proprietor then
|
|
||||||
worth = worth + calculateNPCWorth(innard, publicCell)
|
|
||||||
else
|
|
||||||
worth = worth + calculateNPCWorth(innard)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
if not publicHouses[city] then publicHouses[city] = {} end
|
|
||||||
if not publicHouses[city][typeOfPub] then publicHouses[city][typeOfPub] = {} end
|
|
||||||
|
|
||||||
publicHouses[city][typeOfPub][publicCell.id] = {
|
|
||||||
name = name,
|
|
||||||
city = city,
|
|
||||||
cell = publicCell,
|
|
||||||
proprietor = proprietor,
|
|
||||||
proprietorName = proprietor.object.name,
|
|
||||||
worth = worth
|
|
||||||
}
|
|
||||||
|
|
||||||
interop.setPublicHouseTable(publicHouses)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- looks through doors to find a cell that matches a wandering NPCs name
|
|
||||||
local function pickHomeForNPC(cell, npc)
|
|
||||||
-- wilderness cells don't have name
|
|
||||||
if not cell.name then return end
|
|
||||||
|
|
||||||
-- don't move contextual, such as Animated Morrowind, NPCs at all
|
|
||||||
for _, str in pairs(contextualNPCs) do if npc.object.id:match(str) then return end end
|
|
||||||
|
|
||||||
local name = npc.object.name
|
|
||||||
local city = common.split(cell.name, ",")[1]
|
|
||||||
for door in cell:iterateReferences(tes3.objectType.door) do
|
|
||||||
if door.destination then
|
|
||||||
local dest = door.destination.cell
|
|
||||||
|
|
||||||
-- 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)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- haven't found a home, so put them in an inn or guildhall
|
|
||||||
if config.homelessWanderersToPublicHouses then
|
|
||||||
log(common.logLevels.medium, "Didn't find a home for %s, trying inns", npc.object.name)
|
|
||||||
local dest = pickPublicHouseForNPC(npc, city)
|
|
||||||
-- return createHomedNPCTableEntry(npc, dest, door)
|
|
||||||
if dest then return createHomedNPCTableEntry(npc, dest, cell, false) end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- }}}
|
|
||||||
|
|
||||||
-- {{{ checks
|
|
||||||
|
|
||||||
local function isCityCell(internalCellId, externalCellId)
|
|
||||||
-- easy mode
|
|
||||||
if string.match(internalCellId, externalCellId) then
|
|
||||||
log(common.logLevels.large, "easy mode city: %s in %s", internalCellId, externalCellId)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local cityMatch = "^(%w+), (.*)"
|
|
||||||
-- check for "advanced" cities
|
|
||||||
local _, _, internalCity = string.find(internalCellId, cityMatch)
|
|
||||||
local _, _, externalCity = string.find(externalCellId, cityMatch)
|
|
||||||
|
|
||||||
if externalCity and externalCity == internalCity then
|
|
||||||
log(common.logLevels.large, "hard mode city: %s in %s, %s == %s",
|
|
||||||
internalCellId, externalCellId, externalCity, internalCity)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
log(common.logLevels.large, "hard mode not city: %s not in %s, %s ~= %s or both are nil",
|
|
||||||
internalCellId, externalCellId, externalCity, internalCity)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isIgnoredCell(cell)
|
|
||||||
log(common.logLevels.large, "%s is %s, %s is %s", cell.id, config.ignored[cell.id] and "ignored" or "not ignored",
|
|
||||||
cell.sourceMod, config.ignored[cell.sourceMod] and "ignored" or "not ignored")
|
|
||||||
|
|
||||||
-- don't do things in the wilderness
|
|
||||||
-- local wilderness = false
|
|
||||||
-- if not cell.name then wilderness = true end
|
|
||||||
|
|
||||||
return config.ignored[cell.id] or config.ignored[cell.sourceMod] -- or wilderness
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isCantonCell(cellName)
|
|
||||||
for _, str in pairs(waistworks) do if cellName:match(str) then return true end end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function fargothCheck()
|
|
||||||
local fargothJournal = tes3.getJournalIndex({id = "MS_Lookout"})
|
|
||||||
if not fargothJournal then return false end
|
|
||||||
|
|
||||||
-- 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)
|
|
||||||
|
|
||||||
return fargothJournal > 10 and fargothJournal <= 30
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isIgnoredNPC(npc)
|
|
||||||
local obj = npc.baseObject and npc.baseObject or npc.object
|
|
||||||
|
|
||||||
-- ignore dead, attack on sight NPCs, and vampires
|
|
||||||
local isDead = false
|
|
||||||
local isHostile = false
|
|
||||||
local isVampire = false
|
|
||||||
|
|
||||||
if npc.mobile then
|
|
||||||
if npc.mobile.health.current <= 0 or npc.mobile.isDead then isDead = true end
|
|
||||||
if npc.mobile.fight > 70 then isHostile = true end
|
|
||||||
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 and (obj.head.vampiric and true or false) or false
|
|
||||||
if obj.id:match("[Dd]ead") or obj.name:match("[Dd]ead") then isDead = true end
|
|
||||||
end
|
|
||||||
|
|
||||||
local isFargothActive = obj.id:match("fargoth") and fargothCheck() or false
|
|
||||||
|
|
||||||
-- todo: non mwscript version of this
|
|
||||||
local isWerewolf = mwscript.getSpellEffects({reference = npc, spell = "werewolf vision"})
|
|
||||||
-- 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
|
|
||||||
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"), --
|
|
||||||
obj.name, npc.object.id, npc.object.baseObject and npc.object.baseObject.id or "nil", --
|
|
||||||
config.ignored[obj.id:lower()], obj.sourceMod, config.ignored[obj.sourceMod:lower()], --
|
|
||||||
obj.isGuard, isDead, isVampire, isWerewolf, (obj.class and obj.class.id == "Dreamers"), --
|
|
||||||
followers[npc.object.id], isHostile, obj.id:match("fargoth") and "fargoth:" or "", --
|
|
||||||
obj.id:match("fargoth") and isFargothActive or "")
|
|
||||||
|
|
||||||
return config.ignored[obj.id:lower()] or --
|
|
||||||
config.ignored[obj.sourceMod:lower()] or --
|
|
||||||
obj.isGuard or --
|
|
||||||
isFargothActive or --
|
|
||||||
isDead or -- don't move dead NPCS
|
|
||||||
isHostile or --
|
|
||||||
followers[npc.object.id] or -- ignore followers
|
|
||||||
isVampire or --
|
|
||||||
isWerewolf or --
|
|
||||||
(obj.class and obj.class.id == "Dreamers") --
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isNPCPet(creature)
|
|
||||||
local obj = creature.baseObject and creature.baseObject or creature.object
|
|
||||||
|
|
||||||
-- todo: more pets?
|
|
||||||
if obj.id:match("guar") and obj.mesh:match("pack") then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- checks NPC class and faction in cells for block list and adds to publicHouse list
|
|
||||||
-- todo: rewrite this
|
|
||||||
local function isPublicHouse(cell)
|
|
||||||
-- only interior cells are public "houses"
|
|
||||||
if not isInteriorCell(cell) then return false end
|
|
||||||
|
|
||||||
local typeOfPub = pickPublicHouseType(cell)
|
|
||||||
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}
|
|
||||||
for npc in cell:iterateReferences(tes3.objectType.npc) do
|
|
||||||
-- Check for NPCS of ignored classes first
|
|
||||||
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,
|
|
||||||
npc.object.class and npc.object.class.id or "none", cell.name)
|
|
||||||
|
|
||||||
createPublicHouseTableEntry(cell, npc, city, publicHouseName)
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local faction = npc.object.faction
|
|
||||||
|
|
||||||
if faction then
|
|
||||||
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 npcs.factions[faction].master = npc end
|
|
||||||
|
|
||||||
npcs.factions[faction].total = npcs.factions[faction].total + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
npcs.total = npcs.total + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- no NPCs of ignored classes, so let's check out factions
|
|
||||||
for faction, info in pairs(npcs.factions) do
|
|
||||||
info.percentage = (info.total / npcs.total) * 100
|
|
||||||
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, 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
|
|
||||||
if (config.ignored[faction.id] or faction.playerJoined) and
|
|
||||||
(npcs.total >= config.minimumOccupancy or faction == "Blades") and info.percentage >=
|
|
||||||
config.factionIgnorePercentage then
|
|
||||||
log(common.logLevels.medium, "%s is %s%% faction %s, marking public.", cell.name, info.percentage, faction)
|
|
||||||
|
|
||||||
createPublicHouseTableEntry(cell, npcs.factions[faction].master, city, publicHouseName)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
log(common.logLevels.large, "%s isn't public", cell.name)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- doors that lead to ignored, exterior, canton, unoccupied, or public cells, and doors that aren't in cities
|
|
||||||
local function isIgnoredDoor(door, homeCellId)
|
|
||||||
-- don't lock non-cell change doors
|
|
||||||
if not door.destination then
|
|
||||||
log(common.logLevels.large, "Non-Cell-change door %s, ignoring", door.id)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- we use this a lot, so set a reference to it
|
|
||||||
local dest = door.destination.cell
|
|
||||||
|
|
||||||
-- Only doors in cities and towns (interior cells with names that contain the exterior cell)
|
|
||||||
local inCity = isCityCell(dest.id, homeCellId)
|
|
||||||
|
|
||||||
-- peek inside doors to look for guild halls, inns and clubs
|
|
||||||
local leadsToPublicCell = isPublicHouse(dest)
|
|
||||||
|
|
||||||
-- don't lock unoccupied cells
|
|
||||||
local hasOccupants = false
|
|
||||||
for npc in dest:iterateReferences(tes3.objectType.npc) do
|
|
||||||
if not isIgnoredNPC(npc) then
|
|
||||||
hasOccupants = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- don't lock doors to underworks in addition to other canton cells
|
|
||||||
local isCanton = isCantonCell(dest.id) or dest.id:match(underworks)
|
|
||||||
|
|
||||||
log(common.logLevels.large, "%s is %s, (%sin a city, is %spublic, %soccupied)", --
|
|
||||||
dest.id, isIgnoredCell(dest) and "ignored" or "not ignored", -- destination is ignored
|
|
||||||
inCity and "" or "not ", leadsToPublicCell and "" or "not ", hasOccupants and "" or "un") -- in a city, is public, is ocupado
|
|
||||||
|
|
||||||
return isIgnoredCell(dest) or
|
|
||||||
not isInteriorCell(dest) or
|
|
||||||
isCanton or
|
|
||||||
not inCity or
|
|
||||||
leadsToPublicCell or
|
|
||||||
not hasOccupants
|
|
||||||
end
|
|
||||||
|
|
||||||
-- AT NIGHT
|
|
||||||
local function checkTime()
|
|
||||||
log(common.logLevels.large, "Current time is %s, things are closed between %s and %s",
|
|
||||||
tes3.worldController.hour.value, config.closeTime, config.openTime)
|
|
||||||
return tes3.worldController.hour.value >= config.closeTime or tes3.worldController.hour.value <= config.openTime
|
|
||||||
end
|
|
||||||
|
|
||||||
-- inclement weather
|
|
||||||
local function checkWeather(cell)
|
|
||||||
if not cell.region then return end
|
|
||||||
|
|
||||||
log(common.logLevels.large, "Weather: %s >= %s == %s", cell.region.weather.index, config.worstWeather,
|
|
||||||
cell.region.weather.index >= config.worstWeather)
|
|
||||||
|
|
||||||
return cell.region.weather.index >= config.worstWeather
|
|
||||||
end
|
|
||||||
|
|
||||||
-- travel agents, their steeds, and argonians stick around
|
|
||||||
local function isBadWeatherNPC(npc)
|
|
||||||
local obj = npc.baseObject and npc.baseObject or npc.object
|
|
||||||
if not obj then return end
|
|
||||||
|
|
||||||
log(common.logLevels.large, "NPC Inclement Weather: %s is %s, %s", npc.object.name, npc.object.class.name,
|
|
||||||
npc.object.race.id)
|
|
||||||
|
|
||||||
-- todo: better detection of NPCs who offer travel services
|
|
||||||
-- found a rogue "shipmaster" in molag mar
|
|
||||||
return obj.class.name == "Caravaner" or
|
|
||||||
obj.class.name == "Gondolier" or
|
|
||||||
obj.class.name == "Shipmaster" or
|
|
||||||
obj.race.id == "Argonian"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- }}}
|
|
||||||
|
|
||||||
-- {{{ cell change checks
|
-- {{{ cell change checks
|
||||||
|
|
||||||
local function checkEnteredNPCHome(cell)
|
local function checkEnteredNPCHome(cell)
|
||||||
|
@ -620,7 +46,7 @@ local function checkEnteredNPCHome(cell)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function checkEnteredPublicHouse(cell, city)
|
local function checkEnteredPublicHouse(cell, city)
|
||||||
local typeOfPub = pickPublicHouseType(cell)
|
local typeOfPub = housing.pickPublicHouseType(cell)
|
||||||
|
|
||||||
local publicHouse = publicHouses[city] and (publicHouses[city][typeOfPub] and publicHouses[city][typeOfPub][cell.name])
|
local publicHouse = publicHouses[city] and (publicHouses[city][typeOfPub] and publicHouses[city][typeOfPub][cell.name])
|
||||||
|
|
||||||
|
@ -635,255 +61,24 @@ end
|
||||||
|
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- }}}
|
|
||||||
|
|
||||||
-- {{{ real meat and potatoes functions
|
|
||||||
local function moveNPC(homeData)
|
|
||||||
-- add to in memory table
|
|
||||||
table.insert(movedNPCs, homeData)
|
|
||||||
interop.setMovedNPCTable(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({
|
|
||||||
cell = homeData.home,
|
|
||||||
reference = homeData.npc,
|
|
||||||
position = homeData.homePosition,
|
|
||||||
orientation = homeData.homeOrientation
|
|
||||||
})
|
|
||||||
|
|
||||||
log(common.logLevels.medium, "Moving %s to home %s (%s, %s, %s)", homeData.npc.object.name, homeData.home.id,
|
|
||||||
homeData.homePosition.x, homeData.homePosition.y, homeData.homePosition.z)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function putNPCsBack()
|
|
||||||
for i = #movedNPCs, 1, -1 do
|
|
||||||
local data = table.remove(movedNPCs, i)
|
|
||||||
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)
|
|
||||||
|
|
||||||
-- unset NPC data so we don't try to move them on load
|
|
||||||
data.npc.data.NPCsGoHome = nil
|
|
||||||
|
|
||||||
-- and put them back
|
|
||||||
tes3.positionCell({
|
|
||||||
cell = data.ogPlace,
|
|
||||||
reference = data.npc,
|
|
||||||
position = data.ogPosition,
|
|
||||||
orientation = data.ogPlace
|
|
||||||
})
|
|
||||||
end
|
|
||||||
interop.setMovedNPCTable(movedNPCs)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- search in a specific cell for moved NPCs
|
|
||||||
local function checkForMovedNPCs(cell)
|
|
||||||
-- NPCs don't get moved to exterior cells, so no need to check them for moved NPCs
|
|
||||||
if not isInteriorCell(cell) then return end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
local function searchCellsForNPCs()
|
|
||||||
for _, cell in pairs(tes3.getActiveCells()) do
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
log(common.logLevels.small, "Looking for NPCs to process in cell:%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
|
|
||||||
for npc in cell:iterateReferences(tes3.objectType.npc) do
|
|
||||||
-- for npc, _ in pairs(cellsInMemory[cell].npcs) do
|
|
||||||
if not isIgnoredNPC(npc) then
|
|
||||||
log(common.logLevels.large, "People change")
|
|
||||||
-- if not npc.data.NPCsGoHome then npc.data.NPCsGoHome = {} end
|
|
||||||
|
|
||||||
-- find NPC homes
|
|
||||||
local npcHome = config.moveNPCs and pickHomeForNPC(cell, npc) or nil
|
|
||||||
|
|
||||||
local tmpLogLevelNPCHome = npcHome and common.logLevels.medium or common.logLevels.large
|
|
||||||
log(tmpLogLevelNPCHome, "%s %s %s%s", npc.object.name,
|
|
||||||
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.") or ".")
|
|
||||||
|
|
||||||
-- disable or move NPCs
|
|
||||||
if (checkTime() or
|
|
||||||
(checkWeather(cell) and
|
|
||||||
(not isBadWeatherNPC(npc) or (isBadWeatherNPC(npc) and not config.keepBadWeatherNPCs)))) then
|
|
||||||
if npcHome then
|
|
||||||
moveNPC(npcHome)
|
|
||||||
-- elseif not npc.data.NPCsGoHome.modified then
|
|
||||||
elseif not npc.disabled then
|
|
||||||
log(common.logLevels.medium, "Disabling homeless %s", npc.object.name)
|
|
||||||
-- npc:disable() -- ! this one sometimes causes crashes
|
|
||||||
mwscript.disable({reference = npc}) -- ! this one is deprecated
|
|
||||||
-- 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
|
|
||||||
else
|
|
||||||
-- 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)
|
|
||||||
-- npc:enable()
|
|
||||||
mwscript.enable({reference = npc})
|
|
||||||
-- tes3.setEnabled({reference = npc, enabled = true})
|
|
||||||
-- npc.data.NPCsGoHome.modified = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- now put NPCs back
|
|
||||||
-- if not (checkTime() or checkWeather(cell)) and #movedNPCs > 0 then putNPCsBack() end
|
|
||||||
if not (checkTime() or checkWeather(cell)) then putNPCsBack() end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function processSiltStriders(cell)
|
|
||||||
if not config.disableNPCs then return end
|
|
||||||
|
|
||||||
log(common.logLevels.small, "Looking for silt striders to process in cell:%s", cell.name)
|
|
||||||
for activator in cell:iterateReferences(tes3.objectType.activator) do
|
|
||||||
log(common.logLevels.large, "Is %s a silt strider??", activator.object.id)
|
|
||||||
if activator.object.id:match("siltstrider") 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)
|
|
||||||
mwscript.disable({reference = activator})
|
|
||||||
-- activator:disable()
|
|
||||||
-- tes3.setEnabled({reference = activator, enabled = false})
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if activator.disabled then
|
|
||||||
log(common.logLevels.medium, "Enabling silt strider %s!", activator.object.name)
|
|
||||||
mwscript.enable({reference = activator})
|
|
||||||
-- activator:enable()
|
|
||||||
-- tes3.setEnabled({reference = activator, enabled = true})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
log(common.logLevels.large, "Done with silt striders")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- deal with trader's guars, and other npc linked creatures/whatever
|
|
||||||
local function processPets(cell)
|
|
||||||
if not config.disableNPCs then return end
|
|
||||||
|
|
||||||
log(common.logLevels.small, "Looking for NPC pets to process in cell:%s", cell.name)
|
|
||||||
|
|
||||||
for creature in cell:iterateReferences(tes3.objectType.creature) do
|
|
||||||
if isNPCPet(creature) then
|
|
||||||
if checkTime() then
|
|
||||||
-- disable
|
|
||||||
if not creature.disabled then
|
|
||||||
log(common.logLevels.medium, "Disabling NPC Pet %s!", creature.object.id)
|
|
||||||
mwscript.disable({reference = creature })
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- enable
|
|
||||||
if creature.disabled then
|
|
||||||
log(common.logLevels.medium, "Enabling NPC Pet %s!", creature.object.id)
|
|
||||||
mwscript.enable({reference = creature })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function processDoors(cell)
|
|
||||||
if not config.lockDoors then return end
|
|
||||||
|
|
||||||
log(common.logLevels.small, "Looking for doors to process in cell:%s", cell.id)
|
|
||||||
|
|
||||||
for door in cell:iterateReferences(tes3.objectType.door) do
|
|
||||||
if not door.data.NPCsGoHome then door.data.NPCsGoHome = {} end
|
|
||||||
|
|
||||||
if not isIgnoredDoor(door, cell.id) then
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
log(common.logLevels.large, "Found %slocked %s with destination %s",
|
|
||||||
door.data.NPCsGoHome.alreadyLocked and "" or "un", door.id, door.destination.cell.id)
|
|
||||||
|
|
||||||
if checkTime() then
|
|
||||||
if not door.data.NPCsGoHome.alreadyLocked then
|
|
||||||
log(common.logLevels.medium, "locking: %s to %s", door.object.name, door.destination.cell.id)
|
|
||||||
|
|
||||||
local lockLevel = math.random(25, 100)
|
|
||||||
tes3.lock({reference = door, level = lockLevel})
|
|
||||||
door.data.NPCsGoHome.modified = true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- only unlock doors that we locked before
|
|
||||||
if door.data.NPCsGoHome.modified then
|
|
||||||
door.data.NPCsGoHome.modified = false
|
|
||||||
|
|
||||||
tes3.setLockLevel({reference = door, level = 0})
|
|
||||||
tes3.unlock({reference = door})
|
|
||||||
|
|
||||||
log(common.logLevels.medium, "unlocking: %s to %s", door.object.name, door.destination.cell.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
log(common.logLevels.large, "Now locked Status: %s", tes3.getLocked({reference = door}))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
log(common.logLevels.large, "Done with doors")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function applyChanges(cell)
|
local function applyChanges(cell)
|
||||||
if not cell then cell = tes3.getPlayerCell() end
|
if not cell then cell = tes3.getPlayerCell() end
|
||||||
|
|
||||||
if isIgnoredCell(cell) then return end
|
if checks.isIgnoredCell(cell) then return end
|
||||||
|
|
||||||
-- Interior cell, except Canton cells, don't do anything
|
-- Interior cell, except Canton cells, don't do anything
|
||||||
if isInteriorCell(cell) and not (config.waistWorks and isCantonCell(cell.id)) then return end
|
if checks.isInteriorCell(cell) and not (config.waistWorks and checks.isCantonCell(cell.id)) then return end
|
||||||
|
|
||||||
-- don't do anything to public houses
|
-- don't do anything to public houses
|
||||||
if isPublicHouse(cell) then return end
|
if checks.isPublicHouse(cell) then return end
|
||||||
|
|
||||||
-- Deal with NPCs and mounts in cell
|
-- Deal with NPCs and mounts in cell
|
||||||
processNPCs(cell)
|
processors.processNPCs(cell)
|
||||||
processPets(cell)
|
processors.processPets(cell)
|
||||||
processSiltStriders(cell)
|
processors.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)
|
processors.processDoors(cell)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function updateCells()
|
local function updateCells()
|
||||||
|
@ -902,8 +97,8 @@ local function updatePlayerTrespass(cell, previousCell)
|
||||||
|
|
||||||
local inCity = previousCell and (previousCell.id:match(cell.id) or cell.id:match(previousCell.id))
|
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 checks.isInteriorCell(cell) and not checks.isIgnoredCell(cell) and not checks.isPublicHouse(cell) and inCity then
|
||||||
if checkTime() then
|
if checks.checkTime() then
|
||||||
tes3.player.data.NPCsGoHome.intruding = true
|
tes3.player.data.NPCsGoHome.intruding = true
|
||||||
else
|
else
|
||||||
tes3.player.data.NPCsGoHome.intruding = false
|
tes3.player.data.NPCsGoHome.intruding = false
|
||||||
|
@ -922,7 +117,7 @@ local function onActivated(e)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if tes3.player.data.NPCsGoHome.intruding and not isIgnoredNPC(e.target) then
|
if tes3.player.data.NPCsGoHome.intruding and not checks.isIgnoredNPC(e.target) then
|
||||||
tes3.messageBox(string.format("%s: Get out before I call the guards!", e.target.object.name))
|
tes3.messageBox(string.format("%s: Get out before I call the guards!", e.target.object.name))
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
@ -932,7 +127,7 @@ local function onLoaded()
|
||||||
tes3.player.data.NPCsGoHome = tes3.player.data.NPCsGoHome or {}
|
tes3.player.data.NPCsGoHome = tes3.player.data.NPCsGoHome or {}
|
||||||
-- tes3.player.data.NPCsGoHome.movedNPCs = tes3.player.data.NPCsGoHome.movedNPCs or {}
|
-- tes3.player.data.NPCsGoHome.movedNPCs = tes3.player.data.NPCsGoHome.movedNPCs or {}
|
||||||
-- movedNPCs = tes3.player.data.NPCsGoHome.movedNPCs or {}
|
-- movedNPCs = tes3.player.data.NPCsGoHome.movedNPCs or {}
|
||||||
if tes3.player.cell then searchCellsForNPCs() end
|
if tes3.player.cell then processors.searchCellsForNPCs() end
|
||||||
|
|
||||||
followers = buildFollowerList()
|
followers = buildFollowerList()
|
||||||
|
|
||||||
|
@ -957,13 +152,24 @@ local function onCellChanged(e)
|
||||||
end
|
end
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- {{{ event registering
|
-- {{{ init
|
||||||
|
local function onInitialized()
|
||||||
|
-- set up runtime data references
|
||||||
|
publicHouses = common.runtimeData.publicHouses
|
||||||
|
homes = common.runtimeData.homes
|
||||||
|
movedNPCs = common.runtimeData.movedNPCs
|
||||||
|
followers = common.runtimeData.followers
|
||||||
|
|
||||||
|
-- Register events
|
||||||
event.register("loaded", onLoaded)
|
event.register("loaded", onLoaded)
|
||||||
event.register("cellChanged", onCellChanged)
|
event.register("cellChanged", onCellChanged)
|
||||||
|
|
||||||
event.register("activate", onActivated)
|
event.register("activate", onActivated)
|
||||||
|
|
||||||
|
-- MCM
|
||||||
event.register("modConfigReady", function() mwse.mcm.register(require("celediel.NPCsGoHome.mcm")) end)
|
event.register("modConfigReady", function() mwse.mcm.register(require("celediel.NPCsGoHome.mcm")) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
event.register("initialized", onInitialized)
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- vim:fdm=marker
|
-- vim:fdm=marker
|
||||||
|
|
Loading…
Reference in a new issue