mirror of
https://github.com/ornfelt/azerothcore_lua_scripts
synced 2026-08-22 06:26:03 -04:00
500 lines
14 KiB
Lua
500 lines
14 KiB
Lua
--[[
|
|
symbol group name parser field
|
|
------ ---------- ------------
|
|
s syllablesStart parser_data["start"]
|
|
m syllablesMiddle parser_data["middle"]
|
|
e syllablesEnd parser_data["end"]
|
|
P syllablesPre parser_data["pre"]
|
|
p syllablesPost parser_data["post"]
|
|
v phonemesVocals parser_data["vocals"]
|
|
c phonemesConsonants parser_data["consonants"]
|
|
A customGroupA parser_data["cga"]
|
|
B customGroupB parser_data["cgb"]
|
|
... (custom groups exist from `A` to `O`)
|
|
N customGroupN parser_data["cgn"]
|
|
O customGroupO parser_data["cgo"]
|
|
? phonemesVocals/ parser_data["vocals"]/
|
|
phonemesConsonants parser_data["consonants"]
|
|
]]
|
|
local PATH = string.match(debug.getinfo(1,"S").source,
|
|
"^@(.+/)[%a%-%d_]+%.lua$") or "./"
|
|
|
|
local function loadrequire(module)
|
|
local loaded
|
|
local function requiref(module)
|
|
loaded = require(module)
|
|
end
|
|
pcall(requiref, module)
|
|
return loaded
|
|
end
|
|
|
|
local RangedTable = require("namegen.rangedtable")
|
|
|
|
-- ======================
|
|
-- VARIABLES
|
|
-- ======================
|
|
|
|
-- the table containing the generators
|
|
local namegen_generators_list = {}
|
|
local namegen = {}
|
|
|
|
-- ======================
|
|
-- UTILITIES
|
|
-- ======================
|
|
|
|
local function file_exists(name)
|
|
local f=io.open(name,"r")
|
|
if f~=nil then io.close(f) return true else return false end
|
|
end
|
|
|
|
local function get_path(filename)
|
|
if filename:find("%.cfg$") then
|
|
return PATH .. filename
|
|
else
|
|
return PATH .. filename .. ".cfg"
|
|
end
|
|
end
|
|
|
|
local function split(str)
|
|
local t = {}
|
|
for v in string.gmatch(str:gsub(",", ""), "%S+") do
|
|
t[#t + 1] = v
|
|
end
|
|
return t
|
|
end
|
|
|
|
local function ng_debug(str, ...)
|
|
if DEBUG == true then
|
|
if ... then
|
|
print(string.format(str, ...))
|
|
else
|
|
print(str)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- ======================
|
|
-- PARSERS
|
|
-- ======================
|
|
|
|
local function parse_rules(rules)
|
|
local rules_weight = {}
|
|
local rule_pattern = [[^%%*(%d*)(%S+)]]
|
|
for _, v in pairs(rules) do
|
|
local chance, rule = string.match(v, rule_pattern)
|
|
ng_debug([[chance: "%s", rule: "%s"]], chance, rule)
|
|
chance = chance == "" and 100 or tonumber(chance)
|
|
rules_weight[#rules_weight + 1] = {chance, rule}
|
|
end
|
|
return RangedTable(rules_weight)
|
|
end
|
|
|
|
local function parse_property(name, value, parser_data)
|
|
local v = name == "name" and value or split(value)
|
|
if name == "name" then
|
|
ng_debug([[parse_property: "%s"]], value)
|
|
parser_data["name"] = v
|
|
elseif name == "syllablesStart" then
|
|
parser_data["start"] = v
|
|
elseif name == "syllablesMiddle" then
|
|
parser_data["middle"] = v
|
|
elseif name == "syllablesEnd" then
|
|
parser_data["end"] = v
|
|
elseif name =="syllablesPre" then
|
|
parser_data["pre"] = v
|
|
elseif name =="syllablesPost" then
|
|
parser_data["post"] = v
|
|
elseif name == "phonemesVocals" then
|
|
parser_data["vocals"] = v
|
|
elseif name =="phonemesConsonants" then
|
|
parser_data.consonants = v
|
|
elseif name == "rules" then
|
|
parser_data["rules"] = parse_rules(v)
|
|
elseif name == "illegal" then
|
|
-- /* illegal strings are converted to lowercase */
|
|
parser_data.illegal = split(string.lower(value))
|
|
else
|
|
local cg = string.match(name, "^customGroup(%a)")
|
|
if cg then
|
|
parser_data["cg" .. cg:lower()] = v
|
|
else
|
|
ng_debug("parse_property else")
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function parse_lines(path, data)
|
|
ng_debug("starting `parse_lines`", path)
|
|
local data
|
|
local name_pattern = [[name ?"(.+)" ?{]]
|
|
local property_pattern = [[ +(.+) = "(.+)"]]
|
|
local end_body_pattern = [[}]]
|
|
|
|
local body = false
|
|
for line in io.lines(path) do
|
|
local name = string.match(line, name_pattern)
|
|
if name ~= nil then
|
|
namegen_generators_list[name] = {}
|
|
data = namegen_generators_list[name]
|
|
parse_property("name", name, data)
|
|
body = true
|
|
elseif body == true then
|
|
if string.match(line, end_body_pattern) then
|
|
body = false
|
|
else
|
|
local name, value = string.match(line, property_pattern)
|
|
if name and value then
|
|
parse_property(name, value, data)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
ng_debug("ending `parse_lines`")
|
|
end
|
|
|
|
local function parse_file(filename)
|
|
ng_debug("starting `parse_file`")
|
|
local path = get_path(filename)
|
|
if not file_exists(path) then
|
|
error(string.format("File \"%s\" not found!\n",path))
|
|
end
|
|
parse_lines(get_path(filename))
|
|
ng_debug("ending `parse_file`")
|
|
end
|
|
|
|
local function parse_index()
|
|
ng_debug("starting `parse_index`")
|
|
parse_file("itemdefinitions.cfg")
|
|
ng_debug("ending `parse_index`")
|
|
end
|
|
|
|
|
|
-- ======================
|
|
-- WORD VALIDATION
|
|
-- ======================
|
|
|
|
-- check for occurrences of triple characters (case-insensitive)
|
|
local function word_has_triples(str)
|
|
local str = str:lower()
|
|
for i = 1, #str - 2 do
|
|
local a = str:sub(i, i)
|
|
local b = str:sub(i+1, i+1)
|
|
local c = str:sub(i+2, i+2)
|
|
if a == b and a == c then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- check for occurrences of illegal strings (case-insensitive)
|
|
local function word_has_illegal(data, str)
|
|
local str = str:lower()
|
|
|
|
if not data.illegal then return false end
|
|
|
|
for i = 1, #data.illegal do
|
|
if str:find(data.illegal[i]) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- check for repeated syllables (case-insensitive)
|
|
local function word_repeated_syllables(str)
|
|
local word = str:lower():gsub("['%-_]", "")
|
|
|
|
for step = 2, math.min(5, math.floor(#str / 2)) do
|
|
for i = 1, #word - step + 1 do
|
|
local search = word:sub(i, i + step - 1)
|
|
local sub = word:sub(i + step, (i + step) + step - 1)
|
|
if search == sub then
|
|
return true
|
|
end
|
|
ng_debug("not repeatted", str, step, search, sub)
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- verify if the word passes the above checks
|
|
local function word_is_ok(data, str)
|
|
return ((#str > 0) and
|
|
not word_has_triples(str) and
|
|
not word_has_illegal(data, str) and
|
|
not word_repeated_syllables(str))
|
|
end
|
|
|
|
-- removes double, leading and ending spaces
|
|
local function word_prune_spaces(str)
|
|
str = str:gsub(" +$", "")
|
|
str = str:gsub("^ +", "")
|
|
str = str:gsub(" +", " ")
|
|
return str
|
|
end
|
|
|
|
local function get_lst_from_token(token, data)
|
|
if token == 'P' then
|
|
ng_debug("token case 1")
|
|
return data["pre"]
|
|
elseif token == 's' then
|
|
ng_debug("token case 2")
|
|
return data["start"]
|
|
elseif token == 'm' then
|
|
ng_debug("token case 3")
|
|
return data["middle"]
|
|
elseif token == 'e' then
|
|
ng_debug("token case 4")
|
|
return data["end"]
|
|
elseif token == 'p' then
|
|
ng_debug("token case 5")
|
|
return data["post"]
|
|
elseif token == 'v' then
|
|
ng_debug("token case 6")
|
|
return data["vocals"]
|
|
elseif token == 'c' then
|
|
ng_debug("token case 7")
|
|
return data["consonants"]
|
|
elseif token == '?' then
|
|
ng_debug("token case 8")
|
|
return ((random(1, 2) == 1) and data.vocals or
|
|
data.consonants)
|
|
elseif token >= "A" and token < "P" then
|
|
ng_debug("token case 9")
|
|
return data["cg" .. token:lower()]
|
|
elseif token == "'" then
|
|
ng_debug("token case 10")
|
|
return {"'"}
|
|
end
|
|
end
|
|
|
|
local function generate_custom(name, rule)
|
|
local random = math.random
|
|
|
|
local data = namegen_generators_list[name]
|
|
if data == nil then
|
|
error(string.format("The name \"%s\" has not been found.\n",name))
|
|
end
|
|
|
|
-- start name generation
|
|
local buf, i, it
|
|
repeat
|
|
buf = ""
|
|
i = 1
|
|
while i <= #rule do
|
|
it = rule:sub(i, i)
|
|
-- append normal character
|
|
if ((it >= 'a' and it <= 'z') or
|
|
(it >= 'A' and it <= 'Z') or
|
|
it == '\'' or it == '-')
|
|
then
|
|
ng_debug("buf case 1")
|
|
buf = buf .. it
|
|
elseif it == '/' then
|
|
-- special character
|
|
i = i + 1
|
|
ng_debug("buf case 2")
|
|
buf = buf .. it
|
|
elseif it == '_' then
|
|
-- convert underscore to space
|
|
ng_debug("buf case 3")
|
|
buf = buf .. " "
|
|
-- interpret a wildcard
|
|
elseif it == '$' then
|
|
local chance = 100;
|
|
i = i + 1
|
|
local it = rule:sub(i, i)
|
|
ng_debug("buf case 4, it: %s", it)
|
|
-- food for the randomiser
|
|
if it >= '0' and it <= '9' then
|
|
ng_debug("buf case 4.1")
|
|
chance = 0
|
|
while it >= '0' and it <= '9' do
|
|
chance = chance * 10
|
|
chance = chance + tonumber(it)
|
|
i = i + 1
|
|
it = rule:sub(i, i);
|
|
end
|
|
end
|
|
-- evaluate the wildcard according to its chance
|
|
if chance >= random(100) then
|
|
ng_debug("buf case 4.2")
|
|
local lst = get_lst_from_token(it, data)
|
|
if lst == nil then
|
|
error(string.format(
|
|
[[Wrong rules syntax(it:"%s", rule:"%s")]],
|
|
it, rule))
|
|
end
|
|
-- got the list, now choose something on it
|
|
if #lst == 0 then
|
|
error(string.format(
|
|
"No data found in the requested string (wildcard %s). Check your name generation rule %s.",
|
|
it,rule
|
|
))
|
|
else
|
|
buf = buf .. (lst[random(1, #lst)]:gsub('_', ' '))
|
|
end
|
|
end
|
|
end
|
|
ng_debug([[i: %d, buf: %s, it: %s, rule: %s]],
|
|
i, buf, it, rule)
|
|
i = i + 1
|
|
end
|
|
until word_is_ok(data, buf)
|
|
ng_debug([[ending "generate_custom 'repeat ... until'"]])
|
|
|
|
-- prune undesired spaces and return the name
|
|
return word_prune_spaces(buf)
|
|
end
|
|
|
|
-- generate a new name with one of the rules from set
|
|
local function generate(name)
|
|
ng_debug([[starting "generate(%s)"]], name)
|
|
local data = namegen_generators_list[name]
|
|
if data == nil then
|
|
error(string.format("The name \"%s\" has not been found.\n",name))
|
|
end
|
|
-- check if the rules list is present */
|
|
if data.rules:size() == 0 then
|
|
error("The rules list is empty!")
|
|
end
|
|
|
|
-- choose the rule */
|
|
local res = generate_custom(name, data.rules:choice())
|
|
ng_debug([[starting "generate(%s)"]], name)
|
|
return res
|
|
end
|
|
|
|
local function possible_rules(str)
|
|
local res = {[str] = true}
|
|
while true do
|
|
local changed = false
|
|
local count = #res
|
|
for rule, _ in pairs(res) do
|
|
local chance_rule = rule:match(
|
|
"%$?[%a_'%- ]*(%$%d+[%a_'%- ]+)%$*.*$")
|
|
-- print(rule, chance_rule)
|
|
if chance_rule then
|
|
local a = rule:gsub(chance_rule, "")
|
|
local b = rule:gsub("%$%d+", "$", 1)
|
|
-- print(rule, a, b)
|
|
res[rule] = nil
|
|
res[a] = true
|
|
res[b] = true
|
|
changed = true
|
|
break
|
|
end
|
|
end
|
|
if not changed then break end
|
|
end
|
|
--[[
|
|
print(str)
|
|
for k, _ in pairs(res) do
|
|
print(k)
|
|
end
|
|
]]--
|
|
return res
|
|
end
|
|
|
|
local function exhaust_rules(name)
|
|
local data = namegen_generators_list[name]
|
|
local rules = {}
|
|
for v in data.rules:values() do
|
|
rules[#rules + 1] = v
|
|
end
|
|
local plain_rules = {}
|
|
for _, rule in ipairs(rules) do
|
|
local possible = possible_rules(rule)
|
|
for plain, _ in pairs(possible) do
|
|
plain_rules[#plain_rules + 1] = plain
|
|
end
|
|
end
|
|
return plain_rules
|
|
end
|
|
|
|
local function combine_strings(...)
|
|
local str
|
|
for _, v in pairs(...) do
|
|
str = (str and " " or "") .. v
|
|
end
|
|
return str
|
|
end
|
|
|
|
local function map_all(fcn, tab, idx, ...)
|
|
-- http://stackoverflow.com/a/13059680/5496529
|
|
if idx < 1 then
|
|
fcn(...)
|
|
else
|
|
local t = tab[idx]
|
|
for i = 1, #t do map_all(fcn, tab, idx-1, t[i], ...) end
|
|
end
|
|
end
|
|
|
|
local function exhaust_set(name)
|
|
local names = {}
|
|
local data = namegen_generators_list[name]
|
|
local rules = exhaust_rules(name)
|
|
local function combine(...)
|
|
local t = {...}
|
|
local str
|
|
for i, v in ipairs(t) do
|
|
if v ~= nil and v ~= "" then
|
|
str = (str and (str .. v) or v)
|
|
end
|
|
end
|
|
print(str)
|
|
str = word_prune_spaces(str)
|
|
names[str] = str
|
|
end
|
|
for _, rule in ipairs(rules) do
|
|
local groups = {}
|
|
local tokens = split(rule:gsub("%$", " "))
|
|
for _, token in ipairs(tokens) do
|
|
for c in string.gmatch(token, ".") do
|
|
local lst = (get_lst_from_token(c, data) or
|
|
{[1] = c:gsub("_", " ")})
|
|
if lst == nil then
|
|
error("invalid list", c)
|
|
end
|
|
groups[#groups + 1] = lst
|
|
end
|
|
end
|
|
map_all(combine, groups, #groups)
|
|
end
|
|
return names
|
|
end
|
|
|
|
|
|
local function get_sets()
|
|
local t = {}
|
|
for k, _ in pairs(namegen_generators_list) do
|
|
if namegen_generators_list[k].rules then
|
|
t[#t + 1] = k
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
|
|
-- ------------------------------
|
|
-- load default sets (as specified on `namegen.index`)
|
|
-- ------------------------------
|
|
parse_index()
|
|
|
|
|
|
-- ------------------------------
|
|
-- publicly available functions
|
|
-- ------------------------------
|
|
|
|
namegen.get_sets = get_sets
|
|
namegen.parse_file = parse_file
|
|
namegen.generate = generate
|
|
namegen.generate_custom = generate_custom
|
|
namegen.exhaust_set = exhaust_set
|
|
namegen.exhaust_rules = exhaust_rules
|
|
|
|
return namegen
|