Compare commits
10 Commits
d797adb812
...
e9d228262c
Author | SHA1 | Date | |
---|---|---|---|
|
e9d228262c | ||
|
abd2681b50 | ||
|
ca71e468b2 | ||
|
6dbca27945 | ||
|
463d432f03 | ||
|
09402846df | ||
|
0eaa3388b0 | ||
|
8fa28fcb6a | ||
|
715e408c40 | ||
|
325ed769df |
@ -1,8 +1,8 @@
|
||||
# Death Compass
|
||||
|
||||
![](./textures/death_compass_16_0.png) ![](./textures/death_compass_16_1.png) ![](./textures/death_compass_16_2.png) ![](./textures/death_compass_16_4.png) ![](./textures/death_compass_16_5.png) ![](./textures/death_compass_16_6.png) ![](./textures/death_compass_16_7.png) ![](./textures/death_compass_16_8.png) ![](./textures/death_compass_16_9.png) ![](./textures/death_compass_16_10.png) ![](./textures/death_compass_16_11.png) ![](./textures/death_compass_16_12.png) ![](./textures/death_compass_16_13.png) ![](./textures/death_compass_16_14.png) ![](./textures/death_compass_16_15.png) ![](./textures/death_compass_16_0.png)
|
||||
![](screenshot.jpg)
|
||||
|
||||
Have you ever died and been frustrated with being unable to find your way back to your corpse? Carry a death compass with you. When you die, the compass will respawn in your inventory and be magically bound to point to the location of your demise.
|
||||
Have you ever died and been frustrated with being unable to find your way back to your corpse? Carry a death compass with you. When you die, the compass will respawn in your inventory and be magically bound to point its bony finger to the location of your demise.
|
||||
|
||||
But hurry! The server administrator may have limited the duration that the compass will function for.
|
||||
|
||||
|
421
i18n.py
Normal file
421
i18n.py
Normal file
@ -0,0 +1,421 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Script to generate the template file and update the translation files.
|
||||
# Copy the script into the mod or modpack root folder and run it there.
|
||||
#
|
||||
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
|
||||
# LGPLv2.1+
|
||||
#
|
||||
# See https://github.com/minetest-tools/update_translations for
|
||||
# potential future updates to this script.
|
||||
|
||||
from __future__ import print_function
|
||||
import os, fnmatch, re, shutil, errno
|
||||
from sys import argv as _argv
|
||||
|
||||
# Running params
|
||||
params = {"recursive": False,
|
||||
"help": False,
|
||||
"mods": False,
|
||||
"verbose": False,
|
||||
"folders": []
|
||||
}
|
||||
# Available CLI options
|
||||
options = {"recursive": ['--recursive', '-r'],
|
||||
"help": ['--help', '-h'],
|
||||
"mods": ['--installed-mods'],
|
||||
"verbose": ['--verbose', '-v']
|
||||
}
|
||||
|
||||
# Strings longer than this will have extra space added between
|
||||
# them in the translation files to make it easier to distinguish their
|
||||
# beginnings and endings at a glance
|
||||
doublespace_threshold = 60
|
||||
|
||||
def set_params_folders(tab: list):
|
||||
'''Initialize params["folders"] from CLI arguments.'''
|
||||
# Discarding argument 0 (tool name)
|
||||
for param in tab[1:]:
|
||||
stop_param = False
|
||||
for option in options:
|
||||
if param in options[option]:
|
||||
stop_param = True
|
||||
break
|
||||
if not stop_param:
|
||||
params["folders"].append(os.path.abspath(param))
|
||||
|
||||
def set_params(tab: list):
|
||||
'''Initialize params from CLI arguments.'''
|
||||
for option in options:
|
||||
for option_name in options[option]:
|
||||
if option_name in tab:
|
||||
params[option] = True
|
||||
break
|
||||
|
||||
def print_help(name):
|
||||
'''Prints some help message.'''
|
||||
print(f'''SYNOPSIS
|
||||
{name} [OPTIONS] [PATHS...]
|
||||
DESCRIPTION
|
||||
{', '.join(options["help"])}
|
||||
prints this help message
|
||||
{', '.join(options["recursive"])}
|
||||
run on all subfolders of paths given
|
||||
{', '.join(options["mods"])}
|
||||
run on locally installed modules
|
||||
{', '.join(options["verbose"])}
|
||||
add output information
|
||||
''')
|
||||
|
||||
|
||||
def main():
|
||||
'''Main function'''
|
||||
set_params(_argv)
|
||||
set_params_folders(_argv)
|
||||
if params["help"]:
|
||||
print_help(_argv[0])
|
||||
elif params["recursive"] and params["mods"]:
|
||||
print("Option --installed-mods is incompatible with --recursive")
|
||||
else:
|
||||
# Add recursivity message
|
||||
print("Running ", end='')
|
||||
if params["recursive"]:
|
||||
print("recursively ", end='')
|
||||
# Running
|
||||
if params["mods"]:
|
||||
print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}")
|
||||
run_all_subfolders("~/.minetest/mods")
|
||||
elif len(params["folders"]) >= 2:
|
||||
print("on folder list:", params["folders"])
|
||||
for f in params["folders"]:
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(f)
|
||||
else:
|
||||
update_folder(f)
|
||||
elif len(params["folders"]) == 1:
|
||||
print("on folder", params["folders"][0])
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(params["folders"][0])
|
||||
else:
|
||||
update_folder(params["folders"][0])
|
||||
else:
|
||||
print("on folder", os.path.abspath("./"))
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(os.path.abspath("./"))
|
||||
else:
|
||||
update_folder(os.path.abspath("./"))
|
||||
|
||||
#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ')
|
||||
#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote
|
||||
pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL)
|
||||
pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL)
|
||||
|
||||
# Handles "concatenation" .. " of strings"
|
||||
pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL)
|
||||
|
||||
pattern_tr = re.compile(r'(.+?[^@])=(.*)')
|
||||
pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)')
|
||||
pattern_tr_filename = re.compile(r'\.tr$')
|
||||
pattern_po_language_code = re.compile(r'(.*)\.po$')
|
||||
|
||||
#attempt to read the mod's name from the mod.conf file. Returns None on failure
|
||||
def get_modname(folder):
|
||||
try:
|
||||
with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf:
|
||||
for line in mod_conf:
|
||||
match = pattern_name.match(line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
#If there are already .tr files in /locale, returns a list of their names
|
||||
def get_existing_tr_files(folder):
|
||||
out = []
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
if pattern_tr_filename.search(name):
|
||||
out.append(name)
|
||||
return out
|
||||
|
||||
# A series of search and replaces that massage a .po file's contents into
|
||||
# a .tr file's equivalent
|
||||
def process_po_file(text):
|
||||
# The first three items are for unused matches
|
||||
text = re.sub(r'#~ msgid "', "", text)
|
||||
text = re.sub(r'"\n#~ msgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\n#~ msgstr "', "=", text)
|
||||
# comment lines
|
||||
text = re.sub(r'#.*\n', "", text)
|
||||
# converting msg pairs into "=" pairs
|
||||
text = re.sub(r'msgid "', "", text)
|
||||
text = re.sub(r'"\nmsgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\nmsgstr "', "=", text)
|
||||
# various line breaks and escape codes
|
||||
text = re.sub(r'"\n"', "", text)
|
||||
text = re.sub(r'"\n', "\n", text)
|
||||
text = re.sub(r'\\"', '"', text)
|
||||
text = re.sub(r'\\n', '@n', text)
|
||||
# remove header text
|
||||
text = re.sub(r'=Project-Id-Version:.*\n', "", text)
|
||||
# remove double-spaced lines
|
||||
text = re.sub(r'\n\n', '\n', text)
|
||||
return text
|
||||
|
||||
# Go through existing .po files and, if a .tr file for that language
|
||||
# *doesn't* exist, convert it and create it.
|
||||
# The .tr file that results will subsequently be reprocessed so
|
||||
# any "no longer used" strings will be preserved.
|
||||
# Note that "fuzzy" tags will be lost in this process.
|
||||
def process_po_files(folder, modname):
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
code_match = pattern_po_language_code.match(name)
|
||||
if code_match == None:
|
||||
continue
|
||||
language_code = code_match.group(1)
|
||||
tr_name = modname + "." + language_code + ".tr"
|
||||
tr_file = os.path.join(root, tr_name)
|
||||
if os.path.exists(tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"{tr_name} already exists, ignoring {name}")
|
||||
continue
|
||||
fname = os.path.join(root, name)
|
||||
with open(fname, "r", encoding='utf-8') as po_file:
|
||||
if params["verbose"]:
|
||||
print(f"Importing translations from {name}")
|
||||
text = process_po_file(po_file.read())
|
||||
with open(tr_file, "wt", encoding='utf-8') as tr_out:
|
||||
tr_out.write(text)
|
||||
|
||||
# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
|
||||
# Creates a directory if it doesn't exist, silently does
|
||||
# nothing if it already exists
|
||||
def mkdir_p(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc: # Python >2.5
|
||||
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
||||
pass
|
||||
else: raise
|
||||
|
||||
# Converts the template dictionary to a text to be written as a file
|
||||
# dKeyStrings is a dictionary of localized string to source file sets
|
||||
# dOld is a dictionary of existing translations and comments from
|
||||
# the previous version of this text
|
||||
def strings_to_text(dkeyStrings, dOld, mod_name):
|
||||
lOut = [f"# textdomain: {mod_name}\n"]
|
||||
|
||||
dGroupedBySource = {}
|
||||
|
||||
for key in dkeyStrings:
|
||||
sourceList = list(dkeyStrings[key])
|
||||
sourceList.sort()
|
||||
sourceString = "\n".join(sourceList)
|
||||
listForSource = dGroupedBySource.get(sourceString, [])
|
||||
listForSource.append(key)
|
||||
dGroupedBySource[sourceString] = listForSource
|
||||
|
||||
lSourceKeys = list(dGroupedBySource.keys())
|
||||
lSourceKeys.sort()
|
||||
for source in lSourceKeys:
|
||||
localizedStrings = dGroupedBySource[source]
|
||||
localizedStrings.sort()
|
||||
lOut.append("")
|
||||
lOut.append(source)
|
||||
lOut.append("")
|
||||
for localizedString in localizedStrings:
|
||||
val = dOld.get(localizedString, {})
|
||||
translation = val.get("translation", "")
|
||||
comment = val.get("comment")
|
||||
if len(localizedString) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{localizedString}={translation}")
|
||||
if len(localizedString) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
|
||||
|
||||
unusedExist = False
|
||||
for key in dOld:
|
||||
if key not in dkeyStrings:
|
||||
val = dOld[key]
|
||||
translation = val.get("translation")
|
||||
comment = val.get("comment")
|
||||
# only keep an unused translation if there was translated
|
||||
# text or a comment associated with it
|
||||
if translation != None and (translation != "" or comment):
|
||||
if not unusedExist:
|
||||
unusedExist = True
|
||||
lOut.append("\n\n##### not used anymore #####\n")
|
||||
if len(key) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{key}={translation}")
|
||||
if len(key) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
return "\n".join(lOut) + '\n'
|
||||
|
||||
# Writes a template.txt file
|
||||
# dkeyStrings is the dictionary returned by generate_template
|
||||
def write_template(templ_file, dkeyStrings, mod_name):
|
||||
# read existing template file to preserve comments
|
||||
existing_template = import_tr_file(templ_file)
|
||||
|
||||
text = strings_to_text(dkeyStrings, existing_template[0], mod_name)
|
||||
mkdir_p(os.path.dirname(templ_file))
|
||||
with open(templ_file, "wt", encoding='utf-8') as template_file:
|
||||
template_file.write(text)
|
||||
|
||||
|
||||
# Gets all translatable strings from a lua file
|
||||
def read_lua_file_strings(lua_file):
|
||||
lOut = []
|
||||
with open(lua_file, encoding='utf-8') as text_file:
|
||||
text = text_file.read()
|
||||
#TODO remove comments here
|
||||
|
||||
text = re.sub(pattern_concat, "", text)
|
||||
|
||||
strings = []
|
||||
for s in pattern_lua.findall(text):
|
||||
strings.append(s[1])
|
||||
for s in pattern_lua_bracketed.findall(text):
|
||||
strings.append(s)
|
||||
|
||||
for s in strings:
|
||||
s = re.sub(r'"\.\.\s+"', "", s)
|
||||
s = re.sub("@[^@=0-9]", "@@", s)
|
||||
s = s.replace('\\"', '"')
|
||||
s = s.replace("\\'", "'")
|
||||
s = s.replace("\n", "@n")
|
||||
s = s.replace("\\n", "@n")
|
||||
s = s.replace("=", "@=")
|
||||
lOut.append(s)
|
||||
return lOut
|
||||
|
||||
# Gets strings from an existing translation file
|
||||
# returns both a dictionary of translations
|
||||
# and the full original source text so that the new text
|
||||
# can be compared to it for changes.
|
||||
def import_tr_file(tr_file):
|
||||
dOut = {}
|
||||
text = None
|
||||
if os.path.exists(tr_file):
|
||||
with open(tr_file, "r", encoding='utf-8') as existing_file :
|
||||
# save the full text to allow for comparison
|
||||
# of the old version with the new output
|
||||
text = existing_file.read()
|
||||
existing_file.seek(0)
|
||||
# a running record of the current comment block
|
||||
# we're inside, to allow preceeding multi-line comments
|
||||
# to be retained for a translation line
|
||||
latest_comment_block = None
|
||||
for line in existing_file.readlines():
|
||||
line = line.rstrip('\n')
|
||||
if line[:3] == "###":
|
||||
# Reset comment block if we hit a header
|
||||
latest_comment_block = None
|
||||
continue
|
||||
if line[:1] == "#":
|
||||
# Save the comment we're inside
|
||||
if not latest_comment_block:
|
||||
latest_comment_block = line
|
||||
else:
|
||||
latest_comment_block = latest_comment_block + "\n" + line
|
||||
continue
|
||||
match = pattern_tr.match(line)
|
||||
if match:
|
||||
# this line is a translated line
|
||||
outval = {}
|
||||
outval["translation"] = match.group(2)
|
||||
if latest_comment_block:
|
||||
# if there was a comment, record that.
|
||||
outval["comment"] = latest_comment_block
|
||||
latest_comment_block = None
|
||||
dOut[match.group(1)] = outval
|
||||
return (dOut, text)
|
||||
|
||||
# Walks all lua files in the mod folder, collects translatable strings,
|
||||
# and writes it to a template.txt file
|
||||
# Returns a dictionary of localized strings to source file sets
|
||||
# that can be used with the strings_to_text function.
|
||||
def generate_template(folder, mod_name):
|
||||
dOut = {}
|
||||
for root, dirs, files in os.walk(folder):
|
||||
for name in files:
|
||||
if fnmatch.fnmatch(name, "*.lua"):
|
||||
fname = os.path.join(root, name)
|
||||
found = read_lua_file_strings(fname)
|
||||
if params["verbose"]:
|
||||
print(f"{fname}: {str(len(found))} translatable strings")
|
||||
|
||||
for s in found:
|
||||
sources = dOut.get(s, set())
|
||||
sources.add(f"### {os.path.basename(fname)} ###")
|
||||
dOut[s] = sources
|
||||
|
||||
if len(dOut) == 0:
|
||||
return None
|
||||
templ_file = os.path.join(folder, "locale/template.txt")
|
||||
write_template(templ_file, dOut, mod_name)
|
||||
return dOut
|
||||
|
||||
# Updates an existing .tr file, copying the old one to a ".old" file
|
||||
# if any changes have happened
|
||||
# dNew is the data used to generate the template, it has all the
|
||||
# currently-existing localized strings
|
||||
def update_tr_file(dNew, mod_name, tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"updating {tr_file}")
|
||||
|
||||
tr_import = import_tr_file(tr_file)
|
||||
dOld = tr_import[0]
|
||||
textOld = tr_import[1]
|
||||
|
||||
textNew = strings_to_text(dNew, dOld, mod_name)
|
||||
|
||||
if textOld and textOld != textNew:
|
||||
print(f"{tr_file} has changed.")
|
||||
shutil.copyfile(tr_file, f"{tr_file}.old")
|
||||
|
||||
with open(tr_file, "w", encoding='utf-8') as new_tr_file:
|
||||
new_tr_file.write(textNew)
|
||||
|
||||
# Updates translation files for the mod in the given folder
|
||||
def update_mod(folder):
|
||||
modname = get_modname(folder)
|
||||
if modname is not None:
|
||||
process_po_files(folder, modname)
|
||||
print(f"Updating translations for {modname}")
|
||||
data = generate_template(folder, modname)
|
||||
if data == None:
|
||||
print(f"No translatable strings found in {modname}")
|
||||
else:
|
||||
for tr_file in get_existing_tr_files(folder):
|
||||
update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
|
||||
else:
|
||||
print("Unable to find modname in folder " + folder)
|
||||
|
||||
# Determines if the folder being pointed to is a mod or a mod pack
|
||||
# and then runs update_mod accordingly
|
||||
def update_folder(folder):
|
||||
is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
|
||||
if is_modpack:
|
||||
subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
|
||||
for subfolder in subfolders:
|
||||
update_mod(subfolder + "/")
|
||||
else:
|
||||
update_mod(folder)
|
||||
print("Done.")
|
||||
|
||||
def run_all_subfolders(folder):
|
||||
for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]:
|
||||
update_folder(modfolder + "/")
|
||||
|
||||
|
||||
main()
|
192
init.lua
192
init.lua
@ -9,8 +9,67 @@ local automatic = minetest.settings:get_bool("death_compass_automatic", false)
|
||||
|
||||
local range_to_inactivate = 5
|
||||
|
||||
local hud_position = {
|
||||
x= tonumber(minetest.settings:get("death_compass_hud_x")) or 0.5,
|
||||
y= tonumber(minetest.settings:get("death_compass_hud_y")) or 0.9,
|
||||
}
|
||||
local hud_color = tonumber("0x" .. (minetest.settings:get("death_compass_hud_color") or "FFFF00")) or 0xFFFF00
|
||||
|
||||
-- If round is true the return string will only have the two largest-scale values
|
||||
local function clock_string(seconds, round)
|
||||
seconds = math.floor(seconds)
|
||||
local days = math.floor(seconds/86400)
|
||||
seconds = seconds - days*86400
|
||||
local hours = math.floor(seconds/3600)
|
||||
seconds = seconds - hours*3600
|
||||
local minutes = math.floor(seconds/60)
|
||||
seconds = seconds - minutes*60
|
||||
|
||||
local ret = {}
|
||||
if days == 1 then
|
||||
table.insert(ret, S("1 day"))
|
||||
elseif days > 1 then
|
||||
table.insert(ret, S("@1 days", days))
|
||||
end
|
||||
if hours == 1 then
|
||||
table.insert(ret, S("1 hour"))
|
||||
elseif hours > 1 then
|
||||
table.insert(ret, S("@1 hours", hours))
|
||||
end
|
||||
if minutes == 1 then
|
||||
table.insert(ret, S("1 minute"))
|
||||
elseif minutes > 1 then
|
||||
table.insert(ret, S("@1 minutes", minutes))
|
||||
end
|
||||
if seconds == 1 then
|
||||
table.insert(ret, S("1 second"))
|
||||
elseif seconds > 1 then
|
||||
table.insert(ret, S("@1 seconds", seconds))
|
||||
end
|
||||
|
||||
if #ret == 0 then
|
||||
return S("@1 seconds", 0)
|
||||
end
|
||||
if #ret == 1 then
|
||||
return ret[1]
|
||||
end
|
||||
if round or #ret == 2 then
|
||||
return S("@1 and @2", ret[1], ret[2])
|
||||
end
|
||||
|
||||
return table.concat(ret, S(", "))
|
||||
end
|
||||
|
||||
local documentation = S("This does nothing in its current inert state. If you have this in your inventory when you die, however, it will follow you into your next life's inventory and point toward the location of your previous life's end.")
|
||||
local durationdesc
|
||||
if duration > 0 then
|
||||
durationdesc = S("The Death Compass' guidance will only last for @1 after death.", clock_string(duration, false))
|
||||
else
|
||||
durationdesc = S("The Death Compass will point toward your corpse until you find it.")
|
||||
end
|
||||
|
||||
-- set a position to the compass stack
|
||||
function set_target(stack, pos, name)
|
||||
local function set_target(stack, pos, name)
|
||||
local meta=stack:get_meta()
|
||||
meta:set_string("target_pos", minetest.pos_to_string(pos))
|
||||
meta:set_string("target_corpse", name)
|
||||
@ -41,6 +100,55 @@ local function stop_ticking(player_name)
|
||||
end
|
||||
end
|
||||
|
||||
local player_huds = {}
|
||||
local function hide_hud(player, player_name)
|
||||
local id = player_huds[player_name]
|
||||
if id then
|
||||
player:hud_remove(id)
|
||||
player_huds[player_name] = nil
|
||||
end
|
||||
end
|
||||
local function update_hud(player, player_name, compass)
|
||||
local metadata = compass:get_meta()
|
||||
|
||||
local target_pos = minetest.string_to_pos(metadata:get_string("target_pos"))
|
||||
local player_pos = player:get_pos()
|
||||
local distance = vector.distance(player_pos, target_pos)
|
||||
if not target_pos then
|
||||
return
|
||||
end
|
||||
|
||||
local time_of_death = metadata:get_int("time_of_death")
|
||||
local target_name = metadata:get_string("target_corpse")
|
||||
|
||||
local description
|
||||
if duration > 0 then
|
||||
local remaining = time_of_death + duration - minetest.get_gametime()
|
||||
if remaining < 0 then
|
||||
return
|
||||
end
|
||||
description = S("@1m to @2's corpse, @3 remaining", math.floor(distance),
|
||||
target_name, clock_string(remaining, true))
|
||||
else
|
||||
description = S("@1m to @2's corpse, died @3 ago", math.floor(distance),
|
||||
target_name, clock_string(minetest.get_gametime() - time_of_death, true))
|
||||
end
|
||||
|
||||
local id = player_huds[player_name]
|
||||
if not id then
|
||||
id = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = hud_position,
|
||||
text = description,
|
||||
number = hud_color,
|
||||
scale = 20,
|
||||
})
|
||||
player_huds[player_name] = id
|
||||
else
|
||||
player:hud_change(id, "text", description)
|
||||
end
|
||||
end
|
||||
|
||||
-- get right image number for players compass
|
||||
local function get_compass_stack(player, stack)
|
||||
local target = get_destination(player, stack)
|
||||
@ -55,10 +163,10 @@ local function get_compass_stack(player, stack)
|
||||
return inactive_return
|
||||
end
|
||||
local pos = player:get_pos()
|
||||
local dist = vector.distance(pos, target)
|
||||
local distance = vector.distance(pos, target)
|
||||
local player_name = player:get_player_name()
|
||||
|
||||
if dist < range_to_inactivate then
|
||||
if distance < range_to_inactivate then
|
||||
stop_ticking(player_name)
|
||||
minetest.sound_play("death_compass_bone_crunch", {to_player=player_name, gain = 1.0})
|
||||
return inactive_return
|
||||
@ -77,6 +185,7 @@ local function get_compass_stack(player, stack)
|
||||
local metadata = stack:get_meta():to_table()
|
||||
local meta_fields = metadata.fields
|
||||
local time_of_death = tonumber(meta_fields.time_of_death)
|
||||
|
||||
if duration > 0 then
|
||||
local remaining = time_of_death + duration - minetest.get_gametime()
|
||||
if remaining < 0 then
|
||||
@ -85,13 +194,8 @@ local function get_compass_stack(player, stack)
|
||||
return inactive_return
|
||||
end
|
||||
start_ticking(player_name)
|
||||
meta_fields.description = S("@1m to @2's corpse, @3s remaining",
|
||||
math.floor(dist), meta_fields.target_corpse, remaining)
|
||||
else
|
||||
meta_fields.description = S("@1m to @2's corpse, died @3s ago",
|
||||
math.floor(dist), meta_fields.target_corpse, minetest.get_gametime() - time_of_death)
|
||||
end
|
||||
|
||||
|
||||
local newstack = ItemStack("death_compass:dir"..compass_image)
|
||||
if metadata then
|
||||
newstack:get_meta():from_table(metadata)
|
||||
@ -99,23 +203,34 @@ local function get_compass_stack(player, stack)
|
||||
return newstack
|
||||
end
|
||||
|
||||
-- update inventory
|
||||
-- update inventory and hud
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for i,player in ipairs(minetest.get_connected_players()) do
|
||||
for i, player in ipairs(minetest.get_connected_players()) do
|
||||
local player_name = player:get_player_name()
|
||||
if player:get_inventory() then
|
||||
for i,stack in ipairs(player:get_inventory():get_list("main")) do
|
||||
local compass_in_quickbar
|
||||
local inv = player:get_inventory()
|
||||
if inv then
|
||||
for i, stack in ipairs(inv:get_list("main")) do
|
||||
if i > 8 then
|
||||
break
|
||||
end
|
||||
if string.sub(stack:get_name(), 0, 17) == "death_compass:dir" then
|
||||
player:get_inventory():set_stack("main", i, get_compass_stack(player, stack))
|
||||
player_name = nil -- don't stop the sound playing
|
||||
compass_in_quickbar = true
|
||||
end
|
||||
end
|
||||
if compass_in_quickbar then
|
||||
local wielded = player:get_wielded_item()
|
||||
if string.sub(wielded:get_name(), 0, 17) == "death_compass:dir" then
|
||||
update_hud(player, player_name, wielded)
|
||||
else
|
||||
hide_hud(player, player_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
if player_name then
|
||||
if not compass_in_quickbar then
|
||||
stop_ticking(player_name)
|
||||
hide_hud(player, player_name)
|
||||
end
|
||||
end
|
||||
end)
|
||||
@ -123,23 +238,30 @@ end)
|
||||
-- register items
|
||||
for i = 0, 15 do
|
||||
local image = "death_compass_16_"..i..".png"
|
||||
local groups = {death_compass = 1, not_in_creative_inventory = 1}
|
||||
minetest.register_tool("death_compass:dir"..i, {
|
||||
minetest.register_craftitem("death_compass:dir"..i, {
|
||||
description = S("Death Compass"),
|
||||
inventory_image = image,
|
||||
wield_image = image,
|
||||
stack_max = 1,
|
||||
groups = groups,
|
||||
groups = {death_compass = 1, not_in_creative_inventory = 1},
|
||||
})
|
||||
end
|
||||
|
||||
if not automatic then
|
||||
minetest.register_tool("death_compass:inactive", {
|
||||
description = S("Inactive Death Compass"),
|
||||
local display_doc = function(itemstack, user)
|
||||
local player_name = user:get_player_name()
|
||||
minetest.chat_send_player(player_name, documentation .. "\n" .. durationdesc)
|
||||
end
|
||||
|
||||
minetest.register_craftitem("death_compass:inactive", {
|
||||
description = S("Death Compass"),
|
||||
_doc_items_longdesc = documentation,
|
||||
_doc_items_usagehelp = durationdesc,
|
||||
inventory_image = "death_compass_inactive.png",
|
||||
wield_image = "death_compass_inactive.png",
|
||||
stack_max = 1,
|
||||
groups = {death_compass = 1},
|
||||
on_place = display_doc,
|
||||
on_secondary_use = display_doc,
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
@ -150,6 +272,16 @@ if not automatic then
|
||||
{'', 'bones:bones', ''}
|
||||
}
|
||||
})
|
||||
|
||||
-- Allow a player to deliberately deactivate a death compass
|
||||
minetest.register_craft({
|
||||
output = 'death_compass:inactive',
|
||||
type = "shapeless",
|
||||
recipe = {
|
||||
'group:death_compass',
|
||||
}
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
local player_death_location = {}
|
||||
@ -162,7 +294,7 @@ minetest.register_on_dieplayer(function(player, reason)
|
||||
count = 1
|
||||
else
|
||||
for i, itemstack in pairs(list) do
|
||||
if minetest.get_item_group(itemstack:get_name(), "death_compass") > 0 then
|
||||
if itemstack:get_name() == "death_compass:inactive" then
|
||||
count = count + itemstack:get_count()
|
||||
list[i] = ItemStack("")
|
||||
end
|
||||
@ -177,6 +309,14 @@ end)
|
||||
-- Called when a player dies
|
||||
-- `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange
|
||||
|
||||
-- Using the regular minetest.register_on_dieplayer causes the new callback to be inserted *after*
|
||||
-- the on_dieplayer used by the bones mod, which means the bones mod clears the player inventory before
|
||||
-- we get to this and we can't tell if there was a death compass in it.
|
||||
-- We must therefore rearrange the callback table to move this mod's callback to the front
|
||||
-- to ensure it always goes first.
|
||||
local death_compass_dieplayer_callback = table.remove(minetest.registered_on_dieplayers)
|
||||
table.insert(minetest.registered_on_dieplayers, 1, death_compass_dieplayer_callback)
|
||||
|
||||
minetest.register_on_respawnplayer(function(player)
|
||||
local player_name = player:get_player_name()
|
||||
local compasses = player_death_location[player_name]
|
||||
@ -186,7 +326,7 @@ minetest.register_on_respawnplayer(function(player)
|
||||
-- Remove any death compasses they might still have for some reason
|
||||
local current = inv:get_list("main")
|
||||
for i, item in pairs(current) do
|
||||
if minetest.get_item_group(item:get_name(), "death_compass") > 0 then
|
||||
if item:get_name() == "death_compass:inactive" then
|
||||
current[i] = ItemStack("")
|
||||
end
|
||||
end
|
||||
@ -203,4 +343,8 @@ minetest.register_on_respawnplayer(function(player)
|
||||
end)
|
||||
-- * Called when player is to be respawned
|
||||
-- * Called _before_ repositioning of player occurs
|
||||
-- * return true in func to disable regular player placement
|
||||
-- * return true in func to disable regular player placement
|
||||
|
||||
minetest.register_on_leaveplayer(function(player, timed_out)
|
||||
hide_hud(player, player:get_player_name())
|
||||
end)
|
30
locale/death_compass.ru.tr
Normal file
30
locale/death_compass.ru.tr
Normal file
@ -0,0 +1,30 @@
|
||||
# textdomain: death_compass
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
# String used for concatenating the "days, hours, minutes" strings together into a duration description.
|
||||
, =,
|
||||
1 day=1 день
|
||||
1 hour=1 час
|
||||
1 minute=1 минута
|
||||
1 second=1 секунда
|
||||
# string used when there's just two elements of the "hours and minutes" duration description
|
||||
@1 and @2=@1 и @2
|
||||
@1 days=@1 дней
|
||||
@1 hours=@1 часов
|
||||
@1 minutes=@1 минут
|
||||
@1 seconds=@1 секунд
|
||||
# @1 is a numeric distance in meters, @2 is the name of a player, @3 is a duration
|
||||
@1m to @2's corpse, @3 remaining=@1 м до костей @2, @3 осталось
|
||||
# @1 is a numeric distance in meters, @2 is the name of a player, @3 is a duration
|
||||
@1m to @2's corpse, died @3 ago=@1 м до костей @2, умер @3 назад
|
||||
Death Compass=Компас смерти
|
||||
|
||||
The Death Compass will point toward your corpse until you find it.=Компас смерти будет указывать на кости до тех пор пока вы их не найдете.
|
||||
|
||||
# @1 is a duration
|
||||
The Death Compass' guidance will only last for @1 after death.=Руководство по компасу смерти будет действовать @1 после смерти
|
||||
|
||||
This does nothing in its current inert state. If you have this in your inventory when you die, however, it will follow you into your next life's inventory and point toward the location of your previous life's end.=Он ничего не делает в текущем состоянии. Если вы имели его в своем инвенторе во время смерти, то он будет в вашем инвенторе после вашего возрождения и будет указывать вам на ваше место смерти.
|
||||
|
30
locale/template.txt
Normal file
30
locale/template.txt
Normal file
@ -0,0 +1,30 @@
|
||||
# textdomain: death_compass
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
# String used for concatenating the "days, hours, minutes" strings together into a duration description.
|
||||
, =
|
||||
1 day=
|
||||
1 hour=
|
||||
1 minute=
|
||||
1 second=
|
||||
# string used when there's just two elements of the "hours and minutes" duration description
|
||||
@1 and @2=
|
||||
@1 days=
|
||||
@1 hours=
|
||||
@1 minutes=
|
||||
@1 seconds=
|
||||
# @1 is a numeric distance in meters, @2 is the name of a player, @3 is a duration
|
||||
@1m to @2's corpse, @3 remaining=
|
||||
# @1 is a numeric distance in meters, @2 is the name of a player, @3 is a duration
|
||||
@1m to @2's corpse, died @3 ago=
|
||||
Death Compass=
|
||||
|
||||
The Death Compass will point toward your corpse until you find it.=
|
||||
|
||||
# @1 is a duration
|
||||
The Death Compass' guidance will only last for @1 after death.=
|
||||
|
||||
This does nothing in its current inert state. If you have this in your inventory when you die, however, it will follow you into your next life's inventory and point toward the location of your previous life's end.=
|
||||
|
3
mod.conf
3
mod.conf
@ -1,2 +1,3 @@
|
||||
name = death_compass
|
||||
description = A compass that points to the last place you died
|
||||
description = A compass that points to the last place you died
|
||||
optional_depends = bones
|
BIN
screenshot.jpg
Normal file
BIN
screenshot.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
@ -4,4 +4,9 @@ death_compass_duration (Death compass duration) int 0
|
||||
|
||||
# When a player dies they'll be given a death compass automatically, and it will
|
||||
# disappear when its duration expires or the player reaches the death location
|
||||
death_compass_automatic (Death compass given to players automatically) bool false
|
||||
death_compass_automatic (Death compass given to players automatically) bool false
|
||||
|
||||
[HUD properties]
|
||||
death_compass_hud_x (Relative X coordinate of HUD) float 0.5
|
||||
death_compass_hud_y (Relative Y coordinate of HUD) float 0.9
|
||||
death_compass_hud_color (Color string for HUD) string FFFF00
|
Loading…
x
Reference in New Issue
Block a user