v2017.11.26

- fixed spider cinematic being played for everyone connected
- fixed rocco sirocco's cinematic not being played
- implemented teleports between NS, starbase and lego club station
This commit is contained in:
lcdr
2017-11-26 16:13:19 +01:00
parent a5227c16e8
commit c863396dab
25 changed files with 225 additions and 85 deletions

View File

@@ -16,17 +16,6 @@ class AddMission(ChatCommand):
else:
sender.char.add_mission(int(args.mission))
class AutocompleteMissions(ChatCommand):
def __init__(self):
super().__init__("autocompletemissions")
self.command.add_argument("--enable", type=toggle_bool)
def run(self, args, sender):
if args.enable is None:
sender.char.autocomplete_missions = not sender.char.autocomplete_missions
else:
sender.char.autocomplete_missions = args.enable
class CompleteMission(ChatCommand):
def __init__(self):
super().__init__("completemission")

View File

@@ -1,6 +1,7 @@
import asyncio
import datetime
import logging
import math
import os
import re
import secrets
@@ -8,8 +9,12 @@ import time
from ..auth import Account, GMLevel, PasswordState
from ..bitstream import BitStream, c_bool, c_ushort
from ..ldf import LDF, LDFDataType
from ..messages import WorldClientMsg
from ..world import server
from ..components.physics import AABB, CollisionSphere, PrimitiveModelType
from ..math.quaternion import Quaternion
from ..math.vector import Vector3
from .command import ChatCommand, normal_bool
log = logging.getLogger(__name__)
@@ -112,6 +117,45 @@ class Mute(ChatCommand):
else:
server.chat.sys_msg_sender("Player not connected")
class PhysicsDebug(ChatCommand):
def __init__(self):
super().__init__("physicsdebug")
self.debug_markers = []
server.add_handler("proximity_radius", self.on_proximity_radius)
def run(self, args, sender):
if self.debug_markers:
for marker in self.debug_markers:
server.replica_manager.destruct(marker)
self.debug_markers.clear()
else:
for obj in server.general.tracked_objects.copy():
self.spawn_debug_marker(obj)
def on_proximity_radius(self, obj):
if not self.debug_markers:
return
self.spawn_debug_marker(obj)
def spawn_debug_marker(self, obj):
coll = server.general.tracked_objects[obj]
set_vars = {"parent": obj, "rotation": Quaternion.identity}
if isinstance(coll, AABB):
config = LDF()
config.ldf_set("primitiveModelType", LDFDataType.INT32, PrimitiveModelType.Cuboid)
config.ldf_set("primitiveModelValueX", LDFDataType.FLOAT, coll.max.x-coll.min.x)
config.ldf_set("primitiveModelValueY", LDFDataType.FLOAT, coll.max.y-coll.min.y)
config.ldf_set("primitiveModelValueZ", LDFDataType.FLOAT, coll.max.z-coll.min.z)
set_vars["position"] = Vector3((coll.min.x+coll.max.x)/2, coll.min.y, (coll.min.z+coll.max.z)/2)
set_vars["config"] = config
marker = server.spawn_object(14510, set_vars)
elif isinstance(coll, CollisionSphere):
set_vars["position"] = coll.position
set_vars["scale"] = math.sqrt(coll.sq_radius)/5
marker = server.spawn_object(6548, set_vars)
self.debug_markers.append(marker)
class ResetPassword(ChatCommand):
def __init__(self):
super().__init__("resetpassword")

View File

@@ -561,6 +561,10 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP
def bounce_notification(self, object_id_bounced:c_int64=None, object_id_bouncer:c_int64=None, success:bool=None):
pass
@single
def display_zone_summary(self, is_property_map:bool=False, is_zone_start:bool=False, sender:GameObject=None):
pass
@broadcast
def start_arranging_with_item(self, first_time:bool=True, build_area:GameObject=0, build_start_pos:Vector3=None, source_bag:c_int=None, source_id:c_int64=None, source_lot:c_int=None, source_type:c_int=None, target_id:c_int64=None, target_lot:c_int=None, target_pos:Vector3=None, target_type:c_int=None):
self.object.inventory.push_equipped_items_state()

