v2017.05.14

- added imagination cost to skills
- fixed pickup effects
- started implementing spider queen battle
This commit is contained in:
lcdr
2017-05-14 18:27:17 +02:00
parent a9eebc1190
commit a6a435e241
18 changed files with 195 additions and 37 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,7 @@
from ..bitstream import c_bit
from ..math.quaternion import Quaternion
from .component import Component
from .skill import BehaviorTemplate
UPDATE_INTERVAL = 1 # todo: make interval skill-dependent
@@ -9,12 +10,18 @@ class BaseCombatAIComponent(Component):
super().__init__(obj, set_vars, comp_id)
self.object.ai = self
self._flags["target"] = "ai_flag"
self.skill_range = 7
self.target = None
self.enabled = False
self.update_handle = None
def on_startup(self):
self.object.physics.proximity_radius(7)
if self.object.skill.skills:
behavior = self.object._v_server.db.skill_behavior[self.object.skill.skills[0]][0]
assert behavior.template == BehaviorTemplate.NPCCombatSkill
self.skill_range = behavior.min_range
self.object.physics.proximity_radius(self.skill_range)
self.enable()
def enable(self):
@@ -40,7 +47,7 @@ class BaseCombatAIComponent(Component):
self.target = None
enemy_factions = self.object._v_server.db.factions.get(self.object.stats.faction, ())
# todo: make distance skill-dependent
nearest_dist = 7**2 # starting distance is maximum distance
nearest_dist = self.skill_range**2 # starting distance is maximum distance
for obj in self.object._v_server.game_objects.values():
if hasattr(obj, "stats") and obj.stats.faction in enemy_factions:
dist = self.object.physics.position.sq_distance(obj.physics.position)

View File

@@ -119,7 +119,7 @@ class ProjectileAttack(Behavior):
proj_behavs = []
for skill_id in self.object._v_server.db.object_skills[int(behavior.projectile_lot)]:
proj_behavs.append(self.object._v_server.db.skill_behavior[skill_id])
proj_behavs.append(self.object._v_server.db.skill_behavior[skill_id][0])
projectile_count = 1
if hasattr(behavior, "spread_count") and behavior.spread_count > 0:
@@ -136,7 +136,7 @@ class ProjectileAttack(Behavior):
proj_behavs = []
for skill_id in self.object._v_server.db.object_skills[int(behavior.projectile_lot)]:
proj_behavs.append(self.object._v_server.db.skill_behavior[skill_id])
proj_behavs.append(self.object._v_server.db.skill_behavior[skill_id][0])
projectile_count = 1
if hasattr(behavior, "spread_count") and behavior.spread_count > 0:
@@ -267,6 +267,10 @@ class AttackDelay(Behavior):
ChargeUp = AttackDelay # works the same
class RepairArmor(Behavior):
@staticmethod
def serialize(self, behavior, bitstream, target, level):
pass
@staticmethod
def deserialize(self, behavior, bitstream, target, level):
target.stats.armor += behavior.armor

View File

