Initial commit

This commit is contained in:
stujones11 2013-09-28 17:11:46 +01:00
commit 8871dd1285
479 changed files with 22322 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
## Generic ignorable patterns and files
*~
.*.swp
*bak*
tags
*.vim

22
README.txt Normal file
View File

@ -0,0 +1,22 @@
Mintest Modpack - NPC Framework [minetest-npcf]
===============================================
Version: 0.1.0
License of Source Code: LGPL
---------------------------
(c) Copyright Stuart Jones, 2013
License of Textures: WTFPL
--------------------------
(c) Copyright Stuart Jones, 2013
NPC Framework [npcf]
====================
This mod adds some, hopefully useful, non-player characters to the minetest game.
The mod also provides a framework for others to create and manage their own custom NPCs.
Additional Text Colors [textcolors]
===================================
This mod adds colored text for NPC nametags. Can be safely removed to save space,
NPC nametags will only use the default white text supplied by npcf itself.

0
modpack.txt Normal file
View File

199
npcf/README.md Normal file
View File

@ -0,0 +1,199 @@
NPC Framework for Minetest 0.4.8
--------------------------------
This mod adds some, hopefully useful, non-player characters to the minetest game.
The mod also provides a framework for others to create and manage their own custom NPCs.
Features currently include overhead name tags, formspec handling, ownership and management,
file based backups and automatic duplicate entity removal. (experimental)
These NPC's are not craftable although by default will be available in the creative menu.
Servers would be encouraged to override this default and allocate the NPCs on /give basis.
### Info NPC
The Info NPC is a simple information serving character. You could think of them as a
human book providing information about a particular server location, or whatever else you like.
Supports multiple pages of text. 12 lines per page, ~50 chars per line.
### Deco NPC
A purely decorative NPC, can be set to roam freely and/or follow random players it encounters.
### Guard NPC
Protect yourself and your property against other players and mobs. Features 3d weapon and armor.
Can be left to guard a certain area or set to follow their owner.
### Trader NPC
Provides a quantity based exchange system. The owner can set a given number of exchanges.
This would likely be best used in conjunction with one of physical currency mods.
Buy [default:mese] Qty [1] - Sell [default:gold_ingot] Qty [10]
Buy [default:gold_ingot] Qty [20] - Sell [default:mese] Qty [1]
Note that the NPC's owner cannot trade with their own NPC, that would be rather pointless anyway.
### Builder NPC
Not really much point to this atm other than it's really fun to watch. By default, it be can only
build a basic hut, however this is compatible (or so it seems) with all the schematics provided by
Dan Duncombe's instabuild mod. These should be automatically available for selection if you have
the instabuild mod installed.
Server Commands
---------------
Server commands are issued as follows.
/npcf <command> [npc_name] [args]
### list
List all registered NPCs and display online status.
### clearobjects
Clear all loaded NPCs. (requires server priv)
### loadobjects
Reload all currently unloaded NPCs. (requires server priv)
### getpos <npc_name>
Display the integer position of the named NPC.
### reload <npc_name>
Reload an unloaded NPC. (requires ownership or server priv)
## clear <npc_name>
Clear (unload) named NPC. (requires ownership or server priv)
## delete <npc_name>
Permanently unload and delete named NPC. (requires server priv)
### setskin <npc_name> <skin_filename|random>
Set the skin texture of the named NPC.
/npcf setskin npc_name character.png
If you have Zeg9's skins mod installed you can select a random texture from said mod.
/npcf setskin npc_name random
### setpos <npc_name> <pos>
Set named NPC location. (x, y, z)
/npcf setpos npc_name 0, 5, 0
API Reference
-------------
Use the global npcf api to create your own NPC.
npcf:register_npc("my_mod:my_cool_npc" ,{
description = "My Cool NPC",
})
This is a minimal example, see the NPCs included for more complex usage examples.
### npcf
The global NPC framework namespace. Other global variables include.
NPCF_ANIM_STAND = 1
NPCF_ANIM_SIT = 2
NPCF_ANIM_LAY = 3
NPCF_ANIM_WALK = 4
NPCF_ANIM_WALK_MINE = 5
NPCF_ANIM_MINE = 6
NPCF_ANIM_RUN = 7
NPCF_SHOW_IN_CREATIVE = true
NPCF_SHOW_NAMETAGS = true
NPCF_BUILDER_REQ_MATERIALS = false
NPCF_DECO_FREE_ROAMING = true
NPCF_GUARD_ATTACK_PLAYERS = true
NPCF_DUPLICATE_REMOVAL_TIME = 10
All of the above can be overridden by including a npcf.conf file in the npcf directory.
See: npcf.conf.example
### npcf:register_npc(name, def)
Register a non-player character. Used as a replacement for minetest.register_entity, it includes
all the callbacks and properties (at the time of writing) available there. The only exceptions
are the get_staticdata callback (used internally) and you are currently not able to create arbitrary
property variables, instead the framework provides 'metadata' and 'var' tables for those purposes.
The metadata table is persistent following a reload and automatically stores submitted form data.
The var table should be used for non-persistent data storage only. Note that self.timer is
automatically incremented by the framework but should be externally reset.
Additional properties included by the framework. (defaults)
on_registration(luaentity, pos, sender),
on_construct = function(self),
on_receive_fields = function(self, fields, sender),
animation = {
stand_START = 0,
stand_END = 79,
sit_START = 81,
sit_END = 160,
lay_START = 162,
lay_END = 166,
walk_START = 168,
walk_END = 187,
mine_START = 189,
mine_END = 198,
walk_mine_START = 200,
walk_mine_END = 219,
},
animation_speed = 30,
armor_groups = {immortal=1},
inventory_image = "npcf_info_inv.png",
show_nametag = true,
nametag_color = "white",
metadata = {},
var = {},
timer = 0,
### npcf:set_animation(luaentity, state)
Sets the entity animation state.
on_activate = function(self, staticdata, dtime_s)
npcf:set_animation(self, NPCF_ANIM_STAND)
end,
### npcf:get_index()
Returns a table of all registered NPCs. (loaded or unloaded)
{[npc_name] = owner_name, ... }
### npcf:get_luaentity(npc_name)
Returns a minetest ObjectRef of the npc entity.
### npcf:get_face_direction(v1, v2)
Helper routine used internally and by some of the example NPCs.
Returns a yaw value in radians for position v1 facing position v2.
### npcf:get_walk_velocity(speed, y, yaw)
Returns a velocity vector for the given speed, y velocity and yaw.
### npcf:show_formspec(player_name, npc_name, formspec)
Shows a formspec, similar to minetest.show_formspec() but with the npc_name included.
Submitted data can then be captured in the NPC's own 'on_receive_fields' callback.
Note that Form text fields, dropdown, list and checkbox selections are automatically
stored in the NPC's metadata table. Image/Button clicks, however, are not.

3
npcf/depends.txt Normal file
View File

@ -0,0 +1,3 @@
default
instabuild?
skins?

9
npcf/init.lua Normal file
View File

@ -0,0 +1,9 @@
NPCF_PATH = minetest.get_modpath(minetest.get_current_modname())
dofile(NPCF_PATH.."/npcf.lua")
dofile(NPCF_PATH.."/npcs/info_npc.lua")
dofile(NPCF_PATH.."/npcs/deco_npc.lua")
dofile(NPCF_PATH.."/npcs/guard_npc.lua")
dofile(NPCF_PATH.."/npcs/trade_npc.lua")
dofile(NPCF_PATH.."/npcs/builder_npc.lua")

11262
npcf/models/npcf_deco.x Normal file

File diff suppressed because it is too large Load Diff

9156
npcf/models/npcf_guard.x Normal file

File diff suppressed because it is too large Load Diff

23
npcf/npcf.conf.example Normal file
View File

