Initial commit

This commit is contained in:
Rares Bozga
2025-11-29 22:53:32 +02:00
parent e9fa13dbd3
commit 461e95114c
157 changed files with 7597 additions and 1 deletions

View File

@@ -0,0 +1,62 @@
#
# Anvil Input
# A library that lets you use anvil inputs instead of chat inputs.
#
#
# Requests input from the player with the given prompt, and then applies
# the result to the callback.
#
# The callback is a task with two definitions: a player, and the input.
# The callback is called an filled automatically by this task once the anvil
# input is complete.
#
anvil_input:
debug: false
type: task
definitions: player|prompt|callback
script:
- if !<[player].is_online>:
- stop
- define inventory <inventory[anvil[title=<&f>邑邑邑邑]]>
- define rename <item[warped_trapdoor[display=<&f><[prompt]>]]>
- flag <[rename]> anvil_input:true
- inventory set slot:1 origin:<[rename]> destination:<[inventory]>
- flag <[player]> anvil_input:<[callback]>
- inventory open destination:<[inventory]> player:<[player]>
# Handles anvil input events
anvil_input_world:
debug: false
type: world
events:
on player prepares anvil craft item:
- if !<player.has_flag[anvil_input]>:
- stop
- determine 0 passively
- wait 1t
- take flagged:anvil_input quantity:9999
on player clicks in anvil:
- if !<player.has_flag[anvil_input]>:
- stop
- determine cancelled passively
- if <context.slot> != 3:
- stop
- define result <context.item.display.if_null[<context.inventory.slot[1].display.if_null[<empty>]>].strip_color>
- define callback <player.flag[anvil_input]>
- flag <player> anvil_input:!
- playsound sound:input.ok <player> custom
- run <[callback]> def.player:<player> def.input:<[result]>
- inventory close
- wait 1t
- take flagged:anvil_input quantity:9999
- adjust <player> item_on_cursor:<item[air]>
on player closes anvil:
- if !<player.has_flag[anvil_input]>:
- stop
- flag <player> anvil_input:!
- wait 1t
- take flagged:anvil_input quantity:9999
- narrate "<&c>Cancelled input..."
on player join:
- take flagged:anvil_input quantity:9999

149
scripts/lib/ball.dsc Normal file
View File