@@ -359,7 +359,7 @@ class CharacterComponent(Component, CharMission, CharTrade):
if rotation is not None:
self.object.physics.rotation.update(rotation)
self.object.physics.attr_changed("rotation")
await self.transfer_to_world(((self.world[0] // 100)*100, self.world[1], self.world[2]))
await self.transfer_to_world(((self.world[0] // 100)*100, self.world[1], 0))
# I'm going to put all game messages that are player-only but which i'm not sure of the component here
@@ -391,8 +391,7 @@ class CharacterComponent(Component, CharMission, CharTrade):
lot = self.dropped_loot[loot_object_id]
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 self.object._v_server.db.object_skills[lot]:
behavior = self.object._v_server.db.skill_behavior[skill_id]
self.object.skill.deserialize_behavior(behavior, b"", self.object)
self.object.skill.cast_skill(skill_id)
else:
self.object.inventory.add_item_to_inventory(lot)
del self.dropped_loot[loot_object_id]
@@ -533,6 +532,7 @@ class CharacterComponent(Component, CharMission, CharTrade):
self.object._v_server.db.properties[self.object._v_server.world_id[0]][self.object._v_server.world_id[2]][spawner_id] = model.lot, position, rotation
self.object._v_server.spawn_model(spawner_id, model.lot, position, rotation)
self.object.inventory.remove_item_from_inv(InventoryType.Models, model)
self.object._v_server.world_control_object.script.on_model_placed(self.object)
break
def delete_model_from_client(self, model_id:c_int64=0, reason:c_uint=DeleteReason.PickingModelUp):

View File

@@ -30,7 +30,6 @@ class CharMission:
if task.value == task.target_value:
break
self.object._v_server.commit()
return mission_progress
def update_mission_task(self, task_type, target, parameter=None, increment=1, mission_id=None):
@@ -133,8 +132,6 @@ class CharMission:
attachment = None
self.object._v_server.mail.send_mail("%[MissionEmail_{id}_senderName]".format(id=id), "%[MissionEmail_{id}_subjectText]".format(id=id), "%[MissionEmail_{id}_bodyText]".format(id=id), self.object, attachment)
self.object._v_server.commit()
@single
def offer_mission(self, mission_id:c_int=None, offerer:GameObject=None):
pass

View File

@@ -258,7 +258,7 @@ class InventoryComponent(Component):
if hasattr(stack, "module_lots"):
extra_info.ldf_set("assemblyPartLOTs", LDFDataType.STRING, [(LDFDataType.INT32, i) for i in stack.module_lots])
self.add_item_to_inventory_client_sync(bound=True, bound_on_equip=True, bound_on_pickup=True, loot_type_source=source_type, extra_info=extra_info, object_template=stack.lot, inv_type=inventory_type, amount=added_amount, new_obj_id=stack.object_id, flying_loot_pos=Vector3.zero, show_flying_loot=show_flying_loot, slot_id=index)
self.add_item_to_inventory_client_sync(loot_type_source=source_type, extra_info=extra_info, object_template=stack.lot, inv_type=inventory_type, amount=added_amount, new_obj_id=stack.object_id, flying_loot_pos=Vector3.zero, show_flying_loot=show_flying_loot, slot_id=index)
return stack
@single

View File

@@ -12,7 +12,7 @@ class PropertyData(Serializable):
def serialize(self, out):
out.write(c_int64(0))
out.write(c_int(0))
out.write(c_int(25166))
out.write(c_ushort(0))
out.write(c_ushort(0))
out.write(c_uint(0))
@@ -161,14 +161,13 @@ class PropertyEntranceComponent(Component):
clone_id = player.char.clone_id
for model in player.inventory.models:
if model is not None and model.lot == 6416:
player.char.traveling_rocket = model.module_lots
self.fire_event_client_side(args="RocketEquipped", obj=model, sender=player, param1=clone_id)
break
def property_entrance_sync(self, player, include_null_address:bool=None, include_null_description:bool=None, players_own:bool=None, update_ui:bool=None, num_results:c_int=None, reputation_time:c_int=None, sort_method:c_int=None, start_index:c_int=None, filter_text:bytes=None):
my_property = PropertySelectQueryProperty()
#my_property.clone_id = player.char.clone_id
my_property.is_owned = True
#my_property.is_owned = True
self.property_select_query(nav_offset=0, there_are_more=False, my_clone_id=0, has_featured_property=False, was_friends=False, properties=[my_property], player=player)
@@ -223,6 +222,17 @@ class PropertyVendorComponent(Component):
assert multi_interact_id is None
self.open_property_vendor(player=player)
@single # this is actually @broadcast but it's not needed and this packet is particularly large so i'm setting this to single
def download_property_data(self, data:PropertyData=None):
pass
def query_property_data(self, player):
property = PropertyData()
property.owner = player
property.path = self.object._v_server.db.property_template[self.object._v_server.world_id[0]]
self.download_property_data(property, player=player)
@single
def open_property_vendor(self):
pass
@@ -231,6 +241,7 @@ class PropertyVendorComponent(Component):
# 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)
self.object._v_server.world_control_object.script.on_property_rented(player)
@single
def property_rental_response(self, clone_id:c_uint=None, code:c_int=None, property_id:c_int64=None, rentdue:c_int64=None):

View File

@@ -120,6 +120,7 @@ class SkillSlot:
Hat = 3
class CastType:
Two = 2 # not sure, but i'll use this for casts
Consumable = 3
EverlastingConsumable = 4
@@ -154,9 +155,9 @@ class SkillComponent(Component):
self.last_ui_handle += 1
bitstream = BitStream()
behavior = self.object._v_server.db.skill_behavior[skill_id]
behavior = self.object._v_server.db.skill_behavior[skill_id][0]
self.serialize_behavior(behavior, bitstream, target)
self.start_skill(skill_id=skill_id, 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=bitstream)
self.start_skill(skill_id=skill_id, cast_type=CastType.Two, 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=bitstream)
def cast_sync_skill(self, delay, behavior, target):
ui_behavior_handle = self.last_ui_handle
@@ -190,7 +191,12 @@ class SkillComponent(Component):
assert optional_originator_id in (0, self.object.object_id)
assert originator_rot == Quaternion(0, 0, 0, 0)
self.echo_start_skill(used_mouse, caster_latency, cast_type, last_clicked_posit, optional_originator_id, optional_target_id, originator_rot, bitstream, skill_id, ui_skill_handle)
if cast_type == CastType.Two: # casts?
player = None # send to all including self
else:
player = self.object # exclude self
self.echo_start_skill(used_mouse, caster_latency, cast_type, last_clicked_posit, optional_originator_id, optional_target_id, originator_rot, bitstream, skill_id, ui_skill_handle, player=player)
if hasattr(self.object, "char"):
self.object.char.update_mission_task(TaskType.UseSkill, None, skill_id)
@@ -202,7 +208,8 @@ class SkillComponent(Component):
else:
target = self.object
self.picked_target_id = optional_target_id
behavior = self.object._v_server.db.skill_behavior[skill_id]
behavior, imagination_cost = self.object._v_server.db.skill_behavior[skill_id]
self.object.stats.imagination -= imagination_cost
self.original_target_id = target.object_id
self.deserialize_behavior(behavior, bitstream, target)
@@ -304,7 +311,7 @@ class SkillComponent(Component):
def add_skill_for_item(self, item, add_buffs=True):
if item.lot in self.object._v_server.db.object_skills:
for skill_id in self.object._v_server.db.object_skills[item.lot]:
behavior = self.object._v_server.db.skill_behavior[skill_id]
behavior = self.object._v_server.db.skill_behavior[skill_id][0]
if behavior.template in PASSIVE_BEHAVIORS:
if add_buffs:
if hasattr(self.object, "char"):
@@ -332,13 +339,13 @@ class SkillComponent(Component):
def remove_skill_for_item(self, item):
if item.lot in self.object._v_server.db.object_skills:
for skill_id in self.object._v_server.db.object_skills[item.lot]:
behavior = self.object._v_server.db.skill_behavior[skill_id]
behavior = self.object._v_server.db.skill_behavior[skill_id][0]
if behavior.template in PASSIVE_BEHAVIORS:
self.undo_behavior(behavior)
else:
self.remove_skill(skill_id=skill_id)
def remove_skill_server(self, skill_id):
behavior = self.object._v_server.db.skill_behavior[skill_id]
behavior = self.object._v_server.db.skill_behavior[skill_id][0]
if behavior.template in PASSIVE_BEHAVIORS:
self.undo_behavior(behavior)

View File

@@ -6,7 +6,9 @@ class SpawnerComponent(Component):
def __init__(self, obj, set_vars, comp_id):
super().__init__(obj, set_vars, comp_id)
self.object.spawner = self
self.active = True
self.spawntemplate = set_vars["spawntemplate"]
self.name = set_vars.get("spawner_name")
self.unknown = set_vars.get("spawner_unknown", (0, 0, 1))
self.waypoints = set_vars["spawner_waypoints"]
self.spawned_on_smash = set_vars.get("spawned_on_smash", False)
@@ -16,11 +18,15 @@ class SpawnerComponent(Component):
pass
def on_startup(self):
if not self.spawned_on_smash:
if self.name is not None:
self.object._v_server.spawners[self.name] = self.object
if self.active and not self.spawned_on_smash:
for _ in range(min(self.unknown[2], len(self.waypoints))):
self.spawn()
def spawn(self):
if not self.active:
return
spawned_vars = self.waypoints[self.last_waypoint_index].copy()
spawned_vars["spawner"] = self.object
spawned = self.object._v_server.spawn_object(self.spawntemplate, spawned_vars)
@@ -29,3 +35,12 @@ class SpawnerComponent(Component):
else:
self.last_waypoint_index += 1
return spawned
def deactivate(self):
self.active = False
def destroy(self):
self.deactivate()
for obj in self.object._v_server.game_objects.copy().values():
if obj.spawner_object == self.object:
self.object._v_server.destruct(obj)

View File

@@ -76,7 +76,7 @@ class GameObject:
for component_type, component_id in sorted(comp_ids, key=lambda x: component_order.index(x[0]) if x[0] in component_order else 99999):
if component_type == 5:
if "custom_script" in set_vars and set_vars["custom_script"] != "":
if "custom_script" in set_vars and set_vars["custom_script"] is not None:
script = importlib.import_module("luserver.scripts."+set_vars["custom_script"])
comp = script.ScriptComponent,
elif component_id is not None and component_id in self._v_server.db.script_component:

View File

@@ -215,6 +215,9 @@ class GameMessage(Enum):
MatchRequest = 1308
MatchResponse = 1309
MatchUpdate = 1310
ZonePropertyModelRotated = 1370
ZonePropertyModelRemovedWhileEquipped = 1371
ZonePropertyModelEquipped = 1372
UsedInformationPlaque = 1419
ActivateBrickMode = 1438
ModifyLegoScore = 1459

View File

@@ -0,0 +1,5 @@
import luserver.components.script as script
class ScriptComponent(script.ScriptComponent):
def on_destruction(self):
self.object._v_server.world_control_object.script.on_spider_defeated()

View File

@@ -0,0 +1,97 @@
import luserver.components.script as script
from luserver.bitstream import c_int64
from luserver.game_object import GameObject
from luserver.ldf import LDFDataType
from luserver.messages import single
from luserver.components.mission import MissionState, TaskType
FLAG_DEFEATED_SPIDER = 71
class ScriptComponent(script.ScriptComponent):
@single
def player_ready(self, player):
if player.char.get_flag(FLAG_DEFEATED_SPIDER):
return
self.set_network_var("unclaimed", LDFDataType.BOOLEAN, True)
fx = self.object._v_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")
self.notify_client_object(name="maelstromSkyOn", param1=0, param2=0, param_str=b"", param_obj=None)
def on_spider_defeated(self):
self.object._v_server.spawners["SpiderBoss"].spawner.deactivate()
for spawner in ("AggroVol", "Instancer", "Land_Target", "Rocks", "RFS_Targets", "SpiderEggs", "SpiderRocket_Bot", "SpiderRocket_Mid", "SpiderRocket_Top", "TeleVol"):
self.object._v_server.spawners[spawner].spawner.destroy()
for i in range(5):
self.object._v_server.spawners["ROF_Targets_0"+str(i)].spawner.destroy()
for i in range(1, 9):
self.object._v_server.spawners["Zone"+str(i)+"Vol"].spawner.destroy()
self.notify_client_object(name="PlayCinematic", param1=0, param2=0, param_str=b"DestroyMaelstrom", param_obj=None)
player = [obj for obj in self.object._v_server.game_objects.values() if obj.lot == 1][0]
player.char.set_flag(True, FLAG_DEFEATED_SPIDER)
self.object.call_later(0.5, self.tornado_off)
def tornado_off(self):
fx = self.object._v_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 = self.object._v_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)
self.object.call_later(8, self.kill_fx_object)
def turn_sky_off(self):
self.notify_client_object(name="SkyOff", param1=0, param2=0, param_str=b"", param_obj=None)
def show_vendor(self):
self.notify_client_object(name="vendorOn", param1=0, param2=0, param_str=b"", param_obj=None)
def kill_fx_object(self):
fx = self.object._v_server.get_objects_in_group("FXObject")[0]
fx.render.stop_f_x_effect(name=b"beam")
self.object._v_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=None)
player.char.update_mission_task(TaskType.Script, self.object.lot, mission_id=951)
self.object.call_later(2, self.bounds_on)
def bounds_on(self):
self.notify_client_object(name="boundsAnim", param1=0, param2=0, param_str=b"", param_obj=None)
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:
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:
self.set_network_var("Tooltip", LDFDataType.STRING, "TwoMoreModels")
elif not player.char.get_flag(103):
player.char.set_flag(True, 103)
elif not player.char.get_flag(104):
player.char.set_flag(True, 104)
self.set_network_var("Tooltip", LDFDataType.STRING, "TwoMoreModelsOff")
def zone_property_model_rotated(self, player:GameObject=0, 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:
self.set_network_var("Tooltip", LDFDataType.STRING, "PlaceModel")
def zone_property_model_removed_while_equipped(self, player:GameObject=0, property_id:c_int64=0):
player.char.set_flag(True, 111)
def zone_property_model_equipped(self, player:GameObject=0, property_id:c_int64=0):
self.set_network_var("PlayerAction", LDFDataType.STRING, "ModelEquipped")

View File

@@ -147,12 +147,12 @@ class WorldServer(server.Server, pyraknet.replicamanager.ReplicaManager):
def set_world_id(self, world_id):
self.world_id = world_id[0], 0, world_id[1]
if self.world_id[0] != 0: # char
world_control_lot = self.db.world_info[self.world_id[0]]
if world_control_lot is not None:
self.world_control_object = self.spawn_object(world_control_lot, is_world_control=True)
else:
self.world_control_object = None
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.spawners = {}
self.world_data = self.db.world_data[self.world_id[0]]
self.reset_v_()
for obj in self.world_data.objects.values():

View File

@@ -74,8 +74,8 @@ class Init:
self.root.level_scores = tuple(i[0] for i in self.cdclient.execute("select requiredUScore from LevelProgressionLookup").fetchall())
self.root.world_info = BTrees.IOBTree.BTree()
for world_id, template in self.cdclient.execute("select zoneID, zoneControlTemplate from ZoneTable"):
self.root.world_info[world_id] = template
for world_id, script_id, template in self.cdclient.execute("select zoneID, scriptID, zoneControlTemplate from ZoneTable"):
self.root.world_info[world_id] = scripts.SCRIPTS.get(script_id), template
def gen_missions(self):
self.root.missions = BTrees.IOBTree.BTree()

View File

@@ -34,8 +34,8 @@ class Init:
root.object_skills.setdefault(row[0], []).append(row[1])
root.skill_behavior = BTrees.IOBTree.BTree()
for skill_id, behavior_id in self.cdclient.execute("select skillID, behaviorID from SkillBehavior"):
root.skill_behavior[skill_id] = self.get_behavior(behavior_id)
for skill_id, behavior_id, imagination_cost in self.cdclient.execute("select skillID, behaviorID, imaginationcost from SkillBehavior"):
root.skill_behavior[skill_id] = self.get_behavior(behavior_id), imagination_cost
print("behavs_accessed", self.behavs_accessed)
def get_behavior(self, behavior_id):
@@ -159,6 +159,12 @@ class Init:
elif hasattr(behavior, "behavior 1"):
behavior.behavior = self.get_behavior(getattr(behavior, "behavior 1"))
delattr(behavior, "behavior 1")
if hasattr(behavior, "max range"):
behavior.max_range = getattr(behavior, "max range")
delattr(behavior, "max range")
if hasattr(behavior, "min range"):
behavior.min_range = getattr(behavior, "min range")
delattr(behavior, "min range")
elif template_id == BehaviorTemplate.Verify:
behavior.action = self.get_behavior(behavior.action)

View File

@@ -88,12 +88,12 @@ def _parse_config(config, triggers=None):
spawned_vars = {}
if "custom_script_client" in config:
spawned_vars["custom_script"] = ""
spawned_vars["custom_script"] = None
if "custom_script_server" in config:
if config["custom_script_server"] == "":
spawned_vars["custom_script"] = ""
spawned_vars["custom_script"] = None
else:
spawned_vars["custom_script"] = scripts.SCRIPTS.get(config["custom_script_server"][len("scripts\\"):], "")
spawned_vars["custom_script"] = scripts.SCRIPTS.get(config["custom_script_server"][len("scripts\\"):])
if "groupID" in config:
spawned_vars["groups"] = config["groupID"][:-1].split(";")
if "is_smashable" in config:
@@ -301,6 +301,7 @@ class _LUZImporter:
elif path_type == PathType.Spawner:
if spawner_vars["spawntemplate"] == 0:
continue
spawner_vars["spawner_name"] = path_name
spawner_vars["spawner_waypoints"] = waypoints
spawner = GameObject(SimpleNamespace(db=self.root), 176, object_id, spawner_vars)
del spawner._v_server

View File

@@ -29,6 +29,7 @@ SCRIPTS = {
1088: "general.mailbox",
1093: "forbidden_valley.ronin_statue",
1094: "forbidden_valley.candle",
1136: "avant_gardens.property.small.world_control",
1167: "avant_gardens.wisp_lee",
1195: "general.wishing_well",
1206: "forbidden_valley.brickmaster_clang",
@@ -49,6 +50,7 @@ SCRIPTS = {
1519: "nexus_tower.venture_cannon",
1527: "general.transfer_world_on_use",
1556: "nexus_tower.shadow_orb",
1564: "avant_gardens.property.small.spider_queen",
1566: "avant_gardens.rocco_sirocco",
1569: "venture_explorer.broken_console",
1573: "avant_gardens.melodie_foxtrot",