View File

@@ -11,7 +11,6 @@ from ..mission import check_prereqs, MissionProgress, MissionState, ObtainItemTy
class CharMission:
def __init__(self):
self.autocomplete_missions = False
self.missions = PersistentMapping()
# add achievements
for mission_id, data in server.db.missions.items():

View File

@@ -5,12 +5,12 @@ from ...messages import single
class CharUI:
@single
def display_message_box(self, show:bool=None, callback_client:GameObject=None, identifier:str=None, image_id:c_int=None, text:str=None, user_data:str=None):
def display_message_box(self, show:bool=None, callback_client:GameObject=None, id:str=None, image_id:c_int=None, text:str=None, user_data:str=None):
pass
def disp_message_box(self, text):
def disp_message_box(self, text, id="", callback=None):
"""display_message_box with default parameters."""
self.display_message_box(show=True, callback_client=None, identifier="", image_id=0, text=text, user_data="")
self.display_message_box(show=True, callback_client=callback, id=id, image_id=0, text=text, user_data="")
@single
def display_tooltip(self, do_or_die:bool=False, no_repeat:bool=False, no_revive:bool=False, is_property_tooltip:bool=False, show:bool=None, translate:bool=False, time:c_int=None, id:str=None, localize_params:LDF=None, image_name:str=None, text:str=None):

View File

@@ -31,6 +31,6 @@ class Comp108Component(Component):
if self.driver_id != 0:
server.game_objects[self.driver_id].char.dismount()
def request_die(self, unknown_bool:bool=None, death_type:str=None, direction_relative_angle_xz:float=None, direction_relative_angle_y:float=None, direction_relative_force:float=None, kill_type:c_int=0, killer:GameObject=None, loot_owner:GameObject=0):
def request_die(self, unknown_bool:bool=None, death_type:str=None, direction_relative_angle_xz:float=None, direction_relative_angle_y:float=None, direction_relative_force:float=None, kill_type:c_int=0, killer:GameObject=None, loot_owner:GameObject=None):
#self.object.destructible.deal_damage(10000, self) # die permanently on crash
self.object.call_later(3, self.object.destructible.resurrect)

View File

@@ -46,11 +46,11 @@ class DestructibleComponent(Component):
self.object.stats.life = max(0, self.object.stats.life - (damage - self.object.stats.armor))
self.object.stats.armor = max(0, self.object.stats.armor - damage)
def simply_die(self, death_type:str="", kill_type:c_int=KillType.Violent, killer:GameObject=None, loot_owner:GameObject=0):
def simply_die(self, death_type:str="", kill_type:c_int=KillType.Violent, killer:GameObject=None, loot_owner:GameObject=None):
"""Shorthand for request_die with default values."""
self.request_die(False, death_type, 0, 0, 10, kill_type, killer, loot_owner)
def request_die(self, unknown_bool:bool=None, death_type:str=None, direction_relative_angle_xz:float=None, direction_relative_angle_y:float=None, direction_relative_force:float=None, kill_type:c_int=KillType.Violent, killer:GameObject=None, loot_owner:GameObject=0):
def request_die(self, unknown_bool:bool=None, death_type:str=None, direction_relative_angle_xz:float=None, direction_relative_angle_y:float=None, direction_relative_force:float=None, kill_type:c_int=KillType.Violent, killer:GameObject=None, loot_owner:GameObject=None):
if self.object.stats.life == 0:
# already dead
return

View File

