public void HandleActionDropItem(ObjectGuid itemGuid) { // Goody Goody -- lets build drop chain // First start drop animation new ActionChain(this, () => { // check packs of item. WorldObject item; if (!TryRemoveFromInventory(itemGuid, out item)) { // check to see if this item is wielded if (TryDequipObject(itemGuid, out item)) { Session.Network.EnqueueSend( new GameMessageSound(Guid, Sound.WieldObject, 1.0f), new GameMessageObjDescEvent(this), new GameMessageUpdateInstanceId(Guid, new ObjectGuid(0), PropertyInstanceId.Wielder)); } } item.SetPropertiesForWorld(this); UniversalMotion motion = new UniversalMotion(MotionStance.Standing); motion.MovementData.ForwardCommand = (uint)MotionCommand.Pickup; Session.Network.EnqueueSend(new GameMessageUpdateInstanceId(itemGuid, new ObjectGuid(0), PropertyInstanceId.Container)); // Set drop motion CurrentLandblock.EnqueueBroadcastMotion(this, motion); // Now wait for Drop Motion to finish -- use ActionChain ActionChain chain = new ActionChain(); // Wait for drop animation var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId); var pickupAnimationLength = motionTable.GetAnimationLength(MotionCommand.Pickup); chain.AddDelaySeconds(pickupAnimationLength); // Play drop sound // Put item on landblock chain.AddAction(this, () => { motion = new UniversalMotion(MotionStance.Standing); CurrentLandblock.EnqueueBroadcastMotion(this, motion); Session.Network.EnqueueSend( new GameMessageSound(Guid, Sound.DropItem, (float)1.0), new GameMessagePutObjectIn3d(Session, this, itemGuid), new GameMessageUpdateInstanceId(itemGuid, new ObjectGuid(0), PropertyInstanceId.Container)); // This is the sequence magic - adds back into 3d space seem to be treated like teleport. Debug.Assert(item != null, "item != null"); item.Sequences.GetNextSequence(SequenceType.ObjectTeleport); item.Sequences.GetNextSequence(SequenceType.ObjectVector); CurrentLandblock.AddWorldObject(item); Session.Network.EnqueueSend(new GameMessageUpdateObject(item)); }); chain.EnqueueChain(); // Removed SaveSession - this was causing items that were dropped to not be removed from inventory. // If this causes a problem with vendor, we need to fix vendor. Og II }).EnqueueChain(); }
/// <summary> /// Handles the eviction process for a player house /// </summary> public static void HandleEviction(House house, uint playerGuid, bool multihouse = false, bool force = false) { // clear out slumlord inventory var slumlord = house.SlumLord; slumlord.ClearInventory(true); var player = PlayerManager.FindByGuid(playerGuid, out bool isOnline); if (!PropertyManager.GetBool("house_rent_enabled", true).Item&& !multihouse && !force) { // rent disabled, push forward var purchaseTime = (uint)(player.HousePurchaseTimestamp ?? 0); var nextRentTime = house.GetRentDue(purchaseTime); player.HouseRentTimestamp = (int)nextRentTime; log.Debug($"[HOUSE] HouseManager.HandleRentPaid({player.Name}): house rent disabled via config"); // re-add item to queue AddRentQueue(player, house); return; } // handle eviction house.HouseOwner = null; house.MonarchId = null; house.HouseOwnerName = null; house.ClearPermissions(); house.SaveBiotaToDatabase(); // relink house.UpdateLinks(); if (house.HasDungeon) { var dungeonHouse = house.GetDungeonHouse(); if (dungeonHouse != null) { dungeonHouse.UpdateLinks(); } } // player slumlord 'off' animation slumlord.Off(); // reset slumlord name slumlord.SetAndBroadcastName(); slumlord.SaveBiotaToDatabase(); // if evicting a multihouse owner's previous house, // no update for player properties if (player.HouseInstance == house.Guid.Full) { player.HouseId = null; player.HouseInstance = null; //player.HousePurchaseTimestamp = null; player.HouseRentTimestamp = null; } else { log.Warn($"[HOUSE] HouseManager.HandleRentEviction({house.Guid}, {player.Name}, {multihouse}): house guids don't match {player.HouseInstance}"); } house.ClearRestrictions(); log.Debug($"[HOUSE] HouseManager.HandleRentEviction({player.Name})"); if (multihouse) { RemoveRentQueue(house.Guid.Full); player.SaveBiotaToDatabase(); return; } if (!isOnline) { // inform player of eviction when they log in var offlinePlayer = PlayerManager.GetOfflinePlayer(playerGuid); if (offlinePlayer == null) { log.Warn($"[HOUSE] {player.Name}.HandleEviction(): couldn't find offline player"); return; } offlinePlayer.SetProperty(PropertyBool.HouseEvicted, true); offlinePlayer.SaveBiotaToDatabase(); return; } var onlinePlayer = PlayerManager.GetOnlinePlayer(playerGuid); onlinePlayer.House = null; // send text message onlinePlayer.Session.Network.EnqueueSend(new GameMessageSystemChat("You've been evicted from your house!", ChatMessageType.Broadcast)); onlinePlayer.RemoveDeed(); onlinePlayer.SaveBiotaToDatabase(); // clear house panel for online player var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); // wait for slumlord inventory biotas above to save actionChain.AddAction(onlinePlayer, onlinePlayer.HandleActionQueryHouse); actionChain.EnqueueChain(); }
public void HandleSwitchToMissileCombatMode(ActionChain combatModeChain) { // TODO and FIXME: GetInventoryItem doesn't work for this so this function is effectively broke HeldItem mEquipedMissile = Children.Find(s => s.EquipMask == EquipMask.MissileWeapon); if (mEquipedMissile?.Guid != null) { WorldObject missileWeapon = GetInventoryItem(new ObjectGuid(mEquipedMissile.Guid)); if (missileWeapon == null) { log.InfoFormat("Changing combat mode for {0} - could not locate wielded weapon {1}", Guid, mEquipedMissile.Guid); return; } var mEquipedAmmo = WieldedObjects.First(s => s.Value.CurrentWieldedLocation == EquipMask.MissileAmmo).Value; MotionStance ms; CombatStyle cs; if (missileWeapon.DefaultCombatStyle != null) { cs = missileWeapon.DefaultCombatStyle.Value; } else { log.InfoFormat("Changing combat mode for {0} - wielded item {1} has not be assigned a default combat style", Guid, mEquipedMissile.Guid); return; } switch (cs) { case CombatStyle.Bow: ms = MotionStance.BowAttack; break; case CombatStyle.Crossbow: ms = MotionStance.CrossBowAttack; break; default: ms = MotionStance.Invalid; break; } UniversalMotion mm = new UniversalMotion(ms); mm.MovementData.CurrentStyle = (ushort)ms; if (mEquipedAmmo == null) { CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageUpdatePosition(this)); SetMotionState(this, mm); } else { CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageUpdatePosition(this)); SetMotionState(this, mm); mm.MovementData.ForwardCommand = (uint)MotionCommand.Reload; SetMotionState(this, mm); CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageUpdatePosition(this)); // FIXME: (Og II)<this is a hack for now to be removed. Need to pull delay from dat file combatModeChain.AddDelaySeconds(0.25); // System.Threading.Thread.Sleep(250); // used for debugging mm.MovementData.ForwardCommand = (ushort)MotionCommand.Invalid; SetMotionState(this, mm); // FIXME: (Og II)<this is a hack for now to be removed. Need to pull delay from dat file combatModeChain.AddDelaySeconds(0.40); combatModeChain.AddAction(this, () => CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageParentEvent(this, mEquipedAmmo, 1, 1))); // CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageParentEvent(this, ammo, 1, 1)); // used for debugging } CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessagePrivateUpdatePropertyInt(Sequences, PropertyInt.CombatMode, (int)CombatMode.Missile)); } }
public void ExecuteEmote(BiotaPropertiesEmote emote, BiotaPropertiesEmoteAction emoteAction, ActionChain actionChain, WorldObject sourceObject = null, WorldObject targetObject = null) { var player = targetObject as Player; var creature = sourceObject as Creature; var targetCreature = targetObject as Creature; var emoteType = (EmoteType)emoteAction.Type; //if (emoteType != EmoteType.Motion && emoteType != EmoteType.Turn && emoteType != EmoteType.Move) //Console.WriteLine($"ExecuteEmote({emoteType})"); var text = emoteAction.Message; switch ((EmoteType)emoteAction.Type) { case EmoteType.Act: // short for 'acting' text var message = Replace(text, sourceObject, targetObject); sourceObject?.EnqueueBroadcast(new GameMessageSystemChat(message, ChatMessageType.Broadcast), 30.0f); break; case EmoteType.Activate: actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { if (creature != null && (creature.ActivationTarget ?? 0) > 0) { var activationTarget = creature.CurrentLandblock?.GetObject(creature.ActivationTarget ?? 0); activationTarget?.ActOnUse(creature); } }); break; case EmoteType.AddCharacterTitle: // emoteAction.Stat == null for all EmoteType.AddCharacterTitle entries in current db? if (player != null) { player.AddTitle((CharacterTitle)emoteAction.Stat); } break; case EmoteType.AddContract: //if (player != null) //Contracts werent in emote table //player.AddContract(emoteAction.Stat); break; case EmoteType.AdminSpam: var players = WorldManager.GetAll(); foreach (var onlinePlayer in players) { onlinePlayer.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.AdminTell)); } break; case EmoteType.AwardLevelProportionalSkillXP: if (player != null) { player.GrantLevelProportionalSkillXP((Skill)emoteAction.Stat, emoteAction.Percent ?? 0, (ulong)emoteAction.Max); } break; case EmoteType.AwardLevelProportionalXP: if (player != null) { player.GrantLevelProportionalXp(emoteAction.Percent ?? 0, (ulong)emoteAction.Max); } break; case EmoteType.AwardLuminance: if (player != null) { player.GrantLuminance((long)emoteAction.Amount); } break; case EmoteType.AwardNoShareXP: actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { if (player != null) { player.EarnXP((long)emoteAction.Amount64); player.Session.Network.EnqueueSend(new GameMessageSystemChat("You've earned " + emoteAction.Amount64 + " experience.", ChatMessageType.Broadcast)); } }); break; case EmoteType.AwardSkillPoints: if (player != null) { player.AwardSkillPoints((Skill)emoteAction.Stat, (uint)emoteAction.Amount, true); } break; case EmoteType.AwardSkillXP: if (player != null) { player.RaiseSkillGameAction((Skill)emoteAction.Stat, (uint)emoteAction.Amount, true); } break; case EmoteType.AwardTrainingCredits: if (player != null) { player.AddSkillCredits((int)emoteAction.Amount, true); } break; case EmoteType.AwardXP: actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { if (player != null) { player.EarnXP((long)emoteAction.Amount64); player.Session.Network.EnqueueSend(new GameMessageSystemChat("You've earned " + emoteAction.Amount64 + " experience.", ChatMessageType.Broadcast)); } }); break; case EmoteType.BLog: // only one test drudge used this emoteAction. break; case EmoteType.CastSpell: if (WorldObject is Player) { (WorldObject as Player).CreatePlayerSpell((uint)emoteAction.SpellId); } else if (WorldObject is Creature) { (WorldObject as Creature).CreateCreatureSpell(player.Guid, (uint)emoteAction.SpellId); } break; case EmoteType.CastSpellInstant: var spellTable = DatManager.PortalDat.SpellTable; var spell = spellTable.Spells[(uint)emoteAction.SpellId]; actionChain.AddAction(sourceObject, () => { if (spell.TargetEffect > 0) { creature.CreateCreatureSpell(targetObject.Guid, (uint)emoteAction.SpellId); } else { creature.CreateCreatureSpell((uint)emoteAction.SpellId); } }); break; case EmoteType.CloseMe: targetObject.Close(WorldObject); break; case EmoteType.CreateTreasure: break; case EmoteType.DecrementIntStat: var id = (PropertyInt)emoteAction.Stat; var prop = player.GetProperty(id); if (prop != null) { player.SetProperty(id, prop.Value - 1); } break; case EmoteType.DecrementMyQuest: break; case EmoteType.DecrementQuest: // Used as part of the test drudge for events break; case EmoteType.DeleteSelf: sourceObject.CurrentLandblock?.RemoveWorldObject(sourceObject.Guid, false); break; case EmoteType.DirectBroadcast: text = Replace(emoteAction.Message, WorldObject, targetObject); if (player != null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); } break; case EmoteType.EraseMyQuest: break; case EmoteType.EraseQuest: if (player != null) { player.QuestManager.Erase(emoteAction.Message); } break; case EmoteType.FellowBroadcast: text = emoteAction.Message; if (player != null) { var fellowship = player.Fellowship; if (fellowship == null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); } else { foreach (var fellow in fellowship.FellowshipMembers) { fellow.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); } } } break; case EmoteType.Generate: uint wcid = (uint)emoteAction.WeenieClassId; var item = WorldObjectFactory.CreateNewWorldObject((wcid)); break; case EmoteType.Give: bool success = false; if (player != null && emoteAction.WeenieClassId != null) { actionChain.AddAction(sourceObject, () => { item = WorldObjectFactory.CreateNewWorldObject((uint)emoteAction.WeenieClassId); var stackSize = emoteAction.StackSize ?? 1; var stackMsg = ""; if (stackSize > 1) { item.StackSize = (ushort)stackSize; stackMsg = stackSize + " "; // pluralize? } success = player.TryCreateInInventoryWithNetworking(item); // transaction / rollback on failure? if (success) { var msg = new GameMessageSystemChat($"{WorldObject.Name} gives you {stackMsg}{item.Name}.", ChatMessageType.Broadcast); var sound = new GameMessageSound(player.Guid, Sound.ReceiveItem, 1); player.Session.Network.EnqueueSend(msg, sound); } }); } break; case EmoteType.Goto: var rng = Physics.Common.Random.RollDice(0.0f, 1.0f); var firstEmote = sourceObject.Biota.BiotaPropertiesEmote.FirstOrDefault(e => e.Category == (uint)EmoteCategory.GotoSet && rng < e.Probability); foreach (var action in firstEmote.BiotaPropertiesEmoteAction) { actionChain.AddAction(player, () => { ExecuteEmote(firstEmote, action, actionChain, sourceObject, targetObject); }); } break; case EmoteType.IncrementIntStat: if (player == null || emoteAction.Stat == null) { break; } id = (PropertyInt)emoteAction.Stat; prop = player.GetProperty(id); if (prop != null) { player.SetProperty(id, prop.Value + 1); } break; case EmoteType.IncrementMyQuest: break; case EmoteType.IncrementQuest: if (player != null) { player.QuestManager.Increment(emoteAction.Message); } break; case EmoteType.InflictVitaePenalty: if (player != null) { player.VitaeCpPool++; // TODO: full path } break; case EmoteType.InqAttributeStat: if (targetCreature != null) { var attr = targetCreature.GetCreatureAttribute((PropertyAttribute)emoteAction.Stat); success = attr != null && attr.Ranks >= emoteAction.Min && attr.Ranks <= emoteAction.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqBoolStat: // This is only used with NPC's 24944 and 6386, which are dev tester npc's. Not worth the current effort. // Could also be post-ToD break; case EmoteType.InqContractsFull: // not part of the game at PY16? //if (player != null) //{ // var contracts = player.TrackedContracts; // InqCategory(contracts.Count != 0 ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); //} break; case EmoteType.InqEvent: var started = EventManager.IsEventStarted(emoteAction.Message); InqCategory(started ? EmoteCategory.EventSuccess : EmoteCategory.EventFailure, emoteAction, sourceObject, targetObject, actionChain); break; case EmoteType.InqFellowNum: InqCategory(player != null && player.Fellowship != null ? EmoteCategory.TestSuccess : EmoteCategory.TestNoFellow, emoteAction, sourceObject, targetObject, actionChain); break; case EmoteType.InqFellowQuest: // focusing on 1 person quests to begin with break; case EmoteType.InqFloatStat: //InqProperty(target.GetProperty((PropertyFloat)emote.Stat), emote); break; case EmoteType.InqInt64Stat: //InqProperty(target.GetProperty((PropertyInt64)emote.Stat), emote); break; case EmoteType.InqIntStat: if (emoteAction.Stat != 25) { break; // ?? } success = player.Level >= emoteAction.Min && player.Level <= emoteAction.Max; // rng for failure case? var useRNG = !success; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emoteAction, sourceObject, targetObject, actionChain, useRNG); break; case EmoteType.InqMyQuest: break; case EmoteType.InqMyQuestBitsOff: break; case EmoteType.InqMyQuestBitsOn: break; case EmoteType.InqMyQuestSolves: break; case EmoteType.InqNumCharacterTitles: //if (player != null) //InqCategory(player.NumCharacterTitles != 0 ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); break; case EmoteType.InqOwnsItems: //if (player != null) //InqCategory(player.Inventory.Count > 0 ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); break; case EmoteType.InqPackSpace: //if (player != null) //{ // var freeSpace = player.ContainerCapacity > player.ItemCapacity; // InqCategory(freeSpace ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emote); //} break; case EmoteType.InqQuest: if (player != null) { var hasQuest = player.QuestManager.HasQuest(emoteAction.Message); InqCategory(hasQuest ? EmoteCategory.QuestSuccess : EmoteCategory.QuestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqQuestBitsOff: break; case EmoteType.InqQuestBitsOn: break; case EmoteType.InqQuestSolves: // should this be different from InqQuest? if (player != null) { var hasQuest = player.QuestManager.HasQuest(emoteAction.Message); InqCategory(hasQuest ? EmoteCategory.QuestSuccess : EmoteCategory.QuestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqRawAttributeStat: if (targetCreature != null) { var attr = targetCreature.GetCreatureAttribute((PropertyAttribute)emoteAction.Stat); success = attr != null && attr.Base >= emoteAction.Min && attr.Base <= emoteAction.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqRawSecondaryAttributeStat: if (targetCreature != null) { var vital = targetCreature.GetCreatureVital((PropertyAttribute2nd)emoteAction.Stat); success = vital != null && vital.Base >= emoteAction.Min && vital.Base <= emoteAction.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqRawSkillStat: if (targetCreature != null) { var skill = targetCreature.GetCreatureSkill((Skill)emoteAction.Stat); success = skill != null && skill.Base >= emoteAction.Min && skill.Base <= emoteAction.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqSecondaryAttributeStat: if (targetCreature != null) { var vital = targetCreature.GetCreatureVital((PropertyAttribute2nd)emoteAction.Stat); success = vital != null && vital.Ranks >= emoteAction.Min && vital.Ranks <= emoteAction.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqSkillSpecialized: if (targetCreature != null) { var skill = targetCreature.GetCreatureSkill((Skill)emoteAction.Stat); //InqProperty(skill.Status == SkillStatus.Specialized, emoteAction); } break; case EmoteType.InqSkillStat: if (targetCreature != null) { var skill = targetCreature.GetCreatureSkill((Skill)emoteAction.Stat); success = skill != null && skill.Ranks >= emoteAction.Min && skill.Ranks <= emoteAction.Max; InqCategory(success ? EmoteCategory.TestSuccess : EmoteCategory.TestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqSkillTrained: if (targetCreature != null) { var skill = targetCreature.GetCreatureSkill((Skill)emoteAction.Stat); // TestNoQuality? InqProperty(skill.AdvancementClass == SkillAdvancementClass.Trained || skill.AdvancementClass == SkillAdvancementClass.Specialized, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.InqStringStat: //InqProperty(targetCreature.GetProperty((PropertyString)emoteAction.Stat), emote); break; case EmoteType.InqYesNo: ConfirmationManager.ProcessConfirmation((uint)emoteAction.Stat, true); break; case EmoteType.Invalid: break; case EmoteType.KillSelf: if (targetCreature != null) { targetCreature.Smite(targetCreature); } break; case EmoteType.LocalBroadcast: if (actionChain != null) { actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { sourceObject?.EnqueueBroadcast(new GameMessageCreatureMessage(emoteAction.Message, sourceObject.Name, sourceObject.Guid.Full, ChatMessageType.Broadcast)); }); } else { sourceObject.EnqueueBroadcast(new GameMessageCreatureMessage(emoteAction.Message, sourceObject.Name, sourceObject.Guid.Full, ChatMessageType.Broadcast)); } break; case EmoteType.LocalSignal: break; case EmoteType.LockFellow: if (player != null && player.Fellowship != null) { player.HandleActionFellowshipChangeOpenness(false); } break; case EmoteType.ForceMotion: // TODO: figure out the difference case EmoteType.Motion: if (sourceObject == null || sourceObject.CurrentMotionState == null) { break; } if (emote.Category != (uint)EmoteCategory.Vendor && emote.Style != null) { var startingMotion = new UniversalMotion((MotionStance)emote.Style, new MotionItem((MotionCommand)emote.Substyle)); var motion = new UniversalMotion((MotionStance)emote.Style, new MotionItem((MotionCommand)emoteAction.Motion, emoteAction.Extent)); if (sourceObject.CurrentMotionState.Stance != startingMotion.Stance) { if (sourceObject.CurrentMotionState.Stance == MotionStance.Invalid) { actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { sourceObject.EnqueueBroadcastMotion(startingMotion); sourceObject.CurrentMotionState = startingMotion; }); } } else { if (sourceObject.CurrentMotionState.Commands.Count > 0 && sourceObject.CurrentMotionState.Commands[0].Motion == startingMotion.Commands[0].Motion) { actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { sourceObject.EnqueueBroadcastMotion(motion); sourceObject.CurrentMotionState = motion; }); actionChain.AddDelaySeconds(DatManager.PortalDat.ReadFromDat <DatLoader.FileTypes.MotionTable>(sourceObject.MotionTableId).GetAnimationLength((MotionCommand)emoteAction.Motion)); if (motion.Commands[0].Motion != MotionCommand.Sleeping && motion.Commands[0].Motion != MotionCommand.Sitting) // this feels like it can be handled better, somehow? { actionChain.AddAction(sourceObject, () => { sourceObject.EnqueueBroadcastMotion(startingMotion); sourceObject.CurrentMotionState = startingMotion; }); } } } } else { var motion = new UniversalMotion(MotionStance.NonCombat, new MotionItem((MotionCommand)emoteAction.Motion, emoteAction.Extent)); actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { sourceObject.EnqueueBroadcastMotion(motion); sourceObject.CurrentMotionState = motion; }); } break; case EmoteType.Move: // what is the difference between this and MoveToPos? // using MoveToPos logic for now... if (targetCreature != null) { var currentPos = targetCreature.Location; var newPos = new Position(); newPos.LandblockId = new LandblockId(currentPos.LandblockId.Raw); newPos.Pos = new Vector3(emoteAction.OriginX ?? currentPos.Pos.X, emoteAction.OriginY ?? currentPos.Pos.Y, emoteAction.OriginZ ?? currentPos.Pos.Z); if (emoteAction.AnglesX == null || emoteAction.AnglesY == null || emoteAction.AnglesZ == null || emoteAction.AnglesW == null) { newPos.Rotation = new Quaternion(currentPos.Rotation.X, currentPos.Rotation.Y, currentPos.Rotation.Z, currentPos.Rotation.W); } else { newPos.Rotation = new Quaternion(emoteAction.AnglesX ?? 0, emoteAction.AnglesY ?? 0, emoteAction.AnglesZ ?? 0, emoteAction.AnglesW ?? 1); } if (emoteAction.ObjCellId != null) { newPos.LandblockId = new LandblockId(emoteAction.ObjCellId.Value); } targetCreature.MoveTo(newPos, targetCreature.GetRunRate()); } break; case EmoteType.MoveHome: // TODO: call MoveToManager on server if (targetCreature != null) { targetCreature.MoveTo(targetCreature.Home, targetCreature.GetRunRate()); } break; case EmoteType.MoveToPos: if (targetCreature != null) { var currentPos = targetCreature.Location; var newPos = new Position(); newPos.LandblockId = new LandblockId(currentPos.LandblockId.Raw); newPos.Pos = new Vector3(emoteAction.OriginX ?? currentPos.Pos.X, emoteAction.OriginY ?? currentPos.Pos.Y, emoteAction.OriginZ ?? currentPos.Pos.Z); if (emoteAction.AnglesX == null || emoteAction.AnglesY == null || emoteAction.AnglesZ == null || emoteAction.AnglesW == null) { newPos.Rotation = new Quaternion(currentPos.Rotation.X, currentPos.Rotation.Y, currentPos.Rotation.Z, currentPos.Rotation.W); } else { newPos.Rotation = new Quaternion(emoteAction.AnglesX ?? 0, emoteAction.AnglesY ?? 0, emoteAction.AnglesZ ?? 0, emoteAction.AnglesW ?? 1); } if (emoteAction.ObjCellId != null) { newPos.LandblockId = new LandblockId(emoteAction.ObjCellId.Value); } targetCreature.MoveTo(newPos, targetCreature.GetRunRate()); } break; case EmoteType.OpenMe: sourceObject.Open(sourceObject); break; case EmoteType.PetCastSpellOnOwner: if (creature != null) { creature.CreateCreatureSpell(targetObject.Guid, (uint)emoteAction.SpellId); } break; case EmoteType.PhysScript: // TODO: landblock broadcast if (sourceObject != null) { sourceObject.PhysicsObj.play_script((PlayScript)emoteAction.PScript, 1.0f); } break; case EmoteType.PopUp: if (player != null) { if ((ConfirmationType)emoteAction.Stat == ConfirmationType.Undefined) { player.Session.Network.EnqueueSend(new GameEventPopupString(player.Session, emoteAction.Message)); } else { Confirmation confirm = new Confirmation((ConfirmationType)emoteAction.Stat, emoteAction.Message, sourceObject.Guid.Full, targetObject.Guid.Full); ConfirmationManager.AddConfirmation(confirm); player.Session.Network.EnqueueSend(new GameEventConfirmationRequest(player.Session, (ConfirmationType)emoteAction.Stat, confirm.ConfirmationID, confirm.Message)); } } break; case EmoteType.RemoveContract: if (player != null) { player.HandleActionAbandonContract((uint)emoteAction.Stat); } break; case EmoteType.RemoveVitaePenalty: if (player != null) { player.VitaeCpPool = 0; // TODO: call full path } break; case EmoteType.ResetHomePosition: //creature = sourceObject as Creature; //if (creature != null) // creature.Home = emoteAction.Position; break; case EmoteType.Say: actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { sourceObject?.EnqueueBroadcast(new GameMessageCreatureMessage(emoteAction.Message, sourceObject.Name, sourceObject.Guid.Full, ChatMessageType.Emote)); }); break; case EmoteType.SetAltRacialSkills: break; case EmoteType.SetBoolStat: targetObject.SetProperty((PropertyBool)emoteAction.Stat, emoteAction.Amount == 0 ? false : true); break; case EmoteType.SetEyePalette: if (creature != null) { creature.EyesPaletteDID = (uint)emoteAction.Display; } break; case EmoteType.SetEyeTexture: if (creature != null) { creature.EyesTextureDID = (uint)emoteAction.Display; } break; case EmoteType.SetFloatStat: targetObject.SetProperty((PropertyFloat)emoteAction.Stat, (float)emoteAction.Amount); break; case EmoteType.SetHeadObject: if (creature != null) { creature.HeadObjectDID = (uint)emoteAction.Display; } break; case EmoteType.SetHeadPalette: break; case EmoteType.SetInt64Stat: player.SetProperty((PropertyInt)emoteAction.Stat, (int)emoteAction.Amount); break; case EmoteType.SetIntStat: player.SetProperty((PropertyInt)emoteAction.Stat, (int)emoteAction.Amount); break; case EmoteType.SetMouthPalette: break; case EmoteType.SetMouthTexture: creature = sourceObject as Creature; if (creature != null) { creature.MouthTextureDID = (uint)emoteAction.Display; } break; case EmoteType.SetMyQuestBitsOff: break; case EmoteType.SetMyQuestBitsOn: break; case EmoteType.SetMyQuestCompletions: break; case EmoteType.SetNosePalette: break; case EmoteType.SetNoseTexture: creature = sourceObject as Creature; if (creature != null) { creature.NoseTextureDID = (uint)emoteAction.Display; } break; case EmoteType.SetQuestBitsOff: break; case EmoteType.SetQuestBitsOn: break; case EmoteType.SetQuestCompletions: break; case EmoteType.SetSanctuaryPosition: //if (player != null) //player.Sanctuary = emote.Position; break; case EmoteType.Sound: targetObject.EnqueueBroadcast(new GameMessageSound(targetObject.Guid, (Sound)emoteAction.Sound, 1.0f)); break; case EmoteType.SpendLuminance: if (player != null) { player.SpendLuminance((long)emoteAction.Amount); } break; case EmoteType.StampFellowQuest: break; case EmoteType.StampMyQuest: break; case EmoteType.StampQuest: // work needs to be done here if (player != null) { player.QuestManager.Add(emoteAction.Message); } break; case EmoteType.StartBarber: break; case EmoteType.StartEvent: EventManager.StartEvent(emoteAction.Message); break; case EmoteType.StopEvent: EventManager.StopEvent(emoteAction.Message); break; case EmoteType.TakeItems: if (player != null && emoteAction.WeenieClassId != null) { item = WorldObjectFactory.CreateNewWorldObject((uint)emoteAction.WeenieClassId); if (item == null) { break; } success = player.TryRemoveItemFromInventoryWithNetworking(item, (ushort)emoteAction.Amount); } break; case EmoteType.TeachSpell: if (player != null) { player.LearnSpellWithNetworking((uint)emoteAction.SpellId); } break; case EmoteType.TeleportSelf: //if (WorldObject is Player) //(WorldObject as Player).Teleport(emote.Position); break; case EmoteType.TeleportTarget: //if (player != null) //player.Teleport(emote.Position); break; case EmoteType.Tell: actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { player.Session.Network.EnqueueSend(new GameMessageHearDirectSpeech(sourceObject, emoteAction.Message, player, ChatMessageType.Tell)); }); break; case EmoteType.TellFellow: text = emoteAction.Message; if (player != null) { var fellowship = player.Fellowship; if (fellowship == null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Tell)); } else { foreach (var fellow in fellowship.FellowshipMembers) { fellow.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Tell)); } } } break; case EmoteType.TextDirect: if (player != null) { // should these delays be moved to 1 place?? actionChain.AddDelaySeconds(emoteAction.Delay); text = emoteAction.Message; // no known instances of replace tokens in current text, but could be added in future actionChain.AddAction(player, () => { player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Broadcast)); }); } break; case EmoteType.Turn: if (creature != null) { actionChain.AddDelaySeconds(emoteAction.Delay); var pos = new Position(creature.Location.Cell, creature.Location.PositionX, creature.Location.PositionY, creature.Location.PositionZ, emoteAction.AnglesX ?? 0, emoteAction.AnglesY ?? 0, emoteAction.AnglesZ ?? 0, emoteAction.AnglesW ?? 0); actionChain.AddAction(creature, () => { creature.TurnTo(pos); }); var rotateTime = creature.GetRotateDelay(pos); actionChain.AddDelaySeconds(rotateTime); } break; case EmoteType.TurnToTarget: if (creature != null && targetCreature != null) { actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(creature, () => { creature.Rotate(targetCreature); }); var rotateTime = creature.GetRotateDelay(targetCreature); actionChain.AddDelaySeconds(rotateTime); } break; case EmoteType.UntrainSkill: if (player != null) { player.UntrainSkill((Skill)emoteAction.Stat, 1); } break; case EmoteType.UpdateFellowQuest: break; case EmoteType.UpdateMyQuest: break; case EmoteType.UpdateQuest: // only delay seems to be with test NPC here // still, unsafe to use any emotes directly outside of a chain, // as they could be executed out-of-order if (player != null) { var questName = emoteAction.Message; player.QuestManager.Add(questName); var hasQuest = player.QuestManager.HasQuest(questName); InqCategory(hasQuest ? EmoteCategory.QuestSuccess : EmoteCategory.QuestFailure, emoteAction, sourceObject, targetObject, actionChain); } break; case EmoteType.WorldBroadcast: if (player != null) { actionChain.AddDelaySeconds(emoteAction.Delay); actionChain.AddAction(sourceObject, () => { player.Session.Network.EnqueueSend(new GameMessageHearDirectSpeech(sourceObject, emoteAction.Message, player, ChatMessageType.WorldBroadcast)); }); } break; default: log.Debug($"EmoteManager.Execute - Encountered Unhandled EmoteType {(EmoteType)emoteAction.Type} for {sourceObject.Name} ({sourceObject.WeenieClassId})"); break; } }
/// <summary> /// Performs a melee attack for the monster /// </summary> /// <returns>The length in seconds for the attack animation</returns> public float MeleeAttack() { var target = AttackTarget as Creature; var targetPlayer = AttackTarget as Player; var targetPet = AttackTarget as CombatPet; var combatPet = this as CombatPet; if (target == null || !target.IsAlive) { FindNextTarget(); return(0.0f); } if (CurrentMotionState.Stance == MotionStance.NonCombat) { DoAttackStance(); } // select combat maneuver var motionCommand = GetCombatManeuver(); if (motionCommand == null) { return(0.0f); } DoSwingMotion(AttackTarget, motionCommand.Value, out float animLength, out var attackFrames); PhysicsObj.stick_to_object(AttackTarget.PhysicsObj.ID); var numStrikes = attackFrames.Count; var actionChain = new ActionChain(); var prevTime = 0.0f; for (var i = 0; i < numStrikes; i++) { actionChain.AddDelaySeconds(attackFrames[i] * animLength - prevTime); prevTime = attackFrames[i] * animLength; actionChain.AddAction(this, () => { if (AttackTarget == null || IsDead) { return; } if (WeenieType == WeenieType.GamePiece) { target.TakeDamage(this, DamageType.Slash, target.Health.Current); (this as GamePiece).OnDealtDamage(); return; } var weapon = GetEquippedWeapon(); var damageEvent = DamageEvent.CalculateDamage(this, target, weapon, motionCommand); //var damage = CalculateDamage(ref damageType, maneuver, bodyPart, ref critical, ref shieldMod); if (damageEvent.HasDamage) { if (combatPet != null || targetPet != null) { // combat pet inflicting or receiving damage //Console.WriteLine($"{target.Name} taking {Math.Round(damage)} {damageType} damage from {Name}"); target.TakeDamage(this, damageEvent.DamageType, damageEvent.Damage); EmitSplatter(target, damageEvent.Damage); } else if (targetPlayer != null) { // this is a player taking damage targetPlayer.TakeDamage(this, damageEvent); if (damageEvent.ShieldMod != 1.0f) { var shieldSkill = targetPlayer.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(targetPlayer, shieldSkill, shieldSkill.Current); // ? } } } else { target.OnEvade(this, CombatType.Melee); } if (combatPet != null) { combatPet.PetOnAttackMonster(target); } }); } actionChain.EnqueueChain(); // TODO: figure out exact speed / delay formula var meleeDelay = ThreadSafeRandom.Next(MeleeDelayMin, MeleeDelayMax); NextAttackTime = Timers.RunningTime + animLength + meleeDelay; return(animLength); }
/// <summary> /// Method used to perform the animation, sound, and vital update on consumption of food or potions /// </summary> /// <param name="consumableName">Name of the consumable</param> /// <param name="sound">Either Sound.Eat1 or Sound.Drink1</param> /// <param name="buffType">ConsumableBuffType.Spell,ConsumableBuffType.Health,ConsumableBuffType.Stamina,ConsumableBuffType.Mana</param> /// <param name="boostAmount">Amount the Vital is boosted by; can be null, if buffType = ConsumableBuffType.Spell</param> /// <param name="spellDID">Id of the spell cast by the consumable; can be null, if buffType != ConsumableBuffType.Spell</param> public void ApplyComsumable(string consumableName, Sound sound, ConsumableBuffType buffType, uint?boostAmount, uint?spellDID) { GameMessageSystemChat buffMessage; MotionCommand motionCommand; if (sound == Sound.Eat1) { motionCommand = MotionCommand.Eat; } else { motionCommand = MotionCommand.Drink; } var soundEvent = new GameMessageSound(Guid, sound, 1.0f); var motion = new UniversalMotion(MotionStance.Standing, new MotionItem(motionCommand)); DoMotion(motion); if (buffType == ConsumableBuffType.Spell) { // Null check for safety if (spellDID == null) { spellDID = 0; } // TODO: Handle spell cast buffMessage = new GameMessageSystemChat($"Consuming {consumableName} not yet fully implemented.", ChatMessageType.System); } else { CreatureVital creatureVital; string vitalName; // Null check for safety if (boostAmount == null) { boostAmount = 0; } switch (buffType) { case ConsumableBuffType.Health: creatureVital = Health; vitalName = "Health"; break; case ConsumableBuffType.Mana: creatureVital = Mana; vitalName = "Mana"; break; default: creatureVital = Stamina; vitalName = "Stamina"; break; } uint updatedVitalAmount = creatureVital.Current + (uint)boostAmount; if (updatedVitalAmount > creatureVital.MaxValue) { updatedVitalAmount = creatureVital.MaxValue; } boostAmount = updatedVitalAmount - creatureVital.Current; UpdateVital(creatureVital, updatedVitalAmount); buffMessage = new GameMessageSystemChat($"You regain {boostAmount} {vitalName}.", ChatMessageType.Craft); } Session.Network.EnqueueSend(soundEvent, buffMessage); // Wait for animation var motionChain = new ActionChain(); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>((uint)MotionTableId); var motionAnimationLength = motionTable.GetAnimationLength(MotionCommand.Eat); motionChain.AddDelaySeconds(motionAnimationLength); // Return to standing position after the animation delay motionChain.AddAction(this, () => DoMotion(new UniversalMotion(MotionStance.Standing))); motionChain.EnqueueChain(); }
private void FinalizeTrade(Player target) { if (!VerifyTrade_BusyState(target) || !VerifyTrade_Inventory(target)) { return; } IsBusy = true; target.IsBusy = true; TradeTransferInProgress = true; target.TradeTransferInProgress = true; Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, "The items are being traded")); target.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(target.Session, "The items are being traded")); var tradedItems = new Collection <(Biota biota, ReaderWriterLockSlim rwLock)>(); var myEscrow = new List <WorldObject>(); var targetEscrow = new List <WorldObject>(); foreach (ObjectGuid itemGuid in ItemsInTradeWindow) { if (TryRemoveFromInventoryWithNetworking(itemGuid, out var wo, RemoveFromInventoryAction.TradeItem) || TryDequipObjectWithNetworking(itemGuid, out wo, DequipObjectAction.TradeItem)) { targetEscrow.Add(wo); tradedItems.Add((wo.Biota, wo.BiotaDatabaseLock)); } } foreach (ObjectGuid itemGuid in target.ItemsInTradeWindow) { if (target.TryRemoveFromInventoryWithNetworking(itemGuid, out var wo, RemoveFromInventoryAction.TradeItem) || target.TryDequipObjectWithNetworking(itemGuid, out wo, DequipObjectAction.TradeItem)) { myEscrow.Add(wo); tradedItems.Add((wo.Biota, wo.BiotaDatabaseLock)); } } var actionChain = new ActionChain(); actionChain.AddDelaySeconds(0.5f); actionChain.AddAction(CurrentLandblock, () => { foreach (var wo in myEscrow) { TryCreateInInventoryWithNetworking(wo); } foreach (var wo in targetEscrow) { target.TryCreateInInventoryWithNetworking(wo); } Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TradeComplete)); target.Session.Network.EnqueueSend(new GameEventWeenieError(target.Session, WeenieError.TradeComplete)); TradeTransferInProgress = false; target.TradeTransferInProgress = false; IsBusy = false; target.IsBusy = false; DatabaseManager.Shard.SaveBiotasInParallel(tradedItems, null); HandleActionResetTrade(Guid); target.HandleActionResetTrade(target.Guid); }); actionChain.EnqueueChain(); }
/// <summary> /// Launches a missile attack from player to target /// </summary> public void LaunchMissile(WorldObject target) { var weapon = GetEquippedMissileWeapon(); if (weapon == null || CombatMode == CombatMode.NonCombat) { return; } var ammo = weapon.IsAmmoLauncher ? GetEquippedAmmo() : weapon; if (ammo == null) { return; } var creature = target as Creature; if (!IsAlive || MissileTarget == null || !creature.IsAlive) { MissileTarget = null; return; } // launch animation var actionChain = new ActionChain(); var launchTime = EnqueueMotion(actionChain, MotionCommand.AimLevel); // launch projectile actionChain.AddAction(this, () => { var sound = GetLaunchMissileSound(weapon); EnqueueBroadcast(new GameMessageSound(Guid, sound, 1.0f)); // stamina usage // TODO: ensure enough stamina for attack // TODO: verify formulas - double/triple cost for bow/xbow? var staminaCost = GetAttackStamina(GetAccuracyRange()); UpdateVitalDelta(Stamina, -staminaCost); float targetTime = 0.0f; var projectile = LaunchProjectile(ammo, target, out targetTime); UpdateAmmoAfterLaunch(ammo); }); // ammo remaining? if (ammo.StackSize == 1) { actionChain.AddAction(this, () => { SetCombatMode(CombatMode.NonCombat); }); actionChain.EnqueueChain(); return; } // reload animation var reloadTime = EnqueueMotion(actionChain, MotionCommand.Reload); // reset for next projectile EnqueueMotion(actionChain, MotionCommand.Ready); var linkTime = MotionTable.GetAnimationLength(MotionTableId, CurrentMotionState.Stance, MotionCommand.Reload, MotionCommand.Ready); //var cycleTime = MotionTable.GetCycleLength(MotionTableId, CurrentMotionState.Stance, MotionCommand.Ready); actionChain.AddAction(this, () => EnqueueBroadcast(new GameMessageParentEvent(this, ammo, (int)ACE.Entity.Enum.ParentLocation.RightHand, (int)ACE.Entity.Enum.Placement.RightHandCombat))); actionChain.AddDelaySeconds(linkTime); actionChain.AddAction(this, () => { Session.Network.EnqueueSend(new GameEventAttackDone(Session)); if (creature.IsAlive && GetCharacterOption(CharacterOption.AutoRepeatAttacks)) { Session.Network.EnqueueSend(new GameEventCombatCommenceAttack(Session)); Session.Network.EnqueueSend(new GameEventAttackDone(Session)); var nextAttack = new ActionChain(); nextAttack.AddDelaySeconds(AccuracyLevel + 0.1f); // perform next attack nextAttack.AddAction(this, () => { LaunchMissile(target); }); nextAttack.EnqueueChain(); } else { MissileTarget = null; } }); actionChain.EnqueueChain(); }
/// <summary> /// This is raised by Player.HandleActionUseItem.<para /> /// The item does not exist in the players possession.<para /> /// If the item was outside of range, the player will have been commanded to move using DoMoveTo before ActOnUse is called.<para /> /// When this is called, it should be assumed that the player is within range. /// </summary> public override void ActOnUse(WorldObject activator) { if (!(activator is Player player)) { return; } if (AllowedActivator != null) { // do nothing / in use error msg? return; } if (player.IsAdvocate || player.AdvocateQuest || player.AdvocateState) { // Advocates cannot change their PK status if (PkLevelModifier == 1) { return; // maybe send error msg to tell PK to ask another advocate to @remove them (or maybe make the @remove command support self removal) } // letting it fall through for the NpkSwitch because it will not change status and error properly. } //if (player.PkLevelModifier == 0) // wrong check but if PkTimestamp(? maybe different timestamp) + MINIMUM_TIME_SINCE_PK_FLOAT < Time.GetUnixTimestamp proceed else fail //{ if (player.PkLevelModifier != PkLevelModifier) { AllowedActivator = ObjectGuid.Invalid.Full; var useMotion = UseTargetSuccessAnimation != MotionCommand.Invalid ? UseTargetSuccessAnimation : MotionCommand.Twitch1; EnqueueBroadcastMotion(new Motion(this, useMotion)); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId); var useTime = motionTable.GetAnimationLength(useMotion); player.LastUseTime += useTime; var actionChain = new ActionChain(); actionChain.AddDelaySeconds(useTime); actionChain.AddAction(player, () => { player.Session.Network.EnqueueSend(new GameMessageSystemChat(GetProperty(PropertyString.UseMessage), ChatMessageType.Broadcast)); player.PkLevelModifier = PkLevelModifier; if (player.PkLevelModifier == 1) { player.PlayerKillerStatus = PlayerKillerStatus.PK; } else { player.PlayerKillerStatus = PlayerKillerStatus.NPK; } player.EnqueueBroadcast(new GameMessagePublicUpdatePropertyInt(player, PropertyInt.PlayerKillerStatus, (int)player.PlayerKillerStatus)); Reset(); }); actionChain.EnqueueChain(); } else { if (UseTargetFailureAnimation != MotionCommand.Invalid) { EnqueueBroadcastMotion(new Motion(this, UseTargetFailureAnimation)); } player.Session.Network.EnqueueSend(new GameMessageSystemChat(GetProperty(PropertyString.ActivationFailure), ChatMessageType.Broadcast)); } }
public override void OnUse(Session session) { bool success = true; string failReason = "You are unable to read the scroll."; switch (Power) { // research: http://asheron.wikia.com/wiki/Announcements_-_2002/06_-_Castling case spellLevel2: // Level 2 case spellLevel3: // Level 3 case spellLevel4: // Level 4 case spellLevel5: // Level 5 case spellLevel6: // Level 6 if (session.Player.CanReadScroll(School, Power)) { success = true; } else { success = false; failReason = "You are not skilled enough in the inscribed spell's school of magic to understand the writing on this scroll."; } break; default: // Level 1 or Level 7+ never fail success = true; break; } if (!session.Player.UnknownSpell(SpellId)) { success = false; failReason = "You already know the spell inscribed upon this scroll."; } ActionChain readScrollChain = new ActionChain(); readScrollChain.AddAction(session.Player, () => session.Player.HandleActionMotion(motionReading)); readScrollChain.AddDelaySeconds(2); if (success) { readScrollChain.AddAction(session.Player, () => session.Player.LearnSpell(SpellId)); readScrollChain.AddAction(session.Player, () => session.Player.HandleActionMotion(motionReady)); var removeObjMessage = new GameMessageRemoveObject(this); var destroyMessage = new GameMessageSystemChat("The scroll is destroyed.", ChatMessageType.Magic); readScrollChain.AddAction(session.Player, () => session.Network.EnqueueSend(destroyMessage, removeObjMessage)); readScrollChain.AddAction(session.Player, () => session.Player.RemoveWorldObjectFromInventory(Guid)); } else { readScrollChain.AddDelaySeconds(2); readScrollChain.AddAction(session.Player, () => session.Player.HandleActionMotion(motionReady)); var failMessage = new GameMessageSystemChat($"{failReason}", ChatMessageType.Magic); readScrollChain.AddAction(session.Player, () => session.Network.EnqueueSend(failMessage)); } var sendUseDoneEvent = new GameEventUseDone(session.Player.Session); readScrollChain.AddAction(session.Player, () => session.Network.EnqueueSend(sendUseDoneEvent)); readScrollChain.EnqueueChain(); }
public void HandleActionRecallAllegianceHometown() { //Console.WriteLine($"{Name}.HandleActionRecallAllegianceHometown()"); if (PKTimerActive) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently)); return; } if (RecallsDisabled) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand)); return; } // check if player is in an allegiance if (Allegiance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance)); return; } if (Allegiance.Sanctuary == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourAllegianceDoesNotHaveHometown)); return; } if (CombatMode != CombatMode.NonCombat) { // this should be handled by a different thing, probably a function that forces player into peacemode var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat); SetCombatMode(CombatMode.NonCombat); Session.Network.EnqueueSend(updateCombatMode); } EnqueueBroadcast(new GameMessageSystemChat($"{Name} is going to the Allegiance hometown.", ChatMessageType.Recall), 96.0f); EnqueueBroadcastMotion(motionAllegianceHometownRecall); var startPos = new Position(Location); // Wait for animation var actionChain = new ActionChain(); // Then do teleport var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.AllegianceHometownRecall); actionChain.AddDelaySeconds(animLength); actionChain.AddAction(this, () => { var endPos = new Position(Location); if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar)); return; } if (Allegiance != null) { Teleport(Allegiance.Sanctuary); } }); actionChain.EnqueueChain(); }
public void CreateMoveToChain(WorldObject target, Action <bool> callback, float?useRadius = null, bool rotate = true) { if (FastTick) { CreateMoveToChain2(target, callback, useRadius, rotate); return; } var thisMoveToChainNumber = GetNextMoveToChainNumber(); if (target.Location == null) { StopExistingMoveToChains(); log.Error($"{Name}.CreateMoveToChain({target.Name}): target.Location is null"); callback(false); return; } // fix bug in magic combat mode after walking to target, // crouch animation steps out of range if (useRadius == null) { useRadius = target.UseRadius ?? 0.6f; } if (CombatMode == CombatMode.Magic) { useRadius = Math.Max(0.0f, useRadius.Value - 0.2f); } // already within use distance? var withinUseRadius = CurrentLandblock.WithinUseRadius(this, target.Guid, out var targetValid, useRadius); if (withinUseRadius) { if (rotate) { // send TurnTo motion var rotateTime = Rotate(target); var actionChain = new ActionChain(); actionChain.AddDelaySeconds(rotateTime); actionChain.AddAction(this, () => { lastCompletedMove = thisMoveToChainNumber; callback(true); }); actionChain.EnqueueChain(); } else { lastCompletedMove = thisMoveToChainNumber; callback(true); } return; } if (target.WeenieType == WeenieType.Portal) { MoveToPosition(target.Location); } else { MoveToObject(target, useRadius); } moveToChainStartTime = DateTime.UtcNow; MoveToChain(target, thisMoveToChainNumber, callback, useRadius); }
public void PlayerEnterWorld() { PlayerManager.SwitchPlayerFromOfflineToOnline(this); Teleporting = true; // Save the the LoginTimestamp var lastLoginTimestamp = Time.GetUnixTime(); LoginTimestamp = lastLoginTimestamp; LastTeleportStartTimestamp = lastLoginTimestamp; Character.LastLoginTimestamp = lastLoginTimestamp; Character.TotalLogins++; CharacterChangesDetected = true; Sequences.SetSequence(SequenceType.ObjectInstance, new UShortSequence((ushort)Character.TotalLogins)); if (BarberActive) { BarberActive = false; } if (AllegianceNode != null) { AllegianceRank = (int)AllegianceNode.Rank; } else { AllegianceRank = null; } if (!Account15Days) { var accountTimeSpan = DateTime.UtcNow - Account.CreateTime; if (accountTimeSpan.TotalDays >= 15) { Account15Days = true; } } // SendSelf will trigger the entrance into portal space SendSelf(); // Update or override certain properties sent to client. SendPropertyUpdatesAndOverrides(); // Init the client with the chat channel ID's, and then notify the player that they've choined the associated channels. UpdateChatChannels(); var general = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveEnteredThe_Channel, "General"); var trade = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveEnteredThe_Channel, "Trade"); var lfg = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveEnteredThe_Channel, "LFG"); var roleplay = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveEnteredThe_Channel, "Roleplay"); Session.Network.EnqueueSend(general, trade, lfg, roleplay); // check if vassals earned XP while offline HandleAllegianceOnLogin(); HandleHouseOnLogin(); // retail appeared to send the squelch list very early, // even before the CreatePlayer, but doing it here if (SquelchManager.HasSquelches) { SquelchManager.SendSquelchDB(); } AuditItemSpells(); HandleMissingXp(); HandleSkillCreditRefund(); if (PlayerKillerStatus == PlayerKillerStatus.PKLite && !PropertyManager.GetBool("pkl_server").Item) { var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); actionChain.AddAction(this, () => { UpdateProperty(this, PropertyInt.PlayerKillerStatus, (int)PlayerKillerStatus.NPK, true); Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNonPKAgain)); }); actionChain.EnqueueChain(); } HandleDBUpdates(); }
public static void HandleHouseSelect(Session session, bool confirmed, params string[] parameters) { if (!int.TryParse(parameters[0], out var houseIdx)) { return; } // ensure current multihouse owner if (!session.Player.IsMultiHouseOwner(false)) { log.Warn($"{session.Player.Name} tried to /house-select {houseIdx}, but they are not currently a multi-house owner!"); return; } // get house info for this index var multihouses = session.Player.GetMultiHouses(); if (houseIdx < 1 || houseIdx > multihouses.Count) { session.Network.EnqueueSend(new GameMessageSystemChat($"Please enter a number between 1 and {multihouses.Count}.", ChatMessageType.Broadcast)); return; } var keepHouse = multihouses[houseIdx - 1]; // show confirmation popup if (!confirmed) { var houseType = $"{keepHouse.HouseType}".ToLower();; var loc = HouseManager.GetCoords(keepHouse.SlumLord.Location); var msg = $"Are you sure you want to keep the {houseType} at\n{loc}?"; session.Player.ConfirmationManager.EnqueueSend(new Confirmation_Custom(session.Player.Guid, () => HandleHouseSelect(session, true, parameters)), msg); return; } // house to keep confirmed, abandon the other houses var abandonHouses = new List <House>(multihouses); abandonHouses.RemoveAt(houseIdx - 1); foreach (var abandonHouse in abandonHouses) { var house = session.Player.GetHouse(abandonHouse.Guid.Full); HouseManager.HandleEviction(house, house.HouseOwner ?? 0, true); } // set player properties for house to keep var player = PlayerManager.FindByGuid(keepHouse.HouseOwner ?? 0, out bool isOnline); if (player == null) { log.Error($"{session.Player.Name}.HandleHouseSelect({houseIdx}) - couldn't find HouseOwner {keepHouse.HouseOwner} for {keepHouse.Name} ({keepHouse.Guid})"); return; } player.HouseId = keepHouse.HouseId; player.HouseInstance = keepHouse.Guid.Full; player.SaveBiotaToDatabase(); // update house panel for current player var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); // wait for slumlord inventory biotas above to save actionChain.AddAction(session.Player, session.Player.HandleActionQueryHouse); actionChain.EnqueueChain(); Console.WriteLine("OK"); }
public static void UseObjectOnTarget(Player player, WorldObject source, WorldObject target) { var recipe = DatabaseManager.World.GetCachedCookbook(source.WeenieClassId, target.WeenieClassId); if (recipe == null) { var message = new GameMessageSystemChat($"The {source.Name} cannot be used on the {target.Name}.", ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(); return; } ActionChain craftChain = new ActionChain(); CreatureSkill skill = null; bool skillSuccess = true; // assume success, unless there's a skill check double percentSuccess = 1; UniversalMotion motion = new UniversalMotion(MotionStance.Standing, new MotionItem(MotionCommand.ClapHands)); craftChain.AddAction(player, () => player.HandleActionMotion(motion)); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(player.MotionTableId); var craftAnimationLength = motionTable.GetAnimationLength(MotionCommand.ClapHands); craftChain.AddDelaySeconds(craftAnimationLength); craftChain.AddAction(player, () => { if (recipe.Recipe.Skill > 0 && recipe.Recipe.Difficulty > 0) { // there's a skill associated with this Skill skillId = (Skill)recipe.Recipe.Skill; // this shouldn't happen, but sanity check for unexpected nulls skill = player.GetCreatureSkill(skillId); if (skill == null) { log.Warn("Unexpectedly missing skill in Recipe usage"); player.SendUseDoneEvent(); return; } percentSuccess = skill.GetPercentSuccess(recipe.Recipe.Difficulty); //FIXME: Pretty certain this is broken } if (skill != null) { if (skill.AdvancementClass == SkillAdvancementClass.Untrained) { var message = new GameEventWeenieError(player.Session, WeenieError.YouAreNotTrainedInThatTradeSkill); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(WeenieError.YouAreNotTrainedInThatTradeSkill); return; } } // straight skill check, if applicable if (skill != null) { skillSuccess = _random.NextDouble() < percentSuccess; } if (skillSuccess) { bool destroyTarget = _random.NextDouble() < recipe.Recipe.SuccessDestroyTargetChance; bool destroySource = _random.NextDouble() < recipe.Recipe.SuccessDestroySourceChance; if (destroyTarget) { if (target.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworking(target, (ushort)recipe.Recipe.SuccessDestroyTargetAmount); } else if (target.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(target)) { throw new Exception($"Failed to remove {target.Name} from player inventory."); } } else { target.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.SuccessDestroyTargetMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.SuccessDestroyTargetMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (destroySource) { if (source.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworking(source, (ushort)recipe.Recipe.SuccessDestroySourceAmount); } else if (source.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(source)) { throw new Exception($"Failed to remove {source.Name} from player inventory."); } } else { source.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.SuccessDestroySourceMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.SuccessDestroySourceMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (recipe.Recipe.SuccessWCID > 0) { var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.SuccessWCID); if (wo != null) { if (recipe.Recipe.SuccessAmount > 1) { wo.StackSize = (ushort)recipe.Recipe.SuccessAmount; } player.TryCreateInInventoryWithNetworking(wo); } } var message = new GameMessageSystemChat(recipe.Recipe.SuccessMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } else { bool destroyTarget = _random.NextDouble() < recipe.Recipe.FailDestroyTargetChance; bool destroySource = _random.NextDouble() < recipe.Recipe.FailDestroySourceChance; if (destroyTarget) { if (target.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworking(target, (ushort)recipe.Recipe.FailDestroyTargetAmount); } else if (target.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(target)) { throw new Exception($"Failed to remove {target.Name} from player inventory."); } } else { target.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.FailDestroyTargetMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.FailDestroyTargetMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (destroySource) { if (source.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworking(source, (ushort)recipe.Recipe.FailDestroySourceAmount); } else if (source.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(source)) { throw new Exception($"Failed to remove {source.Name} from player inventory."); } } else { source.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.FailDestroySourceMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.FailDestroySourceMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (recipe.Recipe.FailWCID > 0) { var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.FailWCID); if (wo != null) { if (recipe.Recipe.FailAmount > 1) { wo.StackSize = (ushort)recipe.Recipe.FailAmount; } player.TryCreateInInventoryWithNetworking(wo); } } var message = new GameMessageSystemChat(recipe.Recipe.FailMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } player.SendUseDoneEvent(); }); craftChain.EnqueueChain(); }
public void Decay(TimeSpan elapsed) { // http://asheron.wikia.com/wiki/Item_Decay if (decayCompleted) { return; } var previousTTR = TimeToRot; if (!TimeToRot.HasValue) { TimeToRot = DefaultTimeToRot.TotalSeconds; if (this is Corpse && Level.HasValue) { log.Info($"{Name} (0x{Guid.ToString()}).Decay: TimeToRot had no value, set to {TimeToRot}"); } return; } var corpse = this as Corpse; if (corpse != null) { if (!corpse.InventoryLoaded) { return; } if (corpse.Inventory.Count == 0 && TimeToRot.Value > Corpse.EmptyDecayTime) { TimeToRot = Corpse.EmptyDecayTime; if (Level.HasValue && PropertyManager.GetBool("corpse_decay_tick_logging").Item) { log.Info($"{corpse.Name} (0x{corpse.Guid.ToString()}).Decay({elapsed.ToString()}): InventoryLoaded = {corpse.InventoryLoaded} | Inventory.Count = {corpse.Inventory.Count} | previous TimeToRot: {previousTTR} | current TimeToRot: {TimeToRot}"); } return; } } if (TimeToRot > 0) { TimeToRot -= elapsed.TotalSeconds; if (this is Corpse && Level.HasValue && PropertyManager.GetBool("corpse_decay_tick_logging").Item) { log.Info($"{corpse.Name} (0x{corpse.Guid.ToString()}).Decay({elapsed.ToString()}): previous TimeToRot: {previousTTR} | current TimeToRot: {TimeToRot}"); } // Is there still time left? if (TimeToRot > 0) { return; } TimeToRot = -2; // We force it to -2 to make sure it doesn't end up at 0 or -1. 0 indicates instant rot. -1 indicates no rot. 0 and -1 can be found in weenie defaults if (this is Corpse && Level.HasValue && PropertyManager.GetBool("corpse_decay_tick_logging").Item) { log.Info($"{corpse.Name} (0x{corpse.Guid.ToString()}).Decay({elapsed.ToString()}): previous TimeToRot: {previousTTR} | current TimeToRot: {TimeToRot}"); } } if (this is Container container && container.IsOpen) { // If you wanted to add a grace period to the container to give Player B more time to open it after Player A closes it, it would go here. return; } // Time to rot has elapsed, time to disappear... decayCompleted = true; // If this is a player corpse, puke out the corpses contents onto the landblock if (corpse != null && !corpse.IsMonster) { var inventoryGUIDs = corpse.Inventory.Keys.ToList(); var pukedItems = ""; foreach (var guid in inventoryGUIDs) { if (corpse.TryRemoveFromInventory(guid, out var item)) { item.Location = new Position(corpse.Location); item.Placement = ACE.Entity.Enum.Placement.Resting; // This is needed to make items lay flat on the ground. CurrentLandblock.AddWorldObject(item); item.SaveBiotaToDatabase(); pukedItems += $"{item.Name} (0x{item.Guid.Full.ToString("X8")}), "; } } if (pukedItems.EndsWith(", ")) { pukedItems = pukedItems.Substring(0, pukedItems.Length - 2); } log.Info($"{corpse.Name} (0x{corpse.Guid.ToString()}) at {corpse.Location.ToLOCString()} has decayed{((pukedItems == "") ? "" : $" and placed the following items on the landblock: {pukedItems}")}."); } if (corpse != null) { EnqueueBroadcast(new GameMessageScript(Guid, ACE.Entity.Enum.PlayScript.Destroy)); var actionChain = new ActionChain(); actionChain.AddDelaySeconds(1.0f); actionChain.AddAction(this, () => Destroy()); actionChain.EnqueueChain(); } else { Destroy(); } }
public void LogOut_Inner(bool clientSessionTerminatedAbruptly = false) { IsLoggingOut = true; if (Fellowship != null) { FellowshipQuit(false); } if (IsTrading && TradePartner != null) { var tradePartner = PlayerManager.GetOnlinePlayer(TradePartner); if (tradePartner != null) { tradePartner.HandleActionCloseTradeNegotiations(); } } if (!clientSessionTerminatedAbruptly) { if (PropertyManager.GetBool("use_turbine_chat").Item) { if (GetCharacterOption(CharacterOption.ListenToGeneralChat)) { LeaveTurbineChatChannel("General"); } if (GetCharacterOption(CharacterOption.ListenToTradeChat)) { LeaveTurbineChatChannel("Trade"); } if (GetCharacterOption(CharacterOption.ListenToLFGChat)) { LeaveTurbineChatChannel("LFG"); } if (GetCharacterOption(CharacterOption.ListenToRoleplayChat)) { LeaveTurbineChatChannel("Roleplay"); } if (GetCharacterOption(CharacterOption.ListenToAllegianceChat) && Allegiance != null) { LeaveTurbineChatChannel("Allegiance"); } if (GetCharacterOption(CharacterOption.ListenToSocietyChat) && Society != FactionBits.None) { LeaveTurbineChatChannel("Society"); } } } if (CurrentActivePet != null) { CurrentActivePet.Destroy(); } if (CurrentLandblock != null) { var logout = new Motion(MotionStance.NonCombat, MotionCommand.LogOut); EnqueueBroadcastMotion(logout); EnqueueBroadcastPhysicsState(); var logoutChain = new ActionChain(); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>((uint)MotionTableId); float logoutAnimationLength = motionTable.GetAnimationLength(MotionCommand.LogOut); logoutChain.AddDelaySeconds(logoutAnimationLength); // remove the player from landblock management -- after the animation has run logoutChain.AddAction(this, () => { if (CurrentLandblock == null) { log.Debug($"0x{Guid}:{Name}.LogOut_Inner.logoutChain: CurrentLandblock is null, unable to remove from a landblock..."); if (Location != null) { log.Debug($"0x{Guid}:{Name}.LogOut_Inner.logoutChain: Location is not null, Location = {Location.ToLOCString()}"); } } CurrentLandblock?.RemoveWorldObject(Guid, false); SetPropertiesAtLogOut(); SavePlayerToDatabase(); PlayerManager.SwitchPlayerFromOnlineToOffline(this); }); // close any open landblock containers (chests / corpses) if (LastOpenedContainerId != ObjectGuid.Invalid) { var container = CurrentLandblock.GetObject(LastOpenedContainerId) as Container; if (container != null) { container.Close(this); } } logoutChain.EnqueueChain(); } else { log.Debug($"0x{Guid}:{Name}.LogOut_Inner: CurrentLandblock is null"); if (Location != null) { log.Debug($"0x{Guid}:{Name}.LogOut_Inner: Location is not null, Location = {Location.ToLOCString()}"); var validLoadedLandblock = LandblockManager.GetLandblock(Location.LongObjCellID, false); if (validLoadedLandblock.GetObject(Guid.Full) != null) { log.Debug($"0x{Guid}:{Name}.LogOut_Inner: Player is still on landblock, removing..."); validLoadedLandblock.RemoveWorldObject(Guid, false); } else { log.Debug($"0x{Guid}:{Name}.LogOut_Inner: Player is not found on the landblock Location references."); } } else { log.Debug($"0x{Guid}:{Name}.LogOut_Inner: Location is null"); } SetPropertiesAtLogOut(); SavePlayerToDatabase(); PlayerManager.SwitchPlayerFromOnlineToOffline(this); } }
public void SwitchToMeleeAttack() { if (IsDead) { return; } var weapon = GetEquippedMissileWeapon(); var ammo = GetEquippedAmmo(); if (weapon == null && ammo == null) { return; } var actionChain = new ActionChain(); EnqueueMotion_Force(actionChain, MotionStance.NonCombat, MotionCommand.Ready, (MotionCommand)CurrentMotionState.Stance); EnqueueMotion_Force(actionChain, MotionStance.HandCombat, MotionCommand.Ready, MotionCommand.NonCombat); actionChain.AddAction(this, () => { if (IsDead) { return; } // actually destroys the missile weapon + ammo here, // to ensure they can't be re-selected from inventory if (weapon != null) { TryUnwieldObjectWithBroadcasting(weapon.Guid, out _, out _); weapon.Destroy(); } if (ammo != null) { TryUnwieldObjectWithBroadcasting(ammo.Guid, out _, out _); ammo.Destroy(); } EquipInventoryItems(true); var innerChain = new ActionChain(); EnqueueMotion_Force(innerChain, MotionStance.NonCombat, MotionCommand.Ready, (MotionCommand)CurrentMotionState.Stance); innerChain.AddAction(this, () => { if (IsDead) { return; } //DoAttackStance(); // inlined DoAttackStance() / slightly modified -- do not rely on SetCombatMode() for stance swapping time in 1 action, // as it doesn't support that anymore var newStanceTime = SetCombatMode(CombatMode.Melee); NextMoveTime = NextAttackTime = Timers.RunningTime + newStanceTime; PrevAttackTime = NextMoveTime - (AiUseMagicDelay ?? 3.0f); PhysicsObj.StartTimer(); // end inline ResetAttack(); SwitchWeaponsPending = false; // this is an unfortunate hack to fix the following scenario: // since this function can be called at any point in time now, // including when LaunchMissile -> EnqueueMotion is in the middle of an action queue, // CurrentMotionState.Stance can get reset to the previous combat stance if that happens var newStance = CurrentMotionState.Stance; var swapChain = new ActionChain(); swapChain.AddDelaySeconds(2.0f); swapChain.AddAction(this, () => CurrentMotionState.Stance = newStance); swapChain.EnqueueChain(); }); innerChain.EnqueueChain(); }); actionChain.EnqueueChain(); }
/// <summary> /// Called every ~5 secs for equipped mana consuming items /// </summary> public void ManaConsumersTick() { if (!EquippedObjectsLoaded) { return; } var EquippedManaConsumers = EquippedObjects.Where(k => (k.Value.IsAffecting ?? false) && //k.Value.ManaRate.HasValue && k.Value.ItemMaxMana.HasValue && k.Value.ItemCurMana.HasValue && k.Value.ItemCurMana.Value > 0).ToList(); foreach (var k in EquippedManaConsumers) { var item = k.Value; // this was a bug in lootgen until 7/11/2019, mostly for clothing/armor/shields // tons of existing items on servers are in this bugged state, where they aren't ticking mana. // this retroactively fixes them when equipped // items such as Impious Staff are excluded from this via IsAffecting if (item.ManaRate == null) { item.ManaRate = LootGenerationFactory.GetManaRate(item); log.Warn($"{Name}.ManaConsumersTick(): {k.Value.Name} ({k.Value.Guid}) fixed missing ManaRate"); } var rate = item.ManaRate.Value; if (LumAugItemManaUsage != 0) { rate *= GetNegativeRatingMod(LumAugItemManaUsage * 5); } if (!item.ItemManaConsumptionTimestamp.HasValue) { item.ItemManaConsumptionTimestamp = DateTime.UtcNow; } DateTime mostRecentBurn = item.ItemManaConsumptionTimestamp.Value; var timePerBurn = -1 / rate; var secondsSinceLastBurn = (DateTime.UtcNow - mostRecentBurn).TotalSeconds; var delta = secondsSinceLastBurn / timePerBurn; var deltaChopped = (int)Math.Floor(delta); var deltaExtra = delta - deltaChopped; if (deltaChopped <= 0) { continue; } var timeToAdd = (int)Math.Floor(deltaChopped * timePerBurn); item.ItemManaConsumptionTimestamp = mostRecentBurn + new TimeSpan(0, 0, timeToAdd); var manaToBurn = Math.Min(item.ItemCurMana.Value, deltaChopped); deltaChopped = Math.Clamp(deltaChopped, 0, 10); item.ItemCurMana -= deltaChopped; if (item.ItemCurMana < 1 || item.ItemCurMana == null) { item.IsAffecting = false; var msg = new GameMessageSystemChat($"Your {item.Name} is out of Mana.", ChatMessageType.Magic); var sound = new GameMessageSound(Guid, Sound.ItemManaDepleted); Session.Network.EnqueueSend(msg, sound); if (item.WielderId != null) { if (item.Biota.BiotaPropertiesSpellBook != null) { // unsure if these messages / sounds were ever sent in retail, // or if it just purged the enchantments invisibly // doing a delay here to prevent 'SpellExpired' sounds from overlapping with 'ItemManaDepleted' var actionChain = new ActionChain(); actionChain.AddDelaySeconds(2.0f); actionChain.AddAction(this, () => { for (int i = 0; i < item.Biota.BiotaPropertiesSpellBook.Count; i++) { RemoveItemSpell(item, (uint)item.Biota.BiotaPropertiesSpellBook.ElementAt(i).Spell); } }); actionChain.EnqueueChain(); } } } else { // get time until empty var secondsUntilEmpty = ((item.ItemCurMana - deltaExtra) * timePerBurn); if (secondsUntilEmpty <= 120 && (!item.ItemManaDepletionMessageTimestamp.HasValue || (DateTime.UtcNow - item.ItemManaDepletionMessageTimestamp.Value).TotalSeconds > 120)) { item.ItemManaDepletionMessageTimestamp = DateTime.UtcNow; Session.Network.EnqueueSend(new GameMessageSystemChat($"Your {item.Name} is low on Mana.", ChatMessageType.Magic)); } } } }
/// <summary> /// Vendor has validated the transactions and sent a list of items for processing. /// </summary> public void FinalizeBuyTransaction(Vendor vendor, List <WorldObject> uqlist, List <WorldObject> genlist, uint goldcost, uint altcost) { // vendor accepted the transaction var valid = ValidateBuyTransaction(vendor, goldcost, altcost); if (valid) { if (altcost > 0) { var altCurrencyWCID = vendor.AlternateCurrency ?? 0; SpendCurrency(altCurrencyWCID, altcost, true); } else { SpendCurrency(Vendor.CoinStackWCID, goldcost, true); } foreach (WorldObject wo in uqlist) { wo.RemoveProperty(PropertyFloat.SoldTimestamp); TryCreateInInventoryWithNetworking(wo); } foreach (var gen in genlist) { var service = gen.GetProperty(PropertyBool.VendorService) ?? false; if (!service) { TryCreateInInventoryWithNetworking(gen); } else { var spell = new Spell(gen.SpellDID ?? 0); if (!spell.NotFound) { var preCastTime = vendor.PreCastMotion(this); vendor.IsBusy = true; var castChain = new ActionChain(); castChain.AddDelaySeconds(preCastTime); castChain.AddAction(vendor, () => { vendor.TryCastSpell(spell, this, vendor); vendor.PostCastMotion(); }); var postCastTime = vendor.GetPostCastTime(); castChain.AddDelaySeconds(postCastTime); castChain.AddAction(vendor, () => vendor.IsBusy = false); castChain.EnqueueChain(); } else if (gen.GetProperty(PropertyInt.HomeRealm).HasValue) { var realmId = gen.GetProperty(PropertyInt.HomeRealm).Value; RealmManager.SetHomeRealm(this, realmId); } } } Session.Network.EnqueueSend(new GameMessageSound(Guid, Sound.PickUpItem)); if (PropertyManager.GetBool("player_receive_immediate_save").Item) { RushNextPlayerSave(5); } } vendor.BuyItems_FinalTransaction(this, uqlist, valid, altcost); }
/// <summary> /// Broadcasts the player death animation, updates vitae, and sends network messages for player death /// Queues the action to call TeleportOnDeath and enter portal space soon /// </summary> protected override void Die(DamageHistoryInfo lastDamager, DamageHistoryInfo topDamager) { if (topDamager?.Guid == Guid && IsPKType) { var topDamagerOther = DamageHistory.GetTopDamager(false); if (topDamagerOther != null && topDamagerOther.IsPlayer) { topDamager = topDamagerOther; } } UpdateVital(Health, 0); NumDeaths++; suicideInProgress = false; // TODO: instead of setting IsBusy here, // eventually all of the places that check for states such as IsBusy || Teleporting // might want to use a common function, and IsDead should return a separate error IsBusy = true; // killer = top damager for looting rights if (topDamager != null) { KillerId = topDamager.Guid.Full; } // broadcast death animation var deathAnim = new Motion(MotionStance.NonCombat, MotionCommand.Dead); EnqueueBroadcastMotion(deathAnim); // create network messages for player death var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0); // TODO: death sounds? seems to play automatically in client // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f); var msgNumDeaths = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths); // send network messages for player death Session.Network.EnqueueSend(msgHealthUpdate, msgNumDeaths); if (lastDamager?.Guid == Guid) // suicide { var msgSelfInflictedDeath = new GameEventWeenieError(Session, WeenieError.YouKilledYourself); Session.Network.EnqueueSend(msgSelfInflictedDeath); } // update vitae // players who died in a PKLite fight do not accrue vitae if (!IsPKLiteDeath(topDamager)) { InflictVitaePenalty(); } if (IsPKDeath(topDamager) || AugmentationSpellsRemainPastDeath == 0) { var msgPurgeEnchantments = new GameEventMagicPurgeEnchantments(Session); EnchantmentManager.RemoveAllEnchantments(); Session.Network.EnqueueSend(msgPurgeEnchantments); } // wait for the death animation to finish var dieChain = new ActionChain(); var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.Dead); dieChain.AddDelaySeconds(animLength + 1.0f); dieChain.AddAction(this, () => { CreateCorpse(topDamager); ThreadSafeTeleportOnDeath(); // enter portal space SetLifestoneProtection(); if (IsPKDeath(topDamager) || IsPKLiteDeath(topDamager)) { SetMinimumTimeSincePK(); } IsBusy = false; }); dieChain.EnqueueChain(); }
/// <summary> /// Broadcasts the player death animation, updates vitae, and sends network messages for player death /// Queues the action to call TeleportOnDeath and enter portal space soon /// </summary> protected override void Die(WorldObject lastDamager, WorldObject topDamager) { UpdateVital(Health, 0); NumDeaths++; suicideInProgress = false; // killer = top damager for looting rights if (topDamager != null) { KillerId = topDamager.Guid.Full; } // broadcast death animation var deathAnim = new Motion(MotionStance.NonCombat, MotionCommand.Dead); EnqueueBroadcastMotion(deathAnim); // killer death message = last damager var killerMsg = lastDamager != null ? " to " + lastDamager.Name : ""; var currentDeathMessage = $"died{killerMsg}."; // create network messages for player death var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0); // TODO: death sounds? seems to play automatically in client // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f); var msgNumDeaths = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths); // send network messages for player death Session.Network.EnqueueSend(msgHealthUpdate, msgNumDeaths); if (lastDamager.Guid == Guid) // suicide { var msgSelfInflictedDeath = new GameEventWeenieError(Session, WeenieError.YouKilledYourself); Session.Network.EnqueueSend(msgSelfInflictedDeath); } // update vitae // players who died in a PKLite fight do not accrue vitae if (!IsPKLiteDeath(topDamager)) { InflictVitaePenalty(); } if (IsPKDeath(topDamager) || AugmentationSpellsRemainPastDeath == 0) { var msgPurgeEnchantments = new GameEventMagicPurgeEnchantments(Session); EnchantmentManager.RemoveAllEnchantments(); Session.Network.EnqueueSend(msgPurgeEnchantments); } // wait for the death animation to finish var dieChain = new ActionChain(); var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.Dead); dieChain.AddDelaySeconds(animLength + 1.0f); dieChain.AddAction(this, () => { CreateCorpse(topDamager); TeleportOnDeath(); // enter portal space SetLifestoneProtection(); SetMinimumTimeSincePK(); }); dieChain.EnqueueChain(); }
/// <summary> /// Recalls you to your allegiance's Mansion or Villa /// </summary> public void HandleActionTeleToMansion() { //Console.WriteLine($"{Name}.HandleActionTeleToMansion()"); if (RecallsDisabled) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand)); return; } // check if player is in an allegiance if (Allegiance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance)); return; } var allegianceHouse = Allegiance.GetHouse(); if (allegianceHouse == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchDoesNotOwnAMansionOrVilla)); return; } if (allegianceHouse.HouseType < ACE.Entity.Enum.HouseType.Villa) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchsHouseIsNotAMansionOrVilla)); return; } // ensure allegiance housing has allegiance permissions enabled if (allegianceHouse.MonarchId == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchHasClosedTheMansion)); return; } if (CombatMode != CombatMode.NonCombat) { // this should be handled by a different thing, probably a function that forces player into peacemode var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat); SetCombatMode(CombatMode.NonCombat); Session.Network.EnqueueSend(updateCombatMode); } EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the Allegiance housing.", ChatMessageType.Recall), 96.0f); EnqueueBroadcastMotion(motionHouseRecall); var startPos = new Position(Location); // Wait for animation var actionChain = new ActionChain(); // Then do teleport var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.HouseRecall); actionChain.AddDelaySeconds(animLength); actionChain.AddAction(this, () => { var endPos = new Position(Location); if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar)); return; } Teleport(allegianceHouse.SlumLord.Location); }); actionChain.EnqueueChain(); }
public void LogOut_Inner(bool clientSessionTerminatedAbruptly = false) { if (Fellowship != null) { FellowshipQuit(false); } if (IsTrading && TradePartner != null) { var tradePartner = PlayerManager.GetOnlinePlayer(TradePartner); if (tradePartner != null) { tradePartner.HandleActionCloseTradeNegotiations(); } } if (!clientSessionTerminatedAbruptly) { // Thie retail server sends a ChatRoomTracker 0x0295 first, then the status message, 0x028B. It does them one at a time for each individual channel. // The ChatRoomTracker message doesn't seem to change at all. // For the purpose of ACE, we simplify this process. var general = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveLeftThe_Channel, "General"); var trade = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveLeftThe_Channel, "Trade"); var lfg = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveLeftThe_Channel, "LFG"); var roleplay = new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouHaveLeftThe_Channel, "Roleplay"); Session.Network.EnqueueSend(general, trade, lfg, roleplay); } if (CurrentActiveCombatPet != null) { CurrentActiveCombatPet.Destroy(); } if (CurrentLandblock != null) { var logout = new Motion(MotionStance.NonCombat, MotionCommand.LogOut); EnqueueBroadcastMotion(logout); EnqueueBroadcastPhysicsState(); var logoutChain = new ActionChain(); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>((uint)MotionTableId); float logoutAnimationLength = motionTable.GetAnimationLength(MotionCommand.LogOut); logoutChain.AddDelaySeconds(logoutAnimationLength); // remove the player from landblock management -- after the animation has run logoutChain.AddAction(this, () => { CurrentLandblock?.RemoveWorldObject(Guid, false); SetPropertiesAtLogOut(); SavePlayerToDatabase(); PlayerManager.SwitchPlayerFromOnlineToOffline(this); }); // close any open landblock containers (chests / corpses) if (LastOpenedContainerId != ObjectGuid.Invalid) { var container = CurrentLandblock.GetObject(LastOpenedContainerId) as Container; if (container != null) { container.Close(this); } } logoutChain.EnqueueChain(); } else { SetPropertiesAtLogOut(); SavePlayerToDatabase(); PlayerManager.SwitchPlayerFromOnlineToOffline(this); } }
/// <summary> /// Called on player login /// If a player has any skills trained that require updates from ACE-World-16-Patches, /// ensure these updates are installed, and if they aren't, send a helpful message to player with instructions for installation /// </summary> public void HandleDBUpdates() { // dirty fighting var dfSkill = GetCreatureSkill(Skill.DirtyFighting); if (dfSkill.AdvancementClass >= SkillAdvancementClass.Trained) { foreach (var spellID in SpellExtensions.DirtyFightingSpells) { var spell = new Server.Entity.Spell(spellID); if (spell.NotFound) { var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); actionChain.AddAction(this, () => { Session.Network.EnqueueSend(new GameMessageSystemChat("To install Dirty Fighting, please apply the latest patches from https://github.com/ACEmulator/ACE-World-16PY-Patches", ChatMessageType.Broadcast)); }); actionChain.EnqueueChain(); } break; // performance improvement: only check first spell } } // void magic var voidSkill = GetCreatureSkill(Skill.VoidMagic); if (voidSkill.AdvancementClass >= SkillAdvancementClass.Trained) { foreach (var spellID in SpellExtensions.VoidMagicSpells) { var spell = new Server.Entity.Spell(spellID); if (spell.NotFound) { var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); actionChain.AddAction(this, () => { Session.Network.EnqueueSend(new GameMessageSystemChat("To install Void Magic, please apply the latest patches from https://github.com/ACEmulator/ACE-World-16PY-Patches", ChatMessageType.Broadcast)); }); actionChain.EnqueueChain(); } break; // performance improvement: only check first spell (measured 102ms to check 75 uncached void spells) } } // summoning var summoning = GetCreatureSkill(Skill.Summoning); if (summoning.AdvancementClass >= SkillAdvancementClass.Trained) { uint essenceWCID = 48878; var weenie = DatabaseManager.World.GetCachedWeenie(essenceWCID); if (weenie == null) { var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); actionChain.AddAction(this, () => { Session.Network.EnqueueSend(new GameMessageSystemChat("To install Summoning, please apply the latest patches from https://github.com/ACEmulator/ACE-World-16PY-Patches", ChatMessageType.Broadcast)); }); actionChain.EnqueueChain(); } } }
/// <summary> /// This is raised by Player.HandleActionUseItem.<para /> /// The item should be in the players possession. /// </summary> public override void UseItem(Player player) { bool success = true; string failReason = "You are unable to read the scroll."; switch (Power) { // research: http://asheron.wikia.com/wiki/Announcements_-_2002/06_-_Castling case spellLevel2: // Level 2 case spellLevel3: // Level 3 case spellLevel4: // Level 4 case spellLevel5: // Level 5 case spellLevel6: // Level 6 if (!player.CanReadScroll(School, Power)) { success = false; failReason = "You are not skilled enough in the inscribed spell's school of magic to understand the writing on this scroll."; } break; } if (player.SpellIsKnown(SpellId)) { success = false; failReason = "You already know the spell inscribed upon this scroll."; } var actionChain = new ActionChain(); actionChain .AddAction(player, () => player.EnqueueBroadcastMotion(motionReading)) .AddDelaySeconds(2); if (success) { actionChain.AddAction(player, () => { player.LearnSpellWithNetworking(SpellId); player.EnqueueBroadcastMotion(motionReady); if (player.TryRemoveFromInventoryWithNetworking(this)) { player.Session.Network.EnqueueSend(new GameMessageSystemChat("The scroll is destroyed.", ChatMessageType.Magic)); Destroy(); } }); } else { actionChain .AddDelaySeconds(2) .AddAction(player, () => { player.EnqueueBroadcastMotion(motionReady); player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{failReason}", ChatMessageType.Magic)); }); } actionChain .AddAction(player, () => player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session))); actionChain.EnqueueChain(); }
/// <summary> /// Launches a missile attack from monster to target /// </summary> public void LaunchMissile() { //IsTurning = false; var weapon = GetEquippedMissileWeapon(); if (weapon == null || AttackTarget == null) { return; } var ammo = weapon.IsAmmoLauncher ? GetEquippedAmmo() : weapon; if (ammo == null) { return; } // ensure direct line of sight if (!IsDirectVisible(AttackTarget)) { NextAttackTime = Timers.RunningTime + 1.0f; return; } // should this be called each launch? AttackHeight = ChooseAttackHeight(); var dist = GetDistanceToTarget(); //Console.WriteLine("RangeAttack: " + dist); if (DebugMove) { Console.WriteLine($"[{Timers.RunningTime}] - {Name} ({Guid}) - LaunchMissile"); } // get z-angle for aim motion var aimVelocity = GetAimVelocity(AttackTarget); var aimLevel = GetAimLevel(aimVelocity); // calculate projectile spawn pos and velocity var localOrigin = GetProjectileSpawnOrigin(ammo.WeenieClassId, aimLevel); var velocity = CalculateProjectileVelocity(localOrigin, AttackTarget, out Vector3 origin, out Quaternion orientation); //Console.WriteLine($"Velocity: {velocity}"); // launch animation var actionChain = new ActionChain(); var launchTime = EnqueueMotion(actionChain, aimLevel); //Console.WriteLine("LaunchTime: " + launchTime); // launch projectile actionChain.AddAction(this, () => { if (IsDead) { return; } var sound = GetLaunchMissileSound(weapon); EnqueueBroadcast(new GameMessageSound(Guid, sound, 1.0f)); // TODO: monster stamina usage if (AttackTarget != null) { var projectile = LaunchProjectile(weapon, ammo, AttackTarget, origin, orientation, velocity); UpdateAmmoAfterLaunch(ammo); } }); // will ammo be depleted? /*if (ammo.StackSize == null || ammo.StackSize <= 1) * { * // compare monsters: lugianmontokrenegade / sclavusse / zombielichtowerarcher * actionChain.EnqueueChain(); * NextMoveTime = NextAttackTime = Timers.RunningTime + launchTime + MissileDelay; * return; * }*/ // reload animation var animSpeed = GetAnimSpeed(); var reloadTime = EnqueueMotion(actionChain, MotionCommand.Reload, animSpeed); //Console.WriteLine("ReloadTime: " + reloadTime); // reset for next projectile EnqueueMotion(actionChain, MotionCommand.Ready); var linkTime = MotionTable.GetAnimationLength(MotionTableId, CurrentMotionState.Stance, MotionCommand.Reload, MotionCommand.Ready); if (weapon.IsThrownWeapon) { actionChain.EnqueueChain(); actionChain = new ActionChain(); actionChain.AddDelaySeconds(linkTime); } //Console.WriteLine($"Reload time: launchTime({launchTime}) + reloadTime({reloadTime}) + linkTime({linkTime})"); actionChain.AddAction(this, () => EnqueueBroadcast(new GameMessageParentEvent(this, ammo, ACE.Entity.Enum.ParentLocation.RightHand, ACE.Entity.Enum.Placement.RightHandCombat))); actionChain.EnqueueChain(); var timeOffset = launchTime + reloadTime + linkTime; var missileDelay = MissileDelay; if (!weapon.IsAmmoLauncher) { missileDelay *= 1.5f; } NextMoveTime = NextAttackTime = Timers.RunningTime + timeOffset + missileDelay; }
public void ActOnJoin(ObjectGuid playerId) { if (active) { return; } active = true; Player player = CurrentLandblock.GetObject(playerId) as Player; // team is either 0 or 1. -1 means failed to join var msgJoinResponse = new GameEventJoinGameResponse(player.Session, Guid.Full, 1); // 0 or 1 for winning team. -1 is used for stalemate, -2 (and gameId of 0) is used to exit game mode in client // var msgGameOver = new GameEventGameOver(player.Session, 0, -2); // player.Session.Network.EnqueueSend(msgJoinResponse, msgGameOver); player.Session.Network.EnqueueSend(msgJoinResponse); // 0xA9B2002E [135.97 133.313 94.4447] 1 0 0 0 (holtburg game location) var drudgeRook1 = WorldObjectFactory.CreateNewWorldObject(14343) as GamePiece; drudgeRook1.Location = new Position(Location.Cell, Location.PositionX - 3.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09788f, 0, 0, 0, 1); drudgeRook1.EnterWorld(); var drudgeKnight1 = WorldObjectFactory.CreateNewWorldObject(14344) as GamePiece; drudgeKnight1.Location = new Position(Location.Cell, Location.PositionX - 2.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09771f, 0, 0, 0, 1); drudgeKnight1.EnterWorld(); var drudgeBishop1 = WorldObjectFactory.CreateNewWorldObject(14345) as GamePiece; drudgeBishop1.Location = new Position(Location.Cell, Location.PositionX - 1.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09753f, 0, 0, 0, 1); drudgeBishop1.EnterWorld(); var drudgeQueen = WorldObjectFactory.CreateNewWorldObject(14346) as GamePiece; drudgeQueen.Location = new Position(Location.Cell, Location.PositionX - 0.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09735f, 0, 0, 0, 1); drudgeQueen.EnterWorld(); var drudgeKing = WorldObjectFactory.CreateNewWorldObject(14347) as GamePiece; drudgeKing.Location = new Position(Location.Cell, Location.PositionX + 0.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09718f, 0, 0, 0, 1); drudgeKing.EnterWorld(); var drudgeBishop2 = WorldObjectFactory.CreateNewWorldObject(14345) as GamePiece; drudgeBishop2.Location = new Position(Location.Cell, Location.PositionX + 1.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09753f, 0, 0, 0, 1); drudgeBishop2.EnterWorld(); var drudgeKnight2 = WorldObjectFactory.CreateNewWorldObject(14344) as GamePiece; drudgeKnight2.Location = new Position(Location.Cell, Location.PositionX + 2.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09771f, 0, 0, 0, 1); drudgeKnight2.EnterWorld(); var drudgeRook2 = WorldObjectFactory.CreateNewWorldObject(14343) as GamePiece; drudgeRook2.Location = new Position(Location.Cell, Location.PositionX + 3.5f, Location.PositionY - 3.5f, Location.PositionZ - 0.09788f, 0, 0, 0, 1); drudgeRook2.EnterWorld(); var drudgePawn1 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn1.Location = new Position(Location.Cell, Location.PositionX - 3.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09788f, 0, 0, 0, 1); drudgePawn1.EnterWorld(); var drudgePawn2 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn2.Location = new Position(Location.Cell, Location.PositionX - 2.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09771f, 0, 0, 0, 1); drudgePawn2.EnterWorld(); var drudgePawn3 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn3.Location = new Position(Location.Cell, Location.PositionX - 1.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09753f, 0, 0, 0, 1); drudgePawn3.EnterWorld(); var drudgePawn4 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn4.Location = new Position(Location.Cell, Location.PositionX - 0.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09735f, 0, 0, 0, 1); drudgePawn4.EnterWorld(); var drudgePawn5 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn5.Location = new Position(Location.Cell, Location.PositionX + 0.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09718f, 0, 0, 0, 1); drudgePawn5.EnterWorld(); var drudgePawn6 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn6.Location = new Position(Location.Cell, Location.PositionX + 1.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09753f, 0, 0, 0, 1); drudgePawn6.EnterWorld(); var drudgePawn7 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn7.Location = new Position(Location.Cell, Location.PositionX + 2.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09771f, 0, 0, 0, 1); drudgePawn7.EnterWorld(); var drudgePawn8 = WorldObjectFactory.CreateNewWorldObject(14342) as GamePiece; drudgePawn8.Location = new Position(Location.Cell, Location.PositionX + 3.5f, Location.PositionY - 2.5f, Location.PositionZ - 0.09788f, 0, 0, 0, 1); drudgePawn8.EnterWorld(); // Nobody ever actually started a game so the database is currently missing the mosswart versions of the above pieces. :( // For HellsWrath... ActionChain gdlChain = new ActionChain(); gdlChain.AddDelaySeconds(5); gdlChain.AddAction(this, () => { drudgeRook1.Kill(); drudgeBishop1.Kill(); drudgeKnight1.Kill(); drudgeQueen.Kill(); drudgeKing.Kill(); drudgeBishop2.Kill(); drudgeKnight2.Kill(); drudgeRook2.Kill(); drudgePawn1.Kill(); drudgePawn2.Kill(); drudgePawn3.Kill(); drudgePawn4.Kill(); drudgePawn5.Kill(); drudgePawn6.Kill(); drudgePawn7.Kill(); drudgePawn8.Kill(); var msgGameOver = new GameEventGameOver(player.Session, Guid.Full, 0); player.Session.Network.EnqueueSend(msgGameOver); }); gdlChain.AddDelaySeconds(2); gdlChain.AddAction(this, () => { byte[] msg = Convert.FromBase64String("Z2FtZXNkZWFkbG9s"); var popupGDL = new GameEventPopupString(player.Session, System.Text.Encoding.UTF8.GetString(msg, 0, msg.Length)); var msgGameOver2 = new GameEventGameOver(player.Session, 0, -2); player.Session.Network.EnqueueSend(popupGDL, msgGameOver2); player.ChessGamesLost++; player.ChessTotalGames++; active = false; }); gdlChain.EnqueueChain(); }
public void PlayerEnterWorld() { PlayerManager.SwitchPlayerFromOfflineToOnline(this); Teleporting = true; // Save the the LoginTimestamp var lastLoginTimestamp = Time.GetUnixTime(); LoginTimestamp = lastLoginTimestamp; LastTeleportStartTimestamp = lastLoginTimestamp; Character.LastLoginTimestamp = lastLoginTimestamp; Character.TotalLogins++; CharacterChangesDetected = true; Sequences.SetSequence(SequenceType.ObjectInstance, new UShortSequence((ushort)Character.TotalLogins)); if (BarberActive) { BarberActive = false; } if (AllegianceNode != null) { AllegianceRank = (int)AllegianceNode.Rank; } else { AllegianceRank = null; } if (!Account15Days) { var accountTimeSpan = DateTime.UtcNow - Account.CreateTime; if (accountTimeSpan.TotalDays >= 15) { Account15Days = true; } } // SendSelf will trigger the entrance into portal space SendSelf(); // Update or override certain properties sent to client. // bugged: do not send this here, or else a freshly loaded acclient will overrwrite the values // wait until first enter world is completed //SendPropertyUpdatesAndOverrides(); if (PropertyManager.GetBool("use_turbine_chat").Item) { // Init the client with the chat channel ID's, and then notify the player that they've joined the associated channels. Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.TurbineChatIsEnabled)); if (GetCharacterOption(CharacterOption.ListenToAllegianceChat) && Allegiance != null) { JoinTurbineChatChannel("Allegiance"); } if (GetCharacterOption(CharacterOption.ListenToGeneralChat)) { JoinTurbineChatChannel("General"); } if (GetCharacterOption(CharacterOption.ListenToTradeChat)) { JoinTurbineChatChannel("Trade"); } if (GetCharacterOption(CharacterOption.ListenToLFGChat)) { JoinTurbineChatChannel("LFG"); } if (GetCharacterOption(CharacterOption.ListenToRoleplayChat)) { JoinTurbineChatChannel("Roleplay"); } if (GetCharacterOption(CharacterOption.ListenToSocietyChat) && Society != FactionBits.None) { JoinTurbineChatChannel("Society"); } } // check if vassals earned XP while offline HandleAllegianceOnLogin(); HandleHouseOnLogin(); // retail appeared to send the squelch list very early, // even before the CreatePlayer, but doing it here if (SquelchManager.HasSquelches) { SquelchManager.SendSquelchDB(); } AuditItemSpells(); AuditEquippedItems(); HandleMissingXp(); HandleSkillCreditRefund(); HandleSkillTemplesReset(); HandleSkillSpecCreditRefund(); HandleFreeSkillResetRenewal(); HandleFreeAttributeResetRenewal(); if (PlayerKillerStatus == PlayerKillerStatus.PKLite && !PropertyManager.GetBool("pkl_server").Item) { var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); actionChain.AddAction(this, () => { UpdateProperty(this, PropertyInt.PlayerKillerStatus, (int)PlayerKillerStatus.NPK, true); Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNonPKAgain)); }); actionChain.EnqueueChain(); } HandleDBUpdates(); }
/// <summary> /// This method is used to pick items off the world - out of 3D space and into our inventory or to a wielded slot. /// It checks the use case needed, sends the appropriate response messages. In addition, it will move to objects /// that are out of range in the attemp to pick them up. It will call update apperiance if needed and you have /// wielded an item from the ground. Og II /// </summary> /// <param name="container"></param> /// <param name="itemGuid"></param> /// <param name="placement"></param> /// <param name="iidPropertyId"></param> private void PickupItem(Container container, ObjectGuid itemGuid, int placement, PropertyInstanceId iidPropertyId) { // Logical operations: // !! FIXME: How to handle repeat on condition? // while (!objectInRange) // try Move to object // !! FIXME: How to handle conditional // Try acquire from landblock // if acquire successful: // add to container ActionChain pickUpItemChain = new ActionChain(); // Move to the object pickUpItemChain.AddChain(CreateMoveToChain(itemGuid, PickUpDistance)); // Pick up the object // Start pickup animation pickUpItemChain.AddAction(this, () => { var motion = new UniversalMotion(MotionStance.Standing); motion.MovementData.ForwardCommand = (uint)MotionCommand.Pickup; CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageUpdatePosition(this), new GameMessageUpdateMotion(Guid, Sequences.GetCurrentSequence(SequenceType.ObjectInstance), Sequences, motion)); }); // Wait for animation to progress var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId); var pickupAnimationLength = motionTable.GetAnimationLength(MotionCommand.Pickup); pickUpItemChain.AddDelaySeconds(pickupAnimationLength); // Ask landblock to transfer item // pickUpItemChain.AddAction(CurrentLandblock, () => CurrentLandblock.TransferItem(itemGuid, containerGuid)); if (container.Guid.IsPlayer()) { CurrentLandblock.QueueItemTransfer(pickUpItemChain, itemGuid, container.Guid); } else { CurrentLandblock.ScheduleItemTransferInContainer(pickUpItemChain, itemGuid, (Container)GetInventoryItem(container.Guid)); } // Finish pickup animation pickUpItemChain.AddAction(this, () => { // If success, the item is in our inventory: WorldObject item = GetInventoryItem(itemGuid); if (item.ContainerId != Guid.Full) { //Burden += item.Burden ?? 0; if (item.WeenieType == WeenieType.Coin) { UpdateCurrencyClientCalculations(WeenieType.Coin); } } if (item is Container itemAsContainer) { Session.Network.EnqueueSend(new GameEventViewContents(Session, itemAsContainer)); foreach (var packItem in itemAsContainer.Inventory) { Session.Network.EnqueueSend(new GameMessageCreateObject(packItem.Value)); UpdateCurrencyClientCalculations(WeenieType.Coin); } } // Update all our stuff if we succeeded if (item != null) { item.SetPropertiesForContainer(placement); // FIXME(ddevec): I'm not 100% sure which of these need to be broadcasts, and which are local sends... var motion = new UniversalMotion(MotionStance.Standing); if (iidPropertyId == PropertyInstanceId.Container) { Session.Network.EnqueueSend( ////new GameMessagePrivateUpdatePropertyInt(Session.Player.Sequences, PropertyInt.EncumbranceVal, UpdateBurden()), new GameMessageSound(Guid, Sound.PickUpItem, 1.0f), new GameMessageUpdateInstanceId(itemGuid, container.Guid, iidPropertyId), new GameMessagePutObjectInContainer(Session, container.Guid, item, placement)); } else { AddToWieldedObjects(item, container, (EquipMask)placement); Session.Network.EnqueueSend(new GameMessageSound(Guid, Sound.WieldObject, (float)1.0), new GameMessageObjDescEvent(this), new GameMessageUpdateInstanceId(container.Guid, itemGuid, PropertyInstanceId.Wielder), new GameEventWieldItem(Session, itemGuid.Full, placement)); } CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageUpdateMotion(Guid, Sequences.GetCurrentSequence(SequenceType.ObjectInstance), Sequences, motion), new GameMessagePickupEvent(item)); if (iidPropertyId == PropertyInstanceId.Wielder) { CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageObjDescEvent(this)); } // TODO: Og II - check this later to see if it is still required. Session.Network.EnqueueSend(new GameMessageUpdateObject(item)); } // If we didn't succeed, just stand up and be ashamed of ourself else { var motion = new UniversalMotion(MotionStance.Standing); CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageUpdateMotion(Guid, Sequences.GetCurrentSequence(SequenceType.ObjectInstance), Sequences, motion)); // CurrentLandblock.EnqueueBroadcast(self shame); } }); // Set chain to run pickUpItemChain.EnqueueChain(); }