public static void Handle(ClientMessage message, Session session) { var objectGuid = new ObjectGuid(message.Payload.ReadUInt32()); QueuedGameAction action = new QueuedGameAction(session.Player.Guid.Full, objectGuid.Full, GameActionType.DropItem); session.Player.AddToActionQueue(action); }
public static void Handle(ClientMessage message, Session session) { uint vendorID = message.Payload.ReadUInt32(); uint itemcount = message.Payload.ReadUInt32(); List <ItemProfile> items = new List <ItemProfile>(); while (itemcount > 0) { itemcount--; ItemProfile item = new ItemProfile(); item.Amount = message.Payload.ReadUInt32(); item.Amount = item.Amount & 0xFFFFFF; item.Iid = message.Payload.ReadUInt32(); items.Add(item); } // curancy 0 default, if else then currancy is set other then money uint i_alternateCurrencyID = message.Payload.ReadUInt32(); // todo: take into account other currencyIds other then assuming default QueuedGameAction action = new QueuedGameAction(vendorID, items, GameActionType.Buy); session.Player.AddToActionQueue(action); return; }
public void Kill(Session session) { IsAlive = false; // Create and send the death notice string killMessage = $"{session.Player.Name} has killed {Name}."; var creatureDeathEvent = new GameEventDeathNotice(session, killMessage); session.Network.EnqueueSend(creatureDeathEvent); // MovementEvent: (Hand-)Combat or in the case of smite: from Standing to Death // TODO: Check if the duration of the motion can somehow be computed GeneralMotion motionDeath = new GeneralMotion(MotionStance.Standing, new MotionItem(MotionCommand.Dead)); QueuedGameAction actionDeath = new QueuedGameAction(this.Guid.Full, motionDeath, 2.0f, true, GameActionType.MovementEvent); session.Player.AddToActionQueue(actionDeath); // Create Corspe and set a location on the ground // TODO: set text of killer in description and find a better computation for the location, some corpse could end up in the ground var corpse = CorpseObjectFactory.CreateCorpse(this, this.Location); corpse.Location.PositionY -= corpse.PhysicsData.ObjScale; corpse.Location.PositionZ -= corpse.PhysicsData.ObjScale / 2; // Remove Creature from Landblock and add Corpse in that location via the ActionQueue to honor the motion delays QueuedGameAction removeCreature = new QueuedGameAction(this.Guid.Full, this, true, true, GameActionType.ObjectDelete); QueuedGameAction addCorpse = new QueuedGameAction(this.Guid.Full, corpse, true, GameActionType.ObjectCreate); session.Player.AddToActionQueue(removeCreature); session.Player.AddToActionQueue(addCorpse); }
public static void Handle(ClientMessage message, Session session) { var id = message.Payload.ReadUInt32(); QueuedGameAction action = new QueuedGameAction(id, GameActionType.IdentifyObject); session.Player.AddToExaminationQueue(action); }
public static void Handle(ClientMessage message, Session session) { uint fullId = message.Payload.ReadUInt32(); QueuedGameAction action = new QueuedGameAction(fullId, GameActionType.QueryHealth); session.Player.AddToActionQueue(action); }
public static void Handle(ClientMessage message, Session session) { var itemGuid = new ObjectGuid(message.Payload.ReadUInt32()); var containerGuid = new ObjectGuid(message.Payload.ReadUInt32()); QueuedGameAction action = new QueuedGameAction(containerGuid.Full, itemGuid.Full, GameActionType.PutItemInContainer); session.Player.AddToActionQueue(action); }
public static void Handle(ClientMessage message, Session session) { uint fullId = message.Payload.ReadUInt32(); QueuedGameAction action = new QueuedGameAction(fullId, GameActionType.EvtInventoryUseEvent); session.Player.AddToActionQueue(action); return; }
public virtual void OnKill(Session session) { IsAlive = false; // This will determine if the derived type is a player var isDerivedPlayer = Guid.IsPlayer(); // TODO: Implement some proper respawn timers, check the generators for that RespawnTime = WorldManager.PortalYearTicks + 10; if (!isDerivedPlayer) { // Create and send the death notice string killMessage = $"{session.Player.Name} has killed {Name}."; var creatureDeathEvent = new GameEventDeathNotice(session, killMessage); session.Network.EnqueueSend(creatureDeathEvent); } // MovementEvent: (Hand-)Combat or in the case of smite: from Standing to Death // TODO: Check if the duration of the motion can somehow be computed UniversalMotion motionDeath = new UniversalMotion(MotionStance.Standing, new MotionItem(MotionCommand.Dead)); QueuedGameAction actionDeath = new QueuedGameAction(this.Guid.Full, motionDeath, 2.0f, true, GameActionType.MovementEvent); session.Player.AddToActionQueue(actionDeath); // Create Corspe and set a location on the ground // TODO: set text of killer in description and find a better computation for the location, some corpse could end up in the ground var corpse = CorpseObjectFactory.CreateCorpse(this, this.Location); corpse.Location.PositionY -= corpse.PhysicsData.ObjScale; corpse.Location.PositionZ -= corpse.PhysicsData.ObjScale / 2; // Corpses stay on the ground for 5 * player level but minimum 1 hour // corpse.DespawnTime = Math.Max((int)session.Player.PropertiesInt[Enum.Properties.PropertyInt.Level] * 5, 360) + WorldManager.PortalYearTicks; // as in live corpse.DespawnTime = 20 + WorldManager.PortalYearTicks; // only for testing // If the object is a creature, Remove it from from Landblock if (!isDerivedPlayer) { QueuedGameAction removeCreature = new QueuedGameAction(this.Guid.Full, this, true, true, GameActionType.ObjectDelete); session.Player.AddToActionQueue(removeCreature); } // Add Corpse in that location via the ActionQueue to honor the motion delays QueuedGameAction addCorpse = new QueuedGameAction(this.Guid.Full, corpse, true, GameActionType.ObjectCreate); session.Player.AddToActionQueue(addCorpse); }
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.Buy: { // todo: lots, need vendor list, money checks, etc. var money = new GameMessagePrivateUpdatePropertyInt(player.Session, PropertyInt.CoinValue, 4000); var sound = new GameMessageSound(player.Guid, Sound.PickUpItem, 1); var sendUseDoneEvent = new GameEventUseDone(player.Session); player.Session.Network.EnqueueSend(money, sound, sendUseDoneEvent); // send updated vendor inventory. player.Session.Network.EnqueueSend(new GameEventApproachVendor(player.Session, action.ObjectId)); // this is just some testing code for now. foreach (ItemProfile item in action.ProfileItems) { // todo: something with vendor id and profile list... iid list from vendor dbs. // todo: something with amounts.. if (item.Iid == 5) { while (item.Amount > 0) { item.Amount--; WorldObject loot = LootGenerationFactory.CreateTestWorldObject(5090); LootGenerationFactory.AddToContainer(loot, player); player.TrackObject(loot); } var rudecomment = "Who do you think you are, Johny Apple Seed ?"; var buyrudemsg = new GameMessageSystemChat(rudecomment, ChatMessageType.Tell); player.Session.Network.EnqueueSend(buyrudemsg); } else if (item.Iid == 10) { while (item.Amount > 0) { item.Amount--; WorldObject loot = LootGenerationFactory.CreateTestWorldObject(30537); LootGenerationFactory.AddToContainer(loot, player); player.TrackObject(loot); } var rudecomment = "That smells awful, Enjoy eating it!"; var buyrudemsg = new GameMessageSystemChat(rudecomment, ChatMessageType.Tell); player.Session.Network.EnqueueSend(buyrudemsg); } } break; } case GameActionType.PutItemInContainer: { var playerGuid = new ObjectGuid(action.ObjectId); var inventoryGuid = new ObjectGuid(action.SecondaryObjectId); // Has to be a player so need to check before I make the cast. // If he is not a player, something is bad wrong. Og II if (playerGuid.IsPlayer()) { var aPlayer = (Player)GetWorldObject(playerGuid); var inventoryItem = GetWorldObject(inventoryGuid); float arrivedRadiusSquared = 0.00f; bool validGuids; if (WithinUseRadius(playerGuid, inventoryGuid, out arrivedRadiusSquared, out validGuids)) { aPlayer.NotifyAndAddToInventory(inventoryItem); } else { if (validGuids) { aPlayer.SetDestinationInformation(inventoryItem.PhysicsData.Position, arrivedRadiusSquared); aPlayer.BlockedGameAction = action; aPlayer.OnAutonomousMove(inventoryItem.PhysicsData.Position, aPlayer.Sequences, MovementTypes.MoveToObject, inventoryGuid); } } } break; } case GameActionType.DropItem: { var g = new ObjectGuid(action.ObjectId); if (worldObjects.ContainsKey(g)) { var playerId = new ObjectGuid(action.ObjectId); var inventoryId = new ObjectGuid(action.SecondaryObjectId); if (playerId.IsPlayer()) { var aPlayer = (Player)worldObjects[playerId]; aPlayer.NotifyAndDropItem(inventoryId); } } 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: { AddWorldObject(action.WorldObject); break; } case GameActionType.ObjectDelete: { 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 (worldObjects.ContainsKey(targetId)) { target = 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); } else if ((obj.DescriptionFlags & ObjectDescriptionFlag.Vendor) != 0) { (obj as Vendor).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; } } }
/// <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 }
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> /// 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 }
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; } } }