use new MWSE aiConfig; imperial carriages; some minor refactoring
This commit is contained in:
parent
20754b63f4
commit
57399d5da2
|
@ -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
|
||||||
|
|
|
@ -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}))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue