Reorganise game into modpacks
121
mods/other/afkkick/COPYING
Normal file
|
@ -0,0 +1,121 @@
|
|||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
23
mods/other/afkkick/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
Afk Kick mod for Minetest by GunshipPenguin
|
||||
|
||||
Kicks players after they are Afk for an amount of time. By default,
|
||||
players are kicked after five minutes, although this can be configured.
|
||||
|
||||
Licence: CC0 (see COPYING file)
|
||||
|
||||
This mod can be configured by changing the variables declared in the
|
||||
start of init.lua. The following is a brief explanation of each one.
|
||||
|
||||
MAX_INACTIVE_TIME (default 300)
|
||||
|
||||
Maximum amount of time that a player may remain AFK (in seconds)
|
||||
before being kicked.
|
||||
|
||||
CHECK_INTERVAL (default 1)
|
||||
|
||||
Time between checks for inactivity (In seconds)
|
||||
|
||||
WARN_TIME (default 20)
|
||||
|
||||
Number of seconds remaining before being kicked that a player will
|
||||
start to be warned via chat message.
|
72
mods/other/afkkick/init.lua
Normal file
|
@ -0,0 +1,72 @@
|
|||
--[[
|
||||
Afk Kick mod for Minetest by GunshipPenguin
|
||||
|
||||
To the extent possible under law, the author(s)
|
||||
have dedicated all copyright and related and neighboring rights
|
||||
to this software to the public domain worldwide. This software is
|
||||
distributed without any warranty.
|
||||
]]
|
||||
|
||||
local MAX_INACTIVE_TIME = 120
|
||||
local CHECK_INTERVAL = 1
|
||||
local WARN_TIME = 20
|
||||
|
||||
local players = {}
|
||||
local checkTimer = 0
|
||||
|
||||
minetest.register_privilege("canafk")
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local playerName = player:get_player_name()
|
||||
players[playerName] = {
|
||||
lastAction = minetest.get_gametime()
|
||||
}
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local playerName = player:get_player_name()
|
||||
players[playerName] = nil
|
||||
end)
|
||||
|
||||
minetest.register_on_chat_message(function(playerName, message)
|
||||
players[playerName]["lastAction"] = minetest.get_gametime()
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
local currGameTime = minetest.get_gametime()
|
||||
|
||||
--Check for inactivity once every CHECK_INTERVAL seconds
|
||||
checkTimer = checkTimer + dtime
|
||||
|
||||
local checkNow = checkTimer >= CHECK_INTERVAL
|
||||
if checkNow then
|
||||
checkTimer = checkTimer - CHECK_INTERVAL
|
||||
end
|
||||
|
||||
--Loop through each player in players
|
||||
for playerName,_ in pairs(players) do
|
||||
local player = minetest.get_player_by_name(playerName)
|
||||
if player then
|
||||
--Check if this player is doing an action
|
||||
for _,keyPressed in pairs(player:get_player_control()) do
|
||||
if keyPressed then
|
||||
players[playerName]["lastAction"] = currGameTime
|
||||
end
|
||||
end
|
||||
|
||||
if checkNow and not minetest.check_player_privs(player, { canafk = true }) then
|
||||
--Kick player if he/she has been inactive for longer than MAX_INACTIVE_TIME seconds
|
||||
if players[playerName]["lastAction"] + MAX_INACTIVE_TIME < currGameTime then
|
||||
minetest.kick_player(playerName, "Kicked for inactivity")
|
||||
end
|
||||
|
||||
--Warn player if he/she has less than WARN_TIME seconds to move or be kicked
|
||||
if players[playerName]["lastAction"] + MAX_INACTIVE_TIME - WARN_TIME < currGameTime then
|
||||
minetest.chat_send_player(playerName, minetest.colorize("#FF8C00", "Warning, you have " ..
|
||||
tostring(players[playerName]["lastAction"] + MAX_INACTIVE_TIME - currGameTime + 1) ..
|
||||
" seconds to move or be kicked"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
24
mods/other/email/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Email
|
||||
|
||||

|
||||
|
||||
Created by rubenwardy.
|
||||
|
||||
License: WTFPL.
|
||||
|
||||
## Usage
|
||||
|
||||
Send an email:
|
||||
|
||||
* /mail username message to send
|
||||
|
||||
View inbox:
|
||||
|
||||
* /inbox
|
||||
* /inbox text
|
||||
|
||||
No formspec commands (good for IRC):
|
||||
|
||||
* /mail username message to send
|
||||
* /inbox text
|
||||
* /inbox clear
|
2
mods/other/email/depends.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
chatplus?
|
||||
sfinv?
|
1
mods/other/email/description.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Adds email style inboxes to Minetest.
|
44
mods/other/email/hud.lua
Normal file
|
@ -0,0 +1,44 @@
|
|||
local hudkit = dofile(minetest.get_modpath("email") .. "/hudkit.lua")
|
||||
email.hud = hudkit()
|
||||
function email.update_hud(player)
|
||||
local name = player:get_player_name()
|
||||
local inbox = email.get_inbox(name)
|
||||
|
||||
if inbox and #inbox > 0 then
|
||||
if email.hud:exists(player, "email:text") then
|
||||
email.hud:change(player, "email:text", "text", #inbox .. " /inbox")
|
||||
else
|
||||
email.hud:add(player, "email:icon", {
|
||||
hud_elem_type = "image",
|
||||
name = "MailIcon",
|
||||
position = {x=0.52, y=0.52},
|
||||
text="email_mail.png",
|
||||
scale = {x=1,y=1},
|
||||
alignment = {x=0.5, y=0.5},
|
||||
})
|
||||
|
||||
email.hud:add(player, "email:text", {
|
||||
hud_elem_type = "text",
|
||||
name = "MailText",
|
||||
position = {x=0.55, y=0.52},
|
||||
text= #inbox .. " /inbox",
|
||||
scale = {x=1,y=1},
|
||||
alignment = {x=0.5, y=0.5},
|
||||
})
|
||||
end
|
||||
else
|
||||
email.hud:remove(player, "email:icon")
|
||||
email.hud:remove(player, "email:text")
|
||||
end
|
||||
end
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
email.hud.players[player:get_player_name()] = nil
|
||||
end)
|
||||
function email.update_all_hud()
|
||||
local players = minetest.get_connected_players()
|
||||
for _, player in pairs(players) do
|
||||
email.update_hud(player)
|
||||
end
|
||||
minetest.after(5, email.update_all_hud)
|
||||
end
|
||||
minetest.after(5, email.update_all_hud)
|
48
mods/other/email/hudkit.lua
Normal file
|
@ -0,0 +1,48 @@
|
|||
-- HudKit, by rubenwardy
|
||||
-- License: Either WTFPL or CC0, you can choose.
|
||||
|
||||
local function hudkit()
|
||||
return {
|
||||
players = {},
|
||||
|
||||
add = function(self, player, id, def)
|
||||
local name = player:get_player_name()
|
||||
local elements = self.players[name]
|
||||
|
||||
if not elements then
|
||||
self.players[name] = {}
|
||||
elements = self.players[name]
|
||||
end
|
||||
|
||||
elements[id] = player:hud_add(def)
|
||||
end,
|
||||
|
||||
exists = function(self, player, id)
|
||||
local elements = self.players[player:get_player_name()]
|
||||
return elements and elements[id]
|
||||
end,
|
||||
|
||||
change = function(self, player, id, stat, value)
|
||||
local elements = self.players[player:get_player_name()]
|
||||
if not elements or not elements[id] then
|
||||
return false
|
||||
end
|
||||
|
||||
player:hud_change(elements[id], stat, value)
|
||||
return true
|
||||
end,
|
||||
|
||||
remove = function(self, player, id)
|
||||
local elements = self.players[player:get_player_name()]
|
||||
if not elements or not elements[id] then
|
||||
return false
|
||||
end
|
||||
|
||||
player:hud_remove(elements[id])
|
||||
elements[id] = nil
|
||||
return true
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return hudkit
|
231
mods/other/email/init.lua
Normal file
|
@ -0,0 +1,231 @@
|
|||
email = {
|
||||
log = function(msg) end
|
||||
}
|
||||
local _loading = true
|
||||
local send_as = {}
|
||||
local forward_to = {}
|
||||
local storage = minetest.get_mod_storage()
|
||||
|
||||
if minetest.global_exists("chatplus") then
|
||||
email.log = chatplus.log
|
||||
|
||||
function chatplus.on_old_inbox(inbox)
|
||||
if _loading then
|
||||
return
|
||||
end
|
||||
|
||||
email.inboxes = inbox
|
||||
end
|
||||
end
|
||||
|
||||
function email.init()
|
||||
email.inboxes = {}
|
||||
email.load()
|
||||
_loading = false
|
||||
if minetest.global_exists("chatplus") and chatplus.old_inbox then
|
||||
chatplus.on_old_inbox(chatplus.old_inbox)
|
||||
end
|
||||
end
|
||||
|
||||
function email.load()
|
||||
local file = io.open(minetest.get_worldpath() .. "/email.txt", "r")
|
||||
if file then
|
||||
local from_file = minetest.deserialize(file:read("*all"))
|
||||
file:close()
|
||||
if type(from_file) == "table" then
|
||||
if from_file.mod == "email" and tonumber(from_file.ver_min) <= 1 then
|
||||
email.inboxes = from_file.inboxes
|
||||
else
|
||||
error("[Email] Attempt to read incompatible email.txt file.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
forward_to = minetest.parse_json(storage:get_string("forward_to")) or {}
|
||||
send_as = minetest.parse_json(storage:get_string("send_as")) or {}
|
||||
end
|
||||
|
||||
function email.save()
|
||||
local file = io.open(minetest.get_worldpath() .. "/email.txt", "w")
|
||||
if file then
|
||||
file:write(minetest.serialize({
|
||||
mod = "email", version = 1, ver_min = 1,
|
||||
inboxes = email.inboxes }))
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
minetest.register_on_shutdown(email.save)
|
||||
|
||||
function email.get_inbox(name)
|
||||
return email.inboxes[name] or {}
|
||||
end
|
||||
|
||||
function email.clear_inbox(name)
|
||||
email.inboxes[name] = nil
|
||||
email.save()
|
||||
end
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local inbox = email.get_inbox(player:get_player_name())
|
||||
if #inbox > 0 then
|
||||
minetest.after(10, function()
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
minetest.colorize("#00FF00",
|
||||
"(" .. #inbox .. ") You have mail! Type /inbox to recieve"))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
function email.get_formspec(name)
|
||||
local inbox = email.get_inbox(name)
|
||||
|
||||
local function row(fs, c1, date, from, msg)
|
||||
date = minetest.formspec_escape(date)
|
||||
from = minetest.formspec_escape(from)
|
||||
msg = minetest.formspec_escape(msg)
|
||||
return fs .. ",#d0d0d0," .. table.concat({date, c1, from, msg}, ",")
|
||||
end
|
||||
|
||||
local fs = "vertlabel[0,0;Your Inbox]"
|
||||
fs = fs .. "tablecolumns[color;text;color;text;text]"
|
||||
fs = fs .. "tableoptions[highlight=#ffffff33]"
|
||||
fs = fs .. "table[0.5,0;11.25,7;inbox;"
|
||||
fs = fs .. "#ffffff,Date,,From,Message"
|
||||
if #inbox == 0 then
|
||||
fs = row(fs, "#d0d0d0", "", ":)", "Well done! Your inbox is empty!")
|
||||
else
|
||||
for i = 1, #inbox do
|
||||
local color = "#ffffff"
|
||||
if minetest.check_player_privs(inbox[i].from, {kick = true, ban = true}) then
|
||||
color = "#FFD700"
|
||||
end
|
||||
local msg = inbox[i].msg
|
||||
fs = row(fs, color, inbox[i].date, inbox[i].from, msg:sub(1, 44))
|
||||
while #msg > 45 do
|
||||
msg = msg:sub(45, #msg)
|
||||
fs = row(fs, color, "", "", msg:sub(1, 44))
|
||||
end
|
||||
end
|
||||
end
|
||||
fs = fs .. "]"
|
||||
|
||||
fs = fs .. "button[0,7.25;2,1;clear;Delete All]"
|
||||
--fs = fs .. "button[0,7.25;2,1;clear;Mark as read]"
|
||||
fs = fs .. "button_exit[10.1,7.25;2,1;close;Close]"
|
||||
fs = fs .. "label[2,7.4;Exit then type /mail username message to reply]"
|
||||
|
||||
return fs
|
||||
end
|
||||
|
||||
function email.show_inbox(name, text_mode)
|
||||
if text_mode then
|
||||
local inbox = email.get_inbox(name)
|
||||
if #inbox == 0 then
|
||||
return true, "Your inbox is empty!"
|
||||
else
|
||||
minetest.chat_send_player(name, #inbox .. " items in your inbox:")
|
||||
for i = 1, #inbox do
|
||||
local item = inbox[i]
|
||||
minetest.chat_send_player(name, i .. ") " ..item.date ..
|
||||
" <" .. item.from .. "> " .. item.msg)
|
||||
end
|
||||
return true, "End of mail (" .. #inbox .. " items)"
|
||||
end
|
||||
else
|
||||
local fs = "size[12,8]" .. email.get_formspec(name)
|
||||
minetest.show_formspec(name, "email:inbox", fs)
|
||||
|
||||
return true, "Opened inbox!"
|
||||
end
|
||||
end
|
||||
|
||||
if minetest.global_exists("sfinv") then
|
||||
sfinv.register_page("email:inbox", {
|
||||
title = "Inbox",
|
||||
get = function(self, player, context)
|
||||
local name = player:get_player_name()
|
||||
return sfinv.make_formspec(player, context, email.get_formspec(name), false, "size[12,8]")
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player,formname,fields)
|
||||
if fields.clear then
|
||||
local name = player:get_player_name()
|
||||
email.clear_inbox(name)
|
||||
minetest.chat_send_player(name, "Inbox cleared!")
|
||||
email.show_inbox(name)
|
||||
end
|
||||
|
||||
--[[if fields.mark_all_read then
|
||||
if player and player.inbox and #player.inbox > 0 then
|
||||
for i = 1, #player.inbox do
|
||||
player.inbox[i].read = true
|
||||
end
|
||||
minetest.chat_send_player(name, "Marked all as read!")
|
||||
email.show_inbox(name)
|
||||
end
|
||||
end]]--
|
||||
end)
|
||||
|
||||
function email.send_mail(aname, ato, msg)
|
||||
local name = send_as[aname] or aname
|
||||
local to = forward_to[ato] or ato
|
||||
|
||||
minetest.log("action", "Email - To: "..to..", From: "..name..", MSG: "..msg)
|
||||
email.log("Email - To: "..to..", From: "..name..", MSG: "..msg)
|
||||
if not minetest.player_exists(to) then
|
||||
return false, "Player '" .. to .. "' does not exist"
|
||||
end
|
||||
|
||||
local mail = {
|
||||
date = os.date("%Y-%m-%d %H:%M:%S"),
|
||||
from = name,
|
||||
msg = msg}
|
||||
|
||||
if email.inboxes[to] then
|
||||
table.insert(email.inboxes[to], mail)
|
||||
else
|
||||
email.inboxes[to] = { mail }
|
||||
end
|
||||
|
||||
email.save()
|
||||
|
||||
minetest.chat_send_player(to, "Mail from " .. name .. ": " .. msg)
|
||||
|
||||
return true, "Message sent to " .. ato
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("inbox", {
|
||||
params = "[/clear/text]",
|
||||
description = "Inbox: Blank to see inbox. Use 'clear' to empty inbox, 'text' for text only.",
|
||||
func = function(name, param)
|
||||
if param == "clear" then
|
||||
email.clear_inbox(name)
|
||||
|
||||
return true, "Inbox cleared"
|
||||
elseif param == "text" or param == "txt" or param == "t" or not minetest.get_player_by_name(name) then
|
||||
return email.show_inbox(name, true)
|
||||
else
|
||||
return email.show_inbox(name, false)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("mail", {
|
||||
params = "name msg",
|
||||
description = "mail: add a message to a player's inbox",
|
||||
func = function(name, param)
|
||||
local to, msg = string.match(param, "^([%a%d_-]+) (.+)")
|
||||
if to and msg then
|
||||
return email.send_mail(name, to, msg)
|
||||
else
|
||||
return false, "Usage: mail <playername> <some message>"
|
||||
end
|
||||
|
||||
end
|
||||
})
|
||||
|
||||
dofile(minetest.get_modpath("email") .. "/hud.lua")
|
||||
|
||||
email.init()
|
BIN
mods/other/email/textures/email_mail.png
Normal file
After Width: | Height: | Size: 270 B |
0
mods/other/modpack.txt
Normal file
5
mods/other/no_minimap/init.lua
Normal file
|
@ -0,0 +1,5 @@
|
|||
minetest.register_on_joinplayer(function(player)
|
||||
player:hud_set_flags({
|
||||
minimap = false,
|
||||
})
|
||||
end)
|
17
mods/other/random_messages/README.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
RandomMessages mod by arsdragonfly.
|
||||
Put your messages in (world directory)/random_messages,1 message per line.
|
||||
Messages can be all kinds of hints, mod usage, etc.
|
||||
Add/Remove messages on the fly:
|
||||
/rmessages viewmessages
|
||||
to see all the messages.
|
||||
/rmessages addmessage blah blah blah
|
||||
to add the random message blah blah blah.
|
||||
/rmessages removemessage 2
|
||||
to remove the 2nd random message in /rmessages viewmessages .
|
||||
In minetest.conf, random_messages_interval decides how often a message is sent.
|
||||
Released under CC0.
|
||||
Special thanks to:
|
||||
Michael Rasmussen (michael@jamhome.us)
|
||||
Enjoy it! ^_^
|
||||
arsdragonfly@gmail.com
|
||||
6/19/2013
|
1
mods/other/random_messages/depends.txt
Normal file
|
@ -0,0 +1 @@
|
|||
default
|
152
mods/other/random_messages/init.lua
Normal file
|
@ -0,0 +1,152 @@
|
|||
--[[
|
||||
RandomMessages mod by arsdragonfly.
|
||||
arsdragonfly@gmail.com
|
||||
6/19/2013
|
||||
--]]
|
||||
--Time between two subsequent messages.
|
||||
local MESSAGE_INTERVAL = 0
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
random_messages = {}
|
||||
random_messages.messages = {} --This table contains all messages.
|
||||
|
||||
function table.count( t ) -- luacheck: ignore
|
||||
local i = 0
|
||||
for k in pairs( t ) do i = i + 1 end
|
||||
return i
|
||||
end
|
||||
|
||||
function table.random( t ) -- luacheck: ignore
|
||||
local rk = math.random( 1, table.count( t ) )
|
||||
local i = 1
|
||||
for k, v in pairs( t ) do
|
||||
if ( i == rk ) then return v, k end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
function random_messages.initialize() --Set the interval in minetest.conf.
|
||||
minetest.settings:set("random_messages_interval",60)
|
||||
minetest.settings:write();
|
||||
return 60
|
||||
end
|
||||
|
||||
function random_messages.set_interval() --Read the interval from minetest.conf and set it if it doesn't exist
|
||||
MESSAGE_INTERVAL = tonumber(minetest.settings:get("random_messages_interval")) or random_messages.initialize()
|
||||
end
|
||||
|
||||
function random_messages.check_params(name,func,params)
|
||||
local stat,msg = func(params)
|
||||
if not stat then
|
||||
minetest.chat_send_player(name,msg)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function random_messages.read_messages()
|
||||
random_messages.messages = {
|
||||
"To talk to only your team, start your messages with /t. For example, /t Hello team!",
|
||||
"Eat apples to restore health quickly.",
|
||||
"Steel swords do more damage than guns, but you need to be up close.",
|
||||
"Gain more score by killing more than you die, or by capturing the flag.",
|
||||
"You gain more score the better the opponent you defeat.",
|
||||
"Find weapons in chests or mine and use furnaces to make stronger swords.",
|
||||
"Players are immune to attack for 15 seconds after they respawn.",
|
||||
"Access the pro section of the chest by achieving a 1k+ score and killing 3 people for every 2 deaths.",
|
||||
"Use team doors (steel) to stop the enemy walking into your base.",
|
||||
"Sprint by pressing the fast key (E) when you have stamina.",
|
||||
"Like CTF? Give feedback using /report, and consider donating at rubenwardy.com/donate",
|
||||
"Map makers needed! Visit ctf.rubenwardy.com to get involved.",
|
||||
"Using limited resources for building structures that don't strengthen your base's defences is discouraged.",
|
||||
"To report misbehaving players to moderators, please use /report <name> <action>",
|
||||
"Swearing, trolling and being rude will not be tolerated and strict action will be taken.",
|
||||
"Trapping team mates on purpose is strictly against the rules and you will be kicked immediately.",
|
||||
"Help your team claim victory by storing extra weapons in the team chest, and never taking more than you need.",
|
||||
"Excessive spawn-killing is a direct violation of the rules - appropriate punishments will be given.",
|
||||
"Use /r to check your score and rank, and /rankings to see the league tables."
|
||||
}
|
||||
end
|
||||
|
||||
function random_messages.display_message(message_number)
|
||||
local msg = random_messages.messages[message_number] or message_number
|
||||
if msg then
|
||||
minetest.chat_send_all(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function random_messages.show_message()
|
||||
random_messages.display_message(table.random(random_messages.messages))
|
||||
end
|
||||
|
||||
function random_messages.list_messages()
|
||||
local str = ""
|
||||
for k,v in pairs(random_messages.messages) do
|
||||
str = str .. k .. " | " .. v .. "\n"
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function random_messages.remove_message(k)
|
||||
table.remove(random_messages.messages,k)
|
||||
random_messages.save_messages()
|
||||
end
|
||||
|
||||
function random_messages.add_message(t)
|
||||
table.insert(random_messages.messages,table.concat(t," ",2))
|
||||
random_messages.save_messages()
|
||||
end
|
||||
|
||||
function random_messages.save_messages()
|
||||
local output = io.open(minetest.get_worldpath().."/random_messages","w")
|
||||
for k,v in pairs(random_messages.messages) do
|
||||
output:write(v .. "\n")
|
||||
end
|
||||
io.close(output)
|
||||
end
|
||||
|
||||
--When server starts:
|
||||
random_messages.set_interval()
|
||||
random_messages.read_messages()
|
||||
|
||||
local function step(dtime)
|
||||
random_messages.show_message()
|
||||
minetest.after(MESSAGE_INTERVAL, step)
|
||||
end
|
||||
minetest.after(MESSAGE_INTERVAL, step)
|
||||
|
||||
local register_chatcommand_table = {
|
||||
params = "viewmessages | removemessage <number> | addmessage <number>",
|
||||
privs = {server = true},
|
||||
description = "View and/or alter the server's random messages",
|
||||
func = function(name,param)
|
||||
local t = string.split(param, " ")
|
||||
if t[1] == "viewmessages" then
|
||||
minetest.chat_send_player(name,random_messages.list_messages())
|
||||
elseif t[1] == "removemessage" then
|
||||
if not random_messages.check_params(
|
||||
name,
|
||||
function (params)
|
||||
if not tonumber(params[2]) or
|
||||
random_messages.messages[tonumber(params[2])] == nil then
|
||||
return false,"ERROR: No such message."
|
||||
end
|
||||
return true
|
||||
end,
|
||||
t) then return end
|
||||
random_messages.remove_message(t[2])
|
||||
elseif t[1] == "addmessage" then
|
||||
if not t[2] then
|
||||
minetest.chat_send_player(name,"ERROR: No message.")
|
||||
else
|
||||
random_messages.add_message(t)
|
||||
end
|
||||
else
|
||||
minetest.chat_send_player(name,"ERROR: Invalid command.")
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
minetest.register_chatcommand("random_messages", register_chatcommand_table)
|
||||
minetest.register_chatcommand("rmessages", register_chatcommand_table)
|
12
mods/other/report/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Report
|
||||
|
||||
Allows players to report things to admins and online moderators.
|
||||
|
||||
Suppose `player`, `griefer` and `moderator` are online players.
|
||||
`player` runs this command: `/report griefer is griefing`
|
||||
`moderator` sees: `-!- player reported: griefer is griefing`
|
||||
The Admin (named in "name") is mailed via chatplus: `<player1> Report: player2 is griefing (mods online: player3)`
|
||||
|
||||
License: WTFPL
|
||||
Depends: email.
|
||||
|
2
mods/other/report/depends.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
email
|
||||
irc?
|
1
mods/other/report/description.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Allows players to report misconduct or bugs using /report.
|
90
mods/other/report/init.lua
Normal file
|
@ -0,0 +1,90 @@
|
|||
local storage = minetest.get_mod_storage()
|
||||
|
||||
local function get_irc_mods()
|
||||
return storage:get_string("irc_mods"):split(",")
|
||||
end
|
||||
|
||||
local function add_irc_mod(name)
|
||||
local mods = get_irc_mods()
|
||||
if table.indexof(mods, name) > 0 then
|
||||
return false
|
||||
end
|
||||
mods[#mods + 1] = name
|
||||
storage:set_string("irc_mods", table.concat(mods, ","))
|
||||
return true
|
||||
end
|
||||
|
||||
local function remove_irc_mod(name)
|
||||
local mods = get_irc_mods()
|
||||
local idx = table.indexof(mods, name)
|
||||
if idx > 0 then
|
||||
table.remove(mods, idx)
|
||||
storage:set_string("irc_mods", table.concat(mods, ","))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("report_sub", {
|
||||
privs = { kick = true },
|
||||
func = function(name, param)
|
||||
if param:lower():trim() == "remove" then
|
||||
if remove_irc_mod(name) then
|
||||
return true, "Successfully removed!"
|
||||
else
|
||||
return false, "Unable to remove, are you even subscribed?"
|
||||
end
|
||||
else
|
||||
if add_irc_mod(name) then
|
||||
return true, "Successfully added!"
|
||||
else
|
||||
return false, "Unable to add, are you already subscribed?"
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("report", {
|
||||
func = function(name, param)
|
||||
param = param:trim()
|
||||
if param == "" then
|
||||
return false, "Please add a message to your report. " ..
|
||||
"If it's about (a) particular player(s), please also include their name(s)."
|
||||
end
|
||||
local _, count = string.gsub(param, " ", "")
|
||||
if count == 0 then
|
||||
minetest.chat_send_player(name, "If you're reporting a player, " ..
|
||||
"you should also include a reason why. (Eg: swearing, sabotage)")
|
||||
end
|
||||
|
||||
-- Send to online moderators / admins
|
||||
-- Get comma separated list of online moderators and admins
|
||||
local mods = {}
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local toname = player:get_player_name()
|
||||
if minetest.check_player_privs(toname, {kick = true, ban = true}) then
|
||||
table.insert(mods, toname)
|
||||
minetest.chat_send_player(toname, minetest.colorize("#FFFF00",
|
||||
"-!- " .. name .. " reported: " .. param))
|
||||
end
|
||||
end
|
||||
|
||||
-- Build message for offline listeners
|
||||
local msg
|
||||
if #mods == 0 then
|
||||
msg = "Report from " ..name .. ": " .. param .. " (no mods online)"
|
||||
else
|
||||
msg = "Report from " ..name .. ": " .. param .. " (mods online: " ..
|
||||
table.concat(mods, ", ") .. ")"
|
||||
end
|
||||
|
||||
-- Send to IRC moderators
|
||||
for _, toname in pairs(get_irc_mods()) do
|
||||
if not minetest.get_player_by_name(toname) then
|
||||
minetest.chat_send_player(toname, msg)
|
||||
end
|
||||
end
|
||||
|
||||
return true, "Reported. We'll get back to you."
|
||||
end
|
||||
})
|
7
mods/other/sprint/LICENSE.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
Copyright 2018 by rubenwardy and GunshipPenguin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
mods/other/sprint/README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Sprint Mod For minetest
|
||||
|
||||
Allows players to sprint by holding E, by default increasing their travel speed
|
||||
by 80% and their jump speed by 10%. Also adds a stamina bar that goes down when
|
||||
the player sprints and goes up they're not sprinting.
|
||||
|
||||
This mod is compatible with the HUD bars [hudbars] mod, but does not depend on
|
||||
it. In this case, a green HUD bar will be displayed, also showing a number.
|
||||
If this mod is not present, a standard statbar with 0-20 “half-arrows” is shown,
|
||||
which is a bit more coarse than the HUD bar version.
|
||||
|
||||
This mod is by rubenwardy, and is based on a mod by GunshipPenguin but heavily
|
||||
rewritten to be more performant.
|
||||
|
||||
License: MIT (see license.txt)
|
||||
|
||||
## Settings
|
||||
|
||||
1.0 represents normal speed so 1.5 would mean that a sprinting player would
|
||||
travel 50% faster than a walking player and 2.4 would mean that a sprinting
|
||||
player would travel 140% faster than a walking player.
|
||||
|
||||
* `sprint_speed`- 1 is normal walking speed, defaults to 1.8.
|
||||
* `sprint_jump` - 1 is normal jump speed, defaults to 1.1.
|
||||
* `sprint_stamina` - How long the sprint lasts in seconds, defaults to 20.
|
||||
* `sprint_heal_rate` - Multiply this by the stamina to get how long it takes to recharge, defaults to 0.5.
|
||||
* `sprint_min` - The minimum value at which you can start sprinting, defaults to 0.5.
|
||||
|
||||
## Recharging when the sprint key is down
|
||||
|
||||
Follow the instructions in comments marked by `##number##`.
|
1
mods/other/sprint/depends.txt
Normal file
|
@ -0,0 +1 @@
|
|||
hudbars?
|
123
mods/other/sprint/init.lua
Normal file
|
@ -0,0 +1,123 @@
|
|||
-- Sprint mod by rubenwardy. License: MIT.
|
||||
-- Heavily modified from a mod by GunshipPenguin
|
||||
|
||||
-- Config, see README.md
|
||||
local MOD_WALK = tonumber(minetest.settings:get("sprint_speed") or 1.8)
|
||||
local MOD_JUMP = tonumber(minetest.settings:get("sprint_jump") or 1.1)
|
||||
local STAMINA_MAX = tonumber(minetest.settings:get("sprint_stamina") or 20)
|
||||
local HEAL_RATE = tonumber(minetest.settings:get("sprint_heal_rate") or 0.5)
|
||||
local MIN_SPRINT = tonumber(minetest.settings:get("sprint_min") or 0.5)
|
||||
local SPRINT_MODIFIERS = {
|
||||
[true] = { speed = MOD_WALK, jump = MOD_JUMP },
|
||||
[false] = { speed = 1.0, jump = 1.0 },
|
||||
}
|
||||
|
||||
if minetest.get_modpath("hudbars") ~= nil then
|
||||
hb.register_hudbar("sprint", 0xFFFFFF, "Stamina",
|
||||
{ bar = "sprint_stamina_bar.png", icon = "sprint_stamina_icon.png" },
|
||||
STAMINA_MAX, STAMINA_MAX,
|
||||
false, "%s: %.1f/%.1f")
|
||||
|
||||
SPRINT_HUDBARS_USED = true
|
||||
else
|
||||
SPRINT_HUDBARS_USED = false
|
||||
end
|
||||
|
||||
local players = {}
|
||||
|
||||
local function setSprinting(player, info, sprinting)
|
||||
if info.sprinting ~= sprinting then
|
||||
player:set_physics_override(SPRINT_MODIFIERS[sprinting])
|
||||
end
|
||||
info.sprinting = sprinting
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for name, info in pairs(players) do
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then
|
||||
players[name] = nil
|
||||
else
|
||||
-- ##?## You can enable recharging when the E key is pressed by
|
||||
-- following these instructions.
|
||||
|
||||
--Check if the player should be sprinting
|
||||
local controls = player:get_player_control()
|
||||
local sprintRequested = controls.aux1 and controls.up
|
||||
-- ##1## replace info.sprintRequested with info.sprinting
|
||||
if sprintRequested ~= info.sprintRequested then
|
||||
if sprintRequested and info.stamina > MIN_SPRINT then
|
||||
setSprinting(player, info, true)
|
||||
end
|
||||
|
||||
if not sprintRequested then
|
||||
setSprinting(player, info, false)
|
||||
end
|
||||
end
|
||||
info.sprintRequested = sprintRequested
|
||||
|
||||
local staminaChanged = false
|
||||
if info.sprinting then
|
||||
info.stamina = info.stamina - dtime
|
||||
staminaChanged = true
|
||||
if info.stamina <= 0 then
|
||||
info.stamina = 0
|
||||
setSprinting(player, info, false)
|
||||
end
|
||||
|
||||
-- ##2## remove `not info.sprintRequested and`
|
||||
elseif not info.sprintRequested and info.stamina < STAMINA_MAX then
|
||||
info.stamina = info.stamina + dtime * HEAL_RATE
|
||||
staminaChanged = true
|
||||
if info.stamina > STAMINA_MAX then
|
||||
info.stamina = STAMINA_MAX
|
||||
end
|
||||
end
|
||||
|
||||
if staminaChanged then
|
||||
if SPRINT_HUDBARS_USED then
|
||||
hb.change_hudbar(player, "sprint", info.stamina)
|
||||
else
|
||||
local numBars = math.floor(20 * info.stamina / STAMINA_MAX)
|
||||
if info.lastHudSendValue ~= numBars then
|
||||
info.lastHudSendValue = numBars
|
||||
player:hud_change(info.hud, "number", numBars)
|
||||
print("Sending hud value")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local info = {
|
||||
sprinting = false, -- Is the player actually sprinting?
|
||||
stamina = STAMINA_MAX, -- integer, the stamina we have left
|
||||
sprintRequested = false, -- is the sprint key down / etc?
|
||||
}
|
||||
|
||||
if SPRINT_HUDBARS_USED then
|
||||
hb.init_hudbar(player, "sprint")
|
||||
else
|
||||
info.hud = player:hud_add({
|
||||
hud_elem_type = "statbar",
|
||||
position = {x=0.5, y=1},
|
||||
size = {x=24, y=24},
|
||||
text = "sprint_stamina_icon.png",
|
||||
number = 20,
|
||||
alignment = {x=0, y=1},
|
||||
offset = {x=-263, y=-110},
|
||||
})
|
||||
end
|
||||
|
||||
players[player:get_player_name()] = info
|
||||
end)
|
||||
|
||||
minetest.register_on_respawnplayer(function(player)
|
||||
players[player:get_player_name()].stamina = STAMINA_MAX
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
players[player:get_player_name()] = nil
|
||||
end)
|
BIN
mods/other/sprint/textures/sprint_particle.png
Normal file
After Width: | Height: | Size: 791 B |
BIN
mods/other/sprint/textures/sprint_stamina_bar.png
Normal file
After Width: | Height: | Size: 80 B |
BIN
mods/other/sprint/textures/sprint_stamina_icon.png
Normal file
After Width: | Height: | Size: 148 B |
83
mods/other/treasurer/GROUPS_AND_PRECIOUSNESS
Normal file
|
@ -0,0 +1,83 @@
|
|||
|
||||
If you wish to register treasures to Treasurer, it is recommended to assign the treasurer to a Treasurer group.
|
||||
|
||||
It is not possible to assign a treasure to multiple groups. If you think a treasure fits into two groups, create
|
||||
two seperate treasure definitions instead. But try to keep it as an exception.
|
||||
|
||||
Think of treasurer groups as categories for treasurers to put into.
|
||||
|
||||
This file contains some guidelines for Treasurer groups and there are even guidelines for preciousness levels.
|
||||
Keep in mind these are only guidelines.
|
||||
|
||||
Treasurer suggests to use the following standard Treasurer groups:
|
||||
|
||||
crafting_component:
|
||||
Generic group for components in crafting recipes. If it is primarily a component for food, use raw_food instead.
|
||||
Preciousness is based roughly on the preciousness of items it can create.
|
||||
|
||||
fuel:
|
||||
Fuel for furnaces.
|
||||
Preciousness is based on burning time.
|
||||
|
||||
food:
|
||||
Can be eaten and restores health.
|
||||
Preciousness should equal the number of hearts restored (restored HP divided by 2), but not higher than 7.
|
||||
|
||||
raw_food:
|
||||
Food which is not fully processed and is not (quite) ready to be eaten.
|
||||
|
||||
melee_weapon:
|
||||
Primarily used to damage close foes, i.e. a sword.
|
||||
Preciousness is based on attack speed and damage.
|
||||
|
||||
ranged_weapon:
|
||||
Primarily used to damage far away foes,
|
||||
Preciousness is based on attack speed, damage and range.
|
||||
|
||||
tool:
|
||||
A tool for other uses.
|
||||
Preciousness is hard to determine; at least is should base on the number of uses.
|
||||
|
||||
|
||||
minetool:
|
||||
A tool to destroy blocks. Includes pickaxes, axes, shovels, …
|
||||
Preciousness is based on power, number of uses and speed.
|
||||
|
||||
deco:
|
||||
Primarily just a decorational thing to place.
|
||||
Preciousness is based on beauty, highly subjective.
|
||||
|
||||
light:
|
||||
Is a light source.
|
||||
Preciousness is based on the brightness. For the maximum brightness (before sun brightness), preciousness should be 3.
|
||||
|
||||
building_block:
|
||||
A block for buildings. Includes stairs, slabs, fences and similar things.
|
||||
Excludes all natural blocks.
|
||||
Preciousness should be roughly based on the “cost” to craft the block.
|
||||
|
||||
seed:
|
||||
Seeds and saplings.
|
||||
Preciousness is based on the percieved “usefulness” of what can grow from the seed.
|
||||
|
||||
transport_vehicle
|
||||
A vehicle to transport players and stuff, i.e. a cart or a boat.
|
||||
Preciousness is hard to determine, maybe speed?
|
||||
|
||||
transport_structure
|
||||
A fixed structure which is neccessary for a transport vehicle to operate, i.e. rails.
|
||||
Preciousness is hard to dertermine …
|
||||
|
||||
ladder
|
||||
A ladder.
|
||||
|
||||
default:
|
||||
This is the group your treasure get assigned to if you don’t specify a group.
|
||||
|
||||
|
||||
Do you miss a group? Feel free to invent your own!
|
||||
|
||||
|
||||
Note that Treasurer groups differ from the groups as defined by the Minetest API. These groups are handled differently.
|
||||
|
||||
|
246
mods/other/treasurer/README.md
Normal file
|
@ -0,0 +1,246 @@
|
|||
= Treasurer’s README file for Treasurer version 0.2.0 =
|
||||
== Overview ==
|
||||
* Name: Treasurer
|
||||
* Technical name: `treasurer`
|
||||
* Purpose: To provide an interface for mods which want to spawn ItemStacks randomly and an interface for mods which create new items.
|
||||
* Version: 0.2.0
|
||||
* Dependencies: none
|
||||
* License: WTFPL
|
||||
|
||||
== Introduction ==
|
||||
Problem:
|
||||
There are a bunch of mods which have cool items but they won’t appear in the world by
|
||||
themselves.
|
||||
There are some mods which randomly distribute treasures into the world. Sadly, these only
|
||||
distribute the items they know—which are just the items of the mod “default” most of the
|
||||
time. The items of the other mods are completely missed.
|
||||
|
||||
The reason for this is that the distributing mods can’t know what sort of items are available
|
||||
unless they explicitly depend on the mods that defines these. Viewed the other way round,
|
||||
the item-defining mods that also distribute these items into the world are limited in the
|
||||
sense that they only know one means of distributing items.
|
||||
|
||||
There is a gap between defining items and distributing them. Every time a mod does both,
|
||||
flexibility is limited and expansion becomes difficult.
|
||||
|
||||
To bridge this gap, Treasurer has been written. Treasurer makes it possible a) for mods to define
|
||||
treasures without bothering _how_ these are distributed into the world and b) for mods to distribute
|
||||
treasures around the world without knowledge about _what_ treasures exactly are distributed.
|
||||
|
||||
== Technical side of Treasurer ==
|
||||
=== technical overview ===
|
||||
To get a working Treasurer architecture and actually get some treasures into the world,
|
||||
you need:
|
||||
* Treasurer
|
||||
* at least one treasure registration mod
|
||||
* at least one treasure spawning mod
|
||||
|
||||
=== treasurer registration mod ===
|
||||
Firstly, there are the treasure registration mods (TRMs). The task of TRMs is to tell
|
||||
Treasurer which items does it have to offer, which relative appearance probabilities these
|
||||
treasures should have, how “precious” the treasure is considered (on a scale from 0 to 10)
|
||||
, optionally how big the stack of items should be and optionally how much worn out it is.
|
||||
TRMs must depend on at least two mods: On treasurer and on the mod or mods
|
||||
where the items are defined. Technically, a TRM consists of nothing more than a long
|
||||
list of “registration” function calls. While this seems trivial, the task of balancing
|
||||
out probabilties and balancing out preciousness levels of treasures is not trivial
|
||||
and it may take a long time to get right.
|
||||
|
||||
It is strongly recommended that a TRM really does nothing
|
||||
more than registering treasures (and not defining items, for example). If you want
|
||||
to make your mod compatible to Treasurer, don’t change your mod, write a TRM for
|
||||
it instead.
|
||||
|
||||
There is an example TRM, called “`trm_default_example`”. It registers some items
|
||||
of the default as treasures. Unsurprisingly, it depends on `treasurer` and `default`.
|
||||
|
||||
=== treasurer spawning mods ===
|
||||
Secondly, there are the treasure spawning mods (TSMs). The task of a TSM is to somehow
|
||||
distribute the available treasures into the world. This is also called “treasure
|
||||
spawning”. How exactly the TSM spawns the treasures is completely up the TSM. But a
|
||||
TSM has to request Treasurer to get some random treasures to distribute. A TSM may
|
||||
optionally request to filter out treasures outside certain preciousness levels
|
||||
and groups, so the result is a bit controllable and not completely random.
|
||||
Treasurer can not guarantee to return the requestet amount of treasures, it may
|
||||
return an empty table, for two reasons:
|
||||
|
||||
* There is no TRM activated. There must be at least one to work.
|
||||
* The filters filtered out everything, leaving Treasurer with an empty treasure pool
|
||||
to choose from. This problem can be fixed by installing more TRMs or by balancing the
|
||||
existing TRMs to cover as many preciousness levels as possible. It also may be that
|
||||
the range specified by the TSM was too small. It is recommended to keep the
|
||||
requested range at least of a size of 1. Treasurer does, however, guarantee that
|
||||
the returned treasures are always in the requested bounds.
|
||||
|
||||
A TSM has to at least depend on Treasurer.
|
||||
Unlike for TRMs, it may not be a problem to also do some other stuff than just
|
||||
spawning treasures if it seems feasible. You may choose to make your TSM fully
|
||||
dependant on Treasure, then it won’t work without Treasurer. You may also choose
|
||||
to only add an optional dependency on Treasurer. For this to work, your mod must
|
||||
now select its own treasures, which of course will only come from a rather limited
|
||||
pool.
|
||||
|
||||
To check if the Treasurer mod is installed, you can use something like this in
|
||||
your code:
|
||||
|
||||
```
|
||||
if(minetest.get_modpath("treasurer")~=nil) then
|
||||
-- Treasurer is installed.
|
||||
-- You can call Treasurer’s functions here.
|
||||
else
|
||||
-- Treasurer is not installed.
|
||||
-- You may write your replacement code here.
|
||||
-- You can not call Treasurer’s funcitons here.
|
||||
end
|
||||
```
|
||||
|
||||
There are two example TSMs. The first one is a very basic one and called “`tsm_gift_example`”.
|
||||
It gives a “welcome gift” (1 random treasure) to players who just joined the server
|
||||
or who respawn. The preciousness and group filters are not used. It does only depend on
|
||||
Treasurer. The second one is called “`tsm_chests_example`” and pretty advanced for an example.
|
||||
It places chests of the mod “default” between 20 and 200 node lenghts below the water
|
||||
surface and puts 1-6 random treasures into these. The lower the chest, the higher
|
||||
the preciousness. It depends on treasurer and default (for the chests, of course).
|
||||
|
||||
=== Recap ===
|
||||
TRMs define treasures, TSMs spawn them. Treasurer manages the pool of available treasures.
|
||||
TRMs and TSMs do not have to know anything from each other.
|
||||
TRMs and TSMs do not neccessarily have to change any line of code of other mods to function.
|
||||
Treasurer depends on nothing.
|
||||
|
||||
Important: It should always only be neccessary for TRMs and TSMs to depend on Treasurer.
|
||||
All other mods do NOT HAVE TO and SHOULD NOT depend on Treasurer.
|
||||
|
||||
|
||||
|
||||
=== Treasure attributes ===
|
||||
This section explains the various attributes a treasure can have.
|
||||
|
||||
==== Rarity ====
|
||||
Rarity in Treasurer works in a pretty primitive way: The relative rarities of all
|
||||
treasures from the treasure pool are simply all added up. The probabilitiy of one
|
||||
certain treasure is then simply the rarity value divided by the sum.
|
||||
|
||||
==== Preciousness ====
|
||||
How “precious” an item is, is highly subjective and also not always easy to categorize.
|
||||
Preciousness in Treasurer’s terms should be therefore viewed as “utility” or as
|
||||
“reward level” or “strength” or even “beauty” or whatever positive attributes you can
|
||||
think of for items. See the text file `GROUPS_AND_PRECIOUSNESS` for a rough
|
||||
guideline.
|
||||
So, if you create a TRM and your treasure is something you want the player work
|
||||
hard for, assign it a high preciousness. Everyday items that are already easy to
|
||||
obtain in normal gameplay certainly deserve a lower precious than items that are
|
||||
expensive to craft.
|
||||
If your treasure consists of a powerful
|
||||
item, assign it a high preciousness. When in doubt, try to value gameplay over
|
||||
personal taste. Remember that TSMs can (and will!) filter out treasures based
|
||||
on their preciousness.
|
||||
For TSM authors, consider preciousness this way: If the trouble the player has
|
||||
to get through to in order to obtain the treasure is high, better filter
|
||||
out unprecious treasures. If your TSM distributes many treasures all over the world and these
|
||||
are easy to obtain, filter out precious treasures.
|
||||
|
||||
TSMs also can just completely ignore preciousness, then the given treasures base
|
||||
on sheer luck.
|
||||
|
||||
==== Treasurer groups ====
|
||||
Every treasure can be assigned to a group. These groups are specific to Treasurer only.
|
||||
The idea is that treasures which share a common property are member of the same group.
|
||||
All groups have a name by which they are identified.
|
||||
For example, if there are apples, plums, pears and oranges and those items can be
|
||||
eaten for health, all those treasures would be members of the group “food”.
|
||||
|
||||
The group system can be used to further narrow down the treasure pool from which you
|
||||
want Treasurer to return treasures. This makes it more interesting than just using
|
||||
an one-dimensional preciousness scale.
|
||||
|
||||
Using the groups system is entirely optional. If your TRM does not specify any group,
|
||||
your treasure will be assigned to the group “default”. It is not possible for a treasure
|
||||
to not belong to any group. If your TSM does not specify a group parameter, Treasurer
|
||||
will use all groups.
|
||||
While not using groups as a TSM may be perfectly okay, not using groups as a TRM is
|
||||
not recommended, because TSM which filter by groups may “overlook” your treasure,
|
||||
even if it would actually fit, simply because you didn’t assign it to a specific group.
|
||||
|
||||
Note that Treasurer groups are completely distinct from Minetest’s group system.
|
||||
|
||||
You can basically invent your own groups on the fly, but it is strongly recommended that you
|
||||
use the groups suggested in the text file `GROUPS_AND_PRECIOUSNESS` whenever possible, for
|
||||
maximum portability of your TSM. The text file also has a rough guideline for finding
|
||||
appropriate values for the preciousness.
|
||||
|
||||
|
||||
==== Recap ====
|
||||
Rarity determines the chance of a treasure, whereas preciousness determines
|
||||
the difficulty to obtain it. Group
|
||||
|
||||
== Overview of examples ==
|
||||
- `trm_default_example` - registers items of default mod
|
||||
- `tsm_chests_example` - spawns chests (from the default mod)
|
||||
- `tsm_gift_example` - gives one treasure as a “welcome gift” to joined or respawned players
|
||||
|
||||
== Treasurer API documentation ==
|
||||
=== API documentation for treasure registration mods ===
|
||||
The API consists of one function, which is called “`treasurer.register_treasure`”.
|
||||
|
||||
==== `treasurer.register_treasure` ====
|
||||
Registers a new treasure (this means the treasure will be ready to be spawned by treasure spawning mods).
|
||||
|
||||
This function does some basic parameter checking to catch the most obvious
|
||||
mistakes. If invalid parameters have been passed, the input is rejected and
|
||||
the function returns false. However, it does not cover every possible
|
||||
mistake, so some invalid treasures may slip through.
|
||||
|
||||
Rarity does not imply preciousness. A rare treasure may not neccessarily a
|
||||
very precious one. A treasure chest with scorched stuff inside may be very
|
||||
rare, but it’s certainly also very unprecious.
|
||||
|
||||
===== Parameters =====
|
||||
* `name`: name of resulting `ItemStack`, e.g. “`mymod:item`”
|
||||
* `rarity`: rarity of treasure on a scale from 0 to 1 (inclusive). lower = rarer
|
||||
* `preciousness` : subjective preciousness on a scale from 0 to 10 (inclusive). higher = more precious.
|
||||
* `count`: optional value which specifies the multiplicity of the item. Default is 1. See `count` syntax help in this file.
|
||||
* `wear`: optional value which specifies the wear of the item. Default is 0, which disables the wear. See `wear` syntax help in this file.
|
||||
* `treasurer_groups`: an optional table of group names to assign this treasure to. If omitted, the treasure is added to the default group.
|
||||
|
||||
===== Return value =====
|
||||
`true` on success, `false` on failure.
|
||||
|
||||
=== data formats ===
|
||||
format of count type:
|
||||
==== `count` ====
|
||||
A `count` can be a number or a table
|
||||
|
||||
* `number`: it’s always so many times
|
||||
* `{min, max}`: it’s pseudorandomly between `min` and `max` times, `math.random` will be used to chose the value
|
||||
* `{min, max, prob_func}`: it’s between `min` and `max` times, and the value is given by `prob_func` (see below)
|
||||
|
||||
==== `wear` ====
|
||||
Completely analogous to `count`.
|
||||
|
||||
==== Format of `prob_func` function ====
|
||||
There are no parameters.
|
||||
|
||||
It returns a random or pseudorandom number between 0 (inclusive) and 1 (exclusive).
|
||||
|
||||
`prob_func` is entirely optional, if it’s not used, treasurer will
|
||||
default to using `math.random`. You can use `prob_func` to define your own
|
||||
“randomness” function, in case you don’t wish your values to be evenly
|
||||
distributed.
|
||||
|
||||
=== API documentation for treasure spawning mods ===
|
||||
The API consists of one function, called “`treasurer.select_random_treasures`”.
|
||||
|
||||
==== `treasurer.select_random_treasures` ====
|
||||
Request some treasures from treasurer.
|
||||
|
||||
===== Parameters =====
|
||||
* `count`: (optional) amount of treasures. If this value is `nil`, Treasurer assumes a default of 1.
|
||||
* `minimal_preciousness`: (optional) don’t consider treasures with a lower preciousness. If `nil`, there’s no lower bound.
|
||||
* `maximum_preciousness`: (optional) don’t consider treasures with a higher preciousness. If `nil`, there’s no upper bound.
|
||||
* `treasurer_group`: (optional): Only consider treasures which are members of at least one of the members of the provided Treasurer group table. `nil` = consider all groups
|
||||
|
||||
|
||||
===== Return value =====
|
||||
A table of `ItemStacks` (the requested treasures). It may be empty.
|
||||
|
48
mods/other/treasurer/Treasurer_ANNOUNCEMENT
Normal file
|
@ -0,0 +1,48 @@
|
|||
== Treasurer [treasurer] ==
|
||||
|
||||
With Treasurer I want to introduce a new way to define and distribute random treasures into the world. Treasurer itself does neither define any items nor does it attempt to distribute them.
|
||||
|
||||
Instead Treasurer provides two interfaces.
|
||||
|
||||
To actually get a game with treasures using Treasurer running, you need at least the following:
|
||||
[list]
|
||||
[*]Treasurer
|
||||
[*]at least one treasure registration mod
|
||||
[*]at least one treasure spawning mod
|
||||
[/list]
|
||||
|
||||
A treasure is basically just a fancy ItemStack. It consists of:
|
||||
- name of item
|
||||
- relative rarity
|
||||
- a preciousness
|
||||
- the amount of item (can be randomized)
|
||||
- a wear (can be randomized)
|
||||
|
||||
|
||||
A TRM does nothing more than telling Treasurer a bunch of treasures it has to offer. Note, however, that a TRM should really do nothing more than just that. The actual mods where the items originate from does most likely NOT have to be changed in order to register its items as treasures. TRMs are simple,
|
||||
|
||||
A TSM distributes (“spawns”) treasures, where it has to ask Treasurer for some treasures. Treasurer then chooses some treasures out of the pool of available treasures and returns a list of ItemStacks. The TSM also may ask Treasurer to filter out treasures which have an either too high or too low preciousness (“preciousness” is defined by the TRMs). Be warned: Treasurer may return no treasures at all, either because no TRM is activated or no treasure that matches the filter exists.
|
||||
“Spawning” here means to put the treasures into the world somehow. How a TSM does the task of spawning is up to the TSM. Treasurer
|
||||
|
||||
Recap: The TSMs will spawn treasures into the world out of a pool of treasures which is offered by TRMs. Treasurer manages the pool.
|
||||
|
||||
Examples for TRMs are:
|
||||
- registering bronze, gold, iron ingots and possibly other items of default (see trm_default_example)
|
||||
- registering bread, wheat, seeds, etc. of farming
|
||||
- registering super cool item X from awesome mod Y, where X could normally only obtained by “/give” or with creative mode.
|
||||
- registering the … bla bla bla, you get the idea
|
||||
|
||||
Examples for TSMs are:
|
||||
- let a mob drop a not mob-specific, random treasure on death
|
||||
- give a random treasure as a “welcome gift” to joining and respawning players (see tsm_gift_example)
|
||||
- placing chests around the world and spawning treasures into them (see tsm_chests_example)
|
||||
|
||||
Some of the above example have been implemented into already working mods, which are distributed with Treasurer
|
||||
as seperate mods.
|
||||
|
||||
|
||||
Dependencies of Treasurer: None!
|
||||
Minimal dependencies of all TRMs: treasurer and all mods that define the registered items
|
||||
Minimal dependency of all TSMs: treasurer
|
||||
|
||||
License: WTFPL
|
0
mods/other/treasurer/depends.txt
Normal file
1
mods/other/treasurer/descripton.txt
Normal file
|
@ -0,0 +1 @@
|
|||
A framework which manages treasures which helps other mods to register and distribute treasures around the world.
|
363
mods/other/treasurer/init.lua
Normal file
|
@ -0,0 +1,363 @@
|
|||
--[==[
|
||||
Treasurer
|
||||
- A mod for Minetest
|
||||
version 0.2.0
|
||||
]==]
|
||||
|
||||
--[=[
|
||||
TABLE OF CONTENTS
|
||||
part 1: Initialization
|
||||
part 2: Treasure API
|
||||
part 3: Treasure spawning mod handling
|
||||
part 4: Internal functions
|
||||
]=]
|
||||
|
||||
--[=[
|
||||
part 1: Initialization
|
||||
]=]
|
||||
|
||||
-- This creates the main table; all functions of this mod are stored in this table
|
||||
treasurer = {}
|
||||
|
||||
-- Table which stores all the treasures
|
||||
treasurer.treasures = {}
|
||||
|
||||
-- This table stores the treasures again, but this time sorted by groups
|
||||
treasurer.groups = {}
|
||||
|
||||
-- Groups defined by the Treasurer API
|
||||
treasurer.groups.treasurer = {}
|
||||
treasurer.groups.treasurer.default = {}
|
||||
|
||||
-- Groups defined by the Minetest API
|
||||
treasurer.groups.minetest = {}
|
||||
|
||||
--[[
|
||||
format of treasure table:
|
||||
treasure = {
|
||||
name, -- treasure name, e.g. mymod:item
|
||||
rarity, -- relative rarity on a scale from 0 to 1 (inclusive).
|
||||
-- a rare treasure must not neccessarily be a precious treasure
|
||||
count, -- count (see below)
|
||||
preciousness, -- preciousness or “worth” of the treasure.
|
||||
-- ranges from 0 (“scorched stuff”) to 10 (“diamond block”)
|
||||
wear, -- wear (see below)
|
||||
metadata, -- unused at the moment
|
||||
|
||||
}
|
||||
treasures can be nodes or items
|
||||
|
||||
format of count type:
|
||||
count = number -- it’s always number times
|
||||
count = {min, max} -- it’s pseudorandomly between min and max times, math.random() will be used to chose the value
|
||||
count = {min, max, prob_func} -- it’s between min and max times, and the value is given by prob_func (which is not neccessarily random [in the strictly mathematical sense])
|
||||
|
||||
format of wear type:
|
||||
completely analogous to count type
|
||||
|
||||
format of prob_func function:
|
||||
prob_func = function()
|
||||
--> returns a random or pseudorandom number between 0 (inclusive) and 1 (exclusive)
|
||||
prob_func is entirely optional, if it’s not used, treasurer will default to math.random.
|
||||
You can use prob_func to define your own random function, in case you don’t like an even
|
||||
distribution
|
||||
|
||||
format of treasurer_groups:
|
||||
This is just a table of strings, each string stands for a group name.
|
||||
]]
|
||||
|
||||
|
||||
--[=[
|
||||
part 2: Treasurer API
|
||||
]=]
|
||||
|
||||
--[[
|
||||
treasurer.register_treasure - registers a new treasure
|
||||
(this means the treasure will be ready to be spawned by treasure spawning mods.
|
||||
|
||||
name: name of resulting ItemStack, e.g. “mymod:item”
|
||||
rarity: rarity of treasure on a scale from 0 to 1 (inclusive). lower = rarer
|
||||
preciousness: preciousness of treasure on a scale from 0 (“scorched stuff”) to 10 (“diamond block”).
|
||||
count: optional value which specifies the multiplicity of the item. Default is 1. See count syntax help in this file.
|
||||
wear: optional value which specifies the wear of the item. Default is 0, which disables the wear. See wear syntax help in this file.
|
||||
treasurer_groups: (optional) a table of group names to assign this treasure to. If omitted, the treasure is added to the default group.
|
||||
This function does some basic parameter checking to catch the most obvious mistakes. If invalid parameters have been passed, the input is rejected and the function returns false. However, it does not cover every possible mistake, so some invalid treasures may slip through.
|
||||
|
||||
returns: true on success, false on failure
|
||||
]]
|
||||
function treasurer.register_treasure(name, rarity, preciousness, count, wear, treasurer_groups )
|
||||
--[[ We don’t trust our input, so we first check if the parameters
|
||||
have the correct types and refuse to add the treasure if a
|
||||
parameter is malformed.
|
||||
What follows is a bunch of parameter checks.
|
||||
]]
|
||||
|
||||
-- check wheather name is a string
|
||||
if type(name) ~= "string" then
|
||||
minetest.log("error","[treasure] I rejected a treasure because the name was of type \""..type(name).."\" instead of \"string\".")
|
||||
return false
|
||||
end
|
||||
-- first check if rarity is even a number
|
||||
if type(rarity) == "number" then
|
||||
-- then check wheather the rarity lies in the allowed range
|
||||
if rarity < 0 or rarity > 1 then
|
||||
minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it’s rarity value is out of bounds. (it was "..tostring(rarity)..".)")
|
||||
return false
|
||||
end
|
||||
else
|
||||
minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of rarity. Given type was \""..type(rarity).."\".")
|
||||
return false
|
||||
end
|
||||
|
||||
-- check if preciousness is even a number
|
||||
if type(preciousness) == "number" then
|
||||
-- then check wheather the preciousness lies in the allowed range
|
||||
if preciousness < 0 or preciousness > 10 then
|
||||
minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it’s preciousness value is out of bounds. (it was "..tostring(preciousness)..".)")
|
||||
return false
|
||||
end
|
||||
else
|
||||
minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of preciousness. Given type was \""..type(preciousness).."\".")
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- first check if count is of a correct type
|
||||
if type(count) ~= "number" and type(count) ~= "nil" and type(count) ~= "table" then
|
||||
minetest.log("error", "[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of “count”. Given type was \""..type(count).."\".")
|
||||
return false
|
||||
end
|
||||
-- if count’s a table, check if it’s format is correct
|
||||
if type(count) == "table" then
|
||||
if(not (type(count[1]) == "number" and type(count[2]) == "number" and (type(count[3]) == "function" or type(count[3]) == "nil"))) then
|
||||
minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had a malformed table for the count parameter.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- now do the same for wear:
|
||||
-- first check if wear is of a correct type
|
||||
if type(wear) ~= "number" and type(wear) ~= "nil" and type(wear) ~= "table" then
|
||||
minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had an illegal type of “wear”. Given type was \""..type(wear).."\".")
|
||||
return false
|
||||
end
|
||||
-- if wear’s a table, check if it’s format is correct
|
||||
if type(wear) == "table" then
|
||||
if(not (type(wear[1]) == "number" and type(wear[2]) == "number" and (type(wear[3]) == "function" or type(wear[3]) == "nil"))) then
|
||||
minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because it had a malformed table for the wear parameter.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- check type of treasurer_group
|
||||
if type(treasurer_groups) ~= "table" and type(treasurer_groups) ~= "nil" and type(treasurer_groups) ~= "string" then
|
||||
minetest.log("error","[treasurer] I rejected the treasure \""..tostring(name).."\" because the treasure_group parameter is of type "..tosting(type(treasurer_groups)).." (expected: nil, string or table).")
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--[[ End of checks. If we reached this point of the code, all checks have been passed
|
||||
and we finally register the treasure.]]
|
||||
|
||||
-- default count is 1
|
||||
if count == nil then count = 1 end
|
||||
-- default wear is 0
|
||||
if wear == nil then wear = 0 end
|
||||
local treasure = {
|
||||
name = name,
|
||||
rarity = rarity,
|
||||
count = count,
|
||||
wear = wear,
|
||||
preciousness = preciousness,
|
||||
metadata = "",
|
||||
}
|
||||
table.insert(treasurer.treasures, treasure)
|
||||
|
||||
--[[ Assign treasure to Treasurer group(s) or default if not provided ]]
|
||||
-- default Treasurer group is default
|
||||
if treasurer_groups == nil then treasurer_groups = "default" end
|
||||
|
||||
if(type(treasurer_groups) == "string") then
|
||||
if(treasurer.groups.treasurer[treasurer_groups] == nil) then
|
||||
treasurer.groups.treasurer[treasurer_groups] = {}
|
||||
end
|
||||
table.insert(treasurer.groups.treasurer[treasurer_groups], treasure)
|
||||
elseif(type(treasurer_groups) == "table") then
|
||||
for i=1,#treasurer_groups do
|
||||
-- assign to Treasurer group (create table if it does not exist yet)
|
||||
if(treasurer.groups.treasurer[treasurer_groups[i]] == nil) then
|
||||
treasurer.groups.treasurer[treasurer_groups[i]] = {}
|
||||
end
|
||||
table.insert(treasurer.groups.treasurer[treasurer_groups[i]], treasure)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
minetest.log("info","[treasurer] Treasure successfully registered: "..name)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--[=[
|
||||
part 3: Treasure spawning mod (TSM) handling
|
||||
]=]
|
||||
|
||||
--[[
|
||||
treasurer.select_random_treasures - request some treasures from treasurer
|
||||
parameters:
|
||||
count: (optional) amount of items in the treasure. If this value is nil, treasurer assumes a default of 1.
|
||||
min_preciousness: (optional) don’t consider treasures with a lower preciousness. nil = no lower limit
|
||||
max_preciousness: (optional) don’t consider treasures with a higher preciousness. nil = no lower limit
|
||||
treasurer_groups: (optional): Only consider treasures which are members of at least one of the members of the provided Treasurer group table. nil = consider all groups
|
||||
returns:
|
||||
a table of ItemStacks (the requested treasures) - may be empty
|
||||
on error, it returns false
|
||||
]]
|
||||
function treasurer.select_random_treasures(count, min_preciousness, max_preciousness, treasurer_groups)
|
||||
if #treasurer.treasures == 0 and count >= 1 then
|
||||
minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I can’t return any because no treasure was registered to me.")
|
||||
return {}
|
||||
end
|
||||
if count == nil then count = 1 end
|
||||
local sum = 0
|
||||
local cumulate = {}
|
||||
local randoms = {}
|
||||
|
||||
-- copy treasures into helper table
|
||||
local p_treasures = {}
|
||||
if(treasurer_groups == nil) then
|
||||
-- if the group filter is not used (defaul behaviour), copy all treasures
|
||||
for i=1,#treasurer.treasures do
|
||||
table.insert(p_treasures, treasurer.treasures[i])
|
||||
end
|
||||
|
||||
-- if the group filter IS used, copy only the treasures from the said groups
|
||||
elseif(type(treasurer_groups) == "string") then
|
||||
if(treasurer.groups.treasurer[treasurer_groups] ~= nil) then
|
||||
for i=1,#treasurer.groups.treasurer[treasurer_groups] do
|
||||
table.insert(p_treasures, treasurer.groups.treasurer[treasurer_groups][i])
|
||||
end
|
||||
else
|
||||
minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I can’t return any because no treasure which fits to the given Treasurer group “"..treasurer_groups.."”.")
|
||||
return {}
|
||||
end
|
||||
elseif(type(treasurer_groups) == "table") then
|
||||
for t=1,#treasurer_groups do
|
||||
if(treasurer.groups.treasurer[treasurer_groups[t]] ~= nil) then
|
||||
for i=1,#treasurer.groups.treasurer[treasurer_groups[t]] do
|
||||
table.insert(p_treasures, treasurer.groups.treasurer[treasurer_groups[t]][i])
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
minetest.log("error","[treasurer] treasurer.select_random_treasures was called with a malformed treasurer_groups parameter!")
|
||||
return false
|
||||
end
|
||||
|
||||
if(min_preciousness ~= nil) then
|
||||
-- filter out too unprecious treasures
|
||||
for t=#p_treasures,1,-1 do
|
||||
if((p_treasures[t].preciousness) < min_preciousness) then
|
||||
table.remove(p_treasures,t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if(max_preciousness ~= nil) then
|
||||
-- filter out too precious treasures
|
||||
for t=#p_treasures,1,-1 do
|
||||
if(p_treasures[t].preciousness > max_preciousness) then
|
||||
table.remove(p_treasures,t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for t=1,#p_treasures do
|
||||
sum = sum + p_treasures[t].rarity
|
||||
cumulate[t] = sum
|
||||
end
|
||||
for c=1,count do
|
||||
randoms[c] = math.random() * sum
|
||||
end
|
||||
|
||||
local treasures = {}
|
||||
for c=1,count do
|
||||
for t=1,#p_treasures do
|
||||
if randoms[c] < cumulate[t] then
|
||||
table.insert(treasures, p_treasures[t])
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local itemstacks = {}
|
||||
for i=1,#treasures do
|
||||
itemstacks[i] = treasurer.treasure_to_itemstack(treasures[i])
|
||||
end
|
||||
if #itemstacks < count then
|
||||
minetest.log("info","[treasurer] I was asked to return "..count.." treasure(s) but I could only return "..(#itemstacks)..".")
|
||||
end
|
||||
return itemstacks
|
||||
end
|
||||
|
||||
--[=[
|
||||
Part 4: internal functions
|
||||
]=]
|
||||
|
||||
--[[ treasurer.treasure_to_itemstack - converts a treasure table to an
|
||||
ItemStack
|
||||
parameter:
|
||||
treasure: a treasure (see format in the head of this file)
|
||||
returns:
|
||||
an ItemStack
|
||||
]]
|
||||
function treasurer.treasure_to_itemstack(treasure)
|
||||
local itemstack = {}
|
||||
itemstack.name = treasure.name
|
||||
itemstack.count = treasurer.determine_count(treasure)
|
||||
itemstack.wear = treasurer.determine_wear(treasure)
|
||||
itemstack.metadata = treasure.metadata
|
||||
|
||||
return ItemStack(itemstack)
|
||||
end
|
||||
|
||||
--[[
|
||||
This determines the count of a treasure by taking the various different
|
||||
possible types of the count value into account
|
||||
This function assumes that the treasure table is valid.
|
||||
returns: the count
|
||||
]]
|
||||
function treasurer.determine_count(treasure)
|
||||
if(type(treasure.count)=="number") then
|
||||
return treasure.count
|
||||
else
|
||||
local min,max,prob = treasure.count[1], treasure.count[2], treasure.count[3]
|
||||
if(prob == nil) then
|
||||
return(math.floor(min + math.random() * (max-min)))
|
||||
else
|
||||
return(math.floor(min + prob() * (max-min)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
This determines the wear of a treasure by taking the various different
|
||||
possible types of the wear value into account.
|
||||
This function assumes that the treasure table is valid.
|
||||
returns: the count
|
||||
]]
|
||||
function treasurer.determine_wear(treasure)
|
||||
if(type(treasure.wear)=="number") then
|
||||
return treasure.wear
|
||||
else
|
||||
local min,max,prob = treasure.wear[1], treasure.wear[2], treasure.wear[3]
|
||||
if(prob == nil) then
|
||||
return(math.floor(min + math.random() * (max-min)))
|
||||
else
|
||||
return(math.floor(min + prob() * (max-min)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
2
mods/other/tsm_chests/depends.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
treasurer
|
||||
default
|
1
mods/other/tsm_chests/description.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Generates some chests in the underground with random treasures.
|
216
mods/other/tsm_chests/init.lua
Executable file
|
@ -0,0 +1,216 @@
|
|||
--[[
|
||||
This is an example treasure spawning mod (TSM) for the default mod.
|
||||
It needs the mods “treasurer” and “default” to work.
|
||||
For an example, it is kinda advanced.
|
||||
|
||||
A TSM’s task is to somehow bring treasures (which are ItemStacks) into the world.
|
||||
This is also called “spawning treasures”.
|
||||
How it does this task is completely free to the programmer of the TSM.
|
||||
|
||||
This TSM spawns the treasures by placing chests (tsm_chests:chest) between 20 and 200 node lengths below the water surface. This cau
|
||||
The chests are provided by the default mod, therefore this TSM depends on the default mod.
|
||||
The treasures are requested from the treasurer mod. The TSM asks the treasurer mod for some treasures.
|
||||
|
||||
However, the treasurer mod comes itself with no treasures whatsoever. You need another mod which tells the treasurer what treasures to add. These mods are called “treasure registration mods” (TRMs).
|
||||
For this, there is another example mod, called “trm_default_example”, which registers a bunch of items of the default mod, like default:gold_ingot.
|
||||
]]
|
||||
|
||||
|
||||
local chest_formspec =
|
||||
"size[8,9]" ..
|
||||
default.gui_bg ..
|
||||
default.gui_bg_img ..
|
||||
default.gui_slots ..
|
||||
"list[current_name;main;0,0.3;8,4;]" ..
|
||||
"list[current_player;main;0,4.85;8,1;]" ..
|
||||
"list[current_player;main;0,6.08;8,3;8]" ..
|
||||
"listring[current_name;main]" ..
|
||||
"listring[current_player;main]" ..
|
||||
default.get_hotbar_bg(0,4.85)
|
||||
|
||||
minetest.register_node("tsm_chests:chest", {
|
||||
description = "Chest",
|
||||
tiles = {"default_chest_top.png", "default_chest_top.png", "default_chest_side.png",
|
||||
"default_chest_side.png", "default_chest_side.png", "default_chest_front.png"},
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 2},
|
||||
legacy_facedir_simple = true,
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
if player then
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
"You're not allowed to put things in treasure chests!")
|
||||
return 0
|
||||
end
|
||||
end,
|
||||
on_construct = function(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("formspec", chest_formspec)
|
||||
meta:set_string("infotext", "Chest")
|
||||
local inv = meta:get_inventory()
|
||||
inv:set_size("main", 8*4)
|
||||
end,
|
||||
can_dig = function(pos,player)
|
||||
local meta = minetest.get_meta(pos);
|
||||
local inv = meta:get_inventory()
|
||||
return inv:is_empty("main")
|
||||
end,
|
||||
on_metadata_inventory_move = function(pos, from_list, from_index,
|
||||
to_list, to_index, count, player)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" moves stuff in chest at " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
on_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" moves stuff to chest at " .. minetest.pos_to_string(pos))
|
||||
end,
|
||||
on_metadata_inventory_take = function(pos, listname, index, stack, player)
|
||||
minetest.log("action", player:get_player_name() ..
|
||||
" takes stuff from chest at " .. minetest.pos_to_string(pos))
|
||||
local inv = minetest.get_inventory({type = "node", pos=pos})
|
||||
if not inv or inv:is_empty("main") then
|
||||
minetest.set_node(pos, {name="air"})
|
||||
minetest.show_formspec(player:get_player_name(), "", player:get_inventory_formspec())
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--[[ here are some configuration variables ]]
|
||||
|
||||
local h_min = -65 -- minimum chest spawning height, relative to water_level
|
||||
local h_max = 40 -- maximum chest spawning height, relative to water_level
|
||||
local t_min = 4 -- minimum amount of treasures found in a chest
|
||||
local t_max = 7 -- maximum amount of treasures found in a chest
|
||||
|
||||
local water_level = tonumber(minetest.settings:get("water_level"))
|
||||
local get_node = minetest.get_node
|
||||
|
||||
local function findGroundLevel(pos, y_min, y_max)
|
||||
local ground = nil
|
||||
local top = y_max
|
||||
for y = y_max, y_min, -1 do
|
||||
local p = {x=pos.x,y=y,z=pos.z}
|
||||
local name = get_node(p).name
|
||||
if name == "air" or name == "default:water_source" or name == "default:lava_source" then
|
||||
top = y
|
||||
break
|
||||
end
|
||||
end
|
||||
for y=top,y_min,-1 do
|
||||
local p = {x=pos.x,y=y,z=pos.z}
|
||||
local name = get_node(p).name
|
||||
if name ~= "air" and name ~= "default:water_source" and name ~= "default:lava_source" then
|
||||
ground = y
|
||||
break
|
||||
end
|
||||
end
|
||||
return ground
|
||||
end
|
||||
|
||||
local function getFacedirs(pos, ground)
|
||||
-- secondly: rotate the chest
|
||||
-- find possible faces
|
||||
local xp, xm, zp, zm
|
||||
xp = minetest.get_node({x=pos.x+1, y=ground+1, z=pos.z})
|
||||
xm = minetest.get_node({x=pos.x-1, y=ground+1, z=pos.z})
|
||||
zp = minetest.get_node({x=pos.x, y=ground+1, z=pos.z+1})
|
||||
zm = minetest.get_node({x=pos.x, y=ground+1, z=pos.z-1})
|
||||
|
||||
-- Set Facedirs
|
||||
local facedirs = {}
|
||||
if xp.name=="air" or xp.name=="default:water_source" then
|
||||
table.insert(facedirs, minetest.dir_to_facedir({x=-1,y=0,z=0}))
|
||||
end
|
||||
if xm.name=="air" or xm.name=="default:water_source" then
|
||||
table.insert(facedirs, minetest.dir_to_facedir({x=1,y=0,z=0}))
|
||||
end
|
||||
if zp.name=="air" or zp.name=="default:water_source" then
|
||||
table.insert(facedirs, minetest.dir_to_facedir({x=0,y=0,z=-1}))
|
||||
end
|
||||
if zm.name=="air" or zm.name=="default:water_source" then
|
||||
table.insert(facedirs, minetest.dir_to_facedir({x=0,y=0,z=1}))
|
||||
end
|
||||
|
||||
return facedirs
|
||||
end
|
||||
|
||||
local function placeChest(pos, chest_pos, ground, nn)
|
||||
local chest = {name = "tsm_chests:chest"}
|
||||
local facedirs = getFacedirs(pos, ground)
|
||||
|
||||
-- choose a random face (if possible)
|
||||
if #facedirs == 0 then
|
||||
minetest.set_node({x=pos.x,y=ground+1, z=pos.z+1}, {name=nn})
|
||||
chest.param2 = minetest.dir_to_facedir({x=0,y=0,z=1})
|
||||
else
|
||||
chest.param2 = facedirs[math.floor(math.random(#facedirs))]
|
||||
end
|
||||
|
||||
-- Lastly: place the chest
|
||||
minetest.set_node(chest_pos, chest)
|
||||
|
||||
-- Get treasure
|
||||
local treasure_amount = math.ceil(math.random(t_min, t_max))
|
||||
-- local height = math.abs(h_min) - math.abs(h_max)
|
||||
-- local y_norm = (ground+1) - h_min
|
||||
-- local scale = 1 - (y_norm/height)
|
||||
local minp = 0 --scale*4 -- minimal preciousness: 0..4
|
||||
local maxp = 10 --scale*4+2.1 -- maximum preciousness: 2.1..6.1
|
||||
local treasures = treasurer.select_random_treasures(treasure_amount, minp, maxp)
|
||||
|
||||
-- Add Treasure to Chst
|
||||
local meta = minetest.get_meta(chest_pos)
|
||||
local inv = meta:get_inventory()
|
||||
for i=1, #treasures do
|
||||
inv:set_stack("main", i, treasures[i])
|
||||
end
|
||||
end
|
||||
|
||||
--[[ here comes the generation code
|
||||
the interesting part which involes treasurer comes way below
|
||||
]]
|
||||
function place_chests(minp, maxp, seed, number_chests)
|
||||
minp = {x=minp.x, y=minp.y, z=minp.z}
|
||||
maxp = {x=maxp.x, y=maxp.y, z=maxp.z}
|
||||
|
||||
-- chests minimum and maximum spawn height
|
||||
local height_min = water_level + h_min
|
||||
local height_max = water_level + h_max
|
||||
|
||||
if(maxp.y < height_min or minp.y > height_max) then
|
||||
return
|
||||
end
|
||||
local y_min = math.max(minp.y, height_min)
|
||||
local y_max = math.min(maxp.y, height_max)
|
||||
local attempts = 0
|
||||
local chests_placed = 0
|
||||
while chests_placed < number_chests and attempts < number_chests + 12 do
|
||||
attempts = attempts + 1
|
||||
local pos = {
|
||||
x = math.random(minp.x, maxp.x),
|
||||
y = minp.y,
|
||||
z = math.random(minp.z, maxp.z)
|
||||
}
|
||||
|
||||
-- Find ground level
|
||||
local ground = findGroundLevel(pos, y_min, y_max)
|
||||
if ground ~= nil then
|
||||
local chest_pos = {x = pos.x, y = ground + 1, z = pos.z}
|
||||
|
||||
-- Don't spawn at barrier
|
||||
if chest_pos.z == 0 then
|
||||
chest_pos.z = -1
|
||||
end
|
||||
|
||||
local nn = minetest.get_node(chest_pos).name -- chest node name (before it becomes a chest)
|
||||
if nn == "air" or nn == "default:water_source" then
|
||||
placeChest(pos, chest_pos, ground, nn)
|
||||
|
||||
chests_placed = chests_placed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.log("warning", "Spawned " .. chests_placed .. "/" .. number_chests .. " chests after " .. attempts .. " attempts!")
|
||||
end
|
BIN
mods/other/tsm_chests/textures/default_chest_front.png
Normal file
After Width: | Height: | Size: 423 B |
BIN
mods/other/tsm_chests/textures/default_chest_side.png
Normal file
After Width: | Height: | Size: 375 B |
BIN
mods/other/tsm_chests/textures/default_chest_top.png
Normal file
After Width: | Height: | Size: 422 B |
96
mods/other/vote/README.md
Normal file
|
@ -0,0 +1,96 @@
|
|||
# Vote
|
||||
A mod for Minetest adding an API to allow voting on servers.
|
||||
|
||||
Version 0.1
|
||||
|
||||
Created by [rubenwardy](http://rubenwardy.com)
|
||||
Copyright (c) 2015, no rights reserved
|
||||
Licensed under WTFPL or CC0 (you choose)
|
||||
|
||||
# Settings
|
||||
|
||||
* vote.maximum_active - maximum votes running at a time, votes are queued if it
|
||||
reaches this. Defaults to 1.
|
||||
|
||||
# Example
|
||||
|
||||
```lua
|
||||
minetest.register_chatcommand("vote_kick", {
|
||||
privs = {
|
||||
interact = true
|
||||
},
|
||||
func = function(name, param)
|
||||
if not minetest.get_player_by_name(param) then
|
||||
minetest.chat_send_player(name, "There is no player called '" ..
|
||||
param .. "'")
|
||||
end
|
||||
|
||||
vote.new_vote(name, {
|
||||
description = "Kick player " .. param,
|
||||
help = "/yes, /no or /abstain",
|
||||
name = param,
|
||||
duration = 60,
|
||||
|
||||
on_result = function(self, result, results)
|
||||
if result == "yes" then
|
||||
minetest.chat_send_all("Vote passed, " ..
|
||||
#results.yes .. " to " .. #results.no .. ", " ..
|
||||
self.name .. " will be kicked.")
|
||||
minetest.kick_player(self.name, "The vote to kick you passed")
|
||||
else
|
||||
minetest.chat_send_all("Vote failed, " ..
|
||||
#results.yes .. " to " .. #results.no .. ", " ..
|
||||
self.name .. " remains ingame.")
|
||||
end
|
||||
end,
|
||||
|
||||
on_vote = function(self, name, value)
|
||||
minetest.chat_send_all(name .. " voted " .. value .. " to '" ..
|
||||
self.description .. "'")
|
||||
end
|
||||
})
|
||||
end
|
||||
})
|
||||
```
|
||||
|
||||
# API
|
||||
|
||||
## Results
|
||||
|
||||
* voted - a key-value table. voted[name] = true if a player called name voted.
|
||||
* abstain - a list of the names of players who abstained.
|
||||
* <option> - a list of the names of players who voted for this option.
|
||||
|
||||
For example:
|
||||
|
||||
```lua
|
||||
results = {
|
||||
voted = {
|
||||
one = true,
|
||||
two = true,
|
||||
three = true,
|
||||
four = true
|
||||
}
|
||||
yes = {"one", "three"},
|
||||
no = {"two"}
|
||||
abstain = {"four"}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Values
|
||||
|
||||
* description - required.
|
||||
* help - recommended. How to respond to the vote.
|
||||
* duration - the duration of the vote, before it expires.
|
||||
* perc_needed - if yes/no, this is the percentage needed to pass.
|
||||
* options - a list of possible options. (not fully supported yet)
|
||||
|
||||
## Methods
|
||||
|
||||
* can_vote(self, name) - return true if player `name` can vote on this issue.
|
||||
* on_start(self) - called when vote starts. Return false to cancel.
|
||||
* on_decide(self, results) - see results section. Return the winning result.
|
||||
* on_result(self, result, results) - when vote ends, result is the winning result
|
||||
* on_vote(self, name, value) - called when a player casts a vote
|
||||
* on_abstain(self, name) - called when a player abstains
|
48
mods/other/vote/hudkit.lua
Normal file
|
@ -0,0 +1,48 @@
|
|||
-- HudKit, by rubenwardy
|
||||
-- License: Either WTFPL or CC0, you can choose.
|
||||
|
||||
local function hudkit()
|
||||
return {
|
||||
players = {},
|
||||
|
||||
add = function(self, player, id, def)
|
||||
local name = player:get_player_name()
|
||||
local elements = self.players[name]
|
||||
|
||||
if not elements then
|
||||
self.players[name] = {}
|
||||
elements = self.players[name]
|
||||
end
|
||||
|
||||
elements[id] = player:hud_add(def)
|
||||
end,
|
||||
|
||||
exists = function(self, player, id)
|
||||
local elements = self.players[player:get_player_name()]
|
||||
return elements and elements[id]
|
||||
end,
|
||||
|
||||
change = function(self, player, id, stat, value)
|
||||
local elements = self.players[player:get_player_name()]
|
||||
if not elements or not elements[id] then
|
||||
return false
|
||||
end
|
||||
|
||||
player:hud_change(elements[id], stat, value)
|
||||
return true
|
||||
end,
|
||||
|
||||
remove = function(self, player, id)
|
||||
local elements = self.players[player:get_player_name()]
|
||||
if not elements or not elements[id] then
|
||||
return false
|
||||
end
|
||||
|
||||
player:hud_remove(elements[id])
|
||||
elements[id] = nil
|
||||
return true
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return hudkit
|
314
mods/other/vote/init.lua
Normal file
|
@ -0,0 +1,314 @@
|
|||
vote = {
|
||||
active = {},
|
||||
queue = {},
|
||||
}
|
||||
|
||||
function vote.new_vote(creator, voteset)
|
||||
local max_votes = tonumber(minetest.settings:get("vote.maximum_active")) or 1
|
||||
local max_queue = tonumber(minetest.settings:get("vote.maximum_active")) or 0
|
||||
|
||||
if #vote.active < max_votes then
|
||||
vote.start_vote(voteset)
|
||||
return true, "Vote Started. You still need to vote: " .. voteset.help
|
||||
elseif max_queue == 0 then
|
||||
return false, "A vote is already running, please try again later."
|
||||
elseif #vote.queue < max_queue then
|
||||
table.insert(vote.queue, voteset)
|
||||
return true, "Vote queued until there is less then " .. max_votes ..
|
||||
" votes active."
|
||||
else
|
||||
return false, "The queue of votes waiting to run is full. Please try again later."
|
||||
end
|
||||
end
|
||||
|
||||
function vote.start_vote(voteset)
|
||||
minetest.log("action", "Vote started: " .. voteset.description)
|
||||
|
||||
table.insert(vote.active, voteset)
|
||||
|
||||
-- Build results table
|
||||
voteset.results = {
|
||||
abstain = {},
|
||||
voted = {}
|
||||
}
|
||||
if voteset.options then
|
||||
for _, option in pairs(voteset.options) do
|
||||
voteset.results[option] = {}
|
||||
print(" - " .. option)
|
||||
end
|
||||
else
|
||||
voteset.results.yes = {}
|
||||
voteset.results.no = {}
|
||||
end
|
||||
|
||||
-- Run start callback
|
||||
if voteset.on_start then
|
||||
voteset:on_start()
|
||||
end
|
||||
|
||||
-- Timer for end
|
||||
if voteset.duration or voteset.time then
|
||||
minetest.after(voteset.duration + 0.1, function()
|
||||
vote.end_vote(voteset)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Show HUD a.s.a.p.
|
||||
vote.update_all_hud()
|
||||
end
|
||||
|
||||
function vote.end_vote(voteset)
|
||||
local removed = false
|
||||
for i, voteset2 in pairs(vote.active) do
|
||||
if voteset == voteset2 then
|
||||
table.remove(vote.active, i, 1)
|
||||
removed = true
|
||||
end
|
||||
end
|
||||
if not removed then
|
||||
return
|
||||
end
|
||||
|
||||
local result = nil
|
||||
if voteset.on_decide then
|
||||
result = voteset:on_decide(voteset.results)
|
||||
elseif voteset.results.yes and voteset.results.no then
|
||||
local total = #voteset.results.yes + #voteset.results.no
|
||||
local perc_needed = voteset.perc_needed or 0.5
|
||||
|
||||
if #voteset.results.yes / total > perc_needed then
|
||||
result = "yes"
|
||||
else
|
||||
result = "no"
|
||||
end
|
||||
end
|
||||
|
||||
minetest.log("action", "Vote '" .. voteset.description ..
|
||||
"' ended with result '" .. result .. "'.")
|
||||
|
||||
if voteset.on_result then
|
||||
voteset:on_result(result, voteset.results)
|
||||
end
|
||||
|
||||
local max_votes = tonumber(minetest.settings:get("vote.maximum_active")) or 1
|
||||
if #vote.active < max_votes and #vote.queue > 0 then
|
||||
local nextvote = table.remove(vote.queue, 1)
|
||||
vote.start_vote(nextvote)
|
||||
else
|
||||
-- Update HUD a.s.a.p.
|
||||
vote.update_all_hud()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function vote.get_next_vote(name)
|
||||
for _, voteset in pairs(vote.active) do
|
||||
if not voteset.results.voted[name] then
|
||||
return voteset
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function vote.check_vote(voteset)
|
||||
local all_players_voted = true
|
||||
local players = minetest.get_connected_players()
|
||||
for _, player in pairs(players) do
|
||||
local name = player:get_player_name()
|
||||
if not voteset.results.voted[name] then
|
||||
all_players_voted = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if all_players_voted then
|
||||
vote.end_vote(voteset)
|
||||
end
|
||||
end
|
||||
|
||||
function vote.vote(voteset, name, value)
|
||||
if not voteset.results[value] then
|
||||
return
|
||||
end
|
||||
|
||||
minetest.log("action", name .. " voted '" .. value .. "' to '"
|
||||
.. voteset.description .. "'")
|
||||
|
||||
table.insert(voteset.results[value], name)
|
||||
voteset.results.voted[name] = true
|
||||
if voteset.on_vote then
|
||||
voteset:on_vote(name, value)
|
||||
end
|
||||
vote.check_vote(voteset)
|
||||
end
|
||||
|
||||
minetest.register_privilege("vote_admin", {
|
||||
description = "Allows a player to manage running votes."
|
||||
})
|
||||
|
||||
function vote.clear()
|
||||
vote.active = {}
|
||||
vote.queue = {}
|
||||
vote.update_all_hud()
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("vote_clear", {
|
||||
privs = {
|
||||
vote_admin = true,
|
||||
},
|
||||
func = vote.clear
|
||||
})
|
||||
|
||||
local hudkit = dofile(minetest.get_modpath("vote") .. "/hudkit.lua")
|
||||
vote.hud = hudkit()
|
||||
function vote.update_hud(player)
|
||||
local name = player:get_player_name()
|
||||
local voteset = vote.get_next_vote(name)
|
||||
if not voteset or not minetest.check_player_privs(name,
|
||||
{interact=true, vote=true}) then
|
||||
vote.hud:remove(player, "vote:desc")
|
||||
vote.hud:remove(player, "vote:bg")
|
||||
vote.hud:remove(player, "vote:help")
|
||||
return
|
||||
end
|
||||
|
||||
if not vote.hud:exists(player, "vote:bg") then
|
||||
vote.hud:add(player, "vote:bg", {
|
||||
hud_elem_type = "image",
|
||||
position = {x = 1, y = 0.5},
|
||||
scale = {x = 1, y = 1},
|
||||
text = "vote_background.png",
|
||||
offset = {x=-100, y = 10},
|
||||
number = 0xFFFFFF
|
||||
})
|
||||
end
|
||||
|
||||
if vote.hud:exists(player, "vote:desc") then
|
||||
vote.hud:change(player, "vote:desc", "text", voteset.description .. "?")
|
||||
else
|
||||
vote.hud:add(player, "vote:desc", {
|
||||
hud_elem_type = "text",
|
||||
position = {x = 1, y = 0.5},
|
||||
scale = {x = 100, y = 100},
|
||||
text = voteset.description .. "?",
|
||||
offset = {x=-100, y = 0},
|
||||
number = 0xFFFFFF
|
||||
})
|
||||
end
|
||||
|
||||
if voteset.help then
|
||||
if vote.hud:exists(player, "vote:help") then
|
||||
vote.hud:change(player, "vote:help", "text", voteset.help)
|
||||
else
|
||||
vote.hud:add(player, "vote:help", {
|
||||
hud_elem_type = "text",
|
||||
position = {x = 1, y = 0.5},
|
||||
scale = {x = 100, y = 100},
|
||||
text = voteset.help,
|
||||
offset = {x=-100, y = 20},
|
||||
number = 0xFFFFFF
|
||||
})
|
||||
end
|
||||
else
|
||||
vote.hud:remove(player, "vote:help")
|
||||
end
|
||||
end
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
vote.hud.players[player:get_player_name()] = nil
|
||||
end)
|
||||
|
||||
function vote.update_all_hud()
|
||||
local players = minetest.get_connected_players()
|
||||
for _, player in pairs(players) do
|
||||
vote.update_hud(player)
|
||||
end
|
||||
minetest.after(5, vote.update_all_hud)
|
||||
end
|
||||
minetest.after(5, vote.update_all_hud)
|
||||
|
||||
minetest.register_privilege("vote", {
|
||||
description = "Can vote on issues",
|
||||
})
|
||||
|
||||
minetest.register_privilege("vote_starter", {
|
||||
description = "Can start votes on issues",
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("yes", {
|
||||
privs = {
|
||||
interact = true,
|
||||
vote = true
|
||||
},
|
||||
func = function(name, params)
|
||||
local voteset = vote.get_next_vote(name)
|
||||
if not voteset then
|
||||
minetest.chat_send_player(name,
|
||||
"There is no vote currently running!")
|
||||
return
|
||||
elseif not voteset.results.yes then
|
||||
minetest.chat_send_player(name, "The vote is not a yes/no one.")
|
||||
return
|
||||
elseif voteset.can_vote and not voteset:can_vote(name) then
|
||||
minetest.chat_send_player(name,
|
||||
"You can't vote in the currently active vote!")
|
||||
return
|
||||
end
|
||||
|
||||
vote.vote(voteset, name, "yes")
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("no", {
|
||||
privs = {
|
||||
interact = true,
|
||||
vote = true
|
||||
},
|
||||
func = function(name, params)
|
||||
local voteset = vote.get_next_vote(name)
|
||||
if not voteset then
|
||||
minetest.chat_send_player(name,
|
||||
"There is no vote currently running!")
|
||||
return
|
||||
elseif not voteset.results.no then
|
||||
minetest.chat_send_player(name, "The vote is not a yes/no one.")
|
||||
return
|
||||
elseif voteset.can_vote and not voteset:can_vote(name) then
|
||||
minetest.chat_send_player(name,
|
||||
"You can't vote in the currently active vote!")
|
||||
return
|
||||
end
|
||||
|
||||
vote.vote(voteset, name, "no")
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("abstain", {
|
||||
privs = {
|
||||
interact = true,
|
||||
vote = true
|
||||
},
|
||||
func = function(name, params)
|
||||
local voteset = vote.get_next_vote(name)
|
||||
if not voteset then
|
||||
minetest.chat_send_player(name,
|
||||
"There is no vote currently running!")
|
||||
return
|
||||
elseif voteset.can_vote and not voteset:can_vote(name) then
|
||||
minetest.chat_send_player(name,
|
||||
"You can't vote in the currently active vote!")
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(voteset.results.abstain, name)
|
||||
voteset.results.voted[name] = true
|
||||
if voteset.on_abstain then
|
||||
voteset:on_abstain(name)
|
||||
end
|
||||
vote.check_vote(voteset)
|
||||
end
|
||||
})
|
||||
|
||||
local set = minetest.settings:get("vote.kick_vote")
|
||||
if set == nil or minetest.is_yes(set) then
|
||||
dofile(minetest.get_modpath("vote") .. "/vote_kick.lua")
|
||||
end
|
BIN
mods/other/vote/textures/vote_background.png
Normal file
After Width: | Height: | Size: 278 B |
125
mods/other/vote/vote_kick.lua
Normal file
|
@ -0,0 +1,125 @@
|
|||
vote.kick_cooldown = 600
|
||||
local vlist = {} -- table storing player name, ip & lock status
|
||||
|
||||
minetest.register_privilege("vote_kick", {
|
||||
description = "Can (start) vote to kick a player",
|
||||
on_grant = function(name, granter)
|
||||
minetest.log("warning", "Player '" .. name .. "' has been" ..
|
||||
" granted 'vote_kick' by '" .. granter .. "'")
|
||||
end,
|
||||
on_revoke = function(name, revoker)
|
||||
minetest.log("warning", "Player '" .. name .. "' has been" ..
|
||||
" revoked of 'vote_kick' by '" .. revoker .. "'")
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("vote_kick", {
|
||||
params = "<name>",
|
||||
description = "Start a vote to kick a player",
|
||||
privs = {
|
||||
interact = true,
|
||||
vote_kick = true,
|
||||
},
|
||||
func = function(name, param)
|
||||
param = param:trim()
|
||||
if param == "" then
|
||||
return false, "Please specify a player name to be vote-kicked!"
|
||||
end
|
||||
|
||||
if not minetest.get_player_by_name(param) then
|
||||
return false, "There is no player called '" ..
|
||||
param .. "'"
|
||||
end
|
||||
|
||||
if minetest.check_player_privs(param, {kick = true, ban = true}) then
|
||||
return false, param .. " is a moderator, and can't be kicked!"
|
||||
end
|
||||
|
||||
minetest.log("warning", "Player '" .. name .. "' started a vote" ..
|
||||
" to kick '" .. param .. "'")
|
||||
return vote.new_vote(name, {
|
||||
description = "Kick " .. param,
|
||||
help = "/yes, /no or /abstain",
|
||||
name = param,
|
||||
duration = 60,
|
||||
perc_needed = 0.8,
|
||||
|
||||
on_result = function(self, result, results)
|
||||
if result == "yes" then
|
||||
minetest.chat_send_all("Vote passed, " ..
|
||||
#results.yes .. " to " .. #results.no .. ", " ..
|
||||
self.name .. " will be kicked.")
|
||||
minetest.kick_player(self.name,
|
||||
("The vote to kick you passed.\n You have been temporarily banned" ..
|
||||
" for %s minutes."):format(tostring(vote.kick_cooldown / 60)))
|
||||
vlist[self.name].locked = true
|
||||
minetest.after(vote.kick_cooldown, function()
|
||||
vlist[self.name] = nil
|
||||
end)
|
||||
else
|
||||
minetest.chat_send_all("Vote failed, " ..
|
||||
#results.yes .. " to " .. #results.no .. ", " ..
|
||||
self.name .. " remains ingame.")
|
||||
end
|
||||
end,
|
||||
|
||||
on_vote = function(self, voter, value)
|
||||
minetest.chat_send_all(voter .. " voted " .. value .. " to '" ..
|
||||
self.description .. "'")
|
||||
end
|
||||
})
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("unblock", {
|
||||
params = "<name>",
|
||||
description = "Unblock a vote-kicked player before the cooldown expires",
|
||||
privs = {kick = true, ban = true},
|
||||
func = function(name, param)
|
||||
param = param:trim()
|
||||
if param == "" then
|
||||
return false, "Please specify a player name to be unblocked!"
|
||||
end
|
||||
|
||||
if not minetest.get_player_by_name(param) then
|
||||
return false, "Can't find player '" .. param .. "'"
|
||||
end
|
||||
|
||||
if not vlist[name].locked then
|
||||
return false, "Failed! " .. param .. " is not blocked"
|
||||
end
|
||||
|
||||
vlist[name].locked = false
|
||||
return true, param .. " has been successfully unblocked!"
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
if not vlist[name] then
|
||||
vlist[name] = {
|
||||
ip = minetest.get_player_ip(name),
|
||||
locked = false
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
if not vlist[name].locked then
|
||||
vlist[name] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_prejoinplayer(function(name, ip)
|
||||
if vlist[name] and vlist[name].locked then
|
||||
return "Please wait until the vote cool down period has elapsed before rejoining!"
|
||||
else
|
||||
for k, v in pairs(vlist) do
|
||||
if v.ip == ip and v.locked then
|
||||
return "This IP has been temporarily blocked."..
|
||||
" Please wait until the cool-down period has elapsed before rejoining."
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
7
mods/other/wield3d/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
## Generic ignorable patterns and files
|
||||
*~
|
||||
.*.swp
|
||||
*bak*
|
||||
tags
|
||||
*.vim
|
||||
|
4
mods/other/wield3d/LICENSE.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
[mod] 3d wielded items [wield3d]
|
||||
================================
|
||||
|
||||
Copyright (C) 2013 Stuart Jones - WTFPL
|
15
mods/other/wield3d/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
[mod] 3d wielded items [wield3d]
|
||||
================================
|
||||
|
||||
Mod Version: 0.3.0
|
||||
|
||||
Minetest Version: 0.4.12 or later
|
||||
|
||||
Decription: Visible 3d wielded items for Minetest
|
||||
|
||||
Depends: default
|
||||
|
||||
Makes hand wielded items visible to other players.
|
||||
|
||||
See wield3d.conf.example for config options (save as wield3d.conf)
|
||||
|
2
mods/other/wield3d/depends.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
default
|
||||
|
131
mods/other/wield3d/init.lua
Normal file
|
@ -0,0 +1,131 @@
|
|||
WIELD3D_INIT_DELAY = 2
|
||||
WIELD3D_RETRY_TIME = 10
|
||||
WIELD3D_UPDATE_TIME = 1
|
||||
|
||||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
local input = io.open(modpath.."/wield3d.conf", "r")
|
||||
if input then
|
||||
dofile(modpath.."/wield3d.conf")
|
||||
input:close()
|
||||
input = nil
|
||||
end
|
||||
dofile(modpath.."/location.lua")
|
||||
|
||||
local location = {
|
||||
"Arm_Right", -- default bone
|
||||
{x=0.2, y=6.5, z=3}, -- default position
|
||||
{x=-100, y=225, z=90}, -- default rotation
|
||||
}
|
||||
local player_wielding = {}
|
||||
local timer = 0
|
||||
|
||||
local function add_wield_entity(player)
|
||||
local name = player:get_player_name()
|
||||
local pos = player:getpos()
|
||||
local inv = player:get_inventory()
|
||||
if name and pos and inv then
|
||||
local offset = {x=pos.x, y=pos.y + 0.5, z=pos.z}
|
||||
local object = minetest.add_entity(offset, "wield3d:wield_entity")
|
||||
if object then
|
||||
object:set_properties({collisionbox={0,0,0, 0,0,0}})
|
||||
object:set_attach(player, location[1], location[2], location[3])
|
||||
local entity = object:get_luaentity()
|
||||
if entity then
|
||||
entity.player = player
|
||||
player_wielding[name] = 1
|
||||
else
|
||||
object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_item("wield3d:hand", {
|
||||
type = "none",
|
||||
wield_image = "wield3d_trans.png",
|
||||
})
|
||||
|
||||
minetest.register_entity("wield3d:wield_entity", {
|
||||
physical = false,
|
||||
collisionbox = {-0.125,-0.125,-0.125, 0.125,0.125,0.125},
|
||||
visual = "wielditem",
|
||||
visual_size = {x=0.25, y=0.25},
|
||||
textures = {"wield3d:hand"},
|
||||
player = nil,
|
||||
item = nil,
|
||||
timer = 0,
|
||||
location = {location[1], location[2], location[3]},
|
||||
on_step = function(self, dtime)
|
||||
self.timer = self.timer + dtime
|
||||
if self.timer < WIELD3D_UPDATE_TIME then
|
||||
return
|
||||
end
|
||||
self.timer = 0
|
||||
local player = self.player
|
||||
if player then
|
||||
local name = player:get_player_name()
|
||||
local p1 = player:getpos()
|
||||
local p2 = self.object:getpos()
|
||||
if p1 and p2 then
|
||||
if vector.equals(p1, p2) then
|
||||
local stack = player:get_wielded_item()
|
||||
local item = stack:get_name() or ""
|
||||
if item == self.item then
|
||||
return
|
||||
end
|
||||
if minetest.get_modpath("wieldview") then
|
||||
local def = minetest.registered_items[item] or {}
|
||||
if def.inventory_image ~= "" then
|
||||
item = ""
|
||||
end
|
||||
end
|
||||
self.item = item
|
||||
if item == "" then
|
||||
item = "wield3d:hand"
|
||||
end
|
||||
local loc = wield3d_location[item] or location
|
||||
if loc[1] ~= self.location[1]
|
||||
or vector.equals(loc[2], self.location[2]) == false
|
||||
or vector.equals(loc[3], self.location[3]) == false then
|
||||
self.object:set_detach()
|
||||
self.object:set_attach(player, loc[1], loc[2], loc[3])
|
||||
self.location = {loc[1], loc[2], loc[3]}
|
||||
end
|
||||
self.object:set_properties({textures={item}})
|
||||
return
|
||||
end
|
||||
end
|
||||
player_wielding[name] = 0
|
||||
end
|
||||
self.object:remove()
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
timer = timer + dtime
|
||||
if timer > WIELD3D_RETRY_TIME then
|
||||
for name, state in pairs(player_wielding) do
|
||||
if state == 0 then
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
add_wield_entity(player)
|
||||
else
|
||||
player_wielding[name] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
timer = 0
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
if name then
|
||||
player_wielding[name] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
player_wielding[player:get_player_name()] = 0
|
||||
minetest.after(WIELD3D_INIT_DELAY, add_wield_entity, player)
|
||||
end)
|
32
mods/other/wield3d/location.lua
Normal file
|
@ -0,0 +1,32 @@
|
|||
-- Wielded Item Location Overrides - [item_name] = {bone, position, rotation}
|
||||
|
||||
local bone = "Arm_Right"
|
||||
|
||||
wield3d_location = {
|
||||
["default:torch"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["default:sapling"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["flowers:dandelion_white"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["flowers:dandelion_yellow"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["flowers:geranium"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["flowers:rose"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["flowers:tulip"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["flowers:viola"] = {bone, {x=0.2, y=6.5, z=3}, {x=-100, y=182, z=83}},
|
||||
["default:shovel_wood"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["default:shovel_stone"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["default:shovel_steel"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["default:shovel_bronze"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["default:shovel_mese"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["default:shovel_diamond"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["bucket:bucket_empty"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["bucket:bucket_water"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["bucket:bucket_lava"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["screwdriver:screwdriver"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["screwdriver:screwdriver1"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["screwdriver:screwdriver2"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["screwdriver:screwdriver3"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["screwdriver:screwdriver4"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["vessels:glass_bottle"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["vessels:drinking_glass"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
["vessels:steel_bottle"] = {bone, {x=0, y=6.5, z=3}, {x=-90, y=137, z=83}},
|
||||
}
|
||||
|
BIN
mods/other/wield3d/textures/wield3d_trans.png
Normal file
After Width: | Height: | Size: 146 B |
11
mods/other/wield3d/wield3d.conf.example
Normal file
|
@ -0,0 +1,11 @@
|
|||
-- Wield3d Configuration (defaults)
|
||||
|
||||
-- Increase this if you get non-attachment glitches when a player first joins.
|
||||
WIELD3D_INIT_DELAY = 1
|
||||
|
||||
-- Number of seconds before retry if initialization fails.
|
||||
WIELD3D_RETRY_TIME = 10
|
||||
|
||||
-- How often player wield items are updated.
|
||||
WIELD3D_UPDATE_TIME = 1
|
||||
|