@ -0,0 +1,23 @@
-- NPC Framework config example.
-- Deafult Settings:
-- Show NPC spawners in creative menu.
NPCF_SHOW_IN_CREATIVE = true
-- Show NPC Nametags.
NPCF_SHOW_NAMETAGS = true
-- Make Builder NPC require all building materials.
NPCF_BUILDER_REQ_MATERIALS = false
-- Allow Deco NPC to roam freely away from players.
NPCF_DECO_FREE_ROAMING = true
-- Allow Guard NPC to attack players.
NPCF_GUARD_ATTACK_PLAYERS = true
-- Remove Duplicate NPCs. Experimental patch for entity duplication bug in minetest.
-- Hopefully someday we can remove this.
NPCF_REMOVE_DUPLICATES = true

697
npcf/npcf.lua Normal file
View File

@ -0,0 +1,697 @@
NPCF_ANIM_STAND = 1
NPCF_ANIM_SIT = 2
NPCF_ANIM_LAY = 3
NPCF_ANIM_WALK = 4
NPCF_ANIM_WALK_MINE = 5
NPCF_ANIM_MINE = 6
NPCF_SHOW_IN_CREATIVE = true
NPCF_SHOW_NAMETAGS = true
NPCF_BUILDER_REQ_MATERIALS = false
NPCF_DECO_FREE_ROAMING = true
NPCF_GUARD_ATTACK_PLAYERS = true
NPCF_DUPLICATE_REMOVAL_TIME = 10
local input = io.open(NPCF_PATH.."/npcf.conf", "r")
if input then
dofile(NPCF_PATH.."/npcf.conf")
input:close()
end
npcf = {}
local form_callback = {}
local default_npc = {
hp_max = 1,
physical = true,
weight = 5,
collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
visual = "mesh",
visual_size = {x=1, y=1},
mesh = "character.x",
textures = {"character.png"},
colors = {},
is_visible = true,
makes_footstep_sound = true,
automatic_rotate = false,
stepheight = 0,
automatic_face_movement_dir = false,
armor_groups = {immortal=1},
animation = {
stand_START = 0,
stand_END = 79,
sit_START = 81,
sit_END = 160,
lay_START = 162,
lay_END = 166,
walk_START = 168,
walk_END = 187,
mine_START = 189,
mine_END = 198,
walk_mine_START = 200,
walk_mine_END = 219,
},
animation_speed = 30,
decription = "Default NPC",
inventory_image = "npcf_info_inv.png",
show_nametag = true,
nametag_color = "white",
metadata = {},
var = {},
}
local nametag = {
npcf_id = "nametag",
physical = false,
collisionbox = {x=0, y=0, z=0},
visual = "sprite",
textures = {"npcf_tag_bg.png"},
visual_size = {x=0.72, y=0.12, z=0.72},
npc_name = nil,
on_activate = function(self, staticdata, dtime_s)
if staticdata == "expired" then
self.object:remove()
end
end,
get_staticdata = function(self)
return "expired"
end,
}
local form_reg = "size[8,3]"
.."label[0,0;NPC Name, max 12 characters (A-Za-z0-9_-)]"
.."field[0.5,1.5;7.5,0.5;name;Name;]"
.."button_exit[5,2.5;2,0.5;cancel;Cancel]"
.."button_exit[7,2.5;1,0.5;submit;Ok]"
local timer = 0
local function get_valid_player_name(player)
if player then
if player:is_player() then
local player_name = player:get_player_name()
if minetest.get_player_by_name(player_name) then
return player_name
end
end
end
end
local function get_valid_entity(luaentity)
if luaentity then
if luaentity.name and luaentity.owner and luaentity.origin then
return true
end
end
end
local function add_nametag(parent)
if parent.npc_name then
local pos = parent.object:getpos()
local tag = minetest.add_entity(pos, "npcf:nametag")
if tag then
local color = "W"
if minetest.get_modpath("textcolors") then
local c = string.upper(parent.nametag_color:sub(1,1))
if string.match("RGBCYMW", c) then
color = c
end
end
local texture = "npcf_tag_bg.png"
local x = math.floor(66 - ((parent.npc_name:len() * 11) / 2))
local i = 0
parent.npc_name:gsub(".", function(char)
if char:byte() > 64 and char:byte() < 91 then
char = "U"..char
end
texture = texture.."^[combine:84x14:"..(x+i)..",0="..color.."_"..char..".png"
i = i + 11
end)
tag:set_attach(parent.object, "", {x=0,y=9,z=0}, {x=0,y=0,z=0})
tag = tag:get_luaentity()
tag.npc_name = parent.npc_name
tag.object:set_properties({textures={texture}})
end
end
end
local function load_npc(npc_name)
if npcf:get_luaentity(npc_name) then
return
end
local path = NPCF_PATH.."/npcs/npc_data/"
local input = io.open(path..npc_name..".npc", "r")
if input then
local data = minetest.deserialize(input:read('*all'))
io.close(input)
if data then
local npc = minetest.add_entity(data.origin.pos, data.name)
if npc then
local luaentity = npc:get_luaentity()
if luaentity then
luaentity.owner = data.owner
luaentity.npc_name = npc_name
luaentity.origin = data.origin
luaentity.skin = data.skin
luaentity.animation = data.animation
luaentity.metadata = data.metadata
luaentity.object:setyaw(data.origin.yaw)
if NPCF_SHOW_NAMETAGS == true and luaentity.show_nametag == true then
add_nametag(luaentity)
end
return 1
end
end
end
end
end
local function save_npc(luaentity)
local path = NPCF_PATH.."/npcs/npc_data/"
local npc = {
name = luaentity.name,
owner = luaentity.owner,
origin = luaentity.origin,
skin = luaentity.skin,
animation = luaentity.animation,
metadata = luaentity.metadata,
}
local filename = luaentity.npc_name
local output = io.open(path..filename..".npc", 'w')
if output then
output:write(minetest.serialize(npc))
io.close(output)
else
minetest.log("error", "Failed to save "..filename)
end
end
function npcf:register_npc(name, def)
for k,v in pairs(default_npc) do
if not def[k] then
def[k] = v
end
end
minetest.register_entity(name, {
hp_max = def.hp_max,
physical = def.physical,
weight = def.weight,
collisionbox = def.collisionbox,
visual = def.visual,
visual_size = def.visual_size,
mesh = def.mesh,
textures = def.textures,
colors = def.colors,
is_visible = def.is_visible,
makes_footstep_sound = def.makes_footstep_sound,
automatic_rotate = def.automatic_rotate,
stepheight = def.stepheight,
automatic_face_movement_dir = def.automatic_face_movement_dir,
armor_groups = def.armor_groups,
on_receive_fields = def.on_receive_fields,
animation = def.animation,
animation_speed = def.animation_speed,
decription = def.description,
show_nametag = def.show_nametag,
nametag_color = def.nametag_color,
metadata = def.metadata,
properties = {textures = def.textures},
var = def.var,
npcf_id = "npc",
npc_name = nil,
owner = nil,
origin = {},
timer = 0,
state = NPCF_ANIM_STAND,
on_activate = function(self, staticdata, dtime_s)
self.object:set_armor_groups(self.armor_groups)
if def.on_receive_fields then
table.insert(form_callback, def.on_receive_fields)
end
if staticdata then
local npc = minetest.deserialize(staticdata)
if npc then
if npc.npc_name and npc.owner and npc.origin then
self.npc_name = npc.npc_name
self.owner = npc.owner
self.origin = npc.origin
else
self.object:remove()
minetest.log("action", "Removed unknown npc")
return
end
if npc.metadata then
self.metadata = npc.metadata
end
if npc.properties then
self.properties = npc.properties
self.object:set_properties(npc.properties)
end
if NPCF_SHOW_NAMETAGS == true and self.show_nametag == true then
add_nametag(self)
end
end
end
local x = self.animation.stand_START
local y = self.animation.stand_END
local speed = self.animation_speed
if x and y then
self.object:set_animation({x=x, y=y}, speed)
end
if type(def.on_construct) == "function" then
def.on_construct(self)
end
if type(def.on_activate) == "function" then
minetest.after(0.5, function()
if self.npc_name and self.owner then
def.on_activate(self, staticdata, dtime_s)
end
end)
end
end,
on_rightclick = function(self, clicker)
if get_valid_entity(self) then
local player_name = get_valid_player_name(clicker)
if player_name then
local ctrl = clicker:get_player_control()
if ctrl.sneak then
local yaw = npcf:get_face_direction(self.object:getpos(), clicker:getpos())
self.object:setyaw(yaw)
self.origin.yaw = yaw
return
end
if type(def.on_rightclick) == "function" then
def.on_rightclick(self, clicker)
end
end
end
end,
on_punch = function(self, hitter)
if get_valid_entity(self) then
if hitter:is_player() then
local player_name = get_valid_player_name(hitter)
if player_name == self.owner then
local ctrl = hitter:get_player_control()
if ctrl.sneak then
local yaw = hitter:get_look_yaw() - math.pi * 0.5
local v = npcf:get_walk_velocity(0.1,0, yaw)
local pos = self.object:getpos()
pos = vector.add(v, pos)
self.object:setpos(pos)
self.origin.pos = pos
end
end
end
if type(def.on_punch) == "function" then
def.on_punch(self, hitter)
end
end
end,
on_step = function(self, dtime)
self.timer = self.timer + dtime
if type(def.on_step) == "function" and get_valid_entity(self) then
def.on_step(self, dtime)
end
end,
get_staticdata = function(self)
local npc_data = {
name = self.name,
npc_name = self.npc_name,
owner = self.owner,
origin = self.origin,
pos = self.object:getpos(),
properties = self.properties,
metadata = self.metadata,
}
return minetest.serialize(npc_data)
end,
})
local groups = {falling_node=1}
if NPCF_SHOW_IN_CREATIVE == false then
groups.not_in_creative_inventory=1
end
minetest.register_node(name.."_spawner", {
description = def.description,
inventory_image = minetest.inventorycube("npcf_inv_top.png", def.inventory_image, def.inventory_image),
tiles = {def.inventory_image, def.inventory_image, def.inventory_image},
groups = groups,
sounds = default.node_sound_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", form_reg)
meta:set_string("infotext", def.description.." spawner")
end,
after_place_node = function(pos, placer, itemstack)
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
itemstack:take_item()
return itemstack
end,
on_punch = function(pos, node, puncher)
local meta = minetest.get_meta(pos)
local owner = meta:get_string("owner")
local player_name = puncher:get_player_name()
local admin = minetest.check_player_privs(player_name, {server=true})
if admin or player_name == owner then
minetest.dig_node(pos)
if player_name == owner then
puncher:get_inventory():add_item("main", node)
end
end
end,
on_receive_fields = function(pos, formname, fields, sender)
if fields.cancel then
return
end
local meta = minetest.get_meta(pos)
local owner = meta:get_string("owner")
local player_name = sender:get_player_name()
if player_name == owner then
if fields.name:len() <= 12 and fields.name:match("^[A-Za-z0-9%_%-]+$") then
local path = NPCF_PATH.."/npcs/npc_data/"
local input = io.open(path..fields.name..".npc", "r")
if input then
io.close(input)
minetest.chat_send_player(player_name, "Error: Name Already Taken!")
return
end
else
minetest.chat_send_player(player_name, "Error: Invalid NPC Name!")
return
end
minetest.dig_node(pos)
local npc_pos = {x=pos.x, y=pos.y + 0.5, z=pos.z}
local npc = minetest.add_entity(npc_pos, name)
if npc then
local luaentity = npc:get_luaentity()
if luaentity then
luaentity.owner = player_name
luaentity.npc_name = fields.name
luaentity.origin = {pos=npc_pos, yaw=luaentity.object:getyaw()}
save_npc(luaentity)
local index = npcf:get_index() or {}
index[fields.name] = owner
local path = NPCF_PATH.."/npcs/npc_data/"
local output = io.open(path.."index.txt", 'w')
if output then
output:write(minetest.serialize(index))
io.close(output)
else
minetest.log("error", "Failed to add "..filename.." to npc index")
end
if luaentity.show_nametag then
add_nametag(luaentity)
end
if type(def.on_registration) == "function" then
def.on_registration(luaentity, pos, sender)
end
end
end
end
end,
})
end
function npcf:set_animation(luaentity, state)
if luaentity and state then
if state ~= luaentity.state then
local speed = luaentity.animation_speed
local anim = luaentity.animation
if speed and anim then
if state == NPCF_ANIM_STAND and anim.stand_START and anim.stand_END then
luaentity.object:set_animation({x=anim.stand_START, y=anim.stand_END}, speed)
elseif state == NPCF_ANIM_SIT and anim.sit_START and anim.sit_END then
luaentity.object:set_animation({x=anim.sit_START, y=anim.sit_END}, speed)
elseif state == NPCF_ANIM_LAY and anim.lay_START and anim.lay_END then
luaentity.object:set_animation({x=anim.lay_START, y=anim.lay_END}, speed)
elseif state == NPCF_ANIM_WALK and anim.walk_START and anim.walk_END then
luaentity.object:set_animation({x=anim.walk_START, y=anim.walk_END}, speed)
elseif state == NPCF_ANIM_WALK_MINE and anim.walk_mine_START and anim.walk_mine_END then
luaentity.object:set_animation({x=anim.walk_mine_START, y=anim.walk_mine_END}, speed)
elseif state == NPCF_ANIM_MINE and anim.mine_START and anim.mine_END then
luaentity.object:set_animation({x=anim.mine_START, y=anim.mine_END}, speed)
end
luaentity.state = state
end
end
end
end
function npcf:get_index()
local index = nil
local path = NPCF_PATH.."/npcs/npc_data/"
local input = io.open(path.."index.txt", "r")
if input then
index = minetest.deserialize(input:read('*all'))
io.close(input)
end
return index
end
function npcf:get_luaentity(npc_name)
if npc_name then
for _,ref in pairs(minetest.luaentities) do
if ref.object then
if ref.npcf_id == "npc" and ref.npc_name == npc_name then
return ref.object:get_luaentity()
end
end
end
end
end
function npcf:get_face_direction(v1, v2)
if v1.x and v2.x and v1.z and v2.z then
dx = v1.x - v2.x
dz = v2.z - v1.z
return math.atan2(dx, dz)
end
end
function npcf:get_walk_velocity(speed, y, yaw)
if speed and y and yaw then
if speed > 0 then
yaw = yaw + math.pi * 0.5
local x = math.cos(yaw) * speed
local z = math.sin(yaw) * speed
return {x=x, y=y, z=z}
end
return {x=0, y=y, z=0}
end
end
function npcf:show_formspec(player_name, npc_name, formspec)
if player_name and npc_name and formspec then
minetest.show_formspec(player_name, "npcf_"..npc_name, formspec)
end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname then
local npc_name = formname:gsub("npcf_", "")
if npc_name ~= formname then
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
for k,v in pairs(fields) do
if k ~= "" then
v = string.gsub(v, "^CHG:", "")
luaentity.metadata[k] = v
end
end
if type(luaentity.on_receive_fields) == "function" then
luaentity.on_receive_fields(luaentity, fields, player)
end
end
end
end
end)
minetest.register_chatcommand("npcf", {
params = "<cmd> [npc_name] [args]",
description = "NPC Management",
func = function(name, param)
local admin = minetest.check_player_privs(name, {server=true})
local cmd, npc_name, args = string.match(param, "^([^ ]+) (.-) (.+)$")
if cmd and npc_name and args then
if cmd == "setpos" then
local p = {}
p.x, p.y, p.z = string.match(args, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
if p.x and p.y and p.z then
p.x = tonumber(p.x)
p.y = tonumber(p.y) + 1
p.z = tonumber(p.z)
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
if admin or luaentity.owner == name then
luaentity.object:setpos(p)
luaentity.origin.pos = p
end
end
end
elseif cmd == "setskin" then
if args == "random" then
local textures = {}
if minetest.get_modpath("skins") then
for _,skin in ipairs(skins.list) do
if string.match(skin, "^character_") then
table.insert(textures, skin..".png")
end
end
args = textures[math.random(1, #textures)]
else
minetest.chat_send_player(name, "Skins mod not found!")
return
end
end
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
if admin or luaentity.owner == name then
luaentity.properties.textures[1] = args
luaentity.object:set_properties(luaentity.properties)
end
end
end
return
end
cmd, npc_name = string.match(param, "([^ ]+) (.+)")
if cmd and npc_name then
if cmd == "delete" and admin then
for _,ref in pairs(minetest.luaentities) do
if ref.object and ref.npc_name == npc_name then
ref.object:remove()
end
end
local path = NPCF_PATH.."/npcs/npc_data/"
local input = io.open(path..npc_name..".npc", "r")
if input then
io.close(input)
os.remove(path..npc_name..".npc")
end
local index = npcf:get_index() or {}
if index[npc_name] then
index[npc_name] = nil
local output = io.open(path.."index.txt", 'w')
if output then
output:write(minetest.serialize(index))
io.close(output)
end
end
elseif cmd == "clear" then
local msg = false
for _,ref in pairs(minetest.luaentities) do
if ref.object and ref.npc_name == npc_name then
if admin or ref.owner == name then
ref.object:remove()
cleared = true
end
end
end
if cleared == false then
minetest.chat_send_player(name, "Unable to clear "..npc_name)
end
elseif cmd == "reload" then
local index = npcf:get_index()
if admin or name == index[npc_name] then
if not load_npc(npc_name) then
minetest.chat_send_player(name, "Unable to reload "..npc_name)
end
end
elseif cmd == "getpos" then
local located = false
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
local pos = luaentity.object:getpos()
if pos then
pos.x = math.floor(pos.x * 10) * 0.1
pos.y = math.floor(pos.y * 10) * 0.1 - 1
pos.z = math.floor(pos.z * 10) * 0.1
local msg = npc_name.." located at "..minetest.pos_to_string(pos)
minetest.chat_send_player(name, msg)
located = true
end
end
if located == false then
minetest.chat_send_player(name, "Unable to locate "..npc_name)
end
end
return
end
cmd = string.match(param, "([^ ]+)")
if cmd then
if cmd == "list" then
local npclist = ""
local index = npcf:get_index()
if index then
for npc_name,_ in pairs(index) do
local status = " - Online"
if not npcf:get_luaentity(npc_name) then
status = " - Offline"
end
npclist = npclist..npc_name..status.."\n"
end
end
if npclist == "" then
npclist = "None"
end
minetest.chat_send_player(name, "NPC List:\n"..npclist)
elseif cmd == "clearobjects" and admin then
for _,ref in pairs(minetest.luaentities) do
if ref.object and ref.npcf_id then
ref.object:remove()
end
end
elseif cmd == "loadobjects" and admin then
local index = npcf:get_index()
if index then
for npc_name,_ in pairs(index) do
load_npc(npc_name)
end
end
end
end
end,
})
minetest.register_entity("npcf:nametag", nametag)
-- Duplicate Entity Removal (experimental)
if NPCF_DUPLICATE_REMOVAL_TIME > 0 then
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer > NPCF_DUPLICATE_REMOVAL_TIME then
timer = 0
local dupes = {}
local index = npcf:get_index()
for _,ref in pairs(minetest.luaentities) do
local to_remove = false
if ref.object then
if ref.npcf_id == "npc" then
if ref.owner == nil or ref.npc_name == nil then
to_remove = true
elseif dupes[ref.npc_name] then
to_remove = true
else
dupes[ref.npc_name] = 1
end
end
local pos = ref.object:getpos()
if to_remove == true then
ref.object:remove()
local pos_str = minetest.pos_to_string(pos)
minetest.log("action", "Removed duplicate npc at "..pos_str)
if NPCF_SHOW_NAMETAGS == true then
for _,object in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
local luaentity = object:get_luaentity()
if luaentity then
if luaentity.npcf_id == "nametag" then
luaentity.object:remove()
minetest.log("action", "Removed duplicate nametag at "..pos_str)
break
end
end
end
end
end
end
end
end
end)
end

309
npcf/npcs/builder_npc.lua Normal file
View File

@ -0,0 +1,309 @@
local MAX_SPEED = 5
local MAX_POS = 1000
local DEFAULT_NODE = {name="air"}
local SCHEMS = {"basic_hut.we"}
local MODPATH = minetest.get_modpath("instabuild")
if MODPATH then
for _,v in ipairs({"factory.we", "large_warehouse.we", "small_farm.we", "tall_tower.we",
"large_farm.we", "mansion.we", "small_house.we", "large_house.we", "modern_house.we",
"small_hut.we", "large_hut.we", "short_tower.we", "small_warehouse.we"}) do
table.insert(SCHEMS, v)
end
end
local function reset_build(self)
self.var.nodedata = {}
self.var.nodelist = {}
self.metadata.index = nil
self.metadata.schematic = nil
self.metadata.build_pos = nil
self.metadata.building = false
end
local function get_registered_nodename(name)
if string.find(name, "^doors") then
name = name:gsub("_[tb]_[12]", "")
elseif string.find(name, "^stairs") then
name = name:gsub("upside_down", "")
elseif string.find(name, "^farming") then
name = name:gsub("_%d", "")
end
return name
end
local function load_schematic(self, filename)
local input = nil
if MODPATH then
input = io.open(MODPATH.."/models/"..filename, "r")
end
if not input then
input = io.open(NPCF_PATH.."/schems/"..filename, "r")
end
if input then
local data = minetest.deserialize(input:read('*all'))
io.close(input)
table.sort(data, function(a,b)
if a.y == b.y then
if a.z == b.z then
return a.x > b.x
end
return a.z > b.z
end
return a.y > b.y
end)
local sorted = {}
local pos = {x=0, y=0, z=0}
while #data > 0 do
local index = 1
local min_pos = {x=MAX_POS, y=MAX_POS, z=MAX_POS}
for i,v in ipairs(data) do
if v.y < min_pos.y or vector.distance(pos, v) < vector.distance(pos, min_pos) then
min_pos = v
index = i
end
end
local node = data[index]
table.insert(sorted, node)
table.remove(data, index)
pos = {x=node.x, y=node.y, z=node.z}
end
self.var.nodedata = {}
self.var.nodelist = {}
for i,v in ipairs(sorted) do
if v.name and v.param1 and v.param2 and v.x and v.y and v.z then
local node = {name=v.name, param1=v.param1, param2=v.param2}
local pos = vector.add(self.metadata.build_pos, {x=v.x, y=v.y, z=v.z})
local name = get_registered_nodename(v.name)
if minetest.registered_items[name] then
self.metadata.inventory[name] = self.metadata.inventory[name] or 0
self.var.nodelist[name] = self.var.nodelist[name] or 0
self.var.nodelist[name] = self.var.nodelist[name] + 1
else
node = DEFAULT_NODE
end
self.var.nodedata[i] = {pos=pos, node=node}
end
end
end
end
local function show_build_form(self, player_name)
local nodelist = {}
for k,v in pairs(self.var.nodelist) do
if string.find(name, "^doors") then
v = v * 0.5
end
if self.metadata.inventory[k] then
v = v - self.metadata.inventory[k]
end
if v < 0 then
v = 0
end
if minetest.registered_items[k].description ~= "" then
k = minetest.registered_items[k].description
end
table.insert(nodelist, k.." ("..v..")")
end
local materials = table.concat(nodelist, ",") or ""
local title = self.metadata.schematic:gsub("%.we","")
local button_build = "button_exit[5.0,1.0;3.0,0.5;build_start;Begin Build]"
if self.metadata.index then
button_build = "button_exit[5.0,1.0;3.0,0.5;build_resume;Resume Build]"
end
local formspec = "size[8,9]"
.."label[3.0,0.0;Project: "..title.."]"
.."textlist[0.0,1.0;4.0,3.5;inv_sel;"..materials..";"..self.var.selected..";]"
.."list[current_player;main;0.0,5.0;8.0,4.0;]"
..button_build
if NPCF_BUILDER_REQ_MATERIALS == true then
formspec = formspec.."list[detached:npcf_"..self.npc_name..";input;6.0,3.5;1,1;]"
end
if self.owner == player_name then
formspec = formspec.."button_exit[5.0,2.0;3.0,0.5;build_cancel;Cancel Build]"
end
npcf:show_formspec(player_name, self.npc_name, formspec)
end
local function get_speed(distance)
local speed = distance * 0.5
if speed > MAX_SPEED then
speed = MAX_SPEED
end
return speed
end
npcf:register_npc("npcf:builder_npc" ,{
description = "Builder NPC",
textures = {"npcf_skin_builder.png"},
nametag_color = "green",
metadata = {
schematic = nil,
inventory = {},
index = nil,
build_pos = nil,
building = false,
},
var = {
selected = "",
nodelist = {},
nodedata = {},
last_pos = {},
},
stepheight = 1,
inventory_image = "npcf_inv_builder_npc.png",
on_construct = function(self)
self.metadata.building = false
self.object:setvelocity({x=0, y=0, z=0})
self.object:setacceleration({x=0, y=-10, z=0})
if self.metadata.schematic and self.metadata.build_pos then
load_schematic(self, self.metadata.schematic)
end
end,
on_activate = function(self, staticdata, dtime_s)
local inv = minetest.create_detached_inventory("npcf_"..self.npc_name, {
on_put = function(inv, listname, index, stack, player)
local player_name = player:get_player_name()
local item = stack:get_name()
if player_name and self.metadata.inventory[item] then
self.metadata.inventory[item] = self.metadata.inventory[item] + stack:get_count()
inv:remove_item("input", stack)
show_build_form(self, player_name)
end
end,
})
inv:set_size("input", 1)
end,
on_rightclick = function(self, clicker)
local player_name = clicker:get_player_name()
if self.owner == player_name then
if not self.metadata.schematic then
local schemlist = table.concat(SCHEMS, ",") or ""
local formspec = "size[6,5]"
.."textlist[0.0,0.0;5.0,4.0;schemlist;"..schemlist..";;]"
.."button_exit[5.0,4.5;1.0,0.5;;Ok]"
npcf:show_formspec(player_name, self.npc_name, formspec)
return
elseif self.metadata.building == true then
self.metadata.building = false
return
end
end
if self.metadata.schematic and self.metadata.building == false then
show_build_form(self, player_name)
end
end,
on_step = function(self, dtime)
if self.timer > 1 then
self.timer = 0
if not self.owner then
return
end
local pos = self.object:getpos()
local yaw = self.object:getyaw()
local state = NPCF_ANIM_STAND
local speed = 0
if self.metadata.building == true then
local nodedata = self.var.nodedata[self.metadata.index]
pos.y = math.floor(pos.y)
local acceleration = {x=0, y=-10, z=0}
if pos.y < nodedata.pos.y then
if self.object:getacceleration().y > 0 and self.var.last_pos.y == pos.y then
self.object:setpos({x=pos.x, y=nodedata.pos.y + 1.5, z=pos.z})
acceleration = {x=0, y=0, z=0}
else
acceleration = {x=0, y=0.1, z=0}
end
end
self.var.last_pos = pos
self.object:setacceleration(acceleration)
yaw = npcf:get_face_direction(pos, nodedata.pos)
local distance = vector.distance(pos, nodedata.pos)
if distance < 4 then
if minetest.registered_items[nodedata.node.name].sounds then
local soundspec = minetest.registered_items[nodedata.node.name].sounds.place
if soundspec then
soundspec.pos = pos
minetest.sound_play(soundspec.name, soundspec)
end
end
minetest.add_node(nodedata.pos, nodedata.node)
local door_top = string.find(nodedata.node.name, "^doors+_t_[12]$")
if NPCF_BUILDER_REQ_MATERIALS == true and not door_top then
local name = get_registered_nodename(nodedata.node.name)
if self.metadata.inventory[name] > 0 then
self.metadata.inventory[name] = self.metadata.inventory[name] - 1
self.var.selected = ""
else
self.metadata.building = false
state = NPCF_ANIM_STAND
speed = 0
local i = 0
for k,v in pairs(self.var.nodelist) do
i = i + 1
if k == name then
self.var.selected = i
break
end
end
end
end
self.metadata.index = self.metadata.index + 1
if self.metadata.index > #self.var.nodedata then
reset_build(self)
end
state = NPCF_ANIM_WALK_MINE
speed = 1
else
state = NPCF_ANIM_WALK
speed = get_speed(distance)
end
elseif vector.equals(pos, self.origin.pos) == false then
self.object:setacceleration({x=0, y=-10, z=0})
yaw = npcf:get_face_direction(pos, self.origin.pos)
local distance = vector.distance(pos, self.origin.pos)
if distance > 1 then
speed = get_speed(distance)
state = NPCF_ANIM_WALK
else
self.object:setpos(self.origin.pos)
yaw = self.origin.yaw
end
end
self.object:setvelocity(npcf:get_walk_velocity(speed, self.object:getvelocity().y, yaw))
self.object:setyaw(yaw)
npcf:set_animation(self, state)
end
end,
on_receive_fields = function(self, fields, sender)
local player_name = sender:get_player_name()
if self.owner == player_name then
if fields.schemlist then
local id = tonumber(string.match(fields.schemlist, "%d+"))
if id then
if SCHEMS[id] then
local pos = {
x=math.ceil(self.origin.pos.x) + 1,
y=math.floor(self.origin.pos.y),
z=math.ceil(self.origin.pos.z) + 1
}
self.metadata.schematic = SCHEMS[id]
self.metadata.build_pos = pos
load_schematic(self, self.metadata.schematic)
end
end
elseif fields.build_cancel then
reset_build(self)
end
end
if fields.build_start then
for i,v in ipairs(self.var.nodedata) do
minetest.remove_node(v.pos)
end
self.metadata.index = 1
self.metadata.building = true
elseif fields.build_resume then
self.metadata.building = true
end
end,
})

200
npcf/npcs/deco_npc.lua Normal file
View File

@ -0,0 +1,200 @@
local WALKING_SPEED = 1
local RUNNING_SPEED = 2.5
local FOLLOW_RADIUS_MIN = 5
local FOLLOW_RADIUS_MAX = 30
local AVOIDED_NODES = {
"ignore",
"default:water_source",
"default:water_flowing",
"default:lava_source",
"default:lava_flowing",
}
local ANIMATION = {
["Stand"] = {id=1, state=NPCF_ANIM_STAND},
["Sit"] = {id=2, state=NPCF_ANIM_SIT},
["Lay"] = {id=3, state=NPCF_ANIM_LAY},
["Mine"] = {id=4, state=NPCF_ANIM_MINE},
}
local function get_target_player(self)
local target_player = nil
local min_dist = FOLLOW_RADIUS_MAX
for _,player in ipairs(minetest.get_connected_players()) do
if player then
local pos = player:getpos()
if pos then
local dist = vector.distance(pos, pos)
if dist < min_dist then
target_player = player
min_dist = dist
end
end
end
end
return target_player
end
npcf:register_npc("npcf:deco_npc" ,{
description = "Decorative NPC",
mesh = "npcf_deco.x",
textures = {"npcf_skin_deco.png"},
nametag_color = "magenta",
animation_speed = 12,
animation = {
stand_START = 0,
stand_END = 79,
sit_START = 81,
sit_END = 160,
lay_START = 162,
lay_END = 166,
walk_START = 168,
walk_END = 187,
mine_START = 189,
mine_END = 198,
run_START = 221,
run_END = 240,
},
metadata = {
free_roaming = "false",
follow_players = "false",
anim_stop = "Stand",
},
var = {
speed = 1,
avoid_dir = 1,
last_pos = {x=0,y=0,z=0},
target = nil,
},
stepheight = 1,
inventory_image = "npcf_inv_deco_npc.png",
on_construct = function(self)
self.object:setvelocity({x=0, y=0, z=0})
self.object:setacceleration({x=0, y=-10, z=0})
npcf:set_animation(self, ANIMATION[self.metadata.anim_stop].state)
end,
on_activate = function(self, staticdata, dtime_s)
if self.metadata.follow_players == "true" then
self.var.target = get_target_player(self)
end
end,
on_rightclick = function(self, clicker)
local player_name = clicker:get_player_name()
local message = "Hello, my name is "..self.npc_name
if self.metadata.message then
message = minetest.formspec_escape(self.metadata.message)
end
local formspec = "label[0,0;"..message.."]"
if player_name == self.owner then
local selected_id = ANIMATION[self.metadata.anim_stop].id or ""
self.metadata.free_roaming = false
self.metadata.follow_players = false
formspec = "size[8,4.0]"
.."field[0.5,1.0;7.5,0.5;message;Message;"..message.."]"
.."label[0.5,1.8;Stationary Animation\\:]"
.."dropdown[4.0,1.8;3.5;anim_stop;Stand,Sit,Lay,Mine;"..selected_id.."]"
.."checkbox[0.5,2.7;follow_players;Follow Players;false]"
.."button_exit[7.0,3.5;1.0,0.5;;Ok]"
if NPCF_DECO_FREE_ROAMING == true then
formspec = formspec.."checkbox[3.5,2.7;free_roaming;Wander Map;false]"
end
end
self.var.speed = 0
npcf:show_formspec(player_name, self.npc_name, formspec)
end,
on_step = function(self, dtime)
if self.timer > 1 then
self.timer = 0
local speed = 0
local pos = self.object:getpos()
local yaw = self.object:getyaw()
local turn = pos.x == self.var.last_pos.x and pos.z == self.var.last_pos.z
local velocity = self.object:getvelocity()
local roaming = NPCF_DECO_FREE_ROAMING == true and self.metadata.free_roaming == "true"
if roaming == true or self.metadata.follow_players == "true" then
speed = self.var.speed
if math.random(10) == 1 then
if speed == 0 or speed == RUNNING_SPEED then
speed = WALKING_SPEED
elseif math.random(5) == 1 then
speed = RUNNING_SPEED
elseif math.random(5) == 1 then
speed = 0
end
elseif math.random(30) == 1 then
self.var.avoid_dir = self.var.avoid_dir * -1
end
if self.metadata.follow_players == "true" then
local valid_target = false
if self.var.target then
local target = self.var.target:getpos()
if target then
valid_target = true
yaw = npcf:get_face_direction(pos, target)
if vector.distance(pos, target) < FOLLOW_RADIUS_MIN then
speed = 0
end
end
end
if math.random(10) == 1 or valid_target == false then
self.var.target = get_target_player(self)
end
end
if speed ~= 0 then
local node_pos = vector.add(npcf:get_walk_velocity(5, 0, yaw), pos)
node_pos = vector.round(node_pos)
local air_content = 0
for i = 1,5 do
local test_pos = {x=node_pos.x, y=node_pos.y-i, z=node_pos.z}
local node = minetest.get_node(test_pos)
if node.name == "air" then
air_content = air_content + 1
end
for _,v in ipairs(AVOIDED_NODES) do
if node.name == v then
turn = true
break
end
end
end
if turn == false then
local objects = minetest.get_objects_inside_radius(node_pos, 2)
if #objects > 0 then
turn = true
end
end
if turn == true or air_content == 5 then
yaw = yaw + math.pi * 0.5 * self.var.avoid_dir
speed = WALKING_SPEED
elseif pos.x == self.var.last_pos.x or pos.z == self.var.last_pos.z then
yaw = yaw + math.pi * 0.25 * self.var.avoid_dir
end
if roaming == true then
if math.random(4) == 1 then
yaw = yaw + (math.random(3) - 2) * 0.25
end
elseif self.var.target == nil then
speed = 0
end
end
self.var.speed = speed
self.object:setyaw(yaw)
end
self.var.last_pos = pos
if speed == 0 then
npcf:set_animation(self, ANIMATION[self.metadata.anim_stop].state)
elseif speed == RUNNING_SPEED then
self.object:set_animation({x=self.animation.run_START, y=self.animation.run_END}, 20)
self.state = nil
else
npcf:set_animation(self, NPCF_ANIM_WALK)
end
self.object:setvelocity(npcf:get_walk_velocity(speed, velocity.y, yaw))
end
end,
on_receive_fields = function(self, fields, sender)
if fields.free_roaming or fields.follow_players then
self.var.speed = WALKING_SPEED
end
end,
})

179
npcf/npcs/guard_npc.lua Normal file
View File

@ -0,0 +1,179 @@
local TARGET_RADIUS = 10
local MAX_SPEED = 5
local function get_wield_image(item)
local wield_image = "npcf_trans.png"
if minetest.registered_items[item] then
if minetest.registered_items[item].inventory_image ~= "" then
wield_image = minetest.registered_items[item].inventory_image
end
end
return wield_image
end
local function get_speed(distance)
local speed = distance * 0.5
if speed > MAX_SPEED then
speed = MAX_SPEED
end
return speed
end
local function get_name_in_list(list, name)
list = list:gsub(",", " ")
for k,v in ipairs(list:split(" ")) do
if k ~= "" and v == name then
return name
end
end
end
npcf:register_npc("npcf:guard_npc" ,{
description = "Guard NPC",
mesh = "npcf_guard.x",
textures = {"character.png", "npcf_skin_armor.png", "npcf_trans.png"},
nametag_color = "red",
inventory_image = "npcf_inv_guard_npc.png",
stepheight = 1,
metadata = {
wielditem = "default:sword_steel",
blacklist = "mobs:oerkki mobs:dungeon_master",
whitelist = "",
attack_players = "false",
follow_owner = "false",
},
on_activate = function(self, staticdata, dtime_s)
self.object:setvelocity({x=0, y=0, z=0})
self.object:setacceleration({x=0, y=-10, z=0})
local wield_image = get_wield_image(self.metadata.wielditem)
local textures = {self.properties.textures[1], "npcf_skin_armor.png", wield_image}
self.object:set_properties({textures = textures})
end,
on_rightclick = function(self, clicker)
local player_name = clicker:get_player_name()
if player_name == self.owner then
local blacklist = minetest.formspec_escape(self.metadata.blacklist)
local whitelist = minetest.formspec_escape(self.metadata.whitelist)
local formspec = "size[8,5]"
.."field[0.5,1.0;4.0,0.5;wielditem;Weapon;"..self.metadata.wielditem.."]"
.."button[6.0,0.5;2.0,0.5;origin;Set Origin]"
.."field[0.5,2.0;7.5.0,0.5;blacklist;Blacklist (Mob Entities);"..blacklist.."]"
.."field[0.5,3.0;7.5.0,0.5;whitelist;Whitelist (Player Names);"..whitelist.."]"
.."checkbox[0.5,3.5;follow_owner;Follow;"..self.metadata.follow_owner.."]"
.."button_exit[7.0,4.5;1.0,0.5;;Ok]"
if NPCF_GUARD_ATTACK_PLAYERS == true then
formspec = formspec.."checkbox[3.5,3.5;attack_players;Attack Players;"
..self.metadata.attack_players.."]"
end
npcf:show_formspec(player_name, self.npc_name, formspec)
end
end,
on_step = function(self, dtime)
if self.timer > 1 then
self.timer = 0
local pos = self.object:getpos()
local yaw = self.object:getyaw()
local state = NPCF_ANIM_STAND
local target = {object=nil, distance=0}
local min_dist = 1000
local speed = 0
local attacking = false
for _,object in ipairs(minetest.get_objects_inside_radius(pos, TARGET_RADIUS)) do
local to_target = false
if object:is_player() then
if NPCF_GUARD_ATTACK_PLAYERS == true and self.metadata.attack_players == "true" then
local player_name = object:get_player_name()
if player_name ~= self.owner then
if not get_name_in_list(self.metadata.whitelist, player_name) then
to_target = true
end
end
end
else
local luaentity = object:get_luaentity()
if luaentity then
if luaentity.name then
if get_name_in_list(self.metadata.blacklist, luaentity.name) then
to_target = true
end
end
end
end
if to_target == true then
local op = object:getpos()
local dv = vector.subtract(pos, op)
local dy = math.abs(dv.y - 1)
if dy < math.abs(dv.x) or dy < math.abs(dv.z) then
local dist = math.floor(vector.distance(pos, op))
if dist < min_dist then
target.object = object
target.distance = dist
min_dist = dist
end
end
end
end
if target.object then
yaw = npcf:get_face_direction(pos, target.object:getpos())
if target.distance < 3 then
attacking = true
state = NPCF_ANIM_MINE
end
if target.distance > 2 then
speed = get_speed(target.distance) * 1.1
state = NPCF_ANIM_WALK
if attacking == true then
state = NPCF_ANIM_WALK_MINE
end
end
if attacking == true then
local tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=1}}
local item = self.metadata.wielditem
if item ~= "" and minetest.registered_items[item] then
if minetest.registered_items[item].tool_capabilities then
tool_caps = minetest.registered_items[item].tool_capabilities
end
end
target.object:punch(self.object, self.var.timer, tool_caps)
end
elseif self.metadata.follow_owner == "true" then
local player = minetest.get_player_by_name(self.owner)
if player then
yaw = npcf:get_face_direction(pos, player:getpos())
local distance = vector.distance(pos, player:getpos())
if distance > 3 then
speed = get_speed(distance)
state = NPCF_ANIM_WALK
end
end
elseif vector.equals(pos, self.origin.pos) == false then
yaw = npcf:get_face_direction(pos, self.origin.pos)
local distance = vector.distance(pos, self.origin.pos)
if distance > 1 then
speed = get_speed(distance)
state = NPCF_ANIM_WALK
else
self.object:setpos(self.origin.pos)
yaw = self.origin.yaw
end
end
self.object:setvelocity(npcf:get_walk_velocity(speed, self.object:getvelocity().y, yaw))
self.object:setyaw(yaw)
npcf:set_animation(self, state)
end
end,
on_receive_fields = function(self, fields, sender)
if self.owner == sender:get_player_name() then
if fields.wielditem then
local wield_image = get_wield_image(fields.wielditem)
local textures = {self.properties.textures[1], "npcf_skin_armor.png", wield_image}
self.object:set_properties({textures = textures})
end
if fields.origin then
self.origin.pos = self.object:getpos()
self.origin.yaw = self.object:getyaw()
end
end
end,
})