@@ -58,7 +58,6 @@ class MissionProgress(Persistent):
self.tasks = [MissionTask(task_type, target, target_value, parameter) for task_type, target, target_value, parameter in mission_data[2]]
self.is_mission = mission_data[3]
import asyncio
import logging
import random
@@ -66,7 +65,6 @@ from ..bitstream import c_int
from ..game_object import GameObject
from ..messages import single
from ..world import server
from ..commands.mission import CompleteMission
from .component import Component
log = logging.getLogger(__name__)
@@ -149,9 +147,6 @@ class MissionNPCComponent(Component):
if mission_state == MissionState.Available:
assert not is_complete
player.char.add_mission(mission_id)
if player.char.autocomplete_missions:
asyncio.get_event_loop().call_soon(CompleteMission.async_complete_mission, CompleteMission, mission_id, False, player)
elif mission_state == MissionState.ReadyToComplete:
assert is_complete
player.char.complete_mission(mission_id)

View File

@@ -36,31 +36,11 @@ class PhysicsComponent(Component):
for comp in self.object.components:
if hasattr(comp, "on_enter") or hasattr(comp, "on_exit"):
server.general.tracked_objects[self.object] = CollisionSphere(self.object, radius)
if server.get_objects_in_group("physics_debug_marker"):
self.spawn_debug_marker()
if "proximity_radius" in server._handlers:
for handler in server._handlers["proximity_radius"]:
handler(self)
break
def spawn_debug_marker(self):
if self.object not in server.general.tracked_objects:
return
coll = server.general.tracked_objects[self.object]
set_vars = {"groups": ("physics_debug_marker",), "parent": self.object, "rotation": Quaternion.identity}
if isinstance(coll, AABB):
config = LDF()
config.ldf_set("primitiveModelType", LDFDataType.INT32, PrimitiveModelType.Cuboid)
config.ldf_set("primitiveModelValueX", LDFDataType.FLOAT, coll.max.x-coll.min.x)
config.ldf_set("primitiveModelValueY", LDFDataType.FLOAT, coll.max.y-coll.min.y)
config.ldf_set("primitiveModelValueZ", LDFDataType.FLOAT, coll.max.z-coll.min.z)
set_vars["position"] = Vector3((coll.min.x+coll.max.x)/2, coll.min.y, (coll.min.z+coll.max.z)/2)
set_vars["config"] = config
server.spawn_object(14510, set_vars)
elif isinstance(coll, CollisionSphere):
set_vars["position"] = coll.position
set_vars["scale"] = math.sqrt(coll.sq_radius)/5
server.spawn_object(6548, set_vars)
# not really related to physics, but depends on physics and hasn't been conclusively associated with a component
def drop_rewards(self, loot_matrix, currency_min, currency_max, owner):
@@ -339,8 +319,6 @@ class PhantomPhysicsComponent(PhysicsComponent):
continue
if hasattr(comp, "on_enter") or hasattr(comp, "on_exit"):
server.general.tracked_objects[self.object] = AABB(self.object)
if server.get_objects_in_group("physics_debug_marker"):
self.spawn_debug_marker()
break
def on_enter(self, player):

View File

@@ -44,8 +44,8 @@ class ScriptedActivityComponent(Component):
def activity_start(self):
pass
def message_box_respond(self, player, button:c_int=None, identifier:str=None, user_data:str=None):
if identifier == "LobbyReady" and button == 1:
def message_box_respond(self, player, button:c_int=None, id:str=None, user_data:str=None):
if id == "LobbyReady" and button == 1:
asyncio.ensure_future(player.char.transfer_to_world((self.transfer_world_id, 0, 0)))
@single

View File

@@ -17,7 +17,9 @@ class GameObject:
self.attr_changed(name)
super().__setattr__(name, value)
def __init__(self, lot, object_id, set_vars={}):
def __init__(self, lot, object_id, set_vars=None):
if set_vars is None:
set_vars = {}
self._handlers = {}
self._flags = {}
self._flags["parent_flag"] = "related_objects_flag"
@@ -81,11 +83,19 @@ class GameObject:
for component_type, component_id in sorted(comp_ids, key=lambda x: component_order.index(x[0]) if x[0] in component_order else 99999):
if component_type == 5:
if "custom_script" in set_vars and set_vars["custom_script"] is not None:
try:
script = importlib.import_module("luserver.scripts."+set_vars["custom_script"])
comp = script.ScriptComponent,
except ModuleNotFoundError as e:
log.warning(str(e))
comp = ScriptComponent,
elif component_id is not None and component_id in server.db.script_component:
try:
script = importlib.import_module("luserver.scripts."+server.db.script_component[component_id])
comp = script.ScriptComponent,
except ModuleNotFoundError as e:
log.warning(str(e))
comp = ScriptComponent,
else:
comp = ScriptComponent,
elif component_type in component:

