From 6dcf8bb4a8de705ca685d6719253f31bd4e4d769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lilian=20J=C3=B3nsd=C3=B3ttir?= Date: Wed, 26 Aug 2020 21:44:56 -0700 Subject: [PATCH] more refactoring, public cells assigned a faction --- MWSE/mods/celediel/NPCsGoHome/common.lua | 39 +++++++++++++- .../NPCsGoHome/functions/cellEvaluators.lua | 52 +++++++++++++++++++ .../celediel/NPCsGoHome/functions/checks.lua | 45 +++++----------- .../NPCsGoHome/functions/dataTables.lua | 26 ++++------ .../celediel/NPCsGoHome/functions/housing.lua | 12 ++--- .../NPCsGoHome/functions/npcEvaluators.lua | 7 ++- .../NPCsGoHome/functions/processors.lua | 1 + MWSE/mods/celediel/NPCsGoHome/main.lua | 2 +- 8 files changed, 125 insertions(+), 59 deletions(-) create mode 100644 MWSE/mods/celediel/NPCsGoHome/functions/cellEvaluators.lua diff --git a/MWSE/mods/celediel/NPCsGoHome/common.lua b/MWSE/mods/celediel/NPCsGoHome/common.lua index c05a13c..95144a8 100644 --- a/MWSE/mods/celediel/NPCsGoHome/common.lua +++ b/MWSE/mods/celediel/NPCsGoHome/common.lua @@ -22,6 +22,16 @@ this.publicHouseTypes = { houses = "Houses", cantonworks = "Cantonworks" } + +-- Canton string matches +-- move NPCs into waistworks +this.waistworks = "[Ww]aistworks" +-- don't lock canalworks +this.canalworks = "[Cc]analworks" +-- doors to underworks should be ignored +-- but NPCs in underworks should not be disabled +this.underworks = "[Uu]nderworks" + -- }}} -- {{{ Filled at runtime @@ -69,13 +79,27 @@ this.vowel = function(str) return n end +-- picks the key of the largest value out of a key:whatever, value:number table +this.keyOfLargestValue = function(t) + local picked + local largest = 0 + for key, value in pairs(t) do + if value > largest then + largest = value + picked = key + end + end + -- return largest == 0 and nil or largest + return picked +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 + elseif cell.id:match(this.canalworks) or cell.id:match(this.waistworks) then return this.publicHouseTypes.cantonworks -- elseif cell.id:match("House") then -- return publicHouseTypes.houses @@ -99,6 +123,19 @@ this.checkModdedCell = function(cellId) return id end + +this.isCantonWorksCell = function(cell) + -- for _, str in pairs(waistworks) do if cell.id:match(str) then return true end end + return cell.id:match(this.waistworks) or cell.id:match(this.canalworks) or cell.id:match(this.underworks) +end + +this.isCantonCell = function(cell) + if cell.isInterior and not cell.behavesAsExterior 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 +end -- }}} return this diff --git a/MWSE/mods/celediel/NPCsGoHome/functions/cellEvaluators.lua b/MWSE/mods/celediel/NPCsGoHome/functions/cellEvaluators.lua new file mode 100644 index 0000000..686b9d1 --- /dev/null +++ b/MWSE/mods/celediel/NPCsGoHome/functions/cellEvaluators.lua @@ -0,0 +1,52 @@ +local common = require("celediel.NPCsGoHome.common") +local config = require("celediel.NPCsGoHome.config").getConfig() +local npcEvaluators = require("celediel.NPCsGoHome.functions.npcEvaluators") + +local this = {} + +-- todo: logging +local function log(level, ...) if config.logLevel >= level then common.log(...) end end + +this.calculateCellWorth = function(cell, proprietor) + -- cell worth is combined worth of all NPCs + local worth = 0 + + for innard in cell:iterateReferences(tes3.objectType.npc) do + worth = worth + npcEvaluators.calculateNPCWorth(innard, innard == proprietor and cell or nil).total + end + + return worth +end + +this.pickCellFaction = function(cell) + -- iterate NPCs in the cell, if 2/3 the population is any one faction, + -- that's the cell's faction, otherwise, cell doesn't have a faction. + local npcs = {majorityFactions = {}, allFactions = {}, total = 0} + for npc in cell:iterateReferences(tes3.objectType.npc) do + local faction = npc.object.faction + + if faction then + if not npcs.allFactions[faction] then npcs.allFactions[faction] = {total = 0, percentage = 0} end + + if not npcs.allFactions[faction].master or npcs.allFactions[faction].master.object.factionIndex < + npc.object.factionIndex then npcs.allFactions[faction].master = npc end + + npcs.allFactions[faction].total = npcs.allFactions[faction].total + 1 + end + + npcs.total = npcs.total + 1 + end + + for faction, info in pairs(npcs.allFactions) do + info.percentage = (info.total / npcs.total) * 100 + if info.percentage >= config.factionIgnorePercentage then + -- return faction.id + npcs.majorityFactions[faction] = info.percentage + end + end + + -- no faction + return table.empty(npcs.majorityFactions) and "none" or common.keyOfLargestValue(npcs.majorityFactions) +end + +return this diff --git a/MWSE/mods/celediel/NPCsGoHome/functions/checks.lua b/MWSE/mods/celediel/NPCsGoHome/functions/checks.lua index 73c475a..f891d3e 100644 --- a/MWSE/mods/celediel/NPCsGoHome/functions/checks.lua +++ b/MWSE/mods/celediel/NPCsGoHome/functions/checks.lua @@ -2,16 +2,9 @@ local common = require("celediel.NPCsGoHome.common") local config = require("celediel.NPCsGoHome.config").getConfig() local dataTables = require("celediel.NPCsGoHome.functions.dataTables") +local cellEvaluators = require("celediel.NPCsGoHome.functions.cellEvaluators") -- {{{ local variables and such --- Canton string matches --- move NPCs into waistworks -local waistworks = "[Ww]aistworks" --- don't lock canalworks -local canalworks = "[Cc]analworks" --- 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" @@ -22,7 +15,7 @@ local function log(level, ...) if config.logLevel >= level then common.log(...) -- patented by Merlord local yeet = function(reference) - -- tes3.positionCell({reference = reference, position = {0, 0, 10000}}) + tes3.positionCell({reference = reference, position = {0, 0, 10000}}) reference:disable() timer.delayOneFrame(function() mwscript.setDelete({reference = reference}) end) end @@ -35,10 +28,10 @@ local function getFightFromSpawnedReference(id) local ref = tes3.createReference({ object = id, - -- cell = toddTest, - cell = tes3.getPlayerCell(), - -- position = zeroVector, - position = {0, 0, 10000}, + cell = toddTest, + -- cell = tes3.getPlayerCell(), + position = tes3vector3.new(0, 0, 0), + -- position = {0, 0, 10000}, orientation = tes3vector3.new(0, 0, 0) }) @@ -97,19 +90,6 @@ this.isIgnoredCell = function(cell) return config.ignored[cell.id] or config.ignored[cell.sourceMod] -- or wilderness end -this.isCantonWorksCell = function(cell) - -- 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 -end - -- ! this one depends on tes3 ! -- this.fargothCheck = function() local fargothJournal = tes3.getJournalIndex({id = "MS_Lookout"}) @@ -186,6 +166,8 @@ this.isPublicHouse = function(cell) if not this.isInteriorCell(cell) then return false end -- gather some data about the cell + local worth = cellEvaluators.calculateCellWorth(cell) + local faction = cellEvaluators.pickCellFaction(cell) local typeOfPub = common.pickPublicHouseType(cell) local city, publicHouseName @@ -204,8 +186,8 @@ this.isPublicHouse = function(cell) 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 - dataTables.createPublicHouseTableEntry(cell, nil, city, publicHouseName) + if config.waistWorks == common.waist.public and cell.id:match(common.waistworks) then + dataTables.createPublicHouseTableEntry(cell, nil, city, publicHouseName, worth, faction) return true end @@ -217,8 +199,7 @@ this.isPublicHouse = function(cell) 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) - dataTables.createPublicHouseTableEntry(cell, npc, city, publicHouseName) - + dataTables.createPublicHouseTableEntry(cell, npc, city, publicHouseName, worth, faction) return true end @@ -251,7 +232,7 @@ this.isPublicHouse = function(cell) config.factionIgnorePercentage then 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, worth, faction) return true end end @@ -287,7 +268,7 @@ this.isIgnoredDoor = function(door, homeCellId) end -- don't lock doors to canton cells - local isCantonWorks = this.isCantonWorksCell(dest) + local isCantonWorks = common.isCantonWorksCell(dest) 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 diff --git a/MWSE/mods/celediel/NPCsGoHome/functions/dataTables.lua b/MWSE/mods/celediel/NPCsGoHome/functions/dataTables.lua index fc8d133..02cd802 100644 --- a/MWSE/mods/celediel/NPCsGoHome/functions/dataTables.lua +++ b/MWSE/mods/celediel/NPCsGoHome/functions/dataTables.lua @@ -3,7 +3,7 @@ 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 evaluators = require("celediel.NPCsGoHome.functions.npcEvaluators") +local npcEvaluators = require("celediel.NPCsGoHome.functions.npcEvaluators") local zeroVector = tes3vector3.new(0, 0, 0) local function log(level, ...) if config.logLevel >= level then common.log(...) end end @@ -13,18 +13,20 @@ local this = {} 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 = common.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) + -- pick the position and orientation the NPC will be placed at + local pickedPosition, pickedOrientation, pos, ori + if isHome and positions.npcs[npc.object.name] then pos = positions.npcs[npc.object.name].position ori = positions.npcs[npc.object.name].orientation elseif not table.empty(common.runtimeData.positions[id]) then + -- pick a random position out of the positions in memory local choice, index = table.choice(common.runtimeData.positions[id]) pos = choice.position ori = choice.orientation @@ -57,7 +59,7 @@ this.createHomedNPCTableEntry = function(npc, home, startingPlace, isHome, posit ogOrientation = ogOrientation, -- tes3vector3 homePosition = pickedPosition, -- tes3vector3 homeOrientation = pickedOrientation, -- tes3vector3 - worth = evaluators.calculateNPCWorth(npc) -- int + worth = npcEvaluators.calculateNPCWorth(npc) -- int } common.runtimeData.homes.byName[npc.object.name] = entry @@ -68,20 +70,9 @@ this.createHomedNPCTableEntry = function(npc, home, startingPlace, isHome, posit return entry end -this.createPublicHouseTableEntry = function(publicCell, proprietor, city, name) +this.createPublicHouseTableEntry = function(publicCell, proprietor, city, name, cellWorth, cellFaction) 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 + evaluators.calculateNPCWorth(innard, publicCell).total - else - worth = worth + evaluators.calculateNPCWorth(innard).total - end - end - local proprietorName = proprietor and proprietor.object.name or "no one" if not common.runtimeData.publicHouses[city] then common.runtimeData.publicHouses[city] = {} end @@ -96,7 +87,8 @@ this.createPublicHouseTableEntry = function(publicCell, proprietor, city, name) cell = publicCell, proprietor = proprietor, proprietorName = proprietorName, - worth = worth + worth = cellWorth, + faction = cellFaction } interop.setRuntimeData(common.runtimeData) diff --git a/MWSE/mods/celediel/NPCsGoHome/functions/housing.lua b/MWSE/mods/celediel/NPCsGoHome/functions/housing.lua index 2ec62f8..6870596 100644 --- a/MWSE/mods/celediel/NPCsGoHome/functions/housing.lua +++ b/MWSE/mods/celediel/NPCsGoHome/functions/housing.lua @@ -1,7 +1,8 @@ --- handles finding homes or picking public spaces NPCs +-- handles finding homes or picking public spaces for NPCs +-- * home cell is chosen according to the following order: +-- * NPC's actual home, i.e.: home cell name contains NPC name local common = require("celediel.NPCsGoHome.common") local config = require("celediel.NPCsGoHome.config").getConfig() -local checks = require("celediel.NPCsGoHome.functions.checks") local dataTables = require("celediel.NPCsGoHome.functions.dataTables") local function log(level, ...) if config.logLevel >= level then common.log(...) end end @@ -31,8 +32,6 @@ this.pickInnForNPC = function(npc, city) -- 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][common.publicHouseTypes.inns] then @@ -81,6 +80,8 @@ this.pickHomeForNPC = function(cell, npc) -- time to pick the "home" local name = npc.object.name local city = common.split(cell.name, ",")[1] + + -- check if the NPC already has a house for door in cell:iterateReferences(tes3.objectType.door) do if door.destination then local dest = door.destination.cell @@ -102,10 +103,9 @@ this.pickHomeForNPC = function(cell, npc) if dest then return dataTables.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.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) diff --git a/MWSE/mods/celediel/NPCsGoHome/functions/npcEvaluators.lua b/MWSE/mods/celediel/NPCsGoHome/functions/npcEvaluators.lua index 16d1552..9fc24b7 100644 --- a/MWSE/mods/celediel/NPCsGoHome/functions/npcEvaluators.lua +++ b/MWSE/mods/celediel/NPCsGoHome/functions/npcEvaluators.lua @@ -1,6 +1,9 @@ --- handles evalutating NPCs +-- handles evaluating NPCs local this = {} +-- todo: logging +local function log(level, ...) if config.logLevel >= level then common.log(...) end end + -- NPCs barter gold + value of all inventory items this.calculateNPCWorth = function(npc, merchantCell) -- start with this @@ -33,7 +36,7 @@ this.calculateNPCWorth = function(npc, merchantCell) end end - -- caculate the total + -- calculate the total local total = 0 for _, v in pairs(worth) do total = total + v end diff --git a/MWSE/mods/celediel/NPCsGoHome/functions/processors.lua b/MWSE/mods/celediel/NPCsGoHome/functions/processors.lua index 21b1c83..c3dde05 100644 --- a/MWSE/mods/celediel/NPCsGoHome/functions/processors.lua +++ b/MWSE/mods/celediel/NPCsGoHome/functions/processors.lua @@ -11,6 +11,7 @@ local function log(level, ...) if config.logLevel >= level then common.log(...) local this = {} +-- create an in memory list of positions for a cell, to ensure multiple NPCs aren't placed in the same spot this.updatePositions = function(cell) local id = cell.id -- update runtime positions in cell, but don't overwrite loaded positions diff --git a/MWSE/mods/celediel/NPCsGoHome/main.lua b/MWSE/mods/celediel/NPCsGoHome/main.lua index c701efd..23b1730 100644 --- a/MWSE/mods/celediel/NPCsGoHome/main.lua +++ b/MWSE/mods/celediel/NPCsGoHome/main.lua @@ -1,5 +1,5 @@ -- {{{ other files --- ? could probably split this file out to others as well +-- todo: too many things require too many other things, needs fix local config = require("celediel.NPCsGoHome.config").getConfig() local common = require("celediel.NPCsGoHome.common") local checks = require("celediel.NPCsGoHome.functions.checks")