54
npcf/npcs/info_npc.lua Normal file
View File

@ -0,0 +1,54 @@
local function get_formspec(text, page)
local lines = text:split("\n")
local start = (page - 1) * 12
local eof = false
local formspec = "size[8,6.5]"
for i = 1, 12 do
if lines[start + i] then
formspec = formspec.."label[0.5,"..(i*0.4)..";"..lines[start+i].."]"
else
eof = true
end
end
if page > 1 then
formspec = formspec.."button[0.0,6.0;1,0.5;page_"..(page-1)..";<<]"
end
if eof == false then
formspec = formspec.."button[7.0,6.0;1,0.5;page_"..(page+1)..";>>]"
end
formspec = formspec.."button_exit[3.0,6.0;2,0.5;;Exit]"
return formspec
end
npcf:register_npc("npcf:info_npc" ,{
description = "Information NPC",
textures = {"npcf_skin_info.png"},
nametag_color = "cyan",
metadata = {
infotext = "Infotext."
},
inventory_image = "npcf_inv_info_npc.png",
on_rightclick = function(self, clicker)
local player_name = clicker:get_player_name()
local infotext = minetest.formspec_escape(self.metadata.infotext)
local formspec = get_formspec(infotext, 1)
if player_name == self.owner then
formspec = "size[8,6]"
.."textarea[0.5,0.5;7.5,5.0;infotext;Infotext;"..infotext.."]"
.."button[0.0,5.5;2.0,0.5;page_1;View]"
.."button_exit[7.0,5.5;1.0,0.5;;Ok]"
end
npcf:show_formspec(player_name, self.npc_name, formspec)
end,
on_receive_fields = function(self, fields, sender)
for k,_ in pairs(fields) do
page = k:gsub("page_", "")
if page ~= k then
local formspec = get_formspec(self.metadata.infotext, tonumber(page))
npcf:show_formspec(sender:get_player_name(), self.npc_name, formspec)
break
end
end
end,
})

