-
Notifications
You must be signed in to change notification settings - Fork 14
/
inhabitants.lua
1403 lines (1271 loc) · 57.8 KB
/
inhabitants.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--[[
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;