use new MWSE aiConfig; imperial carriages; some minor refactoring

This commit is contained in:
Lilian Jónsdóttir 2020-09-06 15:43:25 -07:00
parent 20754b63f4
commit 57399d5da2
4 changed files with 66 additions and 61 deletions

View file

@ -7,6 +7,7 @@ this.version = "0.0.1"
this.modInfo = "Move NPCs to their homes, or public houses (or just disable them), lock doors, " .. this.modInfo = "Move NPCs to their homes, or public houses (or just disable them), lock doors, " ..
"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"
this.logString = this.modName:gsub("%s?%b()%s?","")
-- for config -- for config
this.logLevels = {none = 0, small = 1, medium = 2, large = 3} this.logLevels = {none = 0, small = 1, medium = 2, large = 3}
@ -45,6 +46,8 @@ this.runtimeData = {
}, },
-- NPCs who have been moved -- NPCs who have been moved
movedNPCs = {}, movedNPCs = {},
-- NPCs who have been disabled
disabledNPCs = {},
-- positions that haven't been used -- positions that haven't been used
positions = {}, positions = {},
-- player companions -- player companions
@ -61,10 +64,10 @@ this.split = function(input, sep)
return output return output
end end
this.log = function(...) mwse.log("[%s] %s", this.modName, string.format(...)) end this.log = function(...) mwse.log("[%s] %s", this.logString, string.format(...)) end
this.vowel = function(str) this.vowel = function(word)
local s = string.sub(str, 1, 1) local s = string.sub(word, 1, 1)
local n = "" local n = ""
if string.match(s, "[AOEUIaoeui]") then n = "n" end if string.match(s, "[AOEUIaoeui]") then n = "n" end

View file

@ -52,7 +52,7 @@ this.pickCellFaction = function(cell)
end end
end end
-- from the majority values, return the faction with the largest percentage, or "none" -- from the majority values, return the faction with the largest percentage, or nil
local picked = common.keyOfLargestValue(npcs.majorityFactions) local picked = common.keyOfLargestValue(npcs.majorityFactions)
log(common.logLevels.medium, "Picked faction %s for cell %s", picked, cell.id) log(common.logLevels.medium, "Picked faction %s for cell %s", picked, cell.id)
log(common.logLevels.large, "breakdown:\n%s", json.encode(npcs, {indent = true})) log(common.logLevels.large, "breakdown:\n%s", json.encode(npcs, {indent = true}))

View file

