option to treat waistworks as public + refactoring

This commit is contained in:
Lilian Jónsdóttir 2020-08-23 22:02:33 -07:00
parent 1e4c733b0a
commit ad4a113642
9 changed files with 269 additions and 219 deletions

View file

@ -10,7 +10,12 @@ this.modInfo = "Move NPCs to their homes, or public houses (or just disable them
"and prevent interaction after hours, selectively disable NPCs in inclement weather" "and prevent interaction after hours, selectively disable NPCs in inclement weather"
this.configPath = "NPCSGOHOME" this.configPath = "NPCSGOHOME"
-- for config
this.logLevels = {none = 0, small = 1, medium = 2, large = 3} this.logLevels = {none = 0, small = 1, medium = 2, large = 3}
this.waist = {neither = 0, exterior = 1, public = 2}
-- for runtime data
this.publicHouseTypes = {inns = "Inns", guildhalls = "Guildhalls", temples = "Temples", houses = "Houses", cantonworks = "Cantonworks"}
-- }}} -- }}}
-- {{{ Filled at runtime -- {{{ Filled at runtime
@ -53,6 +58,21 @@ this.vowel = function(str)
return n return n
end end
-- todo: pick this better
this.pickPublicHouseType = function(cell)
if cell.id:match("Guild") then
return this.publicHouseTypes.guildhalls
elseif cell.id:match("Temple") then
return this.publicHouseTypes.temples
elseif cell.id:match("[Cc]analworks") or cell.id:match("[Ww]aistworks") then
return this.publicHouseTypes.cantonworks
-- elseif cell.id:match("House") then
-- return publicHouseTypes.houses
else
return this.publicHouseTypes.inns
end
end
-- }}} -- }}}
return this return this

View file

@ -22,7 +22,7 @@ local defaultConfig = {
disableInteraction = true, disableInteraction = true,
-- door settings -- door settings
lockDoors = true, lockDoors = true,
waistWorks = true, waistWorks = common.waist.interior,
-- debug settings -- debug settings
logLevel = common.logLevels.none, logLevel = common.logLevels.none,
} }

View file

