This commit is contained in:
Milan2018 2024-01-24 18:04:01 +01:00
parent e90004322f
commit 8aa6e23a91
79 changed files with 6708 additions and 251 deletions

View file

@ -0,0 +1,10 @@
1.0.2
Added two larger outdoor decorated Christmas trees. One version has snow.
1.0.1
Added Christmas wreath model and UV
Reduced image sizes and removed high res images
Fixed collision boxes and groups. Can't walk through trees now.
1.0
initial release.

68
christmastree/README.txt Normal file
View file

@ -0,0 +1,68 @@
Minetest mod "christmastree"
===================
by ExeterDad (c) 2014
Version: 1.0.1
About
~~~~~
This mod adds a decorated Christmas trees and a wreath to Minetest. UV mapped textures
have greenery, baubles candy canes, star topper, tree skirt, and wrapped gifts.
The Christmas trees and wreath emit light at level 12 so not to be dark inside a building.
A Christmas wreath is included.
Larger 5 block high Christmas trees, with and without snow.
UV textures are 256x256
To craft a indoor Christmas tree:
0 + 0
0 X 0
X X X
0 = Blank
+ = default:gold_ingot
X = default:sapling
To craft a outdoor Christmas tree:
0 + 0
0 X 0
X X X
0 = Blank
+ = default:gold_ingot
X = (2)default:sapling
To craft a outdoor Christmas tree with snow:
0 + 0
o X o
X X X
0 = Blank
+ = default:gold_ingot
o = default:snow
X = (2)default:sapling
To craft a Christmas wreath:
0 X 0
X 0 X
0 X 0
0 = Blank
X = default:sapling
License of source code, obj model, textures: WTFPL
---------------------------------------
(c) Copyright ExeterDad (2014)
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

View file

@ -0,0 +1 @@
default

107
christmastree/init.lua Normal file
View file

@ -0,0 +1,107 @@
local treebox = {
type ="fixed",
fixed = { { -0.8, -0.5, -0.8, 0.8, 2.5, 0.8 } },
}
local wreathbox = {
type ="fixed",
fixed = { { -0.5, -0.5, 0.33, 0.5, 0.5, 0.5 } },
}
local outdoortreebox = {
type ="fixed",
fixed = { { -1.448, -0.5, -1.448, 1.448, 4.5, 1.448 } },
}
minetest.register_node("christmastree:indoortree", {
description = "Indoor decorated Christmas Tree",
drawtype = "mesh",
paramtype = "light",
light_source = 12,
paramtype2 = "facedir",
mesh = "indoor-christmas-tree.obj",
tiles = { "indoor-tree_UV256.png" },
groups = {choppy=2, oddly_breakable_by_hand=2, flammable=3},
sounds = default.node_sound_wood_defaults(),
selection_box = treebox,
collision_box = treebox,
})
minetest.register_node("christmastree:outdoortree", {
description = "Outdoor decorated Christmas Tree",
drawtype = "mesh",
paramtype = "light",
light_source = 12,
paramtype2 = "facedir",
mesh = "outdoor-christmas-tree.obj",
tiles = { "christmastree_outdoor256.png" },
groups = {choppy=2, oddly_breakable_by_hand=2, flammable=3},
sounds = default.node_sound_wood_defaults(),
selection_box = outdoortreebox,
collision_box = outdoortreebox,
})
minetest.register_node("christmastree:outdoortree_snow", {
description = "Outdoor decorated Christmas Tree with snow",
drawtype = "mesh",
paramtype = "light",
light_source = 12,
paramtype2 = "facedir",
mesh = "outdoor-christmas-tree.obj",
tiles = { "christmastree_outdoor_snow256.png" },
groups = {choppy=2, oddly_breakable_by_hand=2, flammable=3},
sounds = default.node_sound_wood_defaults(),
selection_box = outdoortreebox,
collision_box = outdoortreebox,
})
minetest.register_node("christmastree:christmas_wreath", {
description = "Christmas Wreath",
drawtype = "mesh",
paramtype = "light",
light_source = 12,
paramtype2 = "facedir",
mesh = "christmas-wreath.obj",
tiles = { "wreath_UV256.png" },
groups = {choppy=2, oddly_breakable_by_hand=2, flammable=3},
sounds = default.node_sound_wood_defaults(),
selection_box = wreathbox,
collision_box = wreathbox,
})
minetest.register_craft({
output = "christmastree:indoortree",
recipe = {
{ "","default:gold_ingot","" },
{ "","default:sapling","" },
{ "default:sapling","default:sapling","default:sapling" },
},
})
minetest.register_craft({
output = "christmastree:outdoortree",
recipe = {
{ "","default:gold_ingot","" },
{ "","default:sapling 2","" },
{ "default:sapling 2","default:sapling 2","default:sapling 2" },
},
})
minetest.register_craft({
output = "christmastree:outdoortree_snow",
recipe = {
{ "","default:gold_ingot","" },
{ "default:snow","default:sapling 2","default:snow" },
{ "default:sapling 2","default:sapling 2","default:sapling 2" },
},
})
minetest.register_craft({
output = "christmastree:christmas_wreath",
recipe = {
{ "","default:sapling","" },
{ "default:sapling","","default:sapling" },
{ "","default:sapling","" },
},
})

5
christmastree/mod.conf Normal file
View file

@ -0,0 +1,5 @@
release = 8099
author = ExeterDad
name = christmastree
description = Adds a decorated indoor Christmas tree with wrapped gifts.
title = Christmas Tree

View file

@ -0,0 +1,827 @@
# Blender v2.72 (sub 0) OBJ File: 'christmas_wreath.blend'
# www.blender.org
v -0.014074 0.350084 0.370711
v -0.002834 0.325585 0.372605
v 0.019802 0.325585 0.373064
v 0.039062 0.350084 0.371876
v 0.019832 0.374582 0.370366
v -0.002758 0.374582 0.369119
v 0.239043 0.350084 0.359450
v 0.227748 0.280124 0.362253
v 0.205157 0.254010 0.366836
v 0.193862 0.350084 0.368161
v 0.205157 0.446157 0.365737
v 0.227748 0.420043 0.361280
v 0.239043 0.350084 0.387609
v 0.227748 0.280124 0.384806
v 0.205157 0.254010 0.380223
v 0.193862 0.350084 0.377865
v 0.205157 0.446157 0.381322
v 0.227748 0.420043 0.385779
v -0.014036 0.350084 0.378681
v 0.019832 0.374582 0.376709
v -0.002758 0.374582 0.378137
v 0.123291 0.257205 0.380716
v 0.124349 0.350084 0.373108
v 0.123291 0.442962 0.383917
v 0.121175 0.442962 0.380141
v 0.121175 0.257205 0.378342
v 0.118981 0.258217 0.370002
v 0.120562 0.441950 0.363243
v 0.118981 0.441950 0.366974
v 0.120562 0.258217 0.367495
v -0.036266 0.339779 0.372179
v -0.025049 0.315280 0.374348
v -0.036185 0.339779 0.378839
v -0.024902 0.315280 0.378658
v -0.099209 0.262727 0.384096
v -0.029312 0.259746 0.388242
v -0.099506 0.262727 0.389415
v -0.029567 0.259746 0.400862
v -0.128154 0.104509 0.377590
v -0.052302 0.099821 0.402511
v -0.130728 0.104509 0.385644
v -0.054483 0.099821 0.409334
v -0.155930 -0.053713 0.367841
v -0.065178 -0.059321 0.397657
v -0.159010 -0.053713 0.377477
v -0.067787 -0.059321 0.405821
v -0.173854 -0.211003 0.355902
v -0.070674 -0.217073 0.364260
v -0.173068 -0.211003 0.367003
v -0.070008 -0.217073 0.373672
v -0.193260 -0.364070 0.357167
v -0.089956 -0.360737 0.350581
v -0.193366 -0.364424 0.368088
v -0.090090 -0.360359 0.359834
v -0.185556 -0.501254 0.420498
v -0.087828 -0.467472 0.413913
v -0.184829 -0.500954 0.431419
v -0.087212 -0.467218 0.423165
v -0.089329 -0.461048 0.423138
v -0.186946 -0.494784 0.431392
v -0.187673 -0.495084 0.420471
v -0.089945 -0.461302 0.413885
v -0.478061 0.042207 0.499029
v -0.458491 0.042207 0.425992
v -0.405024 0.042207 0.372525
v -0.258950 0.042207 0.400326
v -0.205483 0.042207 0.426935
v -0.185913 0.042207 0.478185
v -0.386760 0.324364 0.499029
v -0.370927 0.312813 0.425992
v -0.327671 0.281256 0.372525
v -0.209495 0.195042 0.400326
v -0.166239 0.163485 0.426935
v -0.150406 0.151935 0.478185
v -0.147729 0.498746 0.499029
v -0.141682 0.480057 0.425992
v -0.125159 0.428997 0.372525
v -0.080020 0.289499 0.400326
v -0.063498 0.238439 0.426935
v -0.057450 0.219750 0.478185
v 0.147729 0.498746 0.499029
v 0.141681 0.480057 0.425992
v 0.125159 0.428997 0.372525
v 0.080020 0.289499 0.400326
v 0.063498 0.238439 0.426935
v 0.057450 0.219750 0.478185
v 0.386760 0.324363 0.499029
v 0.370927 0.312813 0.425992
v 0.327671 0.281256 0.372525
v 0.209495 0.195042 0.400326
v 0.166239 0.163485 0.426935
v 0.150406 0.151935 0.478185
v 0.478061 0.042207 0.499029
v 0.458491 0.042207 0.425992
v 0.405024 0.042207 0.372525
v 0.258950 0.042207 0.400326
v 0.205483 0.042207 0.426935
v 0.185913 0.042207 0.478185
v 0.386760 -0.239950 0.499029
v 0.370927 -0.228399 0.425992
v 0.327671 -0.196842 0.372525
v 0.209495 -0.110628 0.400326
v 0.166239 -0.079071 0.426935
v 0.150406 -0.067521 0.478185
v 0.147729 -0.414332 0.499029
v 0.141681 -0.395643 0.425992
v 0.125159 -0.344583 0.372525
v 0.080020 -0.205085 0.400326
v 0.063498 -0.154025 0.426935
v 0.057450 -0.135336 0.478185
v -0.147729 -0.414332 0.499029
v -0.141682 -0.395643 0.425992
v -0.125159 -0.344583 0.372525
v -0.080020 -0.205085 0.400326
v -0.063498 -0.154025 0.426935
v -0.057450 -0.135336 0.478185
v -0.386759 -0.239950 0.499029
v -0.370927 -0.228399 0.425992
v -0.327671 -0.196843 0.372525
v -0.209495 -0.110628 0.400326
v -0.166239 -0.079071 0.426935
v -0.150406 -0.067521 0.478185
v -0.000000 0.295699 0.384408
v -0.031591 0.304008 0.382767
v -0.031591 0.320625 0.379486
v -0.000000 0.328934 0.377845
v 0.031591 0.320625 0.379486
v 0.031591 0.304008 0.382767
v -0.000000 0.404468 0.384408
v -0.031591 0.396159 0.382767
v -0.031591 0.379542 0.379486
v -0.000000 0.371233 0.377845
v 0.031591 0.379542 0.379486
v 0.031591 0.396159 0.382767
v 0.000000 0.404468 0.362930
v -0.031591 0.396159 0.364570
v -0.031591 0.379542 0.367852
v 0.000000 0.371233 0.369493
v 0.031591 0.379542 0.367852
v 0.031591 0.396159 0.364570
v -0.000000 0.304166 0.366121
v -0.031591 0.312474 0.367762
v -0.031591 0.320625 0.367852
v -0.000000 0.328934 0.369493
v 0.031591 0.320625 0.367852
v 0.031591 0.312474 0.367762
v 0.008014 0.355736 0.383473
v -0.000028 0.329754 0.378384
v -0.021084 0.327725 0.377224
v -0.039724 0.350399 0.377497
v -0.022648 0.374483 0.379205
v -0.002631 0.376740 0.382084
v -0.244600 0.334099 0.381311
v -0.227239 0.265383 0.379068
v -0.202300 0.241331 0.375602
v -0.199190 0.338030 0.374362
v -0.218790 0.432763 0.376700
v -0.239255 0.404781 0.380040
v -0.243219 0.334219 0.353186
v -0.226133 0.265479 0.356541
v -0.201644 0.241388 0.362231
v -0.198710 0.338072 0.364583
v -0.218026 0.432830 0.361134
v -0.238053 0.404885 0.355570
v 0.008140 0.355978 0.375758
v 0.000325 0.329785 0.373066
v -0.020973 0.327731 0.373132
v -0.039701 0.350399 0.373342
v -0.022573 0.374492 0.372993
v -0.002480 0.376850 0.373303
v -0.121279 0.251552 0.366333
v -0.136160 0.436709 0.362571
v -0.134240 0.436876 0.366447
v -0.119373 0.251717 0.368919
v -0.117725 0.252875 0.377419
v -0.134372 0.435848 0.383355
v -0.132616 0.436000 0.379706
v -0.119358 0.252734 0.379739
v 0.032560 0.345623 0.382119
v 0.022723 0.319938 0.376727
v 0.032755 0.345719 0.375716
v 0.023193 0.319919 0.372577
v 0.090470 0.262727 0.387950
v 0.022828 0.259746 0.369541
v 0.091833 0.262727 0.382762
v 0.023993 0.259746 0.365146
v 0.098859 0.104509 0.397698
v 0.023537 0.099821 0.370940
v 0.101527 0.104509 0.389736
v 0.025867 0.099821 0.364262
v 0.147224 -0.053713 0.396329
v 0.053068 -0.059321 0.379551
v 0.148885 -0.053713 0.386368
v 0.054530 -0.059321 0.371106
v 0.200195 -0.211264 0.373591
v 0.096757 -0.217355 0.372983
v 0.200200 -0.211583 0.362793
v 0.096773 -0.217624 0.363837
v 0.235612 -0.370766 0.348349
v 0.132307 -0.365155 0.349706
v 0.235247 -0.374659 0.338139
v 0.131954 -0.368437 0.341068
v 0.232748 -0.492415 0.395455
v 0.132722 -0.465608 0.397787
v 0.231038 -0.497801 0.386043
v 0.131328 -0.469941 0.389815
v 0.132718 -0.464478 0.386542
v 0.232428 -0.492339 0.382770
v 0.234124 -0.486942 0.392192
v 0.134101 -0.460139 0.394521
vt 0.630783 0.463337
vt 0.664081 0.440474
vt 0.660184 0.452608
vt 0.630283 0.463561
vt 0.604018 0.401001
vt 0.632748 0.387180
vt 0.644100 0.392830
vt 0.604499 0.400779
vt 0.913308 0.919672
vt 0.902899 0.919672
vt 0.903765 0.894055
vt 0.912102 0.894055
vt 0.905182 0.884814
vt 0.910130 0.884814
vt 0.973239 0.452597
vt 0.968344 0.452327
vt 0.968380 0.421463
vt 0.971929 0.421658
vt 0.967540 0.387180
vt 0.973239 0.387494
vt 0.910641 0.955715
vt 0.904880 0.955715
vt 0.903482 0.945684
vt 0.912538 0.945684
vt 0.242518 0.891615
vt 0.217324 0.854009
vt 0.217311 0.845658
vt 0.242501 0.892455
vt 0.452558 0.752973
vt 0.419275 0.711573
vt 0.418938 0.701608
vt 0.465948 0.721515
vt 0.511346 0.712543
vt 0.478969 0.689725
vt 0.478203 0.689424
vt 0.507289 0.700409
vt 0.479927 0.765836
vt 0.451765 0.752674
vt 0.491425 0.760186
vt 0.505276 0.737985
vt 0.571868 0.451409
vt 0.572202 0.441442
vt 0.657901 0.415031
vt 0.579222 0.458133
vt 0.066618 0.847241
vt 0.091340 0.892869
vt 0.091362 0.892326
vt 0.066615 0.855636
vt 0.023686 0.892292
vt 0.048551 0.855606
vt 0.057579 0.862754
vt 0.092857 0.924203
vt 0.057498 0.920050
vt 0.022100 0.924203
vt 0.023653 0.892815
vt 0.048547 0.847214
vt 0.337368 0.727613
vt 0.342606 0.692091
vt 0.344567 0.692018
vt 0.339803 0.727590
vt 0.573153 0.464833
vt 0.579841 0.484337
vt 0.555546 0.493378
vt 0.565803 0.458107
vt 0.909444 0.387180
vt 0.911035 0.387212
vt 0.916000 0.407719
vt 0.914279 0.407691
vt 0.414336 0.601197
vt 0.427099 0.543266
vt 0.459499 0.554430
vt 0.441417 0.610528
vt 0.921365 0.466405
vt 0.918817 0.466395
vt 0.595053 0.542079
vt 0.568164 0.551425
vt 0.337495 0.634321
vt 0.340560 0.633893
vt 0.613301 0.598128
vt 0.581131 0.609309
vt 0.331144 0.576742
vt 0.334811 0.576230
vt 0.919221 0.524865
vt 0.916173 0.524853
vt 0.906369 0.582804
vt 0.902899 0.582728
vt 0.597286 0.665463
vt 0.633423 0.652683
vt 0.647591 0.704354
vt 0.612985 0.720333
vt 0.444528 0.487701
vt 0.480610 0.500443
vt 0.323570 0.520963
vt 0.327600 0.521022
vt 0.509746 0.414513
vt 0.481475 0.388976
vt 0.483075 0.387180
vt 0.511346 0.412716
vt 0.331561 0.466167
vt 0.335556 0.465974
vt 0.911244 0.635425
vt 0.907821 0.635503
vt 0.460053 0.432495
vt 0.494479 0.448741
vt 0.854273 0.934191
vt 0.858296 0.934389
vt 0.858296 0.967730
vt 0.854887 0.967563
vt 0.634117 0.764093
vt 0.662479 0.738547
vt 0.664081 0.740344
vt 0.635719 0.765890
vt 0.344567 0.422693
vt 0.340593 0.422541
vt 0.340350 0.420772
vt 0.344324 0.420924
vt 0.919927 0.672844
vt 0.923341 0.672825
vt 0.923287 0.675138
vt 0.919873 0.675157
vt 0.426795 0.668592
vt 0.402491 0.659556
vt 0.323570 0.970242
vt 0.323570 0.939008
vt 0.324770 0.944714
vt 0.324770 0.964683
vt 0.473901 0.960501
vt 0.473901 0.938761
vt 0.475114 0.932631
vt 0.475114 0.966635
vt 0.975446 0.507877
vt 0.967540 0.507877
vt 0.967645 0.496928
vt 0.974343 0.496928
vt 0.330222 0.939416
vt 0.329022 0.944975
vt 0.972316 0.809978
vt 0.968022 0.809926
vt 0.968820 0.797844
vt 0.971903 0.797882
vt 0.968022 0.786933
vt 0.972316 0.786986
vt 0.469600 0.938755
vt 0.468388 0.932621
vt 0.974760 0.519960
vt 0.968062 0.519960
vt 0.915863 0.329379
vt 0.915863 0.289203
vt 0.927512 0.292258
vt 0.927512 0.326296
vt 0.330222 0.970650
vt 0.329022 0.964944
vt 0.623086 0.932580
vt 0.644779 0.932556
vt 0.641734 0.944293
vt 0.626160 0.944310
vt 0.644779 0.955967
vt 0.623086 0.955991
vt 0.469600 0.960495
vt 0.468388 0.966624
vt 0.904165 0.326308
vt 0.904165 0.292270
vt 0.975468 0.576382
vt 0.967540 0.576382
vt 0.968191 0.564291
vt 0.974908 0.564291
vt 0.968022 0.719488
vt 0.972303 0.719567
vt 0.972000 0.730511
vt 0.968926 0.730455
vt 0.972303 0.742602
vt 0.968022 0.742524
vt 0.975188 0.587282
vt 0.968471 0.587282
vt 0.029602 0.733011
vt 0.023344 0.695786
vt 0.046547 0.706027
vt 0.036988 0.726041
vt 0.245696 0.496712
vt 0.279371 0.474815
vt 0.275080 0.486813
vt 0.245159 0.496897
vt 0.220142 0.433886
vt 0.249249 0.420772
vt 0.260562 0.426726
vt 0.220707 0.433705
vt 0.967538 0.192508
vt 0.977900 0.192763
vt 0.977900 0.213498
vt 0.969601 0.213293
vt 0.181286 0.977834
vt 0.172955 0.977834
vt 0.174980 0.967090
vt 0.179925 0.967090
vt 0.957864 0.884814
vt 0.962814 0.884814
vt 0.962101 0.920440
vt 0.958480 0.920440
vt 0.963270 0.955740
vt 0.957507 0.955740
vt 0.970199 0.155370
vt 0.975934 0.155511
vt 0.977002 0.168014
vt 0.967987 0.167792
vt 0.556717 0.858165
vt 0.581542 0.810681
vt 0.581455 0.818608
vt 0.556692 0.857414
vt 0.625106 0.857076
vt 0.598660 0.817719
vt 0.598893 0.810221
vt 0.625144 0.857854
vt 0.971177 0.038208
vt 0.969212 0.038092
vt 0.969465 0.030574
vt 0.970976 0.030607
vt 0.969748 0.022166
vt 0.971282 0.022167
vt 0.273493 0.967090
vt 0.275016 0.967096
vt 0.275265 0.977789
vt 0.272986 0.977789
vt 0.120285 0.975807
vt 0.118000 0.975728
vt 0.117059 0.968534
vt 0.120285 0.968636
vt 0.069618 0.968534
vt 0.072860 0.968737
vt 0.072860 0.976683
vt 0.070014 0.976452
vt 0.110157 0.796931
vt 0.071099 0.790831
vt 0.036288 0.749100
vt 0.036113 0.740420
vt 0.042978 0.733031
vt 0.096090 0.726938
vt 0.128756 0.748729
vt 0.123339 0.774352
vt 0.095401 0.726655
vt 0.124462 0.736689
vt 0.626298 0.888225
vt 0.590897 0.884040
vt 0.555546 0.888225
vt 0.590354 0.824800
vt 0.098922 0.802883
vt 0.070391 0.790532
vt 0.447199 0.810751
vt 0.471701 0.857240
vt 0.471687 0.856666
vt 0.447272 0.818607
vt 0.430078 0.817673
vt 0.404040 0.856322
vt 0.404062 0.856923
vt 0.429899 0.810168
vt 0.967590 0.649438
vt 0.855832 0.039309
vt 0.853898 0.039351
vt 0.969929 0.649177
vt 0.187687 0.497737
vt 0.197569 0.517563
vt 0.174467 0.527959
vt 0.180170 0.490934
vt 0.827219 0.889860
vt 0.825675 0.889839
vt 0.822790 0.869411
vt 0.824462 0.869329
vt 0.822035 0.815463
vt 0.824606 0.815257
vt 0.223450 0.570626
vt 0.197431 0.581847
vt 0.859966 0.097801
vt 0.856993 0.097876
vt 0.239575 0.628271
vt 0.207280 0.642123
vt 0.045659 0.641641
vt 0.071823 0.652673
vt 0.859532 0.157036
vt 0.855831 0.157099
vt 0.822624 0.765850
vt 0.825793 0.765807
vt 0.817304 0.718221
vt 0.820649 0.718495
vt 0.215736 0.702603
vt 0.251001 0.687638
vt 0.263641 0.742023
vt 0.230354 0.760809
vt 0.087992 0.595135
vt 0.055588 0.581476
vt 0.064372 0.521001
vt 0.099676 0.535818
vt 0.851144 0.216178
vt 0.847153 0.216332
vt 0.127447 0.447823
vt 0.098897 0.422393
vt 0.100206 0.420772
vt 0.128756 0.446202
vt 0.842039 0.275739
vt 0.838285 0.277204
vt 0.806721 0.671787
vt 0.809867 0.673125
vt 0.079414 0.461458
vt 0.112666 0.480301
vt 0.810073 0.972489
vt 0.806721 0.972619
vt 0.807229 0.934298
vt 0.810073 0.934191
vt 0.249332 0.799690
vt 0.278057 0.774445
vt 0.279371 0.776079
vt 0.250645 0.801326
vt 0.855281 0.320421
vt 0.858723 0.318427
vt 0.859966 0.320411
vt 0.856528 0.322401
vt 0.825960 0.642099
vt 0.823109 0.640265
vt 0.824372 0.638277
vt 0.827219 0.640110
vt 0.713099 0.590929
vt 0.708280 0.481970
vt 0.736047 0.482672
vt 0.740669 0.587170
vt 0.753518 0.492220
vt 0.762522 0.578535
vt 0.591864 0.262426
vt 0.635044 0.221853
vt 0.682302 0.247535
vt 0.614763 0.310995
vt 0.510472 0.088702
vt 0.569197 0.081308
vt 0.565529 0.101181
vt 0.518929 0.107049
vt 0.922880 0.200806
vt 0.923339 0.244872
vt 0.904581 0.240305
vt 0.904165 0.200436
vt 0.713099 0.387180
vt 0.740669 0.391762
vt 0.757439 0.407044
vt 0.487458 0.039274
vt 0.574389 0.033706
vt 0.621035 0.109916
vt 0.606664 0.123883
vt 0.904639 0.840483
vt 0.902899 0.808440
vt 0.922248 0.805138
vt 0.923822 0.834129
vt 0.810010 0.593945
vt 0.806721 0.486275
vt 0.834309 0.486859
vt 0.837463 0.590121
vt 0.852007 0.489124
vt 0.853617 0.581474
vt 0.421457 0.099657
vt 0.467291 0.129275
vt 0.456149 0.187528
vt 0.402491 0.193847
vt 0.646187 0.163600
vt 0.626622 0.166482
vt 0.902899 0.762225
vt 0.922248 0.763324
vt 0.810010 0.387180
vt 0.837463 0.391820
vt 0.858700 0.405168
vt 0.617780 0.212707
vt 0.904639 0.719488
vt 0.923822 0.724658
vt 0.736116 0.716770
vt 0.735048 0.822478
vt 0.708524 0.810133
vt 0.709304 0.720715
vt 0.699730 0.257920
vt 0.623520 0.317793
vt 0.481300 0.241212
vt 0.441831 0.277814
vt 0.583516 0.244902
vt 0.327439 0.894677
vt 0.323570 0.859435
vt 0.343301 0.856794
vt 0.346802 0.888679
vt 0.736116 0.924997
vt 0.710842 0.912801
vt 0.520837 0.335232
vt 0.522912 0.322560
vt 0.695824 0.156421
vt 0.655308 0.073585
vt 0.533139 0.269820
vt 0.536916 0.250770
vt 0.323570 0.812556
vt 0.343301 0.814380
vt 0.751536 0.303608
vt 0.746032 0.229154
vt 0.773355 0.225412
vt 0.780172 0.289201
vt 0.427514 0.292195
vt 0.495781 0.228069
vt 0.327439 0.771945
vt 0.346802 0.777636
vt 0.743930 0.121653
vt 0.771339 0.122312
vt 0.792304 0.124512
vt 0.794086 0.215590
vt 0.475822 0.185469
vt 0.905579 0.111039
vt 0.904165 0.064759
vt 0.923733 0.065443
vt 0.925012 0.107315
vt 0.746032 0.022166
vt 0.773355 0.026897
vt 0.795624 0.037148
vt 0.484664 0.139244
vt 0.905579 0.022166
vt 0.925012 0.026906
vt 0.725714 0.672438
vt 0.752768 0.665343
vt 0.402710 0.094002
vt 0.479165 0.022166
vt 0.923339 0.155370
vt 0.904581 0.159327
vt 0.255750 0.220874
vt 0.358292 0.253857
vt 0.294085 0.342496
vt 0.230781 0.255345
vt 0.358292 0.144380
vt 0.255750 0.178300
vt 0.294085 0.055882
vt 0.230781 0.143884
vt 0.190379 0.130772
vt 0.190196 0.022166
vt 0.149978 0.143973
vt 0.086307 0.056110
vt 0.125008 0.178444
vt 0.022100 0.144749
vt 0.022100 0.254226
vt 0.125008 0.221018
vt 0.149978 0.255434
vt 0.086307 0.342725
vt 0.190379 0.268546
vt 0.190196 0.376441
s 1
f 30/1 8/2 9/3 27/4
f 29/5 11/6 12/7 28/8
f 7/9 13/10 14/11 8/12
f 8/12 14/11 15/13 9/14
f 9/15 15/16 16/17 10/18
f 10/18 16/17 17/19 11/20
f 11/21 17/22 18/23 12/24
f 12/24 18/23 13/10 7/9
f 25/25 20/26 21/27 24/28
f 24/29 21/30 19/31 23/32
f 14/33 22/34 26/35 15/36
f 17/37 25/38 24/29 18/39
f 18/39 24/29 23/32 13/40
f 1/41 6/42 28/8 12/7 7/43 8/2 30/1 2/44
f 2/45 30/46 27/47 3/48
f 29/49 5/50 4/51 3/48 27/47 9/52 10/53 11/54
f 5/50 29/49 28/55 6/56
f 31/57 35/58 37/59 33/60
f 32/61 36/62 35/63 31/64
f 32/65 34/66 38/67 36/68
f 41/69 45/70 46/71 42/72
f 38/67 42/73 40/74 36/68
f 36/62 40/75 39/76 35/63
f 35/58 39/77 41/78 37/59
f 39/76 40/75 44/79 43/80
f 39/77 43/81 45/82 41/78
f 42/73 46/83 44/84 40/74
f 44/84 46/83 50/85 48/86
f 47/87 48/88 52/89 51/90
f 46/71 45/70 49/91 50/92
f 43/80 44/79 48/88 47/87
f 45/82 43/81 47/93 49/94
f 59/95 60/96 57/97 58/98
f 49/94 47/93 51/99 53/100
f 48/86 50/85 54/101 52/102
f 50/92 49/91 53/103 54/104
f 57/105 55/106 56/107 58/108
f 61/109 62/110 56/111 55/112
f 60/113 61/114 55/115 57/116
f 62/117 59/118 58/119 56/120
f 54/104 53/103 60/96 59/95
f 51/90 52/89 62/110 61/109
f 53/100 51/99 61/114 60/113
f 52/102 54/101 59/118 62/117
f 38/121 37/122 41/69 42/72
f 124/123 130/124 131/125 125/126
f 127/127 133/128 134/129 128/130
f 129/131 135/132 136/133 130/134
f 130/124 136/135 137/136 131/125
f 131/137 137/138 138/139 132/140
f 132/140 138/139 139/141 133/142
f 133/128 139/143 140/144 134/129
f 134/145 140/146 135/132 129/131
f 135/147 141/148 142/149 136/150
f 136/135 142/151 143/152 137/136
f 137/153 143/154 144/155 138/156
f 138/156 144/155 145/157 139/158
f 139/143 145/159 146/160 140/144
f 140/161 146/162 141/148 135/147
f 141/163 123/164 124/165 142/166
f 142/151 124/123 125/126 143/152
f 143/167 125/168 126/169 144/170
f 144/170 126/169 127/171 145/172
f 145/159 127/127 128/130 146/160
f 146/173 128/174 123/164 141/163
f 181/175 185/176 186/177 182/178
f 178/179 154/180 155/181 175/182
f 177/183 157/184 158/185 176/186
f 153/187 159/188 160/189 154/190
f 154/191 160/192 161/193 155/194
f 155/195 161/196 162/197 156/198
f 156/198 162/197 163/199 157/200
f 157/201 163/202 164/203 158/204
f 158/204 164/203 159/188 153/187
f 171/205 166/206 167/207 174/208
f 173/209 169/210 170/211 172/212
f 166/213 148/214 149/215 167/216
f 167/216 149/215 150/217 168/218
f 168/219 150/220 151/221 169/222
f 169/223 151/224 152/225 170/226
f 170/227 152/228 147/229 165/230
f 164/231 172/232 170/233 165/234 166/235 171/236 160/237 159/238
f 160/237 171/236 174/239 161/240
f 173/209 163/241 162/242 161/243 174/208 167/207 168/244 169/210
f 163/245 173/246 172/232 164/231
f 148/247 178/248 175/249 149/250
f 151/251 177/252 176/253 152/254
f 179/255 183/256 185/257 181/258
f 180/259 184/260 183/261 179/262
f 180/263 182/264 186/265 184/266
f 186/265 190/267 188/268 184/266
f 184/260 188/269 187/270 183/261
f 183/256 187/271 189/272 185/257
f 187/270 188/269 192/273 191/274
f 186/177 185/176 189/275 190/276
f 187/271 191/277 193/278 189/272
f 190/267 194/279 192/280 188/268
f 192/280 194/279 198/281 196/282
f 195/283 196/284 200/285 199/286
f 194/287 193/288 197/289 198/290
f 191/274 192/273 196/284 195/283
f 193/278 191/277 195/291 197/292
f 207/293 208/294 205/295 206/296
f 197/292 195/291 199/297 201/298
f 196/282 198/281 202/299 200/300
f 198/290 197/289 201/301 202/302
f 205/303 203/304 204/305 206/306
f 209/307 210/308 204/309 203/310
f 208/311 209/312 203/313 205/314
f 210/315 207/316 206/317 204/318
f 202/302 201/301 208/294 207/293
f 199/286 200/285 210/308 209/307
f 201/298 199/297 209/312 208/311
f 200/300 202/299 207/316 210/315
f 189/275 193/288 194/287 190/276
f 63/319 69/320 70/321 64/322
f 64/322 70/321 71/323 65/324
f 96/325 90/326 89/327 95/328
f 66/329 72/330 73/331 67/332
f 67/333 73/334 74/335 68/336
f 69/320 75/337 76/338 70/321
f 70/321 76/338 77/339 71/323
f 72/330 66/329 65/340 71/341
f 72/330 78/342 79/343 73/331
f 73/344 79/345 80/346 74/347
f 75/348 81/349 82/350 76/351
f 76/351 82/350 83/352 77/353
f 119/354 120/355 114/356 113/357
f 78/342 84/358 85/359 79/343
f 79/345 85/360 86/361 80/346
f 81/349 87/362 88/363 82/350
f 82/350 88/363 89/364 83/352
f 84/358 90/326 91/365 85/359
f 85/360 91/366 92/367 86/361
f 87/368 93/369 94/370 88/371
f 88/372 94/373 95/328 89/327
f 113/357 114/356 108/374 107/375
f 90/326 96/325 97/376 91/365
f 91/377 97/378 98/379 92/380
f 93/369 99/381 100/382 94/370
f 94/373 100/383 101/384 95/328
f 83/385 84/358 78/342 77/386
f 96/325 102/387 103/388 97/376
f 97/378 103/389 104/390 98/379
f 99/391 105/392 106/393 100/394
f 100/383 106/395 107/375 101/384
f 90/326 84/358 83/385 89/327
f 102/387 108/374 109/396 103/388
f 103/389 109/397 110/398 104/390
f 105/392 111/399 112/400 106/393
f 106/393 112/400 113/401 107/402
f 102/387 96/325 95/328 101/384
f 108/374 114/356 115/403 109/396
f 109/404 115/405 116/406 110/407
f 111/399 117/408 118/409 112/400
f 112/400 118/409 119/410 113/401
f 66/329 120/355 119/354 65/340
f 114/356 120/355 121/411 115/403
f 115/405 121/412 122/413 116/406
f 78/342 72/330 71/341 77/386
f 117/414 63/319 64/322 118/415
f 118/416 64/417 65/340 119/354
f 108/374 102/387 101/384 107/375
f 120/355 66/329 67/332 121/411
f 121/418 67/333 68/336 122/419
f 80/420 75/421 69/422 74/423
f 81/424 75/421 80/420 86/425
f 87/426 81/424 86/425 92/427
f 98/428 93/429 87/426 92/427
f 104/430 99/431 93/429 98/428
f 110/432 105/433 99/431 104/430
f 111/434 105/433 110/432 116/435
f 122/436 117/437 111/434 116/435
f 122/436 68/438 63/439 117/437
f 74/423 69/422 63/439 68/438