@ -102,6 +102,14 @@ this.fargothCheck = function()
return fargothJournal > 10 and fargothJournal <= 30 return fargothJournal > 10 and fargothJournal <= 30
end end
this.offersTravel = function(npc)
if not npc.object.aiConfig.travelDestinations then return false end
for _ in tes3.iterate(npc.object.aiConfig.travelDestinations) do return true end
return false
end
this.isIgnoredNPC = function(npc) this.isIgnoredNPC = function(npc)
local obj = npc.baseObject and npc.baseObject or npc.object local obj = npc.baseObject and npc.baseObject or npc.object
@ -129,33 +137,37 @@ this.isIgnoredNPC = function(npc)
local isWerewolf = mwscript.getSpellEffects({reference = npc, spell = "werewolf vision"}) local isWerewolf = mwscript.getSpellEffects({reference = npc, spell = "werewolf vision"})
-- local isVampire = mwscript.getSpellEffects({reference = npc, spell = "vampire sun damage"}) -- local isVampire = mwscript.getSpellEffects({reference = npc, spell = "vampire sun damage"})
-- LuaFormatter off
-- this just keeps getting uglier but it's debug logging so whatever I don't care -- this just keeps getting uglier but it's debug logging so whatever I don't care
log(common.logLevels.large, ("Checking NPC:%s (%s or %s): id blocked:%s, %s blocked:%s " .. -- log(common.logLevels.large, ("Checking NPC:%s (%s or %s): id blocked:%s, %s blocked:%s " ..
"guard:%s dead:%s vampire:%s werewolf:%s dreamer:%s follower:%s hostile:%s %s%s"), -- "guard:%s dead:%s vampire:%s werewolf:%s dreamer:%s follower:%s hostile:%s %s%s"),
obj.name, npc.object.id, npc.object.baseObject and npc.object.baseObject.id or "nil", -- obj.name, npc.object.id, npc.object.baseObject and npc.object.baseObject.id or "nil",
config.ignored[obj.id:lower()], obj.sourceMod, config.ignored[obj.sourceMod:lower()], -- config.ignored[obj.id:lower()], obj.sourceMod, config.ignored[obj.sourceMod:lower()],
isGuard, isDead, isVampire, isWerewolf, (obj.class and obj.class.id == "Dreamers"), -- 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 "", -- common.runtimeData.followers[npc.object.id], isHostile, obj.id:match("fargoth") and "fargoth:" or "",
obj.id:match("fargoth") and isFargothActive or "") obj.id:match("fargoth") and isFargothActive or "")
return config.ignored[obj.id:lower()] or -- return config.ignored[obj.id:lower()] or
config.ignored[obj.sourceMod:lower()] or -- config.ignored[obj.sourceMod:lower()] or
isGuard or -- isGuard or
isFargothActive or -- isFargothActive or
isDead or -- don't move dead NPCS isDead or
isHostile or -- isHostile or
common.runtimeData.followers[npc.object.id] or -- ignore followers common.runtimeData.followers[npc.object.id] or
isVampire or -- isVampire or
isWerewolf or -- isWerewolf or
(obj.class and obj.class.id == "Dreamers") -- (obj.class and obj.class.id == "Dreamers")
-- LuaFormatter on
end end
this.isNPCPet = function(creature) this.isNPCPet = function(creature) -- > isPet, isLinkedToTravelNPC
local obj = creature.baseObject and creature.baseObject or creature.object local obj = creature.baseObject and creature.baseObject or creature.object
-- todo: more pets? -- todo: more pets?
if obj.id:match("guar") and obj.mesh:match("pack") then if obj.id:match("guar") and obj.mesh:match("pack") then
return true return true
elseif obj.id:match("_[Hh]rs") and obj.mesh:match("_[Hh]orse") then
return true, true
else else
return false return false
end end
@ -230,10 +242,13 @@ this.isPublicHouse = function(cell)
cell.name, faction, config.ignored[faction.id], faction.playerJoined, info.total, info.percentage, cell.name, faction, config.ignored[faction.id], faction.playerJoined, info.total, info.percentage,
npcs.total) npcs.total)
-- less than 3 NPCs can't possibly be a public house unless it's a Blades house -- log(common.logLevels.large, "ignored or joined:%s, occupants or blades:%s, faction percent:%s", (config.ignored[faction.id] or faction.playerJoined),
-- (npcs.total >= config.minimumOccupancy or faction == "Blades"), (info.percentage >= config.factionIgnorePercentage))
-- less than configured amount of NPCs can't be a public house unless it's a Blades house
if (config.ignored[faction.id] or faction.playerJoined) and if (config.ignored[faction.id] or faction.playerJoined) and
(npcs.total >= config.minimumOccupancy or faction == "Blades") and info.percentage >= (npcs.total >= config.minimumOccupancy or faction == "Blades") and
config.factionIgnorePercentage then (info.percentage >= 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)
dataTables.createPublicHouseTableEntry(cell, npcs.factions[faction].master, city, publicHouseName, dataTables.createPublicHouseTableEntry(cell, npcs.factions[faction].master, city, publicHouseName,
@ -286,35 +301,33 @@ this.isIgnoredDoor = function(door, homeCellId)
end end
-- AT NIGHT -- AT NIGHT
this.checkTime = function() this.isNight = function()
local night = tes3.worldController.hour.value >= config.closeTime or tes3.worldController.hour.value <= config.openTime local atNight = tes3.worldController.hour.value >= config.closeTime or tes3.worldController.hour.value <= config.openTime
log(common.logLevels.large, "Current time is %.2f (%snight), things are closed between %s and %s", log(common.logLevels.large, "Current time is %.2f (%snight), things are closed between %s and %s",
tes3.worldController.hour.value, night and "" or "not ", config.closeTime, config.openTime) tes3.worldController.hour.value, atNight and "" or "not ", config.closeTime, config.openTime)
return night
return atNight
end end
-- inclement weather -- inclement weather
this.checkWeather = function(cell) this.isInclementWeather = function(cell)
if not cell.region then return end if not cell.region then return false end
-- local index = cell.region.weather.index
local index = tes3.getCurrentWeather().index
local isBad = index >= config.worstWeather
log(common.logLevels.large, "Weather: current:%s >= configured worst:%s == %s", cell.region.weather.index, log(common.logLevels.large, "Weather in %s: current:%s >= configured worst:%s == %s", cell.id, index,
config.worstWeather, cell.region.weather.index >= config.worstWeather) config.worstWeather, isBad)
return cell.region.weather.index >= config.worstWeather return isBad
end end
-- travel agents, their steeds, and argonians stick around -- travel agents, their steeds, and argonians stick around
this.isBadWeatherNPC = function(npc) this.isBadWeatherNPC = function(npc)
local obj = npc.baseObject and npc.baseObject or npc.object log(common.logLevels.large, "NPC Inclement Weather: %s is %s%s", npc.object.name, npc.object.race.id,
if not obj then return end this.offersTravel(npc) and ", travel agent" or "")
log(common.logLevels.large, "NPC Inclement Weather: %s is %s, %s", npc.object.name, npc.object.class.name, return this.offersTravel(npc) or npc.object.race.id == "Argonian"
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 end
return this return this

View file

@ -11,9 +11,6 @@ local processors = require("celediel.NPCsGoHome.functions.processors")
-- timers -- timers
local updateTimer local updateTimer
-- references to common.runtimeData
local publicHouses, homes, movedNPCs, followers
-- }}} -- }}}
-- {{{ helper functions -- {{{ helper functions
@ -36,7 +33,7 @@ end
-- {{{ cell change checks -- {{{ cell change checks
local function checkEnteredNPCHome(cell) local function checkEnteredNPCHome(cell)
local home = homes.byCell[cell.id] local home = common.runtimeData.homes.byCell[cell.id]
if home then if home then
local msg = string.format("Entering home of %s, %s", home.name, home.homeName) local msg = string.format("Entering home of %s, %s", home.name, home.homeName)
log(common.logLevels.small, msg) log(common.logLevels.small, msg)
@ -47,8 +44,8 @@ end
local function checkEnteredPublicHouse(cell, city) local function checkEnteredPublicHouse(cell, city)
local typeOfPub = common.pickPublicHouseType(cell) local typeOfPub = common.pickPublicHouseType(cell)
local publicHouse = publicHouses[city] and local publicHouse = common.runtimeData.publicHouses[city] and
(publicHouses[city][typeOfPub] and publicHouses[city][typeOfPub][cell.name]) (common.runtimeData.publicHouses[city][typeOfPub] and common.runtimeData.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.", publicHouse.name, local msg = string.format("Entering public space %s, a%s %s in the town of %s.", publicHouse.name,
@ -91,7 +88,7 @@ end
local function updateCells() local function updateCells()
log(common.logLevels.medium, "Updating active cells!") log(common.logLevels.medium, "Updating active cells!")
followers = buildFollowerList() common.runtimeData.followers = buildFollowerList()
processors.searchCellsForPositions() processors.searchCellsForPositions()
for _, cell in pairs(tes3.getActiveCells()) do for _, cell in pairs(tes3.getActiveCells()) do
@ -106,7 +103,7 @@ 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 checks.isInteriorCell(cell) and not checks.isIgnoredCell(cell) and not checks.isPublicHouse(cell) and inCity then if checks.isInteriorCell(cell) and not checks.isIgnoredCell(cell) and not checks.isPublicHouse(cell) and inCity then
if checks.checkTime() then if checks.isNight() 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
@ -133,11 +130,9 @@ end
local function onLoaded() 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 {}
-- movedNPCs = tes3.player.data.NPCsGoHome.movedNPCs or {}
if tes3.player.cell then processors.searchCellsForNPCs() end if tes3.player.cell then processors.searchCellsForNPCs() end
followers = buildFollowerList() common.runtimeData.followers = buildFollowerList()
if not updateTimer or (updateTimer and updateTimer.state ~= timer.active) then if not updateTimer or (updateTimer and updateTimer.state ~= timer.active) then
updateTimer = timer.start({ updateTimer = timer.start({
@ -151,7 +146,7 @@ end
local function onCellChanged(e) local function onCellChanged(e)
updateCells() updateCells()
followers = buildFollowerList() common.runtimeData.followers = buildFollowerList()
updatePlayerTrespass(e.cell, e.previousCell) updatePlayerTrespass(e.cell, e.previousCell)
checkEnteredNPCHome(e.cell) checkEnteredNPCHome(e.cell)
if e.cell.name then -- exterior wilderness cells don't have name if e.cell.name then -- exterior wilderness cells don't have name
@ -166,7 +161,7 @@ local function onKeyDown(e)
-- log(common.logLevels.small, common.inspect(common.runtimeData)) -- log(common.logLevels.small, common.inspect(common.runtimeData))
log(common.logLevels.none, json.encode(common.runtimeData, { indent = true })) log(common.logLevels.none, json.encode(common.runtimeData, { indent = true }))
end end
-- if ctrl log position data formattet for positions.lua -- if ctrl log position data formatted for positions.lua
if e.isControlDown then if e.isControlDown then
log(common.logLevels.none, "{position = %s, orientation = %s,", tes3.player.position, tes3.player.orientation) log(common.logLevels.none, "{position = %s, orientation = %s,", tes3.player.position, tes3.player.orientation)
end end
@ -176,12 +171,6 @@ end
-- {{{ init -- {{{ init
local function onInitialized() 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 -- Register events
log(common.logLevels.small, "Registering events...") log(common.logLevels.small, "Registering events...")
event.register("loaded", onLoaded) event.register("loaded", onLoaded)