201
npcf/npcs/trade_npc.lua Normal file
View File

@ -0,0 +1,201 @@
local function show_formspec(self, player_name, selected_id)
local inv = minetest.get_inventory({type="detached", name="npcf_"..self.npc_name})
if not inv then
return
end
local trades = {}
for i,v in ipairs(self.metadata.trades) do
local avail = self.metadata.inventory[v.item_sell] or 0
local item_buy = minetest.registered_items[v.item_buy].description or v.item_buy
local item_sell = minetest.registered_items[v.item_sell].description or v.item_sell
if item_buy and item_sell then
local str = item_buy.." ("..v.qty_buy..") - "..item_sell.." ("..v.qty_sell..") ["..avail.."]"
trades[i] = minetest.formspec_escape(str)
end
end
local tradelist = table.concat(trades, ",") or ""
local select = ""
local input = inv:get_stack("input", 1)
local output = inv:get_stack("output", 1)
output:clear()
if selected_id == nil and input:get_count() > 0 then
for i,v in ipairs(self.metadata.trades) do
if v.item_buy == input:get_name() then
selected_id = i
break
end
end
end
if selected_id then
local trade = self.metadata.trades[selected_id]
if trade then
if self.owner == player_name then
local qty_sell = self.metadata.inventory[trade.item_sell] or 0
local qty_buy = self.metadata.inventory[trade.item_buy] or 0
output = ItemStack(trade.item_sell.." "..qty_sell)
input = ItemStack(trade.item_buy.." "..qty_buy)
inv:set_stack("input", 1, input)
elseif input:get_name() == trade.item_buy then
local avail = self.metadata.inventory[trade.item_sell] or 0
local req = math.floor(input:get_count() / trade.qty_buy) * trade.qty_sell
if req > avail then
req = avail
end
output = ItemStack(trade.item_sell.." "..req)
end
select = selected_id
end
end
inv:set_stack("output", 1, output)
local formspec = "size[8,10]"
.."list[detached:npcf_"..self.npc_name..";input;0.0,3.7;1,1;]"
.."list[detached:npcf_"..self.npc_name..";output;7.0,3.7;1,1;]"
.."list[current_player;main;0.0,5.0;8.0,4.0;]"
if self.owner == player_name then
formspec = formspec
.."textlist[0.0,0.0;5.7,3.5;inv_select;"..tradelist..";"..select..";]"
.."field[0.3,9.7;2.0,0.5;item_buy;Item Buy;]"
.."field[2.3,9.7;1.0,0.5;qty_buy;Qty;]"
.."field[3.3,9.7;2.0,0.5;item_sell;Item Sell;]"
.."field[5.3,9.7;1.0,0.5;qty_sell;Qty;]"
.."button[6.0,9.4;2.0,0.5;trade_add;Add Trade]"
.."list[detached:npcf_"..self.npc_name..";stock;3.5,3.7;1,1;]"
if select ~= "" then
formspec = formspec.."button[6.0,0.0;2.0,0.5;trade_delete_"..select..";Del Trade]"
end
else
formspec = formspec
.."textlist[0.0,0.0;7.5,3.5;inv_select;"..tradelist..";"..select..";]"
.."button_exit[3.0,4.0;2.0,0.5;trade_accept;Accept]"
end
npcf:show_formspec(player_name, self.npc_name, formspec)
end
local function get_field_qty(str)
if str then
local qty = math.floor(tonumber(str) or 1)
if qty > 0 then
return qty
end
end
return 1
end
local function is_valid_item(item)
if item then
if item ~= "" then
return minetest.registered_items[item]
end
end
end
npcf:register_npc("npcf:trade_npc" ,{
description = "Trader NPC",
mesh = "npcf_deco.x",
textures = {"npcf_skin_trader.png"},
nametag_color = "yellow",
metadata = {
trades = {},
inventory = {},
},
inventory_image = "npcf_inv_trader_npc.png",
on_activate = function(self, staticdata, dtime_s)
local inv = minetest.create_detached_inventory("npcf_"..self.npc_name, {
on_put = function(inv, listname, index, stack, player)
local player_name = player:get_player_name()
if listname == "stock" and self.owner == player_name then
local item = stack:get_name()
if self.metadata.inventory[item] then
self.metadata.inventory[item] = self.metadata.inventory[item] + stack:get_count()
inv:remove_item("stock", stack)
end
end
show_formspec(self, player_name, nil)
end,
on_take = function(inv, listname, index, stack, player)
if self.owner == player:get_player_name() and (listname == "input" or listname == "output") then
local item = stack:get_name()
if self.metadata.inventory[item] then
self.metadata.inventory[item] = self.metadata.inventory[item] - stack:get_count()
end
show_formspec(self, player:get_player_name(), nil)
end
end,
allow_put = function(inv, listname, index, stack, player)
if listname == "stock" or (listname == "input" and self.owner ~= player:get_player_name()) then
return stack:get_count()
end
return 0
end,
allow_take = function(inv, listname, index, stack, player)
if listname == "input" or self.owner == player:get_player_name() then
return stack:get_count()
end
return 0
end,
})
inv:set_size("input", 1)
inv:set_size("output", 1)
inv:set_size("stock", 1)
end,
on_rightclick = function(self, clicker)
show_formspec(self, clicker:get_player_name(), nil)
end,
on_receive_fields = function(self, fields, sender)
local player_name = sender:get_player_name()
if fields.trade_add and self.owner == player_name then
if is_valid_item(fields.item_buy) and is_valid_item(fields.item_sell) then
local trade = {}
trade["item_buy"] = fields.item_buy
trade["qty_buy"] = get_field_qty(fields.qty_buy)
trade["item_sell"] = fields.item_sell
trade["qty_sell"] = get_field_qty(fields.qty_sell)
table.insert(self.metadata.trades, trade)
self.metadata.inventory[fields.item_buy] = self.metadata.inventory[fields.item_buy] or 0
self.metadata.inventory[fields.item_sell] = self.metadata.inventory[fields.item_sell] or 0
show_formspec(self, player_name, nil)
else
minetest.chat_send_player(player_name, "Error: Invalid Item Name!")
end
elseif fields.inv_select then
local id = tonumber(string.match(fields.inv_select, "%d+"))
if id then
show_formspec(self, player_name, id)
end
elseif fields.trade_accept then
local inv = minetest.get_inventory({type="detached", name="npcf_"..self.npc_name})
local input = inv:get_stack("input", 1)
local output = inv:get_stack("output", 1)
local item_buy = input:get_name()
local item_sell = output:get_name()
local qty_buy = input:get_count()
local qty_sell = output:get_count()
local max = output:get_stack_max()
if qty_sell > 0 and max > 0 then
self.metadata.inventory[item_buy] = self.metadata.inventory[item_buy] + qty_buy
self.metadata.inventory[item_sell] = self.metadata.inventory[item_sell] - qty_sell
while qty_sell > max do
sender:get_inventory():add_item("main", item_sell.." "..max)
qty_sell = qty_sell - max
end
if qty_sell > 0 then
sender:get_inventory():add_item("main", item_sell.." "..qty_sell)
end
input:clear()
inv:set_stack("input", 1, input)
end
elseif self.owner == player_name then
for k,_ in pairs(fields) do
selected_id = k:gsub("trade_delete_", "")
if selected_id ~= k and self.metadata.trades[tonumber(selected_id)] then
local inv = minetest.get_inventory({type="detached", name="npcf_"..self.npc_name})
inv:set_stack("input", 1, ItemStack(""))
table.remove(self.metadata.trades, selected_id)
show_formspec(self, player_name, nil)
break
end
end
end
end,
})

