1404 lines
58 KiB
Lua
1404 lines
58 KiB
Lua
|
|
--[[
|
|
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
|
|
other:
|
|
bnr index of this mob's bed in mg_villages.BUILDINGS[ this_building_type ].bed_list
|
|
|
|
|
|
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
|
|
|
|
-- in most cases this will be something like "John D.", "Martha A." etc.
|
|
mg_villages.inhabitants.mob_get_short_name = function( data )
|
|
if( not( data ) or not( data.first_name )) then
|
|
return "- unkown -";
|
|
end
|
|
local str = data.first_name;
|
|
if( data.middle_name ) then
|
|
str = str.." "..data.middle_name..".";
|
|
end
|
|
if( data.last_name ) then
|
|
str = str.." "..data.last_name;
|
|
end
|
|
return str;
|
|
end
|
|
|
|
|
|
-- worker_data contains data about the father of the mob or about the mob him/herself
|
|
-- (needed for determining family relationship)
|
|
mg_villages.inhabitants.mob_get_full_name = function( data, worker_data )
|
|
if( not( data ) or not( data.first_name )) then
|
|
return "- unkown -";
|
|
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";
|
|
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;
|
|
|
|
elseif( data.generation==2 and data.gender=="m" and data.title and data.uniq 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 and 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
|
|
|
|
|
|
-- 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
|
|
|
|
|
|
-- 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
|
|
mg_villages.inhabitants.get_new_inhabitant = function( data, gender, generation, name_exclude, min_age, village )
|
|
-- 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
|
|
|
|
local name_list = mg_villages.inhabitants.get_names_list_full( data, gender, generation, name_exclude, min_age, village );
|
|
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 { btype = building_type }
|
|
-- bpos is the building position data of one building each
|
|
-- Important: This function assigns a mob to each bed that was identified using path_info.
|
|
-- The real positions of the beds have to be calculated using
|
|
-- mg_villages.transform_coordinates( {p.x,p.y,p.z}, bpos )
|
|
-- with p beeing the corresponding entry from mg_villages.BUILDINGS[ bpos.btype ].bed_list
|
|
mg_villages.inhabitants.assign_mobs_to_beds = function( bpos, house_nr, village_to_add_data_bpos, village )
|
|
|
|
if( not( bpos ) or not( bpos.btype )) then
|
|
return bpos;
|
|
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( building_data.bed_list ) or #building_data.bed_list < 1) then
|
|
return bpos;
|
|
end
|
|
|
|
-- does the mob have a preferred spot where he likes to stand to receive customers/work?
|
|
-- i.e. teacher, shop owner, priest,...
|
|
-- this is the index of the mob's workplace in the building_data.workplace_list
|
|
local workplace_index = 1;
|
|
|
|
-- workplaces got assigned earlier on
|
|
local works_at = nil;
|
|
local title = nil;
|
|
local uniq = 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;
|
|
uniq = v.worker.uniq;
|
|
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 v.worker.title == title -- same profession
|
|
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
|
|
worker_names_with_same_profession[ village_to_add_data_bpos[ v.worker.lives_at ].beds[1].first_name ] = 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
bpos.beds = {};
|
|
-- make sure each bed is defined in the bpos.beds data structure, even if empty
|
|
for i,bed in ipairs( building_data.bed_list ) do
|
|
bpos.beds[i] = {};
|
|
-- store the index for faster lookup
|
|
bpos.beds[i].bnr = i;
|
|
local p = mg_villages.transform_coordinates( {bed[1],bed[2],bed[3],bed[4]}, bpos )
|
|
bpos.beds[i].x = p.x;
|
|
bpos.beds[i].y = p.y;
|
|
bpos.beds[i].z = p.z;
|
|
bpos.beds[i].p2 =p.p2;
|
|
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
|
|
v = mg_villages.inhabitants.get_new_inhabitant( v, "m", 2, worker_names_with_same_profession, nil, village );
|
|
-- 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 = uniq;
|
|
v.workplace= 1; -- gets the first available workplace there
|
|
-- if he works at home, the first workplace there is taken
|
|
if( works_at == house_nr ) then
|
|
workplace_index = 2;
|
|
end
|
|
else
|
|
v.title = 'lumberjack';
|
|
v.works_at = house_nr; -- works at home for now; TODO: ought to have a forrest
|
|
v.uniq = 99; -- one of many lumberjacks here
|
|
-- give the next free workplace to the mob
|
|
v.workplace= workplace_index;
|
|
workplace_index = workplace_index+1;
|
|
end
|
|
if( owns and #owns>0 ) then
|
|
v.owns = owns;
|
|
end
|
|
worker_names_with_same_profession[ v.first_name ] = 1;
|
|
end
|
|
|
|
-- 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
|
|
worker_names_with_same_profession[ v.first_name ] = 1;
|
|
-- each soldier gets a workplace (provided one is available)
|
|
v.workplace = workplace_index;
|
|
workplace_index = workplace_index + 1;
|
|
end
|
|
|
|
-- normal house containing a family
|
|
else
|
|
-- the first inhabitant will be the male worker
|
|
if( not( bpos.beds[1].first_name )) then
|
|
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
|
|
if( works_at ) then
|
|
bpos.beds[1].works_at = works_at;
|
|
bpos.beds[1].title = title;
|
|
bpos.beds[1].uniq = uniq;
|
|
bpos.beds[1].workplace= 1;
|
|
-- if he works at home, the first workplace there is taken
|
|
if( works_at == house_nr ) then
|
|
workplace_index = 2;
|
|
end
|
|
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
|
|
bpos.beds[2] = mg_villages.inhabitants.get_new_inhabitant( bpos.beds[2], "f", 2, {}, nil, village ); -- female of parent generation
|
|
-- first names ought to be uniq withhin a family
|
|
name_exclude[ bpos.beds[2].first_name ] = 1;
|
|
-- no work or title assigned to the wife
|
|
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
|
|
-- 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
|
|
-- give the next free workplace to the mob
|
|
v.workplace = workplace_index;
|
|
workplace_index = workplace_index + 1;
|
|
-- 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
|
|
elseif( i==grandmother_bed_id ) then
|
|
v = mg_villages.inhabitants.get_new_inhabitant( v, "f", 3, name_exclude, bpos.beds[1].age+18, village ); -- get the grandmother
|
|
elseif( i==grandfather_bed_id ) then
|
|
v = mg_villages.inhabitants.get_new_inhabitant( v, "m", 3, name_exclude, bpos.beds[1].age+18, village ); -- get the grandfather
|
|
else
|
|
v = mg_villages.inhabitants.get_new_inhabitant( v, "r", 1, name_exclude, nil, village ); -- get a child of random gender
|
|
-- 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
|
|
-- the grandfather (father's side) has to be old enough
|
|
if( bpos.beds[1] and bpos.beds[grandfather_bed_id] and bpos.beds[grandfather_bed_id].first_name
|
|
and bpos.beds[1].age+18 > bpos.beds[grandfather_bed_id].age) then
|
|
bpos.beds[grandfather_bed_id].age = bpos.beds[1].age+18;
|
|
end
|
|
-- ..and also the grandmother (father's side as well)
|
|
if( bpos.beds[1] and bpos.beds[grandmother_bed_id] and bpos.beds[grandmother_bed_id].first_name
|
|
and bpos.beds[1].age+18 > bpos.beds[grandmother_bed_id].age) then
|
|
bpos.beds[grandmother_bed_id].age = bpos.beds[1].age+18;
|
|
end
|
|
end
|
|
return bpos;
|
|
end
|
|
|
|
|
|
-- helper function for listing the plots a mob/house owns (sheds, wagons, fields, ..)
|
|
mg_villages.inhabitants.print_plot_list = function(village_to_add_data_bpos, plotlist)
|
|
local str = "";
|
|
if( not( plotlist )) then
|
|
return "";
|
|
end
|
|
for i,v in ipairs( plotlist ) 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
|
|
-- the , in the list would disrupt formspecs
|
|
return minetest.formspec_escape(str);
|
|
end
|
|
|
|
-- print information about which mobs "live" in a house
|
|
mg_villages.inhabitants.print_house_info = function( village_to_add_data_bpos, house_nr, village_id, pname )
|
|
|
|
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 "-?-").."] ";
|
|
local people_str = "";
|
|
local add_str = "";
|
|
if( bpos.road_nr ) then
|
|
str = str.." at road nr. "..tostring( bpos.road_nr ).." ";
|
|
end
|
|
if( bpos.btype == "road" ) then
|
|
str = str.."is a road.";
|
|
|
|
-- 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.";
|
|
else
|
|
str = str.."belongs to:";
|
|
people_str = minetest.formspec_escape( mg_villages.inhabitants.mob_get_full_name( owner, owner ).." owns this plot");
|
|
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.";
|
|
else
|
|
local worker = village_to_add_data_bpos[ bpos.worker.lives_at ].beds[1];
|
|
str = str.."provides work:";
|
|
local btype2 = mg_villages.BUILDINGS[ village_to_add_data_bpos[ bpos.worker.lives_at ].btype];
|
|
if( btype2 and btype2.typ ) then
|
|
people_str = minetest.formspec_escape( mg_villages.inhabitants.mob_get_full_name( worker, worker ).." who lives at the "..tostring( btype2.typ ).." on plot "..tostring( bpos.worker.lives_at )..", works here");
|
|
else
|
|
people_str = "- unkown -";
|
|
end
|
|
end
|
|
|
|
elseif( not( bpos.beds ) or not( bpos.beds[1])) then
|
|
str = str.."provides neither work nor housing.";
|
|
|
|
else
|
|
str = str.."is inhabitated by ";
|
|
if( #bpos.beds == 1 ) then
|
|
str = str.."only one person:";
|
|
elseif( #bpos.beds > 1 ) then
|
|
str = str..tostring( #bpos.beds ).." people:";
|
|
else
|
|
str = str.."nobody:";
|
|
end
|
|
-- make sure all mobs living here are spawned
|
|
mg_villages.inhabitants.spawn_mobs_for_one_house( bpos, nil, nil, village_id, house_nr );
|
|
for i,v in ipairs( bpos.beds ) do
|
|
if( v and v.first_name ) then
|
|
local worker_data = bpos.beds[1]; -- the father has the job
|
|
if( v and v.works_at ) then
|
|
worker_data = v;
|
|
end
|
|
people_str = people_str..
|
|
tostring( i )..". "..
|
|
minetest.formspec_escape( mg_villages.inhabitants.mob_get_full_name( v, worker_data ));
|
|
if(v and v.works_at and v.works_at==house_nr ) then
|
|
people_str = people_str.." who lives and works here,";
|
|
elseif( v and v.works_at ) then
|
|
local works_at = bpos.beds[1].works_at;
|
|
local btype2 = mg_villages.BUILDINGS[ village_to_add_data_bpos[ works_at ].btype];
|
|
people_str = people_str.." who works at the "..tostring( btype2.typ ).." on plot "..tostring(works_at)..",";
|
|
elseif( i ~= #bpos.beds ) then
|
|
people_str = people_str..",";
|
|
end
|
|
end
|
|
end
|
|
-- other plots owned
|
|
if( bpos.beds and bpos.beds[1] and bpos.beds[1].owns ) then
|
|
add_str = "The family also owns the plot(s) "..
|
|
mg_villages.inhabitants.print_plot_list(village_to_add_data_bpos, bpos.beds[1].owns)..".";
|
|
end
|
|
end
|
|
-- which entrances/front doors does the building have?
|
|
local front_doors = mg_villages.inhabitants.get_front_doors(bpos);
|
|
local door_str = "Entrances: ";
|
|
for i,p in ipairs( front_doors ) do
|
|
door_str = door_str..minetest.pos_to_string( p ).." ";
|
|
end
|
|
if( not( front_doors ) or #front_doors<1) then
|
|
door_str = door_str.."- unknown -";
|
|
end
|
|
|
|
if( people_str == "" ) then
|
|
people_str = "- nobody lives or works here permanently -";
|
|
end
|
|
local link_teleport = "";
|
|
if( pname and minetest.check_player_privs( pname, {teleport=true})) then
|
|
-- teleport to the plotmarker and not somewhere where part of the house may stand
|
|
link_teleport = 'button[8.0,0;1,0.5;teleport_to;Visit]'..
|
|
"field[21,21;0.1,0.1;pos2str;Pos;"..minetest.pos_to_string(
|
|
handle_schematics.get_pos_in_front_of_house( bpos, 0 )).."]";
|
|
end
|
|
|
|
-- allow to click through the diffrent plots
|
|
-- (a second back button doesn't hurt)
|
|
local prev_next_button = "button[8.5,4.7;1,0.5;back_to_plotlist;Back]";
|
|
if( house_nr > 1 ) then
|
|
prev_next_button = prev_next_button..'button[9.5,4.7;1,0.5;prev;Prev]';
|
|
end
|
|
if( house_nr < #village_to_add_data_bpos ) then
|
|
prev_next_button = prev_next_button..'button[10.5,4.7;1,0.5;next;Next]';
|
|
end
|
|
return 'size[12,5.0]'..
|
|
'button_exit[4.0,0;2,0.5;quit;Exit]'..
|
|
'button[9.5,0;2,0.5;back_to_plotlist;Back to plotlist]'..
|
|
-- the back button needs to know which village we are in
|
|
'field[20,20;0.1,0.1;village_id;VillageID;'..minetest.formspec_escape( village_id ).."]"..
|
|
-- when a mob is selected we need to provide the plot nr of this plot
|
|
'field[22,22;0.1,0.1;plot_nr;HouseNr;'..house_nr..']'..
|
|
-- show where the plot is located
|
|
'label[0.5,0;Location: '..minetest.formspec_escape( minetest.pos_to_string( bpos ))..']'..
|
|
-- allow to teleport there (if the player has the teleport priv)
|
|
link_teleport..
|
|
-- allow to click through the plots
|
|
prev_next_button..
|
|
'label[0.5,0.5;'..minetest.formspec_escape(str)..']'..
|
|
'label[0.5,4.1;'..add_str..']'..
|
|
'label[0.5,4.6;'..minetest.formspec_escape(door_str)..']'..
|
|
'tablecolumns[' ..
|
|
'text,align=left]'.. -- name and description of inhabitant
|
|
'table[0.1,1.0;11.4,3.0;mg_villages:formspec_list_inhabitants;'..people_str..']';
|
|
end
|
|
|
|
|
|
-- print information about a particular mob
|
|
mg_villages.inhabitants.print_mob_info = function( village_to_add_data_bpos, house_nr, village_id, bed_nr, pname )
|
|
|
|
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 this_mob_data = village_to_add_data_bpos[ house_nr ].beds[ bed_nr ];
|
|
local gender = "male";
|
|
if( this_mob_data.gender == "f" ) then
|
|
gender = "female";
|
|
end
|
|
|
|
-- identify grandparents and children
|
|
local list_of_children = "";
|
|
local grandfather = -1;
|
|
local grandmother = -1;
|
|
for i,v in ipairs( bpos.beds ) do
|
|
if( not(v.title) and v.generation==3 and v.gender=="m") then
|
|
grandfather = i;
|
|
elseif( not(v.title) and v.generation==3 and v.gender=="f") then
|
|
grandmother = i;
|
|
elseif( not(v.title) and v.generation==1 ) then
|
|
list_of_children = list_of_children..mg_villages.inhabitants.mob_get_short_name( v )..", ";
|
|
end
|
|
end
|
|
if( list_of_children == "" ) then
|
|
list_of_children = "- none -";
|
|
end
|
|
-- contains commata
|
|
list_of_children = minetest.formspec_escape( string.sub( list_of_children, 1, -3));
|
|
|
|
-- show family relationships (father, mother, grandfather, grandmother, children)
|
|
local generation = "adult";
|
|
if( this_mob_data.generation == 1 ) then
|
|
generation = "child";
|
|
if( not( this_mob_data.title )) then -- no guest, servant, soldier, ...
|
|
generation = generation..
|
|
",Father:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[1] )..
|
|
",Mother:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[2] )..
|
|
",Grandfather:,"..mg_villages.inhabitants.mob_get_short_name(bpos.beds[grandfather])..
|
|
",Grandmother:,"..mg_villages.inhabitants.mob_get_short_name(bpos.beds[grandmother]);
|
|
end
|
|
elseif( this_mob_data.generation == 3 ) then
|
|
generation = "senior";
|
|
if( not( this_mob_data.title )) then -- no guest, servant, soldier, ...
|
|
if( this_mob_data.gender=="m" ) then
|
|
generation = generation..
|
|
",Father of:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[1] )..
|
|
",Grandfather of:,"..list_of_children;
|
|
else
|
|
generation = generation..
|
|
",Mother of:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[1] )..
|
|
",Grandmother of:,"..list_of_children;
|
|
end
|
|
end
|
|
elseif( this_mob_data.generation == 2 ) then
|
|
if( this_mob_data.gender=="m" and bed_nr == 1) then
|
|
generation = generation..
|
|
",Father:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[grandfather] )..
|
|
",Mother:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[grandmother] )..
|
|
",Father of:,"..list_of_children;
|
|
elseif( bed_nr == 2) then
|
|
-- the grandparents belong to the man's side
|
|
generation = generation..
|
|
",Mother of:,"..list_of_children;
|
|
end
|
|
end
|
|
-- the mob may have a wife or husband
|
|
if( this_mob_data.generation == 2 and this_mob_data.gender == "m" and bpos.beds[2] and not(bpos.beds[2].title)) then
|
|
generation = generation..
|
|
",Husband of:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[2] );
|
|
elseif( this_mob_data.generation == 2 and this_mob_data.gender == "f" and not(this_mob_data.title)) then
|
|
generation = generation..
|
|
",Wife of:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[1] );
|
|
elseif( this_mob_data.generation == 3 and this_mob_data.gender == "m" and not(this_mob_data.title)) then
|
|
generation = generation..
|
|
",Husband of:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[grandmother] );
|
|
elseif( this_mob_data.generation == 3 and this_mob_data.gender == "f" and not(this_mob_data.title)) then
|
|
generation = generation..
|
|
",Wife of:,"..mg_villages.inhabitants.mob_get_short_name( bpos.beds[grandfather] );
|
|
end
|
|
|
|
local lives_in = minetest.formspec_escape( building_data.typ.." on plot "..house_nr.." at "..
|
|
minetest.pos_to_string( handle_schematics.get_pos_in_front_of_house( bpos, 0 )));
|
|
local profession = "- none -";
|
|
if( this_mob_data.title ) then
|
|
profession = this_mob_data.title;
|
|
if( this_mob_data and this_mob_data.title == "guest" ) then
|
|
profession = profession..",,(just visiting)";
|
|
elseif( not( this_mob_data.uniq ) or this_mob_data.uniq<1 ) then
|
|
profession = profession..",,(the only one in this village)";
|
|
else
|
|
profession = profession..",,(one amongst several in this village)";
|
|
end
|
|
end
|
|
local works_at = "-";
|
|
local pref_workspace = "";
|
|
if( this_mob_data.works_at ) then
|
|
local bpos_work = village_to_add_data_bpos[ this_mob_data.works_at ];
|
|
local building_data_work = mg_villages.BUILDINGS[ bpos_work.btype ];
|
|
if( not( building_data_work )) then
|
|
building_data_work = { typ = "unkown" };
|
|
end
|
|
works_at = minetest.formspec_escape( building_data_work.typ.." on plot "..this_mob_data.works_at..
|
|
" at "..minetest.pos_to_string( handle_schematics.get_pos_in_front_of_house( bpos_work,0)));
|
|
-- does this mob have a fixed workspace?
|
|
if( building_data_work.workplace_list and this_mob_data.workplace) then
|
|
if( building_data_work.workplace_list[ this_mob_data.workplace ] ) then
|
|
pref_workspace = ",Preferred workplace:,"..
|
|
minetest.formspec_escape(
|
|
minetest.pos_to_string(
|
|
mg_villages.transform_coordinates(
|
|
building_data_work.workplace_list[ this_mob_data.workplace], bpos_work ))..
|
|
" ["..tostring( this_mob_data.workplace ).."/"..
|
|
tostring( #building_data_work.workplace_list ).."]");
|
|
else
|
|
pref_workspace = ",Preferred workplace:,no specific one";
|
|
end
|
|
end
|
|
end
|
|
local next_to_bed_str = "";
|
|
if( this_mob_data.bnr and building_data.stand_next_to_bed_list[ this_mob_data.bnr ]) then
|
|
next_to_bed_str = ",Gets up from bed to:,"..
|
|
minetest.formspec_escape(
|
|
minetest.pos_to_string(
|
|
mg_villages.transform_coordinates(
|
|
building_data.stand_next_to_bed_list[ this_mob_data.bnr], bpos)));
|
|
end
|
|
local text =
|
|
"First name:,"..(this_mob_data.first_name or '- ? -')..
|
|
",Middle initial:,"..(this_mob_data.middle_name or '- ? -').."."..
|
|
",Gender:,"..gender..
|
|
",Age:,"..(this_mob_data.age or '- ? -')..
|
|
",Generation:,"..generation..
|
|
",Lives in:,"..lives_in..
|
|
-- TODO: the bed position might be calculated (and be diffrent from this x,y,z here)
|
|
-- TODO: the position next to the bed for getting up can be calculated as well
|
|
",Sleeps in bed at:,"..minetest.formspec_escape( minetest.pos_to_string( this_mob_data )..
|
|
", "..this_mob_data.p2.." ["..(this_mob_data.bnr or "-?-").."/"..
|
|
(#building_data.bed_list or "-?-").."]")..
|
|
-- place next to te bed where the mob can stand
|
|
next_to_bed_str..
|
|
-- position of the mob's mob spawner
|
|
",Has a spawner at:,"..minetest.formspec_escape( minetest.pos_to_string(
|
|
handle_schematics.get_pos_in_front_of_house( bpos, bed_nr)))..
|
|
pref_workspace..
|
|
",Profession:,"..profession..
|
|
",Works at:,"..works_at;
|
|
|
|
if( this_mob_data.owns ) then
|
|
text = text..",Is owner of:,"..
|
|
mg_villages.inhabitants.print_plot_list(village_to_add_data_bpos, this_mob_data.owns)..".";
|
|
end
|
|
|
|
for k,v in pairs( this_mob_data ) do
|
|
if( k~="first_name" and k~="middle_name" and k~="gender" and k~="age" and k~="generation"
|
|
and k~="x" and k~="y" and k~="z" and k~="p2" and k~="bnr"
|
|
and k~="title" and k~="works_at" and k~="owns" and k~="uniq" and k~="workplace"
|
|
and k~="typ" ) then -- typ: content_id of bed head node
|
|
-- add those entries that have not been covered yet
|
|
text = text..","..k..":,"..tostring(v);
|
|
end
|
|
end
|
|
|
|
|
|
local link_teleport = "";
|
|
-- TODO: this ought to be a teleport-to-the-mob-button
|
|
if( pname and minetest.check_player_privs( pname, {teleport=true})) then
|
|
-- teleport to the plotmarker and not somewhere where part of the house may stand
|
|
link_teleport = 'button[6.4,0;1,0.5;teleport_to;Visit]'..
|
|
"field[21,21;0.1,0.1;pos2str;Pos;"..minetest.pos_to_string(
|
|
handle_schematics.get_pos_in_front_of_house( bpos, 0 )).."]";
|
|
end
|
|
|
|
-- allow to click through the inhabitants
|
|
-- (a second back button doesn't hurt)
|
|
local prev_next_button = "button[8.5,7.2;1,0.5;back_to_houselist;Back]";
|
|
if( bed_nr > 1 ) then
|
|
prev_next_button = prev_next_button..'button[9.5,7.2;1,0.5;prev;Prev]';
|
|
end
|
|
if( bed_nr < #bpos.beds ) then
|
|
prev_next_button = prev_next_button..'button[10.5,7.2;1,0.5;next;Next]';
|
|
end
|
|
return 'size[12,7.5]'..
|
|
'button_exit[4.0,0;2,0.5;quit;Exit]'..
|
|
'button[7.5,0;5,0.5;back_to_houselist;Back to all inhabitants of house]'..
|
|
-- the back button needs to know which village we are in
|
|
'field[20,20;0.1,0.1;village_id;VillageID;'..minetest.formspec_escape( village_id ).."]"..
|
|
-- it also needs to know the plot number we might want to go back to
|
|
'field[22,22;0.1,0.1;plot_nr;HouseNr;'..house_nr..']'..
|
|
-- the prev/next buttons need information about the mob nr
|
|
'field[23,23;0.1,0.1;bed_nr;BedNr;'..bed_nr..']'..
|
|
-- show where the plot is located
|
|
'label[0.5,0;Location: '..minetest.formspec_escape( minetest.pos_to_string( bpos ))..']'..
|
|
-- allow to teleport there (if the player has the teleport priv)
|
|
link_teleport..
|
|
-- add prev/next buttons
|
|
prev_next_button..
|
|
'label[0.5,0.5;'..minetest.formspec_escape("Information about inhabitant nr. "..
|
|
tostring( bed_nr )..": "..
|
|
mg_villages.inhabitants.mob_get_short_name( this_mob_data )..
|
|
" ("..( this_mob_data.title or "- no profession -").."):")..']'..
|
|
'tablecolumns[' ..
|
|
'text,align=left;'..
|
|
'text,align=left]'.. -- name and description of inhabitant
|
|
'table[0.1,1.0;11.4,6.0;mg_villages:formspec_list_one_mob;'..text..']';
|
|
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
|
|
mg_villages.inhabitants.jobs_in_buildings[ 'chateau' ] = {'landlord'};
|
|
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'};
|
|
mg_villages.inhabitants.jobs_in_buildings[ 'townhall' ] = {'major'};
|
|
mg_villages.inhabitants.jobs_in_buildings[ 'horsestable'] = {'horsekeeper'};
|
|
|
|
|
|
|
|
-- 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 found_any_home = {}; -- farm_full, hut or house (anything with beds in)
|
|
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
|
|
table.insert( found_any_home, house_id );
|
|
|
|
-- 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
|
|
-- distribute sheds, wagons etc. equally on all places with beds
|
|
if( #found_any_home>0 ) then
|
|
local nr = math.random( #found_any_home );
|
|
village_to_add_data_bpos[ v ].belongs_to = found_any_home[ 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
|
|
|
|
-- even though it should not happen there are still sometimes workers that work on
|
|
-- another plot and wrongly get a random worker job in their house assigned as well;
|
|
-- check for those and eliminiate them
|
|
for house_nr,bpos in ipairs( village_to_add_data_bpos ) do
|
|
if( bpos and bpos.worker and bpos.worker.lives_at and bpos.worker.lives_at ~= house_nr
|
|
and village_to_add_data_bpos[ bpos.worker.lives_at ]
|
|
and village_to_add_data_bpos[ bpos.worker.lives_at ].worker) then
|
|
-- make sure the worker gets no other job or title from his house
|
|
village_to_add_data_bpos[ bpos.worker.lives_at ].worker = nil;
|
|
end
|
|
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
|
|
|
|
|
|
-- apply bpos.pos as offset and apply rotation
|
|
-- TODO: rotate param2 as well
|
|
mg_villages.transform_coordinates = function( pos, bpos )
|
|
if( not( pos ) or not(pos[1]) or not(pos[2]) or not(pos[3])) then
|
|
return nil;
|
|
end
|
|
-- 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
|
|
|
|
-- param2 is rotated the same way as in handle_schematics.generate_building_what_to_place_here_and_how
|
|
if( pos[4] ) then -- param2
|
|
local mirror_x = false;
|
|
local mirror_z = false;
|
|
if( bpos.mirror ) then
|
|
if( building_data.axis and building_data.axis == 1 ) then
|
|
mirror_x = true;
|
|
mirror_z = false;
|
|
-- used for "restore original landscape"
|
|
elseif( building_data.axis and building_data.axis == 3 ) then
|
|
mirror_z = true;
|
|
mirror_x = true;
|
|
else
|
|
mirror_x = false;
|
|
mirror_z = true;
|
|
end
|
|
end
|
|
if( mirror_x ) then
|
|
p.p2 = handle_schematics.rotation_table[ 'facedir' ][ pos[4]+1 ][ bpos.brotate+1 ][ 2 ];
|
|
elseif( mirror_z ) then
|
|
p.p2 = handle_schematics.rotation_table[ 'facedir' ][ pos[4]+1 ][ bpos.brotate+1 ][ 3 ];
|
|
else
|
|
p.p2 = handle_schematics.rotation_table[ 'facedir' ][ pos[4]+1 ][ bpos.brotate+1 ][ 1 ];
|
|
end
|
|
end
|
|
|
|
return p;
|
|
end
|
|
|
|
|
|
mg_villages.get_plot_and_building_data = function( village_id, plot_nr )
|
|
if( not( mg_villages.all_villages[ village_id ])
|
|
or not( mg_villages.all_villages[ village_id ].to_add_data.bpos[ plot_nr ] )) then
|
|
return;
|
|
end
|
|
local bpos = mg_villages.all_villages[ village_id ].to_add_data.bpos[ plot_nr ];
|
|
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
|
|
return {};
|
|
end
|
|
local entrance_list = {};
|
|
for i,e in ipairs( res.building_data.all_entrances ) do
|
|
table.insert( entrance_list, mg_villages.transform_coordinates( e, res.bpos ));
|
|
end
|
|
return entrance_list;
|
|
end
|
|
|
|
|
|
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
|
|
|
|
|
|
-- 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
|
|
--[[
|
|
-- 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
|
|
--]]
|
|
return mg_villages.all_villages[ village_id ].to_add_data.bpos[ plot_nr ].beds[ bed_nr ];
|
|
end
|
|
|
|
|
|
-- mob mods are expected to override this function! mobf_trader mobs are supported directly
|
|
mg_villages.inhabitants.spawn_one_mob = function( bed, village_id, plot_nr, bed_nr, bpos )
|
|
|
|
--print("NPC spawned in village "..tostring( village_id ).." on plot "..tostring(plot_nr)..", sleeping in bed nr. "..tostring( bed_nr ));
|
|
if( minetest.get_modpath("mobf_trader") and mobf_trader and mobf_trader.spawn_one_trader) then
|
|
return mobf_trader.spawn_one_trader( bed, village_id, plot_nr, bed_nr, bpos );
|
|
end
|
|
end
|
|
|
|
mg_villages.inhabitants.spawn_mobs_for_one_house = function( bpos, minp, maxp, village_id, plot_nr )
|
|
if( not( bpos ) or not( bpos.beds )) then
|
|
return;
|
|
end
|
|
for bed_nr,bed in ipairs( bpos.beds ) do
|
|
-- 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
|
|
|
|
bed.mob_id = mg_villages.inhabitants.spawn_one_mob( bed, village_id, plot_nr, bed_nr, bpos );
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- calculate which mob works and lives where
|
|
mg_villages.inhabitants.assign_mobs = function( village, village_id, force_repopulate )
|
|
-- make sure mobs get assigned only once (no point in doing this every time
|
|
-- when part of a village spawned)
|
|
if( village.mob_data_version and not(force_repopulate)) then
|
|
return;
|
|
end
|
|
|
|
-- if force_repopulate is true: recalculate road network, discard all worker- and
|
|
-- bed data and create new mobs
|
|
if( force_repopulate ) then
|
|
for plot_nr,bpos in ipairs(village.to_add_data.bpos) do
|
|
-- delete information about who works here
|
|
bpos.worker = nil;
|
|
-- delete information about who lives here
|
|
bpos.beds = nil;
|
|
-- delete information about the interconnection of the road network
|
|
bpos.xdir = nil;
|
|
bpos.parent_road_plot = nil;
|
|
end
|
|
end
|
|
|
|
-- analyze the road network
|
|
mg_villages.get_road_list( village_id, true );
|
|
|
|
-- 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
|
|
for plot_nr,bpos in ipairs(village.to_add_data.bpos) do
|
|
|
|
-- each bed gets a mob assigned
|
|
bpos = mg_villages.inhabitants.assign_mobs_to_beds( bpos, plot_nr, village.to_add_data.bpos, village );
|
|
end
|
|
-- later versions may become incompatible
|
|
village.mob_data_version = 1;
|
|
end
|
|
|
|
|
|
-- set metadata and/or infotexts for beds and workplace markers
|
|
mg_villages.inhabitants.prepare_metadata = function( village, village_id, minp, maxp )
|
|
local bpos_list = village.to_add_data.bpos;
|
|
for plot_nr,bpos in ipairs(bpos_list) do
|
|
-- put labels on beds
|
|
if( bpos.beds ) then
|
|
for bed_nr, bed in ipairs( bpos.beds ) do
|
|
-- if the bed is located withhin the given area OR no area is given
|
|
-- (for manual calls later on, outside of mapgen)
|
|
if( not( minp ) or not( maxp ) or ( minp.x <= bed.x and maxp.x >= bed.x
|
|
and minp.y <= bed.y and maxp.y >= bed.y
|
|
and minp.z <= bed.z and maxp.z >= bed.z)) then
|
|
local meta = minetest.get_meta( bed );
|
|
meta:set_string('infotext', 'Bed of '..
|
|
mg_villages.inhabitants.mob_get_full_name( bed, bpos.beds[1] ));
|
|
meta:set_string('village_id', village_id );
|
|
meta:set_int( 'plot_nr', plot_nr);
|
|
meta:set_int( 'bed_nr', bed_nr);
|
|
end
|
|
-- beds from the beds mod tend to have their foot as the selection box;
|
|
-- we need to set the infotext for the bed's foot as well
|
|
local p_foot = {x=bed.x,y=bed.y,z=bed.z};
|
|
if( bed.p2==0 ) then p_foot.z = p_foot.z-1;
|
|
elseif( bed.p2==1 ) then p_foot.x = p_foot.x-1;
|
|
elseif( bed.p2==2 ) then p_foot.z = p_foot.z+1;
|
|
elseif( bed.p2==3 ) then p_foot.x = p_foot.x+1;
|
|
end
|
|
if( not( minp ) or not( maxp )
|
|
or ( minp.x <= p_foot.x and maxp.x >= p_foot.x
|
|
and minp.y <= p_foot.y and maxp.y >= p_foot.y
|
|
and minp.z <= p_foot.z and maxp.z >= p_foot.z)) then
|
|
local meta = minetest.get_meta( p_foot );
|
|
-- setting the infotext is enough here
|
|
meta:set_string('infotext', 'Bed of '..
|
|
mg_villages.inhabitants.mob_get_full_name( bed, bpos.beds[1] ));
|
|
end
|
|
-- there might be a workplace belonging to the bed/mob
|
|
if( bed.works_at and bed.workplace
|
|
and bed.workplace>0
|
|
and bpos_list[ bed.works_at ]
|
|
and bpos_list[ bed.works_at ].btype
|
|
and mg_villages.BUILDINGS[ bpos_list[ bed.works_at ].btype ]
|
|
and mg_villages.BUILDINGS[ bpos_list[ bed.works_at ].btype ].workplace_list
|
|
and #mg_villages.BUILDINGS[ bpos_list[ bed.works_at ].btype ].workplace_list >= bed.workplace ) then
|
|
local p = mg_villages.BUILDINGS[ bpos_list[ bed.works_at ].btype ].workplace_list[ bed.workplace ];
|
|
local bpos_work = bpos_list[ bed.works_at ];
|
|
local p_akt = mg_villages.transform_coordinates( {p[1],p[2],p[3]}, bpos_work);
|
|
if( not( minp ) or not( maxp )
|
|
or ( minp.x <= p_akt.x and maxp.x >= bed.x
|
|
and minp.y <= p_akt.y and maxp.y >= p_akt.y
|
|
and minp.z <= p_akt.z and maxp.z >= p_akt.z)) then
|
|
local meta = minetest.get_meta( p_akt );
|
|
meta:set_string('infotext', 'Workplace of '..
|
|
mg_villages.inhabitants.mob_get_full_name( bed, bed ));
|
|
meta:set_string('village_id', village_id );
|
|
-- data about the workplace itshelf
|
|
meta:set_int( 'plot_nr', bed.works_at );
|
|
meta:set_int( 'workplace_nr', bed.workplace );
|
|
-- the data of the *mob* might be more relevant for spawning
|
|
meta:set_int( 'lives_at', plot_nr );
|
|
meta:set_int( 'bed_nr', bed_nr );
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- determine positions of front doors from stored pathinfo data and building_data.front_door_list
|
|
mg_villages.inhabitants.get_front_doors = function( bpos )
|
|
if( not( bpos ) or not( bpos.btype ) or not( mg_villages.BUILDINGS[ bpos.btype ] )) then
|
|
return {};
|
|
end
|
|
local building_data = mg_villages.BUILDINGS[ bpos.btype ];
|
|
if( not( building_data ) or not( building_data.front_door_list )) then
|
|
return {};
|
|
end
|
|
local door_list = {};
|
|
for i,d in ipairs( building_data.front_door_list ) do
|
|
door_list[i] = mg_villages.transform_coordinates( {d[1],d[2],d[3]}, bpos);
|
|
end
|
|
return door_list;
|
|
end
|
|
|
|
|
|
-- spawn mobs in villages
|
|
mg_villages.inhabitants.part_of_village_spawned = function( village, minp, maxp, data, param2_data, a, cid )
|
|
-- for each building in the village
|
|
for plot_nr,bpos in ipairs(village.to_add_data.bpos) do
|
|
-- actually spawn the mobs
|
|
local village_id = tostring( village.vx )..':'..tostring( village.vz );
|
|
mg_villages.inhabitants.spawn_mobs_for_one_house( bpos, minp, maxp, village_id, plot_nr );
|
|
end
|
|
end
|
|
|
|
|
|
--[[ deprecated
|
|
-- 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
|
|
minetest.chat_send_player( name, mg_villages.inhabitants.print_house_info( v.to_add_data.bpos, house_nr, v.nr, name ));
|
|
end
|
|
return;
|
|
end
|
|
end
|
|
-- no village found
|
|
minetest.chat_send_player( name, "There is no village with the number "..tostring( param ).." (yet?).");
|
|
end
|
|
});
|
|
|
|
--]]
|