View File

@@ -194,6 +194,7 @@ class GameMessage(Enum):
BounceNotification = 932
BBBSaveRequest = 1001
NotifyClientObject = 1042
DisplayZoneSummary = 1043
StartBuildingWithItem = 1057
StartArrangingWithItem = 1061
FinishArrangingWithItem = 1062

View File

@@ -19,7 +19,7 @@ log = logging.getLogger(__name__)
# Constant checksums that the client expects to verify map version
# (likely value of the last map revision)
checksum = {
_CHECKSUMS = {
World.VentureExplorer: 0x20b8087c,
World.ReturnToTheVentureExplorer: 0x26680a3c,
World.AvantGardens: 0x49525511,
@@ -57,22 +57,11 @@ class GeneralHandling:
def __init__(self):
server.general = self
self.tracked_objects = {}
physics_debug_cmd = server.chat.commands.add_parser("physicsdebug")
physics_debug_cmd.set_defaults(func=self.physics_debug_cmd)
server.register_handler(WorldServerMsg.LoadComplete, self.on_client_load_complete)
server.register_handler(WorldServerMsg.PositionUpdate, self.on_position_update)
server.register_handler(WorldServerMsg.GameMessage, self.on_game_message)
def physics_debug_cmd(self, args, sender):
debug_markers = server.get_objects_in_group("physics_debug_marker")
if not debug_markers:
for obj in self.tracked_objects.copy():
obj.physics.spawn_debug_marker()
else:
for marker in debug_markers:
server.replica_manager.destruct(marker)
def on_validated(self, address):
player = server.accounts[address].characters.selected()
if server.world_id[0] != 0:
@@ -100,7 +89,7 @@ class GeneralHandling:
load_world.write(c_ushort(world_id))
load_world.write(c_ushort(world_instance))
load_world.write(c_uint(world_clone))
load_world.write(c_uint(checksum.get(World(world_id), 0)))
load_world.write(c_uint(_CHECKSUMS.get(World(world_id), 0)))
load_world.write(bytes(2))
load_world.write(c_float(player.physics.position.x))
load_world.write(c_float(player.physics.position.y))

View File

@@ -10,4 +10,4 @@ class ScriptComponent(script.ScriptComponent):
assert multi_interact_id is None
player.char.set_flag(True, FLAG_ID)
player.inventory.remove_item(InventoryType.Items, lot=MAELSTROM_CUBE_LOT)
server.get_objects_in_group("cagedSpider")[0].script.fire_event_client_side(args="toggle", obj=None, sender=player)
server.get_objects_in_group("cagedSpider")[0].script.fire_event_client_side(args="toggle", obj=None, sender=player, player=player)

View File

@@ -0,0 +1,14 @@
import luserver.components.script as script
from luserver.bitstream import c_int, c_int64
from luserver.game_object import GameObject
from luserver.messages import single
class ScriptComponent(script.ScriptComponent):
# hacky workaround incoming:
# the clientside implementation is broken and doesn't check the sender param
# so the cinematic would get displayed for everyone since this message is broadcast
# easiest fix is to override it for this script to be single instead
# other option would be to allow broadcast messages to be single per-call, but that seems like even more of a hack
@single
def fire_event_client_side(self, args:str=None, obj:GameObject=None, param1:c_int64=0, param2:c_int=-1, sender:GameObject=None):
pass

View File

@@ -1,6 +1,7 @@
import luserver.components.script as script
from luserver.bitstream import c_int
from luserver.game_object import GameObject
from luserver.messages import single
from luserver.components.inventory import InventoryType
from luserver.components.mission import MissionState
@@ -13,3 +14,10 @@ class ScriptComponent(script.ScriptComponent):
player.char.complete_mission(1729)
elif mission_state == MissionState.ReadyToComplete:
player.inventory.remove_item(InventoryType.Items, lot=14397)
self.notify_client_object(name="switch", param1=0, param2=0, param_str=b"", param_obj=None, player=player)
# manually changed from broadcast to single because the client script abuses this message
# see also caged_spider
@single
def notify_client_object(self, name:str=None, param1:c_int=None, param2:c_int=None, param_obj:GameObject=None, param_str:bytes=None):
pass

View File

@@ -128,11 +128,11 @@ class ScriptComponent(script.ScriptComponent):
self.set_player_spawn_points()
def message_box_respond(self, player, button:c_int=None, identifier:str=None, user_data:str=None):
if identifier == "RePlay":
def message_box_respond(self, player, button:c_int=None, id:str=None, user_data:str=None):
if id == "RePlay":
self.start()
elif identifier == "Exit_Question" and button == 1:
elif id == "Exit_Question" and button == 1:
self.game_over(player)
self.object.scripted_activity.remove_player(player)
asyncio.ensure_future(player.char.transfer_to_last_non_instance(Vector3(131.83, 376, -180.31), Quaternion(0, -0.268720, 0, 0.963218)))

View File

@@ -0,0 +1,10 @@
import asyncio
import luserver.components.script as script
# todo: not completely implemented
class ScriptComponent(script.ScriptComponent):
def transfer(self, player, world, respawn_point_name):
player.render.play_animation("lup-teleport")
asyncio.get_event_loop().call_later(4, asyncio.ensure_future, player.char.transfer_to_world(world, respawn_point_name=respawn_point_name))

View File

@@ -0,0 +1,21 @@
import luserver.scripts.general.console_teleport as script
from luserver.amf3 import AMF3
from luserver.bitstream import c_int
from luserver.world import server
from luserver.components.char import TerminateType
# todo: implement visited worlds so the NS/NT choice UI can work
class ScriptComponent(script.ScriptComponent):
def on_use(self, player, multi_interact_id):
assert multi_interact_id is None
# todo: check if player has been to NT, if yes then display choice UI
player.char.disp_message_box(id="TransferBox", text="UI_TRAVEL_TO_LUP_STATION", callback=self.object)
def message_box_respond(self, player, button:c_int=None, id:str=None, user_data:str=None):
if id == "TransferBox":
if button == 1:
# todo: display zone summary (callback not working right now for some reason)
#player.char.display_zone_summary(sender=self.object)
self.transfer(player, (1200, 0, 0), "NS_LEGO_Club")
else:
player.char.terminate_interaction(terminator=self.object, type=TerminateType.FromInteraction)

View File

@@ -6,8 +6,8 @@ from luserver.bitstream import c_int
class ScriptComponent(script.ScriptComponent):
def on_use(self, player, multi_interact_id):
assert multi_interact_id is None
player.char.display_message_box(show=True, callback_client=self.object, identifier="instance_exit", image_id=0, text=self.script_vars.get("transfer_text", "DRAGON_EXIT_QUESTION"), user_data="")
player.char.display_message_box(show=True, callback_client=self.object, id="instance_exit", image_id=0, text=self.script_vars.get("transfer_text", "DRAGON_EXIT_QUESTION"), user_data="")
def message_box_respond(self, player, button:c_int=None, identifier:str=None, user_data:str=None):
if identifier == "instance_exit" and button == 1:
def message_box_respond(self, player, button:c_int=None, id:str=None, user_data:str=None):
if id == "instance_exit" and button == 1:
asyncio.ensure_future(player.char.transfer_to_last_non_instance())

View File

@@ -0,0 +1,28 @@
import luserver.scripts.general.teleport_to_ns_or_nt as script
from luserver.amf3 import AMF3
from luserver.bitstream import c_int
from luserver.world import server
from luserver.components.char import TerminateType
# todo: not completely implemented
# todo: implement visited worlds so the NS/NT choice UI can work
class ScriptComponent(script.ScriptComponent):
def on_use(self, player, multi_interact_id):
assert multi_interact_id is None
if server.world_id[0] == 1700:
# todo: check if player has been to NT, if yes then display choice UI
player.char.disp_message_box(id="TransferBox", text="UI_TRAVEL_TO_NS", callback=self.object)
else:
player.char.u_i_message_server_to_single_client(message_name=b"pushGameState", args=AMF3({"state": "Lobby", "context": {"user": str(player.object_id), "callbackObj": str(self.object.object_id), "HelpVisible": "show", "type": "Lego_Club_Valid"}}))
def message_box_respond(self, player, button:c_int=None, id:str=None, user_data:str=None):
if id == "PlayButton":
self.transfer(player, (1700, 0, 0), "")
elif id == "TransferBox":
if button == 1:
# todo: display zone summary (callback not working right now for some reason)
#player.char.display_zone_summary(sender=self.object)
self.transfer(player, (1200, 0, 0), "NS_LEGO_Club")
else:
player.char.terminate_interaction(terminator=self.object, type=TerminateType.FromInteraction)

View File

@@ -0,0 +1,35 @@
import asyncio
import luserver.scripts.general.teleport_to_ns_or_nt as script
from luserver.amf3 import AMF3
from luserver.bitstream import c_int
from luserver.world import server
from luserver.components.char import TerminateType
# todo: not completely implemented
# todo: implement visited worlds so the NS/NT choice UI can work
class ScriptComponent(script.ScriptComponent):
def on_use(self, player, multi_interact_id):
assert multi_interact_id is None
# todo: check if player has been to NT, if yes then display choice UI
if server.world_id[0] == 1600:
text = "UI_TRAVEL_TO_NS"
else:
text = "UI_TRAVEL_TO_LUP_STATION"
player.char.disp_message_box(id="TransferBox", text=text, callback=self.object)
def message_box_respond(self, player, button:c_int=None, id:str=None, user_data:str=None):
if id == "TransferBox":
if button == 1:
# todo: display zone summary (callback not working right now for some reason)
#player.char.display_zone_summary(sender=self.object)
if server.world_id[0] == 1600:
dest = 1200
spawnpoint = "NS_LW"
else:
dest = 1600
spawnpoint = ""
self.transfer(player, (dest, 0, 0), spawnpoint)
else:
player.char.terminate_interaction(terminator=self.object, type=TerminateType.FromInteraction)

View File

@@ -116,6 +116,7 @@ class WorldServer(Server):
self.not_console_logged_packets.add("GameMessage/ReadyForUpdates")
self.not_console_logged_packets.add("GameMessage/ScriptNetworkVarUpdate")
self.multi = MultiInstanceAccess()
self._handlers = {}
CharHandling()
ChatHandling()
GeneralHandling()
@@ -199,6 +200,19 @@ class WorldServer(Server):
lot, position, rotation = spawn_data
self.spawn_model(spawner_id, lot, position, rotation)
EVENT_NAMES = "proximity_radius"
def add_handler(self, event_name: str, handler):
if event_name not in WorldServer.EVENT_NAMES:
raise ValueError("Invalid event name %s", event_name)
self._handlers.setdefault(event_name, []).append(handler)
def remove_handler(self, event_name: str, handler):
if event_name not in WorldServer.EVENT_NAMES:
raise ValueError("Invalid event name %s", event_name)
if event_name not in self._handlers or handler not in self._handlers[event_name]:
raise RuntimeError("handler not found")
self._handlers[event_name].remove(handler)
def spawn_model(self, spawner_id, lot, position, rotation):
spawned_vars = {}
spawned_vars["position"] = position

View File

@@ -284,13 +284,10 @@ class Init:
self.root.components_registry.setdefault(row[0], []).append((row[1], row[2]))
if row[1] == 5 and row[2] not in self.root.script_component:
comp_row = self.cdclient.execute("select id, script_name from ScriptComponent where id == %i" % row[2]).fetchone()
if comp_row is None:
continue
id, script_name = comp_row
script_name = scripts.SCRIPTS.get(id)
if script_name is not None:
self.root.script_component[id] = script_name
# we don't even need to query the db since we've got our own scripts table
script_id = row[2]
if script_id in scripts.SCRIPTS:
self.root.script_component[script_id] = scripts.SCRIPTS[script_id]
elif row[1] == 7 and row[2] not in self.root.destructible_component:
faction, faction_list, level, loot_matrix_index, currency_index, life, armor, imagination, is_smashable = self.cdclient.execute("select faction, factionList, level, LootMatrixIndex, CurrencyIndex, life, armor, imagination, isSmashable from DestructibleComponent where id == %i" % row[2]).fetchone()

View File

@@ -16,6 +16,7 @@ SCRIPTS = {
847: "avant_gardens.rusty_steele",
849: "nimbus_station.concert_quickbuild",
867: "avant_gardens.survival.buff_station",
877: "avant_gardens.caged_spider",
882: "avant_gardens.survival.world_control",
901: "avant_gardens.survival.stromling_mech",
946: "gnarled_forest.torch",
@@ -43,10 +44,11 @@ SCRIPTS = {
1216: "items.cauldron_of_life",
1218: "items.anvil_of_armor",
1219: "items.fountain_of_imagination",
1239: "nimbus_station.lego_club_door",
1270: "avant_gardens.saluting_npcs",
1271: "avant_gardens.saluting_npcs",
1272: "avant_gardens.saluting_npcs",
1276: "general.transfer_world_on_use",
1276: "nimbus_station.lup_teleport",
1329: "crux_prime.aura_blossom_flower",
1345: "general.poi_mission",
1349: "crux_prime.scroll_shrine",
@@ -54,8 +56,8 @@ SCRIPTS = {
1419: "nexus_tower.water_fountain",
1458: "items.sunflower",
1481: "nexus_tower.vault",
1484: "general.transfer_world_on_use",
1485: "general.transfer_world_on_use",
1484: "nimbus_station.lup_teleport",
1485: "nimbus_station.lego_club_door",
1486: "general.transfer_world_on_use",
1519: "nexus_tower.venture_cannon",
1527: "general.transfer_world_on_use",
@@ -86,6 +88,7 @@ SCRIPTS = {
r"02_server\Map\General\L_FRICTION_VOLUME_SERVER.lua": "general.friction_volume",
r"02_server\Map\General\L_POI_MISSION.lua": "general.poi_mission",
r"02_server\Map\General\L_TOUCH_MISSION_UPDATE_SERVER.lua": "general.touch_complete_mission",
r"02_server\Map\NS\L_NS_LUP_TELEPORT.lua": "nimbus_station.lup_teleport",
r"02_server\Map\NS\L_NS_TOKEN_CONSOLE_SERVER.lua": "nimbus_station.token_console",
r"02_server\Map\NT\L_NT_ASSEMBLYTUBE_SERVER.lua": "nexus_tower.assembly_tube",
r"02_server\Map\NT\L_NT_PARADOXTELE_SERVER.lua": "nexus_tower.paradox_teleporter",
@@ -109,3 +112,4 @@ SCRIPTS = {
r"ai\NS\L_NS_MODULAR_BUILD.lua": "nimbus_station.rocket_modular_build",
r"ai\NS\L_NS_QB_IMAGINATION_STATUE.lua": "nimbus_station.imagination_statue",
r"ai\NS\NS_PP_01\L_NS_PP_01_TELEPORT.lua": "property.teleport"}