@ -1,14 +1,14 @@
local common = require("celediel.NPCsGoHome.common") local common = require("celediel.NPCsGoHome.common")
local config = require("celediel.NPCsGoHome.config").getConfig() local config = require("celediel.NPCsGoHome.config").getConfig()
local housing = require("celediel.NPCsGoHome.functions.housing") local entry = require("celediel.NPCsGoHome.functions.entry")
-- {{{ local variables and such -- {{{ local variables and such
-- Waistworks string match -- Canton string matches
local waistworks = { -- move NPCs into waistworks
"[Cc]analworks", -- These will match Vivec and Molag Mar local waistworks = "[Ww]aistworks"
"[Ww]aistworks" -- and Almas Thirr from Tamriel Rebuilt -- don't lock canalworks
} local canalworks = "[Cc]analworks"
-- these are separate because doors to underworks should be ignored -- doors to underworks should be ignored
-- but NPCs in underworks should not be disabled -- but NPCs in underworks should not be disabled
local underworks = "[Uu]nderworks" local underworks = "[Uu]nderworks"
@ -30,7 +30,6 @@ end
local function getFightFromSpawnedReference(id) local function getFightFromSpawnedReference(id)
-- Spawn a reference of the given id in toddtest -- Spawn a reference of the given id in toddtest
local toddTest = tes3.getCell("toddtest") local toddTest = tes3.getCell("toddtest")
log(common.logLevels.medium, "Spawning %s in %s", id, toddTest.id) log(common.logLevels.medium, "Spawning %s in %s", id, toddTest.id)
local ref = tes3.createReference({ local ref = tes3.createReference({
@ -39,7 +38,7 @@ local function getFightFromSpawnedReference(id)
cell = tes3.getPlayerCell(), cell = tes3.getPlayerCell(),
-- position = zeroVector, -- position = zeroVector,
position = {0, 0, 10000}, position = {0, 0, 10000},
orientation = housing.zeroVector orientation = tes3vector3.new(0, 0, 0)
}) })
local fight = ref.mobile.fight local fight = ref.mobile.fight
@ -97,8 +96,18 @@ this.isIgnoredCell = function(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
this.isCantonCell = function(cellName) this.isCantonWorksCell = function(cell)
for _, str in pairs(waistworks) do if cellName:match(str) then return true end end -- for _, str in pairs(waistworks) do if cell.id:match(str) then return true end end
return cell.id:match(waistworks) or cell.id:match(canalworks) or cell.id:match(underworks)
end
this.isCantonCell = function(cell)
if this.isInteriorCell(cell) then return false end
for door in cell:iterateReferences(tes3.objectType.door) do
if door.destination and this.isCantonWorksCell(door.destination.cell) then
return true
end
end
return false return false
end end
@ -177,7 +186,7 @@ this.isPublicHouse = function(cell)
-- only interior cells are public "houses" -- only interior cells are public "houses"
if not this.isInteriorCell(cell) then return false end if not this.isInteriorCell(cell) then return false end
local typeOfPub = housing.pickPublicHouseType(cell) local typeOfPub = common.pickPublicHouseType(cell)
local city, publicHouseName local city, publicHouseName
if cell.name and string.match(cell.name, ",") then if cell.name and string.match(cell.name, ",") then
@ -191,6 +200,12 @@ this.isPublicHouse = function(cell)
-- don't iterate NPCs in the cell if we've already marked it public -- 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 if common.runtimeData.publicHouses[city] and (common.runtimeData.publicHouses[city][typeOfPub] and common.runtimeData.publicHouses[city][typeOfPub][cell.id]) then return true end
-- if it's a waistworks cell, it's public, with no proprietor
if config.waistWorks == common.waist.public and cell.id:match(waistworks) then
entry.createPublicHouseTableEntry(cell, nil, city, publicHouseName)
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
@ -199,7 +214,7 @@ this.isPublicHouse = function(cell)
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)
housing.createPublicHouseTableEntry(cell, npc, city, publicHouseName) entry.createPublicHouseTableEntry(cell, npc, city, publicHouseName)
return true return true
end end
@ -233,7 +248,7 @@ this.isPublicHouse = function(cell)
config.factionIgnorePercentage then config.factionIgnorePercentage then
log(common.logLevels.medium, "%s is %s%% faction %s, marking public.", cell.name, info.percentage, faction) 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) entry.createPublicHouseTableEntry(cell, npcs.factions[faction].master, city, publicHouseName)
return true return true
end end
end end
@ -268,8 +283,8 @@ this.isIgnoredDoor = function(door, homeCellId)
end end
end end
-- don't lock doors to underworks in addition to other canton cells -- don't lock doors to canton cells
local isCanton = this.isCantonCell(dest.id) or dest.id:match(underworks) local isCantonWorks = this.isCantonWorksCell(dest)
log(common.logLevels.large, "%s is %s, (%sin a city, is %spublic, %soccupied)", -- 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 dest.id, this.isIgnoredCell(dest) and "ignored" or "not ignored", -- destination is ignored
@ -277,7 +292,7 @@ this.isIgnoredDoor = function(door, homeCellId)
return this.isIgnoredCell(dest) or return this.isIgnoredCell(dest) or
not this.isInteriorCell(dest) or not this.isInteriorCell(dest) or
isCanton or isCantonWorks or
not inCity or not inCity or
leadsToPublicCell or leadsToPublicCell or
not hasOccupants not hasOccupants

View file

@ -0,0 +1,150 @@
local common = require("celediel.NPCsGoHome.common")
local interop = require("celediel.NPCsGoHome.interop")
local positions = require("celediel.NPCsGoHome.data.positions")
local config = require("celediel.NPCsGoHome.config").getConfig()
local zeroVector = tes3vector3.new(0, 0, 0)
local function log(level, ...) if config.logLevel >= level then common.log(...) end end
local this = {}
-- {{{ 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
-- }}}
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
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 entry = {
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] = entry
if isHome then common.runtimeData.homes.byCell[home.id] = entry end
interop.setRuntimeData(common.runtimeData)
return entry
end
this.createPublicHouseTableEntry = function(publicCell, proprietor, city, name)
local typeOfPub = common.pickPublicHouseType(publicCell)
local worth = 0
-- cell 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
local proprietorName = proprietor and proprietor.object.name or "no one"
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 = proprietorName,
worth = worth
}
interop.setRuntimeData(common.runtimeData)
end
return this

View file

@ -1,75 +1,15 @@
local common = require("celediel.NPCsGoHome.common") local common = require("celediel.NPCsGoHome.common")
local config = require("celediel.NPCsGoHome.config").getConfig() local config = require("celediel.NPCsGoHome.config").getConfig()
local interop = require("celediel.NPCsGoHome.interop") local checks = require("celediel.NPCsGoHome.functions.checks")
local positions = require("celediel.NPCsGoHome.data.positions") local entry = require("celediel.NPCsGoHome.functions.entry")
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 publicHouseTypes = {inns = "Inns", guildhalls = "Guildhalls", temples = "Temples", houses = "Houses"}
-- animated morrowind NPCs are contextual -- animated morrowind NPCs are contextual
local contextualNPCs = {"^AM_"} local contextualNPCs = {"^AM_"}
local this = {} 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 -- ? 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) this.livesInManor = function(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
@ -94,9 +34,9 @@ this.pickInnForNPC = function(npc, city)
-- ? for others, pick based on value of equipment -- ? for others, pick based on value of equipment
-- but for now pick one at random -- but for now pick one at random
if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][publicHouseTypes.inns] then if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][common.publicHouseTypes.inns] then
local choice = table.choice(common.runtimeData.publicHouses[city][publicHouseTypes.inns]) local choice = table.choice(common.runtimeData.publicHouses[city][common.publicHouseTypes.inns])
if not choice then return end if not choice then return nil end
log(common.logLevels.medium, "Picking inn %s, %s for %s", choice.city, choice.name, npc.object.name) log(common.logLevels.medium, "Picking inn %s, %s for %s", choice.city, choice.name, npc.object.name)
return choice.cell return choice.cell
end end
@ -104,8 +44,8 @@ end
this.pickPublicHouseForNPC = function(npc, city) this.pickPublicHouseForNPC = function(npc, city)
-- look for wandering guild members -- look for wandering guild members
if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][publicHouseTypes.guildhalls] then if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][common.publicHouseTypes.guildhalls] then
for _, data in pairs(common.runtimeData.publicHouses[city][publicHouseTypes.guildhalls]) do for _, data in pairs(common.runtimeData.publicHouses[city][common.publicHouseTypes.guildhalls]) do
-- if npc's faction and proprietor's faction match, pick that one -- if npc's faction and proprietor's faction match, pick that one
if npc.object.faction == data.proprietor.object.faction then 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) log(common.logLevels.medium, "Picking %s for %s based on faction", data.cell.id, npc.object.name)
@ -115,8 +55,8 @@ this.pickPublicHouseForNPC = function(npc, city)
end end
-- temple members go to the temple -- temple members go to the temple
if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][publicHouseTypes.temples] then if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][common.publicHouseTypes.temples] then
for _, data in pairs(common.runtimeData.publicHouses[city][publicHouseTypes.temples]) do for _, data in pairs(common.runtimeData.publicHouses[city][common.publicHouseTypes.temples]) do
if npc.object.faction == data.proprietor.object.faction then 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) log(common.logLevels.medium, "Picking temple %s for %s based on faction", data.cell.id, npc.object.name)
return data.cell return data.cell
@ -128,104 +68,6 @@ this.pickPublicHouseForNPC = function(npc, city)
return this.pickInnForNPC(npc, city) return this.pickInnForNPC(npc, city)
end 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 -- looks through doors to find a cell that matches a wandering NPCs name
this.pickHomeForNPC = function(cell, npc) this.pickHomeForNPC = function(cell, npc)
-- wilderness cells don't have name -- wilderness cells don't have name
@ -234,6 +76,9 @@ this.pickHomeForNPC = function(cell, npc)
-- don't move contextual, such as Animated Morrowind, NPCs at all -- 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 for _, str in pairs(contextualNPCs) do if npc.object.id:match(str) then return end end
-- time to pick the "home"
local picked = nil
local name = npc.object.name local name = npc.object.name
local city = common.split(cell.name, ",")[1] local city = common.split(cell.name, ",")[1]
for door in cell:iterateReferences(tes3.objectType.door) do for door in cell:iterateReferences(tes3.objectType.door) do
@ -243,23 +88,34 @@ this.pickHomeForNPC = function(cell, npc)
-- essentially, if npc full name, or surname matches the cell name -- essentially, if npc full name, or surname matches the cell name
if dest.id:match(name) or this.livesInManor(dest.name, name) then 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 if common.runtimeData.homes.byName[name] then -- already have a home, don't create the table entry again
return common.runtimeData.homes.byName[name] picked = common.runtimeData.homes.byName[name]
else else
return this.createHomedNPCTableEntry(npc, dest, cell, true) picked = entry.createHomedNPCTableEntry(npc, dest, cell, true)
end 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, or inside a canton
if config.homelessWanderersToPublicHouses then if config.homelessWanderersToPublicHouses then
log(common.logLevels.medium, "Didn't find a home for %s, trying inns", npc.object.name) log(common.logLevels.medium, "Didn't find a home for %s, trying inns", npc.object.name)
local dest = this.pickPublicHouseForNPC(npc, city) local dest = this.pickPublicHouseForNPC(npc, city)
-- return createHomedNPCTableEntry(npc, dest, door)
if dest then return this.createHomedNPCTableEntry(npc, dest, cell, false) end if dest then picked = entry.createHomedNPCTableEntry(npc, dest, cell, false) end
-- if nothing was found, then we'll settle on Canton works cell, if the cell is a Canton
if checks.isCantonCell(cell) then
if common.runtimeData.publicHouses[city] and common.runtimeData.publicHouses[city][common.publicHouseTypes.cantonworks] then
-- todo: maybe poorer NPCs in canalworks, others in waistworks ?
local canton = table.choice(common.runtimeData.publicHouses[city][common.publicHouseTypes.cantonworks])
log(common.logLevels.medium, "Picking works %s, %s for %s", canton.city, canton.name, npc.object.name)
if canton then picked = entry.createHomedNPCTableEntry(npc, canton.cell, cell, false) end
end
end
end end
return nil return picked
end end
return this return this

