From c67c5a36212ad234427b40cfe653b90114e9d2d8 Mon Sep 17 00:00:00 2001 From: lcdr Date: Mon, 12 Feb 2018 13:58:32 +0100 Subject: [PATCH] v2018.02.12 - refactored handler system --- luserver/commonserver.py | 18 +- luserver/components/ai.py | 10 +- luserver/components/behaviors.py | 111 +++++++------ luserver/components/bouncer.py | 4 + luserver/components/char/__init__.py | 109 ++++++------ luserver/components/char/activity.py | 7 +- luserver/components/char/camera.py | 11 +- luserver/components/char/mission.py | 17 +- luserver/components/char/pet.py | 11 +- luserver/components/char/property.py | 19 +-- luserver/components/char/subcomponent.py | 8 + luserver/components/char/trade.py | 44 ++--- luserver/components/char/ui.py | 3 +- luserver/components/collectible.py | 4 +- luserver/components/comp108.py | 12 +- luserver/components/component.py | 15 +- luserver/components/destructible.py | 14 +- luserver/components/exhibit.py | 6 +- luserver/components/inventory.py | 35 ++-- luserver/components/launchpad.py | 6 +- luserver/components/minigame.py | 4 +- luserver/components/mission.py | 22 +-- luserver/components/modular_build.py | 16 +- luserver/components/moving_platform.py | 6 +- luserver/components/pet.py | 8 +- luserver/components/physics.py | 36 +--- luserver/components/property.py | 10 +- luserver/components/racing_control.py | 4 +- luserver/components/rebuild.py | 22 ++- luserver/components/scripted_activity.py | 8 +- luserver/components/skill.py | 16 +- luserver/components/spawner.py | 4 +- luserver/components/stats.py | 9 +- luserver/components/switch.py | 6 +- luserver/components/trigger.py | 2 +- luserver/components/vendor.py | 8 +- luserver/game_object.py | 156 ++++++++++-------- luserver/interfaces/plugin.py | 18 +- luserver/messages.py | 2 - luserver/modules/char.py | 16 +- luserver/modules/chat.py | 2 +- luserver/modules/general.py | 14 +- luserver/scripts/avant_gardens/beacon.py | 7 +- .../scripts/avant_gardens/caged_bricks.py | 6 +- .../scripts/avant_gardens/dusty_holster.py | 4 +- .../avant_gardens/epsilon_starcracker.py | 2 +- luserver/scripts/avant_gardens/fan.py | 6 +- luserver/scripts/avant_gardens/laser.py | 7 +- .../scripts/avant_gardens/melodie_foxtrot.py | 2 +- .../property/small/dark_spiderling.py | 5 +- .../property/small/spider_queen.py | 5 +- .../property/small/vance_bulwark.py | 6 +- .../property/small/world_control.py | 36 ++-- .../avant_gardens/quickbuild_elevator.py | 2 +- .../scripts/avant_gardens/rocco_sirocco.py | 6 +- .../scripts/avant_gardens/rusty_steele.py | 2 +- .../avant_gardens/survival/buff_station.py | 2 +- .../avant_gardens/survival/world_control.py | 27 +-- luserver/scripts/avant_gardens/turret.py | 2 +- luserver/scripts/avant_gardens/wisp_lee.py | 4 +- .../scripts/crux_prime/aura_blossom_flower.py | 2 +- luserver/scripts/crux_prime/scroll_shrine.py | 2 +- luserver/scripts/crux_prime/skill_volume.py | 2 +- .../forbidden_valley/bounce_over_wall.py | 2 +- .../forbidden_valley/brickmaster_clang.py | 4 +- luserver/scripts/forbidden_valley/candle.py | 2 +- luserver/scripts/general/flower.py | 2 +- luserver/scripts/general/mailbox.py | 2 +- luserver/scripts/general/notify_visibility.py | 8 +- luserver/scripts/general/poi_mission.py | 2 +- .../scripts/general/shark_death_trigger.py | 4 +- .../scripts/general/teleport_to_ns_or_nt.py | 4 +- .../scripts/general/touch_complete_mission.py | 2 +- .../general/transfer_to_last_non_instance.py | 4 +- .../scripts/gnarled_forest/banana_tree.py | 4 +- luserver/scripts/gnarled_forest/bomb_crate.py | 2 +- luserver/scripts/gnarled_forest/campfire.py | 2 +- luserver/scripts/gnarled_forest/jailkeep.py | 4 +- luserver/scripts/gnarled_forest/torch.py | 2 +- luserver/scripts/items/maelstrom_vacuum.py | 2 +- .../scripts/nexus_tower/paradox_teleporter.py | 2 +- luserver/scripts/nexus_tower/vault.py | 2 +- .../nimbus_station/car_modular_build.py | 6 +- .../nimbus_station/concert_instrument.py | 12 +- .../nimbus_station/concert_quickbuild.py | 4 +- .../nimbus_station/imagination_statue.py | 2 +- .../scripts/nimbus_station/johnny_thunder.py | 10 +- .../scripts/nimbus_station/lego_club_door.py | 4 +- .../scripts/nimbus_station/lup_teleport.py | 4 +- luserver/scripts/nimbus_station/nexus_jay.py | 4 +- .../nimbus_station/racetrack/world_control.py | 4 +- .../nimbus_station/rocket_modular_build.py | 6 +- .../scripts/nimbus_station/token_console.py | 4 +- luserver/scripts/ninjago/ninja.py | 2 +- luserver/scripts/pet_cove/coalessa.py | 2 +- luserver/scripts/property/teleport.py | 5 +- luserver/scripts/venture_explorer/bob.py | 9 +- .../venture_explorer/imagination_smashable.py | 12 +- .../scripts/venture_explorer/ship_shake.py | 6 +- luserver/world.py | 35 ++-- runtime/db/luz_importer.py | 7 +- runtime/db/lvl_importer.py | 4 +- runtime/plugins/commands/camera.py | 4 +- runtime/plugins/commands/misc.py | 11 +- runtime/plugins/commands/mission.py | 22 +-- runtime/plugins/commands/play.py | 2 +- runtime/plugins/commands/server.py | 21 ++- runtime/plugins/commands/spawn.py | 5 +- 108 files changed, 642 insertions(+), 611 deletions(-) create mode 100644 luserver/components/char/subcomponent.py diff --git a/luserver/commonserver.py b/luserver/commonserver.py index e0a7979..6fbfc9b 100644 --- a/luserver/commonserver.py +++ b/luserver/commonserver.py @@ -21,7 +21,7 @@ import logging import os import subprocess from abc import ABC, abstractmethod -from typing import Callable, Dict, List, Optional, Sequence, Tuple +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING from ZODB.Connection import Connection @@ -30,13 +30,22 @@ from pyraknet.messages import Address from pyraknet.server import Event from .auth import Account from .bitstream import WriteStream +if TYPE_CHECKING: + from .game_object import ObjectID, GameObject from .messages import GeneralMsg, LUMessage, WorldClientMsg, WorldServerMsg from .server import Server as _Server from .math.vector import Vector3 from .math.quaternion import Quaternion +LootTableEntry = Sequence[Tuple[int, bool, int]] MissionData = Tuple[Tuple[int, int, bool, Sequence[Sequence[Sequence[int]]], Optional[int], int, int, int], Tuple[Tuple[Tuple[int, ...], ...], ...], Sequence[Tuple[int, int, int, str]], bool, bool, Sequence[int]] +class WorldData: + def __init__(self, objects: "Dict[ObjectID, GameObject]", paths: Dict[str, Tuple[int, Sequence]], spawnpoint: Tuple[Vector3, Quaternion]): + self.objects = objects + self.paths = paths + self.spawnpoint = spawnpoint + class ServerDB: accounts: Dict[str, Account] activities: Dict[int, Tuple[int]] @@ -54,16 +63,21 @@ class ServerDB: launchpad_component: Dict[int, Tuple[int, int, str]] level_rewards: Dict[int, Sequence[Tuple[int, int]]] level_scores: Sequence[int] + loot_table: Dict[int, LootTableEntry] mission_mail: Dict[int, Sequence[Tuple[int, Optional[int]]]] mission_npc_component: Dict[int, Sequence[Tuple[int, bool, bool]]] missions: Dict[int, MissionData] predef_names: Tuple[Sequence[str], Sequence[str], Sequence[str]] object_skills: Dict[int, Sequence[Tuple[int, int]]] + package_component: Dict[int, LootTableEntry] property_template: Sequence[Tuple[float, float, float]] properties: Dict[int, Dict[int, Dict[int, Tuple[int, Vector3, Quaternion]]]] rebuild_component: Dict[int, Tuple[float, float, float, int, int]] script_component: Dict[int, str] servers: Dict[Address, Tuple[int, int, int]] + skill_behavior: Dict[int, Tuple[Any, int]] + vendor_component: Dict[int, LootTableEntry] + world_data: Dict[int, WorldData] world_info: Dict[int, Tuple[str, int]] log = logging.getLogger(__name__) @@ -75,7 +89,7 @@ class Server(_Server, ABC): def __init__(self, address: Address, max_connections: int, db_conn: Connection): super().__init__(address, max_connections) self.conn = db_conn - self.db: "ServerDB" = self.conn.root + self.db: ServerDB = self.conn.root self._packet_handlers: Dict[Tuple[int, int], List[Callable[..., None]]] = {} self._server.add_handler(Event.UserPacket, self._on_lu_packet) self.register_handler(GeneralMsg.Handshake, self._on_handshake) diff --git a/luserver/components/ai.py b/luserver/components/ai.py index f33b207..672faf7 100644 --- a/luserver/components/ai.py +++ b/luserver/components/ai.py @@ -1,7 +1,7 @@ from typing import Optional from pyraknet.bitstream import c_bit, WriteStream -from ..game_object import CallbackID, Config, GameObject, Player +from ..game_object import CallbackID, Config, GameObject, Player, StatsObject from ..world import server from ..math.quaternion import Quaternion from .component import Component @@ -18,8 +18,6 @@ class BaseCombatAIComponent(Component): self.target = None self._enabled = False self.update_handle: Optional[CallbackID] = None - self.object.add_handler("rebuild_init", self._on_rebuild_init) - self.object.add_handler("complete_rebuild", self._on_rebuild_complete) def on_startup(self) -> None: if not hasattr(self.object, "skill"): @@ -35,10 +33,10 @@ class BaseCombatAIComponent(Component): def serialize(self, out: WriteStream, is_creation: bool) -> None: out.write(c_bit(False)) - def _on_rebuild_init(self, _obj: GameObject) -> None: + def on_rebuild_init(self) -> None: self._enabled = False - def _on_rebuild_complete(self, _obj: GameObject, player: Player) -> None: + def on_complete_rebuild(self, player: Player) -> None: self._enabled = True def _disable(self) -> None: @@ -62,7 +60,7 @@ class BaseCombatAIComponent(Component): # todo: make distance skill-dependent nearest_dist = self.skill_range**2 # starting distance is maximum distance for obj in server.game_objects.values(): - if hasattr(obj, "stats") and obj.stats.faction in enemy_factions: + if isinstance(obj, StatsObject) and obj.stats.faction in enemy_factions: dist = self.object.physics.position.sq_distance(obj.physics.position) if dist < nearest_dist: self.target = obj diff --git a/luserver/components/behaviors.py b/luserver/components/behaviors.py index 2e10551..0d1f03e 100644 --- a/luserver/components/behaviors.py +++ b/luserver/components/behaviors.py @@ -1,4 +1,5 @@ import logging +from typing import Any from pyraknet.bitstream import c_bit, c_float, c_int64, c_ubyte, c_uint, c_ushort, ReadStream, WriteStream from ..game_object import GameObject @@ -9,16 +10,16 @@ log = logging.getLogger("luserver.components.skill") class _Behavior: @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: raise NotImplementedError @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: raise NotImplementedError class BasicAttack(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: bitstream.align_write() bitstream.write(c_ushort(0)) bitstream.write(c_bit(False)) @@ -35,7 +36,7 @@ class BasicAttack(_Behavior): self.serialize_behavior(behavior.on_success, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: bitstream.align_read() bitstream.read(c_ushort) # "padding", unused if target == self.object: @@ -58,7 +59,7 @@ class BasicAttack(_Behavior): class TacArc(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: is_hit = True bitstream.write(c_bit(is_hit)) if behavior.check_env: @@ -73,7 +74,7 @@ class TacArc(_Behavior): self.serialize_behavior(behavior.action, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: if hasattr(behavior, "use_picked_target") and behavior.use_picked_target and self.picked_target_id != 0 and self.picked_target_id in server.game_objects: target = server.game_objects[self.picked_target_id] # todo: there seems to be a skill where this doesn't work and where the rest of the code should be executed as if the following lines weren't there? @@ -112,18 +113,18 @@ class TacArc(_Behavior): class And(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: for behav in behavior.behaviors: self.serialize_behavior(behav, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: for behav in behavior.behaviors: self.deserialize_behavior(behav, bitstream, target, level+1) class ProjectileAttack(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: bitstream.write(c_int64(target.object_id)) proj_behavs = [] @@ -137,7 +138,7 @@ class ProjectileAttack(_Behavior): bitstream.write(c_int64(self.cast_projectile(proj_behavs, target))) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: target_id = bitstream.read(c_int64) if target_id != 0 and target_id in server.game_objects: target = server.game_objects[target_id] @@ -156,11 +157,11 @@ class ProjectileAttack(_Behavior): class Heal(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: pass @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: target.stats.life += behavior.life class MovementType: @@ -176,12 +177,12 @@ class MovementType: class MovementSwitch(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: bitstream.write(c_uint(1)) return @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: movement_type = bitstream.read(c_uint) log.debug("Movement type %i", movement_type) if movement_type in (MovementType.Ground, MovementType.Seven, MovementType.Nine, MovementType.Rail): @@ -201,11 +202,11 @@ class MovementSwitch(_Behavior): class AreaOfEffect(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: bitstream.write(c_uint(0)) # number of targets @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: targets = [] for _ in range(bitstream.read(c_uint)): # number of targets target_id = bitstream.read(c_int64) @@ -216,72 +217,72 @@ class AreaOfEffect(_Behavior): class OverTime(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: for interval in range(behavior.num_intervals): self.object.call_later(interval * behavior.delay, self.deserialize_behavior, behavior.action, b"", target) class Imagination(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: pass @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: target.stats.imagination += behavior.imagination class TargetCaster(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: casted_behavior = behavior.action self.serialize_behavior(casted_behavior, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: casted_behavior = behavior.action self.deserialize_behavior(casted_behavior, bitstream, target, level+1) class Stun(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: # needs to be researched more if False:#target.object_id != self.original_target_id: log.debug("Stun writing bit") bitstream.write(c_bit(False)) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: if False:#target and target.object_id != self.original_target_id: log.debug("Stun reading bit") assert not bitstream.read(c_bit) class Duration(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: self.serialize_behavior(behavior.action, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: params = self.deserialize_behavior(behavior.action, bitstream, target, level+1) self.object.call_later(behavior.duration, self.undo_behavior, behavior.action, params) class Knockback(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: bitstream.write(c_bit(False)) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: assert not bitstream.read(c_bit) class AttackDelay(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: handle = self.cast_sync_skill(behavior.delay, behavior.action, target) log.debug("write handle %s", handle) bitstream.write(c_uint(handle)) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: handle = bitstream.read(c_uint) log.debug("read handle %s", handle) self.delayed_behaviors[handle] = behavior.action @@ -290,16 +291,16 @@ ChargeUp = AttackDelay # works the same class RepairArmor(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: pass @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: target.stats.armor += behavior.armor class SpawnObject(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: position = self.object.physics.position + self.object.physics.rotation.rotate(Vector3.forward)*behavior.distance return server.spawn_object(behavior.lot, {"parent": self.object, "position": position}) @@ -307,7 +308,7 @@ SpawnQuickbuild = SpawnObject # works the same class Switch(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: switch = True if getattr(behavior, "imagination", 0) > 0 or not getattr(behavior, "is_enemy_faction", False): log.debug("Switch writing bit") @@ -318,7 +319,7 @@ class Switch(_Behavior): self.serialize_behavior(behavior.action_false, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: switch = True if getattr(behavior, "imagination", 0) > 0 or not getattr(behavior, "is_enemy_faction", False): log.debug("Switch reading bit") @@ -330,11 +331,11 @@ class Switch(_Behavior): class Buff(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: pass @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: if hasattr(behavior, "life"): self.object.stats.max_life += behavior.life if hasattr(behavior, "armor"): @@ -344,11 +345,11 @@ class Buff(_Behavior): class Jetpack(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: pass @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: kwargs = {} if hasattr(behavior, "bypass_checks"): kwargs["bypass_checks"] = bool(behavior.bypass_checks) @@ -366,11 +367,11 @@ class Jetpack(_Behavior): class SkillEvent(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: pass @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: if behavior.id == 14211: event_name = "waterspray" elif behavior.id == 27031: @@ -378,27 +379,27 @@ class SkillEvent(_Behavior): else: event_name = None - target.handle("on_skill_event", self.object, event_name, silent=True) + target.handle("skill_event", self.object, event_name, silent=True) class SkillCastFailed(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: pass @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: self.skill_cast_failed = True class Chain(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: chain_index = bitstream.read(c_uint) log.debug("chain index %i", chain_index) self.deserialize_behavior(behavior.behaviors[chain_index-1], bitstream, target, level+1) class ForceMovement(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: if getattr(behavior, "hit_action", None) is not None or \ getattr(behavior, "hit_action_enemy", None) is not None or \ getattr(behavior, "hit_action_faction", None) is not None: @@ -408,7 +409,7 @@ class ForceMovement(_Behavior): class Interrupt(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: if target != self.object: log.debug("Interrupt: target != self, writing bit") bitstream.write(c_bit(False)) @@ -417,7 +418,7 @@ class Interrupt(_Behavior): bitstream.write(c_bit(False)) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: if target != self.object: log.debug("Interrupt: target != self, reading bit") assert not bitstream.read(c_bit) @@ -428,7 +429,7 @@ class Interrupt(_Behavior): class SwitchMultiple(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: charge_time = bitstream.read(c_float) for behav, value in behavior.behaviors: if charge_time <= value: @@ -437,21 +438,21 @@ class SwitchMultiple(_Behavior): class Start(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: self.deserialize_behavior(behavior.action, bitstream, target, level+1) class NPCCombatSkill(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: self.serialize_behavior(behavior.behavior, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: self.deserialize_behavior(behavior.behavior, bitstream, target, level+1) class Verify(_Behavior): @staticmethod - def serialize(self, behavior, bitstream: WriteStream, target: GameObject, level: int) -> None: + def serialize(self, behavior: Any, bitstream: WriteStream, target: GameObject, level: int) -> None: bitstream.write(c_bit(False)) bitstream.write(c_uint(0)) bitstream.write(c_bit(False)) # blocking @@ -459,7 +460,7 @@ class Verify(_Behavior): self.serialize_behavior(behavior.action, bitstream, target, level+1) @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: assert not bitstream.read(c_bit) assert bitstream.read(c_uint) == 0 assert not bitstream.read(c_bit) @@ -468,12 +469,12 @@ class Verify(_Behavior): class AirMovement(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: handle = bitstream.read(c_uint) log.debug("move handle %s", handle) self.delayed_behaviors[handle] = None # not known yet class ClearTarget(_Behavior): @staticmethod - def deserialize(self, behavior, bitstream: ReadStream, target: GameObject, level: int) -> None: + def deserialize(self, behavior: Any, bitstream: ReadStream, target: GameObject, level: int) -> None: self.deserialize_behavior(behavior.action, bitstream, target, level+1) diff --git a/luserver/components/bouncer.py b/luserver/components/bouncer.py index 3a15266..0cc8c2b 100644 --- a/luserver/components/bouncer.py +++ b/luserver/components/bouncer.py @@ -1,6 +1,10 @@ from pyraknet.bitstream import c_bit, WriteStream +from ..game_object import c_int64, EB, EI from .component import Component class BouncerComponent(Component): def serialize(self, out: WriteStream, is_creation: bool) -> None: out.write(c_bit(False)) + + def on_bounce_notification(self, object_id_bounced:c_int64=EI, object_id_bouncer:c_int64=EI, success:bool=EB) -> None: + pass diff --git a/luserver/components/char/__init__.py b/luserver/components/char/__init__.py index 27207ae..4994aa4 100644 --- a/luserver/components/char/__init__.py +++ b/luserver/components/char/__init__.py @@ -61,7 +61,7 @@ class RewardType: Item = 0 InventorySpace = 4 -class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharPet, CharProperty, CharTrade, CharUI): +class CharacterComponent(Component): object: Player def __init__(self, obj: GameObject, set_vars: Config, comp_id: int): @@ -78,7 +78,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP self.friends = PersistentList() self.mails: List[Mail] = PersistentList() - self.unlocked_emotes = PersistentList() + self.unlocked_emotes: List[int] = PersistentList() self.clone_id = server.new_clone_id() @@ -88,8 +88,13 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP self.dropped_loot: Dict[ObjectID, int] = {} self.last_collisions: List[ObjectID] = [] - CharMission.__init__(self) - CharTrade.__init__(self) + self.activity = CharActivity(self.object) + self.camera = CharCamera(self.object) + self.mission = CharMission(self.object) + self.pet = CharPet(self.object) + self.property = CharProperty(self.object) + self.trade = CharTrade(self.object) + self.ui = CharUI(self.object) # Component stuff @@ -156,23 +161,14 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def serialize(self, out: WriteStream, is_creation: bool) -> None: # First index - creation = is_creation and self.vehicle_id != 0 - out.write(c_bit(creation or self.vehicle_flag)) - if creation or self.vehicle_flag: - out.write(c_bit(creation or self.vehicle_id_flag)) - if creation or self.vehicle_id_flag: + if self.flag("vehicle_flag", out, is_creation and self.vehicle_id != 0): + if self.flag("vehicle_id_flag", out, is_creation and self.vehicle_id != 0): out.write(c_int64(self.vehicle_id)) - if not creation: - self.vehicle_id_flag = False out.write(c_ubyte(1)) # unknown - if not creation: - self.vehicle_flag = False # Second index - out.write(c_bit(self.level_flag or is_creation)) - if self.level_flag or is_creation: + if self.flag("level_flag", out, is_creation): out.write(c_uint(self.level)) - self.level_flag = False # Third index # This index is shared with other components, reflect that when we implement the other ones @@ -239,8 +235,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP out.write(module_str, length_type=c_ushort) self.traveling_rocket = None - out.write(c_bit(self.gm_flag or is_creation)) - if self.gm_flag or is_creation: + if self.flag("gm_flag", out, is_creation): out.write(c_bit(self.pvp_enabled)) out.write(c_bit(self.show_gm_status)) if self.account is None: @@ -249,17 +244,11 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP out.write(c_ubyte(self.account.gm_level)) out.write(c_bit(False)) out.write(c_ubyte(0)) - if self.gm_flag: - self.gm_flag = False - out.write(c_bit(self.rebuilding_flag or is_creation)) - if self.rebuilding_flag or is_creation: + if self.flag("rebuilding_flag", out, is_creation): out.write(c_uint(self.rebuilding)) - if self.rebuilding_flag: - self.rebuilding_flag = False - out.write(c_bit(self.guild_flag or (is_creation and self.tags))) - if self.guild_flag or (is_creation and self.tags): + if self.flag("guild_flag", out, is_creation and self.tags): if self.tags: out.write(c_int64(self.object.object_id)) else: @@ -267,8 +256,6 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP out.write(", ".join(self.tags), length_type=c_ubyte) out.write(c_bit(False)) out.write(c_int(-1)) - if self.guild_flag: - self.guild_flag = False @property def online(self) -> bool: @@ -314,12 +301,11 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP del self.friends[index] def on_destruction(self) -> None: - self.object._handlers.clear() + #self.object._handlers.clear() self.vehicle_id = 0 self.online = False self.dropped_loot.clear() self.last_collisions.clear() - CharTrade.on_destruction(self) self.check_for_leaks() def check_for_leaks(self, fullcheck: bool=False) -> None: @@ -347,7 +333,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP if clean_equipped: log.info(self.object.inventory.equipped[-1]) for item in self.object.inventory.equipped[-1]: - self.object.inventory.un_equip_inventory(item_to_unequip=item.object_id) + self.object.inventory.on_un_equip_inventory(item_to_unequip=item.object_id) if fullcheck: for inv in (self.object.inventory.items, self.object.inventory.temp_items, self.object.inventory.models): @@ -387,7 +373,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP return loot def should_be_dropped(self, lot: int) -> bool: - for mission in self.missions.values(): + for mission in self.mission.missions.values(): if mission.state == MissionState.Active: for task in mission.tasks: if task.type == TaskType.ObtainItem and lot in task.target and task.value < task.target_value: @@ -401,10 +387,10 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP self.object._p_changed = True if respawn_point_name is not None and world[0] in server.db.world_data: - for obj in 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 - self.object.physics.position.update(obj.physics.position) - self.object.physics.rotation.update(obj.physics.rotation) + for lot, obj_id, config in server.db.world_data[world[0]].objects.values(): + if lot == 4945 and ("respawn_name" not in config or respawn_point_name == "" or config["respawn_name"] == respawn_point_name): # respawn point lot + self.object.physics.position.update(config["position"]) + self.object.physics.rotation.update(config["rotation"]) break else: self.object.physics.position.update(server.db.world_data[world[0]].spawnpoint[0]) @@ -450,20 +436,20 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def drop_client_loot(self, use_position:bool=False, final_position:Vector3=Vector3.zero, currency:c_int_=EI, item_template:c_int_=EI, loot_id:c_int64_=EI, owner:GameObject=EO, source_obj:GameObject=EO, spawn_position:Vector3=Vector3.zero) -> None: pass - def play_emote(self, emote_id:c_int_, target:GameObject) -> None: + def on_play_emote(self, emote_id:c_int_, target:GameObject) -> None: self.emote_played(emote_id, target) if target is not None: - target.handle("on_emote_received", self.object, emote_id, silent=True) - self.update_mission_task(TaskType.UseEmote, target.lot, emote_id) + target.handle("emote_received", self.object, emote_id, silent=True) + self.mission.update_mission_task(TaskType.UseEmote, target.lot, emote_id) @single def set_currency(self, currency:c_int64_=EI, loot_type:c_int_=0, position:Vector3=EV, source_lot:c_int_=-1, source_object:GameObject=OBJ_NONE, source_trade:GameObject=OBJ_NONE, source_type:c_int_=0) -> None: self.currency = currency - def pickup_currency(self, currency:c_uint_=EI, position:Vector3=EV) -> None: + def on_pickup_currency(self, currency:c_uint_=EI, position:Vector3=EV) -> None: self.set_currency(currency=self.currency + currency, position=Vector3.zero) - def pickup_item(self, loot_object_id:c_int64_=EI, player_id:c_int64_=EI) -> None: + def on_pickup_item(self, loot_object_id:c_int64_=EI, player_id:c_int64_=EI) -> None: assert player_id == self.object.object_id if loot_object_id not in self.dropped_loot: return @@ -471,12 +457,12 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP if lot in (177, 935, 4035, 6431, 7230, 8200, 8208, 11910, 11911, 11912, 11913, 11914, 11915, 11916, 11917, 11918, 11919, 11920): # powerup for skill_id, _ in server.db.object_skills[lot]: self.object.skill.cast_skill(skill_id) - self.update_mission_task(TaskType.CollectPowerup, skill_id) + self.mission.update_mission_task(TaskType.CollectPowerup, skill_id) else: self.object.inventory.add_item(lot) del self.dropped_loot[loot_object_id] - def request_resurrect(self) -> None: + def on_request_resurrect(self) -> None: self.object.destructible.resurrect() self.object.stats.life = 4 self.object.stats.imagination = 6 @@ -489,7 +475,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def terminate_interaction(self, terminator:GameObject=EO, type:c_int_=EI) -> None: pass - def request_use(self, is_multi_interact_use:bool=EB, multi_interact_id:c_uint_=EI, multi_interact_type:c_int_=EI, obj:GameObject=EO, secondary:bool=False) -> None: + def on_request_use(self, is_multi_interact_use:bool=EB, multi_interact_id:c_uint_=EI, multi_interact_type:c_int_=EI, obj:GameObject=EO, secondary:bool=False) -> None: if not is_multi_interact_use: assert multi_interact_id == 0 multi_interact_id = None @@ -497,17 +483,17 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP if obj is None: return log.debug("Interacting with %s", obj) - obj.handle("on_use", self.object, multi_interact_id) + obj.handle("use", self.object, multi_interact_id) - self.update_mission_task(TaskType.Interact, obj.lot) + self.mission.update_mission_task(TaskType.Interact, obj.lot) @broadcast def emote_played(self, emote_id:c_int_, target:GameObject) -> None: pass - def client_item_consumed(self, item_id:c_int64_=EI) -> None: + def on_client_item_consumed(self, item_id:c_int64_=EI) -> None: item = self.object.inventory.get_stack(InventoryType.Items, item_id) - self.update_mission_task(TaskType.UseConsumable, item.lot) + self.mission.update_mission_task(TaskType.UseConsumable, item.lot) @single def set_user_ctrl_comp_pause(self, paused:bool=EB) -> None: @@ -516,16 +502,18 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def get_flag(self, flag_id: int) -> bool: return bool(self.flags & (1 << flag_id)) - @single def set_flag(self, flag:bool=EB, flag_id:c_int_=EI) -> None: if self.get_flag(flag_id) == flag: return self.flags ^= (-int(flag) ^ self.flags) & (1 << flag_id) if flag: - self.update_mission_task(TaskType.Flag, flag_id) + self.mission.update_mission_task(TaskType.Flag, flag_id) - def player_loaded(self, player_id:c_int64_=EI) -> None: + on_set_flag = set_flag + set_flag = single(set_flag) + + def on_player_loaded(self, player_id:c_int64_=EI) -> None: assert player_id == self.object.object_id self.player_ready() if self.world == (0, 0, 0): @@ -550,7 +538,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def set_jet_pack_mode(self, bypass_checks:bool=True, hover:bool=False, enable:bool=False, effect_id:c_uint_=-1, air_speed:float=10, max_air_speed:float=15, vertical_velocity:float=1, warning_effect_id:c_uint_=-1) -> None: pass - def use_non_equipment_item(self, item_to_use:c_int64_=EI) -> None: + def on_use_non_equipment_item(self, item_to_use:c_int64_=EI) -> None: item = self.object.inventory.get_stack(InventoryType.Items, item_to_use) for component_type, component_id in server.db.components_registry[item.lot]: if component_type == 53: # PackageComponent, make an enum for this somewhen @@ -564,14 +552,11 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP if not lock: self.unlocked_emotes.append(emote_id) - def parse_chat_message(self, client_state:c_int_, text:str) -> None: + def on_parse_chat_message(self, client_state:c_int_, text:str) -> None: if text.startswith("/"): server.chat.parse_command(text[1:], self.object) - def ready_for_updates(self, object_id:c_int64_=EI) -> None: - pass - - def bounce_notification(self, object_id_bounced:c_int64_=EI, object_id_bouncer:c_int64_=EI, success:bool=EB) -> None: + def on_ready_for_updates(self, object_id:c_int64_=EI) -> None: pass @single @@ -590,18 +575,18 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def u_i_message_server_to_single_client(self, args:AMF3=EA, message_name:bytes=EBY) -> None: pass - def report_bug(self, body:str=ES, client_version:bytes=EBY, other_player_id:bytes=EBY, selection:bytes=EBY) -> None: + def on_report_bug(self, body:str=ES, client_version:bytes=EBY, other_player_id:bytes=EBY, selection:bytes=EBY) -> None: # The chat text input has limited length, this one doesn't # So this makes use of that to allow longer chat commands if selection == b"%[UI_HELP_IN_GAME]" and body.startswith("/"): - self.parse_chat_message(0, body) + self.on_parse_chat_message(0, body) return for account in server.accounts.values(): if account.gm_level == GMLevel.Admin: for char in account.characters.values(): server.mail.send_mail(self.object.name, "Bug Report: "+selection.decode(), body, char) - def request_smash_player(self) -> None: + def on_request_smash_player(self) -> None: self.object.destructible.simply_die(killer=self.object) @broadcast @@ -612,7 +597,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def player_reached_respawn_checkpoint(self, pos:Vector3=EV, rot:Quaternion=Quaternion.identity) -> None: pass - def used_information_plaque(self, plaque_object_id:c_int64_=EI) -> None: + def on_used_information_plaque(self, plaque_object_id:c_int64_=EI) -> None: pass @single @@ -655,7 +640,7 @@ class CharacterComponent(Component, CharActivity, CharCamera, CharMission, CharP def server_done_loading_all_objects(self) -> None: pass - def notify_server_level_processing_complete(self) -> None: + def on_notify_server_level_processing_complete(self) -> None: self.object.render.play_f_x_effect(name=b"7074", effect_type="create", effect_id=7074) @single diff --git a/luserver/components/char/activity.py b/luserver/components/char/activity.py index 8aa78d2..ce81203 100644 --- a/luserver/components/char/activity.py +++ b/luserver/components/char/activity.py @@ -1,5 +1,6 @@ from ...game_object import c_int, c_int64, EB, EI, EL, single from ...ldf import LDF, LDFDataType +from .subcomponent import CharSubcomponent class MatchRequestType: Join = 0 @@ -13,8 +14,8 @@ class MatchRequestValue: class MatchUpdateType: Time = 3 -class CharActivity: - def request_activity_summary_leaderboard_data(self, game_id:c_int=0, query_type:c_int=1, results_end:c_int=10, results_start:c_int=0, target:c_int64=EI, weekly:bool=EB) -> None: +class CharActivity(CharSubcomponent): + def on_request_activity_summary_leaderboard_data(self, game_id:c_int=0, query_type:c_int=1, results_end:c_int=10, results_start:c_int=0, target:c_int64=EI, weekly:bool=EB) -> None: leaderboard = LDF() leaderboard.ldf_set("ADO.Result", LDFDataType.BOOLEAN, True) leaderboard.ldf_set("Result.Count", LDFDataType.INT32, 0) @@ -24,7 +25,7 @@ class CharActivity: def send_activity_summary_leaderboard_data(self, game_id:c_int=EI, info_type:c_int=EI, leaderboard_data:LDF=EL, throttled:bool=EB, weekly:bool=EB) -> None: pass - def match_request(self, activator:c_int64=EI, player_choices:LDF=EL, type:c_int=EI, value:c_int=EI) -> None: + def on_match_request(self, activator:c_int64=EI, player_choices:LDF=EL, type:c_int=EI, value:c_int=EI) -> None: self.match_response(response=0) if type == MatchRequestType.Join:# and value == MatchRequestValue.Join: update_data = LDF() diff --git a/luserver/components/char/camera.py b/luserver/components/char/camera.py index ff638dc..ac026b5 100644 --- a/luserver/components/char/camera.py +++ b/luserver/components/char/camera.py @@ -2,6 +2,7 @@ from ...amf3 import AMF3 from ...game_object import broadcast, c_int, c_uint, EF, EL, ES, EO, EV, GameObject, single from ...ldf import LDF from ...math.vector import Vector3 +from .subcomponent import CharSubcomponent class EndBehavior: Return = 0 @@ -16,7 +17,7 @@ class CyclingMode: AllowCycleTeammates = 0 DisallowCycling = 1 -class CharCamera: +class CharCamera(CharSubcomponent): @single def play_cinematic(self, allow_ghost_updates:bool=True, close_multi_interact:bool=False, send_server_notify:bool=False, use_controlled_object_for_audio_listener:bool=False, end_behavior:c_uint=EndBehavior.Return, hide_player_during_cine:bool=False, lead_in:float=-1.0, leave_player_locked_when_finished:bool=False, lock_player:bool=True, path_name:str=ES, result:bool=False, skip_if_same_path:bool=False, start_time_advance:float=EF) -> None: pass @@ -25,15 +26,15 @@ class CharCamera: def end_cinematic(self, lead_out:float=-1, leave_player_locked:bool=False, path_name:str=ES) -> None: pass - def cinematic_update(self, event:c_uint=CinematicEvent.Started, overall_time:float=-1, path_name:str=ES, path_time:float=-1, waypoint:c_int=-1) -> None: + def on_cinematic_update(self, event:c_uint=CinematicEvent.Started, overall_time:float=-1, path_name:str=ES, path_time:float=-1, waypoint:c_int=-1) -> None: if event == CinematicEvent.Ended: # currently only used by the "play cinematic" command, which needs to reset the HUD here - self.u_i_message_server_to_single_client(message_name=b"popGameState", args=AMF3({"state": "front_end"})) + self.object.char.u_i_message_server_to_single_client(message_name=b"popGameState", args=AMF3({"state": "front_end"})) - def toggle_ghost_reference_override(self, override:bool=False) -> None: + def on_toggle_ghost_reference_override(self, override:bool=False) -> None: pass - def set_ghost_reference_position(self, position:Vector3=EV) -> None: + def on_set_ghost_reference_position(self, position:Vector3=EV) -> None: pass @single diff --git a/luserver/components/char/mission.py b/luserver/components/char/mission.py index 6c683cf..7f18d1e 100644 --- a/luserver/components/char/mission.py +++ b/luserver/components/char/mission.py @@ -8,11 +8,12 @@ from ...world import server from ...math.vector import Vector3 from ..inventory import InventoryType, LootType, Stack from ..mission import check_prereqs, MissionProgress, MissionState, ObtainItemType, TaskType +from .subcomponent import CharSubcomponent -class CharMission: - object: Player - - def __init__(self) -> None: +class CharMission(CharSubcomponent): + def __init__(self, player: Player) -> None: + super().__init__(player) + self.object = player self.missions: Dict[int, MissionProgress] = PersistentMapping() # add achievements for mission_id, data in server.db.missions.items(): @@ -99,8 +100,8 @@ class CharMission: source_type = LootType.Achievement self.notify_mission(mission_id, mission_state=MissionState.Unavailable, sending_rewards=True) - self.set_currency(currency=self.currency + mission.rew_currency, position=Vector3.zero, source_type=source_type) - self.modify_lego_score(mission.rew_universe_score, source_type=source_type) + self.object.char.set_currency(currency=self.object.char.currency + mission.rew_currency, position=Vector3.zero, source_type=source_type) + self.object.char.modify_lego_score(mission.rew_universe_score, source_type=source_type) for task in mission.tasks: if task.type == TaskType.ObtainItem and task.parameter == ObtainItemType.RemoveOnComplete: @@ -114,7 +115,7 @@ class CharMission: self.object.inventory.add_item(lot, count, source_type=source_type) if mission.rew_emote is not None: - self.set_emote_lock_state(lock=False, emote_id=mission.rew_emote) + self.object.char.set_emote_lock_state(lock=False, emote_id=mission.rew_emote) self.object.stats.max_life += mission.rew_max_life self.object.stats.max_imagination += mission.rew_max_imagination @@ -149,7 +150,7 @@ class CharMission: def offer_mission(self, mission_id:c_int=EI, offerer:GameObject=EO) -> None: pass - def respond_to_mission(self, mission_id:c_int=EI, player_id:c_int64=EI, receiver:GameObject=EO, reward_item:c_int=-1) -> None: + def on_respond_to_mission(self, mission_id:c_int=EI, player_id:c_int64=EI, receiver:GameObject=EO, reward_item:c_int=-1) -> None: assert player_id == self.object.object_id if reward_item != -1: mission = self.missions[mission_id] diff --git a/luserver/components/char/pet.py b/luserver/components/char/pet.py index b07e417..15e758e 100644 --- a/luserver/components/char/pet.py +++ b/luserver/components/char/pet.py @@ -1,18 +1,17 @@ from pyraknet.bitstream import c_uint -from ...game_object import broadcast, c_int, c_uint64, E, EB, EI, EO, EV, GameObject, OBJ_NONE, Player, Sequence, single +from ...game_object import broadcast, c_int, c_uint64, E, EB, EI, EO, EV, GameObject, OBJ_NONE, Sequence, single from ...game_object import c_uint as c_uint_ from ...math.vector import Vector3 from ...math.quaternion import Quaternion from ..pet import PetTamingNotify +from .subcomponent import CharSubcomponent -class CharPet: - object: Player - +class CharPet(CharSubcomponent): @single def notify_pet_taming_minigame(self, pet:GameObject=EO, player_taming:GameObject=EO, force_teleport:bool=EB, notify_type:c_uint_=EI, pets_dest_pos:Vector3=EV, tele_pos:Vector3=EV, tele_rot:Quaternion=Quaternion.identity) -> None: pass - def client_exit_taming_minigame(self, voluntary_exit:bool=True) -> None: + def on_client_exit_taming_minigame(self, voluntary_exit:bool=True) -> None: self.notify_pet_taming_minigame(pet=OBJ_NONE, player_taming=OBJ_NONE, force_teleport=False, notify_type=PetTamingNotify.Quit, pets_dest_pos=self.object.physics.position, tele_pos=self.object.physics.position, tele_rot=self.object.physics.rotation) @broadcast @@ -23,7 +22,7 @@ class CharPet: def notify_pet_taming_puzzle_selected(self, bricks:Sequence[c_uint, c_uint]=E) -> None: pass - def pet_taming_try_build(self, selections:Sequence[c_uint, c_uint64]=E, client_failed:bool=False) -> None: + def on_pet_taming_try_build(self, selections:Sequence[c_uint, c_uint64]=E, client_failed:bool=False) -> None: if not client_failed: self.pet_taming_try_build_result() self.notify_pet_taming_minigame(pet=OBJ_NONE, player_taming=OBJ_NONE, force_teleport=False, notify_type=PetTamingNotify.NamingPet, pets_dest_pos=self.object.physics.position, tele_pos=self.object.physics.position, tele_rot=self.object.physics.rotation) diff --git a/luserver/components/char/property.py b/luserver/components/char/property.py index 3243ff5..77e2fcf 100644 --- a/luserver/components/char/property.py +++ b/luserver/components/char/property.py @@ -2,7 +2,7 @@ import logging from pyraknet.bitstream import c_int64, c_uint from ...bitstream import WriteStream -from ...game_object import broadcast, c_int, E, EBY, EI, EV, GameObject, Mapping, OBJ_NONE, Player, single +from ...game_object import broadcast, c_int, E, EBY, EI, EV, GameObject, Mapping, OBJ_NONE, single from ...game_object import c_int64 as c_int64_ from ...game_object import c_uint as c_uint_ from ...messages import WorldClientMsg @@ -10,6 +10,7 @@ from ...world import server from ...math.vector import Vector3 from ...math.quaternion import Quaternion from ..inventory import InventoryType +from .subcomponent import CharSubcomponent log = logging.getLogger(__file__) @@ -18,14 +19,12 @@ class DeleteReason: ReturningModelToInventory = 1 BreakingModelApart = 2 -class CharProperty: - object: Player - +class CharProperty(CharSubcomponent): @single def place_model_response(self, position:Vector3=Vector3.zero, property_plaque:GameObject=OBJ_NONE, response:c_int=0, rotation:Quaternion=Quaternion.identity) -> None: pass - def update_model_from_client(self, model_id:c_int64_=EI, position:Vector3=EV, rotation:Quaternion=Quaternion.identity) -> None: + def on_update_model_from_client(self, model_id:c_int64_=EI, position:Vector3=EV, rotation:Quaternion=Quaternion.identity) -> None: for model in self.object.inventory.models: if model is not None and model.object_id == model_id: spawner_id = server.new_object_id() @@ -47,7 +46,7 @@ class CharProperty: self.handle_u_g_c_equip_pre_create_based_on_edit_mode(0, spawner_id) break - def delete_model_from_client(self, model_id:c_int64_=0, reason:c_uint_=DeleteReason.PickingModelUp) -> None: + def on_delete_model_from_client(self, model_id:c_int64_=0, reason:c_uint_=DeleteReason.PickingModelUp) -> None: assert reason in (DeleteReason.PickingModelUp, DeleteReason.ReturningModelToInventory) if reason == DeleteReason.PickingModelUp: server.world_control_object.script.on_model_picked_up(self.object) @@ -62,13 +61,13 @@ class CharProperty: del prop_spawners[spawner.object_id] item = self.object.inventory.add_item(model.lot) if reason == DeleteReason.PickingModelUp: - self.object.inventory.equip_inventory(item_to_equip=item.object_id) + self.object.inventory.on_equip_inventory(item_to_equip=item.object_id) self.handle_u_g_c_equip_post_delete_based_on_edit_mode(inv_item=item.object_id, items_total=item.count) self.get_models_on_property(models={model: spawner for spawner, model in server.models}) self.place_model_response(response=16) break - def b_b_b_save_request(self, local_id:c_int64_=EI, lxfml_data_compressed:bytes=EBY, time_taken_in_ms:c_uint_=EI) -> None: + def on_b_b_b_save_request(self, local_id:c_int64_=EI, lxfml_data_compressed:bytes=EBY, time_taken_in_ms:c_uint_=EI) -> None: save_response = WriteStream() save_response.write_header(WorldClientMsg.BlueprintSaveResponse) save_response.write(c_int64(local_id)) @@ -76,7 +75,7 @@ class CharProperty: save_response.write(c_uint(1)) save_response.write(c_int64(server.new_object_id())) save_response.write(lxfml_data_compressed, length_type=c_uint) - server.send(save_response, self.address) + server.send(save_response, self.object.char.address) @single def handle_u_g_c_equip_post_delete_based_on_edit_mode(self, inv_item:c_int64_=EI, items_total:c_int=0) -> None: @@ -86,7 +85,7 @@ class CharProperty: def handle_u_g_c_equip_pre_create_based_on_edit_mode(self, model_count:c_int=EI, model_id:c_int64_=EI) -> None: pass - def property_contents_from_client(self, query_db:bool=False) -> None: + def on_property_contents_from_client(self, query_db:bool=False) -> None: self.get_models_on_property(models={model: spawner for spawner, model in server.models}) @broadcast diff --git a/luserver/components/char/subcomponent.py b/luserver/components/char/subcomponent.py new file mode 100644 index 0000000..0531543 --- /dev/null +++ b/luserver/components/char/subcomponent.py @@ -0,0 +1,8 @@ +from ...game_object import Player + +class CharSubcomponent: + def __init__(self, player: Player): + self.object = player + for name in dir(self): + if name.startswith("on_"): + self.object.add_handler(name[3:], getattr(self, name)) diff --git a/luserver/components/char/trade.py b/luserver/components/char/trade.py index 460a5f6..c2287d8 100644 --- a/luserver/components/char/trade.py +++ b/luserver/components/char/trade.py @@ -1,9 +1,12 @@ +from typing import cast, Optional + from pyraknet.bitstream import c_uint -from ...game_object import c_int64, c_uint64, E, EI, ES, EO, EP, GameObject, Mapping, Player, single +from ...game_object import c_int64, c_uint64, E, EI, ES, EO, EP, GameObject, Mapping, ObjectID, Player, single from ...game_object import c_uint as c_uint_ from ...world import server from ...math.vector import Vector3 from ..inventory import InventoryType, LootType, Stack +from .subcomponent import CharSubcomponent class TradeInviteResult: NotFound = 0 @@ -14,27 +17,26 @@ class TradeInviteResult: class Trade: def __init__(self) -> None: - self.other_player = None + self.other_player: ObjectID = None self.accepted = False self.currency_offered = 0 self.items_offered = {} -class CharTrade: - object: Player - - def __init__(self) -> None: - self.trade = None +class CharTrade(CharSubcomponent): + def __init__(self, player: Player) -> None: + super().__init__(player) + self.trade: Optional[Trade] = None def on_destruction(self) -> None: self.trade = None - def client_trade_request(self, need_invite_pop_up:bool=False, invitee:Player=EP) -> None: + def on_client_trade_request(self, need_invite_pop_up:bool=False, invitee:Player=EP) -> None: # out of range error not implemented if (self.trade is not None and self.trade.other_player != invitee.object_id) \ or (invitee.char.trade is not None and invitee.char.trade.other_player != self.object.object_id): result = TradeInviteResult.AlreadyTrading else: - invitee.char.server_trade_invite(need_invite_pop_up, requestor=self.object, name=self.object.name) + invitee.char.trade.server_trade_invite(need_invite_pop_up, requestor=self.object, name=self.object.name) invitee.char.trade = Trade() invitee.char.trade.other_player = self.object.object_id result = TradeInviteResult.InviteSent @@ -48,19 +50,19 @@ class CharTrade: def server_trade_initial_reply(self, invitee:GameObject=EO, result_type:c_uint_=EI, name:str=ES) -> None: pass - def client_trade_update(self, currency:c_uint64=E, items:Mapping[c_uint, c_int64, Stack]=E) -> None: + def on_client_trade_update(self, currency:c_uint64=E, items:Mapping[c_uint, c_int64, Stack]=E) -> None: self.trade.currency_offered = currency self.trade.items_offered = items - trade_player = server.game_objects[self.trade.other_player] - trade_player.char.server_trade_update(currency=currency, items=items) + trade_player = cast(Player, server.game_objects[self.trade.other_player]) + trade_player.char.trade.server_trade_update(currency=currency, items=items) @single def server_trade_update(self, about_to_perform:bool=False, currency:c_uint64=E, items:Mapping[c_uint, c_int64, Stack]=E) -> None: if about_to_perform: - trade_player = server.game_objects[self.trade.other_player] + trade_player = cast(Player, server.game_objects[self.trade.other_player]) if self.trade.currency_offered != 0: trade_player.char.set_currency(currency=trade_player.char.currency + self.trade.currency_offered, position=Vector3.zero, source_type=LootType.Trade, source_trade=self.object) - self.set_currency(currency=self.currency - self.trade.currency_offered, position=Vector3.zero, source_type=LootType.Trade, source_trade=trade_player) + self.object.char.set_currency(currency=self.object.char.currency - self.trade.currency_offered, position=Vector3.zero, source_type=LootType.Trade, source_trade=trade_player) for item in self.trade.items_offered.values(): trade_player.inventory.add_item(item.lot, item.count, source_type=LootType.Trade) self.object.inventory.remove_item(InventoryType.Max, object_id=item.object_id, count=item.count) @@ -69,14 +71,14 @@ class CharTrade: def client_trade_cancel(self) -> None: if self.trade is None: return - trade_player = server.game_objects[self.trade.other_player] - trade_player.char.server_trade_cancel() + trade_player = cast(Player, server.game_objects[self.trade.other_player]) + trade_player.char.trade.server_trade_cancel() self.trade = None - def client_trade_accept(self, first:bool=False) -> None: + def on_client_trade_accept(self, first:bool=False) -> None: self.trade.accepted = not first - trade_player = server.game_objects[self.trade.other_player] - trade_player.char.server_trade_accept(first) + trade_player = cast(Player, server.game_objects[self.trade.other_player]) + trade_player.char.trade.server_trade_accept(first) @single def server_trade_cancel(self) -> None: @@ -86,6 +88,6 @@ class CharTrade: def server_trade_accept(self, first:bool=False) -> None: if not first: if self.trade.accepted: - trade_player = server.game_objects[self.trade.other_player] - trade_player.char.server_trade_update(True, 0, {}) + trade_player = cast(Player, server.game_objects[self.trade.other_player]) + trade_player.char.trade.server_trade_update(True, 0, {}) self.server_trade_update(True, 0, {}) diff --git a/luserver/components/char/ui.py b/luserver/components/char/ui.py index 44f5b72..0992b8e 100644 --- a/luserver/components/char/ui.py +++ b/luserver/components/char/ui.py @@ -1,7 +1,8 @@ from ...game_object import c_int, EB, EI, EL, EO, ES, GameObject, OBJ_NONE, single from ...ldf import LDF +from .subcomponent import CharSubcomponent -class CharUI: +class CharUI(CharSubcomponent): @single def display_message_box(self, show:bool=EB, callback_client:GameObject=EO, id:str=ES, image_id:c_int=EI, text:str=ES, user_data:str=ES) -> None: pass diff --git a/luserver/components/collectible.py b/luserver/components/collectible.py index aec9777..ce46161 100644 --- a/luserver/components/collectible.py +++ b/luserver/components/collectible.py @@ -12,6 +12,6 @@ class CollectibleComponent(Component): def serialize(self, out: WriteStream, is_creation: bool) -> None: out.write(c_ushort(self._collectible_id)) - def has_been_collected(self, player:Player=EP) -> None: + def on_has_been_collected(self, player:Player=EP) -> None: coll_id = self._collectible_id + (server.world_id[0] << 8) - player.char.update_mission_task(TaskType.Collect, self.object.lot, increment=coll_id) + player.char.mission.update_mission_task(TaskType.Collect, self.object.lot, increment=coll_id) diff --git a/luserver/components/comp108.py b/luserver/components/comp108.py index 0f07041..d8311d3 100644 --- a/luserver/components/comp108.py +++ b/luserver/components/comp108.py @@ -16,25 +16,21 @@ class Comp108Component(Component): self.driver_id = 0 def serialize(self, out: WriteStream, is_creation: bool) -> None: - out.write(c_bit(self.comp108_main_flag)) - if self.comp108_main_flag: - out.write(c_bit(self.driver_id_flag)) - if self.driver_id_flag: + if self.flag("comp108_main_flag", out): + if self.flag("driver_id_flag", out): out.write(c_int64(self.driver_id)) - self.driver_id_flag = False out.write(c_bit(False)) out.write(c_bit(False)) - self.comp108_main_flag = False def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: assert multi_interact_id is None player.char.mount(self.object) - player.char.disp_tooltip("Use /dismount to dismount.") + player.char.ui.disp_tooltip("Use /dismount to dismount.") def on_destruction(self) -> None: if self.driver_id != 0: cast(Player, server.game_objects[self.driver_id]).char.dismount() - def request_die(self, unknown_bool:bool=EB, death_type:str=ES, direction_relative_angle_xz:float=EF, direction_relative_angle_y:float=EF, direction_relative_force:float=EF, kill_type:c_int=0, killer:GameObject=EO, loot_owner:Player=EP) -> None: + def on_request_die(self, unknown_bool:bool=EB, death_type:str=ES, direction_relative_angle_xz:float=EF, direction_relative_angle_y:float=EF, direction_relative_force:float=EF, kill_type:c_int=0, killer:GameObject=EO, loot_owner:Player=EP) -> None: #self.object.destructible.deal_damage(10000, self) # die permanently on crash self.object.call_later(3, self.object.destructible.resurrect) diff --git a/luserver/components/component.py b/luserver/components/component.py index 6bb6a44..73db837 100644 --- a/luserver/components/component.py +++ b/luserver/components/component.py @@ -1,20 +1,17 @@ from abc import ABC, abstractmethod -from typing import Dict from pyraknet.bitstream import WriteStream -from ..game_object import Config, GameObject - -class Component(ABC): - def __setattr__(self, name: str, value: object) -> None: - self.attr_changed(name) - super().__setattr__(name, value) +from ..game_object import Config, FlagObject, GameObject +class Component(FlagObject, ABC): def __init__(self, obj: GameObject, set_vars: Config, comp_id: int): + super().__init__() self.object = obj - self._flags: Dict[str, str] = {} + for name in dir(self): + if name.startswith("on_"): + self.object.add_handler(name[3:], getattr(self, name)) def attr_changed(self, name: str) -> None: - """In case an attribute change is not registered by __setattr__ (like setting an attribute of an attribute), manually register the change by calling this. Without a registered change changes will not be broadcast to clients!""" if hasattr(self, "_flags") and name in self._flags: setattr(self, self._flags[name], hasattr(self, name)) self.object.signal_serialize() diff --git a/luserver/components/destructible.py b/luserver/components/destructible.py index 27f2fc9..97fa634 100644 --- a/luserver/components/destructible.py +++ b/luserver/components/destructible.py @@ -41,7 +41,7 @@ class DestructibleComponent(Component): out.write(c_bit(False)) def deal_damage(self, damage: int, dealer: GameObject) -> None: - self.object.handle("on_hit", damage, dealer, silent=True) + self.object.handle("hit", damage, dealer, silent=True) if damage > self.object.stats.armor: if damage >= self.object.stats.armor + self.object.stats.life: @@ -49,15 +49,15 @@ class DestructibleComponent(Component): loot_owner = dealer else: loot_owner = OBJ_NONE - self.request_die(unknown_bool=False, death_type="", direction_relative_angle_xz=0, direction_relative_angle_y=0, direction_relative_force=10, killer=dealer, loot_owner=loot_owner) + self.on_request_die(unknown_bool=False, death_type="", direction_relative_angle_xz=0, direction_relative_angle_y=0, direction_relative_force=10, killer=dealer, loot_owner=loot_owner) 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=OBJ_NONE, loot_owner:Player=OBJ_NONE) -> None: """Shorthand for request_die with default values.""" - self.request_die(False, death_type, 0, 0, 10, kill_type, killer, loot_owner) + self.on_request_die(False, death_type, 0, 0, 10, kill_type, killer, loot_owner) - def request_die(self, unknown_bool:bool=EB, death_type:str=ES, direction_relative_angle_xz:float=EF, direction_relative_angle_y:float=EF, direction_relative_force:float=EF, kill_type:c_int=KillType.Violent, killer:GameObject=EO, loot_owner:Player=EP) -> None: + def on_request_die(self, unknown_bool:bool=EB, death_type:str=ES, direction_relative_angle_xz:float=EF, direction_relative_angle_y:float=EF, direction_relative_force:float=EF, kill_type:c_int=KillType.Violent, killer:GameObject=EO, loot_owner:Player=EP) -> None: if self.object.stats.life == 0: # already dead return @@ -68,15 +68,15 @@ class DestructibleComponent(Component): if self.object.stats.imagination != 0: self.object.stats.imagination = 0 - self.object.send_game_message("die", False, True, death_type, direction_relative_angle_xz, direction_relative_angle_y, direction_relative_force, kill_type, killer, loot_owner) + self.object.stats.die(False, True, death_type, direction_relative_angle_xz, direction_relative_angle_y, direction_relative_force, kill_type, killer, loot_owner) if killer and hasattr(killer, "char"): - killer.char.update_mission_task(TaskType.KillEnemy, self.object.lot) + killer.char.mission.update_mission_task(TaskType.KillEnemy, self.object.lot) if loot_owner and hasattr(loot_owner, "char"): self.object.physics.drop_rewards(*self.death_rewards, loot_owner) - if hasattr(self.object, "char"): + if isinstance(self.object, Player): if server.world_id[0] % 100 == 0: coins_lost = min(10000, self.object.char.currency//100) self.object.char.set_currency(currency=self.object.char.currency - coins_lost, loot_type=8, position=Vector3.zero) diff --git a/luserver/components/exhibit.py b/luserver/components/exhibit.py index 32bcd75..880d2ab 100644 --- a/luserver/components/exhibit.py +++ b/luserver/components/exhibit.py @@ -1,6 +1,6 @@ import random -from pyraknet.bitstream import c_bit, c_int, WriteStream +from pyraknet.bitstream import c_int, WriteStream from ..game_object import Config, GameObject from .component import Component @@ -17,7 +17,5 @@ class ExhibitComponent(Component): self.object.call_later(_CYCLE_INTERVAL, self._random_exhibit) def serialize(self, out: WriteStream, is_creation: bool) -> None: - out.write(c_bit(self._exhibit_flag or is_creation)) - if self._exhibit_flag or is_creation: + if self.flag("_exhibit_flag", out, is_creation): out.write(c_int(self._exhibited_lot)) - self._exhibit_flag = False diff --git a/luserver/components/inventory.py b/luserver/components/inventory.py index c8f1d34..866584d 100644 --- a/luserver/components/inventory.py +++ b/luserver/components/inventory.py @@ -142,16 +142,13 @@ class InventoryComponent(Component): for item_lot, equip in server.db.inventory_component[comp_id]: item = self.add_item(item_lot, persistent=False, notify_client=False) if equip: - self.equip_inventory(item_to_equip=item.object_id) + self.on_equip_inventory(item_to_equip=item.object_id) def serialize(self, out: WriteStream, is_creation: bool) -> None: - out.write(c_bit(is_creation or self.equipped_items_flag)) - if is_creation or self.equipped_items_flag: + if self.flag("equipped_items_flag", out, is_creation): out.write(c_uint(len(self.equipped[-1]))) for item in self.equipped[-1]: - item.serialize(out) - if not is_creation: - self.equipped_items_flag = False + out.write(item) out.write(c_bit(False)) def inventory_type_to_inventory(self, inventory_type: int) -> List[Optional[Stack]]: @@ -175,7 +172,7 @@ class InventoryComponent(Component): self.equipped.append(self.equipped[-1].copy()) self.attr_changed("equipped") - def pop_equipped_items_state(self) -> None: + def on_pop_equipped_items_state(self) -> None: if len(self.equipped) == 1: return @@ -191,11 +188,11 @@ class InventoryComponent(Component): if equipped_item.item_type == prev_equipped_item.item_type: if equipped_item.lot != prev_equipped_item.lot: # item has been replaced, equip old item - self.equip_inventory(item_to_equip=prev_equipped_item.object_id) + self.on_equip_inventory(item_to_equip=prev_equipped_item.object_id) break else: # item has been unequipped without replacement, equip again - self.equip_inventory(item_to_equip=prev_equipped_item.object_id) + self.on_equip_inventory(item_to_equip=prev_equipped_item.object_id) # check for newly equipped items that weren't equipped previously for equipped_item in self.equipped[-1]: @@ -204,12 +201,12 @@ class InventoryComponent(Component): break else: # item should be unequipped - self.un_equip_inventory(item_to_unequip=equipped_item.object_id) + self.on_un_equip_inventory(item_to_unequip=equipped_item.object_id) del self.equipped[-2] self.attr_changed("equipped") - def move_item_in_inventory(self, dest_inventory_type:c_int_=0, object_id:c_int64_=EI, inventory_type:c_int_=EI, response_code:c_int_=EI, slot:c_int_=EI) -> None: + def on_move_item_in_inventory(self, dest_inventory_type:c_int_=0, object_id:c_int64_=EI, inventory_type:c_int_=EI, response_code:c_int_=EI, slot:c_int_=EI) -> None: assert dest_inventory_type == 0 assert object_id != 0 assert response_code == 0 @@ -232,7 +229,7 @@ class InventoryComponent(Component): if hasattr(self.object, "char"): - self.object.char.update_mission_task(TaskType.ObtainItem, lot, increment=count) + self.object.char.mission.update_mission_task(TaskType.ObtainItem, lot, increment=count) if inventory_type is None: if item_type == ItemType.Brick: @@ -356,7 +353,7 @@ class InventoryComponent(Component): assert item.count >= 0 if item.count == 0: # delete item if item in self.equipped[-1]: - self.un_equip_inventory(item_to_unequip=item.object_id) + self.on_un_equip_inventory(item_to_unequip=item.object_id) if inventory_type in (InventoryType.Bricks, InventoryType.TempItems, InventoryType.TempModels, InventoryType.MissionObjects): inventory.remove(item) @@ -367,7 +364,7 @@ class InventoryComponent(Component): self.add_item(module_lot) return last_affected_item - def equip_inventory(self, ignore_cooldown:bool=False, out_success:bool=False, item_to_equip:c_int64_=EI) -> None: + def on_equip_inventory(self, ignore_cooldown:bool=False, out_success:bool=False, item_to_equip:c_int64_=EI) -> None: assert not out_success for inv in (self.items, self.temp_items, self.models): for item in inv: @@ -401,12 +398,12 @@ class InventoryComponent(Component): # equip sub-items for sub_item in item.sub_items: sub = self.add_item(sub_item, inventory_type=InventoryType.TempItems) - self.equip_inventory(item_to_equip=sub.object_id) + self.on_equip_inventory(item_to_equip=sub.object_id) # unequip any items of the same type for other_item in self.equipped[-1]: if other_item.item_type == item.item_type and other_item.object_id != item.object_id: - self.un_equip_inventory(item_to_unequip=other_item.object_id) + self.on_un_equip_inventory(item_to_unequip=other_item.object_id) break # if this is a rocket, check for launchpads nearby, and possibly activate the launch sequence @@ -418,7 +415,7 @@ class InventoryComponent(Component): break return - def un_equip_inventory(self, even_if_dead:bool=False, ignore_cooldown:bool=False, out_success:bool=False, item_to_unequip:c_int64_=EI, replacement_object_id:c_int64_=0) -> None: + def on_un_equip_inventory(self, even_if_dead:bool=False, ignore_cooldown:bool=False, out_success:bool=False, item_to_unequip:c_int64_=EI, replacement_object_id:c_int64_=0) -> None: assert not out_success assert replacement_object_id == 0 for item in self.equipped[-1]: @@ -455,7 +452,7 @@ class InventoryComponent(Component): # if this is a sub-item of another item, unequip the other item (and its sub-items) for other_item in self.equipped[-1]: if item.lot in other_item.sub_items: - self.un_equip_inventory(item_to_unequip=other_item.object_id) + self.on_un_equip_inventory(item_to_unequip=other_item.object_id) break @broadcast @@ -463,7 +460,7 @@ class InventoryComponent(Component): inv = self.inventory_type_to_inventory(inventory_type) inv.extend([None] * (size - len(inv))) - def move_item_between_inventory_types(self, inventory_type_a:c_int_=EI, inventory_type_b:c_int_=EI, object_id:c_int64_=EI, show_flying_loot:bool=True, stack_count:c_uint_=1, template_id:c_int_=-1) -> Stack: + def on_move_item_between_inventory_types(self, inventory_type_a:c_int_=EI, inventory_type_b:c_int_=EI, object_id:c_int64_=EI, show_flying_loot:bool=True, stack_count:c_uint_=1, template_id:c_int_=-1) -> Stack: source = self.inventory_type_to_inventory(inventory_type_a) for item in source: if item is not None and (item.object_id == object_id or item.lot == template_id): diff --git a/luserver/components/launchpad.py b/luserver/components/launchpad.py index c240f0b..1b951d6 100644 --- a/luserver/components/launchpad.py +++ b/luserver/components/launchpad.py @@ -27,7 +27,7 @@ class LaunchpadComponent(Component): assert multi_interact_id is None if server.db.config["enabled_worlds"] and self._target_world not in server.db.config["enabled_worlds"]: - player.char.disp_tooltip("This world is currently disabled.") + player.char.ui.disp_tooltip("This world is currently disabled.") return for model in player.inventory.models: @@ -35,13 +35,13 @@ class LaunchpadComponent(Component): self.launch(player, model) break else: - player.char.disp_tooltip("You don't have a rocket!") + player.char.ui.disp_tooltip("You don't have a rocket!") def launch(self, player: Player, rocket: Stack) -> None: player.char.traveling_rocket = rocket.module_lots self.fire_event_client_side(args="RocketEquipped", obj=rocket, sender=player) - def fire_event_server_side(self, player: Player, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:GameObject=EO) -> None: + def on_fire_event_server_side(self, player: Player, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:GameObject=EO) -> None: if args == "ZonePlayer": if param2: param3 = self._default_world_id diff --git a/luserver/components/minigame.py b/luserver/components/minigame.py index 26abb50..fa10f8d 100644 --- a/luserver/components/minigame.py +++ b/luserver/components/minigame.py @@ -7,6 +7,8 @@ class MinigameComponent(Component): def serialize(self, out: WriteStream, is_creation: bool) -> None: pass - @single def player_ready(self) -> None: pass + + player_ready = single(player_ready) + on_player_ready = player_ready diff --git a/luserver/components/mission.py b/luserver/components/mission.py index 3f72a84..b7f58a6 100644 --- a/luserver/components/mission.py +++ b/luserver/components/mission.py @@ -33,7 +33,7 @@ class ObtainItemType: RemoveOnComplete = 2 from persistent import Persistent -from .commonserver import MissionData +from ..commonserver import MissionData class MissionTask(Persistent): def __init__(self, task_type: int, target: int, target_value: int, parameter): @@ -82,7 +82,7 @@ def check_prereqs(mission_id: int, player: Player) -> bool: prereq_mission, prereq_mission_state = prereq_mission else: prereq_mission_state = MissionState.Completed - if prereq_mission in player.char.missions and player.char.missions[prereq_mission].state == prereq_mission_state: + if prereq_mission in player.char.mission.missions and player.char.mission.missions[prereq_mission].state == prereq_mission_state: break # an element was found, this prereq_ors is satisfied else: break # no elements found, not satisfied, checking further prereq_ors unnecessary @@ -106,8 +106,8 @@ class MissionNPCComponent(Component): offer = multi_interact_id else: for mission_id, offers_mission, accepts_mission in self.missions: - if mission_id in player.char.missions: - if accepts_mission and player.char.missions[mission_id].state == MissionState.Active: + if mission_id in player.char.mission.missions: + if accepts_mission and player.char.mission.missions[mission_id].state == MissionState.Active: log.debug("mission %i in progress, offering", mission_id) offer = mission_id break @@ -120,7 +120,7 @@ class MissionNPCComponent(Component): if player.object_id not in self.random_mission_choices: eligible_missions = [] for random_mission_id in random_pool: - if random_mission_id not in player.char.missions: + if random_mission_id not in player.char.mission.missions: eligible_missions.append(random_mission_id) if not eligible_missions: continue @@ -130,7 +130,7 @@ class MissionNPCComponent(Component): self.random_mission_choices[player.object_id] = offer # todo: fix this # disabling for now because handlers accidentally get saved to DB - #player.add_handler("on_destruction", self.clear_random_missions) + #player.add_handler("destruction", self.clear_random_missions) else: offer = self.random_mission_choices[player.object_id] log.debug("choosing saved random mission %i", offer) @@ -140,7 +140,7 @@ class MissionNPCComponent(Component): if offer is not None: log.debug("offering %i", offer) self.offer_mission(offer, offerer=self.object, player=player) - player.char.offer_mission(offer, offerer=self.object) + player.char.mission.offer_mission(offer, offerer=self.object) return offer is not None @@ -148,16 +148,16 @@ class MissionNPCComponent(Component): def offer_mission(self, mission_id:c_int=EI, offerer:GameObject=EO) -> None: pass - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP) -> None: + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP) -> None: if mission_state == MissionState.Available: assert not is_complete - player.char.add_mission(mission_id) + player.char.mission.add_mission(mission_id) elif mission_state == MissionState.ReadyToComplete: assert is_complete - player.char.complete_mission(mission_id) + player.char.mission.complete_mission(mission_id) self.clear_random_missions(player) - def request_linked_mission(self, player:Player=EP, mission_id:c_int=EI, mission_offered:bool=EB) -> None: + def on_request_linked_mission(self, player:Player=EP, mission_id:c_int=EI, mission_offered:bool=EB) -> None: self.on_use(player, None) def clear_random_missions(self, player: Player) -> None: diff --git a/luserver/components/modular_build.py b/luserver/components/modular_build.py index f6ec256..3f810c8 100644 --- a/luserver/components/modular_build.py +++ b/luserver/components/modular_build.py @@ -8,7 +8,7 @@ class ModularBuildComponent(Component): def serialize(self, out: WriteStream, is_creation: bool) -> None: pass - def start_building_with_item(self, player: Player, first_time:bool=True, success:bool=EB, source_bag:c_int=EI, source_id:c_int64=EI, source_lot:c_int=EI, source_type:c_int=EI, target_id:c_int64=EI, target_lot:c_int=EI, target_pos:Vector3=EV, target_type:c_int=EI) -> None: + def on_start_building_with_item(self, player: Player, first_time:bool=True, success:bool=EB, source_bag:c_int=EI, source_id:c_int64=EI, source_lot:c_int=EI, source_type:c_int=EI, target_id:c_int64=EI, target_lot:c_int=EI, target_pos:Vector3=EV, target_type:c_int=EI) -> None: # source is item used for starting, target is module dragged on assert first_time assert not success @@ -17,15 +17,15 @@ class ModularBuildComponent(Component): player.char.start_arranging_with_item(first_time, self.object, player.physics.position, source_bag, source_id, source_lot, source_type, target_id, target_lot, target_pos, target_type) - def done_arranging_with_item(self, player: Player, new_source_bag:c_int=EI, new_source_id:c_int64=EI, new_source_lot:c_int=EI, new_source_type:c_int=EI, new_target_id:c_int64=EI, new_target_lot:c_int=EI, new_target_type:c_int=EI, new_target_pos:Vector3=EV, old_item_bag:c_int=EI, old_item_id:c_int64=EI, old_item_lot:c_int=EI, old_item_type:c_int=EI) -> None: + def on_done_arranging_with_item(self, player: Player, new_source_bag:c_int=EI, new_source_id:c_int64=EI, new_source_lot:c_int=EI, new_source_type:c_int=EI, new_target_id:c_int64=EI, new_target_lot:c_int=EI, new_target_type:c_int=EI, new_target_pos:Vector3=EV, old_item_bag:c_int=EI, old_item_id:c_int64=EI, old_item_lot:c_int=EI, old_item_type:c_int=EI) -> None: for model in player.inventory.temp_models.copy(): - player.inventory.move_item_between_inventory_types(inventory_type_a=InventoryType.TempModels, inventory_type_b=InventoryType.Models, object_id=model.object_id, stack_count=0) + player.inventory.on_move_item_between_inventory_types(inventory_type_a=InventoryType.TempModels, inventory_type_b=InventoryType.Models, object_id=model.object_id, stack_count=0) - def modular_build_move_and_equip(self, player: Player, template_id:c_int=EI) -> None: - new_item = player.inventory.move_item_between_inventory_types(inventory_type_a=InventoryType.TempModels, inventory_type_b=InventoryType.Models, object_id=0, template_id=template_id) - player.inventory.equip_inventory(item_to_equip=new_item.object_id) + def on_modular_build_move_and_equip(self, player: Player, template_id:c_int=EI) -> None: + new_item = player.inventory.on_move_item_between_inventory_types(inventory_type_a=InventoryType.TempModels, inventory_type_b=InventoryType.Models, object_id=0, template_id=template_id) + player.inventory.on_equip_inventory(item_to_equip=new_item.object_id) - def modular_build_finish(self, player: Player, module_lots:Sequence[c_ubyte, c_int]=E) -> None: + def on_modular_build_finish(self, player: Player, module_lots:Sequence[c_ubyte, c_int]=E) -> None: for model in player.inventory.temp_models.copy(): if model.lot in module_lots: player.inventory.remove_item(InventoryType.TempModels, model) @@ -36,7 +36,7 @@ class ModularBuildComponent(Component): player.inventory.add_item(6416, module_lots=module_lots) # modular rocket player.char.finish_arranging_with_item(new_source_bag=0, new_source_id=0, new_source_lot=-1, new_source_type=0, new_target_id=0, new_target_lot=-1, new_target_type=0, new_target_pos=Vector3.zero, old_item_bag=0, old_item_id=0, old_item_lot=-1, old_item_type=0) - def modular_build_convert_model(self, player: Player, model_id:c_int64=EI) -> None: + def on_modular_build_convert_model(self, player: Player, model_id:c_int64=EI) -> None: for model in player.inventory.models: if model is not None and model.object_id == model_id: for module_lot in model.module_lots: diff --git a/luserver/components/moving_platform.py b/luserver/components/moving_platform.py index beb5649..1d1c7fb 100644 --- a/luserver/components/moving_platform.py +++ b/luserver/components/moving_platform.py @@ -43,8 +43,6 @@ class MovingPlatformComponent(Component): self.next_waypoint_index = 1 self.callbacks: List[CallbackID] = [] self.no_autostart = False - self.object.add_handler("rebuild_init", self._on_rebuild_init) - self.object.add_handler("complete_rebuild", self._on_rebuild_complete) if "attached_path" in set_vars: self.path = Path(*server.world_data.paths[set_vars["attached_path"]]) @@ -72,10 +70,10 @@ class MovingPlatformComponent(Component): self.moving_platform_flag = False - def _on_rebuild_init(self, _obj: GameObject) -> None: + def on_rebuild_init(self) -> None: self.stop_pathing() - def _on_rebuild_complete(self, _obj: GameObject, player: Player) -> None: + def on_complete_rebuild(self, player: Player) -> None: if not self.no_autostart: self.start_pathing() diff --git a/luserver/components/pet.py b/luserver/components/pet.py index 3f6965e..528ad87 100644 --- a/luserver/components/pet.py +++ b/luserver/components/pet.py @@ -29,10 +29,10 @@ class PetComponent(Component): def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: assert multi_interact_id is None - player.char.notify_pet_taming_minigame(pet=self.object, player_taming=OBJ_NONE, force_teleport=True, notify_type=PetTamingNotify.Begin, pets_dest_pos=self.object.physics.position, tele_pos=player.physics.position, tele_rot=player.physics.rotation) - player.char.notify_pet_taming_puzzle_selected(bricks=[30367, 21, 48729, 1, 6141, 1, 6143, 21]) + player.char.pet.notify_pet_taming_minigame(pet=self.object, player_taming=OBJ_NONE, force_teleport=True, notify_type=PetTamingNotify.Begin, pets_dest_pos=self.object.physics.position, tele_pos=player.physics.position, tele_rot=player.physics.rotation) + player.char.pet.notify_pet_taming_puzzle_selected(bricks=[30367, 21, 48729, 1, 6141, 1, 6143, 21]) #self.flags = 80 - def pet_taming_minigame_result(self, player: Player, success:bool=EB) -> None: + def on_pet_taming_minigame_result(self, player: Player, success:bool=EB) -> None: if success: - player.char.update_mission_task(TaskType.TamePet, self.object.lot) + player.char.mission.update_mission_task(TaskType.TamePet, self.object.lot) diff --git a/luserver/components/physics.py b/luserver/components/physics.py index 8e00829..8c0e170 100644 --- a/luserver/components/physics.py +++ b/luserver/components/physics.py @@ -80,45 +80,35 @@ class Controllable(PhysicsComponent): self.deeper_unknown_float3 = 0, 0, 0 def serialize(self, out: WriteStream, is_creation: bool) -> None: - out.write(c_bit(self.physics_data_flag or is_creation)) - if self.physics_data_flag or is_creation: + if self.flag("physics_data_flag", out, is_creation): out.write(self.position) out.write(self.rotation) out.write(c_bit(self.on_ground)) out.write(c_bit(self.unknown_bool)) - out.write(c_bit(self.velocity_flag)) - if self.velocity_flag: + if self.flag("velocity_flag", out): out.write(self.velocity) - self.velocity_flag = False - out.write(c_bit(self.angular_velocity_flag)) - if self.angular_velocity_flag: + if self.flag("angular_velocity_flag", out): out.write(c_float(self.angular_velocity[0])) out.write(c_float(self.angular_velocity[1])) out.write(c_float(self.angular_velocity[2])) - self.angular_velocity_flag = False - out.write(c_bit(self.unknown_flag)) - if self.unknown_flag: + if self.flag("unknown_flag", out): out.write(c_int64(self.unknown_object_id)) out.write(c_float(self.unknown_float3[0])) out.write(c_float(self.unknown_float3[1])) out.write(c_float(self.unknown_float3[2])) - out.write(c_bit(self.deeper_unknown_flag)) - if self.deeper_unknown_flag: + if self.flag("deeper_unknown_flag", out): out.write(c_float(self.deeper_unknown_float3[0])) out.write(c_float(self.deeper_unknown_float3[1])) out.write(c_float(self.deeper_unknown_float3[2])) - self.deeper_unknown_flag = False - self.unknown_flag = False self.write_vehicle_stuff(out, is_creation) if not is_creation: out.write(c_bit(False)) - self.physics_data_flag = False def write_vehicle_stuff(self, out: WriteStream, is_creation: bool) -> None: pass # hook for vehiclephysics @@ -146,19 +136,15 @@ class SimplePhysicsComponent(PhysicsComponent): out.write(c_float(0)) out.write(c_bit(False)) out.write(c_bit(False)) - out.write(c_bit(self.physics_data_flag or is_creation)) - if self.physics_data_flag or is_creation: + if self.flag("physics_data_flag", out, is_creation): out.write(self.position) out.write(self.rotation) - self.physics_data_flag = False class RigidBodyPhantomPhysicsComponent(PhysicsComponent): def serialize(self, out: WriteStream, is_creation: bool) -> None: - out.write(c_bit(self.physics_data_flag or is_creation)) - if self.physics_data_flag or is_creation: + if self.flag("physics_data_flag", out, is_creation): out.write(self.position) out.write(self.rotation) - self.physics_data_flag = False class VehiclePhysicsComponent(Controllable): def serialize(self, out: WriteStream, is_creation: bool) -> None: @@ -272,14 +258,11 @@ class PhantomPhysicsComponent(PhysicsComponent): self.respawn_data = set_vars["respawn_data"] def serialize(self, out: WriteStream, is_creation: bool) -> None: - out.write(c_bit(self.physics_data_flag or is_creation)) - if self.physics_data_flag or is_creation: + if self.flag("physics_data_flag", out, is_creation): out.write(self.position) out.write(self.rotation) - self.physics_data_flag = False - out.write(c_bit(self.physics_effect_flag or is_creation)) - if self.physics_effect_flag or is_creation: + if self.flag("physics_effect_flag", out, is_creation): out.write(c_bit(self.physics_effect_active)) if self.physics_effect_active: out.write(c_uint(self.physics_effect_type)) @@ -287,7 +270,6 @@ class PhantomPhysicsComponent(PhysicsComponent): out.write(c_bit(False)) out.write(c_bit(True)) out.write(self.physics_effect_direction) - self.physics_effect_flag = False def on_startup(self) -> None: if self.object.lot in _MODEL_DIMENSIONS or (hasattr(self.object, "primitive_model_type") and self.object.primitive_model_type in (PrimitiveModelType.Cuboid, PrimitiveModelType.Cylinder)): diff --git a/luserver/components/property.py b/luserver/components/property.py index 3bb5e2c..2ab1e06 100644 --- a/luserver/components/property.py +++ b/luserver/components/property.py @@ -159,7 +159,7 @@ class PropertyEntranceComponent(Component): player.char.u_i_message_server_to_single_client(message_name=b"pushGameState", args=AMF3({"state": "property_menu"})) return True - def enter_property1(self, player: Player, index:c_int_=EI, return_to_zone:bool=True) -> None: + def on_enter_property1(self, player: Player, index:c_int_=EI, return_to_zone:bool=True) -> None: clone_id = 0 if not return_to_zone and index == -1: clone_id = player.char.clone_id @@ -169,7 +169,7 @@ class PropertyEntranceComponent(Component): self.fire_event_client_side(args="RocketEquipped", obj=model, sender=player, param1=clone_id) break - def property_entrance_sync(self, player: Player, include_null_address:bool=EB, include_null_description:bool=EB, players_own:bool=EB, update_ui:bool=EB, num_results:c_int_=EI, reputation_time:c_int_=EI, sort_method:c_int_=EI, start_index:c_int_=EI, filter_text:bytes=EBY) -> None: + def on_property_entrance_sync(self, player: Player, include_null_address:bool=EB, include_null_description:bool=EB, players_own:bool=EB, update_ui:bool=EB, num_results:c_int_=EI, reputation_time:c_int_=EI, sort_method:c_int_=EI, start_index:c_int_=EI, filter_text:bytes=EBY) -> None: my_property = PropertySelectQueryProperty() #my_property.clone_id = player.char.clone_id #my_property.is_owned = True @@ -207,7 +207,7 @@ class PropertyManagementComponent(Component): self.download_property_data(property, player=player) - def start_building_with_item(self, player: Player, first_time:bool=True, success:bool=EB, source_bag:c_int_=EI, source_id:c_int64_=EI, source_lot:c_int_=EI, source_type:c_int_=EI, target_id:c_int64_=EI, target_lot:c_int_=EI, target_pos:Vector3=EV, target_type:c_int_=EI) -> None: + def on_start_building_with_item(self, player: Player, first_time:bool=True, success:bool=EB, source_bag:c_int_=EI, source_id:c_int64_=EI, source_lot:c_int_=EI, source_type:c_int_=EI, target_id:c_int64_=EI, target_lot:c_int_=EI, target_pos:Vector3=EV, target_type:c_int_=EI) -> None: # source is item used for starting, target is module dragged on assert first_time assert not success @@ -216,7 +216,7 @@ class PropertyManagementComponent(Component): player.char.start_arranging_with_item(first_time, self.object, player.physics.position, source_bag, source_id, source_lot, source_type, target_id, target_lot, target_pos, target_type) - def set_build_mode(self, start:bool=EB, distance_type:c_int_=-1, mode_paused:bool=False, mode_value:c_int_=1, player_id:c_int64_=EI, start_pos:Vector3=Vector3.zero) -> None: + def on_set_build_mode(self, start:bool=EB, distance_type:c_int_=-1, mode_paused:bool=False, mode_value:c_int_=1, player_id:c_int64_=EI, start_pos:Vector3=Vector3.zero) -> None: server.world_control_object.script.on_build_mode(start) self.set_build_mode_confirmed(start, False, mode_paused, mode_value, player_id, start_pos) @@ -249,7 +249,7 @@ class PropertyVendorComponent(Component): def open_property_vendor(self) -> None: pass - def buy_from_vendor(self, player: Player, confirmed:bool=False, count:c_int_=1, item:c_int_=EI) -> None: + def on_buy_from_vendor(self, player: Player, confirmed:bool=False, count:c_int_=1, item:c_int_=EI) -> None: # seems to actually add a 3188 property item to player's inventory? self.property_rental_response(clone_id=0, code=0, property_id=0, rentdue=0, player=player) # not really implemented player.char.set_flag(True, 108) diff --git a/luserver/components/racing_control.py b/luserver/components/racing_control.py index 9570220..f751d0a 100644 --- a/luserver/components/racing_control.py +++ b/luserver/components/racing_control.py @@ -26,8 +26,7 @@ class RacingControlComponent(ScriptedActivityComponent): super().serialize(out, is_creation) out.write(c_bit(True)) out.write(c_ushort(2)) - out.write(c_bit(self.player_data_flag)) - if self.player_data_flag: + if self.flag("player_data_flag", out): index = 0 for player, data in self.player_data.items(): out.write(c_bit(True)) @@ -37,7 +36,6 @@ class RacingControlComponent(ScriptedActivityComponent): out.write(c_bit(False)) index += 1 out.write(c_bit(False)) - self.player_data_flag = False out.write(c_bit(True)) out.write("MainPath", length_type=c_ushort) diff --git a/luserver/components/rebuild.py b/luserver/components/rebuild.py index 41e3e82..88e9538 100644 --- a/luserver/components/rebuild.py +++ b/luserver/components/rebuild.py @@ -2,7 +2,7 @@ import time from typing import List, Optional from pyraknet.bitstream import c_bit, c_float, c_uint, WriteStream -from ..game_object import broadcast, CallbackID, Config, EB, EF, EI, EO, EP, GameObject, Player, StatsObject +from ..game_object import broadcast, CallbackID, Config, EB, EF, EI, EO, EP, GameObject, OBJ_NONE, Player, StatsObject from ..game_object import c_int as c_int_ from ..game_object import c_uint as c_uint_ from ..world import server @@ -40,9 +40,9 @@ class RebuildComponent(ScriptedActivityComponent): self.callback_handles: List[CallbackID] = [] self.rebuild_start_time: float = 0 self.last_progress: float = 0 - self._flags["rebuild_state"] = "rebuild_flag" - self._flags["success"] = "rebuild_flag" - self._flags["enabled"] = "rebuild_flag" + self._flags["_rebuild_state"] = "_rebuild_flag" + self._flags["success"] = "_rebuild_flag" + self._flags["enabled"] = "_rebuild_flag" self._rebuild_state = RebuildState.Open self.success = False self.enabled = True @@ -70,8 +70,7 @@ class RebuildComponent(ScriptedActivityComponent): def serialize(self, out: WriteStream, is_creation: bool) -> None: super().serialize(out, is_creation) - out.write(c_bit(self.rebuild_flag or is_creation)) - if self.rebuild_flag or is_creation: + if self.flag("_rebuild_flag", out, is_creation): out.write(c_uint(self.rebuild_state)) out.write(c_bit(self.success)) out.write(c_bit(self.enabled)) @@ -84,7 +83,6 @@ class RebuildComponent(ScriptedActivityComponent): out.write(c_bit(False)) out.write(self.rebuild_activator_position) out.write(c_bit(True)) - self.rebuild_flag = False def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: assert multi_interact_id is None @@ -108,10 +106,10 @@ class RebuildComponent(ScriptedActivityComponent): def _drain_imagination(self, player: Player) -> None: if player.stats.imagination == 0: - self.rebuild_cancel(early_release=False, user=player) + self.on_rebuild_cancel(early_release=False, user=player) player.stats.imagination -= 1 - def complete_rebuild(self, player: Player) -> None: + def on_complete_rebuild(self, player: Player) -> None: self.rebuild_state = RebuildState.Completed self.success = True self.enabled = False @@ -127,16 +125,16 @@ class RebuildComponent(ScriptedActivityComponent): break self.callback_handles.append(self.object.call_later(self.smash_time, self.smash_rebuild)) - player.char.update_mission_task(TaskType.QuickBuild, self.activity_id) + player.char.mission.update_mission_task(TaskType.QuickBuild, self.activity_id) # drop rewards self.object.physics.drop_rewards(*self.completion_rewards, player) def smash_rebuild(self) -> None: - self.object.stats.die(death_type="", direction_relative_angle_xz=0, direction_relative_angle_y=0, direction_relative_force=10, killer=None) + self.object.stats.die(death_type="", direction_relative_angle_xz=0, direction_relative_angle_y=0, direction_relative_force=10, killer=OBJ_NONE) server.replica_manager.destruct(self.object) - def rebuild_cancel(self, early_release:bool=EB, user:Player=EP) -> None: + def on_rebuild_cancel(self, early_release:bool=EB, user:Player=EP) -> None: if self.rebuild_state == RebuildState.Building: for handle in self.callback_handles: self.object.cancel_callback(handle) diff --git a/luserver/components/scripted_activity.py b/luserver/components/scripted_activity.py index d0b3792..0060452 100644 --- a/luserver/components/scripted_activity.py +++ b/luserver/components/scripted_activity.py @@ -1,7 +1,7 @@ import asyncio from typing import Dict, List -from pyraknet.bitstream import c_bit, c_float, c_int64, c_uint, WriteStream +from pyraknet.bitstream import c_float, c_int64, c_uint, WriteStream from ..game_object import broadcast, c_int, Config, EB, EBY, EI, EL, ES, EO, GameObject, ObjectID, Player, single from ..world import server from ..ldf import LDF @@ -23,14 +23,12 @@ class ScriptedActivityComponent(Component): self.transfer_world_id = None def serialize(self, out: WriteStream, is_creation: bool) -> None: - out.write(c_bit(self.activity_flag)) - if self.activity_flag: + if self.flag("activity_flag", out): out.write(c_uint(len(self.activity_values))) for object_id, values in self.activity_values.items(): out.write(c_int64(object_id)) for value in values: out.write(c_float(value)) - self.activity_flag = False def add_player(self, player: Player) -> None: self.activity_values[player.object_id] = [0]*10 @@ -44,7 +42,7 @@ class ScriptedActivityComponent(Component): def activity_start(self) -> None: pass - def message_box_respond(self, player: Player, button:c_int=EI, id:str=ES, user_data:str=ES) -> None: + def on_message_box_respond(self, player: Player, button:c_int=EI, id:str=ES, user_data:str=ES) -> None: if id == "LobbyReady" and button == 1: asyncio.ensure_future(player.char.transfer_to_world((self.transfer_world_id, 0, 0))) diff --git a/luserver/components/skill.py b/luserver/components/skill.py index bbe32a2..dbf4c5c 100644 --- a/luserver/components/skill.py +++ b/luserver/components/skill.py @@ -163,7 +163,7 @@ class SkillComponent(Component): bitstream = WriteStream() behavior = server.db.skill_behavior[skill_id][0] self.serialize_behavior(behavior, bitstream, target) - self.start_skill(skill_id=skill_id, cast_type=cast_type, optional_target_id=target.object_id, ui_skill_handle=self.last_ui_skill_handle, optional_originator_id=0, originator_rot=Quaternion(0, 0, 0, 0), bitstream=bytes(bitstream)) + self.on_start_skill(skill_id=skill_id, cast_type=cast_type, optional_target_id=target.object_id, ui_skill_handle=self.last_ui_skill_handle, optional_originator_id=0, originator_rot=Quaternion(0, 0, 0, 0), bitstream=bytes(bitstream)) def cast_sync_skill(self, delay: float, behavior, target: GameObject) -> int: ui_behavior_handle = self.last_ui_handle @@ -173,7 +173,7 @@ class SkillComponent(Component): bitstream = WriteStream() self.serialize_behavior(behavior, bitstream, target) - self.object.call_later(delay, lambda: self.sync_skill(bitstream=bytes(bitstream), ui_behavior_handle=ui_behavior_handle, ui_skill_handle=self.last_ui_skill_handle)) + self.object.call_later(delay, lambda: self.on_sync_skill(bitstream=bytes(bitstream), ui_behavior_handle=ui_behavior_handle, ui_skill_handle=self.last_ui_skill_handle)) return ui_behavior_handle def cast_projectile(self, proj_behavs, target: GameObject) -> ObjectID: @@ -183,14 +183,14 @@ class SkillComponent(Component): self.original_target_id = target.object_id self.serialize_behavior(behav, bitstream, target) delay = 1 - self.object.call_later(delay, lambda: self.request_server_projectile_impact(proj_id, target.object_id, bytes(bitstream))) + self.object.call_later(delay, lambda: self.on_request_server_projectile_impact(proj_id, target.object_id, bytes(bitstream))) return proj_id @broadcast def echo_start_skill(self, used_mouse:bool=False, caster_latency:float=0, cast_type:c_int=0, last_clicked_posit:Vector3=Vector3.zero, optional_originator_id:c_int64=EI, optional_target_id:c_int64=0, originator_rot:Quaternion=Quaternion.identity, bitstream:bytes=EBY, skill_id:c_uint_=EI, ui_skill_handle:c_uint_=0) -> None: pass - def start_skill(self, used_mouse:bool=False, consumable_item_id:c_int64=0, caster_latency:float=0, cast_type:c_int=0, last_clicked_posit:Vector3=Vector3.zero, optional_originator_id:c_int64=EI, optional_target_id:c_int64=0, originator_rot:Quaternion=Quaternion.identity, bitstream:bytes=EBY, skill_id:c_uint_=EI, ui_skill_handle:c_uint_=0) -> None: + def on_start_skill(self, used_mouse:bool=False, consumable_item_id:c_int64=0, caster_latency:float=0, cast_type:c_int=0, last_clicked_posit:Vector3=Vector3.zero, optional_originator_id:c_int64=EI, optional_target_id:c_int64=0, originator_rot:Quaternion=Quaternion.identity, bitstream:bytes=EBY, skill_id:c_uint_=EI, ui_skill_handle:c_uint_=0) -> None: assert not used_mouse assert caster_latency == 0 assert last_clicked_posit == Vector3.zero @@ -207,7 +207,7 @@ class SkillComponent(Component): stream = ReadStream(bitstream) if hasattr(self.object, "char"): - self.object.char.update_mission_task(TaskType.UseSkill, None, skill_id) + self.object.char.mission.update_mission_task(TaskType.UseSkill, None, skill_id) if optional_target_id != 0: if optional_target_id not in server.game_objects: @@ -230,7 +230,7 @@ class SkillComponent(Component): if not self.everlasting and consumable_item_id != 0 and cast_type == CastType.Consumable: self.object.inventory.remove_item(InventoryType.Items, object_id=consumable_item_id) - def select_skill(self, from_skill_set:bool=False, skill_id:c_int=EI) -> None: + def on_select_skill(self, from_skill_set:bool=False, skill_id:c_int=EI) -> None: pass @broadcast @@ -245,7 +245,7 @@ class SkillComponent(Component): def echo_sync_skill(self, done:bool=False, bitstream:bytes=EBY, ui_behavior_handle:c_uint_=EI, ui_skill_handle:c_uint_=EI) -> None: pass - def sync_skill(self, done:bool=False, bitstream:bytes=EBY, ui_behavior_handle:c_uint_=EI, ui_skill_handle:c_uint_=EI) -> None: + def on_sync_skill(self, done:bool=False, bitstream:bytes=EBY, ui_behavior_handle:c_uint_=EI, ui_skill_handle:c_uint_=EI) -> None: if hasattr(self.object, "char"): player = self.object else: @@ -273,7 +273,7 @@ class SkillComponent(Component): if done: del self.delayed_behaviors[ui_behavior_handle] - def request_server_projectile_impact(self, local_id:c_int64=0, target_id:c_int64=0, bitstream:bytes=EBY) -> None: + def on_request_server_projectile_impact(self, local_id:c_int64=0, target_id:c_int64=0, bitstream:bytes=EBY) -> None: stream = ReadStream(bitstream) if target_id == 0: target = self.object diff --git a/luserver/components/spawner.py b/luserver/components/spawner.py index 9727ab7..db0a02d 100644 --- a/luserver/components/spawner.py +++ b/luserver/components/spawner.py @@ -27,7 +27,7 @@ class SpawnerComponent(Component): server.spawners[self.name] = self.object if self._active: if self.spawn_net_on_smash is not None: - server.spawners[self.spawn_net_on_smash].add_handler("on_spawned_destruction", self.spawn_on_smash) + server.spawners[self.spawn_net_on_smash].add_handler("spawned_destruction", self.spawn_on_smash) return self.spawn_all() @@ -69,7 +69,7 @@ class SpawnerComponent(Component): if self.spawn_net_on_smash: self._active = True - def spawn_on_smash(self, spawner: GameObject) -> None: + def spawn_on_smash(self) -> None: if self._active: self.spawn() self._active = False diff --git a/luserver/components/stats.py b/luserver/components/stats.py index 21bece5..7d78c1b 100644 --- a/luserver/components/stats.py +++ b/luserver/components/stats.py @@ -106,8 +106,7 @@ class StatsSubcomponent(Component): if is_creation: out.write(c_bit(False)) - out.write(c_bit(self.stats_flag or is_creation)) - if self.stats_flag or is_creation: + if self.flag("stats_flag", out, is_creation): out.write(c_uint(self.life)) out.write(c_float(self.max_life)) out.write(c_uint(self.armor)) @@ -131,13 +130,11 @@ class StatsSubcomponent(Component): out.write(c_bit(False)) out.write(c_bit(False)) - self.stats_flag = False - out.write(c_bit(False)) def on_destruction(self) -> None: if self.object.spawner_object is not None: - self.object.spawner_object.handle("on_spawned_destruction") + self.object.spawner_object.handle("spawned_destruction") def refill_stats(self) -> None: self.life = self.max_life @@ -146,4 +143,4 @@ class StatsSubcomponent(Component): @broadcast def die(self, client_death:bool=False, spawn_loot:bool=True, death_type:str=ES, direction_relative_angle_xz:float=EF, direction_relative_angle_y:float=EF, direction_relative_force:float=EF, kill_type:c_uint_=0, killer:GameObject=EO, loot_owner:Player=OBJ_NONE) -> None: - self.object.handle("on_death", killer, silent=True) + self.object.handle("death", killer, silent=True) diff --git a/luserver/components/switch.py b/luserver/components/switch.py index d729e24..4945381 100644 --- a/luserver/components/switch.py +++ b/luserver/components/switch.py @@ -10,8 +10,6 @@ class SwitchComponent(Component): self._flags["_activated"] = "placeholder_flag" # needed to register changes for serialization self._enabled = True self._activated = False - self.object.add_handler("rebuild_init", self._on_rebuild_init) - self.object.add_handler("complete_rebuild", self._on_rebuild_complete) @property def activated(self) -> bool: @@ -33,10 +31,10 @@ class SwitchComponent(Component): def serialize(self, out: WriteStream, is_creation: bool) -> None: out.write(c_bit(self.activated)) - def _on_rebuild_init(self, _obj: GameObject) -> None: + def on_rebuild_init(self) -> None: self._enabled = False - def _on_rebuild_complete(self, _obj: GameObject, player: Player) -> None: + def on_complete_rebuild(self, player: Player) -> None: self._enabled = True def on_enter(self, player: Player) -> None: diff --git a/luserver/components/trigger.py b/luserver/components/trigger.py index d6aa2ee..6f79281 100644 --- a/luserver/components/trigger.py +++ b/luserver/components/trigger.py @@ -48,7 +48,7 @@ class TriggerComponent(Component): assert target == "target", target assert args[0:4] == ["exploretask","1","1","1"], args[0:4] player = eventargs[0] - player.char.update_mission_task(TaskType.Discover, args[4]) + player.char.mission.update_mission_task(TaskType.Discover, args[4]) else: log.error("command %s not implemented", command_name) diff --git a/luserver/components/vendor.py b/luserver/components/vendor.py index 75f7d3e..7c03b27 100644 --- a/luserver/components/vendor.py +++ b/luserver/components/vendor.py @@ -1,5 +1,5 @@ import logging -from typing import Optional +from typing import List, Optional, Tuple from pyraknet.bitstream import c_bit, c_uint, WriteStream from ..game_object import c_int, c_int64, Config, E, EB, EI, GameObject, Mapping, Player, single @@ -13,7 +13,7 @@ class VendorComponent(Component): def __init__(self, obj: GameObject, set_vars: Config, comp_id: int): super().__init__(obj, set_vars, comp_id) self.object.vendor = self - self.items_for_sale = [] + self.items_for_sale: List[Tuple[int, bool, int]] = [] for row in server.db.vendor_component[comp_id]: self.items_for_sale.extend(server.db.loot_table[row[0]]) @@ -29,11 +29,11 @@ class VendorComponent(Component): def vendor_open_window(self) -> None: pass - def buy_from_vendor(self, player: Player, confirmed:bool=False, count:c_int=1, item:c_int=EI) -> None: + def on_buy_from_vendor(self, player: Player, confirmed:bool=False, count:c_int=1, item:c_int=EI) -> None: new_item = player.inventory.add_item(item, count) player.char.set_currency(currency=player.char.currency - new_item.base_value*count, position=Vector3.zero) - def sell_to_vendor(self, player: Player, count:c_int=1, item_obj_id:c_int64=EI) -> None: + def on_sell_to_vendor(self, player: Player, count:c_int=1, item_obj_id:c_int64=EI) -> None: found = False for inv_type in (InventoryType.Items, InventoryType.Models, InventoryType.Bricks): inv = player.inventory.inventory_type_to_inventory(inv_type) diff --git a/luserver/game_object.py b/luserver/game_object.py index 8dc3cc6..5dc809f 100644 --- a/luserver/game_object.py +++ b/luserver/game_object.py @@ -1,15 +1,16 @@ import asyncio -import functools import importlib import inspect import logging import re +from abc import ABC, abstractmethod from collections import OrderedDict from functools import wraps -from typing import Callable, cast, Dict, Generic, List, NewType, Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Union -from typing import Sequence as Sequence_ +from typing import Any, Callable, cast, Dict, Generic, List, NewType, Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Union from typing import Mapping as Mapping_ +from typing import Sequence as Sequence_ +from mypy_extensions import TypedDict from persistent import Persistent from pyraknet.bitstream import c_bit, c_float, c_ubyte, c_ushort, ReadStream, Serializable, UnsignedIntStruct, WriteStream @@ -24,8 +25,8 @@ from .bitstream import WriteStream as WriteStream_ from .ldf import LDF from .messages import GameMessage, WorldClientMsg from .world import server -from .math.vector import Vector3 from .math.quaternion import Quaternion +from .math.vector import Vector3 log = logging.getLogger(__name__) @@ -64,11 +65,6 @@ class Sequence(Generic[T, U], Sequence_[U]): class Mapping(Generic[T, U, V], Mapping_[U, V]): pass -try: - from mypy_extensions import TypedDict -except ImportError: - TypedDict = object - class Config(TypedDict, total=False): active_on_load: bool activity_id: int @@ -88,28 +84,54 @@ class Config(TypedDict, total=False): rebuild_activator_position: Vector3 rebuild_complete_time: float rebuild_smash_time: float + respawn_data: Tuple[Vector3, Quaternion] respawn_name: str respawn_point_name: str respawn_time: int rotation: Quaternion scale: float script_vars: Dict[str, object] - spawner: "GameObject" + spawner: "SpawnerObject" spawner_name: str spawner_waypoints: Sequence_["Config"] spawntemplate: int spawn_net_on_smash: str + transfer_world_id: int + +class FlagObject(ABC): + def __init__(self) -> None: + self._flags: Dict[str, str] = {} -class GameObject(Replica): def __setattr__(self, name: str, value: object) -> None: self.attr_changed(name) super().__setattr__(name, value) + @abstractmethod + def attr_changed(self, name: str) -> None: + """In case an attribute change is not registered by __setattr__ (like setting an attribute of an attribute), manually register the change by calling this. Without a registered change changes will not be broadcast to clients!""" + + def flag(self, name: str, stream: WriteStream, additional_condition: bool=False) -> bool: + """ + This function can be used to simplify common conditional bitstream writes. + Evaluate the expression of the attribute with the name "name" or the optional additional condition. + Write this value as a bit to the bitstream stream. + If the flag was True, set it to False. + Return the expression. + """ + flag = getattr(self, name) + condition = flag or additional_condition + stream.write(c_bit(condition)) + if flag: + setattr(self, name, False) + return condition + +class GameObject(Replica, FlagObject): def __init__(self, lot: int, object_id: ObjectID, set_vars: Config=None): + super().__init__() if set_vars is None: set_vars = {} self._handlers: Dict[str, List[Callable[..., None]]] = {} - self._flags: Dict[str, str] = { + self._flags = { "parent_flag": "related_objects_flag", "children_flag": "related_objects_flag", "parent": "parent_flag", @@ -205,7 +227,6 @@ class GameObject(Replica): return "" % (self.name, self.object_id, self.lot) def attr_changed(self, name: str) -> None: - """In case an attribute change is not registered by __setattr__ (like setting an attribute of an attribute), manually register the change by calling this. Without a registered change changes will not be broadcast to clients!""" if hasattr(self, "_flags") and name in self._flags: setattr(self, self._flags[name], hasattr(self, name)) self.signal_serialize() @@ -225,7 +246,7 @@ class GameObject(Replica): stream.write(self.name, length_type=c_ubyte) stream.write(bytes(4)) # time since created on server? - stream.write(c_bit(self.config)) + stream.write(c_bit(bool(self.config))) if self.config: stream.write(self.config.to_bytes()) stream.write(c_bit(hasattr(self, "trigger"))) @@ -246,25 +267,18 @@ class GameObject(Replica): self._serialize_scheduled = False def serialize(self, stream: WriteStream, is_creation: bool=False) -> None: - stream.write(c_bit(self.related_objects_flag or (is_creation and (self.parent is not None or self.children)))) - if self.related_objects_flag or (is_creation and (self.parent is not None or self.children)): - stream.write(c_bit(self.parent_flag or (is_creation and self.parent is not None))) - if self.parent_flag or (is_creation and self.parent is not None): + if self.flag("related_objects_flag", stream, is_creation and (self.parent is not None or self.children)): + if self.flag("parent_flag", stream, is_creation and self.parent is not None): if self.parent is not None: stream.write(c_int64_(self.parent)) else: stream.write(c_int64_(0)) stream.write(c_bit(False)) - self.parent_flag = False - stream.write(c_bit(self.children_flag or (is_creation and self.children))) - if self.children_flag or (is_creation and self.children): + if self.flag("children_flag", stream, is_creation and self.children): stream.write(c_ushort(len(self.children))) for child in self.children: stream.write(c_int64_(child)) - self.children_flag = False - - self.related_objects_flag = False for comp in self.components: comp.serialize(stream, is_creation) @@ -283,12 +297,12 @@ class GameObject(Replica): handle.cancel() del server.callback_handles[self.object_id] - self.handle("on_destruction", silent=True) + self.handle("destruction", silent=True) del server.game_objects[self.object_id] def add_handler(self, event_name: str, handler: Callable[..., None]) -> None: - self._handlers.setdefault(event_name, []).append(functools.partial(handler, self)) + self._handlers.setdefault(event_name, []).append(handler) def remove_handler(self, event_name: str, handler: Callable[..., None]) -> None: if event_name not in self._handlers: @@ -296,31 +310,16 @@ class GameObject(Replica): if handler in self._handlers[event_name]: self._handlers[event_name].remove(handler) - def handlers(self, event_name: str, silent: bool=False) -> List[Callable]: - """ - Return matching handlers for an event. - Handlers are returned in serialization order, except for ScriptComponent, which is moved to the bottom of the list. - """ - handlers: List[Callable] = [] - script_handler = None - if event_name in self._handlers: - handlers.extend(self._handlers[event_name]) - for comp in self.components: - if hasattr(comp, event_name): - handler = getattr(comp, event_name) - if isinstance(comp, ScriptComponent): - script_handler = handler - else: - handlers.append(handler) - if script_handler is not None: - handlers.append(script_handler) + def handlers(self, event_name: str, silent: bool=False) -> Sequence_[Callable]: + """Return matching handlers for an event.""" + if event_name not in self._handlers: + if not silent: + log.info("Object %s has no handlers for %s", self, event_name) + return [] + else: + return self._handlers[event_name] - if not handlers and not silent: - log.info("Object %s has no handlers for %s", self, event_name) - - return handlers - - def handle(self, event_name: str, *args, silent=False, **kwargs) -> None: + def handle(self, event_name: str, *args: Any, silent=False, **kwargs: Any) -> None: """ Calls handlers for an event. See handlers() for the order of handlers. If a handler returns True, it's assumed that the handler has sufficiently handled the event and no further handlers will be called. @@ -329,7 +328,7 @@ class GameObject(Replica): if handler(*args, **kwargs): break - def send_game_message(self, handler_name: str, *args, **kwargs) -> None: + def send_game_message(self, handler_name: str, *args: Any, **kwargs: Any) -> None: """For game messages with multiple handlers: call all the handlers but only send one message over the network.""" handlers = self.handlers(handler_name) if not handlers: @@ -340,7 +339,7 @@ class GameObject(Replica): for handler in handlers[1:]: handler.__wrapped__(handler.__self__, *args, **kwargs) - def call_later(self, delay: float, callback: Callable[..., None], *args) -> CallbackID: + def call_later(self, delay: float, callback: Callable[..., None], *args: Any) -> CallbackID: """ Call a callback in delay seconds. The callback's handle is recorded so that when the object is destructed all pending callbacks are automatically cancelled. Return the callback id to be used for cancel_callback. @@ -350,7 +349,7 @@ class GameObject(Replica): server.last_callback_id += 1 return callback_id - def _callback(self, callback_id: CallbackID, callback: Callable[..., None], *args) -> None: + def _callback(self, callback_id: CallbackID, callback: Callable[..., None], *args: Any) -> None: """Execute a callback and delete the handle from the list because it won't be cancelled.""" del server.callback_handles[self.object_id][callback_id] callback(*args) @@ -367,9 +366,9 @@ class GameObject(Replica): message_name = GameMessage(message_id).name except ValueError: return - handler_name = re.sub("(?!^)([A-Z])", r"_\1", message_name).lower() + event_name = re.sub("(?!^)([A-Z])", r"_\1", message_name).lower() - handlers = self.handlers(handler_name) + handlers = self.handlers(event_name) if not handlers: return @@ -400,8 +399,6 @@ class GameObject(Replica): player = server.accounts[address].selected_char() for handler in handlers: - if hasattr(handler, "__wrapped__"): - handler = functools.partial(handler.__wrapped__, handler.__self__) signature = inspect.signature(handler) playerarg = "player" in signature.parameters if playerarg: @@ -431,7 +428,7 @@ class GameObject(Replica): assert message.read(c_ushort) == 0 # for some reason has a null terminator # todo: convert to LDF return value - if issubclass(type_, GameObject): + if issubclass(type_, GameObject) or issubclass(type_, Player): return server.get_object(message.read(c_int64_)) if issubclass(type_, Serializable): return type_.deserialize(message) @@ -454,17 +451,33 @@ class GameObject(Replica): EO = cast(GameObject, E) # these are for static typing and shouldn't actually be used -class PhysicsObject(GameObject): - physics: "PhysicsComponent" +class _SpecialObjectMeta(type): + def __instancecheck__(self, instance: object) -> bool: + if not isinstance(instance, GameObject): + return False + for attr in self.__annotations__: + if not hasattr(instance, attr): + return False + return True + +if TYPE_CHECKING: + class _SpecialObject(GameObject): + pass +else: + class _SpecialObject(metaclass=_SpecialObjectMeta): + pass + +class PhysicsObject(_SpecialObject): + physics: "PhysicsComponent" class ControllableObject(PhysicsObject): physics: "Controllable" -class RenderObject(GameObject): +class RenderObject(_SpecialObject): render: "RenderComponent" -class ScriptObject(GameObject): +class ScriptObject(_SpecialObject): script: "ScriptComponent" class StatsObject(PhysicsObject, RenderObject): # safe to assume that a stats object is also a physics object - only 2 entries (6622, 6908) in the database didn't match, and those were test objects @@ -473,7 +486,15 @@ class StatsObject(PhysicsObject, RenderObject): # safe to assume that a stats ob class DestructibleObject(StatsObject): destructible: "DestructibleComponent" -class Player(ControllableObject, DestructibleObject, Persistent): +class SpawnerObject(_SpecialObject): + spawner: "SpawnerComponent" + +class Player(ControllableObject, DestructibleObject): + char: "CharacterComponent" + inventory: "InventoryComponent" + skill: "SkillComponent" + +class ActualPlayer(GameObject, Persistent): char: "CharacterComponent" inventory: "InventoryComponent" skill: "SkillComponent" @@ -487,9 +508,6 @@ class Player(ControllableObject, DestructibleObject, Persistent): super().__setattr__(name, value) self._p_changed = True -# backwards compatibility -PersistentObject = Player - EP = cast(Player, E) OBJ_NONE = cast(Player, None) @@ -513,7 +531,7 @@ def _send_game_message(mode: str) -> Callable[[X], X]: from .world import server @wraps(func) - def wrapper(self, *args, **kwargs): + def wrapper(self, *args: Any, **kwargs: Any): game_message_id = GameMessage[re.sub("(^|_)(.)", lambda match: match.group(2).upper(), func.__name__)].value out = WriteStream_() out.write_header(WorldClientMsg.GameMessage) @@ -578,14 +596,14 @@ def _game_message_serialize(out, type_: Type[W], value: W) -> None: out.write(value, length_type=c_uint_) elif type_ == str: out.write(value, length_type=c_uint_) - elif type_ in (c_int_, c_int64_, c_ubyte, c_uint_, c_uint64_): + elif issubclass(type_, (c_int_, c_int64_, c_ubyte, c_uint_, c_uint64_)): out.write(type_(value)) 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 issubclass(type_, GameObject): + elif issubclass(type_, GameObject) or issubclass(type_, Player): if value is OBJ_NONE: out.write(c_int64_(0)) else: diff --git a/luserver/interfaces/plugin.py b/luserver/interfaces/plugin.py index 532c72e..5f14763 100644 --- a/luserver/interfaces/plugin.py +++ b/luserver/interfaces/plugin.py @@ -1,24 +1,26 @@ from argparse import ArgumentTypeError +from typing import Any, List, Optional from ..auth import GMLevel +from ..game_object import GameObject, Player from ..world import server class ChatCommand: - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any): self.command = server.chat.commands.add_parser(*args, **kwargs) self.command.set_defaults(func=self.run) self.command.set_defaults(perm=GMLevel.Admin) - def run(self, args, sender): + def run(self, args: Any, sender: GameObject) -> None: raise NotImplementedError -def toggle_bool(str_): +def toggle_bool(str_: str) -> Optional[bool]: str_ = str_.lower() if str_ == "toggle": return None return normal_bool(str_) -def normal_bool(str_): +def normal_bool(str_: str) -> bool: str_ = str_.lower() if str_ in ("true", "on"): return True @@ -26,7 +28,7 @@ def normal_bool(str_): return False raise ValueError -def object_selector(str_): +def object_selector(str_: str) -> List[GameObject]: selected = [] for obj in server.game_objects.values(): if str_.startswith("!"): @@ -34,7 +36,7 @@ def object_selector(str_): selected.append(obj) return selected -def instance_obj(name): +def instance_obj(name: str) -> GameObject: if not name.startswith("!"): return instance_player(name) else: @@ -45,9 +47,9 @@ def instance_obj(name): return obj raise ArgumentTypeError("Object not found in instance") -def instance_player(name): +def instance_player(name: str) -> Player: name = name.lower() for obj in server.game_objects.values(): - if hasattr(obj, "char") and obj.name.lower().startswith(name): + if isinstance(obj, Player) and obj.name.lower().startswith(name): return obj raise ArgumentTypeError("Player not found in instance") diff --git a/luserver/messages.py b/luserver/messages.py index 48a9fac..c4b305e 100644 --- a/luserver/messages.py +++ b/luserver/messages.py @@ -1,9 +1,7 @@ -from abc import ABC, abstractmethod from enum import Enum, IntEnum class LUMessage(IntEnum): @staticmethod - @abstractmethod def header() -> int: pass diff --git a/luserver/modules/char.py b/luserver/modules/char.py index 3e036c5..5792232 100644 --- a/luserver/modules/char.py +++ b/luserver/modules/char.py @@ -52,14 +52,12 @@ class MouthStyle: import asyncio import logging -from typing import Tuple - -import persistent.wref +from typing import cast, Tuple from pyraknet.bitstream import c_int64, c_bool, c_ubyte, c_uint, c_ushort, ReadStream from pyraknet.messages import Address from ..bitstream import WriteStream -from ..game_object import Player +from ..game_object import ActualPlayer, Player from ..messages import WorldClientMsg, WorldServerMsg from ..world import server @@ -180,7 +178,7 @@ class CharHandling: try: if return_code == _CharacterCreateReturnCode.Success: - new_char = Player(server.new_object_id()) + new_char = ActualPlayer(server.new_object_id()) new_char.name = char_name new_char.char.account = account new_char.char.address = address @@ -198,12 +196,12 @@ class CharHandling: shirt = new_char.inventory.add_item(self._shirt_to_lot(new_char.char.shirt_color, new_char.char.shirt_style), notify_client=False) pants = new_char.inventory.add_item(_PANTS_LOT[new_char.char.pants_color], notify_client=False) - new_char.inventory.equip_inventory(item_to_equip=shirt.object_id) - new_char.inventory.equip_inventory(item_to_equip=pants.object_id) + new_char.inventory.on_equip_inventory(item_to_equip=shirt.object_id) + new_char.inventory.on_equip_inventory(item_to_equip=pants.object_id) characters = account.characters - characters[char_name] = new_char - characters.selected_char_name = char_name + characters[char_name] = cast(Player, new_char) + account.selected_char_name = char_name server.conn.transaction_manager.commit() log.info("Creating new character %s", char_name) except Exception: diff --git a/luserver/modules/chat.py b/luserver/modules/chat.py index 4cb6881..7158d42 100644 --- a/luserver/modules/chat.py +++ b/luserver/modules/chat.py @@ -207,7 +207,7 @@ class ChatHandling: if recipient_name.startswith("!"): recipients = object_selector(recipient_name) for recipient in recipients: - recipient.handle("on_private_chat_message", sender, text) + recipient.handle("private_chat_message", sender, text) else: recipient = server.find_player_by_name(recipient_name) self.send_private_chat_message(sender, text, recipient) diff --git a/luserver/modules/general.py b/luserver/modules/general.py index e6e448e..8246f2d 100644 --- a/luserver/modules/general.py +++ b/luserver/modules/general.py @@ -163,7 +163,7 @@ class GeneralHandling: mis = ET.SubElement(root, "mis") done = ET.SubElement(mis, "done") cur = ET.SubElement(mis, "cur") - for mission_id, mission in player.char.missions.items(): + for mission_id, mission in player.char.mission.missions.items(): if mission.state == 2: m = ET.SubElement(cur, "m", id=str(mission_id)) for task in mission.tasks: @@ -254,18 +254,18 @@ class GeneralHandling: for object_id in collisions: if object_id not in player.char.last_collisions: - server.get_object(object_id).handle("on_enter", player) + server.get_object(object_id).handle("enter", player) for object_id in player.char.last_collisions: - if object_id not in collisions: + if object_id in server.game_objects and object_id not in collisions: obj = server.get_object(object_id) - if obj is not None: - obj.handle("on_exit", player, silent=True) + obj.handle("exit", player, silent=True) player.char.last_collisions = collisions def _on_game_message(self, message: ReadStream, address: Address) -> None: object_id = message.read(c_int64) - obj = server.get_object(object_id) - if obj is None: + try: + obj = server.get_object(object_id) + except KeyError: return obj.on_game_message(message, address) diff --git a/luserver/scripts/avant_gardens/beacon.py b/luserver/scripts/avant_gardens/beacon.py index 0ebdd5c..36c263b 100644 --- a/luserver/scripts/avant_gardens/beacon.py +++ b/luserver/scripts/avant_gardens/beacon.py @@ -1,9 +1,12 @@ +from typing import cast + import luserver.components.script as script +from luserver.game_object import RenderObject from luserver.world import server class ScriptComponent(script.ScriptComponent): - def complete_rebuild(self, player): - jet_fx = server.get_objects_in_group("Jet_FX")[0] + def on_complete_rebuild(self, player): + jet_fx = cast(RenderObject, server.get_objects_in_group("Jet_FX")[0]) jet_fx.render.play_animation("jetFX") # actual jet attack not implemented diff --git a/luserver/scripts/avant_gardens/caged_bricks.py b/luserver/scripts/avant_gardens/caged_bricks.py index ecfe803..7d8b530 100644 --- a/luserver/scripts/avant_gardens/caged_bricks.py +++ b/luserver/scripts/avant_gardens/caged_bricks.py @@ -1,7 +1,7 @@ -from typing import Optional +from typing import cast, Optional import luserver.components.script as script -from luserver.game_object import Player +from luserver.game_object import ScriptObject, Player from luserver.world import server from luserver.components.inventory import InventoryType @@ -13,4 +13,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, player=player) + cast(ScriptObject, server.get_objects_in_group("cagedSpider")[0]).script.fire_event_client_side(args="toggle", obj=None, sender=player, player=player) diff --git a/luserver/scripts/avant_gardens/dusty_holster.py b/luserver/scripts/avant_gardens/dusty_holster.py index 919fa82..db04b2f 100644 --- a/luserver/scripts/avant_gardens/dusty_holster.py +++ b/luserver/scripts/avant_gardens/dusty_holster.py @@ -7,8 +7,8 @@ class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: self.set_missions({1880: ("PlungerGunTargets",)}) - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): - super().mission_dialogue_o_k(is_complete, mission_state, mission_id, player) + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + super().on_mission_dialogue_o_k(is_complete, mission_state, mission_id, player) if mission_id == 1880: if mission_state in (MissionState.Available, MissionState.CompletedAvailable): player.inventory.add_item(14378) diff --git a/luserver/scripts/avant_gardens/epsilon_starcracker.py b/luserver/scripts/avant_gardens/epsilon_starcracker.py index f071846..21922ac 100644 --- a/luserver/scripts/avant_gardens/epsilon_starcracker.py +++ b/luserver/scripts/avant_gardens/epsilon_starcracker.py @@ -3,6 +3,6 @@ from luserver.game_object import c_int, EB, EI, EP, Player from luserver.components.mission import MissionState class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == 1851 and mission_state == MissionState.ReadyToComplete: player.char.start_celebration_effect(animation="", duration=0, icon_id=0, main_text="", mixer_program=b"", music_cue=b"", path_node_name=b"", sound_guid=b"", sub_text="", celebration_id=22) diff --git a/luserver/scripts/avant_gardens/fan.py b/luserver/scripts/avant_gardens/fan.py index fa34678..cd4790e 100644 --- a/luserver/scripts/avant_gardens/fan.py +++ b/luserver/scripts/avant_gardens/fan.py @@ -1,4 +1,7 @@ +from typing import cast + import luserver.components.script as script +from luserver.game_object import PhysicsObject, RenderObject from luserver.world import server class ScriptComponent(script.ScriptComponent): @@ -15,10 +18,11 @@ class ScriptComponent(script.ScriptComponent): self.enable_fx(False) def enable_fx(self, enable=None): - fx_obj = server.get_objects_in_group(self.object.groups[0]+"fx")[0] + fx_obj = cast(RenderObject, server.get_objects_in_group(self.object.groups[0]+"fx")[0]) objs = server.get_objects_in_group(self.object.groups[0]) for obj in objs: if obj.lot == 5958: + obj = cast(PhysicsObject, obj) if enable is None: enable = not obj.physics.physics_effect_active obj.physics.physics_effect_active = enable diff --git a/luserver/scripts/avant_gardens/laser.py b/luserver/scripts/avant_gardens/laser.py index c93b18f..fabc7ce 100644 --- a/luserver/scripts/avant_gardens/laser.py +++ b/luserver/scripts/avant_gardens/laser.py @@ -1,11 +1,14 @@ +from typing import cast + import luserver.components.script as script +from luserver.game_object import ScriptObject from luserver.world import server class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: - sensor = server.get_objects_in_group(self.script_vars["volume_group"])[0] + sensor = cast(ScriptObject, server.get_objects_in_group(self.script_vars["volume_group"])[0]) sensor.script.active = True def on_destruction(self) -> None: - sensor = server.get_objects_in_group(self.script_vars["volume_group"])[0] + sensor = cast(ScriptObject, server.get_objects_in_group(self.script_vars["volume_group"])[0]) sensor.script.active = False diff --git a/luserver/scripts/avant_gardens/melodie_foxtrot.py b/luserver/scripts/avant_gardens/melodie_foxtrot.py index ef6d8b3..2813f5e 100644 --- a/luserver/scripts/avant_gardens/melodie_foxtrot.py +++ b/luserver/scripts/avant_gardens/melodie_foxtrot.py @@ -7,7 +7,7 @@ POSTCARD = 14549 TRIAL_GEAR = 14359, 14321, 14353, 14315 class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == 313 and mission_state == MissionState.ReadyToComplete: player.inventory.remove_item(InventoryType.Items, lot=POSTCARD, count=5) for lot in TRIAL_GEAR: diff --git a/luserver/scripts/avant_gardens/property/small/dark_spiderling.py b/luserver/scripts/avant_gardens/property/small/dark_spiderling.py index 32590b1..f55e591 100644 --- a/luserver/scripts/avant_gardens/property/small/dark_spiderling.py +++ b/luserver/scripts/avant_gardens/property/small/dark_spiderling.py @@ -1,6 +1,9 @@ +from typing import cast + import luserver.components.script as script +from luserver.game_object import ScriptObject from luserver.world import server class ScriptComponent(script.ScriptComponent): def on_destruction(self) -> None: - server.get_objects_in_group("SpiderBoss")[0].script.spiderling_defeated() + cast(ScriptObject, server.get_objects_in_group("SpiderBoss")[0]).script.spiderling_defeated() diff --git a/luserver/scripts/avant_gardens/property/small/spider_queen.py b/luserver/scripts/avant_gardens/property/small/spider_queen.py index 3d4b42f..be4db9e 100644 --- a/luserver/scripts/avant_gardens/property/small/spider_queen.py +++ b/luserver/scripts/avant_gardens/property/small/spider_queen.py @@ -1,4 +1,7 @@ +from typing import cast + import luserver.components.script as script +from luserver.game_object import ScriptObject from luserver.world import server from luserver.math.quaternion import Quaternion @@ -20,7 +23,7 @@ class ScriptComponent(script.ScriptComponent): self.object.call_later(3, self.object.render.play_animation, "idle-withdrawn") for egg in server.get_objects_in_group("SpiderEggs")[:2]: - egg.script.spawn_spider() + cast(ScriptObject, egg).script.spawn_spider() def spiderling_defeated(self): self.spiderlings_defeated += 1 diff --git a/luserver/scripts/avant_gardens/property/small/vance_bulwark.py b/luserver/scripts/avant_gardens/property/small/vance_bulwark.py index 31eabb0..0476e5b 100644 --- a/luserver/scripts/avant_gardens/property/small/vance_bulwark.py +++ b/luserver/scripts/avant_gardens/property/small/vance_bulwark.py @@ -1,5 +1,5 @@ import luserver.components.script as script -from luserver.game_object import c_int, E, Player +from luserver.game_object import c_int, EB, EI, EP, Player from luserver.world import server from luserver.components.inventory import InventoryType from luserver.components.mission import MissionState @@ -7,7 +7,7 @@ from luserver.components.mission import MissionState TRIAL_GEAR = 14359, 14321, 14353, 14315 class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == 320: if mission_state == MissionState.Available: server.world_control_object.script.notify_client_object(name="GuardChat", param1=0, param2=0, param_str=b"", param_obj=self.object) @@ -15,7 +15,7 @@ class ScriptComponent(script.ScriptComponent): elif mission_id == 768: if mission_state == MissionState.Available: if not player.char.get_flag(71): - player.char.play_cinematic(path_name="MissionCam", start_time_advance=0) + player.char.camera.play_cinematic(path_name="MissionCam", start_time_advance=0) elif mission_state == MissionState.ReadyToComplete: for lot in TRIAL_GEAR: player.inventory.remove_item(InventoryType.Items, lot=lot) diff --git a/luserver/scripts/avant_gardens/property/small/world_control.py b/luserver/scripts/avant_gardens/property/small/world_control.py index f0e824d..86c965f 100644 --- a/luserver/scripts/avant_gardens/property/small/world_control.py +++ b/luserver/scripts/avant_gardens/property/small/world_control.py @@ -1,5 +1,7 @@ +from typing import cast + import luserver.components.script as script -from luserver.game_object import c_int64, GameObject, OBJ_NONE, Player, single +from luserver.game_object import c_int64, GameObject, OBJ_NONE, Player, RenderObject, single from luserver.ldf import LDFDataType from luserver.world import server from luserver.components.mission import MissionState, TaskType @@ -10,7 +12,6 @@ class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: self.tutorial = None - @single def player_ready(self, player: Player) -> None: if not player.char.get_flag(FLAG_DEFEATED_SPIDER): self.start_maelstrom() @@ -20,9 +21,12 @@ class ScriptComponent(script.ScriptComponent): # todo: implement distinction between instance and claim property (different launcher) server.spawners["Launcher"].spawner.activate() - if 320 not in player.char.missions: + if 320 not in player.char.mission.missions: server.spawners["PropertyGuard"].spawner.activate() + player_ready = single(player_ready) + on_player_ready = player_ready + def start_maelstrom(self): for spawner in ("SpiderBoss", "SpiderEggs"): server.spawners[spawner].spawner.activate() @@ -34,7 +38,7 @@ class ScriptComponent(script.ScriptComponent): self.set_network_var("unclaimed", LDFDataType.BOOLEAN, True) - fx = server.get_objects_in_group("FXObject")[0] + fx = cast(RenderObject, server.get_objects_in_group("FXObject")[0]) fx.render.play_f_x_effect(name=b"TornadoDebris", effect_type="debrisOn") fx.render.play_f_x_effect(name=b"TornadoVortex", effect_type="VortexOn") fx.render.play_f_x_effect(name=b"silhouette", effect_type="onSilhouette") @@ -42,7 +46,7 @@ class ScriptComponent(script.ScriptComponent): self.notify_client_object(name="maelstromSkyOn", param1=0, param2=0, param_str=b"", param_obj=OBJ_NONE) def on_spider_defeated(self): - player = [obj for obj in server.game_objects.values() if obj.lot == 1][0] + player = cast(Player, [obj for obj in server.game_objects.values() if obj.lot == 1][0]) if player.char.get_flag(FLAG_DEFEATED_SPIDER): return server.spawners["SpiderBoss"].spawner.deactivate() @@ -57,14 +61,14 @@ class ScriptComponent(script.ScriptComponent): self.object.call_later(0.5, self.tornado_off) def tornado_off(self): - fx = server.get_objects_in_group("FXObject")[0] + fx = cast(RenderObject, server.get_objects_in_group("FXObject")[0]) fx.render.stop_f_x_effect(name=b"TornadoDebris") fx.render.stop_f_x_effect(name=b"TornadoVortex") fx.render.stop_f_x_effect(name=b"silhouette") self.object.call_later(2, self.show_clear_effects) def show_clear_effects(self): - fx = server.get_objects_in_group("FXObject")[0] + fx = cast(RenderObject, server.get_objects_in_group("FXObject")[0]) fx.render.play_f_x_effect(name=b"beam", effect_type="beamOn") self.object.call_later(1.5, self.turn_sky_off) self.object.call_later(7, self.show_vendor) @@ -77,13 +81,13 @@ class ScriptComponent(script.ScriptComponent): self.notify_client_object(name="vendorOn", param1=0, param2=0, param_str=b"", param_obj=OBJ_NONE) def kill_fx_object(self): - fx = server.get_objects_in_group("FXObject")[0] + fx = cast(RenderObject, server.get_objects_in_group("FXObject")[0]) fx.render.stop_f_x_effect(name=b"beam") server.spawners["FXObject"].spawner.destroy() def on_property_rented(self, player): self.notify_client_object(name="PlayCinematic", param1=0, param2=0, param_str=b"ShowProperty", param_obj=OBJ_NONE) - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=951) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=951) self.object.call_later(2, self.bounds_on) def bounds_on(self): @@ -99,12 +103,12 @@ class ScriptComponent(script.ScriptComponent): def on_model_placed(self, player): if not player.char.get_flag(101): player.char.set_flag(True, 101) - if 871 in player.char.missions and player.char.missions[871].state == MissionState.Active: + if 871 in player.char.mission.missions and player.char.mission.missions[871].state == MissionState.Active: self.set_network_var("Tooltip", LDFDataType.STRING, "AnotherModel") elif not player.char.get_flag(102): player.char.set_flag(True, 102) - if 871 in player.char.missions and player.char.missions[871].state == MissionState.Active: + if 871 in player.char.mission.missions and player.char.mission.missions[871].state == MissionState.Active: self.set_network_var("Tooltip", LDFDataType.STRING, "TwoMoreModels") elif not player.char.get_flag(103): @@ -121,21 +125,21 @@ class ScriptComponent(script.ScriptComponent): def on_model_picked_up(self, player): if not player.char.get_flag(109): player.char.set_flag(True, 109) - if 891 in player.char.missions and player.char.missions[891].state == MissionState.Active and not player.char.get_flag(110): + if 891 in player.char.mission.missions and player.char.mission.missions[891].state == MissionState.Active and not player.char.get_flag(110): self.set_network_var("Tooltip", LDFDataType.STRING, "Rotate") def on_model_put_away(self, player): player.char.set_flag(True, 111) - def zone_property_model_rotated(self, player:Player=OBJ_NONE, property_id:c_int64=0): + def on_zone_property_model_rotated(self, player:Player=OBJ_NONE, property_id:c_int64=0): if not player.char.get_flag(110): player.char.set_flag(True, 110) - if 891 in player.char.missions and player.char.missions[891].state == MissionState.Active: + if 891 in player.char.mission.missions and player.char.mission.missions[891].state == MissionState.Active: self.set_network_var("Tooltip", LDFDataType.STRING, "PlaceModel") self.tutorial = "place_model" - def zone_property_model_removed_while_equipped(self, player:Player=OBJ_NONE, property_id:c_int64=0): + def on_zone_property_model_removed_while_equipped(self, player:Player=OBJ_NONE, property_id:c_int64=0): self.on_model_put_away(player) - def zone_property_model_equipped(self, player:GameObject=OBJ_NONE, property_id:c_int64=0): + def on_zone_property_model_equipped(self, player:GameObject=OBJ_NONE, property_id:c_int64=0): self.set_network_var("PlayerAction", LDFDataType.STRING, "ModelEquipped") diff --git a/luserver/scripts/avant_gardens/quickbuild_elevator.py b/luserver/scripts/avant_gardens/quickbuild_elevator.py index c9c5cd4..faa1c35 100644 --- a/luserver/scripts/avant_gardens/quickbuild_elevator.py +++ b/luserver/scripts/avant_gardens/quickbuild_elevator.py @@ -4,7 +4,7 @@ class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: self.object.moving_platform.no_autostart = True - def complete_rebuild(self, player): + def on_complete_rebuild(self, player): self.object.physics.proximity_radius(5) self.player = player diff --git a/luserver/scripts/avant_gardens/rocco_sirocco.py b/luserver/scripts/avant_gardens/rocco_sirocco.py index 108d341..c9700e6 100644 --- a/luserver/scripts/avant_gardens/rocco_sirocco.py +++ b/luserver/scripts/avant_gardens/rocco_sirocco.py @@ -4,12 +4,12 @@ from luserver.components.inventory import InventoryType from luserver.components.mission import MissionState class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == 1728: if mission_state == MissionState.Available: # needed to send the mission mail - player.char.add_mission(1729) - player.char.complete_mission(1729) + player.char.mission.add_mission(1729) + player.char.mission.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) diff --git a/luserver/scripts/avant_gardens/rusty_steele.py b/luserver/scripts/avant_gardens/rusty_steele.py index 304ae68..f8ef9bc 100644 --- a/luserver/scripts/avant_gardens/rusty_steele.py +++ b/luserver/scripts/avant_gardens/rusty_steele.py @@ -7,7 +7,7 @@ WHEEL = 14555 FLASHLIGHT = 14556 class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == 1854 and mission_state == MissionState.ReadyToComplete: player.inventory.remove_item(InventoryType.Items, lot=WHEEL, count=4) player.inventory.remove_item(InventoryType.Items, lot=FLASHLIGHT, count=3) diff --git a/luserver/scripts/avant_gardens/survival/buff_station.py b/luserver/scripts/avant_gardens/survival/buff_station.py index b680662..135261c 100644 --- a/luserver/scripts/avant_gardens/survival/buff_station.py +++ b/luserver/scripts/avant_gardens/survival/buff_station.py @@ -7,7 +7,7 @@ DROPS = { 6431: 6} # armor class ScriptComponent(script.ScriptComponent): - def complete_rebuild(self, player): + def on_complete_rebuild(self, player): for powerup, interval in DROPS.items(): self.object.call_later(interval, self.spawn, powerup, player) diff --git a/luserver/scripts/avant_gardens/survival/world_control.py b/luserver/scripts/avant_gardens/survival/world_control.py index 9b6d123..332c1e2 100644 --- a/luserver/scripts/avant_gardens/survival/world_control.py +++ b/luserver/scripts/avant_gardens/survival/world_control.py @@ -1,8 +1,9 @@ import asyncio import time +from typing import cast import luserver.components.script as script -from luserver.game_object import c_int, EI, ES, Player, single +from luserver.game_object import c_int, EI, ES, PhysicsObject, Player, single from luserver.ldf import LDF, LDFDataType from luserver.world import server from luserver.components.inventory import InventoryType @@ -38,8 +39,8 @@ class ScriptComponent(script.ScriptComponent): def set_player_spawn_points(self): for index, player_id in enumerate(self.object.scripted_activity.activity_values): - player = server.get_object(player_id) - spawn = server.get_objects_in_group("P%i_Spawn" % (index+1))[0] + player = cast(Player, server.get_object(player_id)) + spawn = cast(PhysicsObject, server.get_objects_in_group("P%i_Spawn" % (index+1))[0]) 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) def start(self): @@ -63,7 +64,7 @@ class ScriptComponent(script.ScriptComponent): leaderboard.ldf_set("Result[0].RowCount", LDFDataType.INT32, 0) for player_id in self.object.scripted_activity.activity_values: - player = server.get_object(player_id) + player = cast(Player, server.get_object(player_id)) if self.first_time: player.inventory.remove_item(InventoryType.Items, lot=GREEN_IMAGINITE) # todo: don't hardcode this and generalize it across activities self.first_time = False @@ -81,8 +82,8 @@ class ScriptComponent(script.ScriptComponent): server.spawners[spawner].spawner.destroy() for player_id, values in self.object.scripted_activity.activity_values.items(): - player = server.get_object(player_id) - player.char.request_resurrect() + player = cast(Player, server.get_object(player_id)) + player.char.on_request_resurrect() player_time = values[1] player_score = values[0] @@ -91,8 +92,8 @@ class ScriptComponent(script.ScriptComponent): for mission_id, time in SURVIVAL_MISSIONS: if player_time > time: - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=mission_id) - player.char.update_mission_task(TaskType.MinigameAchievement, self.object.scripted_activity.activity_id, ("survival_time_"+self.game_type, 400)) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=mission_id) + player.char.mission.update_mission_task(TaskType.MinigameAchievement, self.object.scripted_activity.activity_id, ("survival_time_"+self.game_type, 400)) def tick(self): self.set_network_var("Update_Timer", LDFDataType.DOUBLE, time.perf_counter()-self.start_time) @@ -100,7 +101,7 @@ class ScriptComponent(script.ScriptComponent): def are_all_players_dead(self): for player_id in self.object.scripted_activity.activity_values: - player = server.get_object(player_id) + player = cast(Player, server.get_object(player_id)) if player.stats.life > 0: return False return True @@ -115,10 +116,9 @@ class ScriptComponent(script.ScriptComponent): self.object.scripted_activity.notify_client_zone_object(name="Player_Died", param1=int(time.perf_counter()-self.start_time), param2=0, param_str=all_dead, param_obj=player) self.game_over(player) else: - player.char.request_resurrect() + player.char.on_request_resurrect() self.set_player_spawn_points() - @single def player_ready(self, player: Player) -> None: self.object.scripted_activity.add_player(player) self.set_network_var("Define_Player_To_UI", LDFDataType.BYTES, str(player.object_id).encode()) @@ -127,7 +127,10 @@ class ScriptComponent(script.ScriptComponent): self.set_player_spawn_points() - def message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): + player_ready = single(player_ready) + on_player_ready = player_ready + + def on_message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): if id == "RePlay": self.start() diff --git a/luserver/scripts/avant_gardens/turret.py b/luserver/scripts/avant_gardens/turret.py index 252f32c..6cd88f1 100644 --- a/luserver/scripts/avant_gardens/turret.py +++ b/luserver/scripts/avant_gardens/turret.py @@ -5,7 +5,7 @@ class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: self.die_callback = self.object.call_later(20, self.die_if_not_building) - def complete_rebuild(self, player): + def on_complete_rebuild(self, player): self.object.physics.lock_node_rotation(node_name=b"base") self.object.cancel_callback(self.die_callback) diff --git a/luserver/scripts/avant_gardens/wisp_lee.py b/luserver/scripts/avant_gardens/wisp_lee.py index fa47919..a076ec9 100644 --- a/luserver/scripts/avant_gardens/wisp_lee.py +++ b/luserver/scripts/avant_gardens/wisp_lee.py @@ -7,7 +7,7 @@ class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: self.set_missions({1849: ("MaelstromSamples",)}) - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): - super().mission_dialogue_o_k(is_complete, mission_state, mission_id, player) + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + super().on_mission_dialogue_o_k(is_complete, mission_state, mission_id, player) if mission_id == 1849 and mission_state == MissionState.ReadyToComplete: player.inventory.remove_item(InventoryType.Items, lot=14592) diff --git a/luserver/scripts/crux_prime/aura_blossom_flower.py b/luserver/scripts/crux_prime/aura_blossom_flower.py index 8057aa9..6e564fd 100644 --- a/luserver/scripts/crux_prime/aura_blossom_flower.py +++ b/luserver/scripts/crux_prime/aura_blossom_flower.py @@ -6,6 +6,6 @@ class ScriptComponent(script.ScriptComponent): if event_name == "waterspray": if "blooming" not in self.script_network_vars: self.object.physics.drop_loot(12317, caster) - caster.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=1136) + caster.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=1136) super().on_skill_event(caster, event_name) diff --git a/luserver/scripts/crux_prime/scroll_shrine.py b/luserver/scripts/crux_prime/scroll_shrine.py index 3da703c..b766e3c 100644 --- a/luserver/scripts/crux_prime/scroll_shrine.py +++ b/luserver/scripts/crux_prime/scroll_shrine.py @@ -6,4 +6,4 @@ from luserver.components.mission import TaskType class ScriptComponent(script.ScriptComponent): def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=969) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=969) diff --git a/luserver/scripts/crux_prime/skill_volume.py b/luserver/scripts/crux_prime/skill_volume.py index 1c21d97..ff7253c 100644 --- a/luserver/scripts/crux_prime/skill_volume.py +++ b/luserver/scripts/crux_prime/skill_volume.py @@ -4,4 +4,4 @@ from luserver.components.mission import TaskType class ScriptComponent(script.ScriptComponent): def on_skill_event(self, caster, event_name): if event_name == "spinjitzu": - caster.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=966) + caster.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=966) diff --git a/luserver/scripts/forbidden_valley/bounce_over_wall.py b/luserver/scripts/forbidden_valley/bounce_over_wall.py index cd81fd6..e509d52 100644 --- a/luserver/scripts/forbidden_valley/bounce_over_wall.py +++ b/luserver/scripts/forbidden_valley/bounce_over_wall.py @@ -3,4 +3,4 @@ from luserver.components.mission import TaskType class ScriptComponent(script.ScriptComponent): def on_enter(self, player): - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=849) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=849) diff --git a/luserver/scripts/forbidden_valley/brickmaster_clang.py b/luserver/scripts/forbidden_valley/brickmaster_clang.py index a882d4a..0361319 100644 --- a/luserver/scripts/forbidden_valley/brickmaster_clang.py +++ b/luserver/scripts/forbidden_valley/brickmaster_clang.py @@ -7,10 +7,10 @@ FREE_NINJAS_MISSIONS = [701, 702, 703, 704] PANDA_MISSION = 786 class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == FREE_NINJAS_MISSION and mission_state == MissionState.Available: for mission in FREE_NINJAS_MISSIONS: - player.char.add_mission(mission) + player.char.mission.add_mission(mission) player.char.set_flag(True, 68) elif mission_id == PANDA_MISSION and mission_state == MissionState.Available: player.char.set_flag(True, 81) diff --git a/luserver/scripts/forbidden_valley/candle.py b/luserver/scripts/forbidden_valley/candle.py index 25a8d35..f70044a 100644 --- a/luserver/scripts/forbidden_valley/candle.py +++ b/luserver/scripts/forbidden_valley/candle.py @@ -12,7 +12,7 @@ class ScriptComponent(script.ScriptComponent): def on_hit(self, damage, attacker): if not self.script_vars["am_hit"]: - attacker.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=850) + attacker.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=850) self.script_vars["am_hit"] = True self.object.render.stop_f_x_effect(name=b"candle_light") diff --git a/luserver/scripts/general/flower.py b/luserver/scripts/general/flower.py index 5d2c6e4..f011b85 100644 --- a/luserver/scripts/general/flower.py +++ b/luserver/scripts/general/flower.py @@ -10,5 +10,5 @@ class ScriptComponent(script.ScriptComponent): if "blooming" not in self.script_network_vars: self.set_network_var("blooming", LDFDataType.BOOLEAN, True) for mission_id in FLOWER_MISSIONS: - caster.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=mission_id) + caster.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=mission_id) self.object.call_later(16, lambda: self.object.destructible.simply_die(killer=caster)) diff --git a/luserver/scripts/general/mailbox.py b/luserver/scripts/general/mailbox.py index 299607e..d81b4d4 100644 --- a/luserver/scripts/general/mailbox.py +++ b/luserver/scripts/general/mailbox.py @@ -8,6 +8,6 @@ class ScriptComponent(script.ScriptComponent): def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: player.char.u_i_message_server_to_single_client(message_name=b"pushGameState", args=AMF3({"state": "Mail"})) - def fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): + def on_fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): if args == "toggleMail": sender.char.u_i_message_server_to_single_client(message_name=b"ToggleMail", args=AMF3({"visible": False})) diff --git a/luserver/scripts/general/notify_visibility.py b/luserver/scripts/general/notify_visibility.py index 3ed4214..bcb5bb5 100644 --- a/luserver/scripts/general/notify_visibility.py +++ b/luserver/scripts/general/notify_visibility.py @@ -1,5 +1,7 @@ +from typing import cast + import luserver.components.script as script -from luserver.game_object import c_int, EB, EI, EO, GameObject +from luserver.game_object import c_int, EB, EI, EO, GameObject, ScriptObject from luserver.world import server from luserver.components.mission import MissionState @@ -10,7 +12,7 @@ class ScriptComponent(script.ScriptComponent): def set_missions(self, missions): self.missions = missions - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:GameObject=EO): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:GameObject=EO): if mission_id in self.missions: if mission_state in (MissionState.Available, MissionState.CompletedAvailable): visible = 1 @@ -24,4 +26,4 @@ class ScriptComponent(script.ScriptComponent): for obj in server.game_objects.values(): if obj.spawner_object in spawners: - obj.script.notify_client_object(name="SetVisibility", param1=visible, param2=0, param_obj=None, param_str=b"") + cast(ScriptObject, obj).script.notify_client_object(name="SetVisibility", param1=visible, param2=0, param_obj=None, param_str=b"") diff --git a/luserver/scripts/general/poi_mission.py b/luserver/scripts/general/poi_mission.py index ad2e20d..2a6b809 100644 --- a/luserver/scripts/general/poi_mission.py +++ b/luserver/scripts/general/poi_mission.py @@ -3,4 +3,4 @@ from luserver.components.mission import TaskType class ScriptComponent(script.ScriptComponent): def on_enter(self, player): - player.char.update_mission_task(TaskType.Discover, self.script_vars["poi"]) + player.char.mission.update_mission_task(TaskType.Discover, self.script_vars["poi"]) diff --git a/luserver/scripts/general/shark_death_trigger.py b/luserver/scripts/general/shark_death_trigger.py index 539e988..8194708 100644 --- a/luserver/scripts/general/shark_death_trigger.py +++ b/luserver/scripts/general/shark_death_trigger.py @@ -9,7 +9,7 @@ class ScriptComponent(script.ScriptComponent): def on_enter(self, player): player.destructible.simply_die(death_type=DEATH_ANIMATION, killer=self.object) - def fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): + def on_fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): if args == "achieve": for achievement_id in EATEN_BY_SHARK_ACHIEVEMENTS: - sender.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=achievement_id) + sender.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=achievement_id) diff --git a/luserver/scripts/general/teleport_to_ns_or_nt.py b/luserver/scripts/general/teleport_to_ns_or_nt.py index 203c7d9..31cfe25 100644 --- a/luserver/scripts/general/teleport_to_ns_or_nt.py +++ b/luserver/scripts/general/teleport_to_ns_or_nt.py @@ -10,9 +10,9 @@ class ScriptComponent(script.ScriptComponent): def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: 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) + player.char.ui.disp_message_box(id="TransferBox", text="UI_TRAVEL_TO_LUP_STATION", callback=self.object) - def message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): + def on_message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): if id == "TransferBox": if button == 1: # todo: display zone summary (callback not working right now for some reason) diff --git a/luserver/scripts/general/touch_complete_mission.py b/luserver/scripts/general/touch_complete_mission.py index bcde715..14cb363 100644 --- a/luserver/scripts/general/touch_complete_mission.py +++ b/luserver/scripts/general/touch_complete_mission.py @@ -3,4 +3,4 @@ from luserver.components.mission import TaskType class ScriptComponent(script.ScriptComponent): def on_enter(self, player): - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=self.script_vars["touch_complete_mission_id"]) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=self.script_vars["touch_complete_mission_id"]) diff --git a/luserver/scripts/general/transfer_to_last_non_instance.py b/luserver/scripts/general/transfer_to_last_non_instance.py index ce0fc0c..db42ee6 100644 --- a/luserver/scripts/general/transfer_to_last_non_instance.py +++ b/luserver/scripts/general/transfer_to_last_non_instance.py @@ -7,8 +7,8 @@ from luserver.game_object import c_int, EI, ES, Player class ScriptComponent(script.ScriptComponent): def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: assert multi_interact_id is None - player.char.disp_message_box(self.script_vars.get("transfer_text", "DRAGON_EXIT_QUESTION"), id="instance_exit", callback=self.object) + player.char.ui.disp_message_box(self.script_vars.get("transfer_text", "DRAGON_EXIT_QUESTION"), id="instance_exit", callback=self.object) - def message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): + def on_message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): if id == "instance_exit" and button == 1: asyncio.ensure_future(player.char.transfer_to_last_non_instance()) diff --git a/luserver/scripts/gnarled_forest/banana_tree.py b/luserver/scripts/gnarled_forest/banana_tree.py index 990d1b3..18e09c4 100644 --- a/luserver/scripts/gnarled_forest/banana_tree.py +++ b/luserver/scripts/gnarled_forest/banana_tree.py @@ -11,7 +11,7 @@ class ScriptComponent(script.ScriptComponent): def spawn_banana(self): if self.banana is None: self.banana = server.spawn_object(6909, {"position": self.banana_pos(), "rotation": self.object.physics.rotation}) - self.banana.add_handler("on_death", self.on_banana_death) + self.banana.add_handler("death", self.on_banana_death) def on_hit(self, damage, attacker): self.object.stats.life = self.object.stats.max_life # indestructible @@ -19,7 +19,7 @@ class ScriptComponent(script.ScriptComponent): self.banana.destructible.simply_die(kill_type=KillType.Silent, killer=self.object) self.banana = None falling_banana = server.spawn_object(6718, {"position": self.banana_pos(), "rotation": self.object.physics.rotation}) - falling_banana.add_handler("on_death", self.on_banana_death) + falling_banana.add_handler("death", self.on_banana_death) def banana_pos(self): offset = Vector3(-5, 12, 0) diff --git a/luserver/scripts/gnarled_forest/bomb_crate.py b/luserver/scripts/gnarled_forest/bomb_crate.py index 47867ca..ca6da90 100644 --- a/luserver/scripts/gnarled_forest/bomb_crate.py +++ b/luserver/scripts/gnarled_forest/bomb_crate.py @@ -31,4 +31,4 @@ class ScriptComponent(script.ScriptComponent): self.object.destructible.simply_die(killer=self.object) for mission_id in MISSIONS: - attacker.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=mission_id) + attacker.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=mission_id) diff --git a/luserver/scripts/gnarled_forest/campfire.py b/luserver/scripts/gnarled_forest/campfire.py index 733e540..dcf4d71 100644 --- a/luserver/scripts/gnarled_forest/campfire.py +++ b/luserver/scripts/gnarled_forest/campfire.py @@ -19,7 +19,7 @@ class ScriptComponent(script.ScriptComponent): def on_enter(self, player): if self.is_burning: - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=440) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=440) self.object.skill.cast_skill(SKILL_ID) def on_skill_event(self, caster, event_name): diff --git a/luserver/scripts/gnarled_forest/jailkeep.py b/luserver/scripts/gnarled_forest/jailkeep.py index 2d83633..d77e2c5 100644 --- a/luserver/scripts/gnarled_forest/jailkeep.py +++ b/luserver/scripts/gnarled_forest/jailkeep.py @@ -6,7 +6,7 @@ FEED_NINJAS_MISSION = 385 FEED_NINJAS_MISSIONS = [386, 387, 388, 390] class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == FEED_NINJAS_MISSION and mission_state == MissionState.Available: for mission in FEED_NINJAS_MISSIONS: - player.char.add_mission(mission) + player.char.mission.add_mission(mission) diff --git a/luserver/scripts/gnarled_forest/torch.py b/luserver/scripts/gnarled_forest/torch.py index 3b6457d..de53082 100644 --- a/luserver/scripts/gnarled_forest/torch.py +++ b/luserver/scripts/gnarled_forest/torch.py @@ -29,6 +29,6 @@ class ScriptComponent(script.ScriptComponent): self.object.render.stop_f_x_effect(name=b"tikitorch") self.object.render.play_f_x_effect(name=b"", effect_type="water", effect_id=611) self.object.render.play_f_x_effect(name=b"", effect_type="steam", effect_id=611) - caster.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=WATER_MISSION) + caster.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=WATER_MISSION) self.object.call_later(8, self.light_torch) diff --git a/luserver/scripts/items/maelstrom_vacuum.py b/luserver/scripts/items/maelstrom_vacuum.py index 78b0426..f74a792 100644 --- a/luserver/scripts/items/maelstrom_vacuum.py +++ b/luserver/scripts/items/maelstrom_vacuum.py @@ -5,7 +5,7 @@ class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: self.object.render.play_animation("idle_maelstrom") - def fire_event_server_side(self, player, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): + def on_fire_event_server_side(self, player, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): if args == "attemptCollection": sample = sender sample.destructible.deal_damage(1, player) diff --git a/luserver/scripts/nexus_tower/paradox_teleporter.py b/luserver/scripts/nexus_tower/paradox_teleporter.py index 034fd6d..f5da712 100644 --- a/luserver/scripts/nexus_tower/paradox_teleporter.py +++ b/luserver/scripts/nexus_tower/paradox_teleporter.py @@ -10,6 +10,6 @@ class ScriptComponent(script.ScriptComponent): break def teleport(self, player, obj): - player.char.play_cinematic(path_name=self.script_vars["cinematic"], start_time_advance=0) + player.char.camera.play_cinematic(path_name=self.script_vars["cinematic"], start_time_advance=0) player.char.teleport(ignore_y=False, pos=obj.physics.position, set_rotation=True, x=obj.physics.rotation.x, y=obj.physics.rotation.y, z=obj.physics.rotation.z, w=obj.physics.rotation.w) player.render.play_animation("paradox-teleport-in", play_immediate=True, priority=4) diff --git a/luserver/scripts/nexus_tower/vault.py b/luserver/scripts/nexus_tower/vault.py index b66721f..e9e6cc6 100644 --- a/luserver/scripts/nexus_tower/vault.py +++ b/luserver/scripts/nexus_tower/vault.py @@ -8,6 +8,6 @@ class ScriptComponent(script.ScriptComponent): def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: player.char.u_i_message_server_to_single_client(message_name=b"pushGameState", args=AMF3({"state": "bank"})) - def fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): + def on_fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): if args == "ToggleBank": sender.char.u_i_message_server_to_single_client(message_name=b"ToggleBank", args=AMF3({"visible": False})) diff --git a/luserver/scripts/nimbus_station/car_modular_build.py b/luserver/scripts/nimbus_station/car_modular_build.py index cdf9bec..aae991a 100644 --- a/luserver/scripts/nimbus_station/car_modular_build.py +++ b/luserver/scripts/nimbus_station/car_modular_build.py @@ -6,6 +6,6 @@ from luserver.game_object import E, Sequence CAR_MISSION = 623 class ScriptComponent(script.ScriptComponent): - def modular_build_finish(self, player, module_lots:Sequence[c_ubyte, c_int]=E): - if CAR_MISSION in player.char.missions and player.char.missions[CAR_MISSION].state == MissionState.Active: - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=CAR_MISSION) + def on_modular_build_finish(self, player, module_lots:Sequence[c_ubyte, c_int]=E): + if CAR_MISSION in player.char.mission.missions and player.char.mission.missions[CAR_MISSION].state == MissionState.Active: + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=CAR_MISSION) diff --git a/luserver/scripts/nimbus_station/concert_instrument.py b/luserver/scripts/nimbus_station/concert_instrument.py index 27ec21d..b7ecd1f 100644 --- a/luserver/scripts/nimbus_station/concert_instrument.py +++ b/luserver/scripts/nimbus_station/concert_instrument.py @@ -47,19 +47,19 @@ class ScriptComponent(script.ScriptComponent): def on_destruction(self) -> None: if self.player is not None: self.stop_playing() - self.player.remove_handler("on_destruction", self.on_player_destruction) + self.player.remove_handler("destruction", self.on_player_destruction) - def complete_rebuild(self, player): + def on_complete_rebuild(self, player): self.player = player # todo: fix this # disabling for now because handlers accidentally get saved to DB - #self.player.add_handler("on_destruction", self.on_player_destruction) + #self.player.add_handler("destruction", self.on_player_destruction) self.object.call_later(0.1, self.play) def play(self): config = CONFIG[self.object.lot] self.notify_client_object(name="startPlaying", param1=0, param2=0, param_str=b"", param_obj=self.player) - self.player.char.play_cinematic(path_name=config["cinematic"], start_time_advance=0) + self.player.char.camera.play_cinematic(path_name=config["cinematic"], start_time_advance=0) self.player.render.play_animation(config["play_anim"]) offset = config["player_offset"] @@ -85,7 +85,7 @@ class ScriptComponent(script.ScriptComponent): return if self.object.lot == GUITAR: - self.player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=176) + self.player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=176) self.notify_client_object(name="stopCheckingMovement", param1=0, param2=0, param_str=b"", param_obj=self.player) self.player.char.end_cinematic(lead_out=1, path_name="") @@ -100,6 +100,6 @@ class ScriptComponent(script.ScriptComponent): def on_player_destruction(self, player): self.object.destructible.simply_die() - def fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:GameObject=EO): + def on_fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:GameObject=EO): if args == "stopPlaying": self.stop_playing() diff --git a/luserver/scripts/nimbus_station/concert_quickbuild.py b/luserver/scripts/nimbus_station/concert_quickbuild.py index 6ff8584..61b06d3 100644 --- a/luserver/scripts/nimbus_station/concert_quickbuild.py +++ b/luserver/scripts/nimbus_station/concert_quickbuild.py @@ -4,5 +4,5 @@ from luserver.components.mission import TaskType # todo: not fully implemented class ScriptComponent(script.ScriptComponent): - def complete_rebuild(self, player): - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=283) + def on_complete_rebuild(self, player): + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=283) diff --git a/luserver/scripts/nimbus_station/imagination_statue.py b/luserver/scripts/nimbus_station/imagination_statue.py index 6782110..882382d 100644 --- a/luserver/scripts/nimbus_station/imagination_statue.py +++ b/luserver/scripts/nimbus_station/imagination_statue.py @@ -5,6 +5,6 @@ SPAWN_AMOUNT = 10 SPAWN_INTERVAL = 1.5 class ScriptComponent(script.ScriptComponent): - def complete_rebuild(self, player): + def on_complete_rebuild(self, player): for i in range(SPAWN_AMOUNT): self.object.call_later(i*SPAWN_INTERVAL, self.object.physics.drop_loot, IMAGINATION_POWERUP_LOT, player) diff --git a/luserver/scripts/nimbus_station/johnny_thunder.py b/luserver/scripts/nimbus_station/johnny_thunder.py index 1b1ece9..c49ee89 100644 --- a/luserver/scripts/nimbus_station/johnny_thunder.py +++ b/luserver/scripts/nimbus_station/johnny_thunder.py @@ -3,9 +3,9 @@ from luserver.game_object import c_int, EB, EI, EP, Player from luserver.components.mission import MissionState class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == 773 and mission_state == MissionState.Available: - player.char.add_mission(774) - player.char.add_mission(775) - player.char.add_mission(776) - player.char.add_mission(777) + player.char.mission.add_mission(774) + player.char.mission.add_mission(775) + player.char.mission.add_mission(776) + player.char.mission.add_mission(777) diff --git a/luserver/scripts/nimbus_station/lego_club_door.py b/luserver/scripts/nimbus_station/lego_club_door.py index 7c1235a..feca013 100644 --- a/luserver/scripts/nimbus_station/lego_club_door.py +++ b/luserver/scripts/nimbus_station/lego_club_door.py @@ -14,11 +14,11 @@ class ScriptComponent(script.ScriptComponent): 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) + player.char.ui.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=EI, id:str=ES, user_data:str=ES): + def on_message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): if id == "PlayButton": self.transfer(player, (1700, 0, 0), "") elif id == "TransferBox": diff --git a/luserver/scripts/nimbus_station/lup_teleport.py b/luserver/scripts/nimbus_station/lup_teleport.py index 746c3bd..57b0e15 100644 --- a/luserver/scripts/nimbus_station/lup_teleport.py +++ b/luserver/scripts/nimbus_station/lup_teleport.py @@ -16,9 +16,9 @@ class ScriptComponent(script.ScriptComponent): text = "UI_TRAVEL_TO_NS" else: text = "UI_TRAVEL_TO_LUP_STATION" - player.char.disp_message_box(id="TransferBox", text=text, callback=self.object) + player.char.ui.disp_message_box(id="TransferBox", text=text, callback=self.object) - def message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): + def on_message_box_respond(self, player, button:c_int=EI, id:str=ES, user_data:str=ES): if id == "TransferBox": if button == 1: # todo: display zone summary (callback not working right now for some reason) diff --git a/luserver/scripts/nimbus_station/nexus_jay.py b/luserver/scripts/nimbus_station/nexus_jay.py index 740a8dd..b71a55a 100644 --- a/luserver/scripts/nimbus_station/nexus_jay.py +++ b/luserver/scripts/nimbus_station/nexus_jay.py @@ -40,7 +40,7 @@ class ScriptComponent(script.ScriptComponent): player.char.start_celebration_effect(animation="", duration=0, icon_id=0, main_text="", mixer_program=b"", music_cue=b"", path_node_name=b"", sound_guid=b"", sub_text="", celebration_id=celebration_id) achievements.append(778) - player.char.add_mission(achievements[0]) # for some reason not an achievement, needs to be added manually + player.char.mission.add_mission(achievements[0]) # for some reason not an achievement, needs to be added manually for achievement_id in achievements: - player.char.complete_mission(achievement_id) + player.char.mission.complete_mission(achievement_id) diff --git a/luserver/scripts/nimbus_station/racetrack/world_control.py b/luserver/scripts/nimbus_station/racetrack/world_control.py index 72d5d2d..249f3c2 100644 --- a/luserver/scripts/nimbus_station/racetrack/world_control.py +++ b/luserver/scripts/nimbus_station/racetrack/world_control.py @@ -5,7 +5,6 @@ from luserver.math.vector import Vector3 from luserver.components.racing_control import RacingNotificationType class ScriptComponent(script.ScriptComponent): - @single def player_ready(self, player: Player) -> None: player.char.teleport(ignore_y=False, pos=Vector3(817.812255859375, 247.02963256835938, -3.9437739849090576), x=0, y=0, z=0) self.object.scripted_activity.add_player(player) @@ -18,6 +17,9 @@ class ScriptComponent(script.ScriptComponent): self.object.racing_control.notify_racing_client(event_type=RacingNotificationType.ActivityStart, param1=0, param_obj=0, param_str="", single_client=0) self.object.scripted_activity.activity_start() + player_ready = single(player_ready) + on_player_ready = player_ready + @broadcast def racing_player_loaded(self, player:GameObject=EO, vehicle:GameObject=EO): pass diff --git a/luserver/scripts/nimbus_station/rocket_modular_build.py b/luserver/scripts/nimbus_station/rocket_modular_build.py index 65fac72..58852ac 100644 --- a/luserver/scripts/nimbus_station/rocket_modular_build.py +++ b/luserver/scripts/nimbus_station/rocket_modular_build.py @@ -7,8 +7,8 @@ MARDOLF_ROCKET_MISSION = 809 ROCKET_MISSION_PARTS = 9516, 9517, 9518 class ScriptComponent(script.ScriptComponent): - def modular_build_finish(self, player, module_lots:Sequence[c_ubyte, c_int]=E): - if MARDOLF_ROCKET_MISSION in player.char.missions and player.char.missions[MARDOLF_ROCKET_MISSION].state == MissionState.Active: + def on_modular_build_finish(self, player, module_lots:Sequence[c_ubyte, c_int]=E): + if MARDOLF_ROCKET_MISSION in player.char.mission.missions and player.char.mission.missions[MARDOLF_ROCKET_MISSION].state == MissionState.Active: for lot in module_lots: if lot in ROCKET_MISSION_PARTS: - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=MARDOLF_ROCKET_MISSION) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=MARDOLF_ROCKET_MISSION) diff --git a/luserver/scripts/nimbus_station/token_console.py b/luserver/scripts/nimbus_station/token_console.py index 203aeaf..04ea8e5 100644 --- a/luserver/scripts/nimbus_station/token_console.py +++ b/luserver/scripts/nimbus_station/token_console.py @@ -13,8 +13,8 @@ INTERACT_MISSION = 863 class ScriptComponent(script.ScriptComponent): def on_use(self, player: Player, multi_interact_id: Optional[int]) -> None: assert multi_interact_id is None - if INTERACT_MISSION in player.char.missions: + if INTERACT_MISSION in player.char.mission.missions: player.inventory.remove_item(InventoryType.Items, lot=MAELSTROM_BRICK, count=BRICKS_TO_TAKE) player.inventory.add_item(player.char.faction_token_lot(), count=TOKENS_TO_GIVE) - player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=INTERACT_MISSION) + player.char.mission.update_mission_task(TaskType.Script, self.object.lot, mission_id=INTERACT_MISSION) diff --git a/luserver/scripts/ninjago/ninja.py b/luserver/scripts/ninjago/ninja.py index ea9a829..6b1141f 100644 --- a/luserver/scripts/ninjago/ninja.py +++ b/luserver/scripts/ninjago/ninja.py @@ -16,7 +16,7 @@ flag = { 1962: 2033} class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_state == MissionState.ReadyToComplete: if mission_id in package: player.inventory.remove_item(InventoryType.Items, lot=package[mission_id]) diff --git a/luserver/scripts/pet_cove/coalessa.py b/luserver/scripts/pet_cove/coalessa.py index d468fcb..4b1016d 100644 --- a/luserver/scripts/pet_cove/coalessa.py +++ b/luserver/scripts/pet_cove/coalessa.py @@ -2,6 +2,6 @@ import luserver.components.script as script from luserver.game_object import c_int, EP, ES, Player class ScriptComponent(script.ScriptComponent): - def fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): + def on_fire_event_server_side(self, args:str=ES, param1:c_int=-1, param2:c_int=-1, param3:c_int=-1, sender:Player=EP): if args == "unlockEmote": sender.char.set_emote_lock_state(emote_id=115, lock=False) diff --git a/luserver/scripts/property/teleport.py b/luserver/scripts/property/teleport.py index d7379cd..d9c5753 100644 --- a/luserver/scripts/property/teleport.py +++ b/luserver/scripts/property/teleport.py @@ -1,7 +1,10 @@ +from typing import cast + import luserver.components.script as script +from luserver.game_object import PhysicsObject from luserver.world import server class ScriptComponent(script.ScriptComponent): def on_enter(self, player): - teleport_obj = server.get_objects_in_group("Teleport")[0] + teleport_obj = cast(PhysicsObject, server.get_objects_in_group("Teleport")[0]) player.char.teleport(ignore_y=False, pos=teleport_obj.physics.position, set_rotation=True, x=teleport_obj.physics.rotation.x, y=teleport_obj.physics.rotation.y, z=teleport_obj.physics.rotation.z, w=teleport_obj.physics.rotation.w) diff --git a/luserver/scripts/venture_explorer/bob.py b/luserver/scripts/venture_explorer/bob.py index fd1c9c4..d33768d 100644 --- a/luserver/scripts/venture_explorer/bob.py +++ b/luserver/scripts/venture_explorer/bob.py @@ -3,7 +3,10 @@ from luserver.game_object import c_int, EB, EI, EP, Player from luserver.components.mission import MissionState class ScriptComponent(script.ScriptComponent): - def mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): + def on_mission_dialogue_o_k(self, is_complete:bool=EB, mission_state:c_int=EI, mission_id:c_int=EI, player:Player=EP): if mission_id == 173 and mission_state == MissionState.ReadyToComplete: - player.stats.imagination = 6 - player.char.complete_mission(664) + player.char.mission.complete_mission(664) + self.object.call_later(0.1, self._set_imagination, player) + + def _set_imagination(self, player: Player) -> None: + player.stats.imagination = 6 diff --git a/luserver/scripts/venture_explorer/imagination_smashable.py b/luserver/scripts/venture_explorer/imagination_smashable.py index 3d5265c..1d76a51 100644 --- a/luserver/scripts/venture_explorer/imagination_smashable.py +++ b/luserver/scripts/venture_explorer/imagination_smashable.py @@ -1,7 +1,7 @@ import random import luserver.components.script as script -from luserver.game_object import broadcast, c_uint, EF, EO, ES, GameObject, OBJ_NONE, Player +from luserver.game_object import GameObject from luserver.components.mission import MissionState # crate chicken easter egg not implemented @@ -10,12 +10,10 @@ BOB_IMAGINATION_MISSION_ID = 173 IMAGINATION_POWERUP_LOT = 935 class ScriptComponent(script.ScriptComponent): - @broadcast - def die(self, client_death:bool=False, spawn_loot:bool=True, death_type:str=ES, direction_relative_angle_xz:float=EF, direction_relative_angle_y:float=EF, direction_relative_force:float=EF, kill_type:c_uint=0, killer:GameObject=EO, loot_owner:Player=OBJ_NONE): - player = loot_owner - if BOB_IMAGINATION_MISSION_ID not in player.char.missions: + def on_death(self, killer: GameObject) -> None: + if BOB_IMAGINATION_MISSION_ID not in killer.char.mission.missions: return - mission = player.char.missions[BOB_IMAGINATION_MISSION_ID] + mission = killer.char.mission.missions[BOB_IMAGINATION_MISSION_ID] if mission.state == MissionState.Completed: for _ in range(random.randint(1, 2)): - self.object.physics.drop_loot(IMAGINATION_POWERUP_LOT, player) + self.object.physics.drop_loot(IMAGINATION_POWERUP_LOT, killer) diff --git a/luserver/scripts/venture_explorer/ship_shake.py b/luserver/scripts/venture_explorer/ship_shake.py index c13c361..4f3eda0 100644 --- a/luserver/scripts/venture_explorer/ship_shake.py +++ b/luserver/scripts/venture_explorer/ship_shake.py @@ -1,6 +1,8 @@ import random +from typing import cast import luserver.components.script as script +from luserver.game_object import RenderObject from luserver.world import server BASE_SHAKE_TIME = 2 @@ -11,8 +13,8 @@ SHAKE_RADIUS = 500 class ScriptComponent(script.ScriptComponent): def on_startup(self) -> None: - self.ship_fx_obj = server.get_objects_in_group("ShipFX")[0] - self.ship_fx2_obj = server.get_objects_in_group("ShipFX2")[0] + self.ship_fx_obj = cast(RenderObject, server.get_objects_in_group("ShipFX")[0]) + self.ship_fx2_obj = cast(RenderObject, server.get_objects_in_group("ShipFX2")[0]) self.object.call_later(BASE_SHAKE_TIME, self.shake) def shake(self): diff --git a/luserver/world.py b/luserver/world.py index c19a45b..7bf83e6 100644 --- a/luserver/world.py +++ b/luserver/world.py @@ -68,7 +68,7 @@ import importlib.util import logging import os.path from contextlib import AbstractContextManager as ACM -from typing import Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, cast, Dict, List, Tuple import BTrees import ZODB @@ -80,8 +80,8 @@ from pyraknet.messages import Address from pyraknet.replicamanager import ReplicaManager from pyraknet.server import Event from .auth import Account -from .commonserver import DisconnectReason, Server -from .game_object import CallbackID, Config, GameObject, ObjectID, Player +from .commonserver import DisconnectReason, Server, WorldData +from .game_object import CallbackID, Config, GameObject, ObjectID, Player, ScriptObject, SpawnerObject from .messages import WorldServerMsg from .math.vector import Vector3 from .math.quaternion import Quaternion @@ -125,7 +125,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: Dict[str, Callable[..., None]] = {} + self._handlers: Dict[str, List[Callable[..., None]]] = {} self.char = CharHandling() self.chat = ChatHandling() self.general = GeneralHandling() @@ -137,11 +137,11 @@ class WorldServer(Server): self.commit() self.current_object_id = 0 self.current_spawned_id = BITS_SPAWNED - self.world_data = None + self.world_data: WorldData = None self.game_objects: Dict[ObjectID, GameObject] = {} self.models = [] self.last_callback_id = CallbackID(0) - self.callback_handles: Dict[ObjectID, asyncio.Handle] = {} + self.callback_handles: Dict[ObjectID, Dict[CallbackID, asyncio.Handle]] = {} self.accounts: Dict[Address, Account] = {} atexit.register(self.shutdown) asyncio.get_event_loop().call_later(60 * 60, self._check_shutdown) @@ -180,12 +180,16 @@ class WorldServer(Server): custom_script, world_control_lot = self.db.world_info[self.world_id[0]] if world_control_lot is None: world_control_lot = 2365 - self.world_control_object = self.spawn_object(world_control_lot, set_vars={"custom_script": custom_script}, is_world_control=True) + self.world_control_object = cast(ScriptObject, self.spawn_object(world_control_lot, set_vars={"custom_script": custom_script}, is_world_control=True)) - self.spawners: Dict[str, GameObject] = {} - self.world_data = self.db.world_data[self.world_id[0]] + self.spawners: Dict[str, SpawnerObject] = {} + wd = self.db.world_data[self.world_id[0]] + objs: Dict[ObjectID, GameObject] = {} + for id, data in wd.objects.items(): + objs[id] = GameObject(*data) + self.world_data = WorldData(objs, wd.paths, wd.spawnpoint) for obj in self.world_data.objects.values(): - obj.handle("on_startup", silent=True) + obj.handle("startup", silent=True) if self.world_id[2] != 0: self.models = [] for spawner_id, spawn_data in self.db.properties[self.world_id[0]][self.world_id[2]].items(): @@ -206,7 +210,7 @@ class WorldServer(Server): raise RuntimeError("handler not found") self._handlers[event_name].remove(handler) - def handle(self, event_name: str, *args) -> None: + def handle(self, event_name: str, *args: Any) -> None: if event_name not in WorldServer.EVENT_NAMES: raise ValueError("Invalid event name %s", event_name) if event_name not in self._handlers: @@ -227,7 +231,7 @@ class WorldServer(Server): def spawn_model(self, spawner_id: ObjectID, lot: int, position: Vector3, rotation: Quaternion) -> None: spawned_vars = {"position": position, "rotation": rotation} spawner_vars = {"spawntemplate": lot, "spawner_waypoints": spawned_vars} - spawner = GameObject(176, spawner_id, set_vars=spawner_vars) + spawner = cast(SpawnerObject, GameObject(176, spawner_id, set_vars=spawner_vars)) self.models.append((spawner, spawner.spawner.spawn())) def _on_disconnect_or_connection_lost(self, address: Address) -> None: @@ -311,18 +315,19 @@ class WorldServer(Server): obj = GameObject(lot, object_id, set_vars) self.game_objects[obj.object_id] = obj self.replica_manager.construct(obj) - obj.handle("on_startup", silent=True) + obj.handle("startup", silent=True) self.handle("spawn", obj) return obj - def get_object(self, object_id: ObjectID) -> Optional[GameObject]: + def get_object(self, object_id: ObjectID) -> GameObject: if object_id == 0: - return + raise ValueError if object_id in self.game_objects: 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.warning("Object %i not found", object_id) + raise KeyError(object_id) def get_objects_in_group(self, group: str) -> List[GameObject]: matches = [] diff --git a/runtime/db/luz_importer.py b/runtime/db/luz_importer.py index 551fe5c..0a1f7bb 100644 --- a/runtime/db/luz_importer.py +++ b/runtime/db/luz_importer.py @@ -6,7 +6,7 @@ import BTrees import luserver.world from pyraknet.bitstream import c_bool, c_float, c_int, c_int64, c_ubyte, c_uint, c_uint64, ReadStream -from luserver.game_object import GameObject +from luserver.commonserver import WorldData from luserver.ldf import LDF from luserver.world import World from luserver.math.quaternion import Quaternion @@ -55,7 +55,7 @@ def import_data(root, maps_path): luz_path = os.path.join(maps_path, luz_path) if world == World.KeelhaulCanyon: continue - root.world_data[world.value] = SimpleNamespace(objects=BTrees.OOBTree.BTree(), paths=BTrees.OOBTree.BTree(), spawnpoint=(Vector3(), Quaternion())) + root.world_data[world.value] = WorldData(BTrees.OOBTree.BTree(), BTrees.OOBTree.BTree(), (Vector3(), Quaternion())) _LUZImporter(luz_path, root, root.world_data[world.value]) @@ -250,5 +250,4 @@ class _LUZImporter: continue spawner_vars["spawner_name"] = path_name spawner_vars["spawner_waypoints"] = waypoints - spawner = GameObject(176, object_id, spawner_vars) - self.world_data.objects[object_id] = spawner + self.world_data.objects[object_id] = 176, object_id, spawner_vars diff --git a/runtime/db/lvl_importer.py b/runtime/db/lvl_importer.py index d6faac8..122e629 100644 --- a/runtime/db/lvl_importer.py +++ b/runtime/db/lvl_importer.py @@ -1,5 +1,4 @@ from pyraknet.bitstream import c_float, c_int64, c_ubyte, c_uint, c_ushort, ReadStream -from luserver.game_object import GameObject from luserver.ldf import LDF from luserver.world import BITS_LOCAL from luserver.math.quaternion import Quaternion @@ -204,5 +203,4 @@ class LVLImporter: spawner_vars["spawner_waypoints"] = spawned_vars, spawned_vars = spawner_vars - obj = GameObject(lot, object_id, spawned_vars) - self.world_data.objects[object_id] = obj + self.world_data.objects[object_id] = lot, object_id, spawned_vars diff --git a/runtime/plugins/commands/camera.py b/runtime/plugins/commands/camera.py index c27efcc..fc99ade 100644 --- a/runtime/plugins/commands/camera.py +++ b/runtime/plugins/commands/camera.py @@ -39,7 +39,7 @@ class CamLookAt(ChatCommand): config.ldf_set("lag", LDFDataType.FLOAT, args.lag) config.ldf_set("FOV", LDFDataType.FLOAT, args.fov) config.ldf_set("verticalOffset", LDFDataType.FLOAT, args.voffset) - sender.char.add_camera_effect(config, effect_id="lookat", effect_type="lookAt", duration=args.duration) + sender.char.camera.add_camera_effect(config, effect_id="lookat", effect_type="lookAt", duration=args.duration) class CamShake(ChatCommand): def __init__(self): @@ -69,7 +69,7 @@ class CamShake(ChatCommand): config.ldf_set("xRotation", LDFDataType.FLOAT, args.rot.x) config.ldf_set("yRotation", LDFDataType.FLOAT, args.rot.y) config.ldf_set("zRotation", LDFDataType.FLOAT, args.rot.z) - sender.char.add_camera_effect(config, effect_id="shake", effect_type="shake", duration=args.duration) + sender.char.camera.add_camera_effect(config, effect_id="shake", effect_type="shake", duration=args.duration) class CamReset(ChatCommand): def __init__(self): diff --git a/runtime/plugins/commands/misc.py b/runtime/plugins/commands/misc.py index 34095c0..acc7182 100644 --- a/runtime/plugins/commands/misc.py +++ b/runtime/plugins/commands/misc.py @@ -2,6 +2,7 @@ import asyncio import logging from luserver.amf3 import AMF3 +from luserver.game_object import Player from luserver.ldf import LDF, LDFDataType from luserver.world import server, World from luserver.interfaces.plugin import ChatCommand, instance_obj, instance_player, normal_bool, toggle_bool @@ -93,18 +94,18 @@ class Freeze(ChatCommand): for obj in server.game_objects.values(): if hasattr(obj, "render") and (not self.enabled or obj != sender): obj.render.freeze_animation(do_freeze=self.enabled) - if hasattr(obj, "char"): + if isinstance(obj, Player): #obj.render.play_f_x_effect(name=b"", effect_type="on-anim", effect_id=4747) if self.enabled and obj != sender: self.velocity[obj] = Vector3(obj.physics.velocity) obj.char.set_gravity_scale(0) obj.char.teleport(ignore_y=False, pos=obj.physics.position-Vector3.up*2, x=0, y=0, z=0) - obj.char.force_camera_target_cycle(optional_target=sender) + obj.char.camera.force_camera_target_cycle(optional_target=sender) else: obj.char.set_gravity_scale(1) if obj in self.velocity and self.velocity[obj].sq_magnitude() > 0.01: obj.char.knockback(vector=self.velocity[obj]) - obj.char.force_camera_target_cycle(optional_target=obj) + obj.char.camera.force_camera_target_cycle(optional_target=obj) if not self.enabled or obj != sender: obj.char.set_user_ctrl_comp_pause(paused=self.enabled) @@ -248,7 +249,7 @@ class Speak(ChatCommand): def run(self, args, sender): for obj in server.game_objects.values(): if obj.name == args.object: - obj.add_handler("on_private_chat_message", Speak.speak) + obj.add_handler("private_chat_message", Speak.speak) server.chat.send_private_chat_message("!"+args.object, "Everything you type will be spoken by the selected object.", sender) return server.chat.sys_msg_sender("Object not found") @@ -263,7 +264,7 @@ class Spectate(ChatCommand): self.command.add_argument("obj", type=instance_obj) def run(self, args, sender): - sender.char.force_camera_target_cycle(optional_target=args.obj) + sender.char.camera.force_camera_target_cycle(optional_target=args.obj) class Tags(ChatCommand): def __init__(self): diff --git a/runtime/plugins/commands/mission.py b/runtime/plugins/commands/mission.py index 6255e96..89014f2 100644 --- a/runtime/plugins/commands/mission.py +++ b/runtime/plugins/commands/mission.py @@ -12,9 +12,9 @@ class AddMission(ChatCommand): def run(self, args, sender): if args.mission in MISSIONS: for mission_id in MISSIONS[args.mission]: - sender.char.add_mission(mission_id) + sender.char.mission.add_mission(mission_id) else: - sender.char.add_mission(int(args.mission)) + sender.char.mission.add_mission(int(args.mission)) class CompleteMission(ChatCommand): def __init__(self): @@ -49,13 +49,13 @@ class CompleteMission(ChatCommand): return missions def async_complete_mission(self, mission_id, fully, sender): - if mission_id not in sender.char.missions: - sender.char.add_mission(mission_id) + if mission_id not in sender.char.mission.missions: + sender.char.mission.add_mission(mission_id) if fully: - sender.char.complete_mission(mission_id) + sender.char.mission.complete_mission(mission_id) else: - mission = sender.char.missions[mission_id] + mission = sender.char.mission.missions[mission_id] if mission.state == MissionState.Active: for task in mission.tasks: if isinstance(task.target, tuple): @@ -66,7 +66,7 @@ class CompleteMission(ChatCommand): parameter = task.parameter[0] else: parameter = None - sender.char.update_mission_task(task.type, target, parameter, increment=task.target_value, mission_id=mission_id) + sender.char.mission.update_mission_task(task.type, target, parameter, increment=task.target_value, mission_id=mission_id) class RemoveMission(ChatCommand): def __init__(self): @@ -74,8 +74,8 @@ class RemoveMission(ChatCommand): self.command.add_argument("id", type=int) def run(self, args, sender): - if args.id in sender.char.missions: - del sender.char.missions[args.id] + if args.id in sender.char.mission.missions: + del sender.char.mission.missions[args.id] server.chat.sys_msg_sender("Mission removed") else: server.chat.sys_msg_sender("Mission not found") @@ -85,12 +85,12 @@ class ResetMissions(ChatCommand): super().__init__("resetmissions") def run(self, args, sender): - sender.char.missions.clear() + sender.char.mission.missions.clear() # add achievements for mission_id, data in server.db.missions.items(): is_mission = data[3] # if False, it's an achievement (internally works the same as missions, that's why the naming is weird) if not is_mission: - sender.char.missions[mission_id] = MissionProgress(mission_id, data) + sender.char.mission.missions[mission_id] = MissionProgress(mission_id, data) MISSIONS = { "VE": [1727, 173, 660, 1896, 308, 1732], diff --git a/runtime/plugins/commands/play.py b/runtime/plugins/commands/play.py index ea0ee44..3cdf9d3 100644 --- a/runtime/plugins/commands/play.py +++ b/runtime/plugins/commands/play.py @@ -22,7 +22,7 @@ class PlayCine(ChatCommand): if args.hideui: # hide HUD sender.char.u_i_message_server_to_single_client(message_name=b"pushGameState", args=AMF3({"state": "front_end"})) - sender.char.play_cinematic(path_name=args.name, send_server_notify=args.hideui, hide_player_during_cine=not args.showplayer, start_time_advance=0) + sender.char.camera.play_cinematic(path_name=args.name, send_server_notify=args.hideui, hide_player_during_cine=not args.showplayer, start_time_advance=0) class PlayEmote(ChatCommand): def __init__(self): diff --git a/runtime/plugins/commands/server.py b/runtime/plugins/commands/server.py index eb67f8c..d6c75de 100644 --- a/runtime/plugins/commands/server.py +++ b/runtime/plugins/commands/server.py @@ -6,10 +6,12 @@ import os import re import secrets import time +from typing import cast from pyraknet.bitstream import c_bool, c_ushort from luserver.auth import Account, GMLevel, PasswordState from luserver.bitstream import WriteStream +from luserver.game_object import Player from luserver.ldf import LDF, LDFDataType from luserver.messages import WorldClientMsg from luserver.world import server @@ -45,7 +47,8 @@ class Ban(ChatCommand): def run(self, args, sender): for obj in server.game_objects.values(): - if obj.name == args.player: + if obj.lot == 1 and obj.name == args.player: + obj = cast(Player, obj) ban_duration = datetime.timedelta(args.days, 0, 0, 0, args.minutes, args.hours, args.weeks) banned_until = time.time() + ban_duration.total_seconds() server.chat.sys_msg_sender("Player will be banned until %s" % datetime.datetime.fromtimestamp(banned_until)) @@ -82,7 +85,7 @@ class CreateAccount(ChatCommand): server.db.accounts[args.username] = Account(args.username, temp_password) server.db.accounts[args.username].password_state = PasswordState.Temp - sender.char.disp_message_box("Account %s created. Temp password %s.\nSave this password somewhere, this is the only time it will be shown." % (args.username, temp_password)) + sender.char.ui.disp_message_box("Account %s created. Temp password %s.\nSave this password somewhere, this is the only time it will be shown." % (args.username, temp_password)) class Kick(ChatCommand): def __init__(self): @@ -91,8 +94,8 @@ class Kick(ChatCommand): def run(self, args, sender): for obj in server.game_objects.values(): - if obj.name == args.player: - server.close_connection(obj.char.address) + if obj.lot == 1 and obj.name == args.player: + server.close_connection(cast(Player, obj).char.address) break else: server.chat.sys_msg_sender("Player not connected") @@ -109,11 +112,11 @@ class Mute(ChatCommand): def run(self, args, sender): for obj in server.game_objects.values(): - if obj.name == args.player: + if obj.lot == 1 and obj.name == args.player: mute_duration = datetime.timedelta(args.days, 0, 0, 0, args.minutes, args.hours, args.weeks) muted_until = time.time() + mute_duration.total_seconds() server.chat.sys_msg_sender("Player will be muted until %s" % datetime.datetime.fromtimestamp(muted_until)) - obj.char.account.muted_until = muted_until + cast(Player, obj).char.account.muted_until = muted_until break else: server.chat.sys_msg_sender("Player not connected") @@ -173,7 +176,7 @@ class ResetPassword(ChatCommand): server.db.accounts[args.username].set_password(temp_password) server.db.accounts[args.username].password_state = PasswordState.Temp - sender.char.disp_message_box("Password reset for %s. Temp password %s.\nSave this password somewhere, this is the only time it will be shown." % (args.username, temp_password)) + sender.char.ui.disp_message_box("Password reset for %s. Temp password %s.\nSave this password somewhere, this is the only time it will be shown." % (args.username, temp_password)) class Restart(ChatCommand): def __init__(self): @@ -266,8 +269,8 @@ class Unmute(ChatCommand): def run(self, args, sender): for obj in server.game_objects.values(): - if obj.name == args.player: - obj.char.account.muted_until = 0 + if obj.lot == 1 and obj.name == args.player: + cast(Player, obj).char.account.muted_until = 0 server.chat.sys_msg_sender("%s has been unmuted." % args.player) break else: diff --git a/runtime/plugins/commands/spawn.py b/runtime/plugins/commands/spawn.py index 3caad2a..16ce923 100644 --- a/runtime/plugins/commands/spawn.py +++ b/runtime/plugins/commands/spawn.py @@ -1,3 +1,6 @@ +from typing import cast + +from luserver.game_object import PhysicsObject from luserver.world import server from luserver.components.physics import PhysicsEffect from luserver.interfaces.plugin import ChatCommand @@ -58,7 +61,7 @@ class SpawnPhantom(ChatCommand): "scale": args.scale, "parent": sender, "position": sender.physics.position+displacement} - obj = server.spawn_object(lot, set_vars) + obj = cast(PhysicsObject, server.spawn_object(lot, set_vars)) obj.physics.physics_effect_active = True obj.physics.physics_effect_type = PhysicsEffect[args.effect.title()] obj.physics.physics_effect_amount = args.amount