/// <summary> /// Launches a projectile from player to target /// </summary> public WorldObject LaunchProjectile(WorldObject weapon, WorldObject ammo, WorldObject target, Vector3 origin, Quaternion orientation, Vector3 velocity) { var player = this as Player; if (!velocity.IsValid()) { if (player != null) { player.SendWeenieError(WeenieError.YourAttackMisfired); } return(null); } var proj = WorldObjectFactory.CreateNewWorldObject(ammo.WeenieClassId); proj.ProjectileSource = this; proj.ProjectileTarget = target; proj.ProjectileLauncher = weapon; proj.Location = new Position(Location.ObjCellID, origin, orientation, false, Location.Instance); SetProjectilePhysicsState(proj, target, velocity); var success = LandblockManager.AddObject(proj); if (!success || proj.PhysicsObj == null) { if (!proj.HitMsg && player != null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat("Your missile attack hit the environment.", ChatMessageType.Broadcast)); } return(null); } if (!IsProjectileVisible(proj)) { proj.OnCollideEnvironment(); return(null); } var pkStatus = player?.PlayerKillerStatus ?? PlayerKillerStatus.Creature; proj.EnqueueBroadcast(new GameMessagePublicUpdatePropertyInt(proj, PropertyInt.PlayerKillerStatus, (int)pkStatus)); proj.EnqueueBroadcast(new GameMessageScript(proj.Guid, PlayScript.Launch, 0f)); // detonate point-blank projectiles immediately /*var radsum = target.PhysicsObj.GetRadius() + proj.PhysicsObj.GetRadius(); * var dist = Vector3.Distance(origin, dest); * if (dist < radsum) * { * Console.WriteLine($"Point blank"); * proj.OnCollideObject(target); * }*/ return(proj); }
/// <summary> /// Launches a projectile from player to target /// </summary> public WorldObject LaunchProjectile(WorldObject weapon, WorldObject ammo, WorldObject target, out float time) { var proj = WorldObjectFactory.CreateNewWorldObject(ammo.WeenieClassId); proj.ProjectileSource = this; proj.ProjectileTarget = target; proj.ProjectileLauncher = weapon; var matchIndoors = Location.Indoors == target.Location.Indoors; var origin = matchIndoors ? Location.ToGlobal() : Location.Pos; origin.Z += Height; var dest = matchIndoors ? target.Location.ToGlobal() : target.Location.Pos; dest.Z += target.Height / GetAimHeight(target); var speed = 35.0f; // TODO: get correct speed var dir = GetDir2D(origin, dest); origin += dir * 2.0f; var velocity = GetProjectileVelocity(target, origin, dir, dest, speed, out time); proj.Velocity = velocity; proj.Location = matchIndoors ? Location.FromGlobal(origin) : new Position(Location.Cell, origin, Location.Rotation); if (!matchIndoors) { proj.Location.LandblockId = new LandblockId(proj.Location.GetCell()); } SetProjectilePhysicsState(proj, target); var result = LandblockManager.AddObject(proj); if (proj.PhysicsObj == null) { return(null); } var player = this as Player; var pkStatus = player?.PlayerKillerStatus ?? PlayerKillerStatus.Creature; proj.EnqueueBroadcast(new GameMessagePublicUpdatePropertyInt(proj, PropertyInt.PlayerKillerStatus, (int)pkStatus)); proj.EnqueueBroadcast(new GameMessageScript(proj.Guid, ACE.Entity.Enum.PlayScript.Launch, 0f)); // detonate point-blank projectiles immediately /*var radsum = target.PhysicsObj.GetRadius() + proj.PhysicsObj.GetRadius(); * var dist = Vector3.Distance(origin, dest); * if (dist < radsum) * { * Console.WriteLine($"Point blank"); * proj.OnCollideObject(target); * }*/ return(proj); }
public static void TestSpell(Session session, params string[] parameters) { uint templatid; float x, y, z; float friction; float electicity; try { templatid = Convert.ToUInt32(parameters[0]); x = float.Parse(parameters[1], CultureInfo.InvariantCulture.NumberFormat); y = float.Parse(parameters[2], CultureInfo.InvariantCulture.NumberFormat); z = float.Parse(parameters[3], CultureInfo.InvariantCulture.NumberFormat); friction = float.Parse(parameters[4], CultureInfo.InvariantCulture.NumberFormat); electicity = float.Parse(parameters[5], CultureInfo.InvariantCulture.NumberFormat); } catch (Exception) { ChatPacket.SendServerMessage(session, $"Invalid Spell Parameters", ChatMessageType.Broadcast); return; } AceVector3 velocity = new AceVector3(x, y, z); LandblockManager.AddObject(SpellObjectFactory.CreateSpell(templatid, session.Player.Location.InFrontOf(2.0f), velocity, friction, electicity)); }
// This is throw away code to understand the world object creation process. public static void Spawn(WorldObject inventoryItem, Position position) { inventoryItem.Sequences.GetNextSequence(SequenceType.ObjectTeleport); inventoryItem.Sequences.GetNextSequence(SequenceType.ObjectVector); inventoryItem.Location = position.InFrontOf(1.00f); inventoryItem.PhysicsDescriptionFlag |= PhysicsDescriptionFlag.Position; LandblockManager.AddObject(inventoryItem); }
public void EnterWorld() { if (Location != null) { LandblockManager.AddObject(this); if (SuppressGenerateEffect != true) { ApplyVisualEffects(ACE.Entity.Enum.PlayScript.Create); } } }
/// <summary> /// creates a portal of the specified weenie at the position provided /// </summary> public static void SpawnPortal(PortalWcid weenieClassId, Position newPosition, float despawnTime) { WorldObject portal = WorldObjectFactory.CreateNewWorldObject((uint)weenieClassId); portal.Positions.Add(PositionType.Location, newPosition); LandblockManager.AddObject(portal); // Create portal decay ActionChain despawnChain = new ActionChain(); despawnChain.AddDelaySeconds(despawnTime); despawnChain.AddAction(portal, () => portal.CurrentLandblock.RemoveWorldObject(portal.Guid, false)); despawnChain.EnqueueChain(); }
/// <summary> /// Launches a projectile from player to target /// </summary> public WorldObject LaunchProjectile(WorldObject ammo, WorldObject target, out float time) { var proj = WorldObjectFactory.CreateNewWorldObject(ammo.WeenieClassId); proj.ProjectileSource = this; proj.ProjectileTarget = target; var origin = Location.ToGlobal(); origin.Z += Height; var dest = target.Location.ToGlobal(); dest.Z += target.Height / GetAimHeight(target); var speed = 35.0f; // TODO: get correct speed var dir = GetDir2D(origin, dest); origin += dir * 2.0f; var velocity = GetProjectileVelocity(target, origin, dir, dest, speed, out time); proj.Velocity = new AceVector3(velocity.X, velocity.Y, velocity.Z); proj.Location = Location.FromGlobal(origin); SetProjectilePhysicsState(proj, target); LandblockManager.AddObject(proj); var player = this as Player; var pkStatus = player == null ? PlayerKillerStatus.Creature : player.PlayerKillerStatus; proj.EnqueueBroadcast(new GameMessagePublicUpdatePropertyInt(proj, PropertyInt.PlayerKillerStatus, (int)pkStatus)); proj.EnqueueBroadcast(new GameMessageScript(proj.Guid, ACE.Entity.Enum.PlayScript.Launch, 0f)); // detonate point-blank projectiles immediately var radsum = target.PhysicsObj.GetRadius() + proj.PhysicsObj.GetRadius(); var dist = Vector3.Distance(origin, dest); if (dist < radsum) { proj.OnCollideObject(target); } return(proj); }
/// <summary> /// creates a portal of the specified weenie at the position provided /// </summary> public static void SpawnPortal(PortalWcid weenieClassId, Position newPosition, float despawnTime) { AceObject aceO = DatabaseManager.World.GetAceObjectByWeenie((ushort)weenieClassId); aceO.AceObjectPropertiesPositions.Add(PositionType.Location, newPosition); WorldObject portal = new Portal(aceO); portal.Guid = GuidManager.NewItemGuid(); LandblockManager.AddObject(portal); // Create portal decay ActionChain despawnChain = new ActionChain(); despawnChain.AddDelaySeconds(despawnTime); despawnChain.AddAction(portal, () => portal.CurrentLandblock.RemoveWorldObject(portal.Guid, false)); despawnChain.EnqueueChain(); }
public static void CreateStaticCreature(Session session, params string[] parameters) { Creature newC = null; if (!(parameters?.Length > 0)) { ChatPacket.SendServerMessage(session, "Usage: @createcreature [static] weenieClassId", ChatMessageType.Broadcast); return; } if (parameters?[0] == "static") { if (parameters?.Length > 1) { uint weenie = Convert.ToUInt32(parameters[1]); newC = MonsterFactory.SpawnCreature(weenie, true, session.Player.Location.InFrontOf(2.0f)); } else { ChatPacket.SendServerMessage(session, "Specify a valid weenieClassId after the static option.", ChatMessageType.Broadcast); return; } } else { uint weenie = Convert.ToUInt32(parameters[0]); newC = MonsterFactory.SpawnCreature(weenie, false, session.Player.Location.InFrontOf(2.0f)); } if (newC != null) { ChatPacket.SendServerMessage(session, $"Now spawning {newC.Name}.", ChatMessageType.Broadcast); LandblockManager.AddObject(newC); } else { ChatPacket.SendServerMessage(session, "Couldn't find that creature in the database or save it's location.", ChatMessageType.Broadcast); } }
/// <summary> /// Launches a projectile from player to target /// </summary> public float LaunchProjectile(WorldObject target) { var ammo = GetEquippedAmmo(); var arrow = WorldObjectFactory.CreateNewWorldObject(ammo.WeenieClassId); var origin = Location.ToGlobal(); origin.Z += Height; var dest = target.Location.ToGlobal(); dest.Z += target.Height / GetAimHeight(target); var speed = 35.0f; var dir = Vector3.Normalize(dest - origin); origin += dir * 2.0f; arrow.Velocity = GetProjectileVelocity(target, origin, dir, dest, speed, out var time); var loc = Location; origin = Position.FromGlobal(origin).Pos; arrow.Location = new Position(loc.LandblockId.Raw, origin.X, origin.Y, origin.Z, loc.Rotation.X, loc.Rotation.Y, loc.Rotation.Z, loc.RotationW); SetProjectilePhysicsState(arrow); LandblockManager.AddObject(arrow); CurrentLandblock.EnqueueBroadcast(arrow.Location, new GameMessageScript(arrow.Guid, ACE.Entity.Enum.PlayScript.Launch, 1.0f)); var actionChain = new ActionChain(); actionChain.AddDelaySeconds(time); actionChain.AddAction(arrow, () => Session.Network.EnqueueSend(new GameMessageSound(arrow.Guid, Sound.Collision, 1.0f))); actionChain.AddAction(arrow, () => CurrentLandblock.RemoveWorldObject(arrow.Guid, false)); actionChain.EnqueueChain(); return(time); }
public static void CreateStaticCreature(Session session, params string[] parameters) { if (!(parameters?.Length > 0)) { ChatPacket.SendServerMessage(session, "Usage: @createstaticcreature weenieClassId", ChatMessageType.Broadcast); return; } uint weenie = Convert.ToUInt32(parameters[0]); Creature newC = MonsterFactory.SpawnStaticCreature(weenie, session.Player.Location.InFrontOf(2.0f)); if (newC != null) { ChatPacket.SendServerMessage(session, $"Now spawning {newC.Name}", ChatMessageType.Broadcast); LandblockManager.AddObject(newC); } else { ChatPacket.SendServerMessage(session, "Couldn't find that creature in the database or save it's location.", ChatMessageType.Broadcast); } }
public static void CreateTrainingWand(Session session, params string[] parameters) { if (!(parameters?.Length > 0)) { ChatPacket.SendServerMessage(session, "Usage: @ctw me or @ctw ground", ChatMessageType.Broadcast); return; } string location = parameters[0]; if (location == "me" | location == "ground") { WorldObject loot = LootGenerationFactory.CreateTrainingWand(session.Player); switch (location) { case "me": { LootGenerationFactory.AddToContainer(loot, session.Player); session.Player.TrackObject(loot); // TODO: Have to send game message CFS break; } case "ground": { LootGenerationFactory.Spawn(loot, session.Player.Location.InFrontOf(1.0f)); LandblockManager.AddObject(loot); break; } } } else { ChatPacket.SendServerMessage(session, "Usage: @ctw me or @ctw ground", ChatMessageType.Broadcast); } }
private void HandleGameAction(QueuedGameAction action, Player player) { switch (action.ActionType) { case GameActionType.TalkDirect: { // TODO: remove this hack (using TalkDirect) ASAP var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } DeathMessageArgs d = new DeathMessageArgs(action.ActionBroadcastMessage, new ObjectGuid(action.ObjectId), new ObjectGuid(action.SecondaryObjectId)); HandleDeathMessage(obj, d); break; } case GameActionType.TeleToHouse: case GameActionType.TeleToLifestone: case GameActionType.TeleToMansion: case GameActionType.TeleToMarketPlace: case GameActionType.TeleToPkArena: case GameActionType.TeleToPklArena: { player.Teleport(action.ActionLocation); break; } case GameActionType.ApplyVisualEffect: { var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } var particleEffect = (PlayScript)action.SecondaryObjectId; HandleParticleEffectEvent(obj, particleEffect); break; } case GameActionType.ApplySoundEffect: { var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } var soundEffect = (Sound)action.SecondaryObjectId; HandleSoundEvent(obj, soundEffect); break; } case GameActionType.PutItemInContainer: { var playerId = new ObjectGuid(action.ObjectId); var inventoryId = new ObjectGuid(action.SecondaryObjectId); if (playerId.IsPlayer()) { Player aPlayer = null; WorldObject inventoryItem = null; if (worldObjects.ContainsKey(playerId) && worldObjects.ContainsKey(inventoryId)) { aPlayer = (Player)worldObjects[playerId]; inventoryItem = worldObjects[inventoryId]; } if ((aPlayer != null) && (inventoryItem != null)) { var motion = new GeneralMotion(MotionStance.Standing); motion.MovementData.ForwardCommand = (ushort)MotionCommand.Pickup; aPlayer.Session.Network.EnqueueSend(new GameMessageUpdatePosition(aPlayer), new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessageSound(aPlayer.Guid, Sound.PickUpItem, (float)1.0)); // Add to the inventory list. aPlayer.AddToInventory(inventoryItem); LandblockManager.RemoveObject(inventoryItem); motion = new GeneralMotion(MotionStance.Standing); aPlayer.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(aPlayer.Session, PropertyInt.EncumbVal, aPlayer.GameData.Burden), new GameMessagePutObjectInContainer(aPlayer.Session, aPlayer, inventoryId), new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessageUpdateInstanceId(inventoryId, playerId), new GameMessagePickupEvent(aPlayer.Session, inventoryItem)); aPlayer.TrackObject(inventoryItem); } } break; } case GameActionType.DropItem: { var g = new ObjectGuid(action.ObjectId); // ReSharper disable once InconsistentlySynchronizedField if (worldObjects.ContainsKey(g)) { var playerId = new ObjectGuid(action.ObjectId); var inventoryId = new ObjectGuid(action.SecondaryObjectId); if (playerId.IsPlayer()) { Player aPlayer = null; WorldObject inventoryItem = null; if (worldObjects.ContainsKey(playerId)) { aPlayer = (Player)worldObjects[playerId]; inventoryItem = aPlayer.GetInventoryItem(inventoryId); aPlayer.RemoveFromInventory(inventoryId); } if ((aPlayer != null) && (inventoryItem != null)) { var targetContainer = new ObjectGuid(0); aPlayer.Session.Network.EnqueueSend( new GameMessagePrivateUpdatePropertyInt( aPlayer.Session, PropertyInt.EncumbVal, (uint)aPlayer.Session.Player.GameData.Burden)); var motion = new GeneralMotion(MotionStance.Standing); motion.MovementData.ForwardCommand = (ushort)MotionCommand.Pickup; aPlayer.Session.Network.EnqueueSend( new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessageUpdateInstanceId(inventoryId, targetContainer)); motion = new GeneralMotion(MotionStance.Standing); aPlayer.Session.Network.EnqueueSend( new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessagePutObjectIn3d(aPlayer.Session, aPlayer, inventoryId), new GameMessageSound(aPlayer.Guid, Sound.DropItem, (float)1.0), new GameMessageUpdateInstanceId(inventoryId, targetContainer)); // This is the sequence magic - adds back into 3d space seem to be treated like teleport. inventoryItem.Sequences.GetNextSequence(SequenceType.ObjectTeleport); inventoryItem.Sequences.GetNextSequence(SequenceType.ObjectVector); LandblockManager.AddObject(inventoryItem); aPlayer.Session.Network.EnqueueSend(new GameMessageUpdatePosition(inventoryItem)); } } } break; } case GameActionType.MovementEvent: { var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } var motion = action.Motion; HandleMovementEvent(obj, motion); break; } case GameActionType.ObjectCreate: { this.AddWorldObject(action.WorldObject); break; } case GameActionType.ObjectDelete: { this.RemoveWorldObject(action.WorldObject.Guid, false); break; } case GameActionType.QueryHealth: { if (action.ObjectId == 0) { // Deselect the formerly selected Target player.SelectedTarget = 0; break; } object target = null; var targetId = new ObjectGuid(action.ObjectId); // Remember the selected Target player.SelectedTarget = action.ObjectId; // TODO: once items are implemented check if there are items that can trigger // the QueryHealth event. So far I believe it only gets triggered for players and creatures if (targetId.IsPlayer() || targetId.IsCreature()) { if (this.worldObjects.ContainsKey(targetId)) { target = this.worldObjects[targetId]; } if (target == null) { // check adjacent landblocks for the targetId foreach (var block in adjacencies) { if (block.Value != null) { if (block.Value.worldObjects.ContainsKey(targetId)) { target = block.Value.worldObjects[targetId]; } } } } if (target != null) { float healthPercentage = 0; if (targetId.IsPlayer()) { Player tmpTarget = (Player)target; healthPercentage = (float)tmpTarget.Health.Current / (float)tmpTarget.Health.MaxValue; } if (targetId.IsCreature()) { Creature tmpTarget = (Creature)target; healthPercentage = (float)tmpTarget.Health.Current / (float)tmpTarget.Health.MaxValue; } var updateHealth = new GameEventUpdateHealth(player.Session, targetId.Full, healthPercentage); player.Session.Network.EnqueueSend(updateHealth); } } break; } case GameActionType.Use: { var g = new ObjectGuid(action.ObjectId); if (worldObjects.ContainsKey(g)) { WorldObject obj = worldObjects[g]; switch (obj.Type) { case Enum.ObjectType.Portal: { // validate within use range :: set to a fixed value as static Portals are normally OnCollide usage float rangeCheck = 5.0f; if (player.Location.SquaredDistanceTo(obj.Location) < rangeCheck) { PortalDestination portalDestination = DatabaseManager.World.GetPortalDestination(obj.WeenieClassid); if (portalDestination != null) { player.Session.Player.Teleport(portalDestination.Position); // always send useDone event var sendUseDoneEvent = new GameEventUseDone(player.Session); player.Session.Network.EnqueueSend(sendUseDoneEvent); } else { string serverMessage = "Portal destination for portal ID " + obj.WeenieClassid + " not yet implemented!"; var usePortalMessage = new GameMessageSystemChat(serverMessage, ChatMessageType.System); // always send useDone event var sendUseDoneEvent = new GameEventUseDone(player.Session); player.Session.Network.EnqueueSend(usePortalMessage, sendUseDoneEvent); } } else { // always send useDone event var sendUseDoneEvent = new GameEventUseDone(player.Session); player.Session.Network.EnqueueSend(sendUseDoneEvent); } break; } case Enum.ObjectType.LifeStone: { string serverMessage = null; // validate within use range float radiusSquared = obj.GameData.UseRadius * obj.GameData.UseRadius; var motionSanctuary = new GeneralMotion(MotionStance.Standing, new MotionItem(MotionCommand.Sanctuary)); var animationEvent = new GameMessageUpdateMotion(player, player.Session, motionSanctuary); // This event was present for a pcap in the training dungeon.. Why? The sound comes with animationEvent... var soundEvent = new GameMessageSound(obj.Guid, Sound.LifestoneOn, 1); if (player.Location.SquaredDistanceTo(obj.Location) >= radiusSquared) { serverMessage = "You wandered too far to attune with the Lifestone!"; } else { player.SetCharacterPosition(PositionType.Sanctuary, player.Location); // create the outbound server message serverMessage = "You have attuned your spirit to this Lifestone. You will resurrect here after you die."; player.EnqueueMovementEvent(motionSanctuary, player.Guid); player.Session.Network.EnqueueSend(soundEvent); } var lifestoneBindMessage = new GameMessageSystemChat(serverMessage, ChatMessageType.Magic); // always send useDone event var sendUseDoneEvent = new GameEventUseDone(player.Session); player.Session.Network.EnqueueSend(lifestoneBindMessage, sendUseDoneEvent); break; } } } break; } } }
/// <summary> /// Launches a projectile from player to target /// </summary> public WorldObject LaunchProjectile(WorldObject target, out float time) { var ammo = GetEquippedAmmo(); var arrow = WorldObjectFactory.CreateNewWorldObject(ammo.WeenieClassId); var origin = Location.ToGlobal(); origin.Z += Height; var dest = target.Location.ToGlobal(); dest.Z += target.Height / GetAimHeight(target); var speed = 35.0f; var dir = Vector3.Normalize(dest - origin); origin += dir * 2.0f; arrow.Velocity = GetProjectileVelocity(target, origin, dir, dest, speed, out time); var loc = Location; origin = Position.FromGlobal(origin).Pos; arrow.Location = new Position(loc.LandblockId.Raw, origin.X, origin.Y, origin.Z, loc.Rotation.X, loc.Rotation.Y, loc.Rotation.Z, loc.RotationW); SetProjectilePhysicsState(arrow); var actionChain = new ActionChain(); // TODO: Get correct aim level based on arrow velocity and add aim motion delay. var motion = new UniversalMotion(CurrentMotionState.Stance); motion.MovementData.CurrentStyle = (uint)CurrentMotionState.Stance; motion.MovementData.ForwardCommand = (uint)MotionCommand.AimLevel; CurrentMotionState = motion; actionChain.AddAction(this, () => DoMotion(motion)); //actionChain.AddDelaySeconds(animLength); actionChain.AddAction(this, () => LandblockManager.AddObject(arrow)); actionChain.AddAction(this, () => CurrentLandblock.EnqueueBroadcast(arrow.Location, new GameMessagePickupEvent(ammo))); var player = this as Player; // TODO: Add support for monster ammo depletion. For now only players will use up ammo. if (player != null) { actionChain.AddAction(this, () => UpdateAmmoAfterLaunch(ammo)); } // Not sure why this would be needed but it is sent in retail pcaps. actionChain.AddAction(arrow, () => CurrentLandblock.EnqueueBroadcast(arrow.Location, new GameMessageSetStackSize(arrow))); if (player != null) { actionChain.AddAction(arrow, () => CurrentLandblock.EnqueueBroadcast(arrow.Location, new GameMessagePublicUpdatePropertyInt( arrow, PropertyInt.PlayerKillerStatus, (int)(player.PlayerKillerStatus ?? ACE.Entity.Enum.PlayerKillerStatus.NPK)))); } else { actionChain.AddAction(arrow, () => CurrentLandblock.EnqueueBroadcast(arrow.Location, new GameMessagePublicUpdatePropertyInt( arrow, PropertyInt.PlayerKillerStatus, (int)ACE.Entity.Enum.PlayerKillerStatus.Creature))); } actionChain.AddAction(arrow, () => CurrentLandblock.EnqueueBroadcast(arrow.Location, new GameMessageScript(arrow.Guid, ACE.Entity.Enum.PlayScript.Launch, 0f))); actionChain.AddDelaySeconds(time); // todo: landblock broadcast? if (player != null) { actionChain.AddAction(arrow, () => player.Session.Network.EnqueueSend(new GameMessageSound(arrow.Guid, Sound.Collision, 1.0f))); } actionChain.AddAction(arrow, () => CurrentLandblock.RemoveWorldObject(arrow.Guid, false)); actionChain.EnqueueChain(); return(arrow); }
private void CreateCorpse() { if (NoCorpse ?? false) { return; } var corpse = WorldObjectFactory.CreateNewWorldObject(DatabaseManager.World.GetCachedWeenie("corpse")) as Corpse; corpse.SetupTableId = SetupTableId; corpse.MotionTableId = MotionTableId; corpse.SoundTableId = SoundTableId; corpse.PaletteBaseDID = PaletteBaseDID; corpse.ClothingBase = ClothingBase; corpse.PhysicsTableId = PhysicsTableId; if (ObjScale.HasValue) { corpse.ObjScale = ObjScale; } if (PaletteTemplate.HasValue) { corpse.PaletteTemplate = PaletteTemplate; } if (Shade.HasValue) { corpse.Shade = Shade; } //if (Translucency.HasValue) // Shadows have Translucency but their corpses do not, videographic evidence can be found on YouTube. // corpse.Translucency = Translucency; if (EquippedObjects.Values.Where(x => (x.CurrentWieldedLocation & (EquipMask.Clothing | EquipMask.Armor | EquipMask.Cloak)) != 0).ToList().Count > 0) // If creature is wearing objects, we need to save the appearance { var objDesc = CalculateObjDesc(); foreach (var animPartChange in objDesc.AnimPartChanges) { corpse.Biota.BiotaPropertiesAnimPart.Add(new Database.Models.Shard.BiotaPropertiesAnimPart { ObjectId = corpse.Guid.Full, AnimationId = animPartChange.PartID, Index = animPartChange.PartIndex }); } foreach (var subPalette in objDesc.SubPalettes) { corpse.Biota.BiotaPropertiesPalette.Add(new Database.Models.Shard.BiotaPropertiesPalette { ObjectId = corpse.Guid.Full, SubPaletteId = subPalette.SubID, Length = (ushort)subPalette.NumColors, Offset = (ushort)subPalette.Offset }); } foreach (var textureChange in objDesc.TextureChanges) { corpse.Biota.BiotaPropertiesTextureMap.Add(new Database.Models.Shard.BiotaPropertiesTextureMap { ObjectId = corpse.Guid.Full, Index = textureChange.PartIndex, OldId = textureChange.OldTexture, NewId = textureChange.NewTexture }); } } //corpse.Location = Location; corpse.Location = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationFinalPositionFromStart(Location, ObjScale ?? 1, MotionCommand.Dead); //corpse.Location.PositionZ = corpse.Location.PositionZ - .5f; // Adding BaseDescriptionFlags |= ObjectDescriptionFlag.Corpse to Corpse objects made them immune to gravity.. this seems to fix floating corpse... corpse.Name = $"Corpse of {Name}"; string killerName = null; if (Killer.HasValue && Killer != 0) { var killer = CurrentLandblock.GetObject(new ObjectGuid(Killer ?? 0)); if (killer != null) { killerName = killer.Name; } } if (String.IsNullOrEmpty(killerName)) { killerName = "misadventure"; } corpse.LongDesc = $"Killed by {killerName}"; if (Killer.HasValue) { corpse.SetProperty(PropertyInstanceId.AllowedActivator, Killer.Value); // Think this will be what limits corpses to Killer first. } // Transfer of generated treasure from creature to corpse here var random = new Random((int)DateTime.UtcNow.Ticks); int level = (int)this.Level; int tier; if (level < 16) { tier = 1; } else if (level < 31) { tier = 2; } else if (level < 60) { tier = 3; } else if (level < 80) { tier = 4; } else if (level < 115) { tier = 5; } else if (level < 160) { tier = 6; } else { tier = 7; } ////Tier 8 is reserved for special creatures, usually based on which landblock they were on...Not level based. to be added later foreach (var trophy in Biota.BiotaPropertiesCreateList.Where(x => x.DestinationType == (int)DestinationType.Contain || x.DestinationType == (int)DestinationType.Treasure || x.DestinationType == (int)DestinationType.ContainTreasure || x.DestinationType == (int)DestinationType.ShopTreasure || x.DestinationType == (int)DestinationType.WieldTreasure).OrderBy(x => x.Shade)) { if (random.NextDouble() < trophy.Shade || trophy.Shade == 1 || trophy.Shade == 0) // Shade in this context is Probability // Should there be rolls for each item or one roll to rule them all? { if (trophy.WeenieClassId == 0) // Randomized Loot { var wo = LootGenerationFactory.CreateRandomLootObjects(tier); corpse.TryAddToInventory(wo); //var book = WorldObjectFactory.CreateNewWorldObject("parchment") as Book; //if (book == null) // continue; //book.SetProperties("IOU", "An IOU for a random loot.", "Sorry about that chief...", "ACEmulator", "prewritten"); //book.AddPage(corpse.Guid.Full, "ACEmulator", "prewritten", false, $"Sorry but at this time we do not have randomized and mutated loot in ACEmulator, you can ignore this item as it's meant only to be placeholder"); //corpse.TryAddToInventory(book); } else // Trophy Loot { var wo = WorldObjectFactory.CreateNewWorldObject(trophy.WeenieClassId); if (wo == null) { continue; } if (trophy.StackSize > 1) { wo.StackSize = (ushort)trophy.StackSize; } if (trophy.Palette > 0) { wo.PaletteTemplate = trophy.Palette; } corpse.TryAddToInventory(wo); } } } corpse.RemoveProperty(PropertyInt.Value); LandblockManager.AddObject(corpse); }
public static void CreatePortal(Session session, params string[] parameters) { LandblockManager.AddObject(PortalObjectFactory.CreatePortal(1234, session.Player.Location.InFrontOf(3.0f), "Test Portal", PortalType.Purple)); }
public static void CreateLifeStone(Session session, params string[] parameters) { LandblockManager.AddObject(LifestoneObjectFactory.CreateLifestone(509, session.Player.Location.InFrontOf(3.0f), LifestoneType.Original)); }
/// <summary> /// Launches a projectile from player to target /// </summary> public WorldObject LaunchProjectile(WorldObject target, out float time) { var ammo = GetEquippedAmmo(); var arrow = WorldObjectFactory.CreateNewWorldObject(ammo.WeenieClassId); arrow.ProjectileSource = this; arrow.ProjectileTarget = target; var origin = Location.ToGlobal(); origin.Z += Height; var dest = target.Location.ToGlobal(); //var dest = target.Location.Pos; dest.Z += target.Height / GetAimHeight(target); var speed = 35.0f; var dir = GetDir2D(origin, dest); origin += dir * 2.0f; var velocity = GetProjectileVelocity(target, origin, dir, dest, speed, out time); arrow.Velocity = new AceVector3(velocity.X, velocity.Y, velocity.Z); origin = Location.FromGlobal(origin).Pos; var rotation = Location.Rotation; arrow.Location = new Position(Location.LandblockId.Raw, origin.X, origin.Y, origin.Z, rotation.X, rotation.Y, rotation.Z, rotation.W); SetProjectilePhysicsState(arrow, target); // TODO: Get correct aim level based on arrow velocity and add aim motion delay. var motion = new UniversalMotion(CurrentMotionState.Stance); motion.MovementData.CurrentStyle = (uint)CurrentMotionState.Stance; motion.MovementData.ForwardCommand = (uint)MotionCommand.AimLevel; CurrentMotionState = motion; DoMotion(motion); //actionChain.AddDelaySeconds(animLength); LandblockManager.AddObject(arrow); arrow.EnqueueBroadcast(new GameMessagePickupEvent(ammo)); var player = this as Player; // TODO: Add support for monster ammo depletion. For now only players will use up ammo. if (player != null) { UpdateAmmoAfterLaunch(ammo); } // Not sure why this would be needed but it is sent in retail pcaps. arrow.EnqueueBroadcast(new GameMessageSetStackSize(arrow)); var pkStatus = player == null ? PlayerKillerStatus.Creature : player.PlayerKillerStatus; arrow.EnqueueBroadcast(new GameMessagePublicUpdatePropertyInt(arrow, PropertyInt.PlayerKillerStatus, (int)pkStatus)); arrow.EnqueueBroadcast(new GameMessageScript(arrow.Guid, ACE.Entity.Enum.PlayScript.Launch, 0f)); // detonate point-blank projectiles immediately var radsum = target.PhysicsObj.GetRadius() + arrow.PhysicsObj.GetRadius(); var dist = Vector3.Distance(origin, dest); if (dist < radsum) { arrow.OnCollideObject(target); } return(arrow); }
/// <summary> /// main game loop /// </summary> public void UseTime() { while (running) { // here we'd move server objects in motion (subject to landscape) and do physics collision detection List <WorldObject> allworldobj = null; List <Player> allplayers = null; List <Creature> allcreatures = null; List <WorldObject> movedObjects = null; List <WorldObject> despawnObjects = null; List <Creature> deadCreatures = null; lock (objectCacheLocker) { allworldobj = worldObjects.Values.ToList(); } // all players on this land block allplayers = allworldobj.OfType <Player>().ToList(); allcreatures = allworldobj.OfType <Creature>().ToList(); despawnObjects = allworldobj.ToList(); despawnObjects = despawnObjects.Where(x => x.DespawnTime > -1).ToList(); deadCreatures = allworldobj.OfType <Creature>().ToList(); deadCreatures = deadCreatures.Where(x => x.IsAlive == false).ToList(); // flag them as updated now in order to reduce chance of missing an update // this is only for moving objects across landblocks. movedObjects = allworldobj.ToList(); movedObjects = movedObjects.Where(p => p.LastUpdatedTicks >= p.LastMovementBroadcastTicks).ToList(); movedObjects.ForEach(m => m.LastMovementBroadcastTicks = WorldManager.PortalYearTicks); if (id.MapScope == Enum.MapScope.Outdoors) { // check to see if a player or other mutable object "roamed" to an adjacent landblock var objectsToRelocate = movedObjects.Where(m => m.Location.LandblockId.IsAdjacentTo(id) && m.Location.LandblockId != id).ToList(); // so, these objects moved to an adjacent block. they could have recalled to that block, died and bounced to a lifestone on that block, or // just simply walked accross the border line. in any case, we won't delete them, we'll just transfer them. the trick, though, is to // figure out how to treat it in adjacent landblocks. if the player walks across the southern border, the north adjacency needs to remove // them, but the south is actually getting them. we need to avoid sending Delete+Create to clients that already know about it, though. objectsToRelocate.ForEach(o => Log($"attempting to relocate object {o.Name} ({o.Guid.Full.ToString("X")})")); // RelocateObject will put them in the right landblock objectsToRelocate.ForEach(o => LandblockManager.RelocateObject(o)); // Remove has logic to make sure it doesn't double up the delete+create when "true" is passed. objectsToRelocate.ForEach(o => RemoveWorldObject(o.Guid, true)); } // for all players on landblock. Parallel.ForEach(allplayers, player => { // Process Action Queue for player. QueuedGameAction action = player.ActionQueuePop(); if (action != null) { HandleGameAction(action, player); } // Process Examination Queue for player QueuedGameAction examination = player.ExaminationQueuePop(); if (examination != null) { HandleGameAction(examination, player); } }); UpdateStatus(allplayers.Count); double tickTime = WorldManager.PortalYearTicks; // per-creature update on landblock. Parallel.ForEach(allworldobj, wo => { // Process the creatures wo.Tick(tickTime); }); // broadcast moving objects to the world.. // players and creatures can move. Parallel.ForEach(movedObjects, mo => { // detect all world objects in ghost range List <WorldObject> woproxghost = new List <WorldObject>(); woproxghost.AddRange(GetWorldObjectsInRange(mo, maxobjectGhostRange, true)); // for all objects in rang of this moving object or in ghost range of moving object update them. Parallel.ForEach(woproxghost, wo => { if (mo.Guid.IsPlayer()) { // if world object is in active zone then. if (wo.Location.SquaredDistanceTo(mo.Location) <= maxobjectRange) { // if world object is in active zone. if (!(mo as Player).GetTrackedObjectGuids().Contains(wo.Guid)) { (mo as Player).TrackObject(wo); } } // if world object is in ghost zone and outside of active zone else if ((mo as Player).GetTrackedObjectGuids().Contains(wo.Guid)) { (mo as Player).StopTrackingObject(wo, true); } } }); if (mo.Location.LandblockId == id) { // update if it's still here Broadcast(BroadcastEventArgs.CreateAction(BroadcastAction.AddOrUpdate, mo), true, Quadrant.All); } else { // remove and readd if it's not RemoveWorldObject(mo.Guid, false); LandblockManager.AddObject(mo); } }); // despawn objects despawnObjects.ForEach(deo => { if (deo.DespawnTime < WorldManager.PortalYearTicks) { RemoveWorldObject(deo.Guid, false); } }); // respawn creatures deadCreatures.ForEach(dc => { if (dc.RespawnTime < WorldManager.PortalYearTicks) { dc.IsAlive = true; // HandleParticleEffectEvent(dc, PlayScript.Create); AddWorldObject(dc); } }); Thread.Sleep(1); } // TODO: release resources }
/// <summary> /// Creates the Magic projectile spells for Life, War, and Void Magic /// </summary> /// <param name="caster"></param> /// <param name="target"></param> /// <param name="spellId"></param> /// <param name="projectileWcid"></param> /// <param name="lifeProjectileDamage"></param> private void CreateSpellProjectile(WorldObject caster, WorldObject target, uint spellId, uint projectileWcid, uint lifeProjectileDamage = 0) { SpellProjectile spellProjectile = WorldObjectFactory.CreateNewWorldObject(projectileWcid) as SpellProjectile; spellProjectile.Setup(spellId); var origin = caster.Location.ToGlobal(); if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc) { origin.Z += caster.Height; } else { origin.Z += caster.Height * 2.0f / 3.0f; } var dest = target.Location.ToGlobal(); dest.Z += target.Height / 2.0f; var direction = Vector3.Normalize(dest - origin); // This is not perfect but is close to values that retail used. TODO: revisit this later. origin += direction * (caster.PhysicsObj.GetRadius() + spellProjectile.PhysicsObj.GetRadius()); float time; var dist = (dest - origin).Length(); float speed = 15f; if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Bolt) { speed = GetStationaryVelocity(15f, dist); } else if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Streak) { speed = GetStationaryVelocity(45f, dist); } else if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc) { speed = GetStationaryVelocity(40f, dist); } // TODO: Implement target leading for non arc spells // Also: velocity seems to increase when target is moving away from the caster and decrease when // the target is moving toward the caster. This still needs more research. var velocity = direction * speed; if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc) { spellProjectile.Velocity = GetSpellProjectileVelocity(origin, dest, speed, out time); } else { spellProjectile.Velocity = new AceVector3(velocity.X, velocity.Y, velocity.Z); var velocityLength = spellProjectile.Velocity.Get().Length(); time = dist / velocityLength; } spellProjectile.FlightTime = time; var loc = caster.Location; origin = loc.Pos; if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc) { origin.Z += caster.Height; } else { origin.Z += caster.Height * 2.0f / 3.0f; } origin += direction * (caster.PhysicsObj.GetRadius() + spellProjectile.PhysicsObj.GetRadius()); spellProjectile.Location = new ACE.Entity.Position(loc.LandblockId.Raw, origin.X, origin.Y, origin.Z, loc.Rotation.X, loc.Rotation.Y, loc.Rotation.Z, loc.RotationW); spellProjectile.ParentWorldObject = (Creature)this; spellProjectile.TargetGuid = target.Guid; spellProjectile.LifeProjectileDamage = lifeProjectileDamage; LandblockManager.AddObject(spellProjectile); CurrentLandblock.EnqueueBroadcast(spellProjectile.Location, new GameMessageScript(spellProjectile.Guid, ACE.Entity.Enum.PlayScript.Launch, spellProjectile.PlayscriptIntensity)); // TODO : removed when real server projectile tracking and collisions are implemented var actionChain = new ActionChain(); actionChain.AddDelaySeconds(spellProjectile.FlightTime); actionChain.AddAction(spellProjectile, () => spellProjectile.HandleOnCollide(spellProjectile.TargetGuid)); actionChain.EnqueueChain(); }
private void HandleGameAction(QueuedGameAction action, Player player) { switch (action.ActionType) { case GameActionType.TalkDirect: { // TODO: remove this hack (using TalkDirect) ASAP var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } DeathMessageArgs d = new DeathMessageArgs(action.ActionBroadcastMessage, new ObjectGuid(action.ObjectId), new ObjectGuid(action.SecondaryObjectId)); HandleDeathMessage(obj, d); break; } case GameActionType.TeleToHouse: case GameActionType.TeleToLifestone: case GameActionType.TeleToMansion: case GameActionType.TeleToMarketPlace: case GameActionType.TeleToPkArena: case GameActionType.TeleToPklArena: { player.Teleport(action.ActionLocation); break; } case GameActionType.ApplyVisualEffect: { var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } var particleEffect = (PlayScript)action.SecondaryObjectId; HandleParticleEffectEvent(obj, particleEffect); break; } case GameActionType.ApplySoundEffect: { var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } var soundEffect = (Sound)action.SecondaryObjectId; HandleSoundEvent(obj, soundEffect); break; } case GameActionType.IdentifyObject: { // TODO: Throttle this request. The live servers did this, likely for a very good reason, so we should, too. var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } var identifyResponse = new GameEventIdentifyObjectResponse(player.Session, action.ObjectId, obj); player.Session.Network.EnqueueSend(identifyResponse); break; } case GameActionType.PutItemInContainer: { var playerId = new ObjectGuid(action.ObjectId); var inventoryId = new ObjectGuid(action.SecondaryObjectId); if (playerId.IsPlayer()) { Player aPlayer = null; WorldObject inventoryItem = null; if (worldObjects.ContainsKey(playerId) && worldObjects.ContainsKey(inventoryId)) { aPlayer = (Player)worldObjects[playerId]; inventoryItem = worldObjects[inventoryId]; } if ((aPlayer != null) && (inventoryItem != null)) { if (aPlayer.PhysicsData.Position.SquaredDistanceTo(inventoryItem.PhysicsData.Position) > Math.Pow(inventoryItem.GameData.UseRadius, 2)) { // This is where I need to hook in the move to object code. // TODO: Og II work on this soon. } var motion = new UniversalMotion(MotionStance.Standing); motion.MovementData.ForwardCommand = (ushort)MotionCommand.Pickup; aPlayer.Session.Network.EnqueueSend(new GameMessageUpdatePosition(aPlayer), new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessageSound(aPlayer.Guid, Sound.PickUpItem, (float)1.0)); // Add to the inventory list. aPlayer.AddToInventory(inventoryItem); LandblockManager.RemoveObject(inventoryItem); motion = new UniversalMotion(MotionStance.Standing); aPlayer.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(aPlayer.Session, PropertyInt.EncumbVal, aPlayer.GameData.Burden), new GameMessagePutObjectInContainer(aPlayer.Session, aPlayer, inventoryId), new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessageUpdateInstanceId(inventoryId, playerId), new GameMessagePickupEvent(aPlayer.Session, inventoryItem)); aPlayer.TrackObject(inventoryItem); // This may not be needed when we fix landblock update object - // TODO: Og II - check this later to see if it is still required. aPlayer.Session.Network.EnqueueSend(new GameMessageUpdateObject(inventoryItem)); } } break; } case GameActionType.DropItem: { var g = new ObjectGuid(action.ObjectId); // ReSharper disable once InconsistentlySynchronizedField if (worldObjects.ContainsKey(g)) { var playerId = new ObjectGuid(action.ObjectId); var inventoryId = new ObjectGuid(action.SecondaryObjectId); if (playerId.IsPlayer()) { Player aPlayer = null; WorldObject inventoryItem = null; if (worldObjects.ContainsKey(playerId)) { aPlayer = (Player)worldObjects[playerId]; inventoryItem = aPlayer.GetInventoryItem(inventoryId); aPlayer.RemoveFromInventory(inventoryId); } if ((aPlayer != null) && (inventoryItem != null)) { var targetContainer = new ObjectGuid(0); aPlayer.Session.Network.EnqueueSend( new GameMessagePrivateUpdatePropertyInt( aPlayer.Session, PropertyInt.EncumbVal, (uint)aPlayer.Session.Player.GameData.Burden)); var motion = new UniversalMotion(MotionStance.Standing); motion.MovementData.ForwardCommand = (ushort)MotionCommand.Pickup; aPlayer.Session.Network.EnqueueSend( new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessageUpdateInstanceId(inventoryId, targetContainer)); motion = new UniversalMotion(MotionStance.Standing); aPlayer.Session.Network.EnqueueSend( new GameMessageUpdateMotion(aPlayer, aPlayer.Session, motion), new GameMessagePutObjectIn3d(aPlayer.Session, aPlayer, inventoryId), new GameMessageSound(aPlayer.Guid, Sound.DropItem, (float)1.0), new GameMessageUpdateInstanceId(inventoryId, targetContainer)); // This is the sequence magic - adds back into 3d space seem to be treated like teleport. inventoryItem.Sequences.GetNextSequence(SequenceType.ObjectTeleport); inventoryItem.Sequences.GetNextSequence(SequenceType.ObjectVector); LandblockManager.AddObject(inventoryItem); // This may not be needed when we fix landblock update object - // TODO: Og II - check this later to see if it is still required. aPlayer.Session.Network.EnqueueSend(new GameMessageUpdateObject(inventoryItem)); aPlayer.Session.Network.EnqueueSend(new GameMessageUpdatePosition(inventoryItem)); } } } break; } case GameActionType.MovementEvent: { var g = new ObjectGuid(action.ObjectId); WorldObject obj = (WorldObject)player; if (worldObjects.ContainsKey(g)) { obj = worldObjects[g]; } var motion = action.Motion; HandleMovementEvent(obj, motion); break; } case GameActionType.ObjectCreate: { this.AddWorldObject(action.WorldObject); break; } case GameActionType.ObjectDelete: { this.RemoveWorldObject(action.WorldObject.Guid, false); break; } case GameActionType.QueryHealth: { if (action.ObjectId == 0) { // Deselect the formerly selected Target player.SelectedTarget = 0; break; } object target = null; var targetId = new ObjectGuid(action.ObjectId); // Remember the selected Target player.SelectedTarget = action.ObjectId; // TODO: once items are implemented check if there are items that can trigger // the QueryHealth event. So far I believe it only gets triggered for players and creatures if (targetId.IsPlayer() || targetId.IsCreature()) { if (this.worldObjects.ContainsKey(targetId)) { target = this.worldObjects[targetId]; } if (target == null) { // check adjacent landblocks for the targetId foreach (var block in adjacencies) { if (block.Value != null) { if (block.Value.worldObjects.ContainsKey(targetId)) { target = block.Value.worldObjects[targetId]; } } } } if (target != null) { float healthPercentage = 0; if (targetId.IsPlayer()) { Player tmpTarget = (Player)target; healthPercentage = (float)tmpTarget.Health.Current / (float)tmpTarget.Health.MaxValue; } if (targetId.IsCreature()) { Creature tmpTarget = (Creature)target; healthPercentage = (float)tmpTarget.Health.Current / (float)tmpTarget.Health.MaxValue; } var updateHealth = new GameEventUpdateHealth(player.Session, targetId.Full, healthPercentage); player.Session.Network.EnqueueSend(updateHealth); } } break; } case GameActionType.Use: { var g = new ObjectGuid(action.ObjectId); if (worldObjects.ContainsKey(g)) { WorldObject obj = worldObjects[g]; if ((obj.DescriptionFlags & ObjectDescriptionFlag.LifeStone) != 0) { (obj as Lifestone).OnUse(player); } else if ((obj.DescriptionFlags & ObjectDescriptionFlag.Portal) != 0) { // TODO: When Physics collisions are implemented, this logic should be switched there, as normal portals are not onUse. (obj as Portal).OnCollide(player); } else if ((obj.DescriptionFlags & ObjectDescriptionFlag.Door) != 0) { (obj as Door).OnUse(player); } // switch (obj.Type) // { // case Enum.ObjectType.Portal: // { // // TODO: When Physics collisions are implemented, this logic should be switched there, as normal portals are not onUse. // // (obj as Portal).OnCollide(player); // // break; // } // case Enum.ObjectType.LifeStone: // { // (obj as Lifestone).OnUse(player); // break; // } // } } break; } } }
public static void CreateLifeStone(Session session, params string[] parameters) { LandblockManager.AddObject(AdminObjectFactory.CreateLifestone(session.Player.Position.InFrontOf(3.0f))); }
/// <summary> /// main game loop /// </summary> public void UseTime() { while (running) { // here we'd move server objects in motion (subject to landscape) and do physics collision detection // for now, we'll move players around List <WorldObject> movedObjects = null; List <Player> players = null; lock (objectCacheLocker) { movedObjects = this.worldObjects.Values.OfType <WorldObject>().ToList(); players = this.worldObjects.Values.OfType <Player>().ToList(); } movedObjects = movedObjects.Where(p => p.LastUpdatedTicks >= p.LastMovementBroadcastTicks).ToList(); // flag them as updated now in order to reduce chance of missing an update movedObjects.ForEach(m => m.LastMovementBroadcastTicks = WorldManager.PortalYearTicks); if (this.id.MapScope == Enum.MapScope.Outdoors) { // check to see if a player or other mutable object "roamed" to an adjacent landblock var objectsToRelocate = movedObjects.Where(m => m.Location.LandblockId.IsAdjacentTo(this.id) && m.Location.LandblockId != this.id).ToList(); // so, these objects moved to an adjacent block. they could have recalled to that block, died and bounced to a lifestone on that block, or // just simply walked accross the border line. in any case, we won't delete them, we'll just transfer them. the trick, though, is to // figure out how to treat it in adjacent landblocks. if the player walks across the southern border, the north adjacency needs to remove // them, but the south is actually getting them. we need to avoid sending Delete+Create to clients that already know about it, though. objectsToRelocate.ForEach(o => Log($"attempting to relocate object {o.Name} ({o.Guid.Full.ToString("X")})")); // RelocateObject will put them in the right landblock objectsToRelocate.ForEach(o => LandblockManager.RelocateObject(o)); // Remove has logic to make sure it doesn't double up the delete+create when "true" is passed. objectsToRelocate.ForEach(o => RemoveWorldObject(o.Guid, true)); } // broadcast Parallel.ForEach(movedObjects, mo => { if (mo.Location.LandblockId == this.id) { // update if it's still here Broadcast(BroadcastEventArgs.CreateAction(BroadcastAction.AddOrUpdate, mo), true, Quadrant.All); } else { // remove and readd if it's not this.RemoveWorldObject(mo.Guid, false); LandblockManager.AddObject(mo); } }); // TODO: figure out if this landblock can be unloaded // process player action queues foreach (Player p in players) { QueuedGameAction action = p.ActionQueuePop(); if (action != null) { HandleGameAction(action, p); } } Thread.Sleep(1); } // TODO: release resources }