View file

@ -0,0 +1,875 @@
# Blender v2.71 (sub 0) OBJ File: 'indoor-christmas-tree.blend'
# www.blender.org
o Cube.001_Cube.002
v 0.063161 -0.481361 -0.357331
v 0.062869 -0.360156 -0.091198
v -0.229477 -0.353768 -0.094429
v -0.229184 -0.474972 -0.360562
v 0.065311 -0.401400 -0.393744
v 0.065018 -0.280196 -0.127612
v -0.227327 -0.273808 -0.130843
v -0.227034 -0.395012 -0.396976
v 0.364089 -0.494747 0.260180
v 0.364089 -0.494747 0.552614
v 0.071656 -0.494747 0.552614
v 0.071656 -0.494747 0.260180
v 0.364089 -0.202314 0.260180
v 0.364089 -0.202314 0.552614
v 0.071656 -0.202314 0.552614
v 0.071656 -0.202314 0.260180
v 0.423160 -0.385025 -0.035821
v 0.419655 -0.382416 0.110330
v 0.332759 -0.500011 0.110344
v 0.336265 -0.502620 -0.035807
v 0.188075 -0.211313 -0.044561
v 0.184569 -0.208704 0.101591
v 0.097674 -0.326299 0.101605
v 0.101180 -0.328907 -0.044546
v -0.308783 -0.451097 -0.373639
v -0.343665 -0.497940 -0.239593
v -0.469522 -0.423831 -0.246446
v -0.434641 -0.376987 -0.380492
v -0.177292 -0.217064 -0.257639
v -0.212174 -0.263907 -0.123593
v -0.338031 -0.189798 -0.130445
v -0.303150 -0.142955 -0.264491
v -0.256514 -0.494747 -0.143510
v -0.256514 -0.494747 0.148923
v -0.548947 -0.494747 0.148923
v -0.548947 -0.494747 -0.143510
v -0.256514 -0.278543 -0.143510
v -0.256514 -0.278543 0.148923
v -0.548947 -0.278543 0.148923
v -0.548947 -0.278543 -0.143510
v 0.016497 -0.176949 0.822118
v 0.016497 2.225140 0.005725
v -0.518721 -0.072056 0.537924
v 0.014362 2.225140 0.004841
v -0.802915 -0.176949 0.002707
v 0.013478 2.225140 0.002707
v -0.518721 -0.072056 -0.532510
v 0.014362 2.225140 0.000573
v 0.016497 -0.176949 -0.816705
v 0.016497 2.225140 -0.000311
v 0.551714 -0.072056 -0.532510
v 0.018631 2.225140 0.000573
v 0.835908 -0.176949 0.002707
v 0.019515 2.225140 0.002707
v 0.551714 -0.072056 0.537924
v 0.018631 2.225140 0.004841
v 0.016497 0.340839 0.614356
v 0.016497 0.803725 0.521561
v 0.016497 1.279110 0.372294
v 0.016497 1.606774 0.266892
v -0.170311 1.606774 0.189514
v -0.244842 1.279110 0.264045
v -0.350389 0.803725 0.369592
v -0.416005 0.340839 0.435208
v -0.247689 1.606774 0.002707
v -0.353091 1.279110 0.002707
v -0.502358 0.803725 0.002707
v -0.595153 0.340839 0.002707
v -0.170311 1.606774 -0.184100
v -0.244842 1.279110 -0.258631
v -0.350389 0.803725 -0.364179
v -0.416005 0.340839 -0.429795
v 0.016497 1.606774 -0.261479
v 0.016497 1.279110 -0.366881
v 0.016497 0.803725 -0.516148
v 0.016497 0.340839 -0.608943
v 0.203304 1.606774 -0.184100
v 0.277835 1.279110 -0.258631
v 0.383382 0.803725 -0.364179
v 0.448998 0.340839 -0.429795
v 0.280682 1.606774 0.002707
v 0.386084 1.279110 0.002707
v 0.535351 0.803725 0.002707
v 0.628146 0.340839 0.002707
v 0.203304 1.606774 0.189514
v 0.277835 1.279110 0.264045
v 0.383382 0.803725 0.369592
v 0.448998 0.340839 0.435208
v -0.029966 0.305894 0.679303
v -0.537002 0.232208 0.485071
v -0.660100 0.305893 -0.043756
v -0.465868 0.232208 -0.550792
v 0.062959 0.305893 -0.673890
v 0.569995 0.232208 -0.479657
v 0.693093 0.305893 0.049169
v 0.498861 0.232208 0.556205
v 0.058078 0.727034 0.629602
v -0.365814 0.762976 0.439337
v -0.610399 0.727034 0.044288
v -0.420134 0.762976 -0.379604
v -0.025085 0.727034 -0.624188
v 0.398807 0.762976 -0.433924
v 0.643392 0.727034 -0.038875
v 0.453127 0.762976 0.385017
v -0.017861 1.216826 0.462077
v -0.361450 1.218359 0.328052
v -0.442874 1.216826 -0.031651
v -0.308849 1.218359 -0.375240
v 0.050854 1.216826 -0.456664
v 0.394443 1.218359 -0.322639
v 0.475867 1.216826 0.037064
v 0.341842 1.218359 0.380653
v 0.048839 1.582472 0.335358
v -0.179657 1.554458 0.241111
v -0.316155 1.582472 0.035049
v -0.221907 1.554458 -0.193447
v -0.015846 1.582472 -0.329945
v 0.212650 1.554458 -0.235697
v 0.349148 1.582472 -0.029636
v 0.254901 1.554458 0.198860
v 0.016497 1.873582 0.138011
v -0.079178 1.873582 0.098382
v -0.118808 1.873582 0.002707
v -0.079178 1.873582 -0.092968
v 0.016497 1.873582 -0.132598
v 0.112171 1.873582 -0.092968
v 0.151801 1.873582 0.002707
v 0.112171 1.873582 0.098382
v -0.002514 1.851223 0.190368
v -0.141710 1.905141 0.131808
v -0.171165 1.851223 -0.016304
v -0.112605 1.905141 -0.155500
v 0.035507 1.851223 -0.184955
v 0.174703 1.905141 -0.126395
v 0.204158 1.851223 0.021717
v 0.145598 1.905141 0.160913
v 0.016497 0.030028 0.069771
v -0.030925 0.030028 0.050128
v -0.050567 0.030028 0.002707
v -0.030925 0.030028 -0.044715
v 0.016497 0.030028 -0.064357
v 0.063918 0.030028 -0.044715
v 0.083561 0.030028 0.002707
v 0.063918 0.030028 0.050128
v -0.253401 -0.489433 0.212336
v -0.322580 -0.489433 -0.039910
v -0.193133 -0.489433 -0.267191
v 0.059113 -0.489433 -0.336370
v 0.286394 -0.489433 -0.206923
v 0.355574 -0.489433 0.045323
v 0.226126 -0.489433 0.272604
v -0.026120 -0.489433 0.341784
v -0.031751 -0.332371 -0.045541
v 0.016497 -0.332371 -0.065526
v 0.064745 -0.332371 0.050955
v 0.016497 -0.332371 0.070940
v -0.051736 -0.332371 0.002707
v 0.084730 -0.332371 0.002707
v -0.031751 -0.332371 0.050955
v 0.064745 -0.332371 -0.045541
v 0.016497 2.498106 0.003866
v 0.015677 2.498106 0.003527
v 0.015337 2.498106 0.002707
v 0.015677 2.498106 0.001887
v 0.016497 2.498106 0.001547
v 0.017317 2.498106 0.001887
v 0.017656 2.498106 0.002707
v 0.017317 2.498106 0.003527
v 0.016497 2.367410 -0.021747
v 0.016497 2.344189 -0.111707
v 0.016497 2.320968 -0.021747
v -0.000795 2.320968 -0.014585
v -0.000795 2.344189 -0.014585
v -0.000795 2.367410 -0.014585
v -0.000795 2.367410 0.019998
v -0.000795 2.344189 0.019998
v -0.000795 2.320968 0.019998
v 0.016497 2.320968 0.027160
v 0.016497 2.344189 0.117120
v 0.016497 2.367410 0.027160
v 0.033788 2.367410 0.019998
v 0.033788 2.344189 0.019998
v 0.033788 2.320968 0.019998
v 0.040950 2.320968 0.002707
v 0.130910 2.344189 0.002707
v 0.040950 2.367410 0.002707
v -0.007957 2.367410 0.002707
v -0.097917 2.344189 0.002707
v -0.007957 2.320968 0.002707
v 0.033788 2.367410 -0.014585
v 0.033788 2.344189 -0.014585
v 0.033788 2.320968 -0.014585
v 0.110646 -0.438986 -0.174004
v -0.077653 -0.438986 0.179417
v -0.041883 -0.438986 -0.188821
v 0.074876 -0.438986 0.194234
v -0.160214 -0.438986 -0.091443
v 0.193207 -0.438986 0.096856
v -0.175031 -0.438986 0.061086
v 0.208024 -0.438986 -0.055673
vt 0.305531 0.759306
vt 0.305531 0.693376
vt 0.322138 0.693115
vt 0.322138 0.759045
vt 0.127009 0.760524
vt 0.127009 0.824752
vt 0.107338 0.825393
vt 0.107338 0.761165
vt 0.432716 0.837587
vt 0.432716 0.772633
vt 0.451511 0.773747
vt 0.451511 0.838701
vt 0.125765 0.827112
vt 0.125765 0.891270
vt 0.107338 0.889552
vt 0.155872 0.819549
vt 0.218576 0.820881
vt 0.218576 0.886696
vt 0.155872 0.885364
vt 0.269209 0.757985
vt 0.269209 0.692055
vt 0.144607 0.399709
vt 0.082230 0.399709
vt 0.076435 0.342636
vt 0.152993 0.348495
vt 0.065629 0.383103
vt 0.002115 0.387857
vt 0.002622 0.335255
vt 0.066818 0.331689
vt 0.075973 0.468467
vt 0.003549 0.468467
vt 0.004133 0.406109
vt 0.077011 0.399096
vt 0.183275 0.409560
vt 0.177073 0.360698
vt 0.236889 0.355149
vt 0.248670 0.406724
vt 0.180198 0.425232
vt 0.234253 0.418784
vt 0.234253 0.484486
vt 0.176292 0.481134
vt 0.156500 0.465367
vt 0.091662 0.465367
vt 0.092722 0.421227
vt 0.155100 0.421227
vt 0.988916 0.422851
vt 0.956702 0.419996
vt 0.956702 0.359115
vt 0.988916 0.361971
vt 0.956702 0.292695
vt 0.987695 0.293240
vt 0.987695 0.359115
vt 0.956702 0.358569
vt 0.318028 0.833008
vt 0.285092 0.832755
vt 0.282500 0.776573
vt 0.315436 0.776827
vt 0.983284 0.658918
vt 0.952201 0.659873
vt 0.952201 0.598107
vt 0.983284 0.597151
vt 0.286551 0.760148
vt 0.316320 0.760378
vt 0.313977 0.775985
vt 0.284208 0.775755
vt 0.655221 0.272244
vt 0.655221 0.244094
vt 0.682642 0.243798
vt 0.682642 0.271948
vt 0.302769 0.660713
vt 0.276136 0.668765
vt 0.293661 0.624807
vt 0.320294 0.616756
vt 0.652886 0.289324
vt 0.620133 0.292421
vt 0.620133 0.244474
vt 0.652886 0.241376
vt 0.282500 0.833008
vt 0.315357 0.833386
vt 0.315357 0.898986
vt 0.282500 0.898608
vt 0.957349 0.757730
vt 0.989329 0.762049
vt 0.989329 0.818542
vt 0.957349 0.814222
vt 0.684976 0.277951
vt 0.684976 0.248220
vt 0.713984 0.241376
vt 0.713984 0.271108
vt 0.320294 0.683939
vt 0.293661 0.691990
vt 0.902832 0.512454
vt 0.902832 0.447791
vt 0.945142 0.452591
vt 0.945142 0.517254
vt 0.315192 0.908185
vt 0.315192 0.972481
vt 0.268191 0.969494
vt 0.268191 0.905199
vt 0.996073 0.532109
vt 0.996073 0.593141
vt 0.954279 0.592889
vt 0.954279 0.531857
vt 0.993996 0.447791
vt 0.993996 0.512101
vt 0.945142 0.512514
vt 0.945142 0.448203
vt 0.202418 0.970901
vt 0.136645 0.971490
vt 0.136645 0.905788
vt 0.202418 0.905199
vt 0.869699 0.523668
vt 0.869699 0.459004
vt 0.955471 0.888456
vt 0.984249 0.976566
vt 0.983853 0.976734
vt 0.930084 0.910567
vt 0.981099 0.186690
vt 0.972680 0.271483
vt 0.972193 0.271547
vt 0.945451 0.181484
vt 0.971754 0.271755
vt 0.918474 0.205118
vt 0.638049 0.971627
vt 0.685335 0.913689
vt 0.685819 0.913864
vt 0.665808 0.994702
vt 0.686215 0.914021
vt 0.697596 0.994127
vt 0.638049 0.896656
vt 0.631957 0.981660
vt 0.631444 0.981728
vt 0.602256 0.892395
vt 0.734294 0.507019
vt 0.730123 0.507081
vt 0.732680 0.477770
vt 0.732878 0.477767
vt 0.991373 0.893387
vt 0.984758 0.976480
vt 0.631061 0.981964
vt 0.577426 0.917204
vt 0.000000 0.655112
vt 0.125495 0.652240
vt 0.136645 0.738689
vt 0.040491 0.760524
vt 0.705027 0.634844
vt 0.586885 0.635654
vt 0.577426 0.562322
vt 0.711147 0.529392
vt 0.711147 0.857971
vt 0.847265 0.853116
vt 0.854003 0.905353
vt 0.732523 0.938180
vt 0.460232 0.861128
vt 0.557232 0.857035
vt 0.562048 0.894374
vt 0.467289 0.933467
vt 0.667359 0.003417
vt 0.759805 0.026125
vt 0.744719 0.121418
vt 0.622139 0.121397
vt 0.266260 0.754098
vt 0.139594 0.721044
vt 0.152178 0.641554
vt 0.264724 0.641691
vt 0.748983 0.292695
vt 0.869699 0.326498
vt 0.860318 0.385756
vt 0.722592 0.385846
vt 0.339097 0.776111
vt 0.430415 0.811853
vt 0.424003 0.852359
vt 0.329838 0.852460
vt 0.759805 0.215656
vt 0.667359 0.237960
vt 0.139594 0.562945
vt 0.266260 0.530384
vt 0.869699 0.444358
vt 0.748983 0.477767
vt 0.430415 0.892416
vt 0.339097 0.928186
vt 0.479105 0.543041
vt 0.571801 0.558061
vt 0.575216 0.662073
vt 0.460232 0.675489
vt 0.840286 0.757730
vt 0.712460 0.732936
vt 0.711147 0.644666
vt 0.822262 0.635762
vt 0.593343 0.326602
vt 0.566633 0.441420
vt 0.503750 0.441070
vt 0.492299 0.318297
vt 0.953920 0.442831
vt 0.873107 0.417140
vt 0.872482 0.375070
vt 0.953371 0.365192
vt 0.575216 0.748257
vt 0.483331 0.778407
vt 0.712460 0.571570
vt 0.840286 0.529392
vt 0.451713 0.439072
vt 0.412888 0.322916
vt 0.873107 0.340232
vt 0.953920 0.297655
vt 0.630153 0.292695
vt 0.722592 0.320635
vt 0.702295 0.423140
vt 0.577426 0.419612
vt 0.331137 0.543041
vt 0.460232 0.583559
vt 0.443014 0.670513
vt 0.327538 0.665571
vt 0.793532 0.000000
vt 0.911423 0.038698
vt 0.899158 0.100636
vt 0.761810 0.098299
vt 0.015270 0.765702
vt 0.103774 0.802048
vt 0.095589 0.843383
vt 0.003564 0.842334
vt 0.040491 0.525175
vt 0.136645 0.548506
vt 0.711147 0.757677
vt 0.577426 0.723651
vt 0.732523 0.757730
vt 0.854003 0.790435
vt 0.467289 0.778407
vt 0.562048 0.812230
vt 0.722592 0.511907
vt 0.630153 0.529392
vt 0.460232 0.745812
vt 0.331137 0.772633
vt 0.911423 0.154273
vt 0.793532 0.181484
vt 0.103774 0.879177
vt 0.015270 0.911295
vt 0.340901 0.185305
vt 0.327538 0.190013
vt 0.331296 0.068638
vt 0.352374 0.080440
vt 0.431125 0.000000
vt 0.434325 0.013935
vt 0.555010 0.001787
vt 0.538749 0.024747
vt 0.619786 0.102682
vt 0.604475 0.106542
vt 0.620133 0.225844
vt 0.593003 0.211407
vt 0.516199 0.292695
vt 0.511052 0.277912
vt 0.406628 0.267100
vt 0.396419 0.292695
vt 0.499634 0.248092
vt 0.506198 0.268139
vt 0.411962 0.245819
vt 0.417980 0.235934
vt 0.347441 0.173379
vt 0.368527 0.169836
vt 0.369753 0.080930
vt 0.380243 0.088518
vt 0.441576 0.015696
vt 0.446267 0.039615
vt 0.534934 0.039831
vt 0.527921 0.051773
vt 0.600332 0.110456
vt 0.577375 0.117871
vt 0.565658 0.199189
vt 0.577143 0.204719
vt 0.539785 0.185019
vt 0.555259 0.193453
vt 0.496740 0.243142
vt 0.491452 0.218687
vt 0.422646 0.222867
vt 0.433388 0.208618
vt 0.372810 0.164914
vt 0.399606 0.160711
vt 0.393364 0.091261
vt 0.409895 0.103029
vt 0.451910 0.041654
vt 0.458228 0.069362
vt 0.525977 0.061847
vt 0.516292 0.079430
vt 0.550074 0.127337
vt 0.575840 0.119882
vt 0.466033 0.091108
vt 0.462379 0.071313
vt 0.513289 0.088820
vt 0.505370 0.099957
vt 0.546971 0.130636
vt 0.527089 0.133925
vt 0.530050 0.182447
vt 0.518467 0.173114
vt 0.488040 0.214655
vt 0.484555 0.194568
vt 0.436305 0.198578
vt 0.445218 0.185719
vt 0.403448 0.155333
vt 0.423499 0.151751
vt 0.432121 0.112562
vt 0.419544 0.104951
vt 0.842279 0.240597
vt 0.916008 0.233238
vt 0.918474 0.252361
vt 0.843510 0.286613
vt 0.766404 0.181484
vt 0.842279 0.213768
vt 0.838844 0.235462
vt 0.761810 0.235818
vt 0.842279 0.256916
vt 0.766404 0.289305
vt 0.388616 0.995064
vt 0.421347 0.931664
vt 0.444263 0.934429
vt 0.448174 0.995064
vt 0.972433 0.145527
vt 0.911423 0.096571
vt 0.920388 0.082733
vt 1.000000 0.104665
vt 0.921613 0.059652
vt 1.000000 0.045656
vt 0.843510 0.181484
vt 0.918474 0.210290
vt 0.914380 0.040848
vt 0.984986 0.000000
vt 0.493760 0.115204
vt 0.500196 0.104527
vt 0.525233 0.132685
vt 0.507839 0.136304
vt 0.513152 0.168686
vt 0.503054 0.161227
vt 0.486550 0.191650
vt 0.482208 0.175375
vt 0.449663 0.181555
vt 0.457512 0.170459
vt 0.428032 0.152388
vt 0.443433 0.149360
vt 0.436707 0.117397
vt 0.448218 0.124436
vt 0.469064 0.110289
vt 0.466715 0.093423
vt 0.112768 0.326771
vt 0.003272 0.251449
vt 0.145979 0.169903
vt 0.154861 0.177225
vt 0.079082 0.001308
vt 0.211802 0.000000
vt 0.163636 0.148273
vt 0.152183 0.149431
vt 0.324570 0.212584
vt 0.251728 0.326771
vt 0.166313 0.176068
vt 0.173627 0.167108
vt 0.327538 0.076629
vt 0.172518 0.155595
vt 0.000000 0.114187
vt 0.144869 0.158390
vt 0.612887 0.816449
vt 0.615530 0.835772
vt 0.603716 0.863090
vt 0.577426 0.826127
vt 0.926551 0.777826
vt 0.893641 0.778745
vt 0.854003 0.765765
vt 0.897846 0.757730
vt 0.890392 0.566292
vt 0.872462 0.582147
vt 0.840286 0.589631
vt 0.848461 0.564553
vt 0.957349 0.783870
vt 0.956802 0.762024
vt 0.923104 0.555466
vt 0.894292 0.539601
vt 0.639881 0.856393
vt 0.671677 0.866234
vt 0.703716 0.892395
vt 0.656027 0.890539
vt 0.951435 0.556009
vt 0.950931 0.529392
vt 0.299671 0.507235
vt 0.310980 0.508964
vt 0.298114 0.589611
vt 0.286607 0.587851
vt 0.924610 0.888456
vt 0.913206 0.887237
vt 0.918168 0.808593
vt 0.929772 0.809834
vt 0.940607 0.663336
vt 0.930845 0.667938
vt 0.909935 0.595313
vt 0.919867 0.590632
vt 0.693787 0.763629
vt 0.703716 0.764895
vt 0.671902 0.828695
vt 0.661801 0.827407
vt 0.951435 0.661962
vt 0.930884 0.589234
vt 0.683972 0.757677
vt 0.651814 0.821351
vt 0.903702 0.888456
vt 0.908499 0.809834
vt 0.320294 0.510701
vt 0.307590 0.591378
vt 0.722592 0.478952
vt 0.725946 0.477974
vt 0.729925 0.507498
vt 0.729766 0.507544
vt 0.732816 0.242991
vt 0.736123 0.241451
vt 0.740113 0.269380
vt 0.739956 0.269453
vt 0.279899 0.574978
vt 0.283878 0.575073
vt 0.283878 0.603975
vt 0.283689 0.603970
vt 0.737749 0.507081
vt 0.733042 0.477770
vt 0.730123 0.477767
vt 0.730123 0.507488
vt 0.276127 0.576106
vt 0.283510 0.604024
vt 0.740311 0.241376
vt 0.740311 0.269376
vt 0.690465 0.892395
vt 0.694388 0.893811
vt 0.320294 0.414965
vt 0.323851 0.412734
vt 0.324205 0.412126
vt 0.324205 0.434221
vt 0.326518 0.348775
vt 0.326518 0.370674
vt 0.321682 0.370314
vt 0.320294 0.368654
vt 0.987249 0.995924
vt 0.984034 0.997287
vt 0.719463 0.259661
vt 0.717858 0.255759
vt 0.732816 0.241376
vt 0.720167 0.259600
vt 0.326516 0.326771
vt 0.320294 0.328835
vt 0.321675 0.327070
vt 0.633897 0.998092
vt 0.630789 1.000000
vt 0.286411 0.529699
vt 0.286024 0.532308
vt 0.286411 0.552403
vt 0.281463 0.530744
vt 0.276127 0.534399
vt 0.276514 0.531790
vt 0.981099 0.288451
vt 0.977153 0.288967
vt 0.717858 0.263558
vt 0.732816 0.277864
vt 0.326354 0.391578
vt 0.326354 0.412126
vt 0.321330 0.411542
vt 0.320294 0.410747
vt 0.697596 0.895090
vt 0.285130 0.555116
vt 0.286359 0.574978
vt 0.281858 0.553211
vt 0.276127 0.556733
vt 0.277357 0.554020
vt 0.991373 0.995230
vt 0.713984 0.254138
vt 0.714687 0.254077
vt 0.326634 0.370674
vt 0.326634 0.391578
vt 0.322158 0.390641
vt 0.320294 0.389542
vt 0.973600 0.290658
vt 0.286607 0.507784
vt 0.286607 0.510227
vt 0.280158 0.529699
vt 0.281367 0.507509
vt 0.276127 0.509677
vt 0.276127 0.507235
vt 0.638049 0.997539
vt 0.722630 0.511027
vt 0.722592 0.510301
vt 0.727654 0.508923
vt 0.732754 0.528777
vt 0.732716 0.507544
vt 0.732754 0.508271
vt 0.287880 0.611178
vt 0.936512 0.812829
vt 0.936532 0.591938
vt 0.647794 0.814075
s off
f 5/1 6/2 2/3 1/4
f 6/5 7/6 3/7 2/8
f 7/9 8/10 4/11 3/12
f 8/13 5/14 1/15 4/7
f 1/16 2/17 3/18 4/19
f 8/20 7/21 6/2 5/1
f 13/22 14/23 10/24 9/25
f 14/26 15/27 11/28 10/29
f 15/30 16/31 12/32 11/33
f 16/34 13/35 9/36 12/37
f 9/38 10/39 11/40 12/41
f 16/42 15/43 14/44 13/45
f 21/46 22/47 18/48 17/49
f 22/50 23/51 19/52 18/53
f 23/54 24/55 20/56 19/57
f 24/58 21/59 17/60 20/61
f 17/62 18/63 19/64 20/65
f 24/66 23/67 22/68 21/69
f 29/70 30/71 26/72 25/73
f 30/74 31/75 27/76 26/77
f 31/78 32/79 28/80 27/81
f 32/82 29/83 25/84 28/85
f 25/86 26/87 27/88 28/89
f 32/90 31/91 30/71 29/70
f 37/92 38/93 34/94 33/95
f 38/96 39/97 35/98 34/99
f 39/100 40/101 36/102 35/103
f 40/104 37/105 33/106 36/107
f 33/108 34/109 35/110 36/111
f 40/112 39/113 38/93 37/92
f 129/114 42/115 44/116 130/117
f 130/118 44/119 46/120 131/121
f 131/121 46/120 48/122 132/123
f 132/124 48/125 50/126 133/127
f 133/127 50/126 52/128 134/129
f 134/130 52/131 54/132 135/133
f 169/134 174/135 164/136 165/137
f 136/138 56/139 42/115 129/114
f 135/133 54/132 56/140 136/141
f 41/142 57/143 64/144 43/145
f 89/146 58/147 63/148 90/149
f 97/150 59/151 62/152 98/153
f 105/154 60/155 61/156 106/157
f 43/158 64/159 68/160 45/161
f 90/162 63/163 67/164 91/165
f 98/166 62/167 66/168 99/169
f 106/170 61/171 65/172 107/173
f 45/161 68/160 72/174 47/175
f 91/165 67/164 71/176 92/177
f 99/169 66/168 70/178 100/179
f 107/173 65/172 69/180 108/181
f 47/182 72/183 76/184 49/185
f 92/186 71/187 75/188 93/189
f 100/190 70/191 74/192 101/193
f 108/194 69/195 73/196 109/197
f 49/185 76/184 80/198 51/199
f 93/189 75/188 79/200 94/201
f 101/193 74/192 78/202 102/203
f 109/197 73/196 77/204 110/205
f 51/206 80/207 84/208 53/209
f 94/210 79/211 83/212 95/213
f 102/214 78/215 82/216 103/217
f 110/218 77/219 81/220 111/221
f 55/222 88/223 57/143 41/142
f 96/224 87/225 58/147 89/146
f 104/226 86/227 59/151 97/150
f 112/228 85/229 60/155 105/154
f 53/209 84/208 88/230 55/231
f 95/213 83/212 87/232 96/233
f 103/217 82/216 86/234 104/235
f 111/221 81/220 85/236 112/237
f 57/238 89/239 90/240 64/241
f 64/241 90/240 91/242 68/243
f 68/243 91/242 92/244 72/245
f 72/245 92/244 93/246 76/247
f 76/247 93/246 94/248 80/249
f 80/249 94/248 95/250 84/251
f 88/252 96/253 89/239 57/238
f 84/251 95/250 96/253 88/252
f 58/254 97/255 98/256 63/257
f 63/257 98/256 99/258 67/259
f 67/259 99/258 100/260 71/261
f 71/261 100/260 101/262 75/263
f 75/263 101/262 102/264 79/265
f 79/265 102/264 103/266 83/267
f 87/268 104/269 97/255 58/254
f 83/267 103/266 104/269 87/268
f 59/270 105/271 106/272 62/273
f 62/273 106/272 107/274 66/275
f 66/275 107/274 108/276 70/277
f 70/277 108/276 109/278 74/279
f 74/279 109/278 110/280 78/281
f 78/281 110/280 111/282 82/283
f 86/284 112/285 105/271 59/270
f 82/283 111/282 112/285 86/284
f 60/286 113/287 114/288 61/289
f 61/289 114/288 115/290 65/291
f 65/291 115/290 116/292 69/293
f 69/293 116/292 117/294 73/295
f 73/295 117/294 118/296 77/297
f 77/297 118/296 119/298 81/299
f 85/300 120/301 113/287 60/286
f 81/299 119/298 120/301 85/300
f 113/302 121/303 122/304 114/305
f 114/306 122/307 123/308 115/309
f 115/309 123/308 124/310 116/311
f 116/312 124/313 125/314 117/315
f 117/316 125/317 126/318 118/319
f 118/319 126/318 127/320 119/321
f 120/322 128/323 121/303 113/302
f 119/321 127/320 128/324 120/325
f 121/326 129/327 130/328 122/329
f 122/329 130/328 131/330 123/331
f 123/331 131/330 132/332 124/333
f 124/333 132/332 133/334 125/335
f 125/335 133/334 134/336 126/337
f 126/337 134/336 135/338 127/339
f 128/340 136/341 129/327 121/326
f 127/339 135/338 136/341 128/340
f 55/342 41/343 137/344 144/345
f 45/346 47/347 140/348 139/349
f 51/350 53/351 143/352 142/353
f 47/347 49/354 141/355 140/348
f 41/343 43/356 138/357 137/344
f 53/351 55/342 144/345 143/352
f 43/356 45/346 139/349 138/357
f 49/354 51/350 142/353 141/355
f 195/358 193/359 149/360 148/361
f 196/362 194/363 145/364 152/365
f 197/366 195/367 148/368 147/369
f 198/370 196/362 152/365 151/371
f 199/372 197/366 147/369 146/373
f 200/374 198/375 151/376 150/377
f 194/378 199/372 146/373 145/379
f 193/359 200/374 150/377 149/360
f 140/380 141/381 154/382 153/383
f 144/384 137/385 156/386 155/387
f 139/388 140/389 153/390 157/391
f 143/392 144/393 155/394 158/395
f 138/396 139/388 157/391 159/397
f 142/398 143/392 158/395 160/399
f 137/385 138/400 159/401 156/386
f 141/381 142/402 160/403 154/382
f 175/404 180/405 161/406 162/407
f 181/408 186/409 167/410 168/411
f 187/412 175/413 162/414 163/415
f 190/416 169/134 165/137 166/417
f 180/405 181/418 168/419 161/406
f 174/420 187/412 163/415 164/421
f 186/409 190/422 166/423 167/410
f 50/126 48/125 172/424 171/425
f 171/426 172/427 173/428 170/429
f 170/430 173/431 174/432 169/433
f 44/116 42/115 178/434 177/435
f 177/436 178/437 179/438 176/439
f 176/440 179/430 180/441 175/442
f 56/140 54/132 184/443 183/444
f 183/445 184/446 185/447 182/448
f 182/448 185/447 186/449 181/450
f 46/120 44/119 177/451 189/452
f 189/453 177/436 176/439 188/454
f 188/455 176/456 175/457 187/458
f 52/128 50/126 171/425 192/459
f 192/447 171/460 170/461 191/462
f 191/462 170/461 169/463 190/464
f 42/115 56/139 183/465 178/434
f 178/437 183/466 182/467 179/438
f 179/468 182/469 181/470 180/471
f 48/122 46/120 189/452 172/472
f 172/473 189/474 188/475 173/476
f 173/476 188/475 187/477 174/478
f 54/132 52/131 192/479 184/443
f 184/480 192/481 191/482 185/483
f 185/483 191/482 190/484 186/485
f 153/383 154/382 193/73 195/486
f 155/387 156/386 194/363 196/362
f 157/391 153/390 195/367 197/366
f 158/487 155/387 196/362 198/370
f 159/397 157/391 197/366 199/372
f 160/399 158/395 198/375 200/374
f 156/488 159/397 199/372 194/378
f 154/489 160/399 200/374 193/359