@@ -0,0 +1,149 @@
# Ball Library
# A library that manages & controls "bouncing balls", i.e. some kind of game ball
# which has gravity, movement, collision, and so on.
# It also efficiently handles everything and adds custom event handlers that other
# plugins can handle.
# Creates a new ball with the given id, at the given location, with the given
# collision size and using the given display item, and some gravity multiplier.
# Only one ball with a given id can exist at one time. If this method is called
# while another ball with this id exists, it will be removed automatically.
ball_create:
debug: false
type: task
definitions: id|location|size|display_item|gravity_multiplier
script:
- ~run ball_remove def.id:<[id]>
- spawn slime[silent=true;size=<[size]>;has_ai=false;visible=false] <[location].with_pitch[0].with_yaw[0]> save:ball_collision
- define ball_collision <entry[ball_collision].spawned_entity>
- define size <[ball_collision].bounding_box.get[1].sub[<[ball_collision].location>].x.abs.add[0.01]>
- spawn armor_stand[invulnerable=true;has_ai=false;gravity=false;visible=false] <[location].with_pitch[0].with_yaw[0].below[<[size].add[1]>]> save:ball_display
- define ball_display <entry[ball_display].spawned_entity>
- adjust <[ball_display]> equipment:<map[].with[helmet].as[<[display_item]>]>
- flag <[ball_collision]> ball:<[id]>
- flag <[ball_collision]> ball_display:<[ball_display]>
- flag <[ball_collision]> ball_velocity:<location[0,0,0,<[location].world>]>
- flag <[ball_collision]> ball_size:<[size]>
- flag <[ball_collision]> ball_gravity:<[gravity_multiplier]>
- flag <[ball_display]> ball:<[id]>
- flag server ball:<server.flag[ball].if_null[<map[]>].with[<[id]>].as[<[ball_collision]>]>
- determine <[ball_collision]>
# Removes a ball by id. If that ball does not exist, it will silently fail.
ball_remove:
debug: false
type: task
definitions: id
script:
- if <server.flag[ball].if_null[<map[]>].contains[<[id]>]>:
- remove <server.flag[ball].get[<[id]>].flag[ball_display]>
- remove <server.flag[ball].get[<[id]>]>
- flag server ball:<server.flag[ball].exclude[<[id]>]>
# Gets a ball by id.
ball_get:
debug: false
type: procedure
definitions: id
script:
- determine <server.flag[ball].if_null[<map[]>].get[<[id]>]>
# Adds a vector to the ball's current velocity
# This has the effect of "pushing" the ball in that direction.
ball_vector_add:
debug: false
type: task
definitions: ball|vector
script:
- flag <[ball]> ball_velocity:<[ball].flag[ball_velocity].add[<[vector]>]>
# Handling a ball's event:
# Create a world script and add the following event
# // on custom event id:ball_move:
# The following context is provided:
# - <context.ball_id> (EleemntTag) - the ball's ID.
# - <context.ball> (EntityTag) - the ball entity with current state in its flag_map
# - <context.now> (LocationTag) - the ball's current location
# - <context.next> (LocationTag) - the ball's computed next location, which may be reflected if it bounced
# - <context.velocity_t0> (LocationTag) - the ball's current velocity
# - <context.velocity_t1> (LocationTag) - the ball's next computed velocity
# - <context.bounced> (ElementTag) - true if the ball bounced, false otherwise
# - <context.ground> (ElementTag) - true if the ball bounced or is on the ground, false otherwise
# You can cancel the event as any other event, or you can "determine output:<list[...]>" containing:
# - element 1 (LocationTag): the next location for the ball for when it gets moved
# - element 2 (LocationTag): the next velocity for the next processing tick
## Do not change! This handles updating the ball's physics as well as movement.
ball_internal_physics_update:
debug: false
type: world
events:
on tick:
- foreach <server.flag[ball].if_null[<map[]>]> key:id as:ball:
- run ball_internal_physics_update_ball def.ball:<[ball]>
on entity damaged:
- if <context.entity.has_flag[ball]>:
- determine cancelled passively
on player right clicks entity:
- if <context.entity.has_flag[ball]>:
- determine cancelled passively
ball_internal_physics_update_ball:
debug: false
type: task
definitions: ball
script:
- define velocity <[ball].flag[ball_velocity]>
- define gravity <[ball].flag[ball_gravity].mul[0.035]>
- define now <[ball].location>
- define next <[ball].location.add[<[velocity]>]>
- define yaw <[next].direction[<[now]>].yaw.sub[180].if_null[0]>
- define pitch <location[0,1,0].quaternion_between_vectors[<[next].sub[<[now]>].normalize>].represented_angle.to_degrees.sub[90].if_null[0]>
- define now <[now].with_yaw[<[yaw]>].with_pitch[<[pitch]>]>
- define size <[ball].flag[ball_size]>
- define trace <[now].ray_trace[range=<[now].distance[<[next]>].add[<[size]>]>].if_null[null]>
- define bounced false
- if <[trace]> != null:
- define bounced true
- define normal <[trace].direction.vector>
- define direction <[next].sub[<[now]>]>
- define dot <[direction].x.mul[<[normal].x>].add[<[direction].y.mul[<[normal].y>]>].add[<[direction].z.mul[<[normal].z>]>]>
- define reflection <[direction].sub[<[normal].mul[<[dot].mul[2]>]>].mul[0.65]>
- define next <[trace].add[<[reflection]>]>
- define velocity <[reflection]>
- if <util.current_time_millis.sub[<[ball].flag[ball_sound_time].if_null[0]>]> > 200:
- playsound <[now]> sound:BLOCK_STONE_BREAK pitch:1.8
- flag <[ball]> ball_sound_time:<util.current_time_millis>
- define grounded false
- if !<[ball].location.below[<[size].div[4]>].material.is_solid>:
- define velocity <[velocity].mul[0.99]>
- define velocity <[velocity].add[0,-<[gravity]>,0]>
- else:
- define velocity <[velocity].mul[0.75]>
- define next <[next].with_y[<[next].above[<[size].add[0.25]>].block.y>]>
- define grounded true
- if <[next].sub[<[now]>].vector_length_squared> >= 0.01:
- definemap event_context:
ball_id: <[ball].flag[ball]>
ball: <[ball]>
now: <[now]>
next: <[next]>
velocity_t0: <[ball].flag[ball_velocity]>
velocity_t1: <[velocity]>
bounced: <[bounced]>
ground: <[grounded]>
- customevent id:ball_move context:<[event_context]> save:move_event
- if <entry[move_event].any_ran>:
- if <entry[move_event].was_cancelled>:
- stop
- if !<entry[move_event].determination_list.is_empty>:
- define changes <entry[move_event].determination_list>
- define next <[changes].get[1].if_null[<[next]>]>
- define velocity <[changes].get[2].if_null[<[velocity]>]>
- flag <[ball]> ball_velocity:<[velocity]>
- if <[size]> <= 2:
- teleport <[ball].flag[ball_display]> <[next].below[<[size].add[0.7]>]>
- else:
- teleport <[ball].flag[ball_display]> <[next].below[<[size].add[0.7]>]>
- teleport <[ball]> <[next]>

