/// <summary> /// Removes threat /// </summary> public override sealed void RemoveThreat(MmoObject cUnit, float amount, Spell spell) { if (IsDead()) { return; } if (amount <= 0) { return; } switch ((ObjectType)cUnit.Guid.Type) { case ObjectType.Npc: case ObjectType.Player: { var id = cUnit.Guid; float oldThreat; if (threatTable.TryGetValue(id, out oldThreat) == false) { return; } // dont use += (two lookups) // making sure to clamp the threat between 1 to max // remember, threat of 1 means that the unit is in the npc's visible range this.threatTable[id] = Mathf.Clamp(oldThreat - amount, 1, int.MaxValue); } break; } }
/// <summary> /// Heals the character. /// </summary> public void Heal(MmoObject cUnit, int value, Spell spell) { if (state == UnitState.Dead) return; // set hp and publish this.SetHealth(currentHealth + value, true); // TODO: Add threat to all attackers from cUnit }
/// <summary> /// Gains power. /// </summary> public void GainPower(MmoObject cUnit, int value, Spell spell) { // already dead so move along if (state == UnitState.Dead) return; // set pow and publish this.SetPower(currentPower + value, true); }
/// <summary> /// Drains power. Calls <see cref="AddThreat(MmoObject, float, Spell)"/> if <see cref="CanAddThreat()"/> returns <value>true</value>. /// </summary> public void DrainPower(MmoObject cUnit, int value, Spell spell) { // already dead so move along if (state == UnitState.Dead) return; // set pow and publish this.SetPower(currentPower - value, true); if (CanAddThreat()) { var threat = value * ServerGameSettings.THREAT_POWER_MULTIPLIER; this.AddThreat(cUnit, threat, spell); } }
/// <summary> /// Kills the <see cref="Character"/> and calls <see cref="OnDeath(MmoObject)"/>. /// Updates the <see cref="State"/> to <see cref="UnitState.Dead"/> and resets <see cref="CurrentHealth"/> and <see cref="CurrentPower"/> to <value>0</value>. /// Publishes the current <see cref="UnitState"/>. /// </summary> public void Kill(MmoObject killer) { if (state == UnitState.Dead) return; // set state and publish this.SetUnitState(UnitState.Dead, true); // curr hp and curr pow dont need to be published // since when the client receives SetUnitState(Dead) // it will automatically updates them and the revision this.SetHealth(0); this.SetPower(0); this.OnDeath(killer); }
/// <summary> /// Adds threat /// </summary> public override sealed void AddThreat(MmoObject cUnit, float amount, Spell spell) { if (IsDead()) { return; } if (amount <= 0) { return; } switch ((ObjectType)cUnit.Guid.Type) { case ObjectType.Npc: case ObjectType.Player: { var id = cUnit.Guid; float oldThreat; if (threatTable.TryGetValue(id, out oldThreat) == false) { // this will not happen in regular scenarios // will only happen when damaging npc without entering its aggro range // mostly when debugging or via gm commands if (ManualAggro(cUnit as Character) == false) { return; } // looking up again to make sure that the manual aggro worked if (threatTable.TryGetValue(id, out oldThreat) == false) { return; } } // dont use += (two lookups) // making sure to clamp the threat between 1 to max // remember, threat of 1 means that the unit is in the npc's visible range this.threatTable[id] = Mathf.Clamp(oldThreat + amount, 1, int.MaxValue); } break; } }
void DoProcessThreat(MmoObject mmoObject) { // this will make sure that we only subscribe world objects if (CanRadarSubscribe(mmoObject) == false) { return; } var character = mmoObject as Character; if (character != null) { if (!visibleCharacters.ContainsKey(character)) { // subscribing character var subscription = mmoObject.PositionUpdateChannel.SubscribeToLast(this.CurrentZone.PrimaryFiber, this.Radar_OnCharacterPosition_AggroEnterCheck, ServerGameSettings.AGGRO_ENTER_CHECK_INTERVAL_MS); this.visibleCharacters.Add(character, subscription); } } }
/// <summary> /// Begins to chase a(n) <see cref="MmoObject"/> and calls <paramref name="onHitCallback"/> when hit. /// </summary> public void SpellChase(MmoObject target, MmoObject owner, int speed, Action onHitCallback) { // we are already being updated so skip if (updateSubscription != null) { return; } this.CurrentFocus = target; this.CurrentSpeed = speed; this.onHit = onHitCallback; // creating the owner disposal subscription this.ownerDisposedSubscription = owner.DisposeChannel.Subscribe(this.CurrentZone.PrimaryFiber, m => this.OnSpellOwner_Disposed()); this.destination = this.CurrentFocus.Position; this.updateSubscription = this.CreateUpdateEvent(this.Update, ServerGameSettings.GAME_UPDATE_INTERVAL); // we are moving Interlocked.Exchange(ref this.interlockedIsMoving, 1); }
/// <summary> /// Damages the character. Calls <see cref="AddThreat(MmoObject, float, Spell)"/> if <see cref="CanAddThreat()"/> returns <value>true</value>. /// </summary> public void Damage(MmoObject cUnit, int value, Spell spell) { // already dead so move along if (state == UnitState.Dead) return; // dont publish curr hp since the char may die after dmg this.SetHealth(currentHealth - value); if (CanAddThreat()) { var threat = value * ServerGameSettings.THREAT_DAMAGE_MULTIPLIER; this.AddThreat(cUnit, threat, spell); } // kills the char if health is 0 if (currentHealth <= 0) this.Kill(cUnit); // only publish curr hp if the char is not dead if (state != UnitState.Dead) this.PublishProperty(PropertyCode.CurrHp, this.currentHealth); }
/// <summary> /// Called when this <see cref="Character"/> is killed. Remember the <paramref name="killer"/> could be <value>this</value>. /// </summary> protected override void OnDeath(MmoObject killer) { base.OnDeath(killer); if (IsMoving) { this.ResetMovement(); this.PublishStopEvent(); } this.DisposeRadarSubscriptions(); this.CurrentAggroState = AggroState.Idle; this.CurrentFocus = null; RandomLootContainer randomLootContainer = null; ICollection <MmoGuid> skipPlayers = null; foreach (var pair in threatTable) { // only players can be rewarded for now var pGuid = pair.Key; if (pGuid.Type != (byte)ObjectType.Player) { continue; } // reward only if the threat is greater than this threshold if (pair.Value <= ServerGameSettings.THREAT_THRESHOLD_FOR_REWARD) { continue; } if (skipPlayers != null) { // making sure the player gets only one loot generation attempt if (skipPlayers.Contains(pGuid)) { continue; } skipPlayers.Add(pGuid); } WorldSession session; if (MmoWorld.Instance.SessionCache.TryGetSessionByPlayerGuid(pGuid.Id, out session)) { // generate loot, only if one of the killer is a player if (randomLootContainer == null) { randomLootContainer = new RandomLootContainer(this, this.lootGroupId, (itemId, looter) => true); } // create the collection only if one killer is a player if (skipPlayers == null) { skipPlayers = this.threatTable.Count > 10 ? (ICollection <MmoGuid>) new HashSet <MmoGuid>() : new List <MmoGuid>(); } var player = session.Player; // player is in a group if (player.InGroup()) { var members = player.Group.GetActiveMembers(); foreach (var member in members) { // making sure the member gets only one loot generation attempt var mGuid = member.Guid; if (skipPlayers.Contains(mGuid)) { continue; } skipPlayers.Add(mGuid); // finding the player WorldSession memberSession; if (MmoWorld.Instance.SessionCache.TryGetSessionByPlayerGuid(mGuid.Id, out memberSession)) { continue; } // checking to see if the player is within the range of the member var memberPlayer = memberSession.Player; if (!player.HaveSubscriptionFor(memberPlayer.Guid) && player != memberPlayer) { continue; } randomLootContainer.GenerateLootFor(memberPlayer); } } // always generate loot for the player randomLootContainer.GenerateLootFor(player); player.RewardKill(this); } } this.threatTable.Clear(); var spawnDelay = ServerGameSettings.BASE_NPC_RESPAWN_TIME_MS; if (randomLootContainer != null) { spawnDelay = ServerGameSettings.BASE_NPC_WAIT_TIME_UNTIL_LOOTED_MS; // set the loot lootContainer = randomLootContainer; // notify all the looters randomLootContainer.NotifyLooters(); } this.QueueRespawn(spawnDelay, ServerGameSettings.BASE_NPC_RESPAWN_TIME_MS, this.iPosition, this.iRotation); }
/// <summary> /// Tells whether a <see cref="MmoObject"/> can be subscribed or not. /// </summary> bool CanRadarSubscribe(MmoObject mmoObject) { var objectType = (ObjectType)mmoObject.Guid.Type; return(mmoObject != this && objectType == ObjectType.Npc || objectType == ObjectType.Player); }
/// <summary> /// Filters the <see cref="mmoObject"/> for combat eligibility and takes appropriate action /// </summary> void IEngager.ProcessThreat(MmoObject mmoObject) { this.DoProcessThreat(mmoObject); }
/// <summary> /// Rewards this <see cref="MmoObject"/> of the kill. /// </summary> /// <param name="killed"> The <see cref="MmoObject"/> that was killed. </param> public virtual void RewardKill(MmoObject killed) { }
/// <summary> /// Called when this <see cref="Character"/> is killed. Remember the <paramref name="killer"/> could be <value>this</value>. /// This will be called after calling <see cref="Kill(MmoObject)"/>. /// Does nothing by default. /// </summary> protected virtual void OnDeath(MmoObject killer) { }
/// <summary> /// Removes threat /// </summary> public virtual void RemoveThreat(MmoObject cUnit, float amount, Spell spell) { }