2017-06-10 23:34:00 +02:00
--[[
Data about mobs is stored for each plot in the array beds .
Each entry in the beds table may have the following entries :
x , y , z position of the bed ; set automaticly by handle_schematics .
This is the position of the node containing the head of the bed .
Supported beds are normal bed , fancy bed and the bed from cottages .
p2 param2 of the node that contains the head of the bed ; set
automaticly by handle_schematics .
first_name the first name of a mob ; all mobs with the same profession in the
same village have diffrent first names ; also family members have
uniq first names ( inside each village ; not globally )
middle_name random middle initial ( just one letter )
gender m or f ( male / female )
generation 1 for children , 2 for parents ( = workers ) , 3 for workers parents
age age of the mob in years
optional entries :
works_at plot_nr of the place where this mob works ( may be the same as the
current one if he works at home )
title profession of the mob ; see worker.title ; also acts as a family name
to some degree
belongs_to some plots are neither workplaces nor places where mobs may live ;
it is assumed that other mobs will " own " these places and work there
aside from their main job ; this includes sheds , meadows , pastures
and wagons
owns array containing the ids of plots which belong_to this plot here
Apart from the beds array , there may also be a worker array . It contains information
about the mob that * works * at this place . Each entry in the worker table contains
the following information :
works_as general job description name ( i.e . " shopkeeper " )
title more specific job description ( i.e . " flower seller " ) ;
also used for the name of the mob and to some degree as a family name
lives_at plot_nr of the house where the worker lives
uniq counts how many other mobs in the village have the same profession
( = worker.title ) ; also determines weather the mob will be called i.e .
" the flower seller " ( if there is only one in this village ) or " a
flower seller " (if there are several)
Important : In order to * really * spawn a mob , you need to override the function
mg_villages.inhabitants . spawn_one_mob ( see mobf_trader for an example ) .
--]]
mg_villages.inhabitants = { }
mg_villages.inhabitants . names_male = { " John " , " James " , " Charles " , " Robert " , " Joseph " ,
" Richard " , " David " , " Michael " , " Christopher " , " Jason " , " Matthew " ,
" Joshua " , " Daniel " , " Andrew " , " Tyler " , " Jakob " , " Nicholas " , " Ethan " ,
" Alexander " , " Jayden " , " Mason " , " Liam " , " Oliver " , " Jack " , " Harry " ,
" George " , " Charlie " , " Jacob " , " Thomas " , " Noah " , " Wiliam " , " Oscar " ,
" Clement " , " August " , " Peter " , " Edgar " , " Calvin " , " Francis " , " Frank " ,
" Eli " , " Adam " , " Samuel " , " Bartholomew " , " Edward " , " Roger " , " Albert " ,
" Carl " , " Alfred " , " Emmett " , " Eric " , " Henry " , " Casimir " , " Alan " ,
" Brian " , " Logan " , " Stephen " , " Alexander " , " Gregory " , " Timothy " ,
" Theodore " , " Marcus " , " Justin " , " Julius " , " Felix " , " Pascal " , " Jim " ,
" Ben " , " Zach " , " Tom " } ;
mg_villages.inhabitants . names_female = { " Amelia " , " Isla " , " Ella " , " Poppy " , " Mia " , " Mary " ,
" Anna " , " Emma " , " Elizabeth " , " Minnie " , " Margret " , " Ruth " , " Helen " ,
" Dorothy " , " Betty " , " Barbara " , " Joan " , " Shirley " , " Patricia " , " Judith " ,
" Carol " , " Linda " , " Sandra " , " Susan " , " Deborah " , " Debra " , " Karen " , " Donna " ,
" Lisa " , " Kimberly " , " Michelle " , " Jennifer " , " Melissa " , " Amy " , " Heather " ,
" Angela " , " Jessica " , " Amanda " , " Sarah " , " Ashley " , " Brittany " , " Samatha " ,
" Emily " , " Hannah " , " Alexis " , " Madison " , " Olivia " , " Abigail " , " Isabella " ,
" Ava " , " Sophia " , " Martha " , " Rosalind " , " Matilda " , " Birgid " , " Jennifer " ,
" Chloe " , " Katherine " , " Penelope " , " Laura " , " Victoria " , " Cecila " , " Julia " ,
" Rose " , " Violet " , " Jasmine " , " Beth " , " Stephanie " , " Jane " , " Jacqueline " ,
" Josephine " , " Danielle " , " Paula " , " Pauline " , " Patricia " , " Francesca " }
-- get a middle name for the mob
mg_villages.inhabitants . get_random_letter = function ( )
return string.char ( string.byte ( " A " ) + math.random ( string.byte ( " Z " ) - string.byte ( " A " ) ) ) ;
end
-- this is for medieval villages
mg_villages.inhabitants . get_family_function_str = function ( data )
if ( data.generation == 2 and data.gender == " m " ) then
return " worker " ;
elseif ( data.generation == 2 and data.gender == " f " ) then
return " wife " ;
elseif ( data.generation == 3 and data.gender == " m " ) then
return " father " ;
elseif ( data.generation == 3 and data.gender == " f " ) then
return " mother " ;
elseif ( data.generation == 1 and data.gender == " m " ) then
return " son " ;
elseif ( data.generation == 1 and data.gender == " f " ) then
return " daughter " ;
else
return " unkown " ;
end
end
mg_villages.inhabitants . mob_get_full_name = function ( data , worker_data )
if ( not ( data ) or not ( data.first_name ) ) then
return ;
end
local str = data.first_name ;
if ( data.mob_id ) then
str = " [ " .. data.mob_id .. " ] " .. minetest.pos_to_string ( data ) .. " " .. data.first_name ;
else
str = " -no mob assigned - " .. minetest.pos_to_string ( data ) .. " " .. data.first_name ;
end
if ( data.middle_name ) then
str = str .. " " .. data.middle_name .. " . " ;
end
if ( data.last_name ) then
str = str .. " " .. data.last_name ;
end
if ( data.age ) then
str = str .. " , age " .. data.age ;
end
if ( worker_data and worker_data.title and worker_data.title ~= " " ) then
if ( data.title and data.title == ' guest ' ) then
str = str .. " , a guest staying at " .. worker_data.title .. " " .. worker_data.first_name .. " 's house " ;
2017-07-22 00:05:19 +02:00
elseif ( data.title and ( data.title == " servant " or data.title == " housemaid " or data.title == " guard " or data.title == " soldier " ) ) then
str = str .. " , a " .. data.title ;
2017-06-10 23:34:00 +02:00
elseif ( data.generation == 2 and data.gender == " m " and data.title and data.uniq > 1 ) then
str = str .. " , a " .. data.title ; --", one of "..tostring( worker_data.uniq ).." "..worker_data.title.."s";
-- if there is a job: , the blacksmith
elseif ( data.generation == 2 and data.gender == " m " and data.title ) then
str = str .. " , the " .. data.title ;
-- if there is a job: , blacksmith Fred's son etc.
elseif ( worker_data.uniq > 1 ) then
str = str .. " , " .. worker_data.title .. " " .. worker_data.first_name .. " 's " .. mg_villages.inhabitants . get_family_function_str ( data ) ;
else
str = str .. " , the " .. worker_data.title .. " 's " .. mg_villages.inhabitants . get_family_function_str ( data ) ;
end
-- else something like i.e. (son)
elseif ( data.generation and data.gender ) then
str = str .. " ( " .. mg_villages.inhabitants . get_family_function_str ( data ) .. " ) " ;
end
return str ;
end
2017-06-18 17:47:39 +02:00
-- override this function if you want more specific names (regional, age based, ..)
-- usually just "gender" is of intrest
-- name_exclude will be evaluated in get_new_inhabitant
-- village contains the village data of the entire village
mg_villages.inhabitants . get_names_list_full = function ( data , gender , generation , name_exclude , min_age , village )
if ( gender == " f " ) then
return mg_villages.inhabitants . names_female ;
else -- if( gender=="m" ) then
return mg_villages.inhabitants . names_male ;
end
end
2017-06-10 23:34:00 +02:00
-- configure a new inhabitant
-- gender can be "m" or "f"
-- generation 2 for parent-generation, 1 for children, 3 for grandparents
-- name_exlcude names the npc is not allowed to carry (=avoid duplicates)
-- (not a list but a hash table)
-- there can only be one mob with the same first name and the same profession per village
2017-06-18 17:47:39 +02:00
mg_villages.inhabitants . get_new_inhabitant = function ( data , gender , generation , name_exclude , min_age , village )
2017-06-10 23:34:00 +02:00
-- only create a new inhabitant if this one has not yet been configured
if ( not ( data ) or data.first_name ) then
return data ;
end
-- the gender of children is random
if ( gender == " r " ) then
if ( math.random ( 2 ) == 1 ) then
gender = " m " ;
else
gender = " f " ;
end
end
2017-06-18 17:47:39 +02:00
local name_list = mg_villages.inhabitants . get_names_list_full ( data , gender , generation , name_exclude , min_age , village ) ;
2017-06-10 23:34:00 +02:00
if ( gender == " f " ) then
data.gender = " f " ; -- female
else -- if( gender=="m" ) then
data.gender = " m " ; -- male
end
local name_list_tmp = { } ;
for i , v in ipairs ( name_list ) do
if ( not ( name_exclude [ v ] ) ) then
table.insert ( name_list_tmp , v ) ;
end
end
data.first_name = name_list_tmp [ math.random ( # name_list_tmp ) ] ;
-- middle name as used in the english speaking world (might help to distinguish mobs with the same first name)
data.middle_name = mg_villages.inhabitants . get_random_letter ( ) ;
data.generation = generation ; -- 2: parent generation; 1: child; 3: grandparents
if ( data.generation == 1 ) then
data.age = math.random ( 18 ) ; -- a child
elseif ( data.generation == 2 ) then
data.age = 18 + math.random ( 30 ) ; -- a parent
elseif ( data.generation == 3 ) then
data.age = 48 + math.random ( 50 ) ; -- a grandparent
end
if ( min_age ) then
data.age = min_age + math.random ( 12 ) ;
end
return data ;
end
-- assign inhabitants to bed positions; create families;
-- bpos needs to contain at least { beds = {list_of_bed_positions}, btype = building_type}
-- bpos is the building position data of one building each
2017-06-18 17:47:39 +02:00
mg_villages.inhabitants . assign_mobs_to_beds = function ( bpos , house_nr , village_to_add_data_bpos , village )
2017-06-10 23:34:00 +02:00
if ( not ( bpos ) or not ( bpos.btype ) or not ( bpos.beds ) ) then
return bpos ;
end
-- make sure no duplicates exist
local check_duplicates = { } ;
local new_table = { } ;
for i , v in ipairs ( bpos.beds ) do
local str = minetest.pos_to_string ( v ) ;
if ( not ( check_duplicates [ str ] ) ) then
table.insert ( new_table , v ) ;
end
check_duplicates [ str ] = 1 ;
end
bpos.beds = new_table ;
-- workplaces got assigned earlier on
local works_at = nil ;
local title = nil ;
local not_uniq = 0 ;
-- any other plots (sheds, wagons, fields, pastures) the worker here may own
local owns = { } ;
for nr , v in ipairs ( village_to_add_data_bpos ) do
-- have we found the workplace of this mob?
if ( v and v.worker and v.worker . lives_at and v.worker . lives_at == house_nr ) then
works_at = nr ;
title = v.worker . title ;
if ( v.worker . uniq ) then
not_uniq = v.worker . uniq ;
end
end
-- ..or another plot that the mob might own?
if ( v and v.belongs_to and v.belongs_to == house_nr ) then
table.insert ( owns , nr ) ;
end
end
local worker_names_with_same_profession = { } ;
-- if the profession of this mob is not uniq then at least make sure that he does not share a name with a mob with the same profession
if ( not_uniq > 1 ) then
for nr , v in ipairs ( village_to_add_data_bpos ) do
if ( v and v.worker and v.worker . lives_at
and village_to_add_data_bpos [ v.worker . lives_at ]
and village_to_add_data_bpos [ v.worker . lives_at ] . beds
and village_to_add_data_bpos [ v.worker . lives_at ] . beds [ 1 ]
and village_to_add_data_bpos [ v.worker . lives_at ] . beds [ 1 ] . first_name ) then
table.insert ( worker_names_with_same_profession , village_to_add_data_bpos [ v.worker . lives_at ] . beds [ 1 ] . first_name ) ;
end
end
end
-- get data about the building
local building_data = mg_villages.BUILDINGS [ bpos.btype ] ;
-- the building type determines which kind of mob will live there
if ( not ( building_data ) or not ( building_data.typ )
-- are there beds where the mob can sleep?
or not ( bpos.beds ) or table.getn ( bpos.beds ) < 1 ) then
return bpos ;
end
-- lumberjack home
if ( building_data.typ == " lumberjack " ) then
for i , v in ipairs ( bpos.beds ) do
-- lumberjacks do not have families and are all male
2017-06-18 17:47:39 +02:00
v = mg_villages.inhabitants . get_new_inhabitant ( v , " m " , 2 , worker_names_with_same_profession , nil , village ) ;
2017-06-10 23:34:00 +02:00
-- the first worker in a lumberjack hut can get work assigned and own other plots
if ( works_at and i == 1 ) then
v.works_at = works_at ;
v.title = title ;
v.uniq = not_uniq ;
else
v.title = ' lumberjack ' ;
v.uniq = 99 ; -- one of many lumberjacks here
end
if ( owns and # owns > 0 ) then
v.owns = owns ;
end
end
2017-07-22 00:05:19 +02:00
-- the castle-type buildings contain guards without family
elseif ( building_data.typ == " castle " ) then
for i , v in ipairs ( bpos.beds ) do
v = mg_villages.inhabitants . get_new_inhabitant ( v , " m " , 2 , worker_names_with_same_profession , nil , village ) ;
v.works_at = house_nr ; -- they work in their castle
v.title = " soldier " ;
v.uniq = 99 ; -- one of many guards here
end
2017-06-10 23:34:00 +02:00
-- normal house containing a family
else
-- the first inhabitant will be the male worker
if ( not ( bpos.beds [ 1 ] . first_name ) ) then
2017-06-18 17:47:39 +02:00
bpos.beds [ 1 ] = mg_villages.inhabitants . get_new_inhabitant ( bpos.beds [ 1 ] , " m " , 2 , worker_names_with_same_profession , nil , village ) ; -- male of parent generation
2017-06-10 23:34:00 +02:00
if ( works_at ) then
bpos.beds [ 1 ] . works_at = works_at ;
bpos.beds [ 1 ] . title = title ;
bpos.beds [ 1 ] . uniq = not_uniq ;
end
if ( owns and # owns > 0 ) then
bpos.beds [ 1 ] . owns = owns ;
end
end
local name_exclude = { } ;
-- the second inhabitant will be the wife of the male worker
if ( bpos.beds [ 2 ] and not ( bpos.beds [ 2 ] . first_name ) ) then
2017-06-18 17:47:39 +02:00
bpos.beds [ 2 ] = mg_villages.inhabitants . get_new_inhabitant ( bpos.beds [ 2 ] , " f " , 2 , { } , nil , village ) ; -- female of parent generation
2017-06-10 23:34:00 +02:00
-- first names ought to be uniq withhin a family
name_exclude [ bpos.beds [ 2 ] . first_name ] = 1 ;
end
-- not all houses will have grandparents
local grandmother_bed_id = 2 + math.random ( 5 ) ;
local grandfather_bed_id = 2 + math.random ( 5 ) ;
-- some houses have guests
local guest_id = 99 ;
-- all but the given number are guests
if ( building_data.guests ) then
guest_id = building_data.guests * - 1 ;
end
-- a child of 18 with a parent of 19 would be...usually impossible unless adopted
local oldest_child = 0 ;
-- the third and subsequent inhabitants will ether be children or grandparents
for i , v in ipairs ( bpos.beds ) do
if ( v and v.first_name and v.generation == 3 and v.gender == " f " ) then
grandmother_bed_id = i ;
elseif ( v and v.first_name and v.generation == 3 and v.gender == " m " ) then
grandfather_bed_id = i ;
-- at max 7 npc per house (taverns may have more beds than that)
elseif ( v and not ( v.first_name ) ) then
if ( i > guest_id ) then
2017-07-22 00:05:19 +02:00
-- a chateau has servants instead of guests like a hotel
if ( building_data.typ == " chateau " ) then
-- working generation (neither children nor grandparents)
v = mg_villages.inhabitants . get_new_inhabitant ( v , " r " , 2 , name_exclude , nil , village ) ;
if ( v.gender == " m " ) then
v.title = " servant " ;
else
v.title = " housemaid " ;
end
v.works_at = house_nr ;
v.uniq = 99 ; -- one of many servants/housemaids here
-- guest in a hotel
else
v = mg_villages.inhabitants . get_new_inhabitant ( v , " r " , math.random ( 3 ) , name_exclude , nil , village ) ; -- get a random guest
v.title = ' guest ' ;
end
2017-06-10 23:34:00 +02:00
elseif ( i == grandmother_bed_id ) then
2017-06-18 17:47:39 +02:00
v = mg_villages.inhabitants . get_new_inhabitant ( v , " f " , 3 , name_exclude , bpos.beds [ 1 ] . age + 18 , village ) ; -- get the grandmother
2017-06-10 23:34:00 +02:00
elseif ( i == grandfather_bed_id ) then
2017-06-18 17:47:39 +02:00
v = mg_villages.inhabitants . get_new_inhabitant ( v , " m " , 3 , name_exclude , bpos.beds [ 1 ] . age + 18 , village ) ; -- get the grandfather
2017-06-10 23:34:00 +02:00
else
2017-06-18 17:47:39 +02:00
v = mg_villages.inhabitants . get_new_inhabitant ( v , " r " , 1 , name_exclude , nil , village ) ; -- get a child of random gender
2017-06-10 23:34:00 +02:00
-- find out how old the oldest child is
if ( v.age > oldest_child ) then
oldest_child = v.age ;
end
end
-- children and grandparents need uniq names withhin a family
name_exclude [ v.first_name ] = 1 ;
end
end
-- the father has to be old enough for his children
if ( bpos.beds [ 1 ] and oldest_child + 18 > bpos.beds [ 1 ] . age ) then
bpos.beds [ 1 ] . age = oldest_child + 18 + math.random ( 10 ) ;
end
-- the mother also has to be old enough as well
if ( bpos.beds [ 2 ] and oldest_child + 18 > bpos.beds [ 2 ] . age ) then
bpos.beds [ 2 ] . age = oldest_child + 18 + math.random ( 10 ) ;
end
end
return bpos ;
end
-- print information about which mobs "live" in a house
2017-06-20 20:13:06 +02:00
mg_villages.inhabitants . print_house_info = function ( village_to_add_data_bpos , house_nr , village_id )
2017-06-10 23:34:00 +02:00
local bpos = village_to_add_data_bpos [ house_nr ] ;
local building_data = mg_villages.BUILDINGS [ bpos.btype ] ;
if ( not ( building_data ) or not ( building_data.typ ) ) then
building_data = { typ = bpos.btype } ;
end
local str = " Plot Nr. " .. tostring ( house_nr ) .. " [ " .. tostring ( building_data.typ or " -?- " ) .. " ] " ;
if ( bpos.btype == " road " ) then
str = str .. " is a road. \n " ;
-- wagon, shed, field and pasture
elseif ( bpos.belongs_to and village_to_add_data_bpos [ bpos.belongs_to ] . beds ) then
local owner = village_to_add_data_bpos [ bpos.belongs_to ] . beds [ 1 ] ;
if ( not ( owner ) or not ( owner.first_name ) ) then
str = str .. " WARNING: NO ONE owns this plot. \n " ;
else
str = str .. " belongs to: " .. mg_villages.inhabitants . mob_get_full_name ( owner , owner ) .. " \n " ;
end
elseif ( not ( bpos.beds ) or # bpos.beds < 1 and bpos.worker and bpos.worker . title ) then
if ( not ( bpos.worker . lives_at ) ) then
str = str .. " WARNING: NO WORKER assigned to this plot. \n " ;
else
local worker = village_to_add_data_bpos [ bpos.worker . lives_at ] . beds [ 1 ] ;
str = str .. mg_villages.inhabitants . mob_get_full_name ( worker , worker ) .. " works here. \n " ;
end
elseif ( not ( bpos.beds ) or not ( bpos.beds [ 1 ] ) ) then
str = str .. " provides neither work nor housing. \n " ;
else
str = str .. " is inhabitated by: \n " ;
-- make sure all mobs living here are spawned
2017-06-20 20:13:06 +02:00
mg_villages.inhabitants . spawn_mobs_for_one_house ( bpos , nil , nil , village_id , house_nr ) ;
2017-06-10 23:34:00 +02:00
for i , v in ipairs ( bpos.beds ) do
if ( v and v.first_name ) then
2017-07-22 00:05:19 +02:00
local worker_data = bpos.beds [ 1 ] ; -- the father has the job
if ( v and v.works_at ) then
worker_data = v ;
end
str = str .. " " .. mg_villages.inhabitants . mob_get_full_name ( v , worker_data ) ;
if ( v and v.works_at and v.works_at == house_nr ) then
2017-06-10 23:34:00 +02:00
str = str .. " who lives and works here \n " ;
2017-07-22 00:05:19 +02:00
elseif ( v and v.works_at ) then
2017-06-10 23:34:00 +02:00
local works_at = bpos.beds [ 1 ] . works_at ;
local btype2 = mg_villages.BUILDINGS [ village_to_add_data_bpos [ works_at ] . btype ] ;
str = str .. " who works at the " .. tostring ( btype2.typ ) .. " on plot " .. tostring ( works_at ) .. " \n " ;
else
str = str .. " \n " ;
end
end
end
-- other plots owned
if ( bpos.beds and bpos.beds [ 1 ] and bpos.beds [ 1 ] . owns ) then
str = str .. " The family also owns the plot(s) " ;
for i , v in ipairs ( bpos.beds [ 1 ] . owns ) do
if ( i > 1 ) then
str = str .. " , " ;
end
local building_data = mg_villages.BUILDINGS [ village_to_add_data_bpos [ v ] . btype ] ;
str = str .. " Nr. " .. tostring ( v ) .. " ( " .. building_data.typ .. " ) " ;
end
str = str .. " \n " ;
end
end
return str ;
end
-- some building types will determine the name of the job
mg_villages.inhabitants . jobs_in_buildings = { } ;
mg_villages.inhabitants . jobs_in_buildings [ ' mill ' ] = { ' miller ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' bakery ' ] = { ' baker ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' church ' ] = { ' priest ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' tower ' ] = { ' guard ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' school ' ] = { ' schoolteacher ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' library ' ] = { ' librarian ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' tavern ' ] = { ' barkeeper ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' pub ' ] = { ' barkeeper ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' inn ' ] = { ' innkeeper ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' hotel ' ] = { ' innkeeper ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' forge ' ] = { ' smith ' ,
-- bronzesmith, bladesmith, locksmith etc. may be of little use in our MT worlds;
-- the blacksmith is the most common one, followed by the coppersmith
{ ' blacksmith ' , ' blacksmith ' , ' blacksmith ' , ' coppersmith ' , ' coppersmith ' ,
' tinsmith ' , ' goldsmith ' } } ;
mg_villages.inhabitants . jobs_in_buildings [ ' shop ' ] = { ' shopkeeper ' ,
-- the shopkeeper is the most common; however, there can be more specialized sellers
{ ' shopkeeper ' , ' shopkeeper ' , ' shopkeeper ' , ' seed seller ' , ' flower seller ' , ' ore seller ' , ' fruit trader ' , ' wood trader ' } } ;
mg_villages.inhabitants . jobs_in_buildings [ ' charachoal ' ] = { ' charachoal burner ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' trader ' ] = { ' trader ' } ; -- TODO: currently only used for clay traders
2017-07-22 00:05:19 +02:00
mg_villages.inhabitants . jobs_in_buildings [ ' chateau ' ] = { ' landlord ' } ;
2017-06-10 23:34:00 +02:00
mg_villages.inhabitants . jobs_in_buildings [ ' sawmill ' ] = { ' sawmill owner ' } ;
mg_villages.inhabitants . jobs_in_buildings [ ' forrest ' ] = { ' lumberjack ' } ; -- TODO: we don't have forrests yet
mg_villages.inhabitants . jobs_in_buildings [ ' village_square ' ] = { ' major ' } ;
2017-07-01 22:54:53 +02:00
mg_villages.inhabitants . jobs_in_buildings [ ' townhall ' ] = { ' major ' } ;
2017-06-27 04:46:02 +02:00
mg_villages.inhabitants . jobs_in_buildings [ ' horsestable ' ] = { ' horsekeeper ' } ;
2017-06-10 23:34:00 +02:00
-- TODO pit - suitable for traders (they sell clay...)
mg_villages.inhabitants . assign_jobs_to_houses = function ( village_to_add_data_bpos )
local workers_required = { } ; -- places that require a specific worker that lives elsewhere
local found_farm_full = { } ; -- farmers (they like to work on fields and pastures)
local found_hut = { } ; -- workers best fit for working in other buildings
local found_house = { } ; -- workers which may either take a random job or work elsewhere
local suggests_worker = { } ; -- sheds and wagons can support workers with a random job
local suggests_farmer = { } ; -- fields and pastures are ideal for farmers
-- find out which jobs need to get taken
for house_id , bpos in ipairs ( village_to_add_data_bpos ) do
-- get data about the building
local building_data = mg_villages.BUILDINGS [ bpos.btype ] ;
-- the building type determines which kind of mobs will live there;
-- nothing gets assigned if we don't have data
if ( not ( building_data ) or not ( building_data.typ )
-- or if a mob is assigned already
or bpos.worker ) then
-- some buildings require a specific worker
elseif ( mg_villages.inhabitants . jobs_in_buildings [ building_data.typ ] ) then
local worker_data = mg_villages.inhabitants . jobs_in_buildings [ building_data.typ ] ;
bpos.worker = { } ;
bpos.worker . works_as = worker_data [ 1 ] ;
-- the worker might be specialized
if ( worker_data [ 2 ] ) then
bpos.worker . title = worker_data [ 2 ] [ math.random ( # worker_data [ 2 ] ) ] ;
-- otherwise his title is the same as his job name
else
bpos.worker . title = bpos.worker . works_as ;
end
-- can the worker sleep there or does he require a house elsewhere?
if ( building_data.bed_count and building_data.bed_count > 0 ) then
bpos.worker . lives_at = house_id ;
else
table.insert ( workers_required , house_id ) ;
end
-- we have found a place with a bed that does not reuiqre a worker directly
elseif ( building_data.bed_count and building_data.bed_count > 0 ) then
-- mobs having to take care of a full farm (=farm where the farmer's main income is
-- gained from farming) are less likely to have time for other jobs
if ( building_data.typ == ' farm_full ' ) then
table.insert ( found_farm_full , house_id ) ;
-- mobs living in a hut are the best candidates for jobs in other buildings
elseif ( building_data.typ == ' hut ' ) then
table.insert ( found_hut , house_id ) ;
-- other mobs may either take on a random job or work in other buildings
else
table.insert ( found_house , house_id ) ;
end
-- sheds and wagons are useful for random jobs but do not really require a worker
elseif ( building_data.typ == ' shed '
or building_data.typ == ' wagon ' ) then
table.insert ( suggests_worker , house_id ) ;
-- fields and pastures are places where full farmers are best at
elseif ( building_data.typ == ' field '
or building_data.typ == ' pasture ' ) then
table.insert ( suggests_farmer , house_id ) ;
end
end
-- these are only additional; they do not require a worker as such
-- assign sheds and wagons randomly to suitable houses
for i , v in ipairs ( suggests_worker ) do
-- order: found_house, found_hut, found_farm_full
if ( # found_house > 0 ) then
local nr = math.random ( # found_house ) ;
village_to_add_data_bpos [ v ] . belongs_to = found_house [ nr ] ;
elseif ( # found_hut > 0 ) then
local nr = math.random ( # found_hut ) ;
village_to_add_data_bpos [ v ] . belongs_to = found_hut [ nr ] ;
elseif ( # found_farm_full > 0 ) then
local nr = math.random ( # found_farm_full ) ;
village_to_add_data_bpos [ v ] . belongs_to = found_farm_full [ nr ] ;
else
-- print("NOT ASSIGNING work PLOT Nr. "..tostring(v).." to anything (nothing suitable found)");
end
end
-- assign fields and pastures randomly to suitable houses
for i , v in ipairs ( suggests_farmer ) do
-- order: found_farm_full, found_house, found_hut
if ( # found_farm_full > 0 ) then
local nr = math.random ( # found_farm_full ) ;
village_to_add_data_bpos [ v ] . belongs_to = found_farm_full [ nr ] ;
elseif ( # found_house > 0 ) then
local nr = math.random ( # found_house ) ;
village_to_add_data_bpos [ v ] . belongs_to = found_house [ nr ] ;
elseif ( # found_hut > 0 ) then
local nr = math.random ( # found_hut ) ;
village_to_add_data_bpos [ v ] . belongs_to = found_hut [ nr ] ;
else
-- print("NOT ASSIGNING farm PLOT Nr. "..tostring(v).." to anything (nothing suitable found)");
end
end
-- find workers for jobs that require workes who live elsewhere
for i , v in ipairs ( workers_required ) do
-- huts are ideal
if ( # found_hut > 0 ) then
local nr = math.random ( # found_hut ) ;
village_to_add_data_bpos [ v ] . worker.lives_at = found_hut [ nr ] ;
table.remove ( found_hut , nr ) ;
-- but workers may also be gained from other houses where workers may live
elseif ( # found_house > 0 ) then
local nr = math.random ( # found_house ) ;
village_to_add_data_bpos [ v ] . worker.lives_at = found_house [ nr ] ;
table.remove ( found_house , nr ) ;
-- if all else fails try to get a worker from a full farm
elseif ( # found_farm_full > 0 ) then
local nr = math.random ( # found_farm_full ) ;
village_to_add_data_bpos [ v ] . worker.lives_at = found_farm_full [ nr ] ;
table.remove ( found_farm_full , nr ) ;
-- we ran out of potential workers...
else
-- no suitable worker found
--local building_data = mg_villages.BUILDINGS[ village_to_add_data_bpos[v].btype ];
--print("NO WORKER FOUND FOR Nr. "..tostring(v).." "..tostring( building_data.typ )..": "..minetest.serialize( village_to_add_data_bpos[v].worker ));
end
end
-- other owners of farm_full buildings become farmers
for i , v in ipairs ( found_farm_full ) do
village_to_add_data_bpos [ v ] . worker = { } ;
village_to_add_data_bpos [ v ] . worker.works_as = " farmer " ;
village_to_add_data_bpos [ v ] . worker.title = " farmer " ;
village_to_add_data_bpos [ v ] . worker.lives_at = v ; -- house number
end
-- add random jobs to the leftover houses
local random_jobs = { ' stonemason ' , ' stoneminer ' , ' carpenter ' , ' toolmaker ' ,
' doormaker ' , ' furnituremaker ' , ' stairmaker ' , ' cooper ' , ' wheelwright ' ,
' saddler ' , ' roofer ' , ' iceman ' , ' potterer ' , ' bricklayer ' , ' dyemaker ' ,
' glassmaker ' } ;
for i , v in ipairs ( found_house ) do
local job = random_jobs [ math.random ( # random_jobs ) ] ;
village_to_add_data_bpos [ v ] . worker = { } ;
village_to_add_data_bpos [ v ] . worker.works_as = job ;
village_to_add_data_bpos [ v ] . worker.title = job ;
village_to_add_data_bpos [ v ] . worker.lives_at = v ; -- house number
end
for i , v in ipairs ( found_hut ) do
local job = random_jobs [ math.random ( # random_jobs ) ] ;
village_to_add_data_bpos [ v ] . worker = { } ;
village_to_add_data_bpos [ v ] . worker.works_as = job ;
village_to_add_data_bpos [ v ] . worker.title = job ;
village_to_add_data_bpos [ v ] . worker.lives_at = v ; -- house number
end
-- find out if there are any duplicate professions
local professions = { } ;
for house_nr , bpos in ipairs ( village_to_add_data_bpos ) do
if ( bpos.worker and bpos.worker . title ) then
if ( not ( professions [ bpos.worker . title ] ) ) then
professions [ bpos.worker . title ] = 1 ;
else
professions [ bpos.worker . title ] = professions [ bpos.worker . title ] + 1 ;
end
end
end
-- mark all those workers who share the same profession as "not_uniq"
for house_nr , bpos in ipairs ( village_to_add_data_bpos ) do
if ( bpos.worker and bpos.worker . title and professions [ bpos.worker . title ] > 1 ) then
bpos.worker . uniq = professions [ bpos.worker . title ] ;
end
end
return village_to_add_data_bpos ;
end
2017-07-06 18:18:40 +02:00
-- apply bpos.pos as offset and apply rotation
mg_villages.transform_coordinates = function ( pos , bpos )
-- start with the start position as stored in bpos
local p = { x = bpos.x , y = bpos.y , z = bpos.z } ;
local building_data = mg_villages.BUILDINGS [ bpos.btype ] ;
-- the height is not affected by rotation
-- the positions are stored as array
p.y = p.y + building_data.yoff + pos [ 2 ] - 1 ;
local rel = { x = pos [ 1 ] , y = pos [ 2 ] , z = pos [ 3 ] } ; -- relative position (usually of entrance)
-- all values start counting with index 1; we need to start with 0 for the offset
local sx = bpos.bsizex - 1 ;
local sz = bpos.bsizez - 1 ;
rel.x = rel.x - 1 ;
rel.z = rel.z - 1 ;
if ( bpos.mirror and bpos.btype ) then
local o = building_data.orients [ 1 ] ;
if ( ( o == 0 or o == 2 ) and ( bpos.brotate == 0 or bpos.brotate == 2 ) ) then
rel.z = sz - rel.z ;
elseif ( ( o == 0 or o == 2 ) and ( bpos.brotate == 1 or bpos.brotate == 3 ) ) then
rel.z = sx - rel.z ;
elseif ( ( o == 1 or o == 3 ) and ( bpos.brotate == 0 or bpos.brotate == 2 ) ) then
rel.x = sx - rel.x ;
elseif ( ( o == 1 or o == 3 ) and ( bpos.brotate == 1 or bpos.brotate == 3 ) ) then
rel.x = sz - rel.x ;
end
end
if ( bpos.brotate == 0 ) then
p.x = p.x + rel.x ;
p.z = p.z + rel.z ;
elseif ( bpos.brotate == 1 ) then
p.x = p.x + rel.z ;
p.z = p.z + sz - rel.x ; -- bsizex and bsizez are swapped
elseif ( bpos.brotate == 2 ) then
p.x = p.x + sx - rel.x ;
p.z = p.z + sz - rel.z ;
elseif ( bpos.brotate == 3 ) then
p.x = p.x + sx - rel.z ; -- bsizex and bsizez are swapped
p.z = p.z + rel.x ;
end
return p ;
end
2017-07-08 22:46:53 +02:00
mg_villages.get_plot_and_building_data = function ( village_id , plot_nr )
2017-07-06 18:18:40 +02:00
if ( not ( mg_villages.all_villages [ village_id ] )
or not ( mg_villages.all_villages [ village_id ] . to_add_data.bpos [ plot_nr ] ) ) then
2017-07-08 22:46:53 +02:00
return ;
2017-07-06 18:18:40 +02:00
end
local bpos = mg_villages.all_villages [ village_id ] . to_add_data.bpos [ plot_nr ] ;
2017-07-08 22:46:53 +02:00
if ( not ( bpos ) or not ( bpos.btype ) or not ( mg_villages.BUILDINGS [ bpos.btype ] ) ) then
return ;
end
return { bpos = bpos , building_data = mg_villages.BUILDINGS [ bpos.btype ] } ;
end
mg_villages.get_entrance_list = function ( village_id , plot_nr )
local res = mg_villages.get_plot_and_building_data ( village_id , plot_nr ) ;
if ( not ( res ) or not ( res.building_data ) or not ( res.building_data . all_entrances ) ) then
2017-07-06 18:18:40 +02:00
return { } ;
end
local entrance_list = { } ;
2017-07-08 22:46:53 +02:00
for i , e in ipairs ( res.building_data . all_entrances ) do
table.insert ( entrance_list , mg_villages.transform_coordinates ( e , res.bpos ) ) ;
2017-07-06 18:18:40 +02:00
end
return entrance_list ;
end
2017-07-08 22:46:53 +02:00
mg_villages.get_path_from_bed_to_outside = function ( village_id , plot_nr , bed_nr , door_nr )
local res = mg_villages.get_plot_and_building_data ( village_id , plot_nr ) ;
if ( not ( res ) or not ( res.building_data ) or not ( res.building_data . short_file_name )
or not ( mg_villages.path_info [ res.building_data . short_file_name ] )
or not ( mg_villages.path_info [ res.building_data . short_file_name ] [ door_nr ] )
or not ( mg_villages.path_info [ res.building_data . short_file_name ] [ door_nr ] [ bed_nr ] ) ) then
return ;
end
local path = { } ;
-- get the path from the bed to front door door_nr
for i , p in ipairs ( mg_villages.path_info [ res.building_data . short_file_name ] [ door_nr ] [ bed_nr ] ) do
table.insert ( path , mg_villages.transform_coordinates ( p , res.bpos ) ) ;
end
local rest_path_id = # mg_villages.path_info [ res.building_data . short_file_name ] [ door_nr ] ;
-- the last entrance is the common path for all beds from the front door door_nr to the outside
if ( rest_path_id == bed_nr ) then
return path ;
end
-- add the path from the front door to the front of the building
for i , p in ipairs ( mg_villages.path_info [ res.building_data . short_file_name ] [ door_nr ] [ rest_path_id ] ) do
table.insert ( path , mg_villages.transform_coordinates ( p , res.bpos ) ) ;
end
return path ;
end
-- door_nr ought to be 1 in most cases (unless the mob is standing in front of another door)
mg_villages.get_path_from_outside_to_bed = function ( village_id , plot_nr , bed_nr , door_nr )
local path = mg_villages.get_path_from_bed_to_outside ( village_id , plot_nr , bed_nr , door_nr ) ;
if ( not ( path ) ) then
return path ;
end
local reverse_path = { } ;
for i = # path , 1 , - 1 do
table.insert ( reverse_path , path [ i ] ) ;
end
return reverse_path ;
end
2017-06-27 16:04:46 +02:00
-- get the information mg_villages has about a mob (useful for mg_villages:mob_spawner)
mg_villages.inhabitants . get_mob_data = function ( village_id , plot_nr , bed_nr )
if ( not ( village_id ) or not ( plot_nr ) or not ( bed_nr )
or not ( mg_villages.all_villages [ village_id ] )
or not ( mg_villages.all_villages [ village_id ] . to_add_data.bpos [ plot_nr ] )
or not ( mg_villages.all_villages [ village_id ] . to_add_data.bpos [ plot_nr ] . beds ) ) then
return ;
end
2017-07-08 22:46:53 +02:00
--[[
-- TODO: mark entrances for manual inspection
for i , p in ipairs ( mg_villages.get_entrance_list ( village_id , plot_nr ) ) do
local bpos = mg_villages.all_villages [ village_id ] . to_add_data.bpos [ plot_nr ] ;
minetest.chat_send_player ( " singleplayer " , " door: " .. minetest.pos_to_string ( p ) ..
" pos: " .. minetest.pos_to_string ( bpos ) ..
" o: " .. tostring ( bpos.o ) .. " r: " .. tostring ( bpos.brotate ) .. " m: " .. tostring ( bpos.mirror ) ) ;
minetest.set_node ( p , { name = " wool:cyan " , param2 = 0 } ) ;
end
--]]
2017-06-27 16:04:46 +02:00
return mg_villages.all_villages [ village_id ] . to_add_data.bpos [ plot_nr ] . beds [ bed_nr ] ;
end
2017-06-10 23:34:00 +02:00
-- mob mods are expected to override this function! mobf_trader mobs are supported directly
2017-06-20 20:13:06 +02:00
mg_villages.inhabitants . spawn_one_mob = function ( bed , village_id , plot_nr , bed_nr , bpos )
2017-06-10 23:34:00 +02:00
2017-06-20 20:13:06 +02:00
--print("NPC spawned in village "..tostring( village_id ).." on plot "..tostring(plot_nr)..", sleeping in bed nr. "..tostring( bed_nr ));
2017-06-10 23:34:00 +02:00
if ( minetest.get_modpath ( " mobf_trader " ) and mobf_trader and mobf_trader.spawn_one_trader ) then
2017-06-20 20:13:06 +02:00
return mobf_trader.spawn_one_trader ( bed , village_id , plot_nr , bed_nr , bpos ) ;
2017-06-10 23:34:00 +02:00
end
end
2017-06-20 20:13:06 +02:00
mg_villages.inhabitants . spawn_mobs_for_one_house = function ( bpos , minp , maxp , village_id , plot_nr )
2017-06-10 23:34:00 +02:00
if ( not ( bpos ) or not ( bpos.beds ) ) then
return ;
end
2017-06-20 20:13:06 +02:00
for bed_nr , bed in ipairs ( bpos.beds ) do
2017-06-10 23:34:00 +02:00
-- only for beds that exist, have a mob assigned and fit into minp/maxp
if ( bed
and bed.first_name
and ( not ( minp )
or ( bed.x >= minp.x and bed.x <= maxp.x
and bed.y >= minp.y and bed.y <= maxp.y
and bed.z >= minp.z and bed.z <= maxp.z ) ) ) then
2017-06-20 20:13:06 +02:00
bed.mob_id = mg_villages.inhabitants . spawn_one_mob ( bed , village_id , plot_nr , bed_nr , bpos ) ;
2017-06-10 23:34:00 +02:00
end
end
end
-- spawn mobs in villages
mg_villages.inhabitants . part_of_village_spawned = function ( village , minp , maxp , data , param2_data , a , cid )
-- some types of buildings require special workers
village.to_add_data . bpos = mg_villages.inhabitants . assign_jobs_to_houses ( village.to_add_data . bpos ) ;
-- for each building in the village
2017-06-20 20:13:06 +02:00
for plot_nr , bpos in ipairs ( village.to_add_data . bpos ) do
2017-06-10 23:34:00 +02:00
-- each bed gets a mob assigned
2017-06-20 20:13:06 +02:00
bpos = mg_villages.inhabitants . assign_mobs_to_beds ( bpos , plot_nr , village.to_add_data . bpos , village ) ;
2017-06-10 23:34:00 +02:00
-- actually spawn the mobs
2017-06-20 20:13:06 +02:00
local village_id = tostring ( village.vx ) .. ' : ' .. tostring ( village.vz ) ;
mg_villages.inhabitants . spawn_mobs_for_one_house ( bpos , minp , maxp , village_id , plot_nr ) ;
2017-06-10 23:34:00 +02:00
end
end
-- command for debugging all inhabitants of a village (useful for debugging only)
minetest.register_chatcommand ( ' inhabitants ' , {
description = " Prints out a list of inhabitants of a village plus their professions. " ,
params = " <village number> " ,
privs = { } ,
func = function ( name , param )
if ( not ( param ) or param == " " ) then
minetest.chat_send_player ( name , " List the inhabitants of which village? Please provide the village number! " ) ;
return ;
end
local nr = tonumber ( param ) ;
for id , v in pairs ( mg_villages.all_villages ) do
-- we have found the village
if ( v and v.nr == nr ) then
minetest.chat_send_player ( name , " Printing information about inhabitants of village no. " .. tostring ( v.nr ) .. " , called " .. ( tostring ( v.name or ' unknown ' ) ) .. " to console. " ) ;
-- actually print it
for house_nr = 1 , # v.to_add_data . bpos do
2017-06-20 20:13:06 +02:00
minetest.chat_send_player ( name , mg_villages.inhabitants . print_house_info ( v.to_add_data . bpos , house_nr , v.nr ) ) ;
2017-06-10 23:34:00 +02:00
end
return ;
end
end
-- no village found
minetest.chat_send_player ( name , " There is no village with the number " .. tostring ( param ) .. " (yet?). " ) ;
end
} ) ;