View file

@ -3,6 +3,7 @@ local config = require("celediel.NPCsGoHome.config").getConfig()
local checks = require("celediel.NPCsGoHome.functions.checks") local checks = require("celediel.NPCsGoHome.functions.checks")
local interop = require("celediel.NPCsGoHome.interop") local interop = require("celediel.NPCsGoHome.interop")
local housing = require("celediel.NPCsGoHome.functions.housing") local housing = require("celediel.NPCsGoHome.functions.housing")
local entry = require("celediel.NPCsGoHome.functions.entry")
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
@ -16,7 +17,7 @@ this.checkForMovedNPCs = function(cell)
log(common.logLevels.medium, "Looking for moved NPCs in cell %s", cell.id) log(common.logLevels.medium, "Looking for moved NPCs in cell %s", cell.id)
for npc in cell:iterateReferences(tes3.objectType.npc) do for npc in cell:iterateReferences(tes3.objectType.npc) do
if npc.data and npc.data.NPCsGoHome then 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) entry.createHomedNPCTableEntry(npc, cell, tes3.getCell(npc.data.NPCsGoHome.cell), true, npc.data.NPCsGoHome.position, npc.data.NPCsGoHome.orientation)
end end
end end
end end
@ -37,7 +38,7 @@ end
this.moveNPC = function(homeData) this.moveNPC = function(homeData)
-- add to in memory table -- add to in memory table
table.insert(common.runtimeData.movedNPCs, homeData) table.insert(common.runtimeData.movedNPCs, homeData)
interop.setMovedNPCTable(common.runtimeData.movedNPCs) interop.setRuntimeData(common.runtimeData)
-- set npc data, so we can move NPCs back after a load -- set npc data, so we can move NPCs back after a load
local npc = homeData.npc local npc = homeData.npc
@ -83,7 +84,7 @@ this.putNPCsBack = function()
orientation = data.ogPlace orientation = data.ogPlace
}) })
end end
interop.setMovedNPCTable(common.runtimeData.movedNPCs) interop.setRuntimeData(common.runtimeData)
end end
this.processNPCs = function(cell) this.processNPCs = function(cell)

