Next generation rewrite

This commit is contained in:
stujones11 2015-07-12 21:12:53 +01:00
parent 6b87897230
commit a58faa6f9e
433 changed files with 12766 additions and 1013 deletions

2
.gitignore vendored
View File

@ -4,4 +4,4 @@
*bak*
tags
*.vim
npcf.conf

376
README.md Normal file
View File

@ -0,0 +1,376 @@
Modpack - NPC Framework [0.2.0]
-------------------------------
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 titles, formspec handling, ownership, chat command management
and file based back-ups. Some example NPC mods have been included as part of the modpack.
The example NPC's are not craftable although by default will be available in the creative mode menu.
Server operators would be advised to override this default behaviour and allocate the NPCs on /give basis.
### Info NPC [npcf_info]
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 [npcf_deco]
A purely decorative NPC, can be set to roam freely and/or follow random players it encounters.
### Guard NPC [npcf_guard]
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 [npcf_trader]
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 the 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 [npcf_builder]
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.
Usage
-----
Place an NPC spawner node on the ground at a chosen location, right-click on the spawner and
give the NPC a unique ID, maximum 16 alpha-numeric characters with no spaces. (underscore and/or hyphen permitted)
This ID will be used to address the NPC using chat command interface and as the NPC's back-up file name.
NPC owner's and server admins can view the ID of any loaded NPC by right-clicking on the NPC entity
Chat Commands
-------------
NPC chat commands are issued as follows.
/npcf <command> [id] [args]
### list
List all registered NPCs and display online status.
### clearobjects
Clear all loaded NPCs. (requires server priv)
### getpos id
Display the position of the NPC.
### reload id
Reload an unloaded NPC. (requires ownership or server priv)
### save id
Save current NPC state to file. (requires ownership or server priv)
### unload id
Unload a loaded NPC. (requires ownership or server priv)
### delete id
Permanently unload and delete the NPC. (requires ownership or server priv)
### load id
Loads the NPC at the origin position. (requires ownership or server priv)
### setpos id pos | here
Set NPC location. (requires ownership or server priv)
Position x y z
/npcf setpos npc_1 0, 1.5, 0
Use 'here' to locate the NPC at the player's current position.
/npcf setpos npc_1 here
### setlook id direction | here
Set NPC face direction. (requires ownership or server priv)
Direction 0-360 degrees (0 = North)
/npcf setlook npc_1 0
Use 'here' to face the NPC towards the player.
/npcf setpos npc_1 here
### tell id message
Send message to NPC (requires ownership or server priv)
Customizable chat command callback for additional NPC interaction.
Has no effect unless overridden by the NPC's registration.
### setskin id skin_filename
Set the skin texture of the NPC. (requires ownership or server priv)
/npcf setskin npc_1 character.png
### titletext id string
Set the NPC title string. (requires ownership or server priv)
maximum 12 alpha-numeric characters (may contain spaces underscore and hyphen)
Use an empty string or only spaces to remove a title.
### titlecolor id color
Set the NPC title color #RRGGBB format. (requires ownership or server priv)
/npcf titlecolor npc_1 #FF0000
Also supports simple color names. (black, white, red, green, blue, cyan, yellow, magenta)
Not case sensitive.
/npcf titlecolor npc_1 Red
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 elaborate usage examples.
Properties
----------
Additional properties included by the framework. (defaults)
on_construct = function(self),
on_destruct = function(self, hitter),
on_update = function(npc),
on_tell = function(npc, sender, message)
on_receive_fields = function(self, fields, sender),
register_spawner = true,
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_state = 0,
animation_speed = 30,
decription = "Default NPC",
inventory_image = "npcf_inv_top.png",
title = {},
metadata = {},
var = {},
timer = 0,
Special Properties
------------------
Properties used internally by the framework.
properties = {textures=textures},
npcf_id = id,
owner = owner,
origin = {pos=pos, yaw=yaw},
These should be considered read-only, with the exception of origin
where it may be desireable update the statically saved position.
self.origin.pos = self.object:getpos()
self.origin.yaw = self.object:getyaw()
Callbacks
---------
Additional callbacks provided by the framework.
### on_construct = function(self)
This is called before the slightly delayed inbuilt on_activate callback.
Please note that the self.npc_id, self.owner and self.origin properties
may not be available or nil at the time of construction.
### on_destruct = function(self, hitter)
Called when an NPC is destroyed by punching. Can be used to unload the NPC when defeated by a player.
See the Guard NPC for an example.
### on_update = function(npc)
Called every time an NPC object is updated (NPCF_UPDATE_TIME) even when the LuaEntitySAO is not loaded.
Note that 'npc' is a NPC object reference, not a LuaEntitySAO reference. The latter can be obtained from
npc.object but only when the LuaEntitySAO is loaded.
## on_tell = function(npc, sender, message)
Called when the 'tell' chat command is issued. Once again here, 'npc' is a NPC object reference rather
than a LuaEntitySAO reference. A LuaEntitySAO reference is available when loaded.
This Behavior diverges from version 0.1.0, however, it does now allow for interaction even when the
associated LuaEntitySAO not loaded.
### on_receive_fields = function(self, fields, sender)
Called when a button is pressed in the NPC's formspec. text fields, dropdown,
list and checkbox selections are automatically stored in the metadata table.
npcf
----
The global NPC framework namespace.
### Global Constants
NPCF_UPDATE_TIME = 4
NPCF_RELOAD_DISTANCE = 32
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
All of the above can be overridden by including a npcf.conf file in the npcf directory.
See: npcf.conf.example
## npcf.index
Ownership table of all spawned NPCs (loaded or unloaded)
npcf.index[id] = owner -- owner's name
## npcf.npcs
Table of loaded NPC object references.
## npcf.npc
NPC object prototype.
autoload = true,
timer = 0,
object = nil -- LuaEntitySAO added as required
## npcf.npc:new(ref)
Create a new NPC object instance.
local npc = npcf.npc:new({
id = id,
pos = pos,
yaw = yaw,
name = name,
owner = owner,
title = {},
properties = {textures = textures},
metadata = {},
var = {},
origin = {
pos = pos,
yaw = yaw,
}
})
If used directly then it is the caller's resposibilty to store the reference and update the index.
Use: npcf:add_npc(ref) instead to have this done automatically by the framework.
## npc:update()
Update the NPC object. Adds a LuaEntitySAO when in range of players (NPCF_RELOAD_DISTANCE)
Called automatically on global step (NPCF_UPDATE_TIME) for all loaded NPC objects.
### npcf:add_title(ref)
Adds a floating title above the NPC entity
### npcf:add_entity(ref)
Adds a LuaEntitySAO based on the NPC reference, returns a minetest ObjectRef on success.
Care should be taken to avoid entity duplication when called externally.
### npcf:add_npc(ref)
Adds a new NPC based on the reference.
local ref = {
id = id,
pos = pos,
yaw = yaw,
name = "my_mod_name:npc",
owner = "owner_name", --optional
}
local npc = npcf:add_npc(ref)
If owner is nil then the NPC will be omitted from npcf.index.
Chat commands for such NPCs will only be available to admins (server priv).
### npcf:register_npc(name, def)
Register a non-player character. Used as a wrapper for minetest.register_entity, it includes
all the callbacks and properties available there with the exception of get_staticdata which is used internally. The framework provides 'metadata' and 'var' tables for data storage, where the metadata table is persistent following a reload and automatically stores submitted form data.
The var table should be used for semi-persistent data storage only. Note that self.timer is
automatically incremented by the framework but should be reset externally.
### npcf:unload(id)
Remove the NPC object instance and all associated entities.
### npcf:delete(id)
Permanently erase thw NPC object and associated back-up file.
### npcf:load(id)
Loads the NPC at the origin position.
### npcf:save(id)
Save current NPC state to file.
on_receive_fields = function(self, fields, sender)
if fields.save then
npcf:save(self.npc_id)
end
end,
### npcf:set_animation(luaentity, state)
Sets the NPC's animation state.
on_activate = function(self, staticdata, dtime_s)
npcf:set_animation(self, NPCF_ANIM_STAND)
end,
### npcf:get_luaentity(id)
Returns a LuaEntitySAO if the NPC object is loaded.
### npcf:get_face_direction(p1, p2)
Helper routine used internally and by some of the example NPCs.
Returns a yaw value in radians for position p1 facing position p2.
### npcf:get_walk_velocity(speed, y, yaw)
Returns a velocity vector for the given speed, y velocity and yaw.
### npcf:show_formspec(player_name, id, formspec)
Shows a formspec, similar to minetest.show_formspec() but with the NPC's id 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.

