494 lines
17 KiB
Lua

-------------------------------------------------------------------------------
-- Based on Mob Framework Mod by Sapier (sapier a t gmx net)
--
-- the prototype definition has been taken from animals_modpack-master/mob_npc/init.lua which was written by Sapier;
-- The mobs defined and spawned here rely on the mobf framework (done by Sapier)
--
--
-------------------------------------------------------------------------------
-- * recognizes bences, chairs, armchairs and toilets as things to sit on (from cottages, 3dforniture and homedecor)
-- * acceptable beds come from cottages, papyrus_bed and beds
minetest.log("action","MOD: mobf_trader mod loading ...")
local version = "0.0.2"
local npc_groups = {
not_in_creative_inventory=1
}
local modpath = minetest.get_modpath("mobf_trader");
mobf_trader = {}
mobf_trader.npc_trader_list = {}
-- TODO: don't talk that much with singleplayer
mobf_trader.check_if_free = function( pos, entity )
local objects = minetest.env:get_objects_inside_radius( pos, 1 );
for k,v in pairs(objects) do
local other = v:get_luaentity( v );
-- dropped objects are not real obstacle; other mobs are in the way, but not mere items
if( other ~= nil and other.name ~= '__builtin:item' ) then
minetest.chat_send_player('singleplayer', 'Found for entity '..tostring( entity )..': '..tostring( v )..', which is '..tostring( other.name ));
return false; -- place already occupied
end
end
return true;
end
mobf_trader.allow_stand = function( entity, state )
-- it happens quite often that no entity is given :-(
if( not( entity )) then
return true;
end
local pos = entity.object:getpos();
local npc_name = entity.name..' '..tostring( entity )..': '; -- TODO: get the real name (which has to be stored somewhere first...)
-- check if the place where the npc wants to stand is free (very simple collusion detection)
if( not( mobf_trader.check_if_free( pos, entity ))) then
minetest.chat_send_player('singleplayer', npc_name..'Excuse me? Can you let me through, please?');
return false;
end
minetest.chat_send_player('singleplayer', npc_name..'Let\'s wait a bit for more intresting things to happen.');
return true;
end
mobf_trader.allow_sit = function( entity, state )
-- it happens quite often that no entity is given :-(
if( not( entity )) then
return true;
end
local pos = entity.object:getpos();
local t_pos = minetest.env:find_node_near( pos, 2, {'cottages:bench', '3dforniture:chair', '3dforniture:armchair', 'homedecor:chair', 'homedecor:armchair',
'3dforniture:toilet', '3dforniture:toilet_open', 'homedecor:toilet', 'homedecor:toilet_open'});
local npc_name = entity.name..' '..tostring( entity )..': '; -- TODO: get the real name (which has to be stored somewhere first...)
if( not( t_pos )) then
minetest.chat_send_player('singleplayer', npc_name..'Sorry, I found no place to sit on.');
return false;
end
-- check if the place where the npc wants to sit is free
if( not( mobf_trader.check_if_free( t_pos, entity ))) then
minetest.chat_send_player('singleplayer', npc_name..'Sorry, I\'m looking for a free seat. This one seems occupied.');
return false;
end
-- find out how to rotate in order to be able to sit depending on rotation
local node = minetest.env:get_node( t_pos );
local yaw = 0;
local param2 = node.param2;
if( param2==0 ) then
yaw = 180;
elseif( param2==1 ) then
yaw = 90;
elseif( param2==2 ) then
yaw = 0;
elseif( param2==3 ) then
yaw = 270;
end
-- this is perfect for armchairs
if( node.name == '3dforniture:toilet_open' or node.name == 'homedecor:toilet_open' ) then
minetest.chat_send_player('singleplayer', npc_name..'I am busy. Come back later!');
elseif( node.name == '3dforniture:toilet' or node.name == 'homedecor:toilet' ) then
minetest.chat_send_player('singleplayer', npc_name..'Well, I suppose you can sit on a toilet if there\'s nothing else around...');
elseif( node.name == '3dforniture:armchair' or node.name == 'homedecor:armchair' ) then
minetest.chat_send_player('singleplayer', npc_name..'I am now sitting and relaxing in a comftable armchair.');
-- on a chair, people usually sit around less orderly
elseif( node.name == '3dforniture:chair' or node.name == 'homedecor:chair' ) then
yaw = math.random( yaw-30, yaw+30 );
if( yaw < 0 ) then
yaw = 360 + yaw;
end
minetest.chat_send_player('singleplayer', npc_name..'I am now sitting on a chair and waiting.');
-- in order to sit properly on the bench, the NPC has to move a bit backwards; more rotation than on chair may occour
elseif( node.name == 'cottages:bench' ) then
-- adjust the position of the npc
if( param2== 0) then
t_pos.z = t_pos.z + 0.3;
elseif( param2==1 ) then
t_pos.x = t_pos.x + 0.3;
elseif( param2==2 ) then
t_pos.z = t_pos.z - 0.3;
elseif( param2==3 ) then
t_pos.x = t_pos.x - 0.3;
end
-- on a bench, sitting less ordered may be more common
yaw = math.random( yaw-60, yaw+60 );
if( yaw < 0 ) then
yaw = 360 + yaw;
end
minetest.chat_send_player('singleplayer', npc_name..'I am now sitting on a bench. Hope there\'ll be supper soon!');
else
minetest.chat_send_player('singleplayer', npc_name..'Help! I\'m sitting on an object I don\'t know!');
end
-- rotate the npc in the right direction
entity.object:setyaw( math.rad( yaw ));
-- move the entity on the furniture; the entity has already been rotated accordingly
entity.object:setpos( {x=t_pos.x, y=t_pos.y+1,z=t_pos.z} );
return true;
end
-- TODO: only sleep at night?
mobf_trader.allow_sleep = function( entity, state )
-- it happens quite often that no entity is given :-(
if( not( entity )) then
return true;
end
local pos = entity.object:getpos();
local t_pos = minetest.env:find_node_near( pos, 2, {'cottages:bed_head', 'papyrus_bed:bed_top', 'beds:bed_top'});
local npc_name = entity.name..' '..tostring( entity )..': '; -- TODO: get the real name (which has to be stored somewhere first...)
if( not( t_pos )) then
minetest.chat_send_player('singleplayer', npc_name..'Sorry, I found no bed where I could sleep in. Hope I\'ll find one soon!');
return false;
end
-- check if the place where the npc wants to sleep is free
if( not( mobf_trader.check_if_free( t_pos, entity ))) then
minetest.chat_send_player('singleplayer', npc_name..'This bed seems to be occupied. I\'ll search for a free one.');
return false;
end
-- find out how to rotate in order to be able to sleep depending on rotation
local node = minetest.env:get_node( t_pos );
-- aim for the middle of the bed
local yaw = 0;
local param2 = node.param2;
if( param2==0 ) then
yaw = 180;
t_pos.z = t_pos.z - 0.5;
elseif( param2==1 ) then
yaw = 90;
t_pos.x = t_pos.x - 0.5;
elseif( param2==2 ) then
yaw = 0;
t_pos.z = t_pos.z + 0.5;
elseif( param2==3 ) then
yaw = 270;
t_pos.x = t_pos.x + 0.5;
end
minetest.chat_send_player('singleplayer', npc_name..'Good night!');
-- rotate the npc in the right direction
entity.object:setyaw( math.rad( yaw ));
-- move the entity on the furniture; the entity has already been rotated accordingly
entity.object:setpos( {x=t_pos.x, y=t_pos.y+1.5,z=t_pos.z} );
return true;
end
mobf_trader.npc_trader_prototype = {
name="npc_trader",
modname="mobf_trader",
generic = {
description="Trader",
base_health=200,
kill_result="",
armor_groups= {
fleshy=3,
},
groups = npc_groups,
envid="on_ground_1",
custom_on_activate_handler=0, --mob_inventory.init_trader_inventory,
},
movement = {
min_accel=0.3,
max_accel=0.7,
max_speed=1.5,
min_speed=0.01,
pattern="stop_and_go",
canfly=false,
},
spawning = {
rate=0,
density=100, --750,
algorithm="building_spawner",
height=2
},
states = {
{
name = "default",
movgen = "none",
chance = 0,
animation = "stand",
graphics = {
visual = "upright_sprite",
sprite_scale={x=1.5,y=2},
sprite_div = {x=1,y=1},
visible_height = 2,
visible_width = 1,
},
graphics_3d = {
visual = "mesh",
mesh = "npc_character.b3d",
textures = {"mob_npc_trader_mesh.png"},
collisionbox = {-0.3,-1.0,-0.3, 0.3,0.8,0.3},
visual_size= {x=1, y=1},
},
},
{
name = "walk",
custom_preconhandler = nil,
movgen = "jordan4ibanez_mov_gen", --"probab_mov_gen", -- TODO
typical_state_time = 5,
chance = 0.15,
animation = "walk"
},
{
name = "stand",
custom_preconhandler = mobf_trader.allow_stand,
movgen = "none",
typical_state_time = 5,
chance = 0.25,
animation = "stand"
},
{
name = "sit",
custom_preconhandler = mobf_trader.allow_sit,
movgen = "none",
typical_state_time = 15,
chance = 0.25,
animation = "sit"
},
{
name = "sleep",
custom_preconhandler = mobf_trader.allow_sleep,
movgen = "none",
typical_state_time = 15,
chance = 0.25,
animation = "sleep",
},
{
name = "mine",
custom_preconhandler = nil,
movgen = "none",
typical_state_time = 5,
chance = 0.05,
animation = "mine"
},
{
name = "walk_mine",
custom_preconhandler = nil,
movgen = "probab_mov_gen", -- TODO
typical_state_time = 5,
chance = 0.05,
animation = "walk_mine"
},
},
animation = {
walk = {
start_frame = 168,
end_frame = 187,
},
stand = {
start_frame = 0,
end_frame = 79,
},
sit = {
start_frame = 81,
end_frame = 160,
},
sleep = {
start_frame = 162,
end_frame = 166,
},
mine = {
start_frame = 189,
end_frame = 198,
},
walk_mine = {
start_frame = 200,
end_frame = 219,
},
},
-- 0- 79 standing
-- 79-149 sitting
-- 149-169 sitting -> lying down
-- 167-167 lying down
-- 168-187 walking
-- 187-197 (or more): digging animation
-- 197-217 walking and digging
-- 217-237 very fast digging
-- what the default trader offers
trader_inventory = {
goods = {},
goods = {
},
random_names = { "Hans","Franz","Xaver","Fritz","Thomas","Martin"},
}
}
-- why is such a basic function not provided?
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- TODO: catch errors (i.e when the trader has already been registered)
--register with animals mod
mobf_trader.add_trader = function( prototype, description, speciality, goods, names, texture )
local new_trader = {};
-- default texture/skin for the trader
if( not(texture) or (texture == "" )) then
texture = "mob_npc_trader_mesh.png";
end
-- print( "prototype: "..minetest.serialize( mobf_trader.npc_trader_prototype ));
-- copy data of the trader
new_trader = deepcopy( prototype );
new_trader.name = "npc_trader_"..speciality;
new_trader.modname = "mobf_trader";
new_trader.generic.description = description;
new_trader.states[1].graphics_3d.textures = { texture };
new_trader.trader_inventory = { goods = goods, random_names = names };
minetest.log( "action", "\t[Mod mobf_trader] Adding mob "..new_trader.name)
-- print( "NEW TRADER: "..minetest.serialize( new_trader ));
new_trader.generic.custom_on_activate_handler = mob_inventory.init_trader_inventory;
mobf_add_mob( new_trader );
table.insert( mobf_trader.npc_trader_list, speciality );
end
-- spawn a trader
mobf_trader.spawn_trader = function( pos, name )
-- slightly above the position of the player so that it does not end up in a solid block
local object = minetest.env:add_entity( {x=pos.x, y=(pos.y+1.5), z=pos.z}, "mobf_trader:npc_trader_"..name.."__default" );
if object ~= nil then
object:setyaw( -1.14 );
end
print("[mobf_trader] Spawned trader "..tostring( name or "?" ).." at position "..minetest.serialize( pos )..".");
end
-- so that this function can be called even when mobf_trader has not been loaded
mobf_trader_spawn_trader = mobf_trader.spawn_trader;
-- add command so that a trader can be spawned
minetest.register_chatcommand("trader", {
params = "<trader type>",
description = "Spawns an npc trader of the given type.",
privs = {},
func = function(name, param)
-- TODO: nicer printing than minetest.serialize
-- TODO: require a priv to spawn them
local params_expected = "<trader type>";
if( param == "" or param==nil) then
minetest.chat_send_player(name, "Please supply the type of trader! Supported: "..minetest.serialize( mobf_trader.npc_trader_list ) );
return;
end
local found = false;
for i,v in ipairs( mobf_trader.npc_trader_list ) do
if( v == param ) then
found = true;
end
end
if( not( found )) then
minetest.chat_send_player(name, "A trader of type \""..tostring( param ).."\" does not exist. Supported: "..minetest.serialize( mobf_trader.npc_trader_list ) );
return;
end
local player = minetest.env:get_player_by_name(name);
local pos = player:getpos();
minetest.chat_send_player(name, "Placing trader \""..tostring( param ).."\"at your position: "..minetest.serialize( pos )..".");
mobf_trader.spawn_trader( pos, param );
end
});
-- import all the traders; if you do not want any of them, comment out the line representing the unwanted traders (they are only created if their mods exist)
dofile(minetest.get_modpath("mobf_trader").."/trader_misc.lua"); -- trades a mixed assortment
dofile(minetest.get_modpath("mobf_trader").."/trader_clay.lua"); -- no more destroying beaches while digging for clay and sand!
dofile(minetest.get_modpath("mobf_trader").."/trader_moretrees.lua"); -- get wood from moretrees without chopping down trees
dofile(minetest.get_modpath("mobf_trader").."/trader_animals.lua"); -- buy animals - no need to catch them with a lasso
dofile(minetest.get_modpath("mobf_trader").."/trader_farming.lua"); -- they sell seeds and fruits - good against hunger!
-- TODO: default:cactus default:papyrus and other plants
-- TODO: accept food in general as trade item (accept groups?)
-- TODO: trader foer angeln?
-- TODO: trader fuer moreores (ingots)
-- TODO: bergbau-trader; verkauft eisen und kohle, kauft brot/food/apples
-- TODO: trader fuer homedecor
-- TODO: trader fuer 3dforniture