1
npcf/schems/basic_hut.we Normal file

File diff suppressed because one or more lines are too long

BIN
npcf/textures/W_-.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

BIN
npcf/textures/W_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

BIN
npcf/textures/W_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

BIN
npcf/textures/W_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

BIN
npcf/textures/W_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
npcf/textures/W_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
npcf/textures/W_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

BIN
npcf/textures/W_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

BIN
npcf/textures/W_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

BIN
npcf/textures/W_8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
npcf/textures/W_9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
npcf/textures/W_UA.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

BIN
npcf/textures/W_UB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

BIN
npcf/textures/W_UC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

BIN
npcf/textures/W_UD.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
npcf/textures/W_UE.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

BIN
npcf/textures/W_UF.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

BIN
npcf/textures/W_UG.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

BIN
npcf/textures/W_UH.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

BIN
npcf/textures/W_UI.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

BIN
npcf/textures/W_UJ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

BIN
npcf/textures/W_UK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

BIN
npcf/textures/W_UL.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

BIN
npcf/textures/W_UM.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
npcf/textures/W_UN.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

BIN
npcf/textures/W_UO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
npcf/textures/W_UP.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

BIN
npcf/textures/W_UQ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

BIN
npcf/textures/W_UR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

BIN
npcf/textures/W_US.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
npcf/textures/W_UT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