View File

@ -1,20 +0,0 @@
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.

1
npcf/.gitignore vendored
View File

@ -1 +0,0 @@
npcf.conf

View File

@ -1,286 +0,0 @@
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
and file based backups.
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 the 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 position of the named NPC.
### reload npc_name
Reload an unloaded NPC. (requires ownership or server priv)
### save npc_name
Save current NPC state to file. (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)
### load npc_name pos | here
Loads the NPC at the specified postion. (requires ownership or server priv)
/npcf load npc_name 0, 1.5, 0
Use 'here' to load the NPC at the player's current position.
/npcf load npc_name here
### setpos npc_name pos | here
Set named NPC location. (x, y, z)
/npcf setpos npc_name 0, 1.5, 0
Use 'here' to load the NPC at the player's current position.
### setskin npc_name skin_filename | random
Set the skin texture of the named NPC. (requires server priv)
/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
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 elaborate usage examples.
Properties
----------
Additional properties included by the framework. (defaults)
on_registration = function(self, 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", --textcolors mod adds red, blue, green, cyan, yellow and magenta
metadata = {},
var = {},
timer = 0,
Special Properties
------------------
Properties used internally by the framework.
properties = {textures = def.textures},
npcf_id = "npc",
npc_name = nil,
owner = nil,
origin = {},
These should be considered read-only, with the exception of origin
where it may be desireable update the statically saved position.
self.origin.pos = self.object:getpos()
self.origin.yaw = self.object:getyaw()
Callbacks
---------
Additional callbacks provided by the framework.
### on_construct = function(self)
This is called before the slightly delayed inbuilt on_activate callback.
Please note that the self.npc_name, self.owner and self.origin properties
may not be available or nil at the time of registration.
### on_receive_fields = function(self, fields, sender)
Called when a button is pressed in the NPC's formspec. text fields, dropdown,
list and checkbox selections are automatically stored in the self.metadata table.
### on_registration = function(self, pos, sender)
Only ever called once upon successful NPC registration using a spawner.
Currently not used anywhere and may be removed from future version.
npcf
----
The global NPC framework namespace.
### Global Constants
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 reset externally.
### npcf:spawn(pos, name, def)
Spawns and registers a NPC entity at the specified position. Returns a minetest ObjectRef on success.
local pos = player:getpos()
local yaw = player:get_look_yaw()
local player_name = player:get_player_name()
local luaentity = npcf:spawn(pos, "npcf:guard_npc", {
owner = player_name,
npc_name = "Sam",
origin = {pos=pos, yaw=yaw}, --optional
})
Note that the on_registration callback will not be issued when spawning NPC's this way.
### npcf:clear(npc_name)
Clear (unload) named NPC.
### npcf:load(npc_name, pos)
Loads the NPC at the specified postion. If pos is nil then the NPC is loaded at the last saved origin.
### npcf:save(luaentity)
Save current NPC state to file.
on_receive_fields = function(self, fields, sender)
if fields.save then
npcf:save(self)
end
end,
### npcf:set_animation(luaentity, state)
Sets the NPC's 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.

50
npcf/README.txt Normal file
View File

@ -0,0 +1,50 @@
Mod - NPC Framework [npcf]
-------------------------------
License Source Code: 2013 Stuart Jones - LGPL v2.1
License Textures: WTFPL
Depends: default
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 titles, formspec handling, ownership, chat command management
and file based back-ups. Some example NPC mods have been included as part of the modpack.
The example NPC's are not craftable although by default will be available in the creative mode menu.
Server operators would be advised to override this default behaviour and allocate the NPCs on /give basis.
Usage
-----
Place an NPC spawner node on the ground at a chosen location, right-click on the spawner and
give the NPC a unique ID, maximum 16 alpha-numeric characters with no spaces. (underscore and/or hyphen permitted)
This ID will be used to address the NPC using chat command interface and as the NPC's back-up file name.
NPC owner's and server admins can view the ID of any loaded NPC by right-clicking on the NPC entity
Chat Commands
-------------
NPC chat commands are issued as follows.
/npcf <command> [id] [args]
* setpos <npc_id> <pos>
* setlook <npc_id> <angle> (0-360)
* titletext <npc_id> <text>
* titlecolor <npc_id> <color> (#RRGGBB)
* tell <npc_id> <args>
* setskin <npc_id> <filename>
* delete <npc_id>
* unload <npc_id>
* load <npc_id>
* save <npc_id>
getpos <npc_id>
* clearobjects (admin only)
list
help (show this message)
* Ownership or server priv required

View File

@ -1,13 +1,85 @@
local params = "<cmd> [id] [args]"
local help = {
"Useage: /npcf "..params,
" ",
"* setpos <npc_id> <pos> ",
"* setlook <npc_id> <angle> (0-360) ",
"* titletext <npc_id> <text> ",
"* titlecolor <npc_id> <color> (#RRGGBB) ",
"* tell <npc_id> <args> ",
"* setskin <npc_id> <filename> ",
"* delete <npc_id> ",
"* unload <npc_id> ",
"* load <npc_id> ",
"* save <npc_id> ",
" getpos <npc_id> ",
"* clearobjects (admin only) ",
" list ",
" help (show this message) ",
" ",
"* Ownership or server priv required ",
}
local palette = {
["black"] = "#000000",
["white"] = "#FFFFFF",
["red"] = "#FF0000",
["green"] = "#00FF00",
["blue"] = "#0000FF",
["cyan"] = "#00FFFF",
["yellow"] = "FFFF00",
["magenta"] = "#FF00FF",
}
local function update_title(npc)
if npc.title.object then
npc.title.object:remove()
npc.title.object = nil
end
if npc.title.text then
npcf:add_title(npc)
end
npcf:save(npc.id)
end
local function get_permission(name, id)
local perm = minetest.check_player_privs(name, {server=true})
if perm or name == npcf.index[id] then
return true
end
minetest.chat_send_player(name, "Permission denied!")
return false
end
minetest.register_chatcommand("npcf", {
params = "<cmd> [npc_name] [args]",
params = params,
description = "NPC Management",
func = function(name, param)
local index = npcf:get_index()
local npc = nil
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
local cmd, npc_id, args = string.match(param, "^([^ ]+) (.-) (.+)$")
if not args then
cmd, npc_id = string.match(param, "([^ ]+) (.+)")
end
if npc_id then
if not npcf.index[npc_id] then
minetest.chat_send_player(name, "Invalid NPC ID "..npc_id)
return
end
npc = npcf.npcs[npc_id]
if not npc and cmd ~= "load" then
minetest.chat_send_player(name, "NPC "..npc_id.." is not currently loaded")
return
end
admin = name == npcf.index[npc_id] or admin
else
cmd = string.match(param, "([^ ]+)")
end
if cmd and npc_id and args then
if cmd == "setpos" then
if admin or name == index[npc_name] then
if not get_permission(name, npc_id) then
return
end
local pos = minetest.string_to_pos(args)
if args == "here" then
local player = minetest.get_player_by_name(name)
@ -17,176 +89,183 @@ minetest.register_chatcommand("npcf", {
end
if pos then
pos.y = pos.y + 1
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
if admin or luaentity.owner == name then
luaentity.object:setpos(pos)
luaentity.origin.pos = pos
npcf:save(luaentity)
npc.pos = pos
npc.origin.pos = pos
npcf:save(npc_id)
if npc.object then
npc.object:setpos(pos)
end
pos = minetest.pos_to_string(pos)
minetest.log("action", name.." moves NPC "..npc_name.." to "..pos)
end
end
minetest.log("action", name.." moves NPC "..npc_id.." to "..pos)
else
minetest.chat_send_player(name, "Invalid position "..args)
end
elseif cmd == "setlook" then
if not get_permission(name, npc_id) then
return
end
elseif cmd == "load" then
if admin or name == index[npc_name] then
local pos = minetest.string_to_pos(args)
local yaw = nil
if args == "here" then
local player = minetest.get_player_by_name(name)
if player then
pos = player:getpos()
end
end
if pos then
pos.y = pos.y + 1
if npcf:load(npc_name, pos) then
minetest.after(1, function()
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
npcf:save(luaentity)
pos = minetest.pos_to_string(pos)
minetest.log("action", name.." loads NPC "..npc_name.." at "..pos)
else
minetest.chat_send_player(name, "Unable to load "..npc_name)
yaw = npcf:get_face_direction(npc.pos, pos)
end
end)
end
else
minetest.chat_send_player(name, "Invalid position "..args)
local deg = tonumber(args)
if deg then
deg = 360 - deg % 360
yaw = math.rad(deg)
end
end
if yaw then
npc.yaw = yaw
npc.origin.yaw = yaw
npcf:save(npc_id)
if npc.object then
npc.object:setyaw(yaw)
end
end
elseif cmd == "titletext" then
if not get_permission(name, npc_id) then
return
end
if string.len(args) > 12 then
minetest.chat_send_player(name, "Title too long, max 12 characters")
return
elseif string.match(args, "^ +$") then
npc.title.text = nil
elseif string.match(args, "^[A-Za-z0-9%_%- ]+$") then
npc.title.text = args
else
minetest.chat_send_player(name, "Invalid title string "..args)
return
end
update_title(npc)
elseif cmd == "titlecolor" then
if not get_permission(name, npc_id) then
return
end
local color = palette[string.lower(args)] or args
if string.len(color) == 7 and string.match(color, "^#[A-Fa-f0-9]") then
npc.title.color = color
else
minetest.chat_send_player(name, "Invalid color string "..color)
return
end
update_title(npc)
elseif cmd == "tell" then
if admin or name == index[npc_name] then
local luaentity = npcf:get_luaentity(npc_name)
if luaentity and luaentity.on_tell then
luaentity.on_tell(luaentity, name, args)
if not get_permission(name, npc_id) then
return
end
if npc.name then
local def = minetest.registered_entities[npc.name]
if type(def.on_tell) == "function" then
def.on_tell(npc, name, args)
end
else
minetest.chat_send_player(name, "You don't have permission to tell "..npc_name.." things")
end
elseif cmd == "setskin" then
if admin or name == index[npc_name] 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!")
if not get_permission(name, npc_id) then
return
end
npc.properties.textures[1] = args
npcf:save(npc_id)
if npc.object then
npc.object:set_properties(npc.properties)
end
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
luaentity.properties.textures[1] = args
luaentity.object:set_properties(luaentity.properties)
end
end
minetest.log("action", name.." changes NPC "..npc_id.." skin to "..args)
else
minetest.chat_send_player(name, "Invalid command "..cmd)
end
return
elseif cmd and npc_id then
if cmd == "titletext" then
if not get_permission(name, npc_id) then
return
end
cmd, npc_name = string.match(param, "([^ ]+) (.+)")
if cmd and npc_name then
if cmd == "delete" and admin then
npcf:clear(npc_name)
local input = io.open(NPCF_DATADIR.."/"..npc_name..".npc", "r")
if input then
io.close(input)
os.remove(NPCF_DATADIR.."/"..npc_name..".npc")
npc.title.text = nil
update_title(npc)
elseif cmd == "delete" then
if not get_permission(name, npc_id) then
return
end
if index[npc_name] then
index[npc_name] = nil
local output = io.open(NPCF_DATADIR.."/index.txt", 'w')
if output then
output:write(minetest.serialize(index))
io.close(output)
npcf:delete(npc_id)
minetest.log("action", name.." deletes NPC "..npc_id)
elseif cmd == "unload" then
if not get_permission(name, npc_id) then
return
end
minetest.log("action", name.." deletes NPC "..npc_name)
end
elseif cmd == "clear" then
if admin or name == index[npc_name] then
npcf:clear(npc_name)
minetest.log("action", name.." clears NPC "..npc_name)
end
elseif cmd == "reload" then
if admin or name == index[npc_name] then
if npcf:load(npc_name, nil) then
minetest.log("action", name.." reloads NPC "..npc_name)
else
minetest.chat_send_player(name, "Unable to reload "..npc_name)
npcf:unload(npc_id)
minetest.log("action", name.." unloads NPC "..npc_id)
elseif cmd == "load" then
if not get_permission(name, npc_id) then
return
end
local npc = npcf:load(npc_id)
if npc then
npc.autoload = true
npc:update()
npcf:save(npc_id)
minetest.log("action", name.." loads NPC "..npc_id)
end
elseif cmd == "save" then
if admin or name == index[npc_name] then
local saved = false
local luaentity = npcf:get_luaentity(npc_name)
if luaentity then
if npcf:save(luaentity) then
saved = true
minetest.chat_send_player(name, npc_name.." has been saved")
minetest.log("action", name.." saves NPC "..npc_name)
end
end
if saved == false then
minetest.chat_send_player(name, "Unable to save "..npc_name)
if not get_permission(name, npc_id) then
return
end
local npc = get_loaded_npc(npc_id, name)
if npc then
npcf:save(npc_id)
minetest.chat_send_player(name, "NPC "..npc_id.." has been saved")
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)
local msg = "NPC "..npc_id
if npc and npcf.index[npc_id] then
local pos = {
x = math.floor(npc.pos.x * 10) * 0.1,
y = math.floor(npc.pos.y * 10) * 0.1 - 1,
z = math.floor(npc.pos.z * 10) * 0.1
}
msg = msg.." located at "..minetest.pos_to_string(pos)
else
msg = msg.." position unavilable"
end
minetest.chat_send_player(name, msg)
located = true
else
minetest.chat_send_player(name, "Invalid command "..cmd)
end
return
elseif cmd then
if cmd == "help" then
minetest.chat_send_player(name, table.concat(help, "\n"))
elseif cmd == "list" then
local msg = "None"
local npclist = {}
for id, _ in pairs(npcf.index) do
local loaded = id
if npcf.npcs[id] then
loaded = loaded.." [loaded]"
end
table.insert(npclist, loaded)
end
if #npclist > 0 then
msg = table.concat(npclist, "\n")
end
minetest.chat_send_player(name, "NPC List: \n\n"..msg)
elseif cmd == "clearobjects" then
if admin then
for id, npc in pairs(npcf.npcs) do
npcf:unload(id)
end
end
if located == false then
minetest.chat_send_player(name, "Unable to locate "..npc_name)
end
else
minetest.chat_send_player(name, "Invalid command "..cmd)
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
table.insert(npclist, npc_name)
end
end
local msg = "None"
if #npclist > 0 then
msg = table.concat(npclist, ", ")
end
minetest.chat_send_player(name, "NPC List: "..msg)
elseif cmd == "clearobjects" and admin then
minetest.log("action", name.." clears all NPC objects")
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
minetest.log("action", name.." reloads all NPC objects")
for npc_name,_ in pairs(index) do
npcf:load(npc_name, nil)
end
end
end
local msg = "Usage: /npcf "..params.."\n\nenter /npcf help for available commands"
minetest.chat_send_player(name, msg)
end,
})

View File

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

View File

@ -1,11 +1,96 @@
NPCF_MODPATH = minetest.get_modpath(minetest.get_current_modname())
NPCF_DATADIR = minetest.get_worldpath().."/npc_data"
NPCF_UPDATE_TIME = 4
NPCF_RELOAD_DISTANCE = 32
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
local input = io.open(NPCF_MODPATH.."/npcf.conf", "r")
if input then
dofile(NPCF_MODPATH.."/npcf.conf")
io.close(input)
end
if not minetest.mkdir(NPCF_DATADIR) then
minetest.log("error", "Unable to create the npc_data directory.\n"
.."All NPC data will be lost on server shutdowm!")
return
end
dofile(NPCF_MODPATH.."/npcf.lua")
dofile(NPCF_MODPATH.."/chatcommands.lua")
dofile(NPCF_MODPATH.."/npcs/info_npc.lua")
dofile(NPCF_MODPATH.."/npcs/deco_npc.lua")
dofile(NPCF_MODPATH.."/npcs/guard_npc.lua")
dofile(NPCF_MODPATH.."/npcs/trade_npc.lua")
dofile(NPCF_MODPATH.."/npcs/builder_npc.lua")
minetest.after(0, function()
local dirlist = minetest.get_dir_list(NPCF_DATADIR) or {}
for _, fn in pairs(dirlist) do
local id = string.match(fn, "^(.+)%.npc$")
if id then
local input = io.open(NPCF_DATADIR.."/"..fn, "r")
if input then
local ref = minetest.deserialize(input:read('*all'))
if ref then
npcf.index[id] = ref.owner
if not ref.autoload == false then
npcf:load(id)
end
end
end
end
end
end)
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname then
local id = formname:gsub("npcf_", "")
if id == formname then
return
end
local npc = npcf.npcs[id]
if npc then
local entity = npcf:get_luaentity(id)
if entity then
for k, v in pairs(fields) do
if k ~= "" then
v = string.gsub(v, "^CHG:", "")
npc.metadata[k] = v
end
end
if type(entity.on_receive_fields) == "function" then
entity:on_receive_fields(fields, player)
end
npcf:save(id)
end
end
end
end)
minetest.register_entity("npcf:title", {
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},
on_activate = function(self, staticdata, dtime_s)
if staticdata == "expired" then
self.object:remove()
end
end,
get_staticdata = function(self)
return "expired"
end,
})
minetest.register_globalstep(function(dtime)
for _, npc in pairs(npcf.npcs) do
npc.timer = npc.timer + dtime
if npc.timer > NPCF_UPDATE_TIME then
npc:update()
npc.timer = 0
end
end
end)

View File

@ -1,51 +1,36 @@
npcf = {}
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
local function deepcopy(obj, seen)
if type(obj) ~= 'table' then
return obj
end
if seen then
if seen[obj] then
return seen[obj]
end
end
local s = seen or {}
local copy = setmetatable({}, getmetatable(obj))
s[obj] = copy
for k, v in pairs(obj) do
copy[deepcopy(k, s)] = deepcopy(v, s)
end
return copy
end
local input = io.open(NPCF_MODPATH.."/npcf.conf", "r")
if input then
dofile(NPCF_MODPATH.."/npcf.conf")
input:close()
input = nil
end
local timer = 0
local index = {}
input = io.open(NPCF_DATADIR.."/index.txt", "r")
if input then
index = minetest.deserialize(input:read('*all'))
io.close(input)
else
os.execute("mkdir \""..NPCF_DATADIR.."\"")
local output = io.open(NPCF_DATADIR.."/index.txt", 'w')
if output then
output:write(minetest.serialize(index))
io.close(output)
end
end
local default_npc = {
hp_max = 1,
npcf = {
npc = {
autoload = true,
timer = 0,
},
npcs = {},
index = {},
default_npc = {
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",
mesh = "character.b3d",
textures = {"character.png"},
colors = {},
is_visible = true,
makes_footstep_sound = true,
automatic_rotate = false,
stepheight = 0,
automatic_face_movement_dir = false,
register_spawner = true,
armor_groups = {immortal=1},
animation = {
stand_START = 0,
@ -61,468 +46,362 @@ local default_npc = {
walk_mine_START = 200,
walk_mine_END = 219,
},
animation_state = 0,
animation_speed = 30,
decription = "Default NPC",
inventory_image = "npcf_inv_top.png",
show_nametag = true,
nametag_color = "white",
inventory_image = "npcf_inv.png",
title = {},
properties = {},
metadata = {},
var = {},
timer = 0,
}
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 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
function npcf.npc:new(ref)
ref = ref or {}
setmetatable(ref, self)
self.__index = self
return ref
end
function npcf.npc:update()
local def = minetest.registered_entities[self.name] or {}
if type(def.on_update) == "function" then
def.on_update(self)
end
if self.object then
local pos = self.object:getpos()
local yaw = self.object:getyaw()
if pos and yaw then
self.pos = pos
self.yaw = yaw
return
end
end
self.object = nil
local objects = minetest.get_objects_inside_radius(self.pos, NPCF_RELOAD_DISTANCE)
for _, object in pairs(objects) do
if object:is_player() then
local npc_object = npcf:add_entity(self)
if npc_object then
self.object = npc_object
npcf:add_title(self)
end
end
end
end
local function get_valid_npc_name(npc_name)
if npc_name then
return npc_name:len() <= 12 and npc_name:match("^[A-Za-z0-9%_%-]+$")
function npcf:add_title(ref)
if not ref.object or not ref.title.text then
return
end
local object = ref.title.object
if object then
if not object:getpos() then
object = nil
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
if not object then
local pos = ref.object:getpos()
object = minetest.add_entity(pos, "npcf:title")
if not object then
return
end
local texture = "npcf_tag_bg.png"
local x = math.floor(66 - ((parent.npc_name:len() * 11) / 2))
local x = math.floor(66 - ((ref.title.text:len() * 11) / 2))
local i = 0
parent.npc_name:gsub(".", function(char)
ref.title.text: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"
if char ~= " " then
texture = texture.."^[combine:84x14:"..(x+i)..",0=".."W_"..char..".png"
end
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}})
if ref.title.color then
texture = texture.."^[colorize:"..ref.title.color
end
local entity = object:get_luaentity()
if not entity then
object:remove()
return
end
ref.title.object = object
entity.npc_id = ref.id
object:set_properties({textures={texture}})
end
object:set_attach(ref.object, "", {x=0,y=9,z=0}, {x=0,y=0,z=0})
end
function npcf:add_entity(ref)
local object = minetest.add_entity(ref.pos, ref.name)
if object then
local entity = object:get_luaentity()
if entity then
object:setyaw(ref.yaw)
object:set_properties(ref.properties)
entity.npc_id = ref.id
entity.properties = ref.properties
entity.metadata = ref.metadata
entity.var = ref.var
entity.owner = ref.owner
entity.origin = ref.origin
return object
end
end
end
function npcf:add_npc(ref)
if ref.id and ref.pos and ref.name then
local def = deepcopy(minetest.registered_entities[ref.name])
if def then
ref.yaw = ref.yaw or {x=0, y=0, z=0}
ref.title = ref.title or def.title
ref.properties = {textures=ref.textures or def.textures}
ref.metadata = ref.metadata or def.metadata
ref.var = ref.var or def.var
ref.origin = {
pos = ref.pos,
yaw = ref.yaw,
}
local npc = npcf.npc:new(ref)
npcf.npcs[ref.id] = npc
npcf.index[ref.id] = ref.owner
return npc
end
end
end
function npcf:register_npc(name, def)
local ref = deepcopy(def) or {}
local default_npc = deepcopy(self.default_npc)
for k, v in pairs(default_npc) do
if not def[k] then
def[k] = v
if not ref[k] then
ref[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 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
if npc.origin.pos then
self.object:setpos(npc.origin.pos)
end
else
ref.on_activate = function(self, staticdata)
if staticdata == "expired" then
self.object:remove()
minetest.log("action", "Removed unknown npc")
return
end
if npc.metadata then
self.metadata = npc.metadata
if self.object then
self.object:set_armor_groups(def.armor_groups)
end
if npc.properties then
self.properties = npc.properties
self.object:set_properties(npc.properties)
if type(ref.on_construct) == "function" then
ref.on_construct(self)
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
minetest.after(0.5, function()
if get_valid_entity(self) then
if type(def.on_activate) == "function" then
def.on_activate(self, staticdata, dtime_s)
end
else
self.object:remove()
end
minetest.after(0.5, function()
def.on_activate(self)
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
minetest.chat_send_player(player_name, self.npc_name)
end
ref.on_rightclick = function(self, clicker)
local id = self.npc_id
local name = clicker:get_player_name()
if id and name then
local admin = minetest.check_player_privs(name, {server=true})
if admin or name == npcf.index[id] then
minetest.chat_send_player(name, "NPC ID: "..id)
end
end
if type(def.on_rightclick) == "function" then
def.on_rightclick(self, clicker)
end
end
ref.on_punch = function(self, hitter)
local hp = self.object:get_hp() or 0
if hp <= 0 then
local id = self.npc_id
if id then
npcf.npcs[id].title.object = nil
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
if type(ref.on_destruct) == "function" then
ref.on_destruct(self, hitter)
end
end
if type(def.on_punch) == "function" then
def.on_punch(self, hitter)
end
end
end,
on_step = function(self, dtime)
ref.on_step = function(self, dtime)
self.timer = self.timer + dtime
if type(def.on_step) == "function" and get_valid_entity(self) then
if type(def.on_step) == "function" then
def.on_step(self, dtime)
end
end,
on_tell = function(self, sender, message)
if type(def.on_tell) == "function" and get_valid_entity(self) then
local player = minetest.get_player_by_name(sender)
local senderpos
if player then
senderpos = player:getpos()
else
senderpos = {0,0,0}
end
def.on_tell(self, sender, senderpos, message)
ref.get_staticdata = function(self)
return "expired"
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
minetest.register_entity(name, ref)
if not ref.register_spawner then
return
end
minetest.register_node(name.."_spawner", {
description = def.description,
inventory_image = minetest.inventorycube("npcf_inv_top.png", def.inventory_image, def.inventory_image),
tiles = {"npcf_inv_top.png", def.inventory_image, def.inventory_image},
description = ref.description,
inventory_image = minetest.inventorycube("npcf_inv.png", ref.inventory_image, ref.inventory_image),
tiles = {"npcf_inv.png", ref.inventory_image, ref.inventory_image},
paramtype2 = "facedir",
groups = groups,
groups = {cracky=3, oddly_breakable_by_hand=3},
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")
meta:set_string("formspec", "size[8,3]"
.."label[0,0;NPC ID, max 16 characters (A-Za-z0-9_-)]"
.."field[0.5,1.5;7.5,0.5;id;ID;]"
.."button_exit[5,2.5;2,0.5;cancel;Cancel]"
.."button_exit[7,2.5;1,0.5;submit;Ok]"
)
meta:set_string("infotext", ref.description.." spawner")
end,
after_place_node = function(pos, placer, itemstack)
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
if minetest.setting_getbool("creative_mode") == false then
itemstack:take_item()
end
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.remove_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 get_valid_npc_name(fields.name) then
if index[fields.name] then
minetest.chat_send_player(player_name, "Error: Name Already Taken!")
local sender_name = sender:get_player_name()
local id = fields.id
if id and sender_name == owner then
if id:len() <= 16 and id:match("^[A-Za-z0-9%_%-]+$") then
if npcf.index[id] then
minetest.chat_send_player(sender_name, "Error: ID Already Taken!")
return
end
else
minetest.chat_send_player(player_name, "Error: Invalid NPC Name!")
minetest.chat_send_player(sender_name, "Error: Invalid ID!")
return
end
minetest.remove_node(pos)
npcf.index[id] = owner
local npc_pos = {x=pos.x, y=pos.y + 0.5, z=pos.z}
local yaw = sender:get_look_yaw() + math.pi * 0.5
local luaentity = npcf:spawn(npc_pos, name, {
owner = player_name,
npc_name = fields.name,
origin = {pos=npc_pos, yaw=yaw}
})
if luaentity and type(def.on_registration) == "function" then
def.on_registration(luaentity, pos, sender)
local ref = {
id = id,
pos = npc_pos,
yaw = yaw,
name = name,
owner = owner,
}
local npc = npcf:add_npc(ref)
npcf:save(ref.id)
if npc then
npc:update()
end
minetest.remove_node(pos)
end
end,
})
end
function npcf:spawn(pos, name, def)
if pos and name and def.npc_name and def.owner then
if get_valid_npc_name(def.npc_name) and index[def.npc_name] == nil then
local entity = minetest.add_entity(pos, name)
if entity then
local luaentity = entity:get_luaentity()
if luaentity then
index[def.npc_name] = def.owner
luaentity.owner = def.owner
luaentity.npc_name = def.npc_name
luaentity.origin = def.origin or {pos=pos, yaw=0}
if def.origin then
luaentity.object:setyaw(luaentity.origin.yaw)
end
if npcf:save(luaentity) then
local output = io.open(NPCF_DATADIR.."/index.txt", 'w')
if output then
output:write(minetest.serialize(index))
io.close(output)
if NPCF_SHOW_NAMETAGS == true and luaentity.show_nametag == true then
add_nametag(luaentity)
end
return luaentity
else
minetest.log("error", "Failed to add "..def.npc_name.." to NPC index")
end
else
minetest.log("error", "Failed to save NPC "..def.npc_name)
end
end
end
end
end
end
function npcf:clear(npc_name)
if get_valid_npc_name(npc_name) then
for _,ref in pairs(minetest.luaentities) do
if ref.object and ref.npc_name == npc_name then
ref.object:remove()
end
end
end
end
function npcf:load(npc_name, pos)
if get_valid_npc_name(npc_name) then
npcf:clear(npc_name)
local input = io.open(NPCF_DATADIR.."/"..npc_name..".npc", "r")
if input then
local data = minetest.deserialize(input:read('*all'))
io.close(input)
if data then
if pos and data.origin then
data.origin.pos = pos
end
if data.origin.pos then
local npc = minetest.add_entity(data.origin.pos, data.name)
function npcf:unload(id)
local npc = self.npcs[id]
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.animation = data.animation
luaentity.metadata = data.metadata
luaentity.object:setyaw(data.origin.yaw)
luaentity.properties = data.properties
luaentity.object:set_properties(data.properties)
if NPCF_SHOW_NAMETAGS == true and luaentity.show_nametag == true then
add_nametag(luaentity)
if npc.object then
npc.object:remove()
end
return 1
npc.autoload = false
npcf:save(id)
self.npcs[id] = nil
end
end
end
end
end
minetest.log("error", "Failed to load "..npc_name)
return
end
minetest.log("error", "Attempt to load invalid NPC")
end
function npcf:save(luaentity)
if get_valid_entity(luaentity) then
local npc = {
name = luaentity.name,
owner = luaentity.owner,
origin = luaentity.origin,
animation = luaentity.animation,
metadata = luaentity.metadata,
properties = luaentity.properties,
}
local npc_name = luaentity.npc_name
local output = io.open(NPCF_DATADIR.."/"..npc_name..".npc", 'w')
if output then
output:write(minetest.serialize(npc))
function npcf:delete(id)
npcf:unload(id)
local output = io.open(NPCF_DATADIR.."/"..id..".npc", "w")
if input then
output:write("")
io.close(output)
return 1
end
minetest.log("error", "Failed to save NPC "..npc_name)
return
end
minetest.log("error", "Attempt to save invalid NPC")
npcf.index[id] = nil
end
function npcf:set_animation(luaentity, state)
if get_valid_entity(luaentity) and state then
if state ~= luaentity.state then
local speed = luaentity.animation_speed
local anim = luaentity.animation
function npcf:load(id)
local input = io.open(NPCF_DATADIR.."/"..id..".npc", 'r')
if input then
local ref = minetest.deserialize(input:read('*all'))
io.close(input)
ref.id = id
ref.pos = ref.origin.pos
ref.yaw = ref.origin.yaw
return npcf:add_npc(ref)
end
minetest.log("error", "Failed to laod NPC: "..id)
end
function npcf:save(id)
local npc = self.npcs[id]
if npc then
local ref = {
name = npc.name,
owner = npc.owner,
title = {
text = npc.title.text,
color = npc.title.color,
},
origin = npc.origin,
metadata = npc.metadata,
properties = npc.properties,
autoload = npc.autoload,
}
local output = io.open(NPCF_DATADIR.."/"..id..".npc", 'w')
if output then
output:write(minetest.serialize(ref))
io.close(output)
return
end
end
minetest.log("error", "Failed to save NPC: "..id)
end
function npcf:set_animation(entity, state)
if entity and state then
if state ~= entity.animation_state then
local speed = entity.animation_speed
local anim = entity.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)
entity.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)
entity.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)
entity.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)
entity.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)
entity.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)
entity.object:set_animation({x=anim.mine_START, y=anim.mine_END}, speed)
end
luaentity.state = state
entity.animation_state = state
end
end
end
end
function npcf:get_index()
return index
end
function npcf:get_luaentity(npc_name)
if get_valid_npc_name(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
function npcf:get_luaentity(id)
local npc = self.npcs[id] or {}
if npc.object then
return npc.object:get_luaentity()
end
end
function npcf:get_face_direction(v1, v2)
if v1 and v2 then
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)
function npcf:get_face_direction(p1, p2)
if p1 and p2 then
if p1.x and p2.x and p1.z and p2.z then
local px = p1.x - p2.x
local pz = p2.z - p1.z
return math.atan2(px, pz)
end
end
end
@ -539,31 +418,9 @@ function npcf:get_walk_velocity(speed, y, yaw)
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)
function npcf:show_formspec(name, id, formspec)
if name and id and formspec then
minetest.show_formspec(name, "npcf_"..id, formspec)
end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname and not fields.quit 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_entity("npcf:nametag", nametag)

