Just so you know, we don't know the file format for every file. If it's just a bunch of random characters, it's probably a .zip or .jar.
# Inventory Restoration
# by Sovde (@Sovde#0001)
# Requires SkBee 2.8.2+ (should work on 2.5.0+, but untested) and skript-gui 1.3+
# Stores backups of inventories when players die using NBT files.
# The files can be found in the directory folder (see options), labeled with the player's UUID.
# By default, backups are stored for 30 days, but you can change that in the options
# Backups can be viewed, edited, restored, and deleted using a GUI.
# Edits will only be saved when the player clicks the "Save edits" button in the GUI.
# When restoring, items will be placed back in the same slots they were in when the player died, if possible.
# If the player has no space in their inventory, items will be dropped on the ground.
options:
# How long backups are stored for
# Set to -1 to store backups forever
age-limit: 30 days
# Where to store backups
directory: plugins/inventory-restore
# Colours
accent: <##aabbff>
main: <##9999a0>
warning: <##f5a147>
error: <##cc3434>
success: <##22aa6a>
# error/success messages:
# by default looks like "[!] Message here."
prefix: [!]
# when backups are restored to the player ({_target})
restored-me-sucess: Your inventory has been restored!
restored-sucess: %{_target}%'s inventory has been restored!
# when backup edits are saved
saved-sucess: Your edits have been saved!
# when backups are deleted
deleted-sucess: Backup deleted!
# when backups can't be found
non-existent-error: That backup does not exist!
# when the player ({_target}) is offline
offline-error: %{_target}% is offline!
command /invrestore <offlineplayer>:
permission: invrestore.restore
trigger:
openInvRestoreGUI(player, arg-1)
# storing death data
on death of player:
# setup nbt
set {_entry} to blank nbt compound
set {_now} to unix timestamp of now
# inventory data
loop items in victim's inventory:
set {_nbt} to full nbt compound copy of loop-value
set tag "Slot" of {_nbt} to index of loop-value
set {_items::%index of loop-value%} to {_nbt}
set compound list tag "Inventory" of {_entry} to {_items::*}
# metadata
set tag "Message" of {_entry} to death message
set long tag "Timestamp" of {_entry} to {_now} * 1000
set {_location} to victim's location
set float list tag "Position" of {_entry} to (x coordinate of {_location}), (y coordinate of {_location}), (z coordinate of {_location})
set tag "World" of {_entry} to "%world of {_location}%"
# save to file
set {_file} to nbt from file "{@directory}/%victim's uuid%-deaths.nbt"
set {_entries::*} to compound list tag "Deaths" of {_file}
add {_entry} to {_entries::*}
# clean up old backups
set {_limit} to {@age-limit}
if {_limit} is not -1:
loop {_entries::*}:
set {_timestamp} to long tag "Timestamp" of loop-value
set {_date} to unix date of ({_timestamp} / 1000)
if time since {_date} >= {_limit}:
remove loop-value from {_entries::*}
set compound list tag "Deaths" of {_file} to {_entries::*}
save nbt file of {_file}
# main gui
function openInvRestoreGUI(viewer: player, target: offlineplayer, page: number = 1):
# get entries from file
set {_file} to nbt of file "{@directory}/%{_target}'s uuid%-deaths.nbt"
set {_entries::*} to compound list tag "Deaths" of {_file}
# clean up old backups
set {_limit} to {@age-limit}
if {_limit} is not -1:
loop {_entries::*}:
set {_timestamp} to long tag "Timestamp" of loop-value
set {_date} to unix date of ({_timestamp} / 1000)
if time since {_date} >= {_limit}:
remove loop-value from {_entries::*}
set compound list tag "Deaths" of {_file} to {_entries::*}
save nbt file of {_file}
# order entries by timestamp, newest to oldest
loop {_entries::*}:
set {_timestamp} to long tag "Timestamp" of loop-value
set {_ordered-entries::%{_timestamp}%} to loop-value
set {_ordered-entries::*} to reversed {_ordered-entries::*}
# set up gui
create a gui with virtual chest inventory with 3 rows named "{@main}Inventory Backups":
format gui slot 4 with {_target}'s skull named "{@accent}%{_target}%{@main}'s Inventory Backups"
set {_slot} to 11
# manage pagination
set {_counter} to 0
set {_start} to 1 + (5 * ({_page} - 1))
set {_end} to 5 + (5 * ({_page} - 1))
loop {_ordered-entries::*}:
add 1 to {_counter}
# skip entries outside of page range
if {_counter} is not between {_start} and {_end}:
continue
set {_item} to a skeleton skull named "Death"
clear {_lore::*}
# add meta-data to item lore
add "&7&o""%tag "Message" of loop-value%""" to {_lore::*}
set {_timestamp} to long tag "Timestamp" of loop-value
add "{@accent}Timestamp: &f%unix date of {_timestamp} / 1000%" to {_lore::*}
set {_pos::*} to float list tag "Position" of loop-value
set {_pos::*} to "%{_pos::1}%", "%{_pos::2}%", "%{_pos::3}%"
add "{@accent}Location: &f%join {_pos::*} with ", "% {@main}in &f%tag "World" of loop-value%" to {_lore::*}
add "" to {_lore::*}
# add instructions to lore
add "{@main}Left-click &fto {@accent}view &fthe backup" to {_lore::*}
add "{@main}Right-click &fto {@accent}restore &fthe backup" to {_lore::*}
add "{@main}Shift-left-click &fto {@accent}teleport &fthe to death location" to {_lore::*}
add "{@main}Shift-right-click &fto {@accent}delete &fthe backup" to {_lore::*}
# check if backup has already been restored to player
if loop-value has tag "restored":
add "%nl%{@warning}This backup has been previously restored!" to {_lore::*}
# add nbt tag for click events
set long tag "Timestamp" of (nbt of {_item}) to {_timestamp}
set lore of {_item} to {_lore::*}
set {_entry} to loop-value
# add item to gui, set click events
format gui slot {_slot} with {_item}:
# view backup
if gui click type is left mouse button:
viewInvRestoreBackup({_viewer}, {_target}, {_timestamp})
# restore backup
else if gui click type is right mouse button:
restoreInvRestoreBackup({_viewer}, {_target}, {_entry}, {_file})
openInvRestoreGUI({_viewer}, {_target})
# teleport to death location
else if gui click type is left mouse button with shift:
set {_pos::*} to float list tag "Position" of {_entry}
set {_world} to tag "World" of {_entry}
close {_viewer}'s inventory
wait 1 tick
teleport {_viewer} to location({_pos::1}, {_pos::2}, {_pos::3}, {_world})
# delete backup
else if gui click type is right mouse button with shift:
deleteInvRestoreBackup({_target}, {_timestamp}, {_file})
openInvRestoreGUI({_viewer}, {_target})
# increment slot
add 1 to {_slot}
if {_slot} is 16:
exit loop
if {_page} is not 1:
format gui slot 21 to a book named "{@main}Previous Page":
openInvRestoreGUI({_viewer}, {_target}, ({_page} - 1))
if size of {_ordered-entries::*} is more than {_end}:
format gui slot 23 to a book named "{@main}Next Page":
openInvRestoreGUI({_viewer}, {_target}, ({_page} + 1))
open last gui to {_viewer}
# viewing the backups
function viewInvRestoreBackup(viewer: player, target: offlineplayer, id: number):
# get entry from file
set {_file} to nbt of file "{@directory}/%{_target}'s uuid%-deaths.nbt"
set {_entries::*} to compound list tag "Deaths" of {_file}
loop {_entries::*}:
if long tag "Timestamp" of loop-value is {_id}:
set {_entry} to loop-value
exit loop
# ensure entry exists
if {_entry} is not set:
send "{@error}{@prefix}{@main} {@non-existent-error}" to {_viewer}
stop
# set up gui
create gui with virtual chest inventory with 6 rows named formatted "&8Backup for %{_target}%":
set {_items::*} to compound list tag "Inventory" of {_entry}
# ensure all inv slots can be modified
loop integers from 0 to 40:
format gui slot loop-value with removable air
# add items to gui
loop {_items::*}:
set {_item} to item from nbt loop-value
set {_slot} to tag "Slot" of loop-value
format gui slot {_slot} with removable {_item}
# controls:
# return to overview
format gui slot 46 to a red stained glass pane named "{@main}Return to {@accent}Overview":
openInvRestoreGUI({_viewer}, {_target})
# save edits
format gui slot 48 to a yellow stained glass pane named "{@accent}Save edits {@main}to this backup":
saveInvRestoreBackup({_viewer}, {_id}, {_file})
viewInvRestoreBackup({_viewer}, {_target}, {_id})
# restore backup
format gui slot 50 to a green stained glass pane named "{@accent}Restore {@main}this backup to {@accent}%{_target}%" with lore "{@accent}Shift-click {@main}to {@warning}clear the recipient's inventory{@main} first.":
if gui click type is left mouse button with shift or right mouse button with shift:
clear {_target}'s inventory
restoreInvRestoreBackup({_viewer}, {_target}, {_entry}, {_file})
# teleport to death location
format gui slot 52 to an ender pearl named "{@accent}Teleport {@main}to the death loation":
set {_pos::*} to float list tag "Position" of {_entry}
set {_world} to tag "World" of {_entry}
close {_viewer}'s inventory
wait 1 tick
teleport {_viewer} to location({_pos::1}, {_pos::2}, {_pos::3}, {_world})
# delete backup
format gui slot 53 to a barrier named "{@error}Delete {@main}this backup":
deleteInvRestoreBackup({_target}, {_id}, {_file})
openInvRestoreGUI({_viewer}, {_target})
open last gui to {_viewer}
# restoring the backups
function restoreInvRestoreBackup(viewer: player, target: offlineplayer, entry: nbtcompound, file: nbtcompound):
# ensure player is online to receive items
if {_target} is offline:
send "{@error}{@prefix}{@main} {@offline-error}" to {_viewer}
stop
# get inventory data
set {_items::*} to compound list tag "Inventory" of {_entry}
# loop through items
loop {_items::*}:
set {_slot} to tag "Slot" of loop-value
set {_item} to item from nbt loop-value
# add to original slot if possible, otherwise give to player if they have space, otherwise drop
if slot {_slot} of {_target}'s inventory is air:
set slot {_slot} of {_target}'s inventory to {_item}
else if {_target} has space for {_item}:
give {_item} to {_target}
else:
drop {_item} at {_target}
# send success message
send "{@success}{@prefix}{@main} {@restored-me-sucess}" to {_target}
send "{@success}{@prefix}{@main} {@restored-sucess}" to {_viewer}
# mark backup as restored
set {_id} to long tag "Timestamp" of {_entry}
set {_entries::*} to compound list tag "Deaths" of {_file}
loop {_entries::*}:
if long tag "Timestamp" of loop-value is {_id}:
set {_entry} to loop-value
exit loop
set tag "restored" of {_entry} to true
set compound list tag "Deaths" of {_file} to {_entries::*}
# save file
save nbt file of {_file}
# Saving edits to backups
function saveInvRestoreBackup(viewer: player, id: number, file: nbtcompound):
# get entry from file
set {_entries::*} to compound list tag "Deaths" of {_file}
loop {_entries::*}:
if long tag "Timestamp" of loop-value is {_id}:
set {_entry} to loop-value
exit loop
# ensure entry exists
if {_entry} is not set:
send "{@error}{@prefix}{@main} {@non-existent-error}" to {_viewer}
stop
# get inventory data
loop integers from 0 to 40:
set {_item} to slot loop-value of {_viewer}'s current inventory
if {_item} is air:
continue
set {_nbt} to full nbt compound copy of {_item}
set tag "Slot" of {_nbt} to loop-value
set {_items::%loop-value%} to {_nbt}
# save inventory data to entry
set compound list tag "Inventory" of {_entry} to {_items::*}
set compound list tag "Deaths" of {_file} to {_entries::*}
# save file
save nbt file of {_file}
send "{@success}{@prefix}{@main} {@saved-sucess}" to {_viewer}
# deleting backups
function deleteInvRestoreBackup(viewer: player, id: number, file: nbtcompound):
# get entry from file
set {_entries::*} to compound list tag "Deaths" of {_file}
loop {_entries::*}:
if long tag "Timestamp" of loop-value is {_id}:
set {_entry} to loop-value
exit loop
# ensure entry exists
if {_entry} is not set:
send "{@error}{@prefix}{@main} {@non-existent-error}" to {_viewer}
stop
# remove entry from file
remove {_entry} from {_entries::*}
set compound list tag "Deaths" of {_file} to {_entries::*}
save nbt file of {_file}
send "{@success}{@prefix}{@main} {@deleted-sucess}" to {_viewer}