View file

@ -1,18 +1,8 @@
local this = {} local this = {}
-- access to the NPCs that have homes or have been assigned a public house -- access to runtime data
local homedNPCs = {} local runtimeData = {}
this.setHomedNPCTable = function(t) homedNPCs = t end this.setRuntimeData = function(t) runtimeData = t end
this.getHomedNPCTable = function() return homedNPCs end this.getRuntimeData = function() return runtimeData end
-- access to any cells that have been marked public
local pubs = {}
this.setPublicHouseTable = function(t) pubs = t end
this.getPublicHouseTable = function() return pubs end
-- access to NPCs that have been moved
local moved = {}
this.setMovedNPCTable = function(t) moved = t end
this.getMovedNPCTable = function() return moved end
return this return this

View file

@ -46,14 +46,18 @@ local function checkEnteredNPCHome(cell)
end end
local function checkEnteredPublicHouse(cell, city) local function checkEnteredPublicHouse(cell, city)
local typeOfPub = housing.pickPublicHouseType(cell) local typeOfPub = common.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])
if publicHouse then if publicHouse then
local msg = string.format("Entering public space %s, a%s %s in the town of %s. Talk to %s, %s for services.", local msg = string.format("Entering public space %s, a%s %s in the town of %s.",
publicHouse.name, common.vowel(typeOfPub), typeOfPub:gsub("s$", ""), publicHouse.city, publicHouse.name, common.vowel(typeOfPub), typeOfPub:gsub("s$", ""), publicHouse.city)
publicHouse.proprietor.object.name, publicHouse.proprietor.object.class)
if publicHouse.proprietor then
msg = msg .. string.format(" Talk to %s, %s for services.", publicHouse.proprietor.object.name, publicHouse.proprietor.object.class)
end
log(common.logLevels.small, msg) log(common.logLevels.small, msg)
message(msg) -- this one is more informative, and not entirely for debugging, and reminiscent of Daggerfall's messages message(msg) -- this one is more informative, and not entirely for debugging, and reminiscent of Daggerfall's messages
end end
@ -67,7 +71,9 @@ local function applyChanges(cell)
if checks.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 checks.isInteriorCell(cell) and not (config.waistWorks and checks.isCantonCell(cell.id)) then return end if checks.isInteriorCell(cell) and not (config.waistWorks == common.waist.exterior and checks.isCantonWorksCell(cell)) then
return
end
-- don't do anything to public houses -- don't do anything to public houses
if checks.isPublicHouse(cell) then return end if checks.isPublicHouse(cell) then return end
@ -161,15 +167,18 @@ local function onInitialized()
followers = common.runtimeData.followers followers = common.runtimeData.followers
-- Register events -- Register events
log(common.logLevels.small, "Registering 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 log(common.logLevels.none, "Successfully initialized")
event.register("modConfigReady", function() mwse.mcm.register(require("celediel.NPCsGoHome.mcm")) end)
end end
event.register("initialized", onInitialized) event.register("initialized", onInitialized)
-- MCM
event.register("modConfigReady", function() mwse.mcm.register(require("celediel.NPCsGoHome.mcm")) end)
-- }}} -- }}}
-- vim:fdm=marker -- vim:fdm=marker