View File

Before

Width:  |  Height:  |  Size: 217 B

After

Width:  |  Height:  |  Size: 217 B

14
npcf_builder/README.txt Normal file
View File

@ -0,0 +1,14 @@
Mod - Builder NPC [npcf_builder]
--------------------------------
License Source Code: 2013 Stuart Jones - LGPL v2.1
License Textures: WTFPL
Depends: npcf
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.

1
npcf_builder/depends.txt Normal file
View File

@ -0,0 +1 @@
npcf

View File

@ -1,9 +1,11 @@
local MODPATH = minetest.get_modpath(minetest.get_current_modname())
local BUILDER_REQ_MATERIALS = minetest.setting_getbool("creative_mode") == false
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
local INSTABUILD_PATH = minetest.get_modpath("instabuild")
if INSTABUILD_PATH 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
@ -33,11 +35,11 @@ end
local function load_schematic(self, filename)
local input = nil
if MODPATH then
input = io.open(MODPATH.."/models/"..filename, "r")
if INSTABUILD_PATH then
input = io.open(INSTABUILD_PATH.."/models/"..filename, "r")
end
if not input then
input = io.open(NPCF_MODPATH.."/schems/"..filename, "r")
input = io.open(MODPATH.."/schems/"..filename, "r")
end
if input then
local data = minetest.deserialize(input:read('*all'))
@ -115,13 +117,13 @@ local function show_build_form(self, player_name)
.."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;]"
if BUILDER_REQ_MATERIALS == true then
formspec = formspec.."list[detached:npcf_"..self.npc_id..";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)
npcf:show_formspec(player_name, self.npc_id, formspec)
end
local function get_speed(distance)
@ -132,10 +134,9 @@ local function get_speed(distance)
return speed
end
npcf:register_npc("npcf:builder_npc" ,{
npcf:register_npc("npcf_builder:npc" ,{
description = "Builder NPC",
textures = {"npcf_skin_builder.png"},
nametag_color = "green",
textures = {"npcf_builder_skin.png"},
metadata = {
schematic = nil,
inventory = {},
@ -149,8 +150,8 @@ npcf:register_npc("npcf:builder_npc" ,{
nodedata = {},
last_pos = {},
},
stepheight = 1,
inventory_image = "npcf_inv_builder_npc.png",
stepheight = 1.1,
inventory_image = "npcf_builder_inv.png",
on_construct = function(self)
self.metadata.building = false
self.object:setvelocity({x=0, y=0, z=0})
@ -159,8 +160,8 @@ npcf:register_npc("npcf:builder_npc" ,{
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_activate = function(self)
local inv = minetest.create_detached_inventory("npcf_"..self.npc_id, {
on_put = function(inv, listname, index, stack, player)
local player_name = player:get_player_name()
local item = stack:get_name()
@ -181,7 +182,7 @@ npcf:register_npc("npcf:builder_npc" ,{
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)
npcf:show_formspec(player_name, self.npc_id, formspec)
return
elseif self.metadata.building == true then
self.metadata.building = false
@ -228,7 +229,7 @@ npcf:register_npc("npcf:builder_npc" ,{
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
if 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

View File

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 678 B

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

11
npcf_deco/README.txt Normal file
View File

@ -0,0 +1,11 @@
Mod - Deco NPC [npcf_deco]
--------------------------
License Source Code: 2013 Stuart Jones - LGPL v2.1
License Textures: WTFPL
Depends: npcf
A purely decorative NPC, can be set to roam freely and/or follow random players it encounters.

1
npcf_deco/depends.txt Normal file
View File

@ -0,0 +1 @@
npcf

View File

@ -1,5 +1,6 @@
local FREE_ROAMING = true
local WALKING_SPEED = 1
local RUNNING_SPEED = 2.5
local RUNNING_SPEED = 2
local FOLLOW_RADIUS_MIN = 5
local FOLLOW_RADIUS_MAX = 30
local AVOIDED_NODES = {
@ -34,11 +35,10 @@ local function get_target_player(self)
return target_player
end
npcf:register_npc("npcf:deco_npc" ,{
npcf:register_npc("npcf_deco:npc" ,{
description = "Decorative NPC",
mesh = "npcf_deco.x",
textures = {"npcf_skin_deco.png"},
nametag_color = "magenta",
textures = {"npcf_deco_skin.png"},
animation_speed = 12,
animation = {
stand_START = 0,
@ -65,35 +65,21 @@ npcf:register_npc("npcf:deco_npc" ,{
last_pos = {x=0,y=0,z=0},
target = nil,
},
stepheight = 1,
inventory_image = "npcf_inv_deco_npc.png",
stepheight = 1.1,
inventory_image = "npcf_deco_inv.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)
-- Deal with legacy errors where these fields sometimes had
-- invalid values...
if self.metadata.follow_players == true then
self.metadata.follow_players = "true"
elseif self.metadata.follow_players == false then
self.metadata.follow_players = "false"
end
if self.metadata.free_roaming == true then
self.metadata.free_roaming = "true"
elseif self.metadata.free_roaming == false then
self.metadata.free_roaming = "false"
end
on_activate = function(self)
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
local message = "Hello"
if self.metadata.message then
message = minetest.formspec_escape(self.metadata.message)
end
@ -106,15 +92,14 @@ npcf:register_npc("npcf:deco_npc" ,{
.."dropdown[4.0,1.8;3.5;anim_stop;Stand,Sit,Lay,Mine;"..selected_id.."]"
.."checkbox[0.5,2.7;follow_players;Follow Players;"..self.metadata.follow_players.."]"
.."button_exit[7.0,3.5;1.0,0.5;;Ok]"
if NPCF_DECO_FREE_ROAMING == true then
if FREE_ROAMING == true then
formspec = formspec.."checkbox[3.5,2.7;free_roaming;Wander Map;"..self.metadata.free_roaming.."]"
end
else
formspec = "size[8,4]"
.."label[0,0;"..message.."]"
formspec = "size[8,4]".."label[0,0;"..message.."]"
end
self.var.speed = 0
npcf:show_formspec(player_name, self.npc_name, formspec)
npcf:show_formspec(player_name, self.npc_id, formspec)
end,
on_step = function(self, dtime)
if self.timer > 1 then
@ -125,7 +110,7 @@ npcf:register_npc("npcf:deco_npc" ,{
local turn = pos.x == self.var.last_pos.x and pos.z == self.var.last_pos.z
local acceleration = {x=0, y=-10, z=0}
local velocity = self.object:getvelocity()
local roaming = NPCF_DECO_FREE_ROAMING == true and self.metadata.free_roaming == "true"
local roaming = 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

View File

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

12
npcf_guard/README.txt Normal file
View File

@ -0,0 +1,12 @@
Mod - Guard NPC [npcf_guard]
----------------------------
License Source Code: 2013 Stuart Jones - LGPL v2.1
License Textures: WTFPL
Depends: npcf
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.

1
npcf_guard/depends.txt Normal file
View File

@ -0,0 +1 @@
npcf

View File

@ -1,4 +1,5 @@
local TARGET_RADIUS = 10
local GUARD_ATTACK_PLAYERS
local TARGET_RADIUS = 20
local MAX_SPEED = 5
local function get_wield_image(item)
@ -30,18 +31,17 @@ end
local function get_armor_texture(self)
if self.metadata.show_armor == "true" then
return "npcf_skin_armor.png"
return "npcf_guard_armor.png"
end
return "npcf_trans.png"
end
npcf:register_npc("npcf:guard_npc" ,{
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,
textures = {"character.png", "npcf_guard_armor.png", "npcf_trans.png"},
inventory_image = "npcf_guard_inv.png",
stepheight = 1.1,
metadata = {
wielditem = "default:sword_steel",
blacklist = "mobs:oerkki mobs:dungeon_master",
@ -50,12 +50,13 @@ npcf:register_npc("npcf:guard_npc" ,{
follow_owner = "false",
show_armor = "true",
},
on_activate = function(self, staticdata, dtime_s)
on_activate = function(self)
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], get_armor_texture(self), wield_image}
self.object:set_properties({textures = textures})
self.properties = {textures = textures}
self.object:set_properties(self.properties)
end,
on_rightclick = function(self, clicker)
local player_name = clicker:get_player_name()
@ -70,11 +71,11 @@ npcf:register_npc("npcf:guard_npc" ,{
.."checkbox[0.5,4.5;follow_owner;Follow;"..self.metadata.follow_owner.."]"
.."button[0.0,6.0;2.0,0.5;origin;Set Origin]"
.."button_exit[7.0,6.0;1.0,0.5;;Ok]"
if NPCF_GUARD_ATTACK_PLAYERS == true then
if GUARD_ATTACK_PLAYERS == true then
formspec = formspec.."checkbox[4.0,4.5;attack_players;Attack Players;"
..self.metadata.attack_players.."]"
end
npcf:show_formspec(player_name, self.npc_name, formspec)
npcf:show_formspec(player_name, self.npc_id, formspec)
end
end,
on_step = function(self, dtime)
@ -92,7 +93,7 @@ npcf:register_npc("npcf:guard_npc" ,{
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
if 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

View File

Before

Width:  |  Height:  |  Size: 973 B

After

Width:  |  Height:  |  Size: 973 B

View File

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 316 B

13
npcf_info/README.txt Normal file
View File

@ -0,0 +1,13 @@
Mod - Info NPC [npcf_info]
--------------------------
License Source Code: 2013 Stuart Jones - LGPL v2.1
License Textures: WTFPL
Depends: npcf
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.

1
npcf_info/depends.txt Normal file
View File

@ -0,0 +1 @@
npcf

View File

@ -20,15 +20,19 @@ local function get_formspec(text, page)
return formspec
end
npcf:register_npc("npcf:info_npc" ,{
npcf:register_npc("npcf_info:npc" , {
description = "Information NPC",
textures = {"npcf_skin_info.png"},
nametag_color = "cyan",
textures = {"npcf_info_skin.png"},
metadata = {
infotext = "Infotext."
},
inventory_image = "npcf_inv_info_npc.png",
inventory_image = "npcf_info_inv.png",
title = {
text = "Info NPC",
color = "#0000CC",
},
on_rightclick = function(self, clicker)
print(dump(self.npc_id))
local player_name = clicker:get_player_name()
local infotext = minetest.formspec_escape(self.metadata.infotext)
local formspec = get_formspec(infotext, 1)
@ -38,17 +42,16 @@ npcf:register_npc("npcf:info_npc" ,{
.."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)
npcf:show_formspec(player_name, self.npc_id, formspec)
end,
on_receive_fields = function(self, fields, sender)
for k,_ in pairs(fields) do
page = k:gsub("page_", "")
local 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)
npcf:show_formspec(sender:get_player_name(), self.npc_id, formspec)
break
end
end
end,
})

View File

Before

Width:  |  Height:  |  Size: 562 B

After

Width:  |  Height:  |  Size: 562 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

11
npcf_mob/README.txt Normal file
View File

@ -0,0 +1,11 @@
Mod - Mob NPC [npcf_mob]
--------------------------
License Source Code: 2013 Stuart Jones - LGPL v2.1
License Textures: 2013 Stuart Jones - WTFPL
Depends: npcf, tnt
A rare but very destructive 'Mushroom Man' mob

1
npcf_mob/depends.txt Normal file
View File

@ -0,0 +1 @@
npcf

280
npcf_mob/init.lua Normal file
View File

@ -0,0 +1,280 @@
local MAX_OBJECT_COUNT = 10
local WALKING_SPEED = 2.5
local RUNNING_SPEED = 4
local ENABLE_TNT = minetest.get_modpath("tnt") ~= nil
local ATTACK_RADIUS = NPCF_RELOAD_DISTANCE + 10
local TARGET_RADIUS = 20
local TARGET_CHANCE = 1000
local TARGET_TIME_MIN = 60
local TARGET_TIME_MAX = 300
local SPAWN_CHANCE = 3
local SPAWN_UPDATE_TIME = 4
local SPAWN_NODES = {
"default:dirt_with_grass",
"default:cobble",
"default:sand",
"default:desert_sand",
"default:desert_stone",
}
local AVOIDED_NODES = {
"ignore",
"default:water_source",
"default:water_flowing",
"default:lava_source",
"default:lava_flowing",
}
local spawn_timer = 0
local target_players = {}
local function get_target_player(pos)
local target_player = nil
local min_dist = ATTACK_RADIUS
for _,player in ipairs(minetest.get_connected_players()) do
if player then
local player_pos = player:getpos()
local hp = player:get_hp() or 0
if player_pos and hp > 0 then
local dist = vector.distance(pos, player_pos)
if dist < min_dist then
target_player = player
min_dist = dist
end
end
end
end
return target_player
end
local function spawn_mob(pos)
if minetest.get_node_light(pos) < 10 then
return
end
for i = 1, MAX_OBJECT_COUNT do
local id = ":npcf_mob_"..i
if not npcf.npcs[id] then
local yaw = math.rad(math.random(360))
local ref = {
id = id,
pos = pos,
yaw = yaw,
name = "npcf_mob:npc",
}
local npc = npcf:add_npc(ref)
if npc then
npc:update()
end
break
end
end
end
npcf:register_npc("npcf_mob:npc", {
description = "Mob NPC",
mesh = "npcf_mob.b3d",
textures = {"npcf_mob_skin.png"},
collisionbox = {-0.35,-1.0,-0.35, 0.35,0.5,0.35},
animation_speed = 25,
metadata = {
anim_stop = "Stand",
},
var = {
speed = WALKING_SPEED,
avoid_dir = 1,
last_pos = {x=0,y=0,z=0},
target = nil,
},
stepheight = 1.1,
register_spawner = false,
armor_groups = {fleshy=100},
on_update = function(npc)
if math.random(5) == 1 then
if not get_target_player(npc.pos) then
if npc.object then
npc.object:remove()
end
npcf.npcs[npc.id] = nil
end
end
end,
on_construct = function(self)
self.object:setvelocity({x=0, y=0, z=0})
self.object:setacceleration({x=0, y=-10, z=0})
end,
on_destruct = function(self, hitter)
local id = self.npc_id
if id then
npcf.npcs[id] = nil
end
local pos = self.object:getpos()
if pos then
minetest.add_particlespawner(
50, 1, pos, pos,
{x=-3, y=3, z=-3}, {x=3, y=3, z=3},
{x=-2, y=-2, z=-2}, {x=2, y=-2, z=2},
0.1, 0.75, 2, 8, false, "npcf_mob_particle.png"
)
if ENABLE_TNT == true then
pos.y = pos.y - 1
minetest.add_node(pos, {name="tnt:tnt_burning"})
minetest.get_node_timer(pos):start(1)
else
local player_pos = hitter:getpos()
if player_pos then
local dist = vector.distance(pos, player_pos)
local damage = (50 * 0.5 ^ dist) * 2
hitter:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy=damage},
})
end
end
end
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 acceleration = {x=0, y=-10, z=0}
local velocity = self.object:getvelocity()
speed = self.var.speed
if math.random(5) == 1 then
if speed == 0 or speed == RUNNING_SPEED then
speed = WALKING_SPEED
elseif math.random(3) == 1 then
speed = RUNNING_SPEED
elseif math.random(10) == 1 then
speed = 0
end
elseif math.random(30) == 1 then
self.var.avoid_dir = self.var.avoid_dir * -1
end
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) < 2 then
speed = 0
self.object:punch(self.var.target, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy=20},
})
end
end
end
if math.random(10) == 1 or valid_target == false then
self.var.target = get_target_player(pos)
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, 1)
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 self.var.target == nil then
speed = 0
end
self.var.speed = speed
self.object:setyaw(yaw)
end
self.var.last_pos = pos
if speed > 0 then
npcf:set_animation(self, NPCF_ANIM_WALK)
else
npcf:set_animation(self, NPCF_ANIM_STAND)
end
local node = minetest.get_node(pos)
if string.find(node.name, "^default:water") then
acceleration = {x=0, y=-4, z=0}
velocity = {x=0, y=3, z=0}
elseif minetest.find_node_near(pos, 2, {"group:water"}) then
acceleration = {x=0, y=-1, z=0}
end
self.object:setvelocity(npcf:get_walk_velocity(speed, velocity.y, yaw))
self.object:setacceleration(acceleration)
end
end,
})
minetest.register_globalstep(function(dtime)
spawn_timer = spawn_timer + dtime
if spawn_timer > SPAWN_UPDATE_TIME then
for _,player in ipairs(minetest.get_connected_players()) do
if player then
local name = player:get_player_name()
local hp = player:get_hp() or 0
if name and hp > 0 then
if not target_players[name] then
if math.random(TARGET_CHANCE) == 1 then
local time = TARGET_TIME_MAX - TARGET_TIME_MIN
target_players[name] = math.random(time) + TARGET_TIME_MIN
end
end
end
if target_players[name] and math.random(SPAWN_CHANCE) == 1 then
local pos = player:getpos()
if pos then
local angle = math.rad(math.random(360))
local x = pos.x + math.cos(angle) * TARGET_RADIUS
local z = pos.z + math.sin(angle) * TARGET_RADIUS
local p1 = {x=x, y=pos.y + TARGET_RADIUS, z=z}
local p2 = {x=x, y=pos.y - TARGET_RADIUS, z=z}
local res, spawn_pos = minetest.line_of_sight(p1, p2, 1)
if spawn_pos then
local node = minetest.get_node(spawn_pos)
for _, v in ipairs(SPAWN_NODES) do
if node.name == v then
spawn_pos.y = spawn_pos.y + 1.5
spawn_mob(spawn_pos)
break
end
end
end
end
target_players[name] = target_players[name] - dtime
if target_players[name] <= 0 then
target_players[name] = nil
end
end
end
end
spawn_timer = 0
end
end)
minetest.register_on_dieplayer(function(player)
for _, npc in pairs(npcf.npcs) do
if npc.object and npc.name == "npcf_mob:npc" then
npc.var.target = get_target_player(npc.pos)
end
end
end)

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

17
npcf_trader/README.txt Normal file
View File

@ -0,0 +1,17 @@
Mod - Trader NPC [npcf_trader]
------------------------------
License Source Code: 2013 Stuart Jones - LGPL v2.1
License Textures: WTFPL
Depends: npcf
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 the 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.

1
npcf_trader/depends.txt Normal file
View File

@ -0,0 +1 @@
npcf

View File

@ -1,5 +1,5 @@
local function show_formspec(self, player_name, selected_id)
local inv = minetest.get_inventory({type="detached", name="npcf_"..self.npc_name})
local inv = minetest.get_inventory({type="detached", name="npcf_"..self.npc_id})
if not inv then
return
end
@ -48,8 +48,8 @@ local function show_formspec(self, player_name, selected_id)
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[detached:npcf_"..self.npc_id..";input;0.0,3.7;1,1;]"
.."list[detached:npcf_"..self.npc_id..";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
@ -59,7 +59,7 @@ local function show_formspec(self, player_name, selected_id)
.."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;]"
.."list[detached:npcf_"..self.npc_id..";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
@ -68,7 +68,7 @@ local function show_formspec(self, player_name, selected_id)
.."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)
npcf:show_formspec(player_name, self.npc_id, formspec)
end
local function get_field_qty(str)
@ -89,18 +89,17 @@ local function is_valid_item(item)
end
end
npcf:register_npc("npcf:trade_npc" ,{
npcf:register_npc("npcf_trader:npc" ,{
description = "Trader NPC",
mesh = "npcf_deco.x",
textures = {"npcf_skin_trader.png"},
nametag_color = "yellow",
mesh = "npcf_trader.x",
textures = {"npcf_trader_skin.png"},
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, {
inventory_image = "npcf_trader_inv.png",
on_activate = function(self)
local inv = minetest.create_detached_inventory("npcf_"..self.npc_id, {
on_put = function(inv, listname, index, stack, player)
local player_name = player:get_player_name()
if listname == "stock" and self.owner == player_name then
@ -163,7 +162,7 @@ npcf:register_npc("npcf:trade_npc" ,{
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 inv = minetest.get_inventory({type="detached", name="npcf_"..self.npc_id})
local input = inv:get_stack("input", 1)
local output = inv:get_stack("output", 1)
local item_buy = input:get_name()
@ -188,7 +187,7 @@ npcf:register_npc("npcf:trade_npc" ,{
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})
local inv = minetest.get_inventory({type="detached", name="npcf_"..self.npc_id})
inv:set_stack("input", 1, ItemStack(""))
table.remove(self.metadata.trades, selected_id)
show_formspec(self, player_name, nil)

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 755 B

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 B

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