View File

@@ -0,0 +1,179 @@
#
# Custom Block - a lib/framework to create custom blocks with display entities easily.
# Note: this prioritizes being very fast even for many blocks, players, and without lag even on Vanilla.
# As a result, the range and scope of this is limited to that; you should *really* use note-blocks or
# tripwire block states for most things if you can help it. A mix of both is best!
#
# You should change the computations to handle more chunks instead of just a 3x3 area if you don't expect
# too many players, if your CPU is particularly powerful and fast, and if you don't mind client-side lag.
#
# This library will automatically "render" only the custom blocks in a certain range around the player.
# By default, this is a 3x3 chunk area around the player; the item displays have a 0.75 view-range.
# This will not show the custom entities particularly far, but it will ensure very good FPS & TPS.
# The algorithm should be fairly fast, especially for Denizen; likely a pure Java algorithm would
# have better performance but not by much, since the `fakespawn` and `flag` utils of Denizen are
# hard to beat.
#
# API
custom_block_create:
debug: false
type: task
definitions: at|material|pitch|yaw|headless
script:
- define pitch <[pitch].if_null[0]>
- define yaw <[yaw].if_null[0]>
- define world <[at].world.name>
- define chunk_x <[at].chunk.x>
- define chunk_z <[at].chunk.z>
#
- define entry_key custom_block_<[world]>,<[chunk_x]>,<[chunk_z]>
- define at <[at].center>
- define where <[at].x>,<[at].y>,<[at].z>
- define value_key <[where]>,<[pitch]>,<[yaw]>,<[material]>
#
- define block_set <server.flag[<[entry_key]>].if_null[<list[]>]>
- if <[block_set].filter_tag[<[filter_value].starts_with[<[where]>]>].size> > 0:
- debug error "[Custom Block Lib] Duplicate block add: <[where]>,<[world]>! Call custom_block_destroy first"
- stop
- define block_set <[block_set].include[<[value_key]>]>
- flag server <[entry_key]>:<[block_set]>
#
- if <[headless].if_null[false]>:
- stop
- define players <list[]>
- repeat 3 from:<[chunk_x].sub[1]> as:i:
- repeat 3 from:<[chunk_z].sub[1]> as:k:
- define chunk <chunk[<[i]>,<[k]>,<[world]>]>
- foreach <[chunk].players> as:player:
- define players <[players].include[<[player]>].deduplicate>
- foreach <[players]> as:player:
- run custom_block_render def.player:<[player]>
custom_block_destroy:
debug: false
type: task
definitions: at|headless
script:
- define world <[at].world.name>
- define chunk_x <[at].chunk.x>
- define chunk_z <[at].chunk.z>
- define entry_key custom_block_<[world]>,<[chunk_x]>,<[chunk_z]>
#
- define at <[at].center>
- define where <[at].x>,<[at].y>,<[at].z>
#
- define block_set <server.flag[<[entry_key]>].if_null[<list[]>]>
- define block_set_length <[block_set].size>
- define block_set <[block_set].filter_tag[<[filter_value].starts_with[<[where]>].not>]>
- if <[block_set].size> == <[block_set_length]>:
- debug error "[Custom Block Lib] Tried removing inexistent custom block: <[where]>,<[world]>"
- stop
- if <[block_set].size> == 0:
- flag server <[entry_key]>:!
- else:
- flag server <[entry_key]>:<[block_set]>
#
- if <[headless].if_null[false]>:
- stop
- define players <list[]>
- repeat 3 from:<[chunk_x].sub[1]> as:i:
- repeat 3 from:<[chunk_z].sub[1]> as:k:
- define chunk <chunk[<[i]>,<[k]>,<[world]>]>
- foreach <[chunk].players> as:player:
- define players <[players].include[<[player]>].deduplicate>
- foreach <[players]> as:player:
- run custom_block_render def.player:<[player]>
custom_block_at:
debug: false
type: procedure
definitions: at
script:
- define world <[at].world.name>
- define chunk_x <[at].chunk.x>
- define chunk_z <[at].chunk.z>
- define entry_key custom_block_<[world]>,<[chunk_x]>,<[chunk_z]>
#
- define at <[at].center>
- define where <[at].x>,<[at].y>,<[at].z>
#
- if !<server.has_flag[<[entry_key]>]>:
- determine null
- define block_set <server.flag[<[entry_key]>]>
- foreach <[block_set]> as:cblock:
- if <[cblock].starts_with[<[where]>]>:
- determine <[cblock].split[,].get[6]>
- determine null
# Render routine
custom_block_flush:
debug: false
type: task
definitions: player|complete
script:
- define complete <[complete].if_null[false]>
- define prerendered <[player].flag[custom_block_prerendered].if_null[<map[]>]>
- if <[complete]>:
- foreach <[player].fake_entities> as:fentity:
- if <[fentity].custom_name.contains[__CustomBlock__].if_null[false]>:
- fakespawn <[fentity]> cancel player:<[player]>
- else:
- foreach <[player].fake_entities> as:fentity:
- if <[fentity].custom_name.contains[__CustomBlock__].if_null[false]>:
- if !<[prerendered].contains[<[fentity].location.escaped>]>:
- fakespawn <[fentity]> cancel player:<[player]>
custom_block_render:
debug: false
type: task
definitions: player
script:
- define world <[player].location.world.name>
- define chunk_x <[player].location.chunk.x>
- define chunk_z <[player].location.chunk.z>
- define player_y <[player].location.y>
- flag <[player]> custom_block_lrx:<[chunk_x]>
- flag <[player]> custom_block_lrz:<[chunk_z]>
- flag <[player]> custom_block_lry:<[player_y]>
- define prerendered <[player].flag[custom_block_prerendered].if_null[<map[]>]>
- define valid <map[]>
- repeat 3 from:<[chunk_x].sub[1]> as:i:
- repeat 3 from:<[chunk_z].sub[1]> as:k:
- define entry_key custom_block_<[world]>,<[i]>,<[k]>
- if <server.has_flag[<[entry_key]>]>:
- foreach <server.flag[<[entry_key]>]> as:cblock:
- define cblock <[cblock].split[,]>
- if <[cblock].get[2].sub[<[player_y]>].abs> > 24:
- foreach next
- define at <location[<[cblock].get[1]>,<[cblock].get[2]>,<[cblock].get[3]>,<[cblock].get[4]>,<[cblock].get[5]>,<[world]>]>
- define valid <[valid].with[<[at].escaped>].as[true]>
- if <[prerendered].contains[<[at].escaped>]>:
- foreach next
- define of <[cblock].get[6]>
- define display <entity[item_display]>
- adjust def:display item:<item[<[of]>]>
- adjust def:display display:fixed
- adjust def:display view_range:0.75
- adjust def:display custom_name:__CustomBlock__
- fakespawn <[display]> <[at]> player:<[player]> duration:-1
- define prerendered <[prerendered].with[<[at].escaped>].as[true]>
- define prerendered <[prerendered].filter_tag[<[valid].contains[<[filter_key]>]>]>
- flag <[player]> custom_block_prerendered:<[prerendered]>
- run custom_block_flush def.player:<[player]>
custom_block_world:
debug: false
type: world
events:
after player joins:
- flag <player> custom_block_prerendered:!
- run custom_block_flush def.player:<player> def.complete:true
- ~run custom_block_render def.player:<player>
on player walks:
- ratelimit <player> 5t
- if <player.flag[custom_block_lrx].if_null[0]> == <context.new_location.chunk.x>:
- if <player.flag[custom_block_lrz].if_null[0]> == <context.new_location.chunk.z>:
- if <player.flag[custom_block_lry].if_null[0].sub[<context.new_location.y>].abs> <= 8:
- stop
- ~run custom_block_render def.player:<player>