View file

@ -39,12 +39,12 @@ category:createYesNoButton({
}) })
category:createYesNoButton({ category:createYesNoButton({
label = "Move NPCs with homes instead of disabling them?", label = "Move NPCs into their homes at night and in bad weather instead of disabling them?",
variable = createTableVar("moveNPCs") variable = createTableVar("moveNPCs")
}) })
category:createYesNoButton({ category:createYesNoButton({
label = "Move \"homeless\" NPCs to Inns at night and in bad weather instead of disabling them?", label = "Move \"homeless\" NPCs to public spaces at night and in bad weather instead of disabling them?",
variable = createTableVar("homelessWanderersToPublicHouses") variable = createTableVar("homelessWanderersToPublicHouses")
}) })
@ -53,8 +53,17 @@ category:createYesNoButton({
variable = createTableVar("disableInteraction") variable = createTableVar("disableInteraction")
}) })
category:createYesNoButton({ category:createDropdown({
label = "Treat Canton waistworks and canalworks as exteriors (lock doors and disable NPCs)", label = "Treat Canton waistworks and canalworks as exteriors, public spaces, or neither",
description = "If canton cells are treated as exterior, inside NPCs will be disabled, and doors will be locked.\n" ..
"If they're treated as public spaces, inside NPCs won't be disabled, and homeless NPCs will be moved inside "..
"(if configured to do so).\n\nIf neither, canton cells will be treated as any other.",
options = {
{label = "Neither", value = common.waist.neither},
{label = "Exterior", value = common.waist.exterior},
{label = "Public", value = common.waist.public},
},
defaultSetting = common.waist.neither,
variable = createTableVar("waistWorks") variable = createTableVar("waistWorks")
}) })