BIN
npcf/textures/W_UU.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

BIN
npcf/textures/W_UV.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

BIN
npcf/textures/W_UW.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
npcf/textures/W_UX.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

BIN
npcf/textures/W_UY.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

BIN
npcf/textures/W_UZ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

BIN
npcf/textures/W__.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

BIN
npcf/textures/W_a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

BIN
npcf/textures/W_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
npcf/textures/W_c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
npcf/textures/W_d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
npcf/textures/W_e.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

BIN
npcf/textures/W_f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

BIN
npcf/textures/W_g.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

BIN
npcf/textures/W_h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

BIN
npcf/textures/W_i.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

BIN
npcf/textures/W_j.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

BIN
npcf/textures/W_k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
npcf/textures/W_l.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

BIN
npcf/textures/W_m.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

BIN
npcf/textures/W_n.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 B

BIN
npcf/textures/W_o.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

BIN
npcf/textures/W_p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
npcf/textures/W_q.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
npcf/textures/W_r.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

BIN
npcf/textures/W_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

BIN
npcf/textures/W_t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

BIN
npcf/textures/W_u.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 B

BIN
npcf/textures/W_v.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

BIN
npcf/textures/W_w.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
npcf/textures/W_x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

BIN
npcf/textures/W_y.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

BIN
npcf/textures/W_z.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

0
textcolors/depends.txt Normal file
View File

0
textcolors/init.lua Normal file
View File

BIN
textcolors/textures/B_-.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

BIN
textcolors/textures/B_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

BIN
textcolors/textures/B_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

BIN
textcolors/textures/B_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

BIN
textcolors/textures/B_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Some files were not shown because too many files have changed in this diff Show More