v2016.12.29

- refactor LDF
- collectible missions now properly save which specific collectibles have been collected
This commit is contained in:
lcdr
2016-12-29 12:16:02 +01:00
parent 77c0e9aa04
commit a15c619c12
25 changed files with 298 additions and 168 deletions

View File

@@ -31,7 +31,7 @@ class LoginReturnCode:
AccountNotActivated = 14
class AuthServer(server.Server):
PEER_TYPE = AuthServerMsg.__int__()
PEER_TYPE = AuthServerMsg.header()
def __init__(self, host, max_connections, db_conn):
super().__init__((host, 1001), max_connections, db_conn)

View File

@@ -3,7 +3,7 @@ from .messages import Message
def write_header(self, subheader):
self.write(c_ubyte(Message.LUPacket))
self.write(c_ushort(type(subheader).__int__()))
self.write(c_ushort(type(subheader).header()))
self.write(c_uint(subheader))
self.write(c_ubyte(0x00))

View File

@@ -5,6 +5,7 @@ from persistent.list import PersistentList
from persistent.mapping import PersistentMapping
from ..bitstream import BitStream, c_bit, c_bool, c_float, c_int, c_int64, c_ubyte, c_uint, c_uint64, c_ushort
from ..ldf import LDF, LDFDataType
from ..messages import WorldClientMsg
from ..world import World
from ..math.quaternion import Quaternion
@@ -287,7 +288,7 @@ class CharacterComponent(Component):
loot.append(lot)
return loot
async def transfer_to_world(self, world, respawn_point_name=None):
async def transfer_to_world(self, world, respawn_point_name=None, include_self=False):
if respawn_point_name is not None:
for obj in self.object._v_server.db.world_data[world[0]].objects.values():
if obj.lot == 4945 and (not hasattr(obj, "respawn_name") or respawn_point_name == "" or obj.respawn_name == respawn_point_name): # respawn point lot
@@ -299,9 +300,9 @@ class CharacterComponent(Component):
self.object.physics.rotation.update(self.object._v_server.db.world_data[world[0]].spawnpoint[1])
self.object.physics.attr_changed("position")
self.object.physics.attr_changed("rotation")
self.object._v_server.commit()
self.object._v_server.commit()
server_address = await self.object._v_server.address_for_world(world)
server_address = await self.object._v_server.address_for_world(world, include_self)
log.info("Sending redirect to world %s", server_address)
redirect = BitStream()
redirect.write_header(WorldClientMsg.Redirect)
@@ -310,6 +311,15 @@ class CharacterComponent(Component):
redirect.write(c_bool(False))
self.object._v_server.send(redirect, self.address)
async def transfer_to_last_non_instance(self, position=None, rotation=None):
if position is not None:
self.object.physics.position.update(position)
self.object.physics.attr_changed("position")
if rotation is not None:
self.object.physics.rotation.update(rotation)
self.object.physics.attr_changed("rotation")
await self.transfer_to_world(((self.world[0] // 100)*100, self.world[1], self.world[2]))
def add_mission(self, mission_id):
mission_progress = MissionProgress(mission_id, self.object._v_server.db.missions[mission_id])
self.missions.append(mission_progress)
@@ -439,7 +449,6 @@ class CharacterComponent(Component):
if task.type == TaskType.Flag and flag_id in task.target:
mission.increment_task(task, self.object)
def player_loaded(self, address, player_id:c_int64=None):
pass
@@ -452,7 +461,7 @@ class CharacterComponent(Component):
def set_jet_pack_mode(self, address, bypass_checks:c_bit=True, hover:c_bit=False, enable:c_bit=False, effect_id:c_uint=-1, air_speed:c_float=10, max_air_speed:c_float=15, vertical_velocity:c_float=1, warning_effect_id:c_uint=-1):
pass
def display_tooltip(self, address, do_or_die:c_bit=False, no_repeat:c_bit=False, no_revive:c_bit=False, is_property_tooltip:c_bit=False, show:c_bit=None, translate:c_bit=False, time:c_int=None, id:"wstr"=None, localize_params:"ldf"=None, str_image_name:"wstr"=None, str_text:"wstr"=None):
def display_tooltip(self, address, do_or_die:c_bit=False, no_repeat:c_bit=False, no_revive:c_bit=False, is_property_tooltip:c_bit=False, show:c_bit=None, translate:c_bit=False, time:c_int=None, id:"wstr"=None, localize_params:LDF=None, str_image_name:"wstr"=None, str_text:"wstr"=None):
pass
def use_non_equipment_item(self, address, item_to_use:c_int64=None):
@@ -562,18 +571,18 @@ class CharacterComponent(Component):
def get_models_on_property(self, address, models:(c_uint, c_int64, c_int64)=None):
pass
def match_request(self, address, activator:c_int64=None, player_choices:"ldf"=None, type:c_int=None, value:c_int=None):
def match_request(self, address, activator:c_int64=None, player_choices:LDF=None, type:c_int=None, value:c_int=None):
# todo: how does the server know which matchmaking activity the client wants?
self.object._v_server.send_game_message(self.match_response, response=0, address=address)
if type == MatchRequestType.Join:# and value == MatchRequestValue.Join:
update_data = {}
update_data["time"] = c_float, 60
update_data = LDF()
update_data.ldf_set("time", LDFDataType.FLOAT, 60.0)
self.object._v_server.send_game_message(self.match_update, data=update_data, type=MatchUpdateType.Time, address=address)
def match_response(self, address, response:c_int=None):
pass
def match_update(self, address, data:"ldf"=None, type:c_int=None):
def match_update(self, address, data:LDF=None, type:c_int=None):
pass
def used_information_plaque(self, address, plaque_object_id:c_int64=None):

View File

@@ -3,8 +3,12 @@ from .component import Component
from .mission import MissionState, TaskType
class CollectibleComponent(Component):
def __init__(self, obj, set_vars, comp_id):
super().__init__(obj, set_vars, comp_id)
self.collectible_id = set_vars.get("collectible_id", 0)
def serialize(self, out, is_creation):
out.write(c_ushort(0)) # todo
out.write(c_ushort(self.collectible_id))
def has_been_collected(self, address, player_id:c_int64=None):
player = self.object._v_server.game_objects[player_id]
@@ -13,4 +17,5 @@ class CollectibleComponent(Component):
if mission.state == MissionState.Active:
for task in mission.tasks:
if task.type == TaskType.Collect and task.target == self.object.lot:
mission.increment_task(task, player)
coll_id = self.collectible_id+(self.object._v_server.world_id[0]<<8)
mission.increment_task(task, player, increment=coll_id)

View File

@@ -1,5 +1,6 @@
import asyncio
from ..bitstream import c_bit, c_float, c_int, c_int64
from ..ldf import LDF
from .component import Component
class Comp108Component(Component):
@@ -25,7 +26,7 @@ class Comp108Component(Component):
assert multi_interact_id is None
self.driver_id = player.object_id
player.char.vehicle_id = self.object.object_id
self.object._v_server.send_game_message(player.char.display_tooltip, show=True, time=1000, id="", localize_params={}, str_image_name="", str_text="Use /dismount to dismount.", address=player.char.address)
self.object._v_server.send_game_message(player.char.display_tooltip, show=True, time=1000, id="", localize_params=LDF(), str_image_name="", str_text="Use /dismount to dismount.", address=player.char.address)
def request_die(self, address, unknown_bool:c_bit=None, death_type:"wstr"=None, direction_relative_angle_xz:c_float=None, direction_relative_angle_y:c_float=None, direction_relative_force:c_float=None, kill_type:c_int=0, killer_id:c_int64=None, loot_owner_id:c_int64=None):
#self.object.destructible.deal_damage(10000, self) # die permanently on crash

View File

@@ -36,6 +36,7 @@ from persistent import Persistent
from persistent.list import PersistentList
from ..bitstream import c_bit, c_int, c_int64, c_uint
from ..ldf import LDF, LDFDataType
from ..math.vector import Vector3
from .component import Component
from .mission import MissionState, TaskType
@@ -196,9 +197,9 @@ class InventoryComponent(Component):
if hasattr(self.object, "char"):
if notify_client:
extra_info = {}
extra_info = LDF()
if hasattr(stack, "module_lots"):
extra_info["assemblyPartLOTs"] = str, [(c_int, i) for i in stack.module_lots]
extra_info.ldf_set("assemblyPartLOTs", LDFDataType.STRING, [(LDFDataType.INT32, i) for i in stack.module_lots])
self.object._v_server.send_game_message(self.add_item_to_inventory_client_sync, bound=True, bound_on_equip=True, bound_on_pickup=True, loot_type_source=source_type, extra_info=extra_info, object_template=stack.lot, inv_type=inventory_type, amount=1, new_obj_id=stack.object_id, flying_loot_pos=Vector3.zero, show_flying_loot=show_flying_loot, slot_id=index, address=self.object.char.address)
@@ -212,7 +213,7 @@ class InventoryComponent(Component):
return stack
def add_item_to_inventory_client_sync(self, address, bound:c_bit=False, bound_on_equip:c_bit=False, bound_on_pickup:c_bit=False, loot_type_source:c_int=0, extra_info:"ldf"=None, object_template:c_int=None, subkey:c_int64=0, inv_type:c_int=0, amount:c_uint=1, item_total:c_uint=0, new_obj_id:c_int64=None, flying_loot_pos:Vector3=None, show_flying_loot:c_bit=True, slot_id:c_int=None):
def add_item_to_inventory_client_sync(self, address, bound:c_bit=False, bound_on_equip:c_bit=False, bound_on_pickup:c_bit=False, loot_type_source:c_int=0, extra_info:LDF=None, object_template:c_int=None, subkey:c_int64=0, inv_type:c_int=0, amount:c_uint=1, item_total:c_uint=0, new_obj_id:c_int64=None, flying_loot_pos:Vector3=None, show_flying_loot:c_bit=True, slot_id:c_int=None):
pass
def remove_item_from_inv(self, inventory_type, item=None, object_id=0, lot=0, amount=1):
@@ -220,9 +221,9 @@ class InventoryComponent(Component):
object_id = item.object_id
if hasattr(self.object, "char"):
self.object._v_server.send_game_message(self.remove_item_from_inventory, inventory_type=inventory_type, extra_info={}, force_deletion=True, object_id=object_id, object_template=lot, stack_count=amount, address=self.object.char.address)
self.object._v_server.send_game_message(self.remove_item_from_inventory, inventory_type=inventory_type, extra_info=LDF, force_deletion=True, object_id=object_id, object_template=lot, stack_count=amount, address=self.object.char.address)
def remove_item_from_inventory(self, address, confirmed:c_bit=True, delete_item:c_bit=True, out_success:c_bit=False, inventory_type:c_int=0, loot_type_source:c_int=0, extra_info:"ldf"=None, force_deletion:c_bit=False, loot_type_source_id:c_int64=0, object_id:c_int64=0, object_template:c_int=0, requesting_object_id:c_int64=0, stack_count:c_uint=1, stack_remaining:c_uint=0, subkey:c_int64=0, trade_id:c_int64=0):
def remove_item_from_inventory(self, address, confirmed:c_bit=True, delete_item:c_bit=True, out_success:c_bit=False, inventory_type:c_int=0, loot_type_source:c_int=0, extra_info:LDF=None, force_deletion:c_bit=False, loot_type_source_id:c_int64=0, object_id:c_int64=0, object_template:c_int=0, requesting_object_id:c_int64=0, stack_count:c_uint=1, stack_remaining:c_uint=0, subkey:c_int64=0, trade_id:c_int64=0):
if confirmed:
assert delete_item
assert not out_success

View File

@@ -57,6 +57,8 @@ class MissionTask(Persistent):
self.target = target
self.value = 0
self.target_value = target_value
if task_type == TaskType.Collect:
parameter = set() # used for collectibles
self.parameter = parameter
class MissionProgress(Persistent):
@@ -84,9 +86,16 @@ class MissionProgress(Persistent):
if not self.is_mission and not check_prereqs(self.id, player):
return
task.value = min(task.value+increment, task.target_value)
task_index = self.tasks.index(task)
player._v_server.send_game_message(player.char.notify_mission_task, self.id, task_mask=1<<(task_index+1), updates=[task.value], address=player.char.address)
if task.type == TaskType.Collect:
task.parameter.add(increment)
task.value = len(task.parameter)
update = increment
else:
task.value = min(task.value+increment, task.target_value)
update = task.value
player._v_server.send_game_message(player.char.notify_mission_task, self.id, task_mask=1<<(task_index+1), updates=[update], address=player.char.address)
if not self.is_mission:
for task in self.tasks:
if task.value < task.target_value:

View File

@@ -8,6 +8,7 @@ from .component import Component
class MovingPlatformComponent(Component):
def __init__(self, obj, set_vars, comp_id):
super().__init__(obj, set_vars, comp_id)
self.object.moving_platform = self
self._flags["moving_platform_unknown"] = "moving_platform_flag"
self._flags["target_position"] = "moving_platform_flag"
self.target_position = Vector3()

View File

@@ -123,6 +123,10 @@ class RebuildComponent(ScriptedActivityComponent):
# drop rewards
self.object.stats.drop_rewards(*self.completion_rewards, player)
# if this is a moving platform, set the waypoint
if hasattr(self, "moving_platform"):
self.moving_platform.update_waypoint()
# update missions that have completing this rebuild as requirement
for mission in player.char.missions:
if mission.state == MissionState.Active:

View File

@@ -1,3 +1,4 @@
from .. ldf import LDF
from ..bitstream import c_bit, c_int, c_int64
from .component import Component
@@ -9,11 +10,15 @@ class ScriptComponent(Component):
if "script_vars" in set_vars:
self.script_vars = set_vars["script_vars"]
self.script_network_vars = LDF()
def serialize(self, out, is_creation):
if is_creation:
out.write(c_bit(False))
out.write(c_bit(self.script_network_vars))
if self.script_network_vars:
out.write(self.script_network_vars.to_bitstream())
def script_network_var_update(self, address, script_vars:"ldf"=None):
def script_network_var_update(self, address, script_vars:LDF=None):
pass
def notify_client_object(self, address, name:"wstr"=None, param1:c_int=None, param2:c_int=None, param_obj:c_int64=None, param_str:"str"=None):

View File

@@ -21,6 +21,9 @@ class ScriptedActivityComponent(Component):
out.write(c_float(0))
self.activity_flag = False
def activity_start(self, address):
pass
def message_box_respond(self, address, button:c_int=None, identifier:"wstr"=None, user_data:"wstr"=None):
if identifier == "LobbyReady" and button == 1:
player = self.object._v_server.accounts[address].characters.selected()

View File

@@ -4,7 +4,7 @@ from collections import OrderedDict
from persistent import Persistent
from . import ldf
from .ldf import LDF
from .bitstream import BitStream, c_bit, c_float, c_int, c_int64, c_ubyte, c_uint, c_ushort
from .components.ai import BaseCombatAIComponent
from .components.bouncer import BouncerComponent
@@ -89,7 +89,7 @@ class GameObject:
self.lot = lot
self.object_id = object_id
self.name = ""
self.config = set_vars.get("config", {})
self.config = set_vars.get("config", LDF())
self.spawner_object = None
self.spawner_waypoint_index = 0
self.scale = set_vars.get("scale", 1)
@@ -170,7 +170,7 @@ class GameObject:
out.write(bytes(4)) # time since created on server?
out.write(c_bit(self.config))
if self.config:
out.write(ldf.to_ldf(self.config, ldf_type="binary"))
out.write(self.config.to_bitstream())
out.write(c_bit(hasattr(self, "trigger")))
out.write(c_bit(self.spawner_object is not None))
if self.spawner_object is not None:

View File

@@ -1,55 +1,145 @@
import enum
from .bitstream import BitStream, c_bool, c_float, c_int, c_int64, c_ubyte, c_uint
DATA_TYPE = {}
DATA_TYPE[str] = 0
DATA_TYPE[c_int] = 1
DATA_TYPE[c_float] = 3
DATA_TYPE[c_bool] = 7
DATA_TYPE[c_int64] = 9 # or 8?
DATA_TYPE[bytes] = 13 # let's just use bytes for 13
DATA_TYPE[0] = str
DATA_TYPE[1] = int # signed int
DATA_TYPE[3] = float
DATA_TYPE[5] = int # unsigned int
DATA_TYPE[7] = lambda x: bool(int(x))
DATA_TYPE[9] = int
DATA_TYPE[13] = lambda x: x.encode() # see above
class LDFDataType(enum.Enum):
STRING = 0
INT32 = 1
FLOAT = 3
UINT32 = 5
BOOLEAN = 7
INT64_8 = 8
INT64_9 = 9
BYTES = 13
def _value_to_ldf_text(type_, value):
if isinstance(value, (list, tuple)):
str_value = "+".join(_value_to_ldf_text(typ, val) for typ, val in value)
value = ""
else:
str_value = str(value)
return "%i:%s" % (DATA_TYPE[type_], str_value)
class LDF(dict):
def __init__(self, source=None):
super().__init__()
if source is not None:
if isinstance(source, str):
self.from_str(source)
elif isinstance(source, BitStream):
self.from_bitstream(source)
def to_ldf(obj, ldf_type):
if ldf_type == "text":
if isinstance(obj, dict):
output = "\n".join("%s=%s" % (key, _value_to_ldf_text(*value)) for key, value in obj.items())
def __getitem__(self, key):
return self.ldf_get(key)[1]
def __setitem__(self, key, value):
return self.ldf_set(key, self.ldf_get(key)[0], value)
def ldf_get(self, key):
return super().__getitem__(key)
def ldf_set(self, key, data_type, value):
if not isinstance(data_type, LDFDataType):
raise TypeError
if data_type == LDFDataType.STRING:
if not isinstance(value, (str, list, tuple)):
raise TypeError
elif data_type in (LDFDataType.INT32, LDFDataType.UINT32, LDFDataType.INT64_8, LDFDataType.INT64_9):
if not isinstance(value, int):
raise TypeError
if data_type == LDFDataType.UINT32:
if value < 0:
raise ValueError
elif data_type == LDFDataType.FLOAT:
if not isinstance(value, float):
raise TypeError
elif data_type == LDFDataType.BOOLEAN:
if not isinstance(value, bool):
raise TypeError
elif data_type == LDFDataType.BYTES:
if not isinstance(value, bytes):
raise TypeError
return super().__setitem__(key, (data_type, value))
def from_str(self, source):
items = source.split("\x0a")
for item in items:
key, type_value = item.split("=", maxsplit=1)
data_type, value = self.from_str_type(type_value)
self.ldf_set(key, data_type, value)
def to_str(self):
return "\n".join("%s=%s" % (key, self.to_str_type(*value)) for key, value in super().items())
@staticmethod
def from_str_type(type_value):
data_type_id, value = type_value.split(":", maxsplit=1)
data_type = LDFDataType(int(data_type_id))
if data_type == LDFDataType.STRING:
value = value
elif data_type in (LDFDataType.INT32, LDFDataType.UINT32, LDFDataType.INT64_8, LDFDataType.INT64_9):
value = int(value)
elif data_type == LDFDataType.FLOAT:
value = float(value)
elif data_type == LDFDataType.BOOLEAN:
value = bool(int(value))
elif data_type == LDFDataType.BYTES:
value = value.encode()
return data_type, value
@staticmethod
def to_str_type(data_type, value):
if isinstance(value, (list, tuple)):
str_value = "+".join(LDF.to_str_type(data_type, val) for data_type, val in value)
else:
output = _value_to_ldf_text(*obj)
if data_type == LDFDataType.STRING:
str_value = value
elif data_type in (LDFDataType.INT32, LDFDataType.FLOAT, LDFDataType.UINT32, LDFDataType.INT64_8, LDFDataType.INT64_9):
str_value = str(value)
elif data_type == LDFDataType.BOOLEAN:
str_value = str(int(value))
elif data_type == LDFDataType.BYTES:
str_value = value.decode()
return "%i:%s" % (data_type.value, str_value)
elif ldf_type == "binary":
if not isinstance(obj, dict):
raise NotImplementedError
def from_bitstream(self, source):
for _ in range(source.read(c_uint)):
encoded_key = source.read(bytes, length=source.read(c_ubyte))
key = encoded_key.decode("utf-16-le")
data_type = LDFDataType(source.read(c_ubyte))
if data_type == LDFDataType.STRING:
value = source.read(str, length_type=c_uint)
elif data_type == LDFDataType.INT32:
value = source.read(c_int)
elif data_type == LDFDataType.FLOAT:
value = source.read(c_float)
elif data_type == LDFDataType.UINT32:
value = source.read(c_uint)
elif data_type == LDFDataType.BOOLEAN:
value = source.read(c_bool)
elif data_type in (LDFDataType.INT64_8, LDFDataType.INT64_9):
value = source.read(c_int64)
elif data_type == LDFDataType.BYTES:
value = source.read(bytes, length=source.read(c_uint))
self.ldf_set(key, data_type, value)
def to_bitstream(self):
uncompressed = BitStream()
uncompressed.write(c_uint(len(obj)))
for key, value in obj.items():
value_type, value_value = value # meh this isn't exactly descriptive
uncompressed.write(c_uint(len(self)))
for key, value in super().items():
data_type, value = value
# can't use normal variable string writing because this writes the length of the encoded instead of the original (include option for this behavior?)
encoded_key = key.encode("utf-16-le")
uncompressed.write(c_ubyte(len(encoded_key)))
uncompressed.write(encoded_key)
uncompressed.write(c_ubyte(DATA_TYPE[value_type]))
if value_type == str:
uncompressed.write(value_value, length_type=c_uint)
else:
if value_type == bytes:
uncompressed.write(c_uint(len(value_value)))
uncompressed.write(value_type(value_value))
uncompressed.write(c_ubyte(data_type.value))
if data_type == LDFDataType.STRING:
uncompressed.write(value, length_type=c_uint)
elif data_type == LDFDataType.INT32:
uncompressed.write(c_int(value))
elif data_type == LDFDataType.FLOAT:
uncompressed.write(c_float(value))
elif data_type == LDFDataType.UINT32:
uncompressed.write(c_uint(value))
elif data_type == LDFDataType.BOOLEAN:
uncompressed.write(c_bool(value))
elif data_type in (LDFDataType.INT64_8, LDFDataType.INT64_9):
uncompressed.write(c_int64(value))
elif data_type == LDFDataType.BYTES:
uncompressed.write(c_uint(len(value)))
uncompressed.write(value)
output = BitStream()
is_compressed = False
@@ -59,43 +149,4 @@ def to_ldf(obj, ldf_type):
raise NotImplementedError
output.write(c_bool(is_compressed))
output.write(uncompressed)
return output
def from_ldf_type_value(type_value): # in its own function for use in luz path spawners where the structure is different
data_type_id, value = type_value.split(":", maxsplit=1)
data_type_id = int(data_type_id)
return DATA_TYPE[data_type_id](value)
def from_ldf(ldf):
ldf_dict = {}
if isinstance(ldf, str):
items = ldf.split("\x0a")
for item in items:
key, type_value = item.split("=", maxsplit=1)
value = from_ldf_type_value(type_value)
ldf_dict[key] = value
elif isinstance(ldf, BitStream):
for _ in range(ldf.read(c_uint)):
encoded_key = ldf.read(bytes, length=ldf.read(c_ubyte))
key = encoded_key.decode("utf-16-le")
data_type_id = ldf.read(c_ubyte)
if data_type_id == 0:
value = ldf.read(str, length_type=c_uint)
elif data_type_id == 1:
value = ldf.read(c_int)
elif data_type_id == 5:
value = ldf.read(c_uint)
elif data_type_id == 7:
value = ldf.read(c_bool)
elif data_type_id in (8, 9):
value = ldf.read(c_int64)
elif data_type_id == 13:
value = ldf.read(bytes, length=ldf.read(c_uint))
else:
raise NotImplementedError(key, data_type_id)
ldf_dict[key] = value
else:
raise NotImplementedError
return ldf_dict
return output

View File

@@ -4,11 +4,9 @@ from pyraknet.messages import Message
Message.LUPacket = 0x53
# for the enums below, __int__() returns the corresponding header
class GeneralMsg(IntEnum):
@staticmethod
def __int__():
def header():
return 0x00
Handshake = 0x00
@@ -17,14 +15,14 @@ class GeneralMsg(IntEnum):
class AuthServerMsg(IntEnum):
@staticmethod
def __int__():
def header():
return 0x01
LoginRequest = 0x00
class SocialMsg(IntEnum):
@staticmethod
def __int__():
def header():
return 0x02
GeneralChatMessage = 0x01
@@ -40,7 +38,7 @@ class SocialMsg(IntEnum):
class WorldServerMsg(IntEnum):
@staticmethod
def __int__():
def header():
return 0x04
SessionInfo = 0x01
@@ -58,7 +56,7 @@ class WorldServerMsg(IntEnum):
class WorldClientMsg(IntEnum):
@staticmethod
def __int__():
def header():
return 0x05
LoginResponse = 0x00
@@ -90,7 +88,7 @@ msg_enum[0x05] = WorldClientMsg
class GameMessage(Enum):
Teleport = 19
DropClientLoot = 30
Die = 37
Die = 37
RequestDie = 38
PlayEmote = 41
PlayAnimation = 43
@@ -126,6 +124,7 @@ class GameMessage(Enum):
BuyFromVendor = 373
SellToVendor = 374
SetInventorySize = 389
ActivityStart = 407
VendorStatusUpdate = 417
ClientItemConsumed = 428
SetFlag = 471

View File

@@ -218,7 +218,7 @@ class CharHandling(ServerModule):
selected_char.char.world = 1000, 0, 0
asyncio.ensure_future(selected_char.char.transfer_to_world(selected_char.char.world, respawn_point_name=""))
else:
asyncio.ensure_future(selected_char.char.transfer_to_world(selected_char.char.world))
asyncio.ensure_future(selected_char.char.transfer_to_world(selected_char.char.world, include_self=True))
def shirt_to_lot(self, color, style):
# The LOTs for the shirts are for some reason in two batches of IDs

View File

@@ -4,10 +4,11 @@ For world server packet handling that is general enough not to be grouped in a s
import logging
import xml.etree.ElementTree as ET
from .. import ldf
from ..ldf import LDF, LDFDataType
from ..bitstream import BitStream, c_bit, c_float, c_int, c_int64, c_uint, c_ushort
from ..messages import WorldClientMsg, WorldServerMsg
from ..world import World
from ..components.mission import TaskType
from .module import ServerModule
log = logging.getLogger(__name__)
@@ -112,8 +113,8 @@ class GeneralHandling(ServerModule):
for model in models:
i = ET.SubElement(in_5, "i", l=str(model.lot), c=str(model.amount), id=str(model.object_id), s=str(player.inventory.models.index(model)))
if hasattr(model, "module_lots"):
module_lots = [(c_int, i) for i in model.module_lots]
module_lots = ldf.to_ldf((str, module_lots), ldf_type="text")
module_lots = [(LDFDataType.INT32, i) for i in model.module_lots]
module_lots = LDF().to_str_type(LDFDataType.STRING, module_lots)
ET.SubElement(i, "x", ma=module_lots)
flag = ET.SubElement(root, "flag")
@@ -138,6 +139,9 @@ class GeneralHandling(ServerModule):
ET.SubElement(m, "sv")
else:
ET.SubElement(m, "sv", v=str(task.value))
if task.type == TaskType.Collect:
for collectible_id in task.parameter:
ET.SubElement(m, "sv", v=str(collectible_id))
elif mission.state == 8:
ET.SubElement(done, "m", id=str(mission.id))
@@ -145,13 +149,13 @@ class GeneralHandling(ServerModule):
xml = xml.dom.minidom.parseString((ET.tostring(root, encoding="unicode")))
#log.debug(xml.toprettyxml(indent=" "))
chd_ldf = {}
chd_ldf["objid"] = c_int64, player.object_id
chd_ldf["template"] = c_int, 1
chd_ldf["name"] = str, player.name
chd_ldf["xmlData"] = bytes, ET.tostring(root)
chd_ldf = LDF()
chd_ldf.ldf_set("objid", LDFDataType.INT64_9, player.object_id)
chd_ldf.ldf_set("template", LDFDataType.INT32, 1)
chd_ldf.ldf_set("name", LDFDataType.STRING, player.name)
chd_ldf.ldf_set("xmlData", LDFDataType.BYTES, ET.tostring(root))
encoded_ldf = ldf.to_ldf(chd_ldf, ldf_type="binary")
encoded_ldf = chd_ldf.to_bitstream()
chardata.write(encoded_ldf)
self.server.send(chardata, address)

View File

@@ -1,6 +1,6 @@
import logging
from ..bitstream import c_float, c_int
from ..ldf import LDF, LDFDataType
from ..math.vector import Vector3
from .module import ServerModule
@@ -25,7 +25,7 @@ class AABB: # axis aligned bounding box
def __init__(self, obj):
if obj.lot in (10042, 14510, 16506):
if obj.primitive_model_type != 1:
log.warn("Primitive model type not 1 %s", obj)
log.warning("Primitive model type not 1 %s", obj)
rel_min = MODEL_DIMENSIONS[obj.lot][0] * obj.primitive_model_scale
rel_max = MODEL_DIMENSIONS[obj.lot][1] * obj.primitive_model_scale
else:
@@ -113,11 +113,11 @@ class PhysicsHandling(ServerModule):
self.debug_markers.append(self.server.spawn_object(obj.lot, set_vars=set_vars))
set_vars = {}
set_vars["position"] = Vector3((aabb.min.x+aabb.max.x)/2, aabb.min.y, (aabb.min.z+aabb.max.z)/2)
config = {}
config["primitiveModelType"] = c_int, 1
config["primitiveModelValueX"] = c_float, aabb.max.x-aabb.min.x
config["primitiveModelValueY"] = c_float, aabb.max.y-aabb.min.y
config["primitiveModelValueZ"] = c_float, aabb.max.z-aabb.min.z
config = LDF()
config.ldf_set("primitiveModelType", LDFDataType.INT32, 1)
config.ldf_set("primitiveModelValueX", LDFDataType.FLOAT, aabb.max.x-aabb.min.x)
config.ldf_set("primitiveModelValueY", LDFDataType.FLOAT, aabb.max.y-aabb.min.y)
config.ldf_set("primitiveModelValueZ", LDFDataType.FLOAT, aabb.max.z-aabb.min.z)
set_vars["config"] = config
self.debug_markers.append(self.server.spawn_object(14510, set_vars=set_vars))

View File

@@ -87,7 +87,6 @@ class SocialHandling(ServerModule):
response.write(c_bool(False)) # is FTP
self.server.send(response, address)
def on_add_friend_response(self, response, address):
assert response.read(c_int64) == 0
request_declined = response.read(c_bool)

View File

@@ -1,12 +1,44 @@
import asyncio
import luserver.components.script as script
from luserver.ldf import LDFDataType
from luserver.bitstream import c_bool, c_int
from luserver.math.vector import Vector3
from luserver.math.quaternion import Quaternion
class ScriptComponent(script.ScriptComponent):
def on_startup(self):
self.script_network_vars.ldf_set("NumberOfPlayers", LDFDataType.INT32, 1)
def set_player_spawn_points(self):
for index, player_id in enumerate(self.object.scripted_activity.players):
player = self.object._v_server.get_object(player_id)
spawn = self.object._v_server.get_objects_in_group("P%i_Spawn" % (index+1))[0]
self.object._v_server.send_game_message(player.char.teleport, ignore_y=False, pos=spawn.physics.position, set_rotation=True, x=spawn.physics.rotation.x, y=spawn.physics.rotation.y, z=spawn.physics.rotation.z, w=spawn.physics.rotation.w, address=player.char.address)
def start(self):
self.object._v_server.send_game_message(self.object.scripted_activity.activity_start, broadcast=True)
self.script_network_vars.clear()
self.script_network_vars.ldf_set("Clear_Scoreboard", LDFDataType.BOOLEAN, True)
self.script_network_vars.ldf_set("Start_Wave_Message", LDFDataType.STRING, "Start!")
self.script_network_vars.ldf_set("wavesStarted", LDFDataType.BOOLEAN, True)
self.object._v_server.send_game_message(self.script_network_var_update, self.script_network_vars, broadcast=True)
def player_ready(self, address):
player = self.object._v_server.accounts[address].characters.selected()
script_vars = {}
script_vars["NumberOfPlayers"] = c_int, 1
script_vars["Define_Player_To_UI"] = bytes, player.object_id
script_vars["Show_ScoreBoard"] = c_bool, int(True)
script_vars["Update_ScoreBoard_Players.1"] = bytes, player.object_id
self.object._v_server.send_game_message(self.script_network_var_update, script_vars, broadcast=True)
self.object.scripted_activity.players.append(player.object_id)
self.script_network_vars.ldf_set("Define_Player_To_UI", LDFDataType.BYTES, str(player.object_id).encode())
self.script_network_vars.ldf_set("Show_ScoreBoard", LDFDataType.BOOLEAN, True)
self.script_network_vars.ldf_set("Update_ScoreBoard_Players.1", LDFDataType.BYTES, str(player.object_id).encode())
self.object._v_server.send_game_message(self.script_network_var_update, self.script_network_vars, broadcast=True)
self.set_player_spawn_points()
def message_box_respond(self, address, button:c_int=None, identifier:"wstr"=None, user_data:"wstr"=None):
if identifier == "RePlay":
self.start()
elif identifier == "Exit_Question" and button == 1:
player = self.object._v_server.accounts[address].characters.selected()
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

@@ -11,4 +11,4 @@ class ScriptComponent(script.ScriptComponent):
def message_box_respond(self, address, button:c_int=None, identifier:"wstr"=None, user_data:"wstr"=None):
if identifier == "instance_exit" and button == 1:
player = self.object._v_server.accounts[address].characters.selected()
asyncio.ensure_future(player.char.transfer_to_world(((player.char.world[0] // 100)*100, player.char.world[1], player.char.world[2])))
asyncio.ensure_future(player.char.transfer_to_last_non_instance())

View File

@@ -9,7 +9,7 @@ from .modules.mail import MailID
class Server(pyraknet.server.Server):
NETWORK_VERSION = 171022
SERVER_PASSWORD = b"3.25 ND1"
EXPECTED_PEER_TYPE = WorldClientMsg.__int__()
EXPECTED_PEER_TYPE = WorldClientMsg.header()
def __init__(self, address, max_connections, db_conn):
super().__init__(address, max_connections, self.SERVER_PASSWORD)
@@ -23,12 +23,12 @@ class Server(pyraknet.server.Server):
def packetname(self, data):
if data[0] == Message.LUPacket:
if data[1] == WorldServerMsg.__int__() and data[3] == WorldServerMsg.Routing:
if data[1] == WorldServerMsg.header() and data[3] == WorldServerMsg.Routing:
data = b"\x53"+data[12:]
if (data[1], data[3]) == (WorldServerMsg.__int__(), WorldServerMsg.GameMessage) or (data[1], data[3]) == (WorldClientMsg.__int__(), WorldClientMsg.GameMessage):
if (data[1], data[3]) == (WorldServerMsg.header(), WorldServerMsg.GameMessage) or (data[1], data[3]) == (WorldClientMsg.header(), WorldClientMsg.GameMessage):
message_name = GameMessage(c_ushort.unpack(data[16:18])[0]).name
return "GameMessage/" + message_name
if (data[1], data[3]) == (WorldServerMsg.__int__(), WorldServerMsg.Mail) or (data[1], data[3]) == (WorldClientMsg.__int__(), WorldClientMsg.Mail):
if (data[1], data[3]) == (WorldServerMsg.header(), WorldServerMsg.Mail) or (data[1], data[3]) == (WorldClientMsg.header(), WorldClientMsg.Mail):
packetname = MailID(c_uint.unpack(data[8:12])[0]).name
return "Mail/" + packetname
return msg_enum[data[1]](data[3]).name
@@ -36,30 +36,30 @@ class Server(pyraknet.server.Server):
def unknown_packetname(self, data):
if data[0] == Message.LUPacket:
if data[1] == WorldServerMsg.__int__() and data[3] == WorldServerMsg.Routing:
if data[1] == WorldServerMsg.header() and data[3] == WorldServerMsg.Routing:
data = b"\x53"+data[12:]
if (data[1], data[3]) == (WorldServerMsg.__int__(), WorldServerMsg.GameMessage) or (data[1], data[3]) == (WorldClientMsg.__int__(), WorldClientMsg.GameMessage):
if (data[1], data[3]) == (WorldServerMsg.header(), WorldServerMsg.GameMessage) or (data[1], data[3]) == (WorldClientMsg.header(), WorldClientMsg.GameMessage):
return "GameMessage/%i" % c_ushort.unpack(data[16:18])[0]
return msg_enum[data[1]].__name__ + "/%.2x" % data[3]
return super().unknown_packetname(data)
def packet_id(self, data):
if data[0] == Message.LUPacket:
if data[1] == WorldServerMsg.__int__() and data[3] == WorldServerMsg.Routing:
if data[1] == WorldServerMsg.header() and data[3] == WorldServerMsg.Routing:
return data[12], data[14]
return data[1], data[3]
return super().packet_id(data)
def handler_data(self, data):
if data[0] == Message.LUPacket:
if data[1] == WorldServerMsg.__int__() and data[3] == WorldServerMsg.Routing:
if data[1] == WorldServerMsg.header() and data[3] == WorldServerMsg.Routing:
return data[19:]
return data[8:]
return super().handler_data(data)
def register_handler(self, packet_id, callback, origin=None):
if isinstance(packet_id, (GeneralMsg, AuthServerMsg, SocialMsg, WorldServerMsg, WorldClientMsg)):
header = type(packet_id).__int__()
header = type(packet_id).header()
subheader = packet_id
packet_id = header, subheader
return super().register_handler(packet_id, callback, origin)
@@ -101,11 +101,13 @@ class Server(pyraknet.server.Server):
def conn_sync(self):
self.conn.sync()
async def address_for_world(self, world_id):
async def address_for_world(self, world_id, include_self=False):
while True:
self.conn_sync()
for server_address, server_world in self.db.servers.items():
if server_world == world_id and (not hasattr(self, "external_address") or server_address != self.external_address):
if server_world == world_id:
if not include_self and hasattr(self, "external_address") and server_address == self.external_address:
continue
return server_address
# no server found, spawn a new one
# todo: os.system probably isn't the best way to do this

View File

@@ -50,10 +50,10 @@ import time
import pyraknet.replicamanager
from . import amf3
from . import ldf
from . import server
from .bitstream import BitStream, c_bit, c_float, c_int64, c_uint, c_ushort
from .game_object import GameObject
from .ldf import LDF
from .messages import GameMessage, WorldClientMsg, WorldServerMsg
from .math.quaternion import Quaternion
from .components.property import PropertyData, PropertySelectQueryProperty
@@ -72,7 +72,7 @@ BITS_LOCAL = 1 << 46
BITS_SPAWNED = 1 << 58 | BITS_LOCAL
class WorldServer(server.Server, pyraknet.replicamanager.ReplicaManager):
PEER_TYPE = WorldServerMsg.__int__()
PEER_TYPE = WorldServerMsg.header()
def __init__(self, address, external_host, world_id, max_connections, db_conn):
server.Server.__init__(self, address, max_connections, db_conn)
@@ -288,7 +288,7 @@ class WorldServer(server.Server, pyraknet.replicamanager.ReplicaManager):
return self.game_objects[object_id]
elif self.world_id[0] != 0 and object_id in self.world_data.objects:
return self.world_data.objects[object_id]
log.warn("Object %i not found", object_id)
log.warning("Object %i not found", object_id)
def get_objects_in_group(self, group):
matches = []
@@ -432,13 +432,13 @@ class WorldServer(server.Server, pyraknet.replicamanager.ReplicaManager):
elif type == BitStream:
out.write(c_uint(len(value)))
out.write(bytes(value))
elif type == "amf":
amf3.write(value, out)
elif type == "ldf":
ldf_text = ldf.to_ldf(value, ldf_type="text")
elif type == LDF:
ldf_text = value.to_str()
out.write(ldf_text, length_type=c_uint)
if ldf_text:
out.write(bytes(2)) # for some reason has a null terminator
elif type == "amf":
amf3.write(value, out)
elif type == "str":
out.write(value, char_size=1, length_type=c_uint)
elif type == "wstr":
@@ -473,14 +473,14 @@ class WorldServer(server.Server, pyraknet.replicamanager.ReplicaManager):
if type == BitStream:
length = message.read(c_uint)
return BitStream(message.read(bytes, length=length))
if type == "amf":
return amf3.read(message)
if type == "ldf":
if type == LDF:
value = message.read(str, length_type=c_uint)
if value:
assert message.read(c_ushort) == 0 # for some reason has a null terminator
# todo: convert to dict
# todo: convert to LDF
return value
if type == "amf":
return amf3.read(message)
if type == "str":
return message.read(str, char_size=1, length_type=c_uint)
if type == "wstr":

View File

@@ -282,8 +282,8 @@ if GENERATE_COMPS:
if currency_index is not None:
_, minvalue, maxvalue = currency_table[currency_index][0]
else:
minvalue = None
maxvalue = None
minvalue = None
maxvalue = None
activity_rewards[object_template] = loot, minvalue, maxvalue

View File

@@ -4,12 +4,12 @@ from types import SimpleNamespace
import BTrees
import luserver.ldf as ldf
from luserver.bitstream import BitStream, c_float, c_int, c_int64, c_ubyte, c_uint, c_uint64, c_ushort
from luserver.game_object import GameObject
from luserver.ldf import LDF
from luserver.world import BITS_LOCAL, World
from luserver.math.quaternion import Quaternion
from luserver.math.vector import Vector3
from luserver.world import BITS_LOCAL, World
import scripts
LUZ_PATHS = {}
@@ -133,7 +133,7 @@ def lvl_parse_chunk_type_2001(lvl, conn, world_data, triggers):
object_id |= BITS_LOCAL
if lot in WHITELISTED_SERVERSIDE_LOTS:
config = ldf.from_ldf(config_data)
config = LDF(config_data)
spawned_vars = {}
spawned_vars["scale"] = scale
@@ -187,6 +187,8 @@ def lvl_parse_chunk_type_2001(lvl, conn, world_data, triggers):
spawned_vars["activity_id"] = config["activityID"]
if "attached_path" in config:
spawned_vars["attached_path"] = config["attached_path"]
if "collectible_id" in config:
spawned_vars["collectible_id"] = config["collectible_id"]
if "rebuild_activators" in config:
spawned_vars["rebuild_activator_position"] = Vector3(*(float(i) for i in config["rebuild_activators"].split("\x1f")))
if "rail_path" in config:
@@ -331,7 +333,7 @@ def load_world_data(conn, maps_path):
config_name = luz.read(str, length_type=c_ubyte)
config_type_and_value = luz.read(str, length_type=c_ubyte)
try:
config[config_name] = ldf.from_ldf_type_value(config_type_and_value)
config[config_name] = LDF.from_str_type(config_type_and_value)
except ValueError:
pass

View File

@@ -3,6 +3,7 @@ Modification of pyraknet.__main__ manual server, with number sorting for packet
Mostly intended for manually replaying captures.
"""
import asyncio
import logging
import os
import re
import threading
@@ -11,6 +12,8 @@ import traceback
import pyraknet.server
logging.basicConfig(format="%(levelname).1s:%(message)s", level=logging.DEBUG)
def atoi(text):
return int(text) if text.isdigit() else text