View file

@ -0,0 +1,633 @@
# Blender v2.72 (sub 0) OBJ File: 'outdoor-christmas-tree.blend'
# www.blender.org
v 0.029152 -0.228498 1.452818
v 0.029152 4.014762 0.010117
v -0.916664 -0.043204 0.950599
v 0.025381 4.014762 0.008554
v -1.418883 -0.228498 0.004783
v 0.023819 4.014762 0.004783
v -0.916664 -0.043204 -0.941033
v 0.025381 4.014762 0.001012
v 0.029152 -0.228498 -1.443252
v 0.029152 4.014762 -0.000550
v 0.974969 -0.043204 -0.941033
v 0.032924 4.014762 0.001012
v 1.477187 -0.228498 0.004783
v 0.034486 4.014762 0.004783
v 0.974969 -0.043204 0.950599
v 0.032924 4.014762 0.008554
v 0.029152 0.686169 1.085668
v 0.029152 1.503851 0.921684
v 0.029152 2.343612 0.657905
v 0.029152 2.922426 0.471642
v -0.300967 2.922426 0.334902
v -0.432675 2.343612 0.466610
v -0.619195 1.503851 0.653130
v -0.735149 0.686169 0.769084
v -0.437706 2.922426 0.004783
v -0.623970 2.343612 0.004783
v -0.887748 1.503851 0.004783
v -1.051733 0.686169 0.004783
v -0.300967 2.922426 -0.325336
v -0.432675 2.343612 -0.457044
v -0.619195 1.503851 -0.643563
v -0.735149 0.686169 -0.759518
v 0.029152 2.922426 -0.462075
v 0.029152 2.343612 -0.648339
v 0.029152 1.503851 -0.912117
v 0.029152 0.686169 -1.076102
v 0.359271 2.922426 -0.325336
v 0.490979 2.343612 -0.457044
v 0.677499 1.503851 -0.643563
v 0.793454 0.686169 -0.759518
v 0.496011 2.922426 0.004783
v 0.682274 2.343612 0.004783
v 0.946053 1.503851 0.004783
v 1.110037 0.686169 0.004783
v 0.359271 2.922426 0.334902
v 0.490979 2.343612 0.466610
v 0.677499 1.503851 0.653130
v 0.793454 0.686169 0.769084
v -0.052955 0.624438 1.200440
v -0.948970 0.494273 0.857200
v -1.166505 0.624438 -0.077324
v -0.823264 0.494273 -0.973339
v 0.111259 0.624438 -1.190874
v 1.007274 0.494273 -0.847633
v 1.224809 0.624438 0.086890
v 0.881569 0.494273 0.982905
v 0.102633 1.368377 1.112609
v -0.646453 1.431869 0.776380
v -1.078674 1.368376 0.078264
v -0.742445 1.431869 -0.670822
v -0.044329 1.368376 -1.103043
v 0.704758 1.431869 -0.766814
v 1.136978 1.368376 -0.068698
v 0.800749 1.431869 0.680388
v -0.031563 2.233588 0.816566
v -0.638741 2.236296 0.579722
v -0.782631 2.233588 -0.055932
v -0.545787 2.236296 -0.663110
v 0.089867 2.233588 -0.807000
v 0.697045 2.236296 -0.570156
v 0.840936 2.233588 0.065498
v 0.604091 2.236296 0.672676
v 0.086306 2.879498 0.592633
v -0.317484 2.830011 0.426082
v -0.558698 2.879498 0.061937
v -0.392147 2.830011 -0.341853
v -0.028002 2.879498 -0.583067
v 0.375788 2.830011 -0.416516
v 0.617002 2.879498 -0.052371
v 0.450451 2.830011 0.351419
v 0.029152 3.393739 0.243889
v -0.139921 3.393739 0.173856
v -0.209953 3.393739 0.004783
v -0.139921 3.393739 -0.164290
v 0.029152 3.393739 -0.234322
v 0.198225 3.393739 -0.164290
v 0.268258 3.393739 0.004783
v 0.198225 3.393739 0.173856
v -0.004442 3.354242 0.336412
v -0.250424 3.449487 0.232927
v -0.302477 3.354242 -0.028811
v -0.198991 3.449487 -0.274793
v 0.062747 3.354242 -0.326845
v 0.308729 3.449487 -0.223360
v 0.360781 3.354242 0.038378
v 0.257296 3.449487 0.284360
v 0.029152 0.137126 0.123296
v -0.054649 0.137126 0.088584
v -0.089361 0.137126 0.004783
v -0.054649 0.137126 -0.079018
v 0.029152 0.137126 -0.113730
v 0.112954 0.137126 -0.079018
v 0.147665 0.137126 0.004783
v 0.112954 0.137126 0.088584
v -0.056110 -0.503049 -0.080479
v 0.029152 -0.503049 -0.115796
v 0.114414 -0.503049 0.090045
v 0.029152 -0.503049 0.125362
v -0.091427 -0.503049 0.004783
v 0.149731 -0.503049 0.004783
v -0.056110 -0.503049 0.090045
v 0.114414 -0.503049 -0.080479
v 0.029152 4.496952 0.006832
v 0.027703 4.496952 0.006232
v 0.027103 4.496952 0.004783
v 0.027703 4.496952 0.003334
v 0.029152 4.496952 0.002734
v 0.030601 4.496952 0.003334
v 0.031201 4.496952 0.004783
v 0.030601 4.496952 0.006232
v 0.029152 4.266080 -0.038430
v 0.029152 4.225060 -0.197405
v 0.029152 4.184040 -0.038430
v -0.001404 4.184040 -0.025773
v -0.001404 4.225060 -0.025773
v -0.001404 4.266080 -0.025773
v -0.001404 4.266080 0.035340
v -0.001404 4.225060 0.035340
v -0.001404 4.184040 0.035340
v 0.029152 4.184040 0.047996
v 0.029152 4.225060 0.206971
v 0.029152 4.266080 0.047996
v 0.059709 4.266080 0.035340
v 0.059709 4.225060 0.035340
v 0.059709 4.184040 0.035340
v 0.072365 4.184040 0.004783
v 0.231340 4.225060 0.004783
v 0.072365 4.266080 0.004783
v -0.014061 4.266080 0.004783
v -0.173036 4.225060 0.004783
v -0.014061 4.184040 0.004783
v 0.059709 4.266080 -0.025773
v 0.059709 4.225060 -0.025773
v 0.059709 4.184040 -0.025773
vt 0.955471 0.888456
vt 0.984249 0.976566
vt 0.983853 0.976734
vt 0.930084 0.910567
vt 0.981099 0.186690
vt 0.972680 0.271483
vt 0.972193 0.271547
vt 0.945451 0.181484
vt 0.971754 0.271755
vt 0.918474 0.205118
vt 0.638049 0.971627
vt 0.685335 0.913689
vt 0.685819 0.913864
vt 0.665808 0.994702
vt 0.686215 0.914021
vt 0.697596 0.994127
vt 0.638049 0.896656
vt 0.631957 0.981660
vt 0.631444 0.981728
vt 0.602256 0.892395
vt 0.734294 0.507019
vt 0.730123 0.507081
vt 0.732680 0.477770
vt 0.732878 0.477767
vt 0.991373 0.893387
vt 0.984758 0.976480
vt 0.631061 0.981964
vt 0.577426 0.917204
vt 0.000000 0.655112
vt 0.125495 0.652240
vt 0.136645 0.738689
vt 0.040491 0.760524
vt 0.705027 0.634844
vt 0.586885 0.635654
vt 0.577915 0.565251
vt 0.711636 0.532322
vt 0.711147 0.857971
vt 0.847265 0.853116
vt 0.854003 0.905353
vt 0.732523 0.938180
vt 0.460232 0.861128
vt 0.557232 0.857035
vt 0.562048 0.894374
vt 0.467289 0.933467
vt 0.667359 0.003417
vt 0.759805 0.026125
vt 0.744719 0.121418
vt 0.622139 0.121397
vt 0.266748 0.757027
vt 0.140082 0.723974
vt 0.152178 0.641554
vt 0.264724 0.641691
vt 0.748983 0.292695
vt 0.869699 0.326498
vt 0.860318 0.385756
vt 0.722592 0.385846
vt 0.339097 0.776111
vt 0.430415 0.811853
vt 0.424003 0.852359
vt 0.329838 0.852460
vt 0.755899 0.217609
vt 0.663453 0.239913
vt 0.144244 0.559845
vt 0.270910 0.527283
vt 0.869699 0.444358
vt 0.748983 0.477767
vt 0.430415 0.892416
vt 0.339097 0.928186
vt 0.475198 0.544994
vt 0.567894 0.560014
vt 0.575216 0.662073
vt 0.460232 0.675489
vt 0.844937 0.754629
vt 0.717110 0.729836
vt 0.711147 0.644666
vt 0.822262 0.635762
vt 0.593343 0.326602
vt 0.566633 0.441420
vt 0.503750 0.441070
vt 0.492299 0.318297
vt 0.953920 0.442831
vt 0.873107 0.417140
vt 0.872482 0.375070
vt 0.953371 0.365192
vt 0.575216 0.748257
vt 0.483331 0.778407
vt 0.712460 0.573034
vt 0.840286 0.530857
vt 0.454813 0.442560
vt 0.415989 0.326404
vt 0.873107 0.340232
vt 0.953920 0.297655
vt 0.630153 0.292695
vt 0.722592 0.320635
vt 0.702295 0.423140
vt 0.577426 0.419612
vt 0.331137 0.544506
vt 0.460232 0.585024
vt 0.443014 0.670513
vt 0.327538 0.665571
vt 0.796633 0.003488
vt 0.914523 0.042186
vt 0.899158 0.100636
vt 0.761810 0.098299
vt 0.015270 0.765702
vt 0.103774 0.802048
vt 0.095589 0.843383
vt 0.003564 0.842334
vt 0.040491 0.525175
vt 0.136645 0.548506
vt 0.711147 0.756901
vt 0.577426 0.722876
vt 0.732523 0.757730
vt 0.854003 0.790435
vt 0.467289 0.778407
vt 0.562048 0.812230
vt 0.722592 0.511907
vt 0.630153 0.529392
vt 0.460232 0.745037
vt 0.331137 0.771858
vt 0.911423 0.154273
vt 0.793532 0.181484
vt 0.103774 0.879177
vt 0.015270 0.911295
vt 0.340901 0.185305
vt 0.327538 0.190013
vt 0.331784 0.071568
vt 0.352374 0.080440
vt 0.431125 0.000000
vt 0.434325 0.013935
vt 0.559660 -0.001313
vt 0.534842 0.026700
vt 0.619786 0.102682
vt 0.604475 0.106542
vt 0.620133 0.227308
vt 0.593003 0.211407
vt 0.516199 0.292695
vt 0.511052 0.277912
vt 0.406628 0.267100
vt 0.396419 0.291920
vt 0.499634 0.248092
vt 0.506198 0.268139
vt 0.411962 0.245819
vt 0.418468 0.238863
vt 0.347441 0.173379
vt 0.368527 0.169836
vt 0.369753 0.080930
vt 0.384894 0.085417
vt 0.441576 0.015696
vt 0.446267 0.039615
vt 0.538034 0.043319
vt 0.527921 0.053238
vt 0.600332 0.110456
vt 0.577375 0.117871
vt 0.565658 0.198414
vt 0.577143 0.204719
vt 0.539785 0.185019
vt 0.555259 0.193453
vt 0.496740 0.243142
vt 0.491452 0.218687
vt 0.422646 0.222867
vt 0.433388 0.208618
vt 0.372810 0.164914
vt 0.399606 0.160711
vt 0.393364 0.091261
vt 0.409895 0.103029
vt 0.451910 0.041654
vt 0.461328 0.072849
vt 0.525977 0.061847
vt 0.516292 0.079430
vt 0.550074 0.127337
vt 0.575840 0.119882
vt 0.466033 0.091108
vt 0.462379 0.071313
vt 0.513289 0.088820
vt 0.505370 0.099957
vt 0.546971 0.130636
vt 0.527089 0.133925
vt 0.530050 0.182447
vt 0.518467 0.173114
vt 0.488040 0.214655
vt 0.484555 0.194568
vt 0.436305 0.198578
vt 0.445218 0.185719
vt 0.403448 0.155333
vt 0.423499 0.151751
vt 0.432121 0.112562
vt 0.419544 0.104951
vt 0.842279 0.240597
vt 0.916008 0.233238
vt 0.918474 0.252361
vt 0.843510 0.286613
vt 0.766404 0.181484
vt 0.842279 0.213768
vt 0.838844 0.235462
vt 0.761810 0.235818
vt 0.842279 0.256916
vt 0.766404 0.289305
vt 0.388616 0.995064
vt 0.421347 0.931664
vt 0.444263 0.934429
vt 0.448174 0.995064
vt 0.972433 0.145527
vt 0.911423 0.096571
vt 0.920388 0.082733
vt 1.000000 0.104665
vt 0.921613 0.059652
vt 1.000000 0.045656
vt 0.843510 0.181484
vt 0.918474 0.210290
vt 0.914380 0.040848
vt 0.984986 0.000000
vt 0.493760 0.115204
vt 0.500196 0.104527
vt 0.525233 0.132685
vt 0.507839 0.136304
vt 0.513152 0.168686
vt 0.503054 0.161227
vt 0.486550 0.191650
vt 0.482208 0.175375
vt 0.449663 0.181555
vt 0.457512 0.170459
vt 0.428032 0.152388
vt 0.443433 0.149360
vt 0.436707 0.117397
vt 0.448218 0.124436
vt 0.469064 0.110289
vt 0.466715 0.093423
vt 0.112768 0.326771
vt 0.003272 0.251449
vt 0.145979 0.169903
vt 0.154861 0.177225
vt 0.079082 0.001308
vt 0.207895 0.001953
vt 0.163636 0.148273
vt 0.152183 0.149431
vt 0.324570 0.212584
vt 0.251728 0.326771
vt 0.166313 0.176068
vt 0.173627 0.167108
vt 0.327538 0.076629
vt 0.172518 0.155595
vt 0.000000 0.114187
vt 0.144869 0.158390
vt 0.299671 0.507235
vt 0.310980 0.508964
vt 0.298114 0.589611
vt 0.286607 0.587851
vt 0.924610 0.888456
vt 0.913206 0.887237
vt 0.918168 0.808593
vt 0.929772 0.809834
vt 0.940607 0.663336
vt 0.930845 0.667938
vt 0.909935 0.595313
vt 0.919867 0.590632
vt 0.693787 0.763629
vt 0.703716 0.764895
vt 0.671902 0.828695
vt 0.661801 0.827407
vt 0.951435 0.661962
vt 0.930884 0.589234
vt 0.683972 0.757677
vt 0.651814 0.821351
vt 0.903702 0.888456
vt 0.908499 0.809834
vt 0.320294 0.510701
vt 0.307590 0.591378
vt 0.722592 0.478952
vt 0.725946 0.477974
vt 0.729925 0.507498
vt 0.729766 0.507544
vt 0.732816 0.242991
vt 0.736123 0.241451
vt 0.740113 0.269380
vt 0.739956 0.269453
vt 0.279899 0.574978
vt 0.283878 0.575073
vt 0.283878 0.603975
vt 0.283689 0.603970
vt 0.737749 0.507081
vt 0.733042 0.477770
vt 0.730123 0.477767
vt 0.730123 0.507488
vt 0.276127 0.576106
vt 0.283510 0.604024
vt 0.740311 0.241376
vt 0.740311 0.269376
vt 0.690465 0.892395
vt 0.694388 0.893811
vt 0.320294 0.414965
vt 0.323851 0.412734
vt 0.324205 0.412126
vt 0.324205 0.434221
vt 0.326518 0.348775
vt 0.326518 0.370674
vt 0.321682 0.370314
vt 0.320294 0.368654
vt 0.987249 0.995924
vt 0.984034 0.997287
vt 0.719463 0.259661
vt 0.717858 0.255759
vt 0.732816 0.241376
vt 0.720167 0.259600
vt 0.326516 0.326771
vt 0.320294 0.328835
vt 0.321675 0.327070
vt 0.633897 0.998092
vt 0.630789 1.000000
vt 0.286411 0.529699
vt 0.286024 0.532308
vt 0.286411 0.552403
vt 0.281463 0.530744
vt 0.276127 0.534399
vt 0.276514 0.531790
vt 0.981099 0.288451
vt 0.977153 0.288967
vt 0.717858 0.263558
vt 0.732816 0.277864
vt 0.326354 0.391578
vt 0.326354 0.412126
vt 0.321330 0.411542
vt 0.320294 0.410747
vt 0.697596 0.895090
vt 0.285130 0.555116
vt 0.286359 0.574978
vt 0.281858 0.553211
vt 0.276127 0.556733
vt 0.277357 0.554020
vt 0.991373 0.995230
vt 0.713984 0.254138
vt 0.714687 0.254077
vt 0.326634 0.370674
vt 0.326634 0.391578
vt 0.322158 0.390641
vt 0.320294 0.389542
vt 0.973600 0.290658
vt 0.286607 0.507784
vt 0.286607 0.510227
vt 0.280158 0.529699
vt 0.281367 0.507509
vt 0.276127 0.509677
vt 0.276127 0.507235
vt 0.638049 0.997539
vt 0.722630 0.511027
vt 0.722592 0.510301
vt 0.727654 0.508923
vt 0.732754 0.528777
vt 0.732716 0.507544
vt 0.732754 0.508271
s off
f 89/1 2/2 4/3 90/4
f 90/5 4/6 6/7 91/8
f 91/8 6/7 8/9 92/10
f 92/11 8/12 10/13 93/14
f 93/14 10/13 12/15 94/16
f 94/17 12/18 14/19 95/20
f 121/21 126/22 116/23 117/24
f 96/25 16/26 2/2 89/1
f 95/20 14/19 16/27 96/28
f 1/29 17/30 24/31 3/32
f 49/33 18/34 23/35 50/36
f 57/37 19/38 22/39 58/40
f 65/41 20/42 21/43 66/44
f 3/45 24/46 28/47 5/48
f 50/49 23/50 27/51 51/52
f 58/53 22/54 26/55 59/56
f 66/57 21/58 25/59 67/60
f 5/48 28/47 32/61 7/62
f 51/52 27/51 31/63 52/64
f 59/56 26/55 30/65 60/66
f 67/60 25/59 29/67 68/68
f 7/69 32/70 36/71 9/72
f 52/73 31/74 35/75 53/76
f 60/77 30/78 34/79 61/80
f 68/81 29/82 33/83 69/84
f 9/72 36/71 40/85 11/86
f 53/76 35/75 39/87 54/88
f 61/80 34/79 38/89 62/90
f 69/84 33/83 37/91 70/92
f 11/93 40/94 44/95 13/96
f 54/97 39/98 43/99 55/100
f 62/101 38/102 42/103 63/104
f 70/105 37/106 41/107 71/108
f 15/109 48/110 17/30 1/29
f 56/111 47/112 18/34 49/33
f 64/113 46/114 19/38 57/37
f 72/115 45/116 20/42 65/41
f 13/96 44/95 48/117 15/118
f 55/100 43/99 47/119 56/120
f 63/104 42/103 46/121 64/122
f 71/108 41/107 45/123 72/124
f 17/125 49/126 50/127 24/128
f 24/128 50/127 51/129 28/130
f 28/130 51/129 52/131 32/132
f 32/132 52/131 53/133 36/134
f 36/134 53/133 54/135 40/136
f 40/136 54/135 55/137 44/138
f 48/139 56/140 49/126 17/125
f 44/138 55/137 56/140 48/139
f 18/141 57/142 58/143 23/144
f 23/144 58/143 59/145 27/146
f 27/146 59/145 60/147 31/148
f 31/148 60/147 61/149 35/150
f 35/150 61/149 62/151 39/152
f 39/152 62/151 63/153 43/154
f 47/155 64/156 57/142 18/141
f 43/154 63/153 64/156 47/155
f 19/157 65/158 66/159 22/160
f 22/160 66/159 67/161 26/162
f 26/162 67/161 68/163 30/164
f 30/164 68/163 69/165 34/166
f 34/166 69/165 70/167 38/168
f 38/168 70/167 71/169 42/170
f 46/171 72/172 65/158 19/157
f 42/170 71/169 72/172 46/171
f 20/173 73/174 74/175 21/176
f 21/176 74/175 75/177 25/178
f 25/178 75/177 76/179 29/180
f 29/180 76/179 77/181 33/182
f 33/182 77/181 78/183 37/184
f 37/184 78/183 79/185 41/186
f 45/187 80/188 73/174 20/173
f 41/186 79/185 80/188 45/187
f 73/189 81/190 82/191 74/192
f 74/193 82/194 83/195 75/196
f 75/196 83/195 84/197 76/198
f 76/199 84/200 85/201 77/202
f 77/203 85/204 86/205 78/206
f 78/206 86/205 87/207 79/208
f 80/209 88/210 81/190 73/189
f 79/208 87/207 88/211 80/212
f 81/213 89/214 90/215 82/216
f 82/216 90/215 91/217 83/218
f 83/218 91/217 92/219 84/220
f 84/220 92/219 93/221 85/222
f 85/222 93/221 94/223 86/224
f 86/224 94/223 95/225 87/226
f 88/227 96/228 89/214 81/213
f 87/226 95/225 96/228 88/227
f 15/229 1/230 97/231 104/232
f 5/233 7/234 100/235 99/236
f 11/237 13/238 103/239 102/240
f 7/234 9/241 101/242 100/235
f 1/230 3/243 98/244 97/231
f 13/238 15/229 104/232 103/239
f 3/243 5/233 99/236 98/244
f 9/241 11/237 102/240 101/242
f 100/245 101/246 106/247 105/248
f 104/249 97/250 108/251 107/252
f 99/253 100/254 105/255 109/256
f 103/257 104/258 107/259 110/260
f 98/261 99/253 109/256 111/262
f 102/263 103/257 110/260 112/264
f 97/250 98/265 111/266 108/251
f 101/246 102/267 112/268 106/247
f 127/269 132/270 113/271 114/272
f 133/273 138/274 119/275 120/276
f 139/277 127/278 114/279 115/280
f 142/281 121/21 117/24 118/282
f 132/270 133/283 120/284 113/271
f 126/285 139/277 115/280 116/286
f 138/274 142/287 118/288 119/275
f 10/13 8/12 124/289 123/290
f 123/291 124/292 125/293 122/294
f 122/295 125/296 126/297 121/298
f 4/3 2/2 130/299 129/300
f 129/301 130/302 131/303 128/304
f 128/305 131/295 132/306 127/307
f 16/27 14/19 136/308 135/309
f 135/310 136/311 137/312 134/313
f 134/313 137/312 138/314 133/315
f 6/7 4/6 129/316 141/317
f 141/318 129/301 128/304 140/319
f 140/320 128/321 127/322 139/323
f 12/15 10/13 123/290 144/324
f 144/312 123/325 122/326 143/327
f 143/327 122/326 121/328 142/329
f 2/2 16/26 135/330 130/299
f 130/302 135/331 134/332 131/303
f 131/333 134/334 133/335 132/336
f 8/9 6/7 141/317 124/337
f 124/338 141/339 140/340 125/341
f 125/341 140/340 139/342 126/343
f 14/19 12/18 144/344 136/308
f 136/345 144/346 143/347 137/348
f 137/348 143/347 142/349 138/350

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

