# 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 : 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}