Next generation rewrite
2
.gitignore
vendored
@ -4,4 +4,4 @@
|
|||||||
*bak*
|
*bak*
|
||||||
tags
|
tags
|
||||||
*.vim
|
*.vim
|
||||||
|
npcf.conf
|
||||||
|
376
README.md
Normal 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.
|
||||||
|
|
20
README.txt
@ -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
@ -1 +0,0 @@
|
|||||||
npcf.conf
|
|
286
npcf/README.md
@ -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
@ -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
|
||||||
|
|
@ -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", {
|
minetest.register_chatcommand("npcf", {
|
||||||
params = "<cmd> [npc_name] [args]",
|
params = params,
|
||||||
description = "NPC Management",
|
description = "NPC Management",
|
||||||
func = function(name, param)
|
func = function(name, param)
|
||||||
local index = npcf:get_index()
|
local npc = nil
|
||||||
local admin = minetest.check_player_privs(name, {server=true})
|
local admin = minetest.check_player_privs(name, {server=true})
|
||||||
local cmd, npc_name, args = string.match(param, "^([^ ]+) (.-) (.+)$")
|
local cmd, npc_id, args = string.match(param, "^([^ ]+) (.-) (.+)$")
|
||||||
if cmd and npc_name and args then
|
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 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)
|
local pos = minetest.string_to_pos(args)
|
||||||
if args == "here" then
|
if args == "here" then
|
||||||
local player = minetest.get_player_by_name(name)
|
local player = minetest.get_player_by_name(name)
|
||||||
@ -17,176 +89,183 @@ minetest.register_chatcommand("npcf", {
|
|||||||
end
|
end
|
||||||
if pos then
|
if pos then
|
||||||
pos.y = pos.y + 1
|
pos.y = pos.y + 1
|
||||||
local luaentity = npcf:get_luaentity(npc_name)
|
npc.pos = pos
|
||||||
if luaentity then
|
npc.origin.pos = pos
|
||||||
if admin or luaentity.owner == name then
|
npcf:save(npc_id)
|
||||||
luaentity.object:setpos(pos)
|
if npc.object then
|
||||||
luaentity.origin.pos = pos
|
npc.object:setpos(pos)
|
||||||
npcf:save(luaentity)
|
end
|
||||||
pos = minetest.pos_to_string(pos)
|
pos = minetest.pos_to_string(pos)
|
||||||
minetest.log("action", name.." moves NPC "..npc_name.." to "..pos)
|
minetest.log("action", name.." moves NPC "..npc_id.." to "..pos)
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
minetest.chat_send_player(name, "Invalid position "..args)
|
minetest.chat_send_player(name, "Invalid position "..args)
|
||||||
end
|
end
|
||||||
|
elseif cmd == "setlook" then
|
||||||
|
if not get_permission(name, npc_id) then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
elseif cmd == "load" then
|
local yaw = nil
|
||||||
if admin or name == index[npc_name] then
|
|
||||||
local pos = minetest.string_to_pos(args)
|
|
||||||
if args == "here" then
|
if args == "here" then
|
||||||
local player = minetest.get_player_by_name(name)
|
local player = minetest.get_player_by_name(name)
|
||||||
if player then
|
if player then
|
||||||
pos = player:getpos()
|
pos = player:getpos()
|
||||||
end
|
|
||||||
end
|
|
||||||
if pos then
|
if pos then
|
||||||
pos.y = pos.y + 1
|
yaw = npcf:get_face_direction(npc.pos, pos)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
else
|
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
|
||||||
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
|
elseif cmd == "tell" then
|
||||||
if admin or name == index[npc_name] then
|
if not get_permission(name, npc_id) then
|
||||||
local luaentity = npcf:get_luaentity(npc_name)
|
return
|
||||||
if luaentity and luaentity.on_tell then
|
end
|
||||||
luaentity.on_tell(luaentity, name, args)
|
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
|
end
|
||||||
else
|
|
||||||
minetest.chat_send_player(name, "You don't have permission to tell "..npc_name.." things")
|
|
||||||
end
|
end
|
||||||
elseif cmd == "setskin" then
|
elseif cmd == "setskin" then
|
||||||
if admin or name == index[npc_name] then
|
if not get_permission(name, npc_id) 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
|
return
|
||||||
end
|
end
|
||||||
|
npc.properties.textures[1] = args
|
||||||
|
npcf:save(npc_id)
|
||||||
|
if npc.object then
|
||||||
|
npc.object:set_properties(npc.properties)
|
||||||
end
|
end
|
||||||
local luaentity = npcf:get_luaentity(npc_name)
|
minetest.log("action", name.." changes NPC "..npc_id.." skin to "..args)
|
||||||
if luaentity then
|
else
|
||||||
luaentity.properties.textures[1] = args
|
minetest.chat_send_player(name, "Invalid command "..cmd)
|
||||||
luaentity.object:set_properties(luaentity.properties)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
|
elseif cmd and npc_id then
|
||||||
|
if cmd == "titletext" then
|
||||||
|
if not get_permission(name, npc_id) then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
cmd, npc_name = string.match(param, "([^ ]+) (.+)")
|
npc.title.text = nil
|
||||||
if cmd and npc_name then
|
update_title(npc)
|
||||||
if cmd == "delete" and admin then
|
elseif cmd == "delete" then
|
||||||
npcf:clear(npc_name)
|
if not get_permission(name, npc_id) then
|
||||||
local input = io.open(NPCF_DATADIR.."/"..npc_name..".npc", "r")
|
return
|
||||||
if input then
|
|
||||||
io.close(input)
|
|
||||||
os.remove(NPCF_DATADIR.."/"..npc_name..".npc")
|
|
||||||
end
|
end
|
||||||
if index[npc_name] then
|
npcf:delete(npc_id)
|
||||||
index[npc_name] = nil
|
minetest.log("action", name.." deletes NPC "..npc_id)
|
||||||
local output = io.open(NPCF_DATADIR.."/index.txt", 'w')
|
elseif cmd == "unload" then
|
||||||
if output then
|
if not get_permission(name, npc_id) then
|
||||||
output:write(minetest.serialize(index))
|
return
|
||||||
io.close(output)
|
|
||||||
end
|
end
|
||||||
minetest.log("action", name.." deletes NPC "..npc_name)
|
npcf:unload(npc_id)
|
||||||
end
|
minetest.log("action", name.." unloads NPC "..npc_id)
|
||||||
elseif cmd == "clear" then
|
elseif cmd == "load" then
|
||||||
if admin or name == index[npc_name] then
|
if not get_permission(name, npc_id) then
|
||||||
npcf:clear(npc_name)
|
return
|
||||||
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)
|
|
||||||
end
|
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
|
end
|
||||||
elseif cmd == "save" then
|
elseif cmd == "save" then
|
||||||
if admin or name == index[npc_name] then
|
if not get_permission(name, npc_id) then
|
||||||
local saved = false
|
return
|
||||||
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)
|
|
||||||
end
|
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
|
end
|
||||||
elseif cmd == "getpos" then
|
elseif cmd == "getpos" then
|
||||||
local located = false
|
local msg = "NPC "..npc_id
|
||||||
local luaentity = npcf:get_luaentity(npc_name)
|
if npc and npcf.index[npc_id] then
|
||||||
if luaentity then
|
local pos = {
|
||||||
local pos = luaentity.object:getpos()
|
x = math.floor(npc.pos.x * 10) * 0.1,
|
||||||
if pos then
|
y = math.floor(npc.pos.y * 10) * 0.1 - 1,
|
||||||
pos.x = math.floor(pos.x * 10) * 0.1
|
z = math.floor(npc.pos.z * 10) * 0.1
|
||||||
pos.y = math.floor(pos.y * 10) * 0.1 - 1
|
}
|
||||||
pos.z = math.floor(pos.z * 10) * 0.1
|
msg = msg.." located at "..minetest.pos_to_string(pos)
|
||||||
local msg = npc_name.." located at "..minetest.pos_to_string(pos)
|
else
|
||||||
|
msg = msg.." position unavilable"
|
||||||
|
end
|
||||||
minetest.chat_send_player(name, msg)
|
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
|
||||||
end
|
end
|
||||||
if located == false then
|
else
|
||||||
minetest.chat_send_player(name, "Unable to locate "..npc_name)
|
minetest.chat_send_player(name, "Invalid command "..cmd)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
cmd = string.match(param, "([^ ]+)")
|
local msg = "Usage: /npcf "..params.."\n\nenter /npcf help for available commands"
|
||||||
if cmd then
|
minetest.chat_send_player(name, msg)
|
||||||
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
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,3 +1 @@
|
|||||||
default
|
default
|
||||||
instabuild?
|
|
||||||
skins?
|
|
||||||
|
@ -1,11 +1,96 @@
|
|||||||
NPCF_MODPATH = minetest.get_modpath(minetest.get_current_modname())
|
NPCF_MODPATH = minetest.get_modpath(minetest.get_current_modname())
|
||||||
NPCF_DATADIR = minetest.get_worldpath().."/npc_data"
|
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.."/npcf.lua")
|
||||||
dofile(NPCF_MODPATH.."/chatcommands.lua")
|
dofile(NPCF_MODPATH.."/chatcommands.lua")
|
||||||
|
|
||||||
dofile(NPCF_MODPATH.."/npcs/info_npc.lua")
|
minetest.after(0, function()
|
||||||
dofile(NPCF_MODPATH.."/npcs/deco_npc.lua")
|
local dirlist = minetest.get_dir_list(NPCF_DATADIR) or {}
|
||||||
dofile(NPCF_MODPATH.."/npcs/guard_npc.lua")
|
for _, fn in pairs(dirlist) do
|
||||||
dofile(NPCF_MODPATH.."/npcs/trade_npc.lua")
|
local id = string.match(fn, "^(.+)%.npc$")
|
||||||
dofile(NPCF_MODPATH.."/npcs/builder_npc.lua")
|
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)
|
||||||
|
|
||||||
|
693
npcf/npcf.lua
@ -1,51 +1,36 @@
|
|||||||
npcf = {}
|
local function deepcopy(obj, seen)
|
||||||
NPCF_ANIM_STAND = 1
|
if type(obj) ~= 'table' then
|
||||||
NPCF_ANIM_SIT = 2
|
return obj
|
||||||
NPCF_ANIM_LAY = 3
|
end
|
||||||
NPCF_ANIM_WALK = 4
|
if seen then
|
||||||
NPCF_ANIM_WALK_MINE = 5
|
if seen[obj] then
|
||||||
NPCF_ANIM_MINE = 6
|
return seen[obj]
|
||||||
NPCF_SHOW_IN_CREATIVE = true
|
end
|
||||||
NPCF_SHOW_NAMETAGS = true
|
end
|
||||||
NPCF_BUILDER_REQ_MATERIALS = false
|
local s = seen or {}
|
||||||
NPCF_DECO_FREE_ROAMING = true
|
local copy = setmetatable({}, getmetatable(obj))
|
||||||
NPCF_GUARD_ATTACK_PLAYERS = true
|
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")
|
npcf = {
|
||||||
if input then
|
npc = {
|
||||||
dofile(NPCF_MODPATH.."/npcf.conf")
|
autoload = true,
|
||||||
input:close()
|
timer = 0,
|
||||||
input = nil
|
},
|
||||||
end
|
npcs = {},
|
||||||
local timer = 0
|
index = {},
|
||||||
local index = {}
|
default_npc = {
|
||||||
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,
|
|
||||||
physical = true,
|
physical = true,
|
||||||
weight = 5,
|
|
||||||
collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
|
collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
|
||||||
visual = "mesh",
|
visual = "mesh",
|
||||||
visual_size = {x=1, y=1},
|
mesh = "character.b3d",
|
||||||
mesh = "character.x",
|
|
||||||
textures = {"character.png"},
|
textures = {"character.png"},
|
||||||
colors = {},
|
|
||||||
is_visible = true,
|
|
||||||
makes_footstep_sound = true,
|
makes_footstep_sound = true,
|
||||||
automatic_rotate = false,
|
register_spawner = true,
|
||||||
stepheight = 0,
|
|
||||||
automatic_face_movement_dir = false,
|
|
||||||
armor_groups = {immortal=1},
|
armor_groups = {immortal=1},
|
||||||
animation = {
|
animation = {
|
||||||
stand_START = 0,
|
stand_START = 0,
|
||||||
@ -61,468 +46,362 @@ local default_npc = {
|
|||||||
walk_mine_START = 200,
|
walk_mine_START = 200,
|
||||||
walk_mine_END = 219,
|
walk_mine_END = 219,
|
||||||
},
|
},
|
||||||
|
animation_state = 0,
|
||||||
animation_speed = 30,
|
animation_speed = 30,
|
||||||
decription = "Default NPC",
|
decription = "Default NPC",
|
||||||
inventory_image = "npcf_inv_top.png",
|
inventory_image = "npcf_inv.png",
|
||||||
show_nametag = true,
|
title = {},
|
||||||
nametag_color = "white",
|
properties = {},
|
||||||
metadata = {},
|
metadata = {},
|
||||||
var = {},
|
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)
|
function npcf.npc:new(ref)
|
||||||
if player then
|
ref = ref or {}
|
||||||
if player:is_player() then
|
setmetatable(ref, self)
|
||||||
local player_name = player:get_player_name()
|
self.__index = self
|
||||||
if minetest.get_player_by_name(player_name) then
|
return ref
|
||||||
return player_name
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_valid_npc_name(npc_name)
|
function npcf:add_title(ref)
|
||||||
if npc_name then
|
if not ref.object or not ref.title.text then
|
||||||
return npc_name:len() <= 12 and npc_name:match("^[A-Za-z0-9%_%-]+$")
|
return
|
||||||
|
end
|
||||||
|
local object = ref.title.object
|
||||||
|
if object then
|
||||||
|
if not object:getpos() then
|
||||||
|
object = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if not object then
|
||||||
local function get_valid_entity(luaentity)
|
local pos = ref.object:getpos()
|
||||||
if luaentity then
|
object = minetest.add_entity(pos, "npcf:title")
|
||||||
if luaentity.name and luaentity.owner and luaentity.origin then
|
if not object then
|
||||||
return true
|
return
|
||||||
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
|
end
|
||||||
local texture = "npcf_tag_bg.png"
|
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
|
local i = 0
|
||||||
parent.npc_name:gsub(".", function(char)
|
ref.title.text:gsub(".", function(char)
|
||||||
if char:byte() > 64 and char:byte() < 91 then
|
if char:byte() > 64 and char:byte() < 91 then
|
||||||
char = "U"..char
|
char = "U"..char
|
||||||
end
|
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
|
i = i + 11
|
||||||
end)
|
end)
|
||||||
tag:set_attach(parent.object, "", {x=0,y=9,z=0}, {x=0,y=0,z=0})
|
if ref.title.color then
|
||||||
tag = tag:get_luaentity()
|
texture = texture.."^[colorize:"..ref.title.color
|
||||||
tag.npc_name = parent.npc_name
|
end
|
||||||
tag.object:set_properties({textures={texture}})
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function npcf:register_npc(name, def)
|
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
|
for k, v in pairs(default_npc) do
|
||||||
if not def[k] then
|
if not ref[k] then
|
||||||
def[k] = v
|
ref[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
minetest.register_entity(name, {
|
ref.on_activate = function(self, staticdata)
|
||||||
hp_max = def.hp_max,
|
if staticdata == "expired" then
|
||||||
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
|
|
||||||
self.object:remove()
|
self.object:remove()
|
||||||
minetest.log("action", "Removed unknown npc")
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if npc.metadata then
|
if self.object then
|
||||||
self.metadata = npc.metadata
|
self.object:set_armor_groups(def.armor_groups)
|
||||||
end
|
end
|
||||||
if npc.properties then
|
if type(ref.on_construct) == "function" then
|
||||||
self.properties = npc.properties
|
ref.on_construct(self)
|
||||||
self.object:set_properties(npc.properties)
|
|
||||||
end
|
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
|
if type(def.on_activate) == "function" then
|
||||||
def.on_activate(self, staticdata, dtime_s)
|
minetest.after(0.5, function()
|
||||||
end
|
def.on_activate(self)
|
||||||
else
|
|
||||||
self.object:remove()
|
|
||||||
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
|
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
|
if type(def.on_rightclick) == "function" then
|
||||||
def.on_rightclick(self, clicker)
|
def.on_rightclick(self, clicker)
|
||||||
end
|
end
|
||||||
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
|
||||||
end,
|
if type(ref.on_destruct) == "function" then
|
||||||
on_punch = function(self, hitter)
|
ref.on_destruct(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
|
||||||
end
|
end
|
||||||
if type(def.on_punch) == "function" then
|
if type(def.on_punch) == "function" then
|
||||||
def.on_punch(self, hitter)
|
def.on_punch(self, hitter)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
ref.on_step = function(self, dtime)
|
||||||
on_step = function(self, dtime)
|
|
||||||
self.timer = self.timer + 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)
|
def.on_step(self, dtime)
|
||||||
end
|
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
|
end
|
||||||
def.on_tell(self, sender, senderpos, message)
|
ref.get_staticdata = function(self)
|
||||||
|
return "expired"
|
||||||
end
|
end
|
||||||
end,
|
minetest.register_entity(name, ref)
|
||||||
get_staticdata = function(self)
|
if not ref.register_spawner then
|
||||||
local npc_data = {
|
return
|
||||||
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
|
end
|
||||||
minetest.register_node(name.."_spawner", {
|
minetest.register_node(name.."_spawner", {
|
||||||
description = def.description,
|
description = ref.description,
|
||||||
inventory_image = minetest.inventorycube("npcf_inv_top.png", def.inventory_image, def.inventory_image),
|
inventory_image = minetest.inventorycube("npcf_inv.png", ref.inventory_image, ref.inventory_image),
|
||||||
tiles = {"npcf_inv_top.png", def.inventory_image, def.inventory_image},
|
tiles = {"npcf_inv.png", ref.inventory_image, ref.inventory_image},
|
||||||
paramtype2 = "facedir",
|
paramtype2 = "facedir",
|
||||||
groups = groups,
|
groups = {cracky=3, oddly_breakable_by_hand=3},
|
||||||
sounds = default.node_sound_defaults(),
|
sounds = default.node_sound_defaults(),
|
||||||
on_construct = function(pos)
|
on_construct = function(pos)
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
meta:set_string("formspec", form_reg)
|
meta:set_string("formspec", "size[8,3]"
|
||||||
meta:set_string("infotext", def.description.." spawner")
|
.."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,
|
end,
|
||||||
after_place_node = function(pos, placer, itemstack)
|
after_place_node = function(pos, placer, itemstack)
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
meta:set_string("owner", placer:get_player_name())
|
meta:set_string("owner", placer:get_player_name())
|
||||||
|
if minetest.setting_getbool("creative_mode") == false then
|
||||||
itemstack:take_item()
|
itemstack:take_item()
|
||||||
|
end
|
||||||
return itemstack
|
return itemstack
|
||||||
end,
|
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)
|
on_receive_fields = function(pos, formname, fields, sender)
|
||||||
if fields.cancel then
|
if fields.cancel then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
local owner = meta:get_string("owner")
|
local owner = meta:get_string("owner")
|
||||||
local player_name = sender:get_player_name()
|
local sender_name = sender:get_player_name()
|
||||||
if player_name == owner then
|
local id = fields.id
|
||||||
if get_valid_npc_name(fields.name) then
|
if id and sender_name == owner then
|
||||||
if index[fields.name] then
|
if id:len() <= 16 and id:match("^[A-Za-z0-9%_%-]+$") then
|
||||||
minetest.chat_send_player(player_name, "Error: Name Already Taken!")
|
if npcf.index[id] then
|
||||||
|
minetest.chat_send_player(sender_name, "Error: ID Already Taken!")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
minetest.chat_send_player(player_name, "Error: Invalid NPC Name!")
|
minetest.chat_send_player(sender_name, "Error: Invalid ID!")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
minetest.remove_node(pos)
|
npcf.index[id] = owner
|
||||||
local npc_pos = {x=pos.x, y=pos.y + 0.5, z=pos.z}
|
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 yaw = sender:get_look_yaw() + math.pi * 0.5
|
||||||
local luaentity = npcf:spawn(npc_pos, name, {
|
local ref = {
|
||||||
owner = player_name,
|
id = id,
|
||||||
npc_name = fields.name,
|
pos = npc_pos,
|
||||||
origin = {pos=npc_pos, yaw=yaw}
|
yaw = yaw,
|
||||||
})
|
name = name,
|
||||||
if luaentity and type(def.on_registration) == "function" then
|
owner = owner,
|
||||||
def.on_registration(luaentity, pos, sender)
|
}
|
||||||
|
local npc = npcf:add_npc(ref)
|
||||||
|
npcf:save(ref.id)
|
||||||
|
if npc then
|
||||||
|
npc:update()
|
||||||
end
|
end
|
||||||
|
minetest.remove_node(pos)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function npcf:spawn(pos, name, def)
|
function npcf:unload(id)
|
||||||
if pos and name and def.npc_name and def.owner then
|
local npc = self.npcs[id]
|
||||||
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)
|
|
||||||
if npc then
|
if npc then
|
||||||
local luaentity = npc:get_luaentity()
|
if npc.object then
|
||||||
if luaentity then
|
npc.object:remove()
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
return 1
|
npc.autoload = false
|
||||||
|
npcf:save(id)
|
||||||
|
self.npcs[id] = nil
|
||||||
end
|
end
|
||||||
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)
|
function npcf:delete(id)
|
||||||
if get_valid_entity(luaentity) then
|
npcf:unload(id)
|
||||||
local npc = {
|
local output = io.open(NPCF_DATADIR.."/"..id..".npc", "w")
|
||||||
name = luaentity.name,
|
if input then
|
||||||
owner = luaentity.owner,
|
output:write("")
|
||||||
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))
|
|
||||||
io.close(output)
|
io.close(output)
|
||||||
return 1
|
|
||||||
end
|
end
|
||||||
minetest.log("error", "Failed to save NPC "..npc_name)
|
npcf.index[id] = nil
|
||||||
return
|
|
||||||
end
|
|
||||||
minetest.log("error", "Attempt to save invalid NPC")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function npcf:set_animation(luaentity, state)
|
function npcf:load(id)
|
||||||
if get_valid_entity(luaentity) and state then
|
local input = io.open(NPCF_DATADIR.."/"..id..".npc", 'r')
|
||||||
if state ~= luaentity.state then
|
if input then
|
||||||
local speed = luaentity.animation_speed
|
local ref = minetest.deserialize(input:read('*all'))
|
||||||
local anim = luaentity.animation
|
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 speed and anim then
|
||||||
if state == NPCF_ANIM_STAND and anim.stand_START and anim.stand_END 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
|
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
|
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
|
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
|
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
|
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
|
end
|
||||||
luaentity.state = state
|
entity.animation_state = state
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function npcf:get_index()
|
function npcf:get_luaentity(id)
|
||||||
return index
|
local npc = self.npcs[id] or {}
|
||||||
end
|
if npc.object then
|
||||||
|
return npc.object:get_luaentity()
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function npcf:get_face_direction(v1, v2)
|
function npcf:get_face_direction(p1, p2)
|
||||||
if v1 and v2 then
|
if p1 and p2 then
|
||||||
if v1.x and v2.x and v1.z and v2.z then
|
if p1.x and p2.x and p1.z and p2.z then
|
||||||
dx = v1.x - v2.x
|
local px = p1.x - p2.x
|
||||||
dz = v2.z - v1.z
|
local pz = p2.z - p1.z
|
||||||
return math.atan2(dx, dz)
|
return math.atan2(px, pz)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -539,31 +418,9 @@ function npcf:get_walk_velocity(speed, y, yaw)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function npcf:show_formspec(player_name, npc_name, formspec)
|
function npcf:show_formspec(name, id, formspec)
|
||||||
if player_name and npc_name and formspec then
|
if name and id and formspec then
|
||||||
minetest.show_formspec(player_name, "npcf_"..npc_name, formspec)
|
minetest.show_formspec(name, "npcf_"..id, formspec)
|
||||||
end
|
end
|
||||||
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)
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
14
npcf_builder/README.txt
Normal 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
@ -0,0 +1 @@
|
|||||||
|
npcf
|
@ -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_SPEED = 5
|
||||||
local MAX_POS = 1000
|
local MAX_POS = 1000
|
||||||
local DEFAULT_NODE = {name="air"}
|
local DEFAULT_NODE = {name="air"}
|
||||||
local SCHEMS = {"basic_hut.we"}
|
local SCHEMS = {"basic_hut.we"}
|
||||||
local MODPATH = minetest.get_modpath("instabuild")
|
local INSTABUILD_PATH = minetest.get_modpath("instabuild")
|
||||||
if MODPATH then
|
if INSTABUILD_PATH then
|
||||||
for _,v in ipairs({"factory.we", "large_warehouse.we", "small_farm.we", "tall_tower.we",
|
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",
|
"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
|
"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 function load_schematic(self, filename)
|
||||||
local input = nil
|
local input = nil
|
||||||
if MODPATH then
|
if INSTABUILD_PATH then
|
||||||
input = io.open(MODPATH.."/models/"..filename, "r")
|
input = io.open(INSTABUILD_PATH.."/models/"..filename, "r")
|
||||||
end
|
end
|
||||||
if not input then
|
if not input then
|
||||||
input = io.open(NPCF_MODPATH.."/schems/"..filename, "r")
|
input = io.open(MODPATH.."/schems/"..filename, "r")
|
||||||
end
|
end
|
||||||
if input then
|
if input then
|
||||||
local data = minetest.deserialize(input:read('*all'))
|
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..";]"
|
.."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;]"
|
.."list[current_player;main;0.0,5.0;8.0,4.0;]"
|
||||||
..button_build
|
..button_build
|
||||||
if NPCF_BUILDER_REQ_MATERIALS == true then
|
if BUILDER_REQ_MATERIALS == true then
|
||||||
formspec = formspec.."list[detached:npcf_"..self.npc_name..";input;6.0,3.5;1,1;]"
|
formspec = formspec.."list[detached:npcf_"..self.npc_id..";input;6.0,3.5;1,1;]"
|
||||||
end
|
end
|
||||||
if self.owner == player_name then
|
if self.owner == player_name then
|
||||||
formspec = formspec.."button_exit[5.0,2.0;3.0,0.5;build_cancel;Cancel Build]"
|
formspec = formspec.."button_exit[5.0,2.0;3.0,0.5;build_cancel;Cancel Build]"
|
||||||
end
|
end
|
||||||
npcf:show_formspec(player_name, self.npc_name, formspec)
|
npcf:show_formspec(player_name, self.npc_id, formspec)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_speed(distance)
|
local function get_speed(distance)
|
||||||
@ -132,10 +134,9 @@ local function get_speed(distance)
|
|||||||
return speed
|
return speed
|
||||||
end
|
end
|
||||||
|
|
||||||
npcf:register_npc("npcf:builder_npc" ,{
|
npcf:register_npc("npcf_builder:npc" ,{
|
||||||
description = "Builder NPC",
|
description = "Builder NPC",
|
||||||
textures = {"npcf_skin_builder.png"},
|
textures = {"npcf_builder_skin.png"},
|
||||||
nametag_color = "green",
|
|
||||||
metadata = {
|
metadata = {
|
||||||
schematic = nil,
|
schematic = nil,
|
||||||
inventory = {},
|
inventory = {},
|
||||||
@ -149,8 +150,8 @@ npcf:register_npc("npcf:builder_npc" ,{
|
|||||||
nodedata = {},
|
nodedata = {},
|
||||||
last_pos = {},
|
last_pos = {},
|
||||||
},
|
},
|
||||||
stepheight = 1,
|
stepheight = 1.1,
|
||||||
inventory_image = "npcf_inv_builder_npc.png",
|
inventory_image = "npcf_builder_inv.png",
|
||||||
on_construct = function(self)
|
on_construct = function(self)
|
||||||
self.metadata.building = false
|
self.metadata.building = false
|
||||||
self.object:setvelocity({x=0, y=0, z=0})
|
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)
|
load_schematic(self, self.metadata.schematic)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
on_activate = function(self, staticdata, dtime_s)
|
on_activate = function(self)
|
||||||
local inv = minetest.create_detached_inventory("npcf_"..self.npc_name, {
|
local inv = minetest.create_detached_inventory("npcf_"..self.npc_id, {
|
||||||
on_put = function(inv, listname, index, stack, player)
|
on_put = function(inv, listname, index, stack, player)
|
||||||
local player_name = player:get_player_name()
|
local player_name = player:get_player_name()
|
||||||
local item = stack:get_name()
|
local item = stack:get_name()
|
||||||
@ -181,7 +182,7 @@ npcf:register_npc("npcf:builder_npc" ,{
|
|||||||
local formspec = "size[6,5]"
|
local formspec = "size[6,5]"
|
||||||
.."textlist[0.0,0.0;5.0,4.0;schemlist;"..schemlist..";;]"
|
.."textlist[0.0,0.0;5.0,4.0;schemlist;"..schemlist..";;]"
|
||||||
.."button_exit[5.0,4.5;1.0,0.5;;Ok]"
|
.."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
|
return
|
||||||
elseif self.metadata.building == true then
|
elseif self.metadata.building == true then
|
||||||
self.metadata.building = false
|
self.metadata.building = false
|
||||||
@ -228,7 +229,7 @@ npcf:register_npc("npcf:builder_npc" ,{
|
|||||||
end
|
end
|
||||||
minetest.add_node(nodedata.pos, nodedata.node)
|
minetest.add_node(nodedata.pos, nodedata.node)
|
||||||
local door_top = string.find(nodedata.node.name, "^doors+_t_[12]$")
|
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)
|
local name = get_registered_nodename(nodedata.node.name)
|
||||||
if self.metadata.inventory[name] > 0 then
|
if self.metadata.inventory[name] > 0 then
|
||||||
self.metadata.inventory[name] = self.metadata.inventory[name] - 1
|
self.metadata.inventory[name] = self.metadata.inventory[name] - 1
|
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 678 B |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
11
npcf_deco/README.txt
Normal 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
@ -0,0 +1 @@
|
|||||||
|
npcf
|
@ -1,5 +1,6 @@
|
|||||||
|
local FREE_ROAMING = true
|
||||||
local WALKING_SPEED = 1
|
local WALKING_SPEED = 1
|
||||||
local RUNNING_SPEED = 2.5
|
local RUNNING_SPEED = 2
|
||||||
local FOLLOW_RADIUS_MIN = 5
|
local FOLLOW_RADIUS_MIN = 5
|
||||||
local FOLLOW_RADIUS_MAX = 30
|
local FOLLOW_RADIUS_MAX = 30
|
||||||
local AVOIDED_NODES = {
|
local AVOIDED_NODES = {
|
||||||
@ -34,11 +35,10 @@ local function get_target_player(self)
|
|||||||
return target_player
|
return target_player
|
||||||
end
|
end
|
||||||
|
|
||||||
npcf:register_npc("npcf:deco_npc" ,{
|
npcf:register_npc("npcf_deco:npc" ,{
|
||||||
description = "Decorative NPC",
|
description = "Decorative NPC",
|
||||||
mesh = "npcf_deco.x",
|
mesh = "npcf_deco.x",
|
||||||
textures = {"npcf_skin_deco.png"},
|
textures = {"npcf_deco_skin.png"},
|
||||||
nametag_color = "magenta",
|
|
||||||
animation_speed = 12,
|
animation_speed = 12,
|
||||||
animation = {
|
animation = {
|
||||||
stand_START = 0,
|
stand_START = 0,
|
||||||
@ -65,35 +65,21 @@ npcf:register_npc("npcf:deco_npc" ,{
|
|||||||
last_pos = {x=0,y=0,z=0},
|
last_pos = {x=0,y=0,z=0},
|
||||||
target = nil,
|
target = nil,
|
||||||
},
|
},
|
||||||
stepheight = 1,
|
stepheight = 1.1,
|
||||||
inventory_image = "npcf_inv_deco_npc.png",
|
inventory_image = "npcf_deco_inv.png",
|
||||||
on_construct = function(self)
|
on_construct = function(self)
|
||||||
self.object:setvelocity({x=0, y=0, z=0})
|
self.object:setvelocity({x=0, y=0, z=0})
|
||||||
self.object:setacceleration({x=0, y=-10, z=0})
|
self.object:setacceleration({x=0, y=-10, z=0})
|
||||||
npcf:set_animation(self, ANIMATION[self.metadata.anim_stop].state)
|
npcf:set_animation(self, ANIMATION[self.metadata.anim_stop].state)
|
||||||
end,
|
end,
|
||||||
on_activate = function(self, staticdata, dtime_s)
|
on_activate = function(self)
|
||||||
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
if self.metadata.follow_players == "true" then
|
if self.metadata.follow_players == "true" then
|
||||||
self.var.target = get_target_player(self)
|
self.var.target = get_target_player(self)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
on_rightclick = function(self, clicker)
|
on_rightclick = function(self, clicker)
|
||||||
local player_name = clicker:get_player_name()
|
local player_name = clicker:get_player_name()
|
||||||
local message = "Hello, my name is "..self.npc_name
|
local message = "Hello"
|
||||||
if self.metadata.message then
|
if self.metadata.message then
|
||||||
message = minetest.formspec_escape(self.metadata.message)
|
message = minetest.formspec_escape(self.metadata.message)
|
||||||
end
|
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.."]"
|
.."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.."]"
|
.."checkbox[0.5,2.7;follow_players;Follow Players;"..self.metadata.follow_players.."]"
|
||||||
.."button_exit[7.0,3.5;1.0,0.5;;Ok]"
|
.."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.."]"
|
formspec = formspec.."checkbox[3.5,2.7;free_roaming;Wander Map;"..self.metadata.free_roaming.."]"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
formspec = "size[8,4]"
|
formspec = "size[8,4]".."label[0,0;"..message.."]"
|
||||||
.."label[0,0;"..message.."]"
|
|
||||||
end
|
end
|
||||||
self.var.speed = 0
|
self.var.speed = 0
|
||||||
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)
|
on_step = function(self, dtime)
|
||||||
if self.timer > 1 then
|
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 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 acceleration = {x=0, y=-10, z=0}
|
||||||
local velocity = self.object:getvelocity()
|
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
|
if roaming == true or self.metadata.follow_players == "true" then
|
||||||
speed = self.var.speed
|
speed = self.var.speed
|
||||||
if math.random(10) == 1 then
|
if math.random(10) == 1 then
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 360 B |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
12
npcf_guard/README.txt
Normal 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
@ -0,0 +1 @@
|
|||||||
|
npcf
|
@ -1,4 +1,5 @@
|
|||||||
local TARGET_RADIUS = 10
|
local GUARD_ATTACK_PLAYERS
|
||||||
|
local TARGET_RADIUS = 20
|
||||||
local MAX_SPEED = 5
|
local MAX_SPEED = 5
|
||||||
|
|
||||||
local function get_wield_image(item)
|
local function get_wield_image(item)
|
||||||
@ -30,18 +31,17 @@ end
|
|||||||
|
|
||||||
local function get_armor_texture(self)
|
local function get_armor_texture(self)
|
||||||
if self.metadata.show_armor == "true" then
|
if self.metadata.show_armor == "true" then
|
||||||
return "npcf_skin_armor.png"
|
return "npcf_guard_armor.png"
|
||||||
end
|
end
|
||||||
return "npcf_trans.png"
|
return "npcf_trans.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
npcf:register_npc("npcf:guard_npc" ,{
|
npcf:register_npc("npcf_guard:npc", {
|
||||||
description = "Guard NPC",
|
description = "Guard NPC",
|
||||||
mesh = "npcf_guard.x",
|
mesh = "npcf_guard.x",
|
||||||
textures = {"character.png", "npcf_skin_armor.png", "npcf_trans.png"},
|
textures = {"character.png", "npcf_guard_armor.png", "npcf_trans.png"},
|
||||||
nametag_color = "red",
|
inventory_image = "npcf_guard_inv.png",
|
||||||
inventory_image = "npcf_inv_guard_npc.png",
|
stepheight = 1.1,
|
||||||
stepheight = 1,
|
|
||||||
metadata = {
|
metadata = {
|
||||||
wielditem = "default:sword_steel",
|
wielditem = "default:sword_steel",
|
||||||
blacklist = "mobs:oerkki mobs:dungeon_master",
|
blacklist = "mobs:oerkki mobs:dungeon_master",
|
||||||
@ -50,12 +50,13 @@ npcf:register_npc("npcf:guard_npc" ,{
|
|||||||
follow_owner = "false",
|
follow_owner = "false",
|
||||||
show_armor = "true",
|
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:setvelocity({x=0, y=0, z=0})
|
||||||
self.object:setacceleration({x=0, y=-10, z=0})
|
self.object:setacceleration({x=0, y=-10, z=0})
|
||||||
local wield_image = get_wield_image(self.metadata.wielditem)
|
local wield_image = get_wield_image(self.metadata.wielditem)
|
||||||
local textures = {self.properties.textures[1], get_armor_texture(self), wield_image}
|
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,
|
end,
|
||||||
on_rightclick = function(self, clicker)
|
on_rightclick = function(self, clicker)
|
||||||
local player_name = clicker:get_player_name()
|
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.."]"
|
.."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[0.0,6.0;2.0,0.5;origin;Set Origin]"
|
||||||
.."button_exit[7.0,6.0;1.0,0.5;;Ok]"
|
.."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;"
|
formspec = formspec.."checkbox[4.0,4.5;attack_players;Attack Players;"
|
||||||
..self.metadata.attack_players.."]"
|
..self.metadata.attack_players.."]"
|
||||||
end
|
end
|
||||||
npcf:show_formspec(player_name, self.npc_name, formspec)
|
npcf:show_formspec(player_name, self.npc_id, formspec)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
on_step = function(self, dtime)
|
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
|
for _,object in ipairs(minetest.get_objects_inside_radius(pos, TARGET_RADIUS)) do
|
||||||
local to_target = false
|
local to_target = false
|
||||||
if object:is_player() then
|
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()
|
local player_name = object:get_player_name()
|
||||||
if player_name ~= self.owner then
|
if player_name ~= self.owner then
|
||||||
if not get_name_in_list(self.metadata.whitelist, player_name) then
|
if not get_name_in_list(self.metadata.whitelist, player_name) then
|
Before Width: | Height: | Size: 973 B After Width: | Height: | Size: 973 B |
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 316 B |
13
npcf_info/README.txt
Normal 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
@ -0,0 +1 @@
|
|||||||
|
npcf
|
@ -20,15 +20,19 @@ local function get_formspec(text, page)
|
|||||||
return formspec
|
return formspec
|
||||||
end
|
end
|
||||||
|
|
||||||
npcf:register_npc("npcf:info_npc" ,{
|
npcf:register_npc("npcf_info:npc" , {
|
||||||
description = "Information NPC",
|
description = "Information NPC",
|
||||||
textures = {"npcf_skin_info.png"},
|
textures = {"npcf_info_skin.png"},
|
||||||
nametag_color = "cyan",
|
|
||||||
metadata = {
|
metadata = {
|
||||||
infotext = "Infotext."
|
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)
|
on_rightclick = function(self, clicker)
|
||||||
|
print(dump(self.npc_id))
|
||||||
local player_name = clicker:get_player_name()
|
local player_name = clicker:get_player_name()
|
||||||
local infotext = minetest.formspec_escape(self.metadata.infotext)
|
local infotext = minetest.formspec_escape(self.metadata.infotext)
|
||||||
local formspec = get_formspec(infotext, 1)
|
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[0.0,5.5;2.0,0.5;page_1;View]"
|
||||||
.."button_exit[7.0,5.5;1.0,0.5;;Ok]"
|
.."button_exit[7.0,5.5;1.0,0.5;;Ok]"
|
||||||
end
|
end
|
||||||
npcf:show_formspec(player_name, self.npc_name, formspec)
|
npcf:show_formspec(player_name, self.npc_id, formspec)
|
||||||
end,
|
end,
|
||||||
on_receive_fields = function(self, fields, sender)
|
on_receive_fields = function(self, fields, sender)
|
||||||
for k,_ in pairs(fields) do
|
for k,_ in pairs(fields) do
|
||||||
page = k:gsub("page_", "")
|
local page = k:gsub("page_", "")
|
||||||
if page ~= k then
|
if page ~= k then
|
||||||
local formspec = get_formspec(self.metadata.infotext, tonumber(page))
|
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
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
Before Width: | Height: | Size: 562 B After Width: | Height: | Size: 562 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
11
npcf_mob/README.txt
Normal 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
@ -0,0 +1 @@
|
|||||||
|
npcf
|
280
npcf_mob/init.lua
Normal 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)
|
||||||
|
|
BIN
npcf_mob/models/npcf_mob.b3d
Normal file
BIN
npcf_mob/models/npcf_mob.blend
Normal file
BIN
npcf_mob/textures/npcf_mob_particle.png
Normal file
After Width: | Height: | Size: 578 B |
BIN
npcf_mob/textures/npcf_mob_skin.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
17
npcf_trader/README.txt
Normal 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
@ -0,0 +1 @@
|
|||||||
|
npcf
|
@ -1,5 +1,5 @@
|
|||||||
local function show_formspec(self, player_name, selected_id)
|
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
|
if not inv then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -48,8 +48,8 @@ local function show_formspec(self, player_name, selected_id)
|
|||||||
end
|
end
|
||||||
inv:set_stack("output", 1, output)
|
inv:set_stack("output", 1, output)
|
||||||
local formspec = "size[8,10]"
|
local formspec = "size[8,10]"
|
||||||
.."list[detached:npcf_"..self.npc_name..";input;0.0,3.7;1,1;]"
|
.."list[detached:npcf_"..self.npc_id..";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..";output;7.0,3.7;1,1;]"
|
||||||
.."list[current_player;main;0.0,5.0;8.0,4.0;]"
|
.."list[current_player;main;0.0,5.0;8.0,4.0;]"
|
||||||
if self.owner == player_name then
|
if self.owner == player_name then
|
||||||
formspec = formspec
|
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[3.3,9.7;2.0,0.5;item_sell;Item Sell;]"
|
||||||
.."field[5.3,9.7;1.0,0.5;qty_sell;Qty;]"
|
.."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]"
|
.."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
|
if select ~= "" then
|
||||||
formspec = formspec.."button[6.0,0.0;2.0,0.5;trade_delete_"..select..";Del Trade]"
|
formspec = formspec.."button[6.0,0.0;2.0,0.5;trade_delete_"..select..";Del Trade]"
|
||||||
end
|
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..";]"
|
.."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]"
|
.."button_exit[3.0,4.0;2.0,0.5;trade_accept;Accept]"
|
||||||
end
|
end
|
||||||
npcf:show_formspec(player_name, self.npc_name, formspec)
|
npcf:show_formspec(player_name, self.npc_id, formspec)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_field_qty(str)
|
local function get_field_qty(str)
|
||||||
@ -89,18 +89,17 @@ local function is_valid_item(item)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
npcf:register_npc("npcf:trade_npc" ,{
|
npcf:register_npc("npcf_trader:npc" ,{
|
||||||
description = "Trader NPC",
|
description = "Trader NPC",
|
||||||
mesh = "npcf_deco.x",
|
mesh = "npcf_trader.x",
|
||||||
textures = {"npcf_skin_trader.png"},
|
textures = {"npcf_trader_skin.png"},
|
||||||
nametag_color = "yellow",
|
|
||||||
metadata = {
|
metadata = {
|
||||||
trades = {},
|
trades = {},
|
||||||
inventory = {},
|
inventory = {},
|
||||||
},
|
},
|
||||||
inventory_image = "npcf_inv_trader_npc.png",
|
inventory_image = "npcf_trader_inv.png",
|
||||||
on_activate = function(self, staticdata, dtime_s)
|
on_activate = function(self)
|
||||||
local inv = minetest.create_detached_inventory("npcf_"..self.npc_name, {
|
local inv = minetest.create_detached_inventory("npcf_"..self.npc_id, {
|
||||||
on_put = function(inv, listname, index, stack, player)
|
on_put = function(inv, listname, index, stack, player)
|
||||||
local player_name = player:get_player_name()
|
local player_name = player:get_player_name()
|
||||||
if listname == "stock" and self.owner == player_name then
|
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)
|
show_formspec(self, player_name, id)
|
||||||
end
|
end
|
||||||
elseif fields.trade_accept then
|
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 input = inv:get_stack("input", 1)
|
||||||
local output = inv:get_stack("output", 1)
|
local output = inv:get_stack("output", 1)
|
||||||
local item_buy = input:get_name()
|
local item_buy = input:get_name()
|
||||||
@ -188,7 +187,7 @@ npcf:register_npc("npcf:trade_npc" ,{
|
|||||||
for k,_ in pairs(fields) do
|
for k,_ in pairs(fields) do
|
||||||
selected_id = k:gsub("trade_delete_", "")
|
selected_id = k:gsub("trade_delete_", "")
|
||||||
if selected_id ~= k and self.metadata.trades[tonumber(selected_id)] then
|
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(""))
|
inv:set_stack("input", 1, ItemStack(""))
|
||||||
table.remove(self.metadata.trades, selected_id)
|
table.remove(self.metadata.trades, selected_id)
|
||||||
show_formspec(self, player_name, nil)
|
show_formspec(self, player_name, nil)
|
11262
npcf_trader/models/npcf_trader.x
Normal file
Before Width: | Height: | Size: 755 B After Width: | Height: | Size: 755 B |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 85 B |
Before Width: | Height: | Size: 129 B |
Before Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 131 B |
Before Width: | Height: | Size: 129 B |
Before Width: | Height: | Size: 128 B |
Before Width: | Height: | Size: 136 B |
Before Width: | Height: | Size: 142 B |
Before Width: | Height: | Size: 107 B |
Before Width: | Height: | Size: 128 B |
Before Width: | Height: | Size: 138 B |
Before Width: | Height: | Size: 130 B |
Before Width: | Height: | Size: 113 B |
Before Width: | Height: | Size: 131 B |
Before Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 100 B |
Before Width: | Height: | Size: 98 B |
Before Width: | Height: | Size: 136 B |
Before Width: | Height: | Size: 104 B |
Before Width: | Height: | Size: 98 B |
Before Width: | Height: | Size: 109 B |
Before Width: | Height: | Size: 140 B |
Before Width: | Height: | Size: 98 B |
Before Width: | Height: | Size: 124 B |
Before Width: | Height: | Size: 130 B |
Before Width: | Height: | Size: 120 B |
Before Width: | Height: | Size: 117 B |
Before Width: | Height: | Size: 130 B |
Before Width: | Height: | Size: 130 B |
Before Width: | Height: | Size: 147 B |
Before Width: | Height: | Size: 93 B |
Before Width: | Height: | Size: 101 B |
Before Width: | Height: | Size: 128 B |
Before Width: | Height: | Size: 122 B |
Before Width: | Height: | Size: 114 B |
Before Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 105 B |
Before Width: | Height: | Size: 83 B |
Before Width: | Height: | Size: 132 B |
Before Width: | Height: | Size: 128 B |
Before Width: | Height: | Size: 115 B |
Before Width: | Height: | Size: 123 B |
Before Width: | Height: | Size: 122 B |
Before Width: | Height: | Size: 102 B |
Before Width: | Height: | Size: 140 B |
Before Width: | Height: | Size: 113 B |
Before Width: | Height: | Size: 107 B |
Before Width: | Height: | Size: 112 B |
Before Width: | Height: | Size: 141 B |
Before Width: | Height: | Size: 107 B |
Before Width: | Height: | Size: 102 B |