17
mail/.github/workflows/luacheck.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: luacheck
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: apt
run: sudo apt-get install -y luarocks
- name: luacheck install
run: luarocks install --local luacheck
- name: luacheck run
run: $HOME/.luarocks/bin/luacheck ./

17
mail/.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
ENGINE_VERSION: [5.0.1, 5.1.1, 5.2.0, 5.3.0, 5.4.1, 5.5.1, 5.6.1, 5.7.0, latest]
steps:
- uses: actions/checkout@v3
- name: test
run: docker-compose up --exit-code-from sut

2
mail/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
i18n.py
locale/*.tr.old

20
mail/.luacheckrc Normal file
View file

@ -0,0 +1,20 @@
globals = {
"mail",
}
read_globals = {
-- Stdlib
string = {fields = {"split"}},
table = {fields = {"copy", "getn"}},
-- Minetest
"minetest",
"vector", "ItemStack",
"dump",
-- Deps
"unified_inventory", "default",
-- optional mods
"mtt", "canonical_name"
}

View file

@ -1,12 +1,10 @@
The file textures/mail_button.png was created by bas080 and is licensed under the WTFPL.
All other files:
Copyright (c) 2016 Carter Kolwey ("Cheapie Systems")
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and/or any 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.

View file

@ -1,26 +0,0 @@
Mail mod for Minetest
=====================
This mod adds a system to Minetest that allows players to send each other messages within the game. Unlike chat messages or /msgs, these can be sent to an offline player and will still be present the next time they join.
License: See the "LICENSE" file
Dependencies: None, but will integrate with unified_inventory if it is present.
Crafting: N/A
Instructions:
The inbox can be accessed by using the /mail command or by pressing the "Mail" button in unified_inventory if that mod is installed. In that window, all messages that the player currently has have the sender and subject (truncated if necessary) shown in a list. Unread messages are shown in red, while read messages are shown in white. The "Mark Read" and "Mark Unread" buttons will change this status, as well as viewing the message.
To view a message, either single-click on it and press "Read", or just double-click on it. A window will then open showing the sender, subject, and body of the message, with buttons to return to the inbox, reply to the message, forward it, or delete it.
Single-clicking a message and pressing the "delete" button will remove the message from the inbox.
The compose window can be opened by pressing the "New Message" button in the inbox. This opens a window where the recipient, subject, and body can be entered, along with buttons to cancel or send the message. Since messages can be sent to any name, including ones that have not yet joined the server for the first time, there is no validation to ensure that the recipient exists.
If a message is sent to a player that is currently online, they will see a notification in the chat that a message has arrived, along with the sender and subject (truncated if necessary) of the message and a brief reminder that they can use the /mail command (or the mail button, if applicable) to view the message.
If a player has unread messages in their inbox when they join, a notification will appear to notify them of this. It offers the option to either go to the inbox now, or not. If they choose not to, a reminder is shown in the chat to remind them that they can use the /mail command (or the mail button, if applicable) to view the message(s) later.
All activity (sending messages, marking them as read/unread, deleting them, etc.) is immediately saved to a file called "mail.db" in the world directory. This file is then read at server startup. If an error occurs while saving this file, a message is printed in the server logs.

117
mail/README.md Normal file
View file

@ -0,0 +1,117 @@
Mail mod for Minetest (ingame mod)
======
![](https://github.com/mt-mods/mail/workflows/test/badge.svg)
![](https://github.com/mt-mods/mail/workflows/luacheck/badge.svg)
[![ContentDB](https://content.minetest.net/packages/mt-mods/mail/shields/downloads/)](https://content.minetest.net/packages/mt-mods/mail/)
This is a fork of @cheapie's mail mod.
It adds a mail-system that allows players to send each other messages in-game and via webmail (optional).
# Screenshot
![](screenshot_1.2.0.png)
# Installation
## In-game mail mod
Install it like any other mod: copy the directory `mail_mod` to your "worldmods" folder or use the [ContentDB](https://content.minetest.net)
## Webmail
To provide a web-based interface to receive/send mails you can use the [mtui](https://github.com/minetest-go/mtui) project.
# Commands/Howto
To access your mail type `/mail` command or click on the mail button in your inventory (`unified_inventory`).
Mails can be deleted, marked as read or unread, replied to and forwarded to another player. You can also manage your contacts and your mailing lists.
# Features
* Inbox page
* Outbox page
* Saved drafts
* Read/unread marks
* To/Cc/Bcc system
* Intuitive UI
* Contacts book
* Mailing lists
* Sorters/filters (new in 1.1.0)
* Multiple selection (new in 1.1.0)
* Settings
* Chat, on join, HUD and sound notifications
* Translated in : English, French, German, Chinese (both traditional and simplified), Spanish, Brazilian Portuguese.
# Compatibility / Migration
Overview:
* `v1` all the data is in the `<worldfolder>/mails.db` file
* `v2` every player has its own (in-) mailbox in the `<worldfolder>/mails/<playername>.json` file
* `v3` every player has an entry in the `<playername>` `mod_storage/` (inbox, outbox, drafts, contacts, mailing lists, settings)
# Dependencies
* None
# License
See the "LICENSE" file
# Textures
* textures/email_mail.png (https://github.com/rubenwardy/email.git WTFPL)
# Contributors / Credits
* Cheapie (Initial idea/project)
* Rubenwardy (Lua/UI improvements)
* BuckarooBanzay (Clean-ups, Refactoring)
* Athozus (Outbox, Maillists, UI, Drafts, Trash, Settings)
* SX (Various fixes, UI)
* fluxionary (Minor fixups)
* Toby1710 (UX fixes)
* Peter Nerlich (CC, BCC)
* Emojigit (Traditional Chinese translation)
* Niklp09 (German translation)
* Dennis Jenkins (UX fixes)
* Thomas Rudin (Maintenance)
* imre84 (UI fixes)
* Chache (Spanish translation)
* APercy (Brazilian Portuguese translation)
* Nuno Filipe Povoa (mail_notif.ogg - https://invent.kde.org/plasma/oxygen-sounds/-/blob/master/sounds/Oxygen-Im-Nudge.ogg)
* TheTrueBeginner (Simplified Chinese translation)
* nyomi (Hungarian translation)
* whosit (UI fixes)
* Wuzzy (German translation)
# Contribute
You can contribute by :
* Reporting an issue : Go to the [Issues](https://github.com/mt-mods/mail/issues) tab, click on the button **New issue** and type a short title then give many informations (Minetest version, tab where the bug occured, steps to reproduce the crash, etc.)
* Give a review on ContentDB : [Just write ;)](https://content.minetest.net/packages/mt-mods/mail/review/)
* Requesting new features : [Open an issue](https://github.com/mt-mods/mail/issues) and indicate what you need more in details.
* Adding those new features : [Open a pull request](https://github.com/mt-mods/mail/pulls), and if issue(s) are linked, ping them (#number).
* Fixing an issue : Same as before, open a pull request.
* Translate into a new language : copy `locale/template.txt` into `locale/mail.<codelang>.tr`, and add translated strings (syntax : `not translated=translated`). Then, open a pull request. We're also working on opening a [Weblate](https://weblate.org) (free web interface) to translate strings easier.
* Add documentation : adds `.md` (markdown) or `.txt` files and open a pull request.
* ...
You're encouraged to create a fork of this repo, then make your changes and create a pull request when it's done. ~~If you do so, please also check "Git branches" section.~~ (obsolete). Request for merging into `master`, if needed we will push into another branch.
If you don't have a GitHub account, you can also contact maintainers to add manually your contributions.
# Git branches
***Caution : might be obsolete***
* master : main branch, where are pushed releases and tags
* dev : for new release works, A.B.C release to A.B+1.0
* A.B.X : for fix releases (no new features), A.B.C release to A.B.C+1
For a new fix release (A.B.C) : merge A.B.X branch to master
For a new release (A.B+1.0) : merge dev to master, with previous A.B.C merges (include fixes in master, prioritize dev branch in case of conflicts)
# Old/Historic stuff
* Old forum topic: https://forum.minetest.net/viewtopic.php?t=14464
* Old mod: https://cheapiesystems.com/git/mail/

157
mail/api.lua Normal file
View file

@ -0,0 +1,157 @@
-- see: mail.md
-- translation
local S = minetest.get_translator("mail")
local f = string.format
mail.registered_on_receives = {}
function mail.register_on_receive(func)
mail.registered_on_receives[#mail.registered_on_receives + 1] = func
end
function mail.send(m)
if type(m.from) ~= "string" then return false, "'from' is not a string" end
if type(m.to) ~= "string" then return false, "'to' is not a string" end
if type(m.body) ~= "string" then return false, "'body' is not a string" end
-- defaults
m.subject = m.subject or "(No subject)"
-- normalize to, cc and bcc while compiling a list of all recipients
local recipients = {}
local undeliverable = {}
m.to = mail.concat_player_list(mail.extractMaillists(m.to, m.from))
m.to = mail.normalize_players_and_add_recipients(m.to, recipients, undeliverable)
if m.cc then
m.cc = mail.concat_player_list(mail.extractMaillists(m.cc, m.from))
m.cc = mail.normalize_players_and_add_recipients(m.cc, recipients, undeliverable)
end
if m.bcc then
m.bcc = mail.concat_player_list(mail.extractMaillists(m.bcc, m.from))
m.bcc = mail.normalize_players_and_add_recipients(m.bcc, recipients, undeliverable)
end
if next(undeliverable) then -- table is not empty
local undeliverable_names = {}
for name in pairs(undeliverable) do
undeliverable_names[#undeliverable_names + 1] = '"' .. name .. '"'
end
return false, f("recipients %s don't exist; cannot send mail.", table.concat(undeliverable_names, ", "))
end
local extra = {}
local extra_log
if m.cc then
table.insert(extra, "CC: " .. m.cc)
end
if m.bcc then
table.insert(extra, "BCC: " .. m.bcc)
end
if #extra > 0 then
extra_log = f(" (%s)", table.concat(extra, " - "))
else
extra_log = ""
end
minetest.log("action", f("[mail] %q send mail to %q%s with subject %q and body %q",
m.from, m.to, extra_log, m.subject, m.body
))
local id
if m.id then
mail.delete_mail(m.from, m.id)
id = m.id
end
-- form the actual mail
local msg = {
id = id or mail.new_uuid(),
from = m.from,
to = m.to,
cc = m.cc,
bcc = m.bcc,
subject = m.subject,
body = m.body,
time = os.time(),
}
-- add in senders outbox
local entry = mail.get_storage_entry(m.from)
table.insert(entry.outbox, 1, msg)
mail.set_storage_entry(m.from, entry)
-- add in every receivers inbox
for recipient in pairs(recipients) do
entry = mail.get_storage_entry(recipient)
table.insert(entry.inbox, msg)
mail.set_storage_entry(recipient, entry)
end
-- notify recipients that happen to be online
local mail_alert = S("You have a new message from @1! Subject: @2", m.from, m.subject) ..
"\n" .. S("To view it, type /mail")
local unified_inventory_alert = S("You could also use the button in your inventory.")
for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
if recipients[name] then
if mail.get_setting(name, "chat_notifications") == true then
minetest.chat_send_player(name, mail_alert)
if minetest.get_modpath("unified_inventory") then
minetest.chat_send_player(name, unified_inventory_alert)
end
end
if mail.get_setting(name, "sound_notifications") == true then
minetest.sound_play("mail_notif", {to_player=name})
end
local receiver_entry = mail.get_storage_entry(name)
local receiver_messages = receiver_entry.inbox
mail.hud_update(name, receiver_messages)
end
end
for i=1, #mail.registered_on_receives do
if mail.registered_on_receives[i](m) then
break
end
end
return true
end
function mail.save_draft(m)
if type(m.from) ~= "string" then return false, "'from' is not a string" end
if type(m.to) ~= "string" then return false, "'to' is not a string" end
if type(m.body) ~= "string" then return false, "'body' is not a string" end
-- defaults
m.subject = m.subject or "(No subject)"
minetest.log("verbose", f("[mail] %q saves draft with subject %q and body %q",
m.from, m.subject, m.body
))
-- remove it is an update
local id
if m.id then
mail.delete_mail(m.from, m.id)
id = m.id
end
-- add (again ie. update) in sender drafts
local entry = mail.get_storage_entry(m.from)
table.insert(entry.drafts, 1, {
id = id or mail.new_uuid(),
from = m.from,
to = m.to,
cc = m.cc,
bcc = m.bcc,
subject = m.subject,
body = m.body,
time = os.time(),
})
mail.set_storage_entry(m.from, entry)
return true
end

97
mail/api.md Normal file
View file

@ -0,0 +1,97 @@
# Mail format
The mail format in the api hooks
```lua
mail = {
from = "sender name",
to = "players, which, are, addressed",
cc = "carbon copy",
bcc = "players, which, get, a, copy, but, are, not, visible, to, others",
subject = "subject line",
body = "mail body"
}
```
The fields `to`, `cc` and `bcc` can contain a player, multiple player names separated by commas, or be empty.
Players in `to` are the recipiants, who are addressed directly. `cc` specifies players that get the mail to get notified, but are not immediate part of the conversation.
There is no technical difference between `to` and `cc`, it just implies meaning for the players.
Players can see all fields making up the mail except `bcc`, which is the only difference to `cc`.
## Sending mail
```lua
local success, error = mail.send({
from = "singleplayer",
to = "playername",
cc = "carbon, copy",
bcc = "blind, carbon, copy",
subject = "subject line",
body = "mail body"
})
-- if "success" is false the error parameter will contain a message
```
# Hooks
On-receive mail hook:
```lua
mail.register_on_receive(function(m)
-- "m" is an object in the form: "Mail format"
end)
```
# Internals
mod-storage entry for a player (indexed by playername and serialized with json):
```lua
{
contacts = {
{
-- name of the player (unique key in the list)
name = "",
-- note
note = ""
},{
...
}
},
inbox = {
{
-- globally unique mail id
id = "d6cce35c-487a-458f-bab2-9032c2621f38",
-- sending player name
from = "",
-- receiving player name
to = "",
-- carbon copy (optional)
cc = "playername, playername2",
-- blind carbon copy (optional)
bcc = "",
-- mail subject
subject = "",
-- mail body
body = "",
-- timestamp (os.time())
time = 1234,
-- read-flag (true: player has read the mail, inbox only)
read = true
},{
...
}
},
outbox = {
-- same format as "inbox"
},
lists = {
{
-- name of the maillist (unique key in the list)
name = "",
-- description
description = "",
-- playername list
players = {"playername", "playername2"}
}
}
}

12
mail/api.spec.lua Normal file
View file

@ -0,0 +1,12 @@
mtt.register("send mail", function(callback)
-- send a mail
local success, err = mail.send({from = "player1", to = "player2", subject = "something", body = "blah"})
assert(success)
assert(not err)
-- check the receivers inbox
local entry = mail.get_storage_entry("player2")
assert(entry)
assert(#entry.inbox > 0)
callback()
end)

10
mail/chatcommands.lua Normal file
View file

@ -0,0 +1,10 @@
minetest.register_chatcommand("mail",{
description = "Open the mail interface",
func = function(name, param)
if #param > 0 then -- if param is not empty
mail.show_compose(name, param) -- make a new message
else
mail.show_mail_menu(name) -- show main menu
end
end
})

View file

@ -1 +0,0 @@
unified_inventory?

19
mail/docker-compose.yml Normal file
View file

@ -0,0 +1,19 @@
version: "3.6"
services:
sut:
build:
context: ./test
args:
ENGINE_VERSION: ${ENGINE_VERSION:-5.7.0}
user: root
volumes:
- "./:/root/.minetest/worlds/world/worldmods/mail/"
- "world_data:/root/.minetest/worlds/world"
- "./test/world.mt:/root/.minetest/worlds/world/world.mt"
- "./test/minetest.conf:/minetest.conf"
ports:
- "30000:30000/udp"
volumes:
world_data: {}

12
mail/gui.lua Normal file
View file

@ -0,0 +1,12 @@
if minetest.get_modpath("unified_inventory") then
unified_inventory.register_button("mail", {
type = "image",
image = "mail_button.png",
tooltip = "Mail",
action = function(player)
mail.show_mail_menu(player:get_player_name())
end
})
end

59
mail/hud.lua Normal file
View file

@ -0,0 +1,59 @@
local huddata = {}
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
local data = {}
data.imageid = player:hud_add({
hud_elem_type = "image",
name = "MailIcon",
position = {x=0.52, y=0.52},
text="",
scale = {x=1,y=1},
alignment = {x=0.5, y=0.5},
})
data.textid = player:hud_add({
hud_elem_type = "text",
name = "MailText",
position = {x=0.55, y=0.52},
text= "",
scale = {x=1,y=1},
alignment = {x=0.5, y=0.5},
})
huddata[name] = data
end)
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
huddata[name] = nil
end)
function mail.hud_update(playername, messages)
local data = huddata[playername]
local player = minetest.get_player_by_name(playername)
if not data or not player then
return
end
local unreadcount = 0
for _, message in ipairs(messages) do
if not message.read then
unreadcount = unreadcount + 1
end
end
if unreadcount == 0 or (not mail.get_setting(playername, "hud_notifications")) then
player:hud_change(data.imageid, "text", "")
player:hud_change(data.textid, "text", "")
else
player:hud_change(data.imageid, "text", "email_mail.png")
player:hud_change(data.textid, "text", unreadcount .. " /mail")
end
end

View file

@ -1,226 +1,101 @@
mail = {}
mail = {
-- version
version = 3,
mail.highlightedmessages = {}
-- mod storage
storage = minetest.get_mod_storage(),
mail.messages = {}
-- ui theme prepend
theme = "",
function mail.load()
local file = io.open(minetest.get_worldpath().."/mail.db","r")
if file then
local data = file:read("*a")
mail.messages = minetest.deserialize(data)
file:close()
end
end
-- ui forms
ui = {},
function mail.save()
local file = io.open(minetest.get_worldpath().."/mail.db","w")
if file and file:write(minetest.serialize(mail.messages)) and file:close() then
return true
else
minetest.log("error","[mail] Save failed - messages may be lost!")
return false
end
end
-- per-user ephemeral data
selected_idxs = {
inbox = {},
outbox = {},
drafts = {},
trash = {},
contacts = {},
maillists = {},
to = {},
cc = {},
bcc = {},
boxtab = {},
sortfield = {},
sortdirection = {},
filter = {},
multipleselection = {},
optionstab = {},
chat_notifications = {},
onjoin_notifications = {},
hud_notifications = {},
sound_notifications = {},
unreadcolorenable = {},
cccolorenable = {},
trash_move_enable = {},
auto_marking_read = {},
},
mail.inboxformspec = "size[8,9;]"..
"button_exit[7.5,0;0.5,0.5;quit;X]"..
"button[6.25,1;1.5,0.5;new;New Message]"..
"button[6.25,2;1.5,0.5;read;Read]"..
"button[6.25,3;1.5,0.5;reply;Reply]"..
"button[6.25,4;1.5,0.5;forward;Forward]"..
"button[6.25,5;1.5,0.5;delete;Delete]"..
"button[6.25,6;1.5,0.5;markread;Mark Read]"..
"button[6.25,7;1.5,0.5;markunread;Mark Unread]"..
"button[6.25,8;1.5,0.5;about;About]"..
"textlist[0,0.5;6,8.5;message;"
colors = {
header = "#999",
selected = "#72FF63",
important = "#FFD700",
additional = "#CCCCDD",
imp_sel = "#B9EB32",
add_sel = "#9FE6A0",
imp_add = "#E6D26F",
imp_add_sel = "#BFE16B",
highlighted = "#608631",
new = "#00F529"
},
function mail.send(src,dst,subject,body)
if not mail.messages[dst] then mail.messages[dst] = {} end
table.insert(mail.messages[dst],1,{unread=true,sender=src,subject=subject,body=body})
for _,player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
if name == dst then
if subject == "" then subject = "(No subject)" end
minetest.chat_send_player(dst,string.format("You have a new message from %s!. Use the /mail command" .. (minetest.get_modpath("unified_inventory") and " or the mail button in the inventory " or " ") .. "to view it. Subject: %s",src,(string.len(subject) > 30 and string.sub(subject,1,27) .. "..." or subject)))
end
end
mail.save()
end
function mail.showabout(name)
local formspec = "size[4,5;]"..
"button[3.5,0;0.5,0.5;back;X]"..
"label[0,0;Mail]"..
"label[0,0.5;By cheapie]"..
"label[0,1;http://github.com/cheapie/mail]"..
"label[0,1.5;See LICENSE file for license information]"..
"label[0,2.5;NOTE: Communication using this system]"..
"label[0,3;is NOT guaranteed to be private!]"..
"label[0,3.5;Admins are able to view the messages]"..
"label[0,4;of any player.]"
minetest.show_formspec(name,"mail:about",formspec)
end
function mail.showinbox(name)
local formspec = mail.inboxformspec
if not mail.messages[name] then mail.messages[name] = {} end
local idx, message
if mail.messages[name][1] then
for idx,message in ipairs(mail.messages[name]) do
if idx ~= 1 then formspec = formspec .. "," end
if message.unread then
formspec = formspec .. "#FF8888"
end
formspec = formspec .. "From: " .. minetest.formspec_escape(message.sender) .. " Subject: "
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec = formspec .. minetest.formspec_escape(string.sub(message.subject,1,27)).. "..."
else
formspec = formspec .. minetest.formspec_escape(message.subject)
end
else
formspec = formspec .. "(No subject)"
end
end
formspec = formspec .. "]label[0,0;Welcome! You've got mail!]"
else
formspec = formspec .. "No mail :(]label[0,0;Welcome!]"
end
minetest.show_formspec(name,"mail:inbox",formspec)
end
function mail.showmessage(name,msgnumber)
local message = mail.messages[name][msgnumber]
local formspec = "size[8,6]button[7.5,0;0.5,0.5;back;X]label[0,0;From: %s]label[0,0.5;Subject: %s]textarea[0.25,1;8,4;body;;%s]button[1,5;2,1;reply;Reply]button[3,5;2,1;forward;Forward]button[5,5;2,1;delete;Delete]"
local sender = minetest.formspec_escape(message.sender)
local subject = minetest.formspec_escape(message.subject)
local body = minetest.formspec_escape(message.body)
formspec = string.format(formspec,sender,subject,body)
minetest.show_formspec(name,"mail:message",formspec)
end
function mail.showcompose(name,defaulttgt,defaultsubj,defaultbody)
local formspec = "size[8,8]field[0.25,0.5;4,1;to;To:;%s]field[0.25,1.5;4,1;subject;Subject:;%s]textarea[0.25,2.5;8,4;body;;%s]button[1,7;2,1;cancel;Cancel]button[7.5,0;0.5,0.5;cancel;X]button[5,7;2,1;send;Send]"
formspec = string.format(formspec,minetest.formspec_escape(defaulttgt),minetest.formspec_escape(defaultsubj),minetest.formspec_escape(defaultbody))
minetest.show_formspec(name,"mail:compose",formspec)
end
minetest.register_on_player_receive_fields(function(player,formname,fields)
if formname == "mail:about" then
mail.showinbox(player:get_player_name())
elseif formname == "mail:inbox" then
local name = player:get_player_name()
if fields.message then
local event = minetest.explode_textlist_event(fields.message)
mail.highlightedmessages[name] = event.index
if event.type == "DCL" and mail.messages[name][mail.highlightedmessages[name]] then
mail.messages[name][mail.highlightedmessages[name]].unread = false
mail.showmessage(name,mail.highlightedmessages[name])
end
end
if fields.read then
if mail.messages[name][mail.highlightedmessages[name]] then
mail.messages[name][mail.highlightedmessages[name]].unread = false
mail.showmessage(name,mail.highlightedmessages[name])
end
elseif fields.delete then
if mail.messages[name][mail.highlightedmessages[name]] then table.remove(mail.messages[name],mail.highlightedmessages[name]) end
mail.showinbox(name)
mail.save()
elseif fields.reply and mail.messages[name][mail.highlightedmessages[name]] then
local message = mail.messages[name][mail.highlightedmessages[name]]
local replyfooter = "Type your reply here."..string.char(10)..string.char(10).."--Original message follows--"..string.char(10)..message.body
mail.showcompose(name,message.sender,"Re: "..message.subject,replyfooter)
elseif fields.forward and mail.messages[name][mail.highlightedmessages[name]] then
local message = mail.messages[name][mail.highlightedmessages[name]]
local fwfooter = "Type your message here."..string.char(10)..string.char(10).."--Original message follows--"..string.char(10)..message.body
mail.showcompose(name,"","Fw: "..message.subject,fwfooter)
elseif fields.markread then
if mail.messages[name][mail.highlightedmessages[name]] then mail.messages[name][mail.highlightedmessages[name]].unread = false end
mail.showinbox(name)
mail.save()
elseif fields.markunread then
if mail.messages[name][mail.highlightedmessages[name]] then mail.messages[name][mail.highlightedmessages[name]].unread = true end
mail.showinbox(name)
mail.save()
elseif fields.new then
mail.showcompose(name,"","","Type your message here.")
elseif fields.quit then
if minetest.get_modpath("unified_inventory") then
unified_inventory.set_inventory_formspec(player, "craft")
end
elseif fields.about then
mail.showabout(name)
end
return true
elseif formname == "mail:message" then
local name = player:get_player_name()
if fields.back then
mail.showinbox(name)
elseif fields.reply then
local message = mail.messages[name][mail.highlightedmessages[name]]
local replyfooter = "Type your reply here."..string.char(10)..string.char(10).."--Original message follows--"..string.char(10)..message.body
mail.showcompose(name,message.sender,"Re: "..message.subject,replyfooter)
elseif fields.forward then
local message = mail.messages[name][mail.highlightedmessages[name]]
local fwfooter = "Type your message here."..string.char(10)..string.char(10).."--Original message follows--"..string.char(10)..message.body
mail.showcompose(name,"","Fw: "..message.subject,fwfooter)
elseif fields.delete then
if mail.messages[name][mail.highlightedmessages[name]] then table.remove(mail.messages[name],mail.highlightedmessages[name]) end
mail.showinbox(name)
mail.save()
end
return true
elseif formname == "mail:compose" then
if fields.send then
mail.send(player:get_player_name(),fields.to,fields.subject,fields.body)
end
mail.showinbox(player:get_player_name())
return true
elseif formname == "mail:unreadnag" then
if fields.yes then
mail.showinbox(player:get_player_name())
else
minetest.chat_send_player(player:get_player_name(),"You can use the /mail command" .. (minetest.get_modpath("unified_inventory") and " or the mail button in the inventory " or " ") .. "to read your messages later.")
end
return true
elseif fields.mail then
mail.showinbox(player:get_player_name())
else
return false
end
end)
if minetest.get_modpath("unified_inventory") then
unified_inventory.register_button("mail", {
type = "image",
image = "mail_button.png",
tooltip = "Mail"
})
end
minetest.register_chatcommand("mail",{
description = "Open the mail interface",
func = function(name)
mail.showinbox(name)
end
message_drafts = {}
}
)
minetest.register_on_joinplayer(function(player)
minetest.after(0,function(player)
local name = player:get_player_name()
local unreadflag = false
if mail.messages[name] then
for _,message in ipairs(mail.messages[name]) do
if message.unread then unreadflag = true end
if minetest.get_modpath("default") then
mail.theme = default.gui_bg .. default.gui_bg_img
end
end
if unreadflag then
minetest.show_formspec(name,"mail:unreadnag","size[3,2]label[0,0;You have unread messages in your inbox.]label[0,0.5;Go there now?]button[0.5,0.75;2,1;yes;Yes]button_exit[0.5,1.5;2,1;no;No]")
end
end,player)
end)
mail.load()
-- sub files
local MP = minetest.get_modpath(minetest.get_current_modname())
dofile(MP .. "/util/normalize.lua")
dofile(MP .. "/util/contact.lua")
dofile(MP .. "/util/uuid.lua")
dofile(MP .. "/util/time_ago.lua")
dofile(MP .. "/chatcommands.lua")
dofile(MP .. "/migrate.lua")
dofile(MP .. "/hud.lua")
dofile(MP .. "/storage.lua")
dofile(MP .. "/api.lua")
dofile(MP .. "/gui.lua")
dofile(MP .. "/onjoin.lua")
dofile(MP .. "/ui/mail.lua")
dofile(MP .. "/ui/inbox.lua")
dofile(MP .. "/ui/outbox.lua")
dofile(MP .. "/ui/drafts.lua")
dofile(MP .. "/ui/trash.lua")
dofile(MP .. "/ui/message.lua")
dofile(MP .. "/ui/receivers.lua")
dofile(MP .. "/ui/events.lua")
dofile(MP .. "/ui/contacts.lua")
dofile(MP .. "/ui/edit_contact.lua")
dofile(MP .. "/ui/select_contact.lua")
dofile(MP .. "/ui/maillists.lua")
dofile(MP .. "/ui/edit_maillists.lua")
dofile(MP .. "/ui/compose.lua")
dofile(MP .. "/ui/options.lua")
dofile(MP .. "/ui/settings.lua")
dofile(MP .. "/ui/about.lua")
-- migrate storage
mail.migrate()
if minetest.get_modpath("mtt") then
dofile(MP .. "/mtt.lua")
dofile(MP .. "/api.spec.lua")
dofile(MP .. "/migrate.spec.lua")
dofile(MP .. "/util/uuid.spec.lua")
dofile(MP .. "/util/normalize.spec.lua")
end

101
mail/locale/mail.de.tr Normal file
View file

@ -0,0 +1,101 @@
# textdomain: mail
Provided by mt-mods=Bereitgestellt von mt-mods
Version=Version
Licenses=Lizenzen
Expat (code), WTFPL (textures)=Expat (Code), WTFPL (Texturen)
Communication using this system is NOT guaranteed to be private!=Die Kommunikation über dieses System ist NICHT garantiert privat!
Admins are able to view the messages of any player.=Admins können die Nachrichten aller Spieler sehen.
Contributors=Mitwirkende
You have a new message from @1! Subject: @2=Sie haben eine neue Nachricht von @1! Betreff: @2
To view it, type /mail=Geben Sie zum Anzeigen „/mail“ ein
You could also use the button in your inventory.=Sie können auch die Schaltfläche in Ihrem Inventar verwenden.
BCC=BCC
Cancel=Abbrechen
Save draft=Entwurf speichern
Send=Senden
No contacts=Keine Kontakte
Name=Name
No drafts=Keine Entwürfe
Edit=Bearbeiten
To=An
Player name=Spielername
That name is already in your contacts=Dieser Name ist bereits in Ihren Kontakten
The contact name cannot be empty.=Der Kontaktname kann nicht leer sein.
Note=Anmerkung
Maillist name=Verteilerlistenname
Desc=Beschreibung
Players=Spieler
That name is already in your mailing lists.=Dieser Name ist bereits in Ihren Verteilerlisten.
The mailing list name cannot be empty.=Der Verteilerlistenname kann nicht leer sein.
Back=Zurück
Mark Read=Als gelesen makieren
Mark Unread=Als ungelesen makieren
Trash=Papierkorb
Inbox=Posteingang
Outbox=Senden
Drafts=Entwürfe
Contacts=Kontakte
Mail lists=Verteilerlisten
Options=Einstellungen
Close=Schließen
(No subject)=(Kein Betreff)
Subject=Betreff
Delete=Löschen
New=Neu
Date=Datum
Filter=Filter
Allow multiple selection=Mehrfachauswahl zulassen
@1 of @2 selected=@1 von @2 ausgewählt
(Un)select all=Alle aus-/abwählen
No mail=Keine Nachrichten
Reply=Antworten
Reply all=Allen antworten
Forward=Weiterleiten
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=Lesen
From=Von
(No description)=(Keine Beschreibung)
No maillist=Keine Verteilerliste
Receivers=Empfänger
You have mail! Type /mail to read=Sie haben Post! „/mail“ eingeben zum Lesen
Add=Hinzufügen
Remove=Entfernen
CC=CC
Notifications=Benachrichtigungen
Chat notifications=Chat-Benachrichtigungen
On join notifications=Bei-Eintritt-Benachrichtigungen
HUD notifications=HUD-Benachrichtigungen
Sound notifications=Klang-Benachrichtigungen
Message list=Nachrichtenliste
Show unread in different color=Ungelesenes in anderer Farbe anzeigen
Show CC/BCC in different color=CC/BCC in anderer Farbe anzeigen
Default sorting fields=Standardsortierfelder
Other=Anderes
Move deleted messages to trash=Gelöschte Nachrichten in den Papierkorb verschieben
Automatic marking read=
Date format:=Datumsformat:
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=Zurücksetzen
Settings=Einstellungen
About=Über
Save=Speichern
Ascending=Aufsteigend
Descending=Absteigend
From/To=Von/An
years=Jahren
months=Monaten
weeks=Wochen
days=Tagen
hours=Stunden
minuts=Minuten
seconds=Sekunden
@1 ago=Vor @1
Restore=Wiederherstellen
Empty=Leer
Trash is empty=Papierkorb ist leer

101
mail/locale/mail.es.tr Normal file
View file

@ -0,0 +1,101 @@
# textdomain: mail
Provided by mt-mods=Proporcionado por mt-mods
Version=Versión
Licenses=Licencias
Expat (code), WTFPL (textures)=Expat (código), WTFPL (texturas)
Communication using this system is NOT guaranteed to be private!=¡NO se garantiza que la comunicación mediante este sistema sea privada!
Admins are able to view the messages of any player.=Los administradores pueden ver los mensajes de cualquier jugador.
Contributors=Colaboradores
You have a new message from @1! Subject: @2=¡Tienes un nuevo mensaje de @1! Asunto: @2
To view it, type /mail=Para verlo, escribe /mail
You could also use the button in your inventory.=También puedes usar el botón de tu inventario.
BCC=CCO
Cancel=Cancelar
Save draft=Guardar borrador
Send=Enviar
No contacts=Sin contactos
Edit=Editar
No drafts=No hay borradores
Player name=Nombre del jugador
That name is already in your contacts=Ese nombre ya está en tus contactos
The contact name cannot be empty.=El nombre de contacto no puede estar vacío.
Note=Nota
Back=Volver
Save=Guardar
Maillist name=Nombre de la lista de correo
Desc=Desc
Players=Jugadores
That name is already in your mailing lists.=Ese nombre ya está en tus listas de correo.
The mailing list name cannot be empty.=El nombre de lista de correo no puede estar vacío.
Mark Read=Marcar como leído
Mark Unread=Marcar como no leído
Reply=Responder
Reply all=Responder a todos
Forward=Reenviar
Reply only to the sender=Responder solo al remitente
Reply to all involved people=Responder a todas las personas implicadas
Transfer message to other people=Transferir el mensaje a otras personas
From=De
Filter=Filtrar
Allow multiple selection=Permitir selección múltiple
@1 of @2 selected=@1 de @2 seleccionado(s)
(Un)select all=(Des)seleccionar todos
No mail=Sin correo
Subject=Asunto
New=Nuevo
Delete=Borrar
Date=Fecha
(No description)=(Sin descripción)
No maillist=Sin lista de correo
Receivers=Recipientes
You have mail! Type /mail to read=¡Tienes correo! Escribe /mail para leerlo
Read=Leído
Trash=Papelera
Inbox=Entrada
Outbox=Enviados
Drafts=Borradores
Contacts=Contactos
Mail lists=Listas de correo
Options=Opciones
Close=Cerrar
(No subject)=(Sin asunto)
To=Para
Add=Añadir
Remove=Quitar
Name=Nombre
CC=CC
Notifications=Notificaciones
Chat notifications=Notificaciones de chat
On join notifications=Notificaciones al unirse
HUD notifications=Notificaciones de interfaz
Sound notifications=Notificaciones de sonido
Message list=Lista de mensajes
Show unread in different color=Mostrar no-leídos en diferente color
Show CC/BCC in different color=Mostrar CC/CCO en diferente color
Default sorting fields=Campos a ordenar por defecto
Other=Otros
Move deleted messages to trash=Mover mensajes borrados a la papelera
Automatic marking read=Marcar como leído automáticamente
Receive a message in the chat when there is a new message=Recibir un mensaje en el chat cuando hay correo nuevo
Receive a message at login when inbox isn't empty=Recibir mensaje al conectarse si la bandeja de entrada no está vacía
Show an HUD notification when inbox isn't empty=Mostrar una notificación en la interfaz cuando la bandeja de entrada no está vacía
Play a sound when there is a new message=Emitir un sonido cuando hay un correo nuevo
Mark a message as read when opened=Marcar un mensaje como leído al abrirlo
Reset=Restablecer
Settings=Ajustes
About=Acerca de
Ascending=Ascendente
Descending=Descendente
From/To=De/Para
years=años
months=meses
weeks=semanas
days=días
hours=horas
minuts=minutos
seconds=segundos
@1 ago=hace @1
Restore=Restaurar
Empty=Vacío
Trash is empty=La papelera está vacía
Date format:=Formato de fecha:

101
mail/locale/mail.fr.tr Normal file
View file

@ -0,0 +1,101 @@
# textdomain: mail
Provided by mt-mods=Fourni par mt-mods
Version=Version
Licenses=Licences
Expat (code), WTFPL (textures)=Expat (code), WTFPL (textures)
Communication using this system is NOT guaranteed to be private!=La communication par ce système n'est pas garantie d'être privée !
Admins are able to view the messages of any player.=Les administrateurs peuvent voir les messages de chaque joueur.
Contributors=Contributeurs
You have a new message from @1! Subject: @2=Vous avez un nouveau message de @1 ! Objet : @2
To view it, type /mail=Pour le consulter, entrez /mail
You could also use the button in your inventory.=Vous pouvez également utiliser le bouton dans votre inventaire
BCC=Cci
Cancel=Annuler
Save draft=Enregistrer le brouillon
Send=Envoyer
No contacts=Aucun contact
Name=Nom
No drafts=Aucun brouillon
Edit=Modifier
To=À
Player name=Nom du joueur
That name is already in your contacts=Ce nom est déjà dans vos contacts
The contact name cannot be empty.=Le nom du contact ne peut pas être vide.
Note=Note
Maillist name=Nom de la liste de diffusion
Desc=Desc
Players=Joueurs
That name is already in your mailing lists.=Ce nom est déjà présent dans vos listes de diffusion.
The mailing list name cannot be empty.=Le nom de la liste de diffusion ne peut pas être vide.
Back=Retour
Mark Read=Marquer comme lu
Mark Unread=Marquer non lu
Trash=Corbeille
Inbox=Boîte de réception
Outbox=Envoyés
Drafts=Brouillons
Contacts=Contacts
Mail lists=Listes de diffusion
Options=Options
Close=Fermer
(No subject)=(Sans objet)
Subject=Objet
Delete=Supprimer
New=Nouveau
Date=Date
Filter=Filtre
Allow multiple selection=Autoriser la sélection multiple
@1 of @2 selected=@1 sur @2 sélectionnés
(Un)select all=Tout (dé)sélectionner
No mail=Aucun mail
Reply=Répondre
Reply all=Répondre à tous
Forward=Transférer
Reply only to the sender=Répondre uniquement à l'expéditeur
Reply to all involved people=Répondre à toutes les personnes concernées
Transfer message to other people=Transférer le message à d'autres personnes
Read=Lire
From=De
(No description)=(Sans description)
No maillist=Aucune liste de diffusion
Receivers=Destinataires
You have mail! Type /mail to read=Vous avez reçu un mail ! Entrez /mail pour le consulter
Add=Ajouter
Remove=Enlever
CC=Cc
Notifications=Notifications
Chat notifications=Notifications dans le tchat
On join notifications=Notifications à la connexion
HUD notifications=Notifications ATH
Sound notifications=Notifications sonores
Message list=Liste de messages
Show unread in different color=Coloriser les non lus
Show CC/BCC in different color=Coloriser les Cc/Cci
Default sorting fields=Champs de tri par défaut
Other=Autre
Move deleted messages to trash=Supprimer les messages dans la corbeille
Automatic marking read=Lu automatique
Date format:=Format de la date :
Receive a message in the chat when there is a new message=Recevoir un message dans le tchat lorsqu'un nouveau message est reçu
Receive a message at login when inbox isn't empty=Recevoir un message à la connexion lorsque la boîte de réception n'est pas vide
Show an HUD notification when inbox isn't empty=Indiquer dans l'ATH que la boîte de réception n'est pas vide
Play a sound when there is a new message=Jouer un son lorsqu'un nouveau message est reçu
Mark a message as read when opened=Marquer un message comme lu lorsqu'il est ouvert
Reset=Réinitialiser
Settings=Paramètres
About=À propos
Save=Sauvegarder
Ascending=Croissant
Descending=Décroissant
From/To=De/À
years=années
months=mois
weeks=semaines
days=jours
hours=heures
minuts=minutes
seconds=secondes
@1 ago=Il y a @1
Restore=Restaurer
Empty=Vider
Trash is empty=La corbeille est vide

101
mail/locale/mail.hu.tr Normal file
View file

@ -0,0 +1,101 @@
# textdomain: mail
Provided by mt-mods=Feltéve, hogy az én mt-mod-om
Version=Verzió
Licenses=License
Expat (code), WTFPL (textures)=Expat (kód), WTFPL (textúrák)
Communication using this system is NOT guaranteed to be private!=A systemben lévő komunikáció nem garantáltan privát!
Admins are able to view the messages of any player.=Az adminok megtudják nézni minden játékos üzenetjét.
Contributors=Közreműködöttek
You have a new message from @1! Subject: @2=Van egy új üzeneted @1-től Cím: @2
To view it, type /mail=Ahhoz hogy megnézd, írd /mail
You could also use the button in your inventory.=A gombot is tudod használni az inventoridban.
BCC=BCC
Cancel=Mégse
Save draft=mentés piszkozatként
Send=Küldés
No contacts=
Name=Név
No drafts=Nincsenek piszkozatok
Edit=Szerkesztés
To=Neki
Player name=Játékos neve
That name is already in your contacts=A név már a kontaktok között van
The contact name cannot be empty.=A contakt neve nem lehet üres.
Note=Jegyzet
Maillist name=Levelező lista neve
Desc=Desc
Players=Játékosok
That name is already in your mailing lists.=A név már benne van a levelező listában
The mailing list name cannot be empty.=A levelező lista neve nem lehet üres
Back=Visza
Mark Read=Jelöld olvasottként
Mark Unread=Jelöld olvasatlanul
Trash=
Inbox=PostaLáda
Outbox=Elküldött
Drafts=Piszkozatok
Contacts=Contaktok
Mail lists=Levelező lista
Options=Lehetőségek
Close=Bezár
(No subject)=(nincs cím)
Subject=Cím
Delete=Törlés
New=Új
Date=Dátum
Filter=Filterek
Allow multiple selection=
@1 of @2 selected=
(Un)select all=(ne válaszd ki) mindegyik választása
No mail=Nincs levél
Reply=Válasz
Reply all=Válaszmindenkinek
Forward=Továbbítás
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=Olvasott
From=Tőle
(No description)=(Nincs leírás)
No maillist=Nincs levelező lista
Receivers=
You have mail! Type /mail to read=Van egy leveled! Írd /mail az olvasáshoz
Add=Hozzáadás
Remove=Elvétel
CC=CC
Notifications=Értesítések
Chat notifications=Chates értesítések
On join notifications=Belépési értesírés
HUD notifications= HUD értesítés
Sound notifications=Hang értesítés
Message list=Üzenetek listája
Show unread in different color=Mutasd a nem olvasottakat más színnel
Show CC/BCC in different color=Mutasd a CC-t/BCC-t más színnel
Default sorting fields=Alap válogató terület
Other=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=Viszaállítás
Settings=Beállítások
About=Róla
Save=Mentés
Ascending=Emelkedő
Descending=Sűlyedő
From/To=Tól(től)/neki
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=

101
mail/locale/mail.pt_BR.tr Normal file
View file

@ -0,0 +1,101 @@
# textdomain: mail
Provided by mt-mods=
Version=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=A comunicação usando este sistema não possui garantia de privacidade
Admins are able to view the messages of any player.=Administradores poderão ler as mensagens de qualquer jogador
Contributors=
You have a new message from @1! Subject: @2=Você tem uma mensagem de @1! Assunto: @2
To view it, type /mail=Para visualizar a mensagem, digite /mail
You could also use the button in your inventory.=Você também pode usar o botão do seu inventário.
BCC=BCC
Cancel=Cancelar
Save draft=Salvar rascunho
Send=Enviar
No contacts=
Name=Nome
No drafts=Sem rascunhos
Edit=Editar
To=Para
Player name=Nome do jogador
That name is already in your contacts=Esse nome já consta em sua lista de contatos
The contact name cannot be empty.=Informe o nome do destinatário
Note=Nota
Maillist name=Nome da lista de discussão
Desc=Descrição
Players=Jogador
That name is already in your mailing lists.=Esse nome ja está sendo usado em sua lista de discussões
The mailing list name cannot be empty.=O nome da lista de discussões deve ser informado
Back=Voltar
Mark Read=Marcar como lido
Mark Unread=Marcar como não lido
Trash=
Inbox=Entrada
Outbox=Enviadas
Drafts=Rascunhos
Contacts=Contatos
Mail lists=Lista de correios
Options=Opções
Close=Fechar
(No subject)=(Sem assunto)
Subject=Assunto
Delete=Apagar
New=Novo
Date=Data
Filter=Filtrar
Allow multiple selection=Permitir selecionar vários
@1 of @2 selected=
(Un)select all=Desmarcar todos
No mail=Sem e-mails no momento
Reply=Responder
Reply all=Responder Todos
Forward=Encaminhar
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=Ler
From=De
(No description)=(sem descrição)
No maillist=Sem lista de discussão
Receivers=
You have mail! Type /mail to read=Você recebeu e-mail! Tecle /mail para ler
Add=Adicionar
Remove=Remover
CC=CC
Notifications=Notificações
Chat notifications=Notificação de conversa
On join notifications=Notificação ao entrar
HUD notifications=Notificação no HUD
Sound notifications=
Message list=Lista de mensagens
Show unread in different color=Exibir mensagens não lidas em uma cor diferente
Show CC/BCC in different color=Exibir mensagens com copia em uma cor diferente
Default sorting fields=Ordenamento de campos padrão
Other=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=Ajustes
About=Sobre
Save=Salvar
Ascending=Ascendente
Descending=Descendente
From/To=De/Para
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=

102
mail/locale/mail.zh_CN.tr Normal file
View file

@ -0,0 +1,102 @@
# textdomain: mail
Provided by mt-mods=
Version=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=
Admins are able to view the messages of any player.=
Contributors=
You have a new message from @1! Subject: @2=
To view it, type /mail=
You could also use the button in your inventory.=
BCC=密送
Cancel=取消
Save draft=保存草稿
Send=发送
No contacts=
Name=名字
#if new means new mail, it would be New=新邮件
No drafts=没有草稿
Edit=编辑
To=收件人
Player name=玩家名字
That name is already in your contacts=
The contact name cannot be empty.=
Note=备注
Maillist name=建组名
Desc=描述
Players=玩家
That name is already in your mailing lists.=
The mailing list name cannot be empty.=
Back=返回
Mark Read=标记为已读
Mark Unread=标记为未读
Trash=
Inbox=收件箱
Outbox=已发送
Drafts=草稿
Contacts=通讯录
Mail lists=建组
Options=
Close=关闭
(No subject)=(无主题)
Subject=主题
Delete=删除
New=新
Date=时间
Filter=筛选
Allow multiple selection=允许多选
@1 of @2 selected=
(Un)select all=(取消)选中所有
No mail=无邮件
Reply=回复
Reply all=回复所有
Forward=转发
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=浏览
From=发件人
(No description)=(无描述)
No maillist=无建组
Receivers=
You have mail! Type /mail to read=
Add=添加
Remove=移除
CC=抄送
Notifications=
Chat notifications=
On join notifications=
HUD notifications=
Sound notifications=
Message list=
Show unread in different color=
Show CC/BCC in different color=
Default sorting fields=
Other=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=
About=关于
Save=保存
Ascending=升序
Descending=降序
From/To=
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=

101
mail/locale/mail.zh_TW.tr Normal file
View file

@ -0,0 +1,101 @@
# textdomain: mail
Provided by mt-mods=
Version=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=
Admins are able to view the messages of any player.=
Contributors=
You have a new message from @1! Subject: @2=
To view it, type /mail=
You could also use the button in your inventory.=
BCC=密件副本
Cancel=取消
Save draft=儲存草稿
Send=發送
No contacts=
Name=名稱
No drafts=沒有草稿
Edit=編輯
To=收件人
Player name=玩家名稱
That name is already in your contacts=
The contact name cannot be empty.=
Note=備註
Maillist name=郵件列表名稱
Desc=描述
Players=玩家
That name is already in your mailing lists.=
The mailing list name cannot be empty.=
Back=返回
Mark Read=標記已讀
Mark Unread=標記未讀
Trash=
Inbox=收件箱
Outbox=寄件備份
Drafts=草稿
Contacts=聯繫人
Mail lists=郵件列表
Options=
Close=關閉
(No subject)=(沒有主旨)
Subject=主旨
Delete=刪除
New=新建
Date=日期
Filter=
Allow multiple selection=
@1 of @2 selected=
(Un)select all=
No mail=沒有郵件
Reply=回覆
Reply all=回覆所有人
Forward=轉寄
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=閱讀
From=寄件者
(No description)=(沒有描述)
No maillist=沒有郵件列表
Receivers=
You have mail! Type /mail to read=
Add=加入
Remove=移除
CC=副本
Notifications=
Chat notifications=
On join notifications=
HUD notifications=
Sound notifications=
Message list=
Show unread in different color=
Show CC/BCC in different color=
Default sorting fields=
Other=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=
About=關於
Save=儲存
Ascending=
Descending=
From/To=
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=

101
mail/locale/template.txt Normal file
View file

@ -0,0 +1,101 @@
# textdomain: mail
Provided by mt-mods=
Version=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=
Admins are able to view the messages of any player.=
Contributors=
You have a new message from @1! Subject: @2=
To view it, type /mail=
You could also use the button in your inventory.=
BCC=
Cancel=
Save draft=
Send=
No contacts=
Name=
No drafts=
Edit=
To=
Player name=
That name is already in your contacts=
The contact name cannot be empty.=
Note=
Maillist name=
Desc=
Players=
That name is already in your mailing lists.=
The mailing list name cannot be empty.=
Back=
Mark Read=
Mark Unread=
Trash=
Inbox=
Outbox=
Drafts=
Contacts=
Mail lists=
Options=
Close=
(No subject)=
Subject=
Delete=
New=
Date=
Filter=
Allow multiple selection=
@1 of @2 selected=
(Un)select all=
No mail=
Reply=
Reply all=
Forward=
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=
From=
(No description)=
No maillist=
Receivers=
You have mail! Type /mail to read=
Add=
Remove=
CC=
Notifications=
Chat notifications=
On join notifications=
HUD notifications=
Sound notifications=
Message list=
Show unread in different color=
Show CC/BCC in different color=
Default sorting fields=
Other=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=
About=
Save=
Ascending=
Descending=
From/To=
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=

76
mail/mail.hu.tr Normal file
View file

@ -0,0 +1,76 @@
# textdomain: mail
# author: nyomi
Provided by mt-mods=Feltéve, hogy az én mt-mod-om
Version=Verzió
Licenses=License
Expat (code), WTFPL (textures)=Expat (kód), WTFPL (textúrák)
Communication using this system is NOT guaranteed to be private!=A systemben lévő komunikáció nem garantáltan privát!
Admins are able to view the messages of any player.=Az adminok megtudják nézni minden játékos üzenetjét.
Contributors=Közreműködöttek
Note=Jegyzet
Settings=Beállítások
About=Róla
You have a new message from @1! Subject: @2=Van egy új üzeneted @1-től Cím: @2
To view it, type /mail=Ahhoz hogy megnézd, írd /mail
You could also use the button in your inventory.=A gombot is tudod használni az inventoridban.
BCC=BCC
Cancel=Mégse
Save draft=mentés piszkozatként
Send=Küldés
Subject=Cím
To=Neki
CC=CC
Name=Név
No drafts=Nincsenek piszkozatok
Edit=Szerkesztés
New=Új
Delete=Törlés
Inbox=PostaLáda
Outbox=Elküldött üzenetek
Drafts=Piszkozatok
Contacts=Contaktok
Mail lists=Levelező lista
Options=Lehetőségek
Close=Bezár
(No subject)=(nincs cím)
Player name=Játékos neve
That name is already in your contacts=A név már a kontaktok között van
The contact name cannot be empty.=A contakt neve nem lehet üres.
Save=Mentés
Maillist name=Levelező lista neve
Desc=Desc
Players=Játékosok
That name is already in your mailing lists.=A név már benne van a levelező listában
The mailing list name cannot be empty.=A levelező lista neve nem lehet üres
Mark Read=Jelöld olvasottként
Mark Unread=Jelöld olvasatlanul
From=Tőle
Read=Olvasott
Filter=Filterek
Allow multiple selection=
@1 selected=@1 kiválasztva
(Un)select all=(ne válaszd ki) mindegyik választása
No mail=Nincs levél
Reply=Válasz
Reply all=Válaszmindenkinek
Forward=Továbbítás
Date=Dátum
Ascending=Emelkedő
Descending=Sűlyedő
(No description)=(Nincs leírás)
No maillist=Nincs levelező lista
You have mail! Type /mail to read=Van egy leveled! Írd /mail az olvasáshoz
Add=Hozzáadás
Remove=Elvétel
Back=Visza
Notifications=Értesítések
Chat notifications=Chates értesítések
On join notifications=Belépési értesírés
HUD notifications= HUD értesítés
Sound notifications=Hang értesítés
Message list=Üzenetek listája
Show unread in different color=Mutasd a nem olvasottakat más színnel
Show CC/BCC in different color=Mutasd a CC-t/BCC-t más színnel
Default sorting fields=Alap válogató terület
From/To=Tól(től)/neki
Reset=Viszaállítás

99
mail/migrate.lua Normal file
View file

@ -0,0 +1,99 @@
local STORAGE_VERSION_KEY = "@@version"
local function migrate_v1_to_v3()
local file = io.open(minetest.get_worldpath().."/mail.db", "r")
assert(file)
print("[mail] Migration from v1 to v3 database")
local data = file:read("*a")
local oldmails = minetest.deserialize(data)
file:close()
for name, oldmessages in pairs(oldmails) do
print("[mail,v1] + migrating player '" .. name .. "'")
local entry = mail.get_storage_entry(name)
for _, msg in ipairs(oldmessages) do
table.insert(entry.inbox, {
id = mail.new_uuid(),
from = msg.sender or msg.from,
to = msg.to or name,
subject = msg.subject,
body = msg.body,
time = msg.time or os.time(),
})
end
mail.set_storage_entry(name, entry)
end
-- rename file
print("[mail,v1] migration done, renaming old mail.db")
os.rename(minetest.get_worldpath().."/mail.db", minetest.get_worldpath().."/mail.db.old")
end
local function read_json_file(path)
local file = io.open(path, "r")
local content = {}
if file then
local json = file:read("*a")
content = minetest.parse_json(json or "[]") or {}
file:close()
end
return content
end
-- migrate from v2 to v3 database
local function migrate_v2_to_v3()
local maildir = minetest.get_worldpath().."/mails"
minetest.mkdir(maildir) -- if necessary (eg. first login)
print("[mail] Migration from v2 to v3 database")
-- defer execution until auth-handler ready (first server-step)
minetest.after(0, function()
for playername, _ in minetest.get_auth_handler().iterate() do
local entry = mail.get_storage_entry(playername)
local player_contacts = read_json_file(maildir .. "/contacts/" .. playername .. ".json")
for _, c in pairs(player_contacts) do
table.insert(entry.contacts, { name = c.name, note = c.note })
end
local saneplayername = string.gsub(playername, "[.|/]", "")
local player_inbox = read_json_file(maildir .. "/" .. saneplayername .. ".json")
print("[mail,v2] + migrating player '" .. playername .. "'")
for _, msg in ipairs(player_inbox) do
table.insert(entry.inbox, {
id = mail.new_uuid(),
from = msg.sender or msg.from,
to = msg.to or playername,
cc = msg.cc,
subject = msg.subject,
body = msg.body,
time = msg.time or os.time(),
read = not msg.unread,
})
end
mail.set_storage_entry(playername, entry)
end
print("[mail,v2] migration done")
end)
end
function mail.migrate()
-- check for v2 storage first, v1-migration might have set the v3-flag already
local version = mail.storage:get_int(STORAGE_VERSION_KEY)
if version < 3 then
-- v2 to v3
migrate_v2_to_v3()
mail.storage:set_int(STORAGE_VERSION_KEY, 3)
end
-- check for v1 storage
local v1_file = io.open(minetest.get_worldpath().."/mail.db", "r")
if v1_file then
-- v1 to v3
migrate_v1_to_v3()
mail.storage:set_int(STORAGE_VERSION_KEY, 3)
end
end

28
mail/migrate.spec.lua Normal file
View file

@ -0,0 +1,28 @@
mtt.register("migrate v1", function(callback)
local entry = mail.get_storage_entry("old_v1_player")
assert(entry)
assert(#entry.inbox == 1)
assert(entry.inbox[1].from == "singleplayer")
assert(entry.inbox[1].to == "old_v1_player")
assert(entry.inbox[1].subject == "test1")
assert(entry.inbox[1].body == "test2")
assert(entry.inbox[1].id)
assert(entry.inbox[1].time > 0)
callback()
end)
mtt.register("migrate v2", function(callback)
local entry = mail.get_storage_entry("old_v2_player")
assert(entry)
assert(#entry.inbox == 1)
assert(entry.inbox[1].from == "someone-else")
assert(entry.inbox[1].to == "old_v2_player")
assert(entry.inbox[1].subject == "test1")
assert(entry.inbox[1].body == "test2")
assert(entry.inbox[1].id)
assert(entry.inbox[1].time == 1678467148)
callback()
end)

6
mail/mod.conf Normal file
View file

@ -0,0 +1,6 @@
name = mail
description = ingame mail-system
optional_depends = canonical_name,default,mtt,unified_inventory
release = 19819
author = mt-mods
title = Mail

10
mail/mtt.lua Normal file
View file

@ -0,0 +1,10 @@
mtt.register("setup", function(callback)
-- create test players
local auth_handler = minetest.get_auth_handler()
auth_handler.set_password("player1", "")
auth_handler.set_password("player2", "")
auth_handler.set_password("player3", "")
callback()
end)

23
mail/onjoin.lua Normal file
View file

@ -0,0 +1,23 @@
-- translation
local S = minetest.get_translator("mail")
minetest.register_on_joinplayer(function(player)
minetest.after(2, function(name)
local entry = mail.get_storage_entry(name)
local messages = entry.inbox
mail.hud_update(name, messages)
local unreadcount = 0
for _, message in pairs(messages) do
if not message.read then
unreadcount = unreadcount + 1
end
end
if unreadcount > 0 and mail.get_setting(name, "onjoin_notifications") then
minetest.chat_send_player(name,
minetest.colorize(mail.colors.new, "(" .. unreadcount .. ") " .. S("You have mail! Type /mail to read")))
end
end, player:get_player_name())
end)

BIN
mail/screenshot_1.1.0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

BIN
mail/screenshot_1.2.0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

BIN
mail/screenshot_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

BIN
mail/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
mail/sounds/mail_notif.ogg Normal file

Binary file not shown.

453
mail/storage.lua Normal file
View file

@ -0,0 +1,453 @@
-- storage getter/setter
local STORAGE_PREFIX = "mail/"
-- create or populate empty fields on an entry
local function populate_entry(e)
e = e or {}
e.contacts = e.contacts or {}
e.inbox = e.inbox or {}
e.outbox = e.outbox or {}
e.drafts = e.drafts or {}
e.trash = e.trash or {}
e.lists = e.lists or {}
e.settings = e.settings or {}
return e
end
local cache = {}
-- retrieve the storage entry for the player
function mail.get_storage_entry(playername)
local key = STORAGE_PREFIX .. playername
if cache[key] then
-- use cached entry
return cache[key]
end
local str = mail.storage:get_string(key)
local entry
if str == "" then
-- new entry
entry = populate_entry()
else
-- deserialize existing entry
local e = minetest.parse_json(str)
entry = populate_entry(e)
end
-- cache for next time
cache[key] = entry
return entry
end
-- entries queued for saving
local save_queued_entries = {}
-- save the storage entry for the player
function mail.set_storage_entry(playername, entry)
local key = STORAGE_PREFIX .. playername
-- cache
cache[key] = entry
-- enqueue for writing
save_queued_entries[key] = entry
end
local function save_worker()
for key, entry in pairs(save_queued_entries) do
-- write to backend
mail.storage:set_string(key, minetest.write_json(entry))
end
-- clear queue
save_queued_entries = {}
-- save every second
minetest.after(1, save_worker)
end
-- start save-worker loop
save_worker()
-- save on shutdown
minetest.register_on_shutdown(save_worker)
-- get a mail by id from the players in- or outbox
function mail.get_message(playername, msg_id)
local entry = mail.get_storage_entry(playername)
for _, msg in ipairs(entry.inbox) do
if msg.id == msg_id then
return msg
end
end
for _, msg in ipairs(entry.outbox) do
if msg.id == msg_id then
return msg
end
end
for _, msg in ipairs(entry.drafts) do
if msg.id == msg_id then
return msg
end
end
for _, msg in ipairs(entry.trash) do
if msg.id == msg_id then
return msg
end
end
end
-- get player boxes where a message appears
function mail.get_message_boxes(playername, msg_id)
local entry = mail.get_storage_entry(playername)
local boxes = {}
for _, msg in ipairs(entry.inbox) do
if msg.id == msg_id then
table.insert(boxes, "inbox")
end
end
for _, msg in ipairs(entry.outbox) do
if msg.id == msg_id then
table.insert(boxes, "outbox")
end
end
for _, msg in ipairs(entry.drafts) do
if msg.id == msg_id then
table.insert(boxes, "drafts")
end
end
for _, msg in ipairs(entry.trash) do
if msg.id == msg_id then
table.insert(boxes, "trash")
end
end
return boxes
end
local function safe_find(str, sub)
return str and sub and str:find(sub, 1, true) or nil
end
function mail.sort_messages(messages, sortfield, descending, filter)
local results = {}
-- Filtering
if filter and filter ~= "" then
for _, msg in ipairs(messages) do
if safe_find(msg.from, filter) or safe_find(msg.to, filter) or safe_find(msg.subject, filter) then
table.insert(results, msg)
end
end
else
for i = 1, #messages do
results[i] = messages[i]
end
end
-- Sorting
if sortfield ~= nil then
if descending then
table.sort(results, function(a, b) return a[sortfield] > b[sortfield] end)
else
table.sort(results, function(a, b) return a[sortfield] < b[sortfield] end)
end
end
return results
end
-- marks a mail read by its id
function mail.mark_read(playername, msg_ids)
local entry = mail.get_storage_entry(playername)
if type(msg_ids) ~= "table" then -- if this is not a table
msg_ids = { msg_ids }
end
for _, read_msg_id in ipairs(msg_ids) do
for _, entry_msg in ipairs(entry.inbox) do
if entry_msg.id == read_msg_id then
entry_msg.read = true
end
end
end
mail.set_storage_entry(playername, entry)
mail.hud_update(playername, entry.inbox)
return
end
-- marks a mail unread by its id
function mail.mark_unread(playername, msg_ids)
local entry = mail.get_storage_entry(playername)
if type(msg_ids) ~= "table" then -- if this is not a table
msg_ids = { msg_ids }
end
for _, unread_msg_id in ipairs(msg_ids) do
for _, entry_msg in ipairs(entry.inbox) do
if entry_msg.id == unread_msg_id then
entry_msg.read = false
end
end
end
mail.set_storage_entry(playername, entry)
return
end
-- deletes a mail by its id
function mail.delete_mail(playername, msg_ids, delete_in_trash)
local entry = mail.get_storage_entry(playername)
if type(msg_ids) ~= "table" then -- if this is not a table
msg_ids = { msg_ids }
end
for i = #entry.inbox, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.inbox[i].id == deleted_msg then
table.remove(entry.inbox, i)
break
end
end
end
for i = #entry.outbox, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.outbox[i].id == deleted_msg then
table.remove(entry.outbox, i)
break
end
end
end
for i = #entry.drafts, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.drafts[i].id == deleted_msg then
table.remove(entry.drafts, i)
break
end
end
end
if delete_in_trash then
for i = #entry.trash, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.trash[i].id == deleted_msg then
table.remove(entry.trash, i)
break
end
end
end
end
mail.set_storage_entry(playername, entry)
mail.hud_update(playername, entry.inbox)
return
end
-- move to trash mails by id
function mail.trash_mail(playername, msg_ids)
local entry = mail.get_storage_entry(playername)
if type(msg_ids) ~= "table" then -- if this is not a table
msg_ids = { msg_ids }
end
for _, id in ipairs(msg_ids) do
local msg = mail.get_message(playername, id)
msg.previous_boxes = mail.get_message_boxes(playername, id)
table.insert(entry.trash, 1, msg)
end
mail.set_storage_entry(playername, entry)
mail.delete_mail(playername, msg_ids)
return
end
-- restore a mail from trash
function mail.restore_mail(playername, msg_id)
local entry = mail.get_storage_entry(playername)
for i, msg in ipairs(entry.trash) do
if msg.id == msg_id then
-- not anymore store previous boxes in json
local previous_boxes = msg.previous_boxes
msg.previous_boxes = nil
-- restore it in all previous boxes
for _, box in ipairs(previous_boxes) do
table.insert(entry[box], msg)
end
-- then delete it from trash
table.remove(entry.trash, i)
end
end
mail.set_storage_entry(playername, entry)
return
end
-- clear the trash
function mail.empty_trash(playername)
local entry = mail.get_storage_entry(playername)
entry.trash = {}
mail.set_storage_entry(playername, entry)
return
end
-- add or update a contact
function mail.update_contact(playername, contact)
local entry = mail.get_storage_entry(playername)
local existing_updated = false
for i, existing_contact in ipairs(entry.contacts) do
if existing_contact.name == contact.name then
-- update
entry.contacts[i] = contact
existing_updated = true
break
end
end
if not existing_updated then
-- insert
table.insert(entry.contacts, contact)
end
mail.set_storage_entry(playername, entry)
end
-- deletes a contact
function mail.delete_contact(playername, contactname)
local entry = mail.get_storage_entry(playername)
for i, existing_contact in ipairs(entry.contacts) do
if existing_contact.name == contactname then
-- delete
table.remove(entry.contacts, i)
mail.set_storage_entry(playername, entry)
return
end
end
end
-- get all contacts
function mail.get_contacts(playername)
local entry = mail.get_storage_entry(playername)
return entry.contacts
end
-- get a contact
function mail.get_contact(playername, contactname)
local entry = mail.get_storage_entry(playername)
for _, existing_contact in ipairs(entry.contacts) do
if existing_contact.name == contactname then
return existing_contact
end
end
return false
end
-- returns the maillists of a player
function mail.get_maillists(playername)
local entry = mail.get_storage_entry(playername)
return entry.lists
end
-- returns the maillists of a player
function mail.get_maillist_by_name(playername, listname)
local entry = mail.get_storage_entry(playername)
for _, list in ipairs(entry.lists) do
if list.name == listname then
return list
end
end
end
-- updates or creates a maillist
function mail.update_maillist(playername, list, old_list_name)
local entry = mail.get_storage_entry(playername)
for i, existing_list in ipairs(entry.lists) do
if existing_list.name == old_list_name then
-- delete
table.remove(entry.lists, i)
break
end
end
-- insert
table.insert(entry.lists, list)
mail.set_storage_entry(playername, entry)
end
function mail.delete_maillist(playername, listname)
local entry = mail.get_storage_entry(playername)
for i, list in ipairs(entry.lists) do
if list.name == listname then
-- delete
table.remove(entry.lists, i)
mail.set_storage_entry(playername, entry)
return
end
end
end
function mail.extractMaillists(receivers_string, maillists_owner)
local receivers = mail.parse_player_list(receivers_string) -- extracted receivers
-- extract players from mailing lists
while string.find(receivers_string, "@") do
local globalReceivers = mail.parse_player_list(receivers_string) -- receivers including maillists
receivers = {}
for _, receiver in ipairs(globalReceivers) do
local receiverInfo = receiver:split("@") -- @maillist
if receiverInfo[1] and receiver == "@" .. receiverInfo[1] then
local maillist = mail.get_maillist_by_name(maillists_owner, receiverInfo[1])
if maillist then
for _, playername in ipairs(maillist.players) do
table.insert(receivers, playername)
end
end
else -- in case of player
table.insert(receivers, receiver)
end
end
receivers_string = mail.concat_player_list(receivers)
end
return receivers
end
function mail.get_setting_default_value(setting_name)
local default_values = {
chat_notifications = true,
onjoin_notifications = true,
hud_notifications = true,
sound_notifications = true,
unreadcolorenable = true,
cccolorenable = true,
defaultsortfield = 3,
defaultsortdirection = 1,
trash_move_enable = true,
auto_marking_read = true,
date_format = "%Y-%m-%d %X",
}
return default_values[setting_name]
end
function mail.get_setting(playername, setting_name)
local entry = mail.get_storage_entry(playername)
if entry.settings[setting_name] ~= nil then
return entry.settings[setting_name]
else
return mail.get_setting_default_value(setting_name)
end
end
-- add or update a setting
function mail.set_setting(playername, key, value)
local entry = mail.get_storage_entry(playername)
entry.settings[key] = value
mail.set_storage_entry(playername, entry)
end
function mail.reset_settings(playername)
local entry = mail.get_storage_entry(playername)
entry.settings = {}
mail.set_storage_entry(playername, entry)
end
function mail.pairsByKeys(t, f)
-- http://www.lua.org/pil/19.3.html
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function() -- iterator function
i = i + 1
if a[i] == nil then
return nil
else
--return a[i], t[a[i]]
-- add the current position and the length for convenience
return a[i], t[a[i]], i, #a
end
end
return iter
end

45
mail/storage.spec.lua Normal file
View file

@ -0,0 +1,45 @@
mtt.register("storage", function(callback)
-- sanity checks
local playername = "player1"
local entry = mail.get_storage_entry(playername)
assert(entry)
-- create
local contact = {
name = "other-player",
note = "my-note"
}
mail.update_contact(playername, contact)
-- read
local contacts = mail.get_contacts(playername)
assert(#contacts == 1)
assert(contacts[1].note == contact.note)
assert(contacts[1].name == contact.name)
-- read through api
local contacts2 = mail.get_contacts(playername)
assert(#contacts2 == 1)
assert(contacts2[1].note == contact.note)
assert(contacts2[1].name == contact.name)
-- update
mail.update_contact(playername, {
name = contact.name,
note = "xy"
})
-- read updated
contacts = mail.get_contacts(playername)
assert(#contacts == 1)
assert(contacts[1].note == "xy")
assert(contacts[1].name == contact.name)
-- delete
mail.delete_contact(playername, contact.name)
contacts = mail.get_contacts(playername)
assert(#contacts == 0)
callback()
end)

15
mail/test/Dockerfile Normal file
View file

@ -0,0 +1,15 @@
ARG ENGINE_VERSION=5.7.0
FROM registry.gitlab.com/minetest/minetest/server:${ENGINE_VERSION}
# copy old v1 maildb for migration testing
COPY ./mail.db /root/.minetest/worlds/world/mail.db
# copy old v2 mail-dir and auth.sqlite for migration testing
COPY ./old_v2_player.json /root/.minetest/worlds/world/mails/
COPY ./auth.sqlite /root/.minetest/worlds/world/auth.sqlite
USER root
RUN apk add git &&\
mkdir -p /root/.minetest/worlds/world/worldmods/ &&\
git clone https://github.com/BuckarooBanzay/mtt /root/.minetest/worlds/world/worldmods/mtt
ENTRYPOINT minetestserver --config /minetest.conf

BIN
mail/test/auth.sqlite Normal file

Binary file not shown.

1
mail/test/mail.db Normal file
View file

@ -0,0 +1 @@
local _={};_[1]="singleplayer";return {old_v1_player={{unread=true,subject="test1",sender=_[1],body="test2"}},[_[1]]={}}

4
mail/test/minetest.conf Normal file
View file

@ -0,0 +1,4 @@
default_game = minetest_game
mg_name = v7
mtt_enable = true
mtt_filter = mail

View file

@ -0,0 +1 @@
[{"body":"test2","sender":"someone-else","subject":"test1","time":1678467148,"unread":false}]

9
mail/test/world.mt Normal file
View file

@ -0,0 +1,9 @@
enable_damage = false
creative_mode = true
mod_storage_backend = sqlite3
auth_backend = sqlite3
player_backend = dummy
backend = dummy
gameid = minetest
world_name = mapblock_lib
server_announce = false

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

69
mail/ui/about.lua Normal file
View file

@ -0,0 +1,69 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:about"
function mail.show_about(name)
local formspec = [[
size[10,6;]
tabheader[0.3,1;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;2;false;false]
button[9.35,0;0.75,0.5;back;X]
label[0,0.8;Mail]
label[0,1.2;]] .. S("Provided by mt-mods") .. [[]
label[0,1.6;]] .. S("Version") .. [[ : 1.3.0]
label[0,2.2;]] .. S("Licenses") .. [[ :]
label[0.2,2.6;]] .. S("Expat (code), WTFPL (textures)") .. [[]
label[0,3.2;https://github.com/mt-mods/mail]
label[0,3.6;https://content.minetest.net/packages/mt-mods/mail]
textarea[0.5,4.8;4,5.5;;]] .. S("Note") .. [[;]] ..
S("Communication using this system is NOT guaranteed to be private!") .. " " ..
S("Admins are able to view the messages of any player.") .. [[]
tablecolumns[color;text;text]
table[5,0.75;4.9,5.5;contributors;]] ..
mail.colors.header .. [[,]] .. S("Contributors") .. [[,,]] ..
mail.colors.important .. [[,Cheapie,Initial idea/project,]] ..
[[,Rubenwardy,Lua/UI improvements,]] ..
[[,BuckarooBanzay,Clean-ups\, Refactoring,]] ..
[[,Athozus,Boxes\, Maillists\, UI\, Settings,]] ..
[[,fluxionary,Minor fixups,]] ..
[[,SX,Various fixes\, UI,]] ..
[[,Toby1710,UX fixes,]] ..
[[,Peter Nerlich,CC\, BCC,]] ..
[[,Niklp,German translation,]] ..
[[,Emojigit,Traditional Chinese trans.,]] ..
[[,Dennis Jenkins,UX fixes,]] ..
[[,Thomas Rudin,Maintenance,]] ..
[[,NatureFreshMilk,Maintenance,]] ..
[[,imre84,UI fixes,]] ..
[[,Chache,Spanish translation,]] ..
[[,APercy,Brazilian Portuguese trans.,]] ..
[[,Nuno Filipe Povoa,mail_notif.ogg,]] ..
[[,TheTrueBeginner,Simplified Chinese trans.,]] ..
[[,nyomi,Hungarian translation,]] ..
[[,whosit,UI fixes,]] ..
[[,Wuzzy,German translation]
]] .. mail.theme
minetest.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local playername = player:get_player_name()
if fields.back then
mail.show_mail_menu(playername)
elseif fields.optionstab == "1" then
mail.selected_idxs.optionstab[playername] = 1
mail.show_settings(playername)
elseif fields.optionstab == "2" then
mail.selected_idxs.optionstab[playername] = 2
mail.show_about(playername)
end
end)

137
mail/ui/compose.lua Normal file
View file

@ -0,0 +1,137 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:compose"
local msg_id = {}
function mail.show_compose(name, to, subject, body, cc, bcc, id)
local formspec = [[
size[8,9]
button[0,0;1,1;tocontacts;]] .. S("To") .. [[:]
field[1.1,0.3;3.2,1;to;;%s]
button[4,0;1,1;cccontacts;]] .. S("CC") .. [[:]
field[5.1,0.3;3.1,1;cc;;%s]
button[4,0.75;1,1;bcccontacts;]] .. S("BCC") .. [[:]
field[5.1,1.05;3.1,1;bcc;;%s]
field[0.25,2;8,1;subject;]] .. S("Subject") .. [[:;%s]
textarea[0.25,2.5;8,6;body;;%s]
button[0.1,8.5;2.5,1;cancel;]] .. S("Cancel") .. [[]
button[2.7,8.5;2.5,1;draft;]] .. S("Save draft") .. [[]
button[5.3,8.5;2.5,1;send;]] .. S("Send") .. [[]
]] .. mail.theme
formspec = string.format(formspec,
minetest.formspec_escape(to) or "",
minetest.formspec_escape(cc) or "",
minetest.formspec_escape(bcc) or "",
minetest.formspec_escape(subject) or "",
minetest.formspec_escape(body) or "")
if id then
msg_id[name] = id
else
msg_id[name] = nil
end
minetest.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
if fields.send then
local id = mail.new_uuid()
if msg_id[name] then
id = msg_id[name]
end
if (fields.to == "" and fields.cc == "" and fields.bcc == "") or fields.body == "" then
-- if mail is invalid then store it as a draft
mail.save_draft({
id = id,
from = name,
to = fields.to,
cc = fields.cc,
bcc = fields.bcc,
subject = fields.subject,
body = fields.body
})
mail.show_mail_menu(name)
return
end
local success, err = mail.send({
id = id,
from = name,
to = fields.to,
cc = fields.cc,
bcc = fields.bcc,
subject = fields.subject,
body = fields.body,
})
if not success then
minetest.chat_send_player(name, err)
return
end
-- add new contacts if some receivers aren't registered
local contacts = mail.get_contacts(name)
local recipients = mail.parse_player_list(fields.to)
local isNew = true
for _,recipient in ipairs(recipients) do
if recipient:sub(1,1) == "@" then -- in case of maillist -- check if first char is @
isNew = false
else
for _,contact in ipairs(contacts) do
if contact.name == recipient then
isNew = false
break
end
end
end
if isNew then
mail.update_contact(name, {name = recipient, note = ""})
end
end
minetest.after(0.5, function()
mail.selected_idxs.drafts[name] = nil
mail.show_mail_menu(name)
end)
elseif fields.tocontacts or fields.cccontacts or fields.bcccontacts then
mail.message_drafts[name] = {
to = fields.to,
cc = fields.cc,
bcc = fields.bcc,
subject = fields.subject,
body = fields.body,
}
mail.show_select_contact(name, fields.to, fields.cc, fields.bcc)
elseif fields.cancel then
mail.message_drafts[name] = nil
mail.show_mail_menu(name)
elseif fields.draft then
local id = mail.new_uuid()
if msg_id[name] then
id = msg_id[name]
end
mail.save_draft({
id = id,
from = name,
to = fields.to,
cc = fields.cc,
bcc = fields.bcc,
subject = fields.subject,
body = fields.body
})
mail.show_mail_menu(name)
end
return true
end)

86
mail/ui/contacts.lua Normal file
View file

@ -0,0 +1,86 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:contacts"
local contacts_formspec = "size[8,9;]" .. mail.theme .. [[
button[6,0.10;2,0.5;new;]] .. S("New") .. [[]
button[6,0.85;2,0.5;edit;]] .. S("Edit") .. [[]
button[6,1.60;2,0.5;delete;]] .. S("Delete") .. [[]
button[6,8.25;2,0.5;back;]] .. S("Back") .. [[]
tablecolumns[color;text;text]
table[0,0;5.75,9;contacts;]] .. mail.colors.header .. "," .. S("Name") .. "," .. S("Note")
function mail.show_contacts(name)
local formspec = contacts_formspec .. mail.compile_contact_list(name, mail.selected_idxs.contacts[name])
minetest.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local contacts = mail.get_contacts(name)
if fields.contacts then
local evt = minetest.explode_table_event(fields.contacts)
for k, _, i in mail.pairsByKeys(contacts) do
if i == evt.row - 1 then
mail.selected_idxs.contacts[name] = tonumber(k)
break
end
end
if evt.type == "DCL" and contacts[mail.selected_idxs.contacts[name]] then
mail.show_edit_contact(
name,
contacts[mail.selected_idxs.contacts[name]].name,
contacts[mail.selected_idxs.contacts[name]].note
)
end
elseif fields.new then
mail.selected_idxs.contacts[name] = "#NEW#"
mail.show_edit_contact(name, "", "")
elseif fields.edit and mail.selected_idxs.contacts[name] and contacts[mail.selected_idxs.contacts[name]] then
mail.show_edit_contact(
name,
contacts[mail.selected_idxs.contacts[name]].name,
contacts[mail.selected_idxs.contacts[name]].note
)
elseif fields.delete then
if contacts[mail.selected_idxs.contacts[name]] then
-- delete the contact and set the selected to the next in the list,
-- except if it was the last. Then determine the new last
local found = false
local last = nil
for k in mail.pairsByKeys(contacts) do
if found then
mail.selected_idxs.contacts[name] = tonumber(k)
break
elseif k == mail.selected_idxs.contacts[name] then
mail.delete_contact(name, contacts[mail.selected_idxs.contacts[name]].name)
mail.selected_idxs.contacts[name] = nil
found = true
else
last = tonumber(k)
end
end
if found and not mail.selected_idxs.contacts[name] then
-- was the last in the list, so take the previous (new last)
mail.selected_idxs.contacts[name] = last
end
end
mail.show_contacts(name)
elseif fields.back then
mail.show_mail_menu(name)
end
return true
end)

58
mail/ui/drafts.lua Normal file
View file

@ -0,0 +1,58 @@
-- translation
local S = minetest.get_translator("mail")
function mail.show_drafts(name)
local trash_tab = ""
if mail.get_setting(name, "trash_move_enable") then
trash_tab = "," .. S("Trash")
end
local drafts_formspec = "size[8.5,10;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. trash_tab .. [[;3;false;false]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;edit;]] .. S("Edit") .. [[]
button[6,1.70;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,9.35;drafts;]] .. mail.colors.header .. "," .. S("To") .. "," .. S("Subject")
local formspec = { drafts_formspec }
local entry = mail.get_storage_entry(name)
local messages = entry.drafts
mail.message_drafts[name] = nil
if messages[1] then
for _, message in ipairs(messages) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(message.to)
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
end
end
if mail.selected_idxs.drafts[name] then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(mail.selected_idxs.drafts[name] + 1)
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No drafts") .. "]"
end
minetest.show_formspec(name, "mail:drafts", table.concat(formspec, ""))
end

77
mail/ui/edit_contact.lua Normal file
View file

@ -0,0 +1,77 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:editcontact"
function mail.show_edit_contact(name, contact_name, note, illegal_name_hint)
local formspec = [[
size[6,7]
button[4,6.25;2,0.5;back;]] .. S("Back") .. [[]
field[0.25,0.5;4,1;name;]] .. S("Player name") .. [[:;%s]
textarea[0.25,1.6;4,6.25;note;]] .. S("Note") .. [[:;%s]
button[4,0.10;2,1;save;]] .. S("Save") .. [[]
]]
if illegal_name_hint == "collision" then
formspec = formspec .. [[
textarea[4.25,1;2.5,6;;;]] ..
S("That name is already in your contacts") .. [[]
]]
elseif illegal_name_hint == "empty" then
formspec = formspec .. [[
textarea[4.25,1;2.5,6;;;]] ..
S("The contact name cannot be empty.") .. [[]
]]
end
formspec = formspec .. mail.theme
formspec = string.format(formspec,
minetest.formspec_escape(contact_name or ""),
minetest.formspec_escape(note or ""))
minetest.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local contacts = mail.get_contacts(name)
if fields.save then
if mail.selected_idxs.contacts[name] then
local contact = contacts[mail.selected_idxs.contacts[name]] or {name = ""}
if contact.name ~= fields.name or fields.name == "" then
-- name changed!
if #fields.name == 0 then
mail.show_edit_contact(name, contact.name, fields.note, "empty")
return true
elseif mail.get_contact(name, fields.name) then
mail.show_edit_contact(name, contact.name, fields.note, "collision")
return true
else
contact.name = fields.name
contact.note = fields.note
mail.update_contact(name, contact)
contacts[mail.selected_idxs.contacts[name]] = nil
end
end
contact.name = fields.name
contact.note = fields.note
mail.update_contact(name, contact)
else
mail.update_contact(name, {
name = fields.name,
note = fields.note,
})
end
mail.show_contacts(name)
elseif fields.back then
mail.show_contacts(name)
end
return true
end)

View file

@ -0,0 +1,87 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:editmaillist"
function mail.show_edit_maillist(playername, maillist_name, desc, players, illegal_name_hint)
local formspec = [[
size[6,7]
button[4,6.25;2,0.5;back;]] .. S("Back") .. [[]
field[0.25,0.5;4,1;name;]] .. S("Maillist name") .. [[:;%s]
textarea[0.25,1.6;4,2;desc;]] .. S("Desc") .. [[:;%s]
textarea[0.25,3.6;4,4.25;players;]] .. S("Players") .. [[:;%s]
button[4,0.10;2,1;save;]] .. S("Save") .. [[]
]]
if illegal_name_hint == "collision" then
formspec = formspec .. [[
textarea[4.25,1;2.5,6;;;]] ..
S("That name is already in your mailing lists.") .. [[]
]]
elseif illegal_name_hint == "empty" then
formspec = formspec .. [[
textarea[4.25,1;2.5,6;;;]] ..
S("The mailing list name cannot be empty.") .. [[]
]]
end
formspec = formspec .. mail.theme
formspec = string.format(formspec,
minetest.formspec_escape(maillist_name or ""),
minetest.formspec_escape(desc or ""),
minetest.formspec_escape(players or ""))
minetest.show_formspec(playername, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local maillists = mail.get_maillists(name)
if fields.save then
local old_maillist = maillists[mail.selected_idxs.maillists[name]] or {name = ""}
if mail.selected_idxs.maillists[name] then
if old_maillist.name ~= fields.name or fields.name == "" then
-- name changed!
if #fields.name == 0 then
mail.show_edit_maillist(name, old_maillist.name, fields.desc, fields.players, "empty")
return true
elseif mail.get_maillist_by_name(name, fields.name) then
mail.show_edit_maillist(name, old_maillist.name, fields.desc, fields.players, "collision")
return true
else
mail.update_maillist(name, {
owner = name,
name = fields.name,
desc = fields.desc,
players = mail.parse_player_list(fields.players)
}, old_maillist.name)
maillists[mail.selected_idxs.maillists[name]] = nil
end
else
mail.update_maillist(name, {
owner = name,
name = fields.name,
desc = fields.desc,
players = mail.parse_player_list(fields.players)
}, old_maillist.name)
end
else
mail.update_maillist(name, {
owner = name,
name = fields.name,
desc = fields.desc,
players = mail.parse_player_list(fields.players)
}, old_maillist.name)
end
mail.show_maillists(name)
elseif fields.back then
mail.show_maillists(name)
end
return true
end)

337
mail/ui/events.lua Normal file
View file

@ -0,0 +1,337 @@
-- Getter to filter and sort messages on demand
local function messageGetter(messages, sortfield, ascending, filter)
local results
return function()
if not results then
results = mail.sort_messages(messages, sortfield, ascending, filter)
end
return results
end
end
local function nonempty(x)
return ((type(x)=="table")and(#x>0))
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "mail:inbox" and formname ~= "mail:outbox"
and formname ~= "mail:drafts" and formname ~= "mail:trash" then
return
elseif fields.quit then
return
end
-- Get player name and handle / convert common input fields
local name = player:get_player_name()
local filter = (fields.search and fields.filter) or mail.selected_idxs.filter[name] or ""
local sortfieldindex = tonumber(fields.sortfield or mail.selected_idxs.sortfield[name]) or 3
local sortdirection = fields.sortdirection or mail.selected_idxs.sortdirection[name] or "1"
local inboxsortfield = ({"from","subject","time"})[sortfieldindex]
local outboxsortfield = ({"to","subject","time"})[sortfieldindex]
-- Be sure that inbox/outbox selected idxs aren't nil
mail.selected_idxs.inbox[name] = mail.selected_idxs.inbox[name] or {}
mail.selected_idxs.outbox[name] = mail.selected_idxs.outbox[name] or {}
-- Store common player configuration for reuse
mail.selected_idxs.sortfield[name] = sortfieldindex
mail.selected_idxs.sortdirection[name] = sortdirection
mail.selected_idxs.filter[name] = filter
if fields.multipleselection then
mail.selected_idxs.multipleselection[name] = fields.multipleselection == "true"
end
-- Avoid several selected after disabling the multiple selection
if not mail.selected_idxs.multipleselection[name] then
mail.selected_idxs.inbox[name] = { mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]] }
mail.selected_idxs.outbox[name] = { mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]] }
end
-- split inbox and outbox msgs for different tests
local entry = mail.get_storage_entry(name)
local messagesDrafts = entry.drafts
local messagesTrash = entry.trash
local getInbox = messageGetter(entry.inbox, inboxsortfield, sortdirection == "2", filter)
local getOutbox = messageGetter(entry.outbox, outboxsortfield, sortdirection == "2", filter)
-- Hanmdle formspec event
if fields.inbox then -- inbox table
local evt = minetest.explode_table_event(fields.inbox)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
end
mail.selected_idxs.sortfield[name] = evt.column-1 -- update column
mail.show_mail_menu(name)
return
end
if mail.selected_idxs.multipleselection[name] then
if not mail.selected_idxs.inbox[name] then
mail.selected_idxs.inbox[name] = {}
end
local selected_id = 0
if mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.inbox[name]) do
if getInbox()[evt.row-1].id == selected_msg then
selected_id = i
table.remove(mail.selected_idxs.inbox[name], i)
break
end
end
end
if selected_id == 0 then
table.insert(mail.selected_idxs.inbox[name], getInbox()[evt.row-1].id)
end
else
mail.selected_idxs.inbox[name] = { (getInbox()[evt.row-1] or {}).id }
end
if evt.type == "DCL" and getInbox()[evt.row-1] then
mail.show_message(name, getInbox()[evt.row-1].id)
else
mail.show_mail_menu(name)
end
return true
end
if fields.outbox then -- outbox table
local evt = minetest.explode_table_event(fields.outbox)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
end
mail.selected_idxs.sortfield[name] = evt.column-1 -- update column
mail.show_mail_menu(name)
return
end
if mail.selected_idxs.multipleselection[name] then
if not mail.selected_idxs.outbox[name] then
mail.selected_idxs.outbox[name] = {}
end
local selected_id = 0
if mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.outbox[name]) do
if getOutbox()[evt.row-1].id == selected_msg then
selected_id = i
table.remove(mail.selected_idxs.outbox[name], i)
break
end
end
end
if selected_id == 0 then
table.insert(mail.selected_idxs.outbox[name], getOutbox()[evt.row-1].id)
end
else
mail.selected_idxs.outbox[name] = { (getOutbox()[evt.row-1] or {}).id }
end
if evt.type == "DCL" and getOutbox()[evt.row-1] then
mail.show_message(name, getOutbox()[evt.row-1].id)
else
mail.show_mail_menu(name)
end
return true
end
if fields.drafts then -- drafts table
local evt = minetest.explode_table_event(fields.drafts)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
end
mail.selected_idxs.sortfield[name] = evt.column-1 -- update column
mail.show_mail_menu(name)
return
end
mail.selected_idxs.drafts[name] = evt.row - 1
if evt.type == "DCL" and messagesDrafts[mail.selected_idxs.drafts[name]] then
mail.show_compose(name,
messagesDrafts[mail.selected_idxs.drafts[name]].to,
messagesDrafts[mail.selected_idxs.drafts[name]].subject,
messagesDrafts[mail.selected_idxs.drafts[name]].body,
messagesDrafts[mail.selected_idxs.drafts[name]].cc,
messagesDrafts[mail.selected_idxs.drafts[name]].bcc,
messagesDrafts[mail.selected_idxs.drafts[name]].id
)
end
return true
end
if fields.trash then -- trash table
local evt = minetest.explode_table_event(fields.trash)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
end
mail.selected_idxs.sortfield[name] = evt.column-1 -- update column
mail.show_mail_menu(name)
return
end
mail.selected_idxs.trash[name] = evt.row - 1
if evt.type == "DCL" and messagesTrash[mail.selected_idxs.trash[name]] then
mail.show_message(name, messagesTrash[mail.selected_idxs.trash[name]].id)
end
return true
end
if fields.boxtab == "1" then
mail.selected_idxs.boxtab[name] = 1
mail.show_inbox(name, sortfieldindex, sortdirection, filter)
elseif fields.boxtab == "2" then
mail.selected_idxs.boxtab[name] = 2
mail.show_outbox(name, sortfieldindex, sortdirection, filter)
elseif fields.boxtab == "3" then
mail.selected_idxs.boxtab[name] = 3
mail.show_drafts(name)
elseif fields.boxtab == "4" then
mail.selected_idxs.boxtab[name] = 4
mail.show_trash(name)
elseif fields.read then
if formname == "mail:inbox" and nonempty(mail.selected_idxs.inbox[name]) then -- inbox table
mail.show_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif formname == "mail:outbox" and nonempty(mail.selected_idxs.outbox[name]) then -- outbox table
mail.show_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
elseif formname == "mail:trash" and messagesTrash[mail.selected_idxs.trash[name]] then
mail.show_message(name, messagesTrash[mail.selected_idxs.trash[name]].id)
end
elseif fields.edit then
if formname == "mail:drafts" and messagesDrafts[mail.selected_idxs.drafts[name]] then
mail.show_compose(name,
messagesDrafts[mail.selected_idxs.drafts[name]].to,
messagesDrafts[mail.selected_idxs.drafts[name]].subject,
messagesDrafts[mail.selected_idxs.drafts[name]].body,
messagesDrafts[mail.selected_idxs.drafts[name]].cc,
messagesDrafts[mail.selected_idxs.drafts[name]].bcc,
messagesDrafts[mail.selected_idxs.drafts[name]].id
)
end
elseif fields.delete then
local trash_enabled = mail.get_setting(name, "trash_move_enable")
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then -- inbox table
if trash_enabled then
mail.trash_mail(name, mail.selected_idxs.inbox[name])
else
mail.delete_mail(name, mail.selected_idxs.inbox[name])
end
mail.selected_idxs.inbox[name] = {}
elseif formname == "mail:outbox" and mail.selected_idxs.outbox[name] then -- outbox table
if trash_enabled then
mail.trash_mail(name, mail.selected_idxs.outbox[name])
else
mail.delete_mail(name, mail.selected_idxs.outbox[name])
end
mail.selected_idxs.outbox[name] = {}
elseif formname == "mail:drafts" and messagesDrafts[mail.selected_idxs.drafts[name]] then -- drafts table
if trash_enabled then
mail.trash_mail(name, messagesDrafts[mail.selected_idxs.drafts[name]].id)
else
mail.delete_mail(name, messagesDrafts[mail.selected_idxs.drafts[name]].id)
end
mail.selected_idxs.drafts[name] = nil
elseif formname == "mail:trash" and messagesTrash[mail.selected_idxs.trash[name]] then -- trash table
mail.delete_mail(name, messagesTrash[mail.selected_idxs.trash[name]].id, true)
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.restore then
if messagesTrash[mail.selected_idxs.trash[name]] then
mail.restore_mail(name, messagesTrash[mail.selected_idxs.trash[name]].id)
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.reply then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
mail.reply(name, message)
elseif
formname == "mail:outbox" and mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
mail.reply(name, message)
end
elseif fields.replyall then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
mail.replyall(name, message)
elseif
formname == "mail:outbox" and mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
mail.replyall(name, message)
end
elseif fields.forward then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
mail.forward(name, message)
elseif
formname == "mail:outbox" and mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
mail.forward(name, message)
end
elseif fields.markread then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then
mail.mark_read(name, mail.selected_idxs.inbox[name])
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.markunread then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then
mail.mark_unread(name, mail.selected_idxs.inbox[name])
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.new then
mail.show_compose(name)
elseif fields.empty then
mail.empty_trash(name)
mail.show_mail_menu(name)
elseif fields.contacts then
mail.show_contacts(name)
elseif fields.maillists then
mail.show_maillists(name)
elseif fields.options then
mail.show_options(name)
elseif fields.selectall then
if formname == "mail:inbox" then
local selected_number = #mail.selected_idxs.inbox[name]
mail.selected_idxs.inbox[name] = {} -- reset for select, unselect and not existing
mail.selected_idxs.multipleselection[name] = true -- enable as the button were pressed
if selected_number < #getInbox() then -- then populate it if selection isn't full
for _, msg in ipairs(getInbox()) do
table.insert(mail.selected_idxs.inbox[name], msg.id)
end
end
elseif formname == "mail:outbox" then
local selected_number = #mail.selected_idxs.outbox[name]
mail.selected_idxs.outbox[name] = {} -- reset for select, unselect and not existing
mail.selected_idxs.multipleselection[name] = true -- enable as the button were pressed
if selected_number < #getOutbox() then -- then populate it if selection isn't full
for _, msg in ipairs(getOutbox()) do
table.insert(mail.selected_idxs.outbox[name], msg.id)
end
end
end
mail.show_mail_menu(name)
elseif fields.sortfield or fields.sortdirection or fields.filter then
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
end
return true
end)

139
mail/ui/inbox.lua Normal file
View file

@ -0,0 +1,139 @@
-- translation
local S = minetest.get_translator("mail")
function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
sortfieldindex = tonumber(sortfieldindex or mail.selected_idxs.sortfield[name])
or mail.get_setting(name, "defaultsortfield") or 3
sortdirection = tostring(sortdirection or mail.selected_idxs.sortdirection[name]
or mail.get_setting(name, "defaultsortdirection") or "1")
filter = filter or mail.selected_idxs.filter[name] or ""
mail.selected_idxs.inbox[name] = mail.selected_idxs.inbox[name] or {}
local entry = mail.get_storage_entry(name)
local sortfield = ({"from","subject","time"})[sortfieldindex]
local messages = mail.sort_messages(entry.inbox, sortfield, sortdirection == "2", filter)
if mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.inbox[name]) do
local is_present = false
for _, msg in ipairs(messages) do
if msg.id == selected_msg then
is_present = true
break
end
end
if not is_present then
table.remove(mail.selected_idxs.inbox[name], i)
end
end
end
local trash_tab = ""
if mail.get_setting(name, "trash_move_enable") then
trash_tab = "," .. S("Trash")
end
local inbox_formspec = "size[8.5,10;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. trash_tab .. [[;1;false;false]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;read;]] .. S("Read") .. [[]
button[6,1.70;2.5,0.5;reply;]] .. S("Reply") .. [[]
button[6,2.45;2.5,0.5;replyall;]] .. S("Reply all") .. [[]
button[6,3.20;2.5,0.5;forward;]] .. S("Forward") .. [[]
button[6,3.95;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,4.82;2.5,0.5;markread;]] .. S("Mark Read") .. [[]
button[6,5.55;2.5,0.5;markunread;]] .. S("Mark Unread") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tooltip[reply;]] .. S("Reply only to the sender") .. [[]
tooltip[replyall;]] .. S("Reply to all involved people") .. [[]
tooltip[forward;]] .. S("Transfer message to other people") .. [[]
dropdown[0,8.5;2,0.5;sortfield;]] ..
S("From") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] .. sortfieldindex .. [[;true]
dropdown[2.0,8.5;2,0.5;sortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] .. sortdirection .. [[;true]
field[4.25,8.95;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
button[5.14,8.62;0.85,0.5;search;Q]
checkbox[0,9.1;multipleselection;]] .. S("Allow multiple selection") .. [[;]] ..
tostring(mail.selected_idxs.multipleselection[name]) .. [[]
label[0,9.65;]] .. S("@1 of @2 selected", tostring(#mail.selected_idxs.inbox[name]), tostring(#messages)) .. [[]
button[3.5,9.5;2.5,0.5;selectall;]] .. S("(Un)select all") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,7.45;inbox;]] .. mail.colors.header .. "," .. S("From") .. "," .. S("Subject")
local formspec = { inbox_formspec }
mail.message_drafts[name] = nil
local unread_color_enable = mail.get_setting(name, "unreadcolorenable")
local cc_color_enable = mail.get_setting(name, "cccolorenable")
if #messages > 0 then
for _, message in ipairs(messages) do
local selected_id = 0
-- check if message is in selection list and return its id
if mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.inbox[name]) do
if message.id == selected_msg then
selected_id = i
break
end
end
end
if selected_id > 0 then
if not message.read and unread_color_enable then
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.imp_add_sel
else
formspec[#formspec + 1] = "," .. mail.colors.imp_sel
end
else
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.add_sel
else
formspec[#formspec + 1] = "," .. mail.colors.selected
end
end
else
if not message.read and unread_color_enable then
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.imp_add
else
formspec[#formspec + 1] = "," .. mail.colors.important
end
else
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.additional
else
formspec[#formspec + 1] = ","
end
end
end
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(message.from)
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
end
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No mail") .. "]"
end
minetest.show_formspec(name, "mail:inbox", table.concat(formspec, ""))
end

17
mail/ui/mail.lua Normal file
View file

@ -0,0 +1,17 @@
-- helper function for tabbed overview
function mail.show_mail_menu(playername, sortfield, sortdirection, filter)
local index = mail.selected_idxs.boxtab[playername] or 1
if not mail.selected_idxs.boxtab[playername] then
mail.selected_idxs.boxtab[playername] = 1
end
if index == 1 then
mail.show_inbox(playername, sortfield, sortdirection, filter)
elseif index == 2 then
mail.show_outbox(playername, sortfield, sortdirection, filter)
elseif index == 3 then
mail.show_drafts(playername)
elseif index == 4 then
mail.show_trash(playername)
end
end

113
mail/ui/maillists.lua Normal file
View file

@ -0,0 +1,113 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:maillists"
local maillists_formspec = "size[8,9;]" .. mail.theme .. [[
button[6,0.10;2,0.5;new;]] .. S("New") .. [[]
button[6,0.85;2,0.5;edit;]] .. S("Edit") .. [[]
button[6,1.60;2,0.5;delete;]] .. S("Delete") .. [[]
button[6,8.25;2,0.5;back;]] .. S("Back") .. [[]
tablecolumns[color;text;text]
table[0,0;5.75,9;maillists;]] .. mail.colors.header .. "," .. S("Name") .. "," .. S("Note")
function mail.show_maillists(name)
local formspec = { maillists_formspec }
local maillists = mail.get_maillists(name)
if maillists[1] then
for _, maillist in ipairs(maillists) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = "@" .. minetest.formspec_escape(maillist.name)
formspec[#formspec + 1] = ","
if maillist.desc ~= "" then
if string.len(maillist.desc) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(maillist.desc, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(maillist.desc)
end
else
formspec[#formspec + 1] = S("(No description)")
end
end
if mail.selected_idxs.maillists[name] then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = mail.selected_idxs.maillists[name]
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No maillist") .. "]"
end
minetest.show_formspec(name, FORMNAME, table.concat(formspec, ""))
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local maillists = mail.get_maillists(name)
if fields.maillists then
local evt = minetest.explode_table_event(fields.maillists)
mail.selected_idxs.maillists[name] = evt.row - 1
if evt.type == "DCL" and maillists[mail.selected_idxs.maillists[name]] then
local maillist = mail.get_maillist_by_name(name, maillists[mail.selected_idxs.maillists[name]].name)
local players_string = mail.concat_player_list(maillist.players)
mail.show_edit_maillist(
name,
maillists[mail.selected_idxs.maillists[name]].name,
maillists[mail.selected_idxs.maillists[name]].desc,
players_string
)
end
elseif fields.new then
mail.selected_idxs.maillists[name] = "#NEW#"
mail.show_edit_maillist(name, "", "", "Player1, Player2, Player3")
elseif fields.edit and maillists[mail.selected_idxs.maillists[name]] then
local maillist = mail.get_maillist_by_name(name, maillists[mail.selected_idxs.maillists[name]].name)
local players_string = mail.concat_player_list(maillist.players)
mail.show_edit_maillist(
name,
maillists[mail.selected_idxs.maillists[name]].name,
maillists[mail.selected_idxs.maillists[name]].desc,
players_string
)
elseif fields.delete then
if maillists[mail.selected_idxs.maillists[name]] then
-- delete the maillist and set the selected to the next in the list,
-- except if it was the last. Then determine the new last
local found = false
local last = nil
for k in mail.pairsByKeys(maillists) do
if found then
mail.selected_idxs.maillists[name] = k
break
elseif k == mail.selected_idxs.maillists[name] then
mail.delete_maillist(name, maillists[mail.selected_idxs.maillists[name]].name)
mail.selected_idxs.maillists[name] = nil
found = true
else
last = k
end
end
if found and not mail.selected_idxs.maillists[name] then
-- was the last in the list, so take the previous (new last)
mail.selected_idxs.maillists[name] = last
end
end
mail.show_maillists(name)
elseif fields.back then
mail.show_mail_menu(name)
end
return true
end)

151
mail/ui/message.lua Normal file
View file

@ -0,0 +1,151 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:message"
function mail.show_message(name, id)
local message = mail.get_message(name, id)
local formspec = [[
size[8,9]
box[0,0;7,1.9;]] .. mail.colors.highlighted .. [[]
button[7.25,0.15;0.75,0.5;back;X]
label[0.2,0.1;]] .. S("From") .. [[: %s]
label[0.2,0.5;]] .. S("To") .. [[: %s]
label[0.2,0.9;]] .. S("CC") .. [[: %s]
label[0.2,1.3;]] .. S("Date") .. [[: %s]
tooltip[0.2,1.3;4.8,0.4;]] .. mail.time_ago(message.time) .. [[]
button[5.1,1;2,1;receivers;]] .. S("Receivers") .. [[]
label[0,2.1;]] .. S("Subject") .. [[: %s]
textarea[0.25,2.6;8,7.0;;;%s]
button[0,8.5;2,1;reply;]] .. S("Reply") .. [[]
button[2,8.5;2,1;replyall;]] .. S("Reply all") .. [[]
button[4,8.5;2,1;forward;]] .. S("Forward") .. [[]
button[6,8.5;2,1;delete;]] .. S("Delete") .. [[]
tooltip[reply;]] .. S("Reply only to the sender") .. [[]
tooltip[replyall;]] .. S("Reply to all involved people") .. [[]
tooltip[forward;]] .. S("Transfer message to other people") .. [[]
]] .. mail.theme
local from = minetest.formspec_escape(message.from) or ""
local to = minetest.formspec_escape(message.to) or ""
if string.len(to) > 70 then to = string.sub(to, 1, 67) .. "..." end
local cc = minetest.formspec_escape(message.cc) or ""
if string.len(cc) > 50 then cc = string.sub(cc, 1, 47) .. "..." end
local date = type(message.time) == "number"
and minetest.formspec_escape(os.date(mail.get_setting(name, "date_format"), message.time)) or ""
local subject = minetest.formspec_escape(message.subject) or ""
local body = minetest.formspec_escape(message.body) or ""
formspec = string.format(formspec, from, to, cc, date, subject, body)
if not message.read and mail.get_setting(name, "auto_marking_read") then
-- mark as read
mail.mark_read(name, id)
end
minetest.show_formspec(name, FORMNAME, formspec)
end
function mail.reply(name, message)
if not message then
-- TODO: workaround for https://github.com/mt-mods/mail/issues/84
minetest.log("error", "[mail] reply called with nil message for player: " .. name)
minetest.log("error", "[mail] current mail-context: " .. dump(mail.selected_idxs))
return
end
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
mail.show_compose(name, message.from, "Re: "..message.subject, replyfooter)
end
function mail.replyall(name, message)
if not message then
-- TODO: workaround for https://github.com/mt-mods/mail/issues/84
minetest.log("error", "[mail] replyall called with nil message for player: " .. name)
minetest.log("error", "[mail] current mail-context: " .. dump(mail.selected_idxs))
return
end
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
-- new recipients are the sender plus the original recipients, minus ourselves
local recipients = message.to or ""
if message.from ~= nil then
recipients = message.from .. ", " .. recipients
end
recipients = mail.parse_player_list(recipients)
for k,v in pairs(recipients) do
if v == name then
table.remove(recipients, k)
break
end
end
recipients = mail.concat_player_list(recipients)
-- new CC is old CC minus ourselves
local cc = mail.parse_player_list(message.cc)
for k,v in pairs(cc) do
if v == name then
table.remove(cc, k)
break
end
end
cc = mail.concat_player_list(cc)
mail.show_compose(name, recipients, "Re: "..message.subject, replyfooter, cc)
end
function mail.forward(name, message)
local fwfooter = "Type your message here.\n\n--Original message follows--\n" .. (message.body or "")
mail.show_compose(name, "", "Fw: " .. (message.subject or ""), fwfooter)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local entry = mail.get_storage_entry(name)
local message = ""
if mail.selected_idxs.inbox[name] and mail.selected_idxs.boxtab[name] == 1 then
message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif mail.selected_idxs.outbox[name] and mail.selected_idxs.boxtab[name] == 2 then
message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
elseif mail.selected_idxs.trash[name] and mail.selected_idxs.boxtab[name] == 4 then
message = mail.get_message(name, entry.trash[mail.selected_idxs.trash[name]].id)
end
if fields.back then
mail.show_mail_menu(name)
return true -- don't uselessly set messages
elseif fields.reply then
mail.reply(name, message)
elseif fields.replyall then
mail.replyall(name, message)
elseif fields.forward then
mail.forward(name, message)
elseif fields.delete then
if mail.get_setting(name, "trash_move_enable") and mail.selected_idxs.boxtab[name] ~= 4 then
mail.trash_mail(name, message.id)
else
mail.delete_mail(name, message.id, true)
end
mail.show_mail_menu(name)
elseif fields.receivers then
mail.show_receivers(name, message.id)
end
return true
end)

13
mail/ui/options.lua Normal file
View file

@ -0,0 +1,13 @@
-- helper function for tabbed options
function mail.show_options(playername)
local index = mail.selected_idxs.optionstab[playername] or 1
if not mail.selected_idxs.optionstab[playername] then
mail.selected_idxs.optionstab[playername] = 1
end
if index == 1 then
mail.show_settings(playername)
elseif index == 2 then
mail.show_about(playername)
end
end

115
mail/ui/outbox.lua Normal file
View file

@ -0,0 +1,115 @@
-- translation
local S = minetest.get_translator("mail")
function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
sortfieldindex = tonumber(sortfieldindex or mail.selected_idxs.sortfield[name])
or mail.get_setting(name, "defaultsortfield") or 3
sortdirection = tostring(sortdirection or mail.selected_idxs.sortdirection[name]
or mail.get_setting(name, "defaultsortdirection") or "1")
filter = filter or mail.selected_idxs.filter[name] or ""
mail.selected_idxs.outbox[name] = mail.selected_idxs.outbox[name] or {}
local entry = mail.get_storage_entry(name)
local sortfield = ({"to","subject","time"})[sortfieldindex]
local messages = mail.sort_messages(entry.outbox, sortfield, sortdirection == "2", filter)
if mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.outbox[name]) do
local is_present = false
for _, msg in ipairs(messages) do
if msg.id == selected_msg then
is_present = true
break
end
end
if not is_present then
table.remove(mail.selected_idxs.outbox[name], i)
end
end
end
local trash_tab = ""
if mail.get_setting(name, "trash_move_enable") then
trash_tab = "," .. S("Trash")
end
local outbox_formspec = "size[8.5,10;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. trash_tab .. [[;2;false;false]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;read;]] .. S("Read") .. [[]
button[6,1.70;2.5,0.5;reply;]] .. S("Reply") .. [[]
button[6,2.45;2.5,0.5;replyall;]] .. S("Reply all") .. [[]
button[6,3.20;2.5,0.5;forward;]] .. S("Forward") .. [[]
button[6,3.95;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tooltip[reply;]] .. S("Reply only to the sender") .. [[]
tooltip[replyall;]] .. S("Reply to all involved people") .. [[]
tooltip[forward;]] .. S("Transfer message to other people") .. [[]
dropdown[0,8.5;2,0.5;sortfield;]] ..
S("To") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] .. sortfieldindex .. [[;true]
dropdown[2.0,8.5;2,0.5;sortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] .. sortdirection .. [[;true]
field[4.25,8.95;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
button[5.14,8.62;0.85,0.5;search;Q]
checkbox[0,9.1;multipleselection;]] .. S("Allow multiple selection") .. [[;]] ..
tostring(mail.selected_idxs.multipleselection[name]) .. [[]
label[0,9.65;]] .. S("@1 of @2 selected", tostring(#mail.selected_idxs.outbox[name]), tostring(#messages)) ..[[]
button[3.5,9.5;2.5,0.5;selectall;]] .. S("(Un)select all") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,7.45;outbox;]] .. mail.colors.header .. "," .. S("To") .. "," .. S("Subject")
local formspec = { outbox_formspec }
mail.message_drafts[name] = nil
if #messages > 0 then
for _, message in ipairs(messages) do
local selected_id = 0
-- check if message is in selection list and return its id
if mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.outbox[name]) do
if message.id == selected_msg then
selected_id = i
break
end
end
end
if selected_id > 0 then
formspec[#formspec + 1] = "," .. mail.colors.selected
else
formspec[#formspec + 1] = ","
end
formspec[#formspec + 1] = ","
if string.len(message.to) > 20 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.to, 1, 17))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.to)
end
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
end
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No mail") .. "]"
end
minetest.show_formspec(name, "mail:outbox", table.concat(formspec, ""))
end

62
mail/ui/receivers.lua Normal file
View file

@ -0,0 +1,62 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:receivers"
function mail.show_receivers(name, id)
local message = mail.get_message(name, id)
local formspec = [[
size[8,6]
box[0,0;7,1.1;]] .. mail.colors.highlighted .. [[]
button[7.25,0.15;0.75,0.5;back;X]
label[0.2,0.1;]] .. S("From") .. [[: %s]
label[0.2,0.5;]] .. S("Date") .. [[: %s]
tablecolumns[color;text]
table[0,1.5;3.8,4.5;to;%s]
tablecolumns[color;text]
table[4,1.5;3.8,4.5;cc;%s]
]] .. mail.theme
local from = minetest.formspec_escape(message.from) or ""
local to = mail.parse_player_list(message.to or "")
local to_str = mail.colors.header .. "," .. S("To") .. ",,"
to_str = to_str .. table.concat(to, ",,")
local cc = mail.parse_player_list(message.cc or "")
local cc_str = mail.colors.header .. "," .. S("CC") .. ",,"
cc_str = cc_str .. table.concat(cc, ",,")
local date = type(message.time) == "number"
and minetest.formspec_escape(os.date(mail.get_setting(name, "date_format"), message.time)) or ""
formspec = string.format(formspec, from, date, to_str, cc_str)
minetest.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local entry = mail.get_storage_entry(name)
local message_id = ""
if mail.selected_idxs.inbox[name] and mail.selected_idxs.boxtab[name] == 1 then
message_id = mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]]
elseif mail.selected_idxs.outbox[name] and mail.selected_idxs.boxtab[name] == 2 then
message_id = mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]]
elseif mail.selected_idxs.trash[name] and mail.selected_idxs.boxtab[name] == 4 then
message_id = entry.trash[mail.selected_idxs.trash[name]].id
end
if fields.back then
mail.show_message(name, message_id)
end
return true
end)

118
mail/ui/select_contact.lua Normal file
View file

@ -0,0 +1,118 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:selectcontact"
local select_contact_formspec = "size[8,9;]" .. mail.theme .. [[
tablecolumns[color;text;text]
table[0,0;3.5,9;contacts;]] .. mail.colors.header .. "," .. S("Name") .. "," .. S("Note") .. [[%s]
button[3.55,2.00;1.75,0.5;toadd; ]] .. S("Add") .. [[]
button[3.55,2.75;1.75,0.5;toremove; ]] .. S("Remove") .. [[]
button[3.55,6.00;1.75,0.5;ccadd; ]] .. S("Add") .. [[]
button[3.55,6.75;1.75,0.5;ccremove; ]] .. S("Remove") .. [[]
tablecolumns[color;text;text]
table[5.15,0.0;2.75,4.5;to;]] .. mail.colors.header .. "," .. S("To") .. ":," .. S("Note") .. [[%s]
tablecolumns[color;text;text]
table[5.15,4.6;2.75,4.5;cc;]] .. mail.colors.header .. "," .. S("CC") .. ":," .. S("Note") .. [[%s]
button[3.55,8.25;1.75,0.5;back;]] .. S("Back") .. [[]
]]
function mail.show_select_contact(name, to, cc)
local formspec = select_contact_formspec
local contacts = mail.compile_contact_list(name, mail.selected_idxs.contacts[name])
-- compile lists
if to then
to = mail.compile_contact_list(name, mail.selected_idxs.to[name], to)
else
to = ""
end
if cc then
cc = mail.compile_contact_list(name, mail.selected_idxs.cc[name], cc)
else
cc = ""
end
--[[if bcc then
bcc = table.concat(mail.compile_contact_list(name, mail.selected_idxs.bcc[name], bcc)
else
bcc = ""
end]]--
formspec = string.format(formspec, contacts, to, cc)--, bcc()
minetest.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local contacts = mail.get_contacts(name)
local draft = mail.message_drafts[name]
-- get indexes for fields with selected rows
-- execute their default button's actions if double clicked
for k,action in pairs({
contacts = "toadd",
to = "toremove",
cc = "ccremove",
bcc = "bccremove"
}) do
if fields[k] then
local evt = minetest.explode_table_event(fields[k])
mail.selected_idxs[k][name] = evt.row - 1
if evt.type == "DCL" and mail.selected_idxs[k][name] then
fields[action] = true
end
return true
end
end
local update = false
-- add
for _,v in pairs({"to","cc","bcc"}) do
if fields[v.."add"] then
update = true
if mail.selected_idxs.contacts[name] then
for k, contact, i in mail.pairsByKeys(contacts) do
if k == mail.selected_idxs.contacts[name] or i == mail.selected_idxs.contacts[name] then
local list = mail.parse_player_list(draft[v])
list[#list+1] = contact.name
mail.selected_idxs[v][name] = #list
draft[v] = mail.concat_player_list(list)
break
end
end
end
end
end
-- remove
for _,v in pairs({"to","cc","bcc"}) do
if fields[v.."remove"] then
update = true
if mail.selected_idxs[v][name] then
local list = mail.parse_player_list(draft[v])
table.remove(list, mail.selected_idxs[v][name])
if #list < mail.selected_idxs[v][name] then
mail.selected_idxs[v][name] = #list
end
draft[v] = mail.concat_player_list(list)
end
end
end
if update then
mail.show_select_contact(name, draft.to, draft.cc, draft.bcc)
return true
end
-- delete old idxs
for _,v in ipairs({"contacts","to","cc","bcc"}) do
mail.selected_idxs[v][name] = nil
end
mail.show_compose(name, draft.to, draft.subject, draft.body, draft.cc, draft.bcc)
return true
end)

142
mail/ui/settings.lua Normal file
View file

@ -0,0 +1,142 @@
-- translation
local S = minetest.get_translator("mail")
local FORMNAME = "mail:settings"
local date_formats = {"%Y-%m-%d %X", "%d/%m/%y %X", "%A %d %B %Y %X"}
function mail.show_settings(name)
-- date formats prepare
local dates_now = {}
local previous_date_format = mail.get_setting(name, "date_format")
local date_dropdown_index = 1
for i, f in pairs(date_formats) do
table.insert(dates_now, os.date(f, os.time()))
if f == previous_date_format then date_dropdown_index = i end
end
local date_dropdown_str = table.concat(dates_now, ",")
local formspec = [[
size[10,6;]
tabheader[0.3,1;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;1;false;false]
button[9.35,0;0.75,0.5;back;X]
box[0,0.8;3,0.45;]] .. mail.colors.highlighted .. [[]
label[0.2,0.8;]] .. S("Notifications") .. [[]
checkbox[0,1.2;chat_notifications;]] .. S("Chat notifications") .. [[;]] ..
tostring(mail.get_setting(name, "chat_notifications")) .. [[]
checkbox[0,1.6;onjoin_notifications;]] .. S("On join notifications") .. [[;]] ..
tostring(mail.get_setting(name, "onjoin_notifications")) .. [[]
checkbox[0,2.0;hud_notifications;]] .. S("HUD notifications") .. [[;]] ..
tostring(mail.get_setting(name, "hud_notifications")) .. [[]
checkbox[0,2.4;sound_notifications;]] .. S("Sound notifications") .. [[;]] ..
tostring(mail.get_setting(name, "sound_notifications")) .. [[]
box[5,0.8;3,0.45;]] .. mail.colors.highlighted .. [[]
label[5.2,0.8;]] .. S("Message list") .. [[]
checkbox[5,1.2;unreadcolorenable;]] .. S("Show unread in different color") .. [[;]] ..
tostring(mail.get_setting(name, "unreadcolorenable")) .. [[]
checkbox[5,1.6;cccolorenable;]] .. S("Show CC/BCC in different color") .. [[;]] ..
tostring(mail.get_setting(name, "cccolorenable")) .. [[]
label[5,2.6;]] .. S("Default sorting fields") .. [[]
dropdown[5.5,3.0;2,0.5;defaultsortfield;]] ..
S("From/To") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] ..
tostring(mail.get_setting(name, "defaultsortfield")) .. [[;true]
dropdown[7.5,3.0;2,0.5;defaultsortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] ..
tostring(mail.get_setting(name, "defaultsortdirection")) .. [[;true]
box[0,3.2;3,0.45;]] .. mail.colors.highlighted .. [[]
label[0.2,3.2;]] .. S("Other") .. [[]
checkbox[0,3.6;trash_move_enable;]] .. S("Move deleted messages to trash") .. [[;]] ..
tostring(mail.get_setting(name, "trash_move_enable")) .. [[]
checkbox[0,4.0;auto_marking_read;]] .. S("Automatic marking read") .. [[;]] ..
tostring(mail.get_setting(name, "auto_marking_read")) .. [[]
label[0.31,4.7;]] .. S("Date format:") .. [[]
dropdown[2.7,4.6;4,0.5;date_format;]] .. date_dropdown_str .. [[;]] ..
tostring(date_dropdown_index) .. [[;true]
tooltip[chat_notifications;]] .. S("Receive a message in the chat when there is a new message") .. [[]
tooltip[onjoin_notifications;]] .. S("Receive a message at login when inbox isn't empty") .. [[]
tooltip[hud_notifications;]] .. S("Show an HUD notification when inbox isn't empty") .. [[]
tooltip[sound_notifications;]] .. S("Play a sound when there is a new message") .. [[]
tooltip[auto_marking_read;]] .. S("Mark a message as read when opened") .. [[]
button[0,5.5;2.5,0.5;save;]] .. S("Save") .. [[]
button[2.7,5.5;2.5,0.5;reset;]] .. S("Reset") .. [[]
]] .. mail.theme
minetest.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local playername = player:get_player_name()
if fields.back then
mail.show_mail_menu(playername)
return
elseif fields.optionstab == "1" then
mail.selected_idxs.optionstab[playername] = 1
elseif fields.optionstab == "2" then
mail.selected_idxs.optionstab[playername] = 2
mail.show_about(playername)
return
elseif fields.chat_notifications then
mail.selected_idxs.chat_notifications[playername] = fields.chat_notifications == "true"
elseif fields.onjoin_notifications then
mail.selected_idxs.onjoin_notifications[playername] = fields.onjoin_notifications == "true"
elseif fields.hud_notifications then
mail.selected_idxs.hud_notifications[playername] = fields.hud_notifications == "true"
elseif fields.sound_notifications then
mail.selected_idxs.sound_notifications[playername] = fields.sound_notifications == "true"
elseif fields.unreadcolorenable then
mail.selected_idxs.unreadcolorenable[playername] = fields.unreadcolorenable == "true"
elseif fields.cccolorenable then
mail.selected_idxs.cccolorenable[playername] = fields.cccolorenable == "true"
elseif fields.trash_move_enable then
mail.selected_idxs.trash_move_enable[playername] = fields.trash_move_enable == "true"
elseif fields.auto_marking_read then
mail.selected_idxs.auto_marking_read[playername] = fields.auto_marking_read == "true"
elseif fields.save then
-- checkboxes
mail.set_setting(playername, "chat_notifications", mail.selected_idxs.chat_notifications[playername])
mail.set_setting(playername, "onjoin_notifications", mail.selected_idxs.onjoin_notifications[playername])
mail.set_setting(playername, "hud_notifications", mail.selected_idxs.hud_notifications[playername])
mail.set_setting(playername, "sound_notifications", mail.selected_idxs.sound_notifications[playername])
mail.set_setting(playername, "unreadcolorenable", mail.selected_idxs.unreadcolorenable[playername])
mail.set_setting(playername, "cccolorenable", mail.selected_idxs.cccolorenable[playername])
mail.set_setting(playername, "trash_move_enable", mail.selected_idxs.trash_move_enable[playername])
mail.set_setting(playername, "auto_marking_read", mail.selected_idxs.auto_marking_read[playername])
-- dropdowns
local defaultsortfield = fields.defaultsortfield or mail.get_setting(playername, "defaultsortfield")
local defaultsortdirection = fields.defaultsortdirection or mail.get_setting(playername, "defaultsortdirection")
local date_format = date_formats[tonumber(fields.date_format)] or mail.get_setting(playername, "date_format")
mail.set_setting(playername, "defaultsortfield", tonumber(defaultsortfield))
mail.set_setting(playername, "defaultsortdirection", tonumber(defaultsortdirection))
mail.set_setting(playername, "date_format", date_format)
-- update visuals
mail.hud_update(playername, mail.get_storage_entry(playername).inbox)
mail.show_settings(playername)
elseif fields.reset then
mail.reset_settings(playername)
mail.show_settings(playername)
end
return
end)

53
mail/ui/trash.lua Normal file
View file

@ -0,0 +1,53 @@
-- translation
local S = minetest.get_translator("mail")
local trash_formspec = "size[8.5,10;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. "," .. S("Trash") .. [[;4;false;false]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;read;]] .. S("Read") .. [[]
button[6,1.70;2.5,0.5;restore;]] .. S("Restore") .. [[]
button[6,2.45;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,3.20;2.5,0.5;empty;]] .. S("Empty") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,9.35;trash;]] .. mail.colors.header .. "," .. S("From/To") .. "," .. S("Subject")
function mail.show_trash(name)
local formspec = { trash_formspec }
local entry = mail.get_storage_entry(name)
local messages = entry.trash
if messages[1] then
for _, message in ipairs(messages) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(message.to)
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
end
end
if mail.selected_idxs.trash[name] then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(mail.selected_idxs.trash[name] + 1)
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("Trash is empty") .. "]"
end
minetest.show_formspec(name, "mail:trash", table.concat(formspec, ""))
end

75
mail/util/contact.lua Normal file
View file

@ -0,0 +1,75 @@
-- translation
local S = minetest.get_translator("mail")
function mail.compile_contact_list(name, selected, playernames)
-- TODO: refactor this - not just compiles *a* list, but *the* list for the contacts screen (too inflexible)
local formspec = {}
local contacts = mail.get_contacts(name)
if playernames == nil then
local length = 0
for k, contact, i, l in mail.pairsByKeys(contacts) do
if i == 1 then length = l end
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(contact.name)
formspec[#formspec + 1] = ","
local note = contact.note
-- display an ellipsis if the note spans multiple lines
local idx = string.find(note, '\n')
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = minetest.formspec_escape(note)
if type(selected) == "string" then
if string.lower(selected) == k then
selected = i
end
end
end
if length > 0 then
if selected and type(selected) == "number" then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(selected + 1)
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2,4.5;" .. S("No contacts") .. "]"
end
else
if type(playernames) == "string" then
playernames = mail.parse_player_list(playernames)
end
for i,c in ipairs(playernames) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(c)
formspec[#formspec + 1] = ","
if contacts[string.lower(c)] == nil then
formspec[#formspec + 1] = ""
else
local note = contacts[string.lower(c)].note
-- display an ellipsis if the note spans multiple lines
local idx = string.find(note, '\n')
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = minetest.formspec_escape(note)
end
if not selected then
if type(selected) == "string" then
if string.lower(selected) == string.lower(c) then
selected = i
end
end
end
end
if #playernames > 0 and selected and type(selected) == "number" then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(selected + 1)
end
formspec[#formspec + 1] = "]"
end
return table.concat(formspec, "")
end

61
mail/util/normalize.lua Normal file
View file

@ -0,0 +1,61 @@
local has_canonical_name = minetest.get_modpath("canonical_name")
--[[
return the field normalized (comma separated, single space)
and add individual player names to recipient list
--]]
function mail.normalize_players_and_add_recipients(field, recipients, undeliverable)
local order = mail.parse_player_list(field)
for _, recipient_name in ipairs(order) do
if not minetest.player_exists(recipient_name) then
undeliverable[recipient_name] = true
else
recipients[recipient_name] = true
end
end
return mail.concat_player_list(order)
end
function mail.parse_player_list(field)
if not field then
return {}
end
local separator = ", "
local pattern = "([^" .. separator .. "]+)"
-- get individual players
local player_set = {}
local order = {}
field:gsub(pattern, function(player_name)
local lower = string.lower(player_name)
if not player_set[lower] then
if has_canonical_name then
player_name = canonical_name.get(player_name) or player_name
end
player_set[lower] = player_name
order[#order+1] = player_name
end
end)
return order
end
function mail.concat_player_list(order)
-- turn list of players back into normalized string
return table.concat(order, ", ")
end
function mail.player_in_list(name, list)
list = list or {}
if type(list) == "string" then
list = mail.parse_player_list(list)
end
for _, player_name in pairs(list) do
if name == player_name then
return true
end
end
return false
end

View file

@ -0,0 +1,12 @@
mtt.register("util/normalize_players_and_add_recipients", function(callback)
local recipients = {}
local undeliverable = {}
local to = mail.normalize_players_and_add_recipients("player1,player2", recipients, undeliverable)
assert(to == "player1, player2")
assert(not next(undeliverable))
assert(recipients["player1"])
assert(recipients["player2"])
callback()
end)

29
mail/util/time_ago.lua Normal file
View file

@ -0,0 +1,29 @@
-- translation
local S = minetest.get_translator("mail")
function mail.time_ago(t)
local elapsed = os.time() - t
local str = ""
local time_units = {
{ S("years"), 31536000 },
{ S("months"), 2592000 },
{ S("weeks"), 604800 },
{ S("days"), 86400 },
{ S("hours"), 3600 },
{ S("minuts"), 60 },
{ S("seconds"), 1 },
}
for _, u in ipairs(time_units) do
local n = math.modf(elapsed/u[2])
if n > 0 then
str = str .. " " .. n .. " " .. u[1]
elapsed = elapsed - n * u[2]
end
end
str = string.sub(str, 2, -1)
return S("@1 ago", str)
end

9
mail/util/uuid.lua Normal file
View file

@ -0,0 +1,9 @@
-- source: https://gist.github.com/jrus/3197011
local random = math.random
function mail.new_uuid()
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
return string.format('%x', v)
end)
end

7
mail/util/uuid.spec.lua Normal file
View file

@ -0,0 +1,7 @@
mtt.register("uuid", function(callback)
assert(mail.new_uuid())
assert(mail.new_uuid() ~= mail.new_uuid())
callback()
end)