8
scripts/lib/formats.dsc Normal file
View File

@@ -0,0 +1,8 @@
#
# Common Formats - change these to change everywhere!
#
formats_prefix:
debug: false
type: format
format: <&8>[<&color[#ff96ee]>Sakura<&color[#e8e8e8]>Falls<&8>] <&7><[text]>

View File

@@ -0,0 +1,63 @@
#
# GUI Restore - deletes & restores inventories that start with
# some specific special chars (usually color codes)
# (but also anvils because of anvil_input.dsc)
#
gui_restore_config:
debug: false
type: data
special_chars: <&f>邑邑邑邑
gui_restore_set_later:
debug: false
type: task
definitions: player|slot|item
script:
- flag <[player]> gui_restore_later:<[player].flag[gui_restore_later].if_null[<map[]>].with[<[slot]>].as[<[item]>]>
gui_restore_save:
debug: false
type: task
definitions: player
script:
- if <[player].has_flag[gui_restore]>:
- run gui_restore_load def.player:<player>
- flag <[player]> gui_restore:<[player].inventory.map_slots>
- define equipment <[player].inventory.equipment_map>
- inventory clear player:<[player]>
- foreach <[equipment]> key:slot as:item:
- inventory set slot:<[slot]> origin:<[item]> player:<[player]>
- foreach <[player].flag[gui_restore_later].if_null[<map[]>]> key:slot as:item:
- inventory set slot:<[slot]> origin:<[item]> player:<[player]>
gui_restore_load:
debug: false
type: task
definitions: player
script:
- if !<[player].has_flag[gui_restore]>:
- stop
- flag <[player]> gui_restore_later:!
- inventory clear player:<[player]>
- foreach <[player].flag[gui_restore]> key:slot as:item:
- inventory set slot:<[slot]> origin:<[item]> player:<[player]>
- flag <[player]> gui_restore:!
gui_restore_world:
debug: false
type: world
events:
on player joins:
- if <player.has_flag[gui_restore_later]>:
- flag <player> gui_restore_later:!
- if <player.has_flag[gui_restore]>:
- run gui_restore_load def.player:<player>
on player opens inventory bukkit_priority:lowest:
- if <context.inventory.title.starts_with[<script[gui_restore_config].data_key[special_chars].parsed>]>:
- run gui_restore_save def.player:<player>
- if <context.inventory.inventory_type> == anvil:
- run gui_restore_save def.player:<player>
on player closes inventory bukkit_priority:lowest:
- if <player.has_flag[gui_restore]>:
- run gui_restore_load def.player:<player>

100
scripts/lib/menu.dsc Normal file
View File

@@ -0,0 +1,100 @@
#
# Utility for creating menus with click handlers on them.
# It also provides a way to make paged menus more easily.
#
# opens a menu with a title, of size, based on the contents.
# the size must be a multiple of 9
#
# the contents should be a map pairing the slot to three keys
# the 'item' key is the item's display in the menu
# the 'script' key is the script to call when clicked
# the 'definitions' key is a map pairing definition name to value
# if the script is null, then no script is called on interaction
# if the definitions are null, the script is called without definitions
# at the end, after opening, this task determines the opened inventory.
#
# the fill argument can be ommitted, or is an item which fills any
# slots not specified in the contents map
menu_open:
debug: false
type: task
definitions: player|title|size|contents|fill
script:
- define display_key <element[menu].to_secret_colors.split[<&ss>].remove[1].parse_tag[<&ss><[parse_value]><&sp>].separated_by[]>
- define inventory <inventory[generic[title=<[title]><[display_key]>;size=<[size]>]]>
# fill
- foreach <[contents]> key:slot as:data:
- define item <[data].get[item].if_null[null]>
# bad item
- if <[item]> == null:
- foreach next
# fit other data
- define script <[data].get[script].if_null[null]>
- define definitions <[data].get[definitions].if_null[null]>
# flag item accordingly
- if <[script]> != null:
- flag <[item]> _menu_script:<[script]>
- if <[definitions]> != null:
- flag <[item]> _menu_definitions:<[definitions]>
# set in inventory
- inventory set slot:<[slot]> origin:<[item]> destination:<[inventory]>
# has fill attribute?
- if <[fill].if_null[null]> != null:
- repeat <[size]> as:slot:
- if !<[contents].contains[<[slot]>]>:
- inventory set slot:<[slot]> origin:<[fill]> destination:<[inventory]>
# open inventory and return
- inventory open player:<[player]> destination:<[inventory]>
- determine <[inventory]>
# checks if the passed menu parameter is actually
# a menu or not based on its display key
menu_is:
debug: false
type: procedure
definitions: inventory
script:
- if <[inventory].title.length.sub[22]> < 1:
- determine false
- define display_key <[inventory].title.substring[<[inventory].title.length.sub[22]>].replace[<&sp>].with[].from_secret_colors>
- determine <[display_key].equals[menu]>
# determines the title of a menu ignoring its display key
# this also strips any colors to avoid annoyances
# useful for other handlers elsewhere
menu_title:
debug: false
type: procedure
definitions: inventory
script:
- determine <[inventory].title.substring[0,<[inventory].title.length.sub[25]>].strip_color.if_null[Unknown]>
# handles basic clicks in menu inventories
menu_click_handler:
debug: false
type: world
events:
on player clicks item in inventory:
# ignore non-gui clicks
## as of 1.20.4
- if <context.inventory.inventory_type> != CHEST:
- stop
# ignore non menus
- if !<proc[menu_is].context[<context.inventory>]>:
- stop
# valid menu, stop all events
- determine cancelled passively
# ignore out of bounds clicks
- if <context.raw_slot> > <context.inventory.size>:
- stop
# handle click if any
- define script <context.item.flag[_menu_script].if_null[null]>
- if <[script]> != null:
- define definitions <context.item.flag[_menu_definitions].if_null[null]>
- if <[definitions]> == null:
# run plain script
- run <[script]>
- else:
# run scripts with definitions
- run <[script]> defmap:<[definitions]>

317
scripts/lib/textbox.dsc Normal file
View File

@@ -0,0 +1,317 @@
#
# Textbox - a lib/framework to display an Undertale/RPG style text box.
# Requires resource pack to be set up correctly.
# The magic character is U+9300, i.e 錀, which you can change in the code.
#
# Writes into the text box display.
# You should provide at most 3 lines into the textbox (though other values are technically allowed).
# Lines are split using $$nl: line3s should be simple text, not a list directly.
# Optionally, provide an avatar_unicode character that should be placed next to the textbox.
textbox_write:
debug: false
type: task
definitions: player|queue|line3s|avatar_unicode
script:
- define avatar_unicode <[avatar_unicode].if_null[null]>
- if <[player].flag[textbox_state].if_null[null]> == writing:
- stop
- if !<[player].is_online>:
- if <[queue].if_null[null]> != null:
- debug log "[Textbox] Write; cancelled queue <[queue].numeric_id><&at><[queue].script.name> for <[player].name>; offline player."
- queue stop <[queue]>
- run textbox_flush def.player:<[player]>
- stop
- if !<[player].is_on_ground> && <player.gamemode> != spectator:
- if <[player].location.below.material.is_solid>:
- teleport <[player]> <[player].location.below.above.with_y[<[player].location.below.above.y.round_down>]>
- else:
- stop
- waituntil <[player].flag[textbox_state].if_null[null]> == null max:5s
- ~run textbox_flush def.player:<[player]>
- if !<[player].is_online>:
- if <[queue].if_null[null]> != null:
- debug log "[Textbox] Write; cancelled queue <[queue].numeric_id><&at><[queue].script.name> for <[player].name>; offline player."
- queue stop <[queue]>
- run textbox_flush def.player:<[player]>
- stop
- define lines <[line3s].split[$$nl].parse_tag[<[parse_value].trim>]>
- flag <[player]> textbox_state:writing
- flag <[player]> textbox_input:<[lines]>
- flag <[player]> textbox_lines:<[lines].size>
- bossbar create textbox_<[player].uuid>_ui players:<[player]> title:錀
- bossbar create textbox_<[player].uuid>_1 players:<[player]> title:<empty>
- bossbar create textbox_<[player].uuid>_2 players:<[player]> title:<empty>
- bossbar create textbox_<[player].uuid>_3 players:<[player]> title:<empty>
- wait 1t
- if <[avatar_unicode]> != null:
- bossbar create textbox_<[player].uuid>_avatar players:<[player]> title:<element[ ].repeat[64]><[avatar_unicode]>
- foreach <[lines]> as:line:
- if <[player].flag[textbox_state].if_null[null]> != writing:
- stop
- if !<[player].is_online>:
- stop
- repeat <[line].length>:
- if <[player].flag[textbox_state].if_null[null]> != writing:
- stop
- if !<[player].is_online>:
- stop
- bossbar update textbox_<[player].uuid>_<[loop_index]> title:<black><bold><[line].substring[1,<[value]>]>
- if <[value].sub[1].mod[3]> == 0:
- playsound sound:textbox.text <[player]> custom pitch:<util.random.decimal[0.98].to[1]>
- wait 1t
- if <[line].substring[<[value].add[1]>,<[value].add[1]>].trim.length.if_null[1]> == 0:
- if <[line].substring[<[value]>,<[value]>]> == ".":
- wait 2t
- if <[line].substring[<[value]>,<[value]>]> == "!":
- wait 2t
- if <[line].substring[<[value]>,<[value]>]> == "?":
- wait 2t
- if <[line].substring[<[value]>,<[value]>]> == "-":
- wait 2t
- if <[line].substring[<[value]>,<[value]>]> == ",":
- wait 2t
- wait <duration[1t]>
- if <[player].flag[textbox_state].if_null[null]> != writing:
- if <[queue].if_null[null]> != null && <[player].flag[textbox_state].if_null[null]> != continue:
- debug log "[Textbox] Write; cancelled queue <[queue].numeric_id><&at><[queue].script.name> for <[player].name>; state mismatch."
- queue stop <[queue]>
- run textbox_flush def.player:<[player]>
- stop
- if !<[player].is_online>:
- if <[queue].if_null[null]> != null:
- debug log "[Textbox] Write; cancelled queue <[queue].numeric_id><&at><[queue].script.name> for <[player].name>; offline player."
- queue stop <[queue]>
- run textbox_flush def.player:<[player]>
- stop
- flag <[player]> textbox_state:continue
# Skips the textbox and fills in the text quickly - i.e. text mashing
textbox_skip:
debug: false
type: task
definitions: player
script:
- if <[player].flag[textbox_state].if_null[null]> != writing:
- stop
- repeat <[player].flag[textbox_lines]>:
- if !<server.current_bossbars.contains[textbox_<[player].uuid>_<[value]>]>:
- bossbar create textbox_<[player].uuid>_<[value]> players:<[player]> title:<empty>
- define lines <[player].flag[textbox_input]>
- foreach <[lines]> as:line:
- bossbar update textbox_<[player].uuid>_<[loop_index]> title:<black><bold><[line]>
- flag <[player]> textbox_state:continue
# Clears textbox and flushes all flag memory values
textbox_flush:
debug: false
type: task
definitions: player
script:
- if <server.current_bossbars.contains[textbox_<[player].uuid>_ui]>:
- bossbar remove textbox_<[player].uuid>_ui
- repeat 3:
- if <server.current_bossbars.contains[textbox_<[player].uuid>_<[value]>]>:
- bossbar remove textbox_<[player].uuid>_<[value]>
- if <server.current_bossbars.contains[textbox_<[player].uuid>_avatar]>:
- bossbar remove textbox_<[player].uuid>_avatar
- if <server.current_bossbars.contains[textbox_<[player].uuid>_top]>:
- bossbar remove textbox_<[player].uuid>_top
- bossbar remove textbox_<[player].uuid>_mid
- bossbar remove textbox_<[player].uuid>_bottom
- flag <[player]> textbox_state:!
- flag <[player]> textbox_input:!
- flag <[player]> textbox_lines:!
- flag <[player]> textbox_choices:!
- wait 10t
# Handle textbox events
# Internal only!
textbox_world:
debug: false
type: world
events:
# Skip the text and close the textbox by right click
on player right clicks block bukkit_priority:lowest:
- ratelimit <player> 5t
- if <context.hand> == OFF_HAND:
- stop
- inject textbox_handle_click
on player right clicks entity bukkit_priority:lowest:
- ratelimit <player> 5t
- if <context.hand> == OFF_HAND:
- stop
- inject textbox_handle_click
on player animates arm_swing:
- ratelimit <player> 5t
- inject textbox_handle_click
# Disallow block breaking (because skip is done via clicking, even in Creative Mode)
on player breaks block:
- define state <player.flag[textbox_state].if_null[false]>
- if <[state]> == writing || <[state]> == continue:
- determine cancelled
# Disallow damage when textbox is active
on player damages entity:
- define state <player.flag[textbox_state].if_null[false]>
- if <[state]> == writing || <[state]> == continue:
- determine cancelled
# Flush if rejoin
on player joins:
- run textbox_flush def.player:<player>
# No movement on text
on player walks:
- define state <player.flag[textbox_state].if_null[false]>
- if <[state]> == writing || <[state]> == continue:
- if <context.old_location.with_pitch[0].with_yaw[0]> != <context.new_location.with_pitch[0].with_yaw[0]>:
- determine cancelled
- if <[state]> == choice:
- if <context.old_location.with_pitch[0].with_yaw[0]> != <context.new_location.with_pitch[0].with_yaw[0]>:
- determine cancelled passively
- ratelimit <player> 5t
#
- define reference <context.old_location>
- define left <[reference].left[0.001]>
- define right <[reference].right[0.001]>
- define up <[reference].forward[0.001]>
- define down <[reference].backward[0.001]>
#
- define min <[left].sub[<context.new_location>].vector_length_squared>
- define min_dir left
#
- define right_check <[right].sub[<context.new_location>].vector_length_squared>
- if <[right_check]> < <[min]> :
- define min <[right_check]>
- define min_dir right
#
- define up_check <[up].sub[<context.new_location>].vector_length_squared>
- if <[up_check]> < <[min]> :
- define min <[up_check]>
- define min_dir top
#
- define down_check <[down].sub[<context.new_location>].vector_length_squared>
- if <[down_check]> < <[min]> :
- define min <[down_check]>
- define min_dir bottom
#
- run textbox_internal_choice_select def.player:<player> def.choice_dir:<[min_dir]>
# Internal only!
textbox_handle_click:
debug: false
type: task
script:
- define state <player.flag[textbox_state].if_null[false]>
- if <[state]> == writing:
- determine cancelled passively
- run textbox_skip def.player:<player>
- ratelimit <player> 5t
- else if <[state]> == continue:
- determine cancelled passively
- playsound sound:textbox.close <player> custom
- ~run textbox_flush def.player:<player>
- ratelimit <player> 10t
- else if <[state]> == choice:
- determine cancelled passively
- playsound sound:input.ok <player> custom
- flag <player> textbox_choice_select:<player.flag[textbox_choices].get[current]>
- ~run textbox_flush def.player:<player>
- ratelimit <player> 10t
# Writes into the text box display.
# You should provide at least 2 and up to 4 choices.
# The choices are defined in the top, left, right, and bottom directions.
# The choices map has this structure:
# choices:
# left:
# text: 'Some Choice 1'
# task: task_to_continue_to_1
# right:
# text: 'Some Choice 2'
# task: task_to_continue_to_2
# ...
# When selected, the task receives the player as input in the <[player]> definition.
# The task also receives the direction that was picked in <[choice]>: top, bottom, left or right
textbox_choice:
debug: false
type: task
definitions: player|queue|choices
script:
- if <[player].flag[textbox_state].if_null[null]> == choice:
- stop
- if !<[player].is_online>:
- if <[queue].if_null[null]> != null:
- debug log "[Textbox] Choice; cancelled queue <[queue].numeric_id><&at><[queue].script.name> for <[player].name>; offline"
- queue stop <[queue]>
- run textbox_flush def.player:<[player]>
- stop
- if !<[player].is_on_ground> && <player.gamemode> != spectator:
- if <[player].location.below.material.is_solid>:
- teleport <[player]> <[player].location.below.above.with_y[<[player].location.below.above.y.round_down>]>
- else:
- stop
- waituntil <[player].flag[textbox_state].if_null[null]> == null
- ~run textbox_flush def.player:<[player]>
- flag <[player]> textbox_state:choice
- flag <[player]> textbox_choices:<map[].with[data].as[<[choices]>].with[current].as[left]>
- define first_choice left
- if <[choices].keys.contains[top]>:
- define first_choice top
- if !<[choices].keys.contains[left]>:
- define first_choice <[choices].keys.first>
- run textbox_internal_choice_select def.player:<[player]> def.choice_dir:<[first_choice]>
- waituntil <[player].flag[textbox_choice_select].if_null[null]> != null || !<[player].is_online>
- if !<[player].is_online>:
- if <[queue].if_null[null]> != null:
- debug log "[Textbox] Choice; cancelled queue <[queue].numeric_id><&at><[queue].script.name> for <[player].name>; offline"
- queue stop <[queue]>
- run textbox_flush def.player:<[player]>
- stop
- define result <[player].flag[textbox_choice_select]>
- flag <[player]> textbox_choice_select:!
- determine <[result]>
# Internal only!
textbox_internal_choice_select:
debug: false
type: task
definitions: player|choice_dir
script:
- if !<server.current_bossbars.contains[textbox_<[player].uuid>_top]>:
- bossbar create textbox_<[player].uuid>_ui players:<[player]> title:錀
- bossbar create textbox_<[player].uuid>_top players:<[player]> title:<empty>
- bossbar create textbox_<[player].uuid>_mid players:<[player]> title:<empty>
- bossbar create textbox_<[player].uuid>_bottom players:<[player]> title:<empty>
- define choices <[player].flag[textbox_choices].get[data]>
#
- define left <&0><&l><[choices].get[left].get[text].if_null[null]>
- define right <&0><&l><[choices].get[right].get[text].if_null[null]>
- define top <&0><&l><[choices].get[top].get[text].if_null[null]>
- define bottom <&0><&l><[choices].get[bottom].get[text].if_null[null]>
#
- if <[choice_dir]> == left:
- if <[left]> == <&0><&l>null:
- stop
- define left "<&4>❤ <&0><&l><[left]>"
- else if <[choice_dir]> == right:
- if <[right]> == <&0><&l>null:
- stop
- define right "<&4>❤ <&0><&l><[right]>"
- else if <[choice_dir]> == top:
- if <[top]> == <&0><&l>null:
- stop
- define top "<&4>❤ <&0><&l><[top]>"
- else:
- if <[bottom]> == <&0><&l>null:
- stop
- define bottom "<&4>❤ <&0><&l><[bottom]>"
#
- define mid_padding <element[<&sp>].repeat[<element[30].sub[<[left].strip_color.length>].sub[<[right].strip_color.length>]>]>
#
- if <[top]> != <&0><&l>null:
- bossbar update textbox_<[player].uuid>_top title:<[top]>
- if <[left]> != <&0><&l>null || <[right]> != <&0><&l>null:
- bossbar update textbox_<[player].uuid>_mid title:<[left]><[mid_padding]><[right]>
- if <[bottom]> != <&0><&l>null:
- bossbar update textbox_<[player].uuid>_bottom title:<[bottom]>
- flag <[player]> textbox_choices:<map[].with[data].as[<[choices]>].with[current].as[<[choice_dir]>]>
- playsound BLOCK_NOTE_BLOCK_BIT <[player]> pitch:2