Ported .Net Core commits:
hondacrx: - Initial commit: Switch to .Net Core 2.0 - Fix build and removed not needed files Fabi: - Updated solution platforms. - Changed folder structure. - Change library target framework to netstandard2.0. - Updated solution platforms again... - Removed windows specific kernel32 function usage (Ctrl-C handler).
This commit is contained in:
@@ -0,0 +1,770 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.GameMath;
|
||||
using Game.AI;
|
||||
using Game.Maps;
|
||||
using Game.Movement;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class AreaTrigger : WorldObject
|
||||
{
|
||||
public AreaTrigger() : base(false)
|
||||
{
|
||||
_previousCheckOrientation = float.PositiveInfinity;
|
||||
_reachedDestination = true;
|
||||
|
||||
objectTypeMask |= TypeMask.AreaTrigger;
|
||||
objectTypeId = TypeId.AreaTrigger;
|
||||
|
||||
m_updateFlag = UpdateFlag.StationaryPosition | UpdateFlag.Areatrigger;
|
||||
|
||||
valuesCount = (int)AreaTriggerFields.End;
|
||||
|
||||
_spline = new Spline();
|
||||
}
|
||||
|
||||
public override void AddToWorld()
|
||||
{
|
||||
// Register the AreaTrigger for guid lookup and for caster
|
||||
if (!IsInWorld)
|
||||
{
|
||||
GetMap().GetObjectsStore().Add(GetGUID(), this);
|
||||
base.AddToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveFromWorld()
|
||||
{
|
||||
// Remove the AreaTrigger from the accessor and from all lists of objects in world
|
||||
if (IsInWorld)
|
||||
{
|
||||
_isRemoved = true;
|
||||
|
||||
Unit caster = GetCaster();
|
||||
if (caster)
|
||||
caster._UnregisterAreaTrigger(this);
|
||||
|
||||
// Handle removal of all units, calling OnUnitExit & deleting auras if needed
|
||||
HandleUnitEnterExit(new List<Unit>());
|
||||
|
||||
_ai.OnRemove();
|
||||
|
||||
base.RemoveFromWorld();
|
||||
GetMap().GetObjectsStore().Remove(GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
public bool CreateAreaTrigger(uint spellMiscId, Unit caster, Unit target, SpellInfo spell, Position pos, int duration, uint spellXSpellVisualId, ObjectGuid castId = default(ObjectGuid), AuraEffect aurEff = null)
|
||||
{
|
||||
_targetGuid = target ? target.GetGUID() : ObjectGuid.Empty;
|
||||
_aurEff = aurEff;
|
||||
|
||||
SetMap(caster.GetMap());
|
||||
Relocate(pos);
|
||||
if (!IsPositionValid())
|
||||
{
|
||||
Log.outError(LogFilter.AreaTrigger, "AreaTrigger (spell {0}) not created. Invalid coordinates (X: {0} Y: {1})", spell.Id, GetPositionX(), GetPositionY());
|
||||
return false;
|
||||
}
|
||||
|
||||
_areaTriggerMiscTemplate = Global.AreaTriggerDataStorage.GetAreaTriggerMiscTemplate(spellMiscId);
|
||||
if (_areaTriggerMiscTemplate == null)
|
||||
{
|
||||
Log.outError(LogFilter.AreaTrigger, "AreaTrigger (spellMiscId {0}) not created. Invalid areatrigger miscid ({1})", spellMiscId, spellMiscId);
|
||||
return false;
|
||||
}
|
||||
|
||||
_Create(ObjectGuid.Create(HighGuid.AreaTrigger, GetMapId(), GetTemplate().Id, caster.GetMap().GenerateLowGuid(HighGuid.AreaTrigger)));
|
||||
SetPhaseMask(caster.GetPhaseMask(), false);
|
||||
|
||||
SetEntry(GetTemplate().Id);
|
||||
SetDuration(duration);
|
||||
|
||||
SetObjectScale(1.0f);
|
||||
|
||||
SetGuidValue(AreaTriggerFields.Caster, caster.GetGUID());
|
||||
SetGuidValue(AreaTriggerFields.CreatingEffectGuid, castId);
|
||||
|
||||
SetUInt32Value(AreaTriggerFields.SpellId, spell.Id);
|
||||
SetUInt32Value(AreaTriggerFields.SpellForVisuals, spell.Id);
|
||||
SetUInt32Value(AreaTriggerFields.SpellXSpellVisualId, spellXSpellVisualId);
|
||||
SetUInt32Value(AreaTriggerFields.TimeToTargetScale, GetMiscTemplate().TimeToTargetScale != 0 ? GetMiscTemplate().TimeToTargetScale : GetUInt32Value(AreaTriggerFields.Duration));
|
||||
SetFloatValue(AreaTriggerFields.BoundsRadius2d, GetTemplate().MaxSearchRadius);
|
||||
SetUInt32Value(AreaTriggerFields.DecalPropertiesId, GetMiscTemplate().DecalPropertiesId);
|
||||
|
||||
for (byte scaleCurveIndex = 0; scaleCurveIndex < SharedConst.MaxAreatriggerScale; ++scaleCurveIndex)
|
||||
if (GetMiscTemplate().ScaleInfo.ExtraScale[scaleCurveIndex].AsInt32 != 0)
|
||||
SetUInt32Value(AreaTriggerFields.ExtraScaleCurve + scaleCurveIndex, (uint)GetMiscTemplate().ScaleInfo.ExtraScale[scaleCurveIndex].AsInt32);
|
||||
|
||||
CopyPhaseFrom(caster);
|
||||
|
||||
if (target && GetTemplate().HasFlag(AreaTriggerFlags.HasAttached))
|
||||
{
|
||||
m_movementInfo.transport.guid = target.GetGUID();
|
||||
}
|
||||
|
||||
UpdateShape();
|
||||
|
||||
if (GetMiscTemplate().HasSplines())
|
||||
{
|
||||
uint timeToTarget = GetMiscTemplate().TimeToTarget != 0 ? GetMiscTemplate().TimeToTarget : GetUInt32Value(AreaTriggerFields.Duration);
|
||||
InitSplineOffsets(GetMiscTemplate().SplinePoints, timeToTarget);
|
||||
}
|
||||
|
||||
// movement on transport of areatriggers on unit is handled by themself
|
||||
Transport transport = m_movementInfo.transport.guid.IsEmpty() ? caster.GetTransport() : null;
|
||||
if (transport)
|
||||
{
|
||||
float x, y, z, o;
|
||||
pos.GetPosition(out x, out y, out z, out o);
|
||||
transport.CalculatePassengerOffset(ref x, ref y, ref z, ref o);
|
||||
m_movementInfo.transport.pos.Relocate(x, y, z, o);
|
||||
|
||||
// This object must be added to transport before adding to map for the client to properly display it
|
||||
transport.AddPassenger(this);
|
||||
}
|
||||
|
||||
AI_Initialize();
|
||||
|
||||
if (!GetMap().AddToMap(this))
|
||||
{ // Returning false will cause the object to be deleted - remove from transport
|
||||
if (transport)
|
||||
transport.RemovePassenger(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
caster._RegisterAreaTrigger(this);
|
||||
|
||||
_ai.OnCreate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(uint diff)
|
||||
{
|
||||
base.Update(diff);
|
||||
_timeSinceCreated += diff;
|
||||
|
||||
if (GetTemplate().HasFlag(AreaTriggerFlags.HasAttached))
|
||||
{
|
||||
Unit target = GetTarget();
|
||||
if (target)
|
||||
GetMap().AreaTriggerRelocation(this, target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), target.GetOrientation());
|
||||
}
|
||||
else
|
||||
UpdateSplinePosition(diff);
|
||||
|
||||
if (GetDuration() != -1)
|
||||
{
|
||||
if (GetDuration() > diff)
|
||||
_UpdateDuration((int)(_duration - diff));
|
||||
else
|
||||
{
|
||||
Remove(); // expired
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ai.OnUpdate(diff);
|
||||
|
||||
UpdateTargetList();
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
if (IsInWorld)
|
||||
AddObjectToRemoveList();
|
||||
}
|
||||
|
||||
public void SetDuration(int newDuration)
|
||||
{
|
||||
_duration = newDuration;
|
||||
_totalDuration = newDuration;
|
||||
|
||||
// negative duration (permanent areatrigger) sent as 0
|
||||
SetUInt32Value(AreaTriggerFields.Duration, (uint)Math.Max(newDuration, 0));
|
||||
}
|
||||
|
||||
void _UpdateDuration(int newDuration)
|
||||
{
|
||||
_duration = newDuration;
|
||||
|
||||
// should be sent in object create packets only
|
||||
UpdateData[(int)AreaTriggerFields.Duration] = _duration;
|
||||
}
|
||||
|
||||
float GetProgress()
|
||||
{
|
||||
return GetTimeSinceCreated() < GetTimeToTargetScale() ? GetTimeSinceCreated() / GetTimeToTargetScale() : 1.0f;
|
||||
}
|
||||
|
||||
void UpdateTargetList()
|
||||
{
|
||||
List<Unit> targetList = new List<Unit>();
|
||||
|
||||
switch (GetTemplate().TriggerType)
|
||||
{
|
||||
case AreaTriggerTypes.Sphere:
|
||||
SearchUnitInSphere(targetList);
|
||||
break;
|
||||
case AreaTriggerTypes.Box:
|
||||
SearchUnitInBox(targetList);
|
||||
break;
|
||||
case AreaTriggerTypes.Polygon:
|
||||
SearchUnitInPolygon(targetList);
|
||||
break;
|
||||
case AreaTriggerTypes.Cylinder:
|
||||
SearchUnitInCylinder(targetList);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
HandleUnitEnterExit(targetList);
|
||||
}
|
||||
|
||||
void SearchUnitInSphere(List<Unit> targetList)
|
||||
{
|
||||
float radius = GetTemplate().SphereDatas.Radius;
|
||||
if (GetTemplate().HasFlag(AreaTriggerFlags.HasDynamicShape))
|
||||
{
|
||||
if (GetMiscTemplate().MorphCurveId != 0)
|
||||
{
|
||||
radius = MathFunctions.lerp(GetTemplate().SphereDatas.Radius, GetTemplate().SphereDatas.RadiusTarget, Global.DB2Mgr.GetCurveValueAt(GetMiscTemplate().MorphCurveId, GetProgress()));
|
||||
}
|
||||
}
|
||||
|
||||
var check = new AnyUnitInObjectRangeCheck(this, radius);
|
||||
var searcher = new UnitListSearcher(this, targetList, check);
|
||||
Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius);
|
||||
}
|
||||
|
||||
void SearchUnitInBox(List<Unit> targetList)
|
||||
{
|
||||
float extentsX, extentsY, extentsZ;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (float* ptr = GetTemplate().BoxDatas.Extents)
|
||||
{
|
||||
extentsX = ptr[0];
|
||||
extentsY = ptr[1];
|
||||
extentsZ = ptr[2];
|
||||
}
|
||||
}
|
||||
|
||||
var check = new AnyUnitInObjectRangeCheck(this, GetTemplate().MaxSearchRadius, false);
|
||||
var searcher = new UnitListSearcher(this, targetList, check);
|
||||
Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius);
|
||||
|
||||
float halfExtentsX = extentsX / 2.0f;
|
||||
float halfExtentsY = extentsY / 2.0f;
|
||||
float halfExtentsZ = extentsZ / 2.0f;
|
||||
|
||||
float minX = GetPositionX() - halfExtentsX;
|
||||
float maxX = GetPositionX() + halfExtentsX;
|
||||
|
||||
float minY = GetPositionY() - halfExtentsY;
|
||||
float maxY = GetPositionY() + halfExtentsY;
|
||||
|
||||
float minZ = GetPositionZ() - halfExtentsZ;
|
||||
float maxZ = GetPositionZ() + halfExtentsZ;
|
||||
|
||||
AxisAlignedBox box = new AxisAlignedBox(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ));
|
||||
|
||||
targetList.RemoveAll(unit =>
|
||||
{
|
||||
return !box.contains(new Vector3(unit.GetPositionX(), unit.GetPositionY(), unit.GetPositionZ()));
|
||||
});
|
||||
}
|
||||
|
||||
void SearchUnitInPolygon(List<Unit> targetList)
|
||||
{
|
||||
var check = new AnyUnitInObjectRangeCheck(this, GetTemplate().MaxSearchRadius, false);
|
||||
var searcher = new UnitListSearcher(this, targetList, check);
|
||||
Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius);
|
||||
|
||||
float height = GetTemplate().PolygonDatas.Height;
|
||||
float minZ = GetPositionZ() - height;
|
||||
float maxZ = GetPositionZ() + height;
|
||||
|
||||
targetList.RemoveAll(unit =>
|
||||
{
|
||||
return !CheckIsInPolygon2D(unit)
|
||||
|| unit.GetPositionZ() < minZ
|
||||
|| unit.GetPositionZ() > maxZ;
|
||||
});
|
||||
}
|
||||
|
||||
void SearchUnitInCylinder(List<Unit> targetList)
|
||||
{
|
||||
var check = new AnyUnitInObjectRangeCheck(this, GetTemplate().MaxSearchRadius, false);
|
||||
var searcher = new UnitListSearcher(this, targetList, check);
|
||||
Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius);
|
||||
|
||||
float height = GetTemplate().CylinderDatas.Height;
|
||||
float minZ = GetPositionZ() - height;
|
||||
float maxZ = GetPositionZ() + height;
|
||||
|
||||
targetList.RemoveAll(unit =>
|
||||
{
|
||||
return unit.GetPositionZ() < minZ
|
||||
|| unit.GetPositionZ() > maxZ;
|
||||
});
|
||||
}
|
||||
|
||||
void HandleUnitEnterExit(List<Unit> newTargetList)
|
||||
{
|
||||
List<ObjectGuid> exitUnits = _insideUnits;
|
||||
_insideUnits.Clear();
|
||||
|
||||
List<Unit> enteringUnits = new List<Unit>();
|
||||
|
||||
foreach (Unit unit in newTargetList)
|
||||
{
|
||||
if (!exitUnits.Remove(unit.GetGUID())) // erase(key_type) returns number of elements erased
|
||||
enteringUnits.Add(unit);
|
||||
|
||||
_insideUnits.Add(unit.GetGUID());
|
||||
}
|
||||
|
||||
// Handle after _insideUnits have been reinserted so we can use GetInsideUnits() in hooks
|
||||
foreach (Unit unit in enteringUnits)
|
||||
{
|
||||
Player player = unit.ToPlayer();
|
||||
if (player)
|
||||
if (player.isDebugAreaTriggers)
|
||||
player.SendSysMessage(CypherStrings.DebugAreatriggerEntered, GetTemplate().Id);
|
||||
|
||||
DoActions(unit);
|
||||
|
||||
_ai.OnUnitEnter(unit);
|
||||
}
|
||||
|
||||
foreach (ObjectGuid exitUnitGuid in exitUnits)
|
||||
{
|
||||
Unit leavingUnit = Global.ObjAccessor.GetUnit(this, exitUnitGuid);
|
||||
if (leavingUnit)
|
||||
{
|
||||
Player player = leavingUnit.ToPlayer();
|
||||
if (player)
|
||||
if (player.isDebugAreaTriggers)
|
||||
player.SendSysMessage(CypherStrings.DebugAreatriggerLeft, GetTemplate().Id);
|
||||
|
||||
UndoActions(leavingUnit);
|
||||
|
||||
_ai.OnUnitExit(leavingUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AreaTriggerTemplate GetTemplate()
|
||||
{
|
||||
return _areaTriggerMiscTemplate.Template;
|
||||
}
|
||||
|
||||
public uint GetScriptId()
|
||||
{
|
||||
return GetTemplate().ScriptId;
|
||||
}
|
||||
|
||||
public Unit GetCaster()
|
||||
{
|
||||
return Global.ObjAccessor.GetUnit(this, GetCasterGuid());
|
||||
}
|
||||
|
||||
Unit GetTarget()
|
||||
{
|
||||
return Global.ObjAccessor.GetUnit(this, _targetGuid);
|
||||
}
|
||||
|
||||
void UpdatePolygonOrientation()
|
||||
{
|
||||
float newOrientation = GetOrientation();
|
||||
|
||||
// No need to recalculate, orientation didn't change
|
||||
if (MathFunctions.fuzzyEq(_previousCheckOrientation, newOrientation))
|
||||
return;
|
||||
|
||||
_polygonVertices = GetTemplate().PolygonVertices;
|
||||
|
||||
float angleSin = (float)Math.Sin(newOrientation);
|
||||
float angleCos = (float)Math.Cos(newOrientation);
|
||||
|
||||
// This is needed to rotate the vertices, following orientation
|
||||
for (var i = 0; i < _polygonVertices.Count; ++i)
|
||||
{
|
||||
Vector2 vertice = _polygonVertices[i];
|
||||
|
||||
vertice.X = vertice.X * angleCos - vertice.Y * angleSin;
|
||||
vertice.Y = vertice.Y * angleCos + vertice.X * angleSin;
|
||||
}
|
||||
|
||||
_previousCheckOrientation = newOrientation;
|
||||
}
|
||||
|
||||
bool CheckIsInPolygon2D(Position pos)
|
||||
{
|
||||
float testX = pos.GetPositionX();
|
||||
float testY = pos.GetPositionY();
|
||||
|
||||
//this method uses the ray tracing algorithm to determine if the point is in the polygon
|
||||
bool locatedInPolygon = false;
|
||||
|
||||
for (int vertex = 0; vertex < _polygonVertices.Count; ++vertex)
|
||||
{
|
||||
int nextVertex;
|
||||
|
||||
//repeat loop for all sets of points
|
||||
if (vertex == (_polygonVertices.Count - 1))
|
||||
{
|
||||
//if i is the last vertex, let j be the first vertex
|
||||
nextVertex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//for all-else, let j=(i+1)th vertex
|
||||
nextVertex = vertex + 1;
|
||||
}
|
||||
|
||||
float vertX_i = GetPositionX() + _polygonVertices[vertex].X;
|
||||
float vertY_i = GetPositionY() + _polygonVertices[vertex].Y;
|
||||
float vertX_j = GetPositionX() + _polygonVertices[nextVertex].X;
|
||||
float vertY_j = GetPositionY() + _polygonVertices[nextVertex].Y;
|
||||
|
||||
// following statement checks if testPoint.Y is below Y-coord of i-th vertex
|
||||
bool belowLowY = vertY_i > testY;
|
||||
// following statement checks if testPoint.Y is below Y-coord of i+1-th vertex
|
||||
bool belowHighY = vertY_j > testY;
|
||||
|
||||
/* following statement is true if testPoint.Y satisfies either (only one is possible)
|
||||
-.(i).Y < testPoint.Y < (i+1).Y OR
|
||||
-.(i).Y > testPoint.Y > (i+1).Y
|
||||
|
||||
(Note)
|
||||
Both of the conditions indicate that a point is located within the edges of the Y-th coordinate
|
||||
of the (i)-th and the (i+1)- th vertices of the polygon. If neither of the above
|
||||
conditions is satisfied, then it is assured that a semi-infinite horizontal line draw
|
||||
to the right from the testpoint will NOT cross the line that connects vertices i and i+1
|
||||
of the polygon
|
||||
*/
|
||||
bool withinYsEdges = belowLowY != belowHighY;
|
||||
|
||||
if (withinYsEdges)
|
||||
{
|
||||
// this is the slope of the line that connects vertices i and i+1 of the polygon
|
||||
float slopeOfLine = (vertX_j - vertX_i) / (vertY_j - vertY_i);
|
||||
|
||||
// this looks up the x-coord of a point lying on the above line, given its y-coord
|
||||
float pointOnLine = (slopeOfLine * (testY - vertY_i)) + vertX_i;
|
||||
|
||||
//checks to see if x-coord of testPoint is smaller than the point on the line with the same y-coord
|
||||
bool isLeftToLine = testX < pointOnLine;
|
||||
|
||||
if (isLeftToLine)
|
||||
{
|
||||
//this statement changes true to false (and vice-versa)
|
||||
locatedInPolygon = !locatedInPolygon;
|
||||
}//end if (isLeftToLine)
|
||||
}//end if (withinYsEdges
|
||||
}
|
||||
|
||||
return locatedInPolygon;
|
||||
}
|
||||
|
||||
public void UpdateShape()
|
||||
{
|
||||
if (GetTemplate().IsPolygon())
|
||||
UpdatePolygonOrientation();
|
||||
}
|
||||
|
||||
bool UnitFitToActionRequirement(Unit unit, Unit caster, AreaTriggerAction action)
|
||||
{
|
||||
switch (action.TargetType)
|
||||
{
|
||||
case AreaTriggerActionUserTypes.Friend:
|
||||
return caster._IsValidAssistTarget(unit, Global.SpellMgr.GetSpellInfo(action.Param));
|
||||
case AreaTriggerActionUserTypes.Enemy:
|
||||
return caster._IsValidAttackTarget(unit, Global.SpellMgr.GetSpellInfo(action.Param));
|
||||
case AreaTriggerActionUserTypes.Raid:
|
||||
return caster.IsInRaidWith(unit);
|
||||
case AreaTriggerActionUserTypes.Party:
|
||||
return caster.IsInPartyWith(unit);
|
||||
case AreaTriggerActionUserTypes.Caster:
|
||||
return unit.GetGUID() == caster.GetGUID();
|
||||
case AreaTriggerActionUserTypes.Any:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DoActions(Unit unit)
|
||||
{
|
||||
Unit caster = GetCaster();
|
||||
if (caster)
|
||||
{
|
||||
foreach (AreaTriggerAction action in GetTemplate().Actions)
|
||||
{
|
||||
if (UnitFitToActionRequirement(unit, caster, action))
|
||||
{
|
||||
switch (action.ActionType)
|
||||
{
|
||||
case AreaTriggerActionTypes.Cast:
|
||||
caster.CastSpell(unit, action.Param, true);
|
||||
break;
|
||||
case AreaTriggerActionTypes.AddAura:
|
||||
caster.AddAura(action.Param, unit);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UndoActions(Unit unit)
|
||||
{
|
||||
foreach (AreaTriggerAction action in GetTemplate().Actions)
|
||||
{
|
||||
if (action.ActionType == AreaTriggerActionTypes.Cast || action.ActionType == AreaTriggerActionTypes.AddAura)
|
||||
unit.RemoveAurasDueToSpell(action.Param, GetCasterGuid());
|
||||
}
|
||||
}
|
||||
|
||||
void InitSplineOffsets(List<Vector3> offsets, uint timeToTarget)
|
||||
{
|
||||
float angleSin = (float)Math.Sin(GetOrientation());
|
||||
float angleCos = (float)Math.Cos(GetOrientation());
|
||||
|
||||
// This is needed to rotate the spline, following caster orientation
|
||||
List<Vector3> rotatedPoints = new List<Vector3>();
|
||||
for (var i = 0; i < offsets.Count; ++i)
|
||||
{
|
||||
Vector3 offset = offsets[i];
|
||||
float tempX = offset.X;
|
||||
float tempY = offset.Y;
|
||||
float tempZ = GetPositionZ();
|
||||
|
||||
offset.X = (tempX * angleCos - tempY * angleSin) + GetPositionX();
|
||||
offset.Y = (tempX * angleSin + tempY * angleCos) + GetPositionY();
|
||||
UpdateAllowedPositionZ(offset.X, offset.Y, ref tempZ);
|
||||
offset.Z += tempZ;
|
||||
|
||||
float x = GetPositionX() + (offset.X * angleCos - offset.Y * angleSin);
|
||||
float y = GetPositionY() + (offset.Y * angleCos + offset.X * angleSin);
|
||||
float z = GetPositionZ();
|
||||
|
||||
UpdateAllowedPositionZ(x, y, ref z);
|
||||
z += offset.Z;
|
||||
|
||||
rotatedPoints.Add(new Vector3(x, y, z));
|
||||
}
|
||||
|
||||
InitSplines(rotatedPoints, timeToTarget);
|
||||
}
|
||||
|
||||
void InitSplines(List<Vector3> splinePoints, uint timeToTarget)
|
||||
{
|
||||
if (splinePoints.Count < 2)
|
||||
return;
|
||||
|
||||
_movementTime = 0;
|
||||
|
||||
_spline.Init_Spline(splinePoints.ToArray(), splinePoints.Count, Spline.EvaluationMode.Linear);
|
||||
_spline.initLengths();
|
||||
|
||||
// should be sent in object create packets only
|
||||
UpdateData[(int)AreaTriggerFields.TimeToTarget] = timeToTarget;
|
||||
|
||||
if (IsInWorld)
|
||||
{
|
||||
if (_reachedDestination)
|
||||
{
|
||||
AreaTriggerReShape reshapeDest = new AreaTriggerReShape();
|
||||
reshapeDest.TriggerGUID = GetGUID();
|
||||
SendMessageToSet(reshapeDest, true);
|
||||
}
|
||||
|
||||
AreaTriggerReShape reshape = new AreaTriggerReShape();
|
||||
reshape.TriggerGUID = GetGUID();
|
||||
reshape.AreaTriggerSpline.HasValue = true;
|
||||
reshape.AreaTriggerSpline.Value.ElapsedTimeForMovement = GetElapsedTimeForMovement();
|
||||
reshape.AreaTriggerSpline.Value.TimeToTarget = timeToTarget;
|
||||
reshape.AreaTriggerSpline.Value.Points = splinePoints;
|
||||
SendMessageToSet(reshape, true);
|
||||
}
|
||||
|
||||
_reachedDestination = false;
|
||||
}
|
||||
|
||||
void UpdateSplinePosition(uint diff)
|
||||
{
|
||||
if (_reachedDestination)
|
||||
return;
|
||||
|
||||
if (!HasSplines())
|
||||
return;
|
||||
|
||||
_movementTime += diff;
|
||||
|
||||
if (_movementTime >= GetTimeToTarget())
|
||||
{
|
||||
_reachedDestination = true;
|
||||
_lastSplineIndex = _spline.last();
|
||||
|
||||
Vector3 lastSplinePosition = _spline.getPoint(_lastSplineIndex);
|
||||
GetMap().AreaTriggerRelocation(this, lastSplinePosition.X, lastSplinePosition.Y, lastSplinePosition.Z, GetOrientation());
|
||||
|
||||
DebugVisualizePosition();
|
||||
|
||||
_ai.OnSplineIndexReached(_lastSplineIndex);
|
||||
_ai.OnDestinationReached();
|
||||
return;
|
||||
}
|
||||
|
||||
float currentTimePercent = _movementTime / GetTimeToTarget();
|
||||
|
||||
if (currentTimePercent <= 0.0f)
|
||||
return;
|
||||
|
||||
if (GetMiscTemplate().MoveCurveId != 0)
|
||||
{
|
||||
float progress = Global.DB2Mgr.GetCurveValueAt(GetMiscTemplate().MoveCurveId, currentTimePercent);
|
||||
if (progress < 0.0f || progress > 1.0f)
|
||||
{
|
||||
Log.outError(LogFilter.AreaTrigger, "AreaTrigger (Id: {0}, SpellMiscId: {1}) has wrong progress ({2}) caused by curve calculation (MoveCurveId: {3})",
|
||||
GetTemplate().Id, GetMiscTemplate().MiscId, progress, GetMiscTemplate().MorphCurveId);
|
||||
}
|
||||
else
|
||||
currentTimePercent = progress;
|
||||
}
|
||||
|
||||
int lastPositionIndex = 0;
|
||||
float percentFromLastPoint = 0;
|
||||
_spline.computeIndex(currentTimePercent, ref lastPositionIndex, ref percentFromLastPoint);
|
||||
|
||||
Vector3 currentPosition;
|
||||
_spline.Evaluate_Percent(lastPositionIndex, percentFromLastPoint, out currentPosition);
|
||||
|
||||
float orientation = GetOrientation();
|
||||
if (GetTemplate().HasFlag(AreaTriggerFlags.HasFaceMovementDir))
|
||||
{
|
||||
Vector3 nextPoint = _spline.getPoint(lastPositionIndex + 1);
|
||||
orientation = GetAngle(nextPoint.X, nextPoint.Y);
|
||||
}
|
||||
|
||||
GetMap().AreaTriggerRelocation(this, currentPosition.X, currentPosition.Y, currentPosition.Z, orientation);
|
||||
|
||||
DebugVisualizePosition();
|
||||
|
||||
if (_lastSplineIndex != lastPositionIndex)
|
||||
{
|
||||
_lastSplineIndex = lastPositionIndex;
|
||||
_ai.OnSplineIndexReached(_lastSplineIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void AI_Initialize()
|
||||
{
|
||||
AI_Destroy();
|
||||
AreaTriggerAI ai = Global.ScriptMgr.GetAreaTriggerAI(this);
|
||||
if (ai == null)
|
||||
ai = new NullAreaTriggerAI(this);
|
||||
|
||||
_ai = ai;
|
||||
_ai.OnInitialize();
|
||||
}
|
||||
|
||||
void AI_Destroy()
|
||||
{
|
||||
_ai = null;
|
||||
}
|
||||
|
||||
AreaTriggerAI GetAI() { return _ai; }
|
||||
|
||||
[System.Diagnostics.Conditional("DEBUG")]
|
||||
void DebugVisualizePosition()
|
||||
{
|
||||
Unit caster = GetCaster();
|
||||
if (caster)
|
||||
{
|
||||
Player player = caster.ToPlayer();
|
||||
if (player)
|
||||
if (player.isDebugAreaTriggers)
|
||||
player.SummonCreature(1, this, TempSummonType.TimedDespawn, GetTimeToTarget());
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRemoved() { return _isRemoved; }
|
||||
public uint GetSpellId() { return GetUInt32Value(AreaTriggerFields.SpellId); }
|
||||
public AuraEffect GetAuraEffect() { return _aurEff; }
|
||||
public uint GetTimeSinceCreated() { return _timeSinceCreated; }
|
||||
public uint GetTimeToTarget() { return GetUInt32Value(AreaTriggerFields.TimeToTarget); }
|
||||
public uint GetTimeToTargetScale() { return GetUInt32Value(AreaTriggerFields.TimeToTargetScale); }
|
||||
public int GetDuration() { return _duration; }
|
||||
public int GetTotalDuration() { return _totalDuration; }
|
||||
|
||||
public void Delay(int delaytime) { SetDuration(GetDuration() - delaytime); }
|
||||
|
||||
public List<ObjectGuid> GetInsideUnits() { return _insideUnits; }
|
||||
|
||||
public AreaTriggerMiscTemplate GetMiscTemplate() { return _areaTriggerMiscTemplate; }
|
||||
|
||||
public ObjectGuid GetCasterGuid() { return GetGuidValue(AreaTriggerFields.Caster); }
|
||||
|
||||
public Vector3 GetRollPitchYaw() { return _rollPitchYaw; }
|
||||
public Vector3 GetTargetRollPitchYaw() { return _targetRollPitchYaw; }
|
||||
|
||||
public bool HasSplines() { return !_spline.empty(); }
|
||||
public Spline GetSpline() { return _spline; }
|
||||
public uint GetElapsedTimeForMovement() { return GetTimeSinceCreated(); } /// @todo: research the right value, in sniffs both timers are nearly identical
|
||||
|
||||
ObjectGuid _targetGuid;
|
||||
|
||||
AuraEffect _aurEff;
|
||||
|
||||
int _duration;
|
||||
int _totalDuration;
|
||||
uint _timeSinceCreated;
|
||||
float _previousCheckOrientation;
|
||||
bool _isRemoved;
|
||||
|
||||
Vector3 _rollPitchYaw;
|
||||
Vector3 _targetRollPitchYaw;
|
||||
List<Vector2> _polygonVertices;
|
||||
Spline _spline;
|
||||
|
||||
bool _reachedDestination;
|
||||
int _lastSplineIndex;
|
||||
uint _movementTime;
|
||||
|
||||
AreaTriggerMiscTemplate _areaTriggerMiscTemplate;
|
||||
List<ObjectGuid> _insideUnits = new List<ObjectGuid>();
|
||||
|
||||
AreaTriggerAI _ai;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.GameMath;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe class AreaTriggerData
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public defaultdatas DefaultDatas;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public spheredatas SphereDatas;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public boxdatas BoxDatas;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public polygondatas PolygonDatas;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public cylinderdatas CylinderDatas;
|
||||
|
||||
public struct defaultdatas
|
||||
{
|
||||
public fixed float Data[SharedConst.MaxAreatriggerEntityData];
|
||||
}
|
||||
|
||||
// AREATRIGGER_TYPE_SPHERE
|
||||
public struct spheredatas
|
||||
{
|
||||
public float Radius;
|
||||
public float RadiusTarget;
|
||||
}
|
||||
|
||||
// AREATRIGGER_TYPE_BOX
|
||||
public struct boxdatas
|
||||
{
|
||||
public fixed float Extents[3];
|
||||
public fixed float ExtentsTarget[3];
|
||||
}
|
||||
|
||||
// AREATRIGGER_TYPE_POLYGON
|
||||
public struct polygondatas
|
||||
{
|
||||
public float Height;
|
||||
public float HeightTarget;
|
||||
}
|
||||
|
||||
// AREATRIGGER_TYPE_CYLINDER
|
||||
public struct cylinderdatas
|
||||
{
|
||||
public float Radius;
|
||||
public float RadiusTarget;
|
||||
public float Height;
|
||||
public float HeightTarget;
|
||||
public float LocationZOffset;
|
||||
public float LocationZOffsetTarget;
|
||||
}
|
||||
}
|
||||
|
||||
public class AreaTriggerScaleInfo
|
||||
{
|
||||
public AreaTriggerScaleInfo()
|
||||
{
|
||||
OverrideScale = new OverrideScaleStruct[SharedConst.MaxAreatriggerScale];
|
||||
ExtraScale = new ExtraScaleStruct[SharedConst.MaxAreatriggerScale];
|
||||
|
||||
ExtraScale[5].AsFloat = 1.0000001f;
|
||||
ExtraScale[6].AsInt32 = 1;
|
||||
}
|
||||
|
||||
public OverrideScaleStruct[] OverrideScale;
|
||||
public ExtraScaleStruct[] ExtraScale;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct OverrideScaleStruct
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int AsInt32;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public float AsFloat;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ExtraScaleStruct
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int AsInt32;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public float AsFloat;
|
||||
}
|
||||
}
|
||||
|
||||
public class AreaTriggerTemplate : AreaTriggerData
|
||||
{
|
||||
public unsafe void InitMaxSearchRadius()
|
||||
{
|
||||
switch (TriggerType)
|
||||
{
|
||||
case AreaTriggerTypes.Sphere:
|
||||
{
|
||||
MaxSearchRadius = Math.Max(SphereDatas.Radius, SphereDatas.RadiusTarget);
|
||||
break;
|
||||
}
|
||||
case AreaTriggerTypes.Box:
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (float* ptr = BoxDatas.Extents)
|
||||
{
|
||||
MaxSearchRadius = (float)Math.Sqrt(ptr[0] * ptr[0] / 4 + ptr[1] * ptr[1] / 4);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AreaTriggerTypes.Polygon:
|
||||
{
|
||||
if (PolygonDatas.Height <= 0.0f)
|
||||
PolygonDatas.Height = 1.0f;
|
||||
|
||||
foreach (Vector2 vertice in PolygonVertices)
|
||||
{
|
||||
float pointDist = vertice.GetLength();
|
||||
|
||||
if (pointDist > MaxSearchRadius)
|
||||
MaxSearchRadius = pointDist;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case AreaTriggerTypes.Cylinder:
|
||||
{
|
||||
MaxSearchRadius = CylinderDatas.Radius;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasFlag(AreaTriggerFlags flag) { return Flags.HasAnyFlag(flag); }
|
||||
|
||||
public bool IsSphere() { return TriggerType == AreaTriggerTypes.Sphere; }
|
||||
public bool IsBox() { return TriggerType == AreaTriggerTypes.Box; }
|
||||
public bool IsPolygon() { return TriggerType == AreaTriggerTypes.Polygon; }
|
||||
public bool IsCylinder() { return TriggerType == AreaTriggerTypes.Cylinder; }
|
||||
|
||||
public uint Id;
|
||||
public AreaTriggerTypes TriggerType;
|
||||
public AreaTriggerFlags Flags;
|
||||
public uint ScriptId;
|
||||
public float MaxSearchRadius;
|
||||
|
||||
public List<Vector2> PolygonVertices = new List<Vector2>();
|
||||
public List<Vector2> PolygonVerticesTarget = new List<Vector2>();
|
||||
public List<AreaTriggerAction> Actions = new List<AreaTriggerAction>();
|
||||
}
|
||||
|
||||
public class AreaTriggerMiscTemplate
|
||||
{
|
||||
public bool HasSplines() { return SplinePoints.Count >= 2; }
|
||||
|
||||
public uint MiscId;
|
||||
public uint AreaTriggerEntry;
|
||||
|
||||
public uint MoveCurveId;
|
||||
public uint ScaleCurveId;
|
||||
public uint MorphCurveId;
|
||||
public uint FacingCurveId;
|
||||
|
||||
public uint DecalPropertiesId;
|
||||
|
||||
public uint TimeToTarget;
|
||||
public uint TimeToTargetScale;
|
||||
|
||||
public AreaTriggerScaleInfo ScaleInfo = new AreaTriggerScaleInfo();
|
||||
|
||||
public AreaTriggerTemplate Template;
|
||||
public List<Vector3> SplinePoints = new List<Vector3>();
|
||||
}
|
||||
|
||||
public struct AreaTriggerAction
|
||||
{
|
||||
public uint Param;
|
||||
public AreaTriggerActionTypes ActionType;
|
||||
public AreaTriggerActionUserTypes TargetType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Maps;
|
||||
using Game.Spells;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class Conversation : WorldObject
|
||||
{
|
||||
public Conversation() : base(false)
|
||||
{
|
||||
_duration = 0;
|
||||
|
||||
objectTypeMask |= TypeMask.Conversation;
|
||||
objectTypeId = TypeId.Conversation;
|
||||
|
||||
m_updateFlag = UpdateFlag.StationaryPosition;
|
||||
|
||||
valuesCount = (int)ConversationFields.End;
|
||||
_dynamicValuesCount = (int)ConversationDynamicFields.End;
|
||||
}
|
||||
|
||||
public override void AddToWorld()
|
||||
{
|
||||
///- Register the Conversation for guid lookup and for caster
|
||||
if (!IsInWorld)
|
||||
{
|
||||
GetMap().GetObjectsStore().Add(GetGUID(), this);
|
||||
base.AddToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveFromWorld()
|
||||
{
|
||||
///- Remove the Conversation from the accessor and from all lists of objects in world
|
||||
if (IsInWorld)
|
||||
{
|
||||
base.RemoveFromWorld();
|
||||
GetMap().GetObjectsStore().Remove(GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsNeverVisibleFor(WorldObject seer)
|
||||
{
|
||||
if (!_participants.Contains(seer.GetGUID()))
|
||||
return true;
|
||||
|
||||
return base.IsNeverVisibleFor(seer);
|
||||
}
|
||||
|
||||
public override void Update(uint diff)
|
||||
{
|
||||
if (GetDuration() > diff)
|
||||
_duration -= diff;
|
||||
else
|
||||
Remove(); // expired
|
||||
|
||||
base.Update(diff);
|
||||
}
|
||||
|
||||
void Remove()
|
||||
{
|
||||
if (IsInWorld)
|
||||
{
|
||||
AddObjectToRemoveList(); // calls RemoveFromWorld
|
||||
}
|
||||
}
|
||||
|
||||
public static Conversation CreateConversation(uint conversationEntry, Unit creator, Position pos, List<ObjectGuid> participants, SpellInfo spellInfo = null)
|
||||
{
|
||||
ConversationTemplate conversationTemplate = Global.ConversationDataStorage.GetConversationTemplate(conversationEntry);
|
||||
if (conversationTemplate == null)
|
||||
return null;
|
||||
|
||||
ulong lowGuid = creator.GetMap().GenerateLowGuid(HighGuid.Conversation);
|
||||
|
||||
Conversation conversation = new Conversation();
|
||||
if (!conversation.Create(lowGuid, conversationEntry, creator.GetMap(), creator, pos, participants, spellInfo))
|
||||
return null;
|
||||
|
||||
return conversation;
|
||||
}
|
||||
|
||||
bool Create(ulong lowGuid, uint conversationEntry, Map map, Unit creator, Position pos, List<ObjectGuid> participants, SpellInfo spellInfo = null)
|
||||
{
|
||||
ConversationTemplate conversationTemplate = Global.ConversationDataStorage.GetConversationTemplate(conversationEntry);
|
||||
//ASSERT(conversationTemplate);
|
||||
|
||||
_creatorGuid = creator.GetGUID();
|
||||
_participants = participants;
|
||||
|
||||
SetMap(map);
|
||||
Relocate(pos);
|
||||
|
||||
base._Create(ObjectGuid.Create(HighGuid.Conversation, GetMapId(), conversationEntry, lowGuid));
|
||||
SetPhaseMask(creator.GetPhaseMask(), false);
|
||||
CopyPhaseFrom(creator);
|
||||
|
||||
SetEntry(conversationEntry);
|
||||
SetObjectScale(1.0f);
|
||||
|
||||
SetUInt32Value(ConversationFields.LastLineEndTime, conversationTemplate.LastLineEndTime);
|
||||
_duration = conversationTemplate.LastLineEndTime;
|
||||
|
||||
ushort actorsIndex = 0;
|
||||
foreach (ConversationActorTemplate actor in conversationTemplate.Actors)
|
||||
{
|
||||
if (actor != null)
|
||||
{
|
||||
ConversationDynamicFieldActor actorField = new ConversationDynamicFieldActor();
|
||||
actorField.ActorTemplate = actor;
|
||||
actorField.Type = ConversationDynamicFieldActor.ActorType.CreatureActor;
|
||||
SetDynamicStructuredValue(ConversationDynamicFields.Actors, actorsIndex++, actorField);
|
||||
}
|
||||
else
|
||||
++actorsIndex;
|
||||
}
|
||||
|
||||
ushort linesIndex = 0;
|
||||
foreach (ConversationLineTemplate line in conversationTemplate.Lines)
|
||||
SetDynamicStructuredValue(ConversationDynamicFields.Lines, linesIndex++, line);
|
||||
|
||||
if (!GetMap().AddToMap(this))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddActor(ObjectGuid actorGuid, ushort actorIdx)
|
||||
{
|
||||
ConversationDynamicFieldActor actorField = new ConversationDynamicFieldActor();
|
||||
actorField.ActorGuid = actorGuid;
|
||||
actorField.Type = ConversationDynamicFieldActor.ActorType.WorldObjectActor;
|
||||
SetDynamicStructuredValue(ConversationDynamicFields.Actors, actorIdx, actorField);
|
||||
}
|
||||
|
||||
void AddParticipant(ObjectGuid participantGuid)
|
||||
{
|
||||
_participants.Add(participantGuid);
|
||||
}
|
||||
|
||||
uint GetDuration() { return _duration; }
|
||||
|
||||
public ObjectGuid GetCreatorGuid() { return _creatorGuid; }
|
||||
|
||||
public override float GetStationaryX() { return _stationaryPosition.GetPositionX(); }
|
||||
public override float GetStationaryY() { return _stationaryPosition.GetPositionY(); }
|
||||
public override float GetStationaryZ() { return _stationaryPosition.GetPositionZ(); }
|
||||
public override float GetStationaryO() { return _stationaryPosition.GetOrientation(); }
|
||||
void RelocateStationaryPosition(Position pos) { _stationaryPosition.Relocate(pos); }
|
||||
|
||||
Position _stationaryPosition = new Position();
|
||||
ObjectGuid _creatorGuid;
|
||||
uint _duration;
|
||||
List<ObjectGuid> _participants = new List<ObjectGuid>();
|
||||
}
|
||||
|
||||
class ConversationDynamicFieldActor
|
||||
{
|
||||
public enum ActorType
|
||||
{
|
||||
WorldObjectActor = 0,
|
||||
CreatureActor = 1
|
||||
}
|
||||
|
||||
public ObjectGuid ActorGuid;
|
||||
public ConversationActorTemplate ActorTemplate;
|
||||
|
||||
public ActorType Type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.DataStorage;
|
||||
using Game.Loots;
|
||||
using Game.Maps;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class Corpse : WorldObject
|
||||
{
|
||||
public Corpse(CorpseType type = CorpseType.Bones) : base(type != CorpseType.Bones)
|
||||
{
|
||||
m_type = type;
|
||||
objectTypeId = TypeId.Corpse;
|
||||
objectTypeMask |= TypeMask.Corpse;
|
||||
|
||||
m_updateFlag = UpdateFlag.StationaryPosition;
|
||||
|
||||
valuesCount = (int)CorpseFields.End;
|
||||
|
||||
m_time = Time.UnixTime;
|
||||
}
|
||||
|
||||
public override void AddToWorld()
|
||||
{
|
||||
// Register the corpse for guid lookup
|
||||
if (!IsInWorld)
|
||||
GetMap().GetObjectsStore().Add(GetGUID(), this);
|
||||
|
||||
base.AddToWorld();
|
||||
}
|
||||
|
||||
public override void RemoveFromWorld()
|
||||
{
|
||||
// Remove the corpse from the accessor
|
||||
if (IsInWorld)
|
||||
GetMap().GetObjectsStore().Remove(GetGUID());
|
||||
|
||||
base.RemoveFromWorld();
|
||||
}
|
||||
|
||||
public bool Create(ulong guidlow, Map map)
|
||||
{
|
||||
_Create(ObjectGuid.Create(HighGuid.Corpse, map.GetId(), 0, guidlow));
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Create(ulong guidlow, Player owner)
|
||||
{
|
||||
Contract.Assert(owner != null);
|
||||
|
||||
Relocate(owner.GetPositionX(), owner.GetPositionY(), owner.GetPositionZ(), owner.GetOrientation());
|
||||
|
||||
if (!IsPositionValid())
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Corpse (guidlow {0}, owner {1}) not created. Suggested coordinates isn't valid (X: {2} Y: {3})",
|
||||
guidlow, owner.GetName(), owner.GetPositionX(), owner.GetPositionY());
|
||||
return false;
|
||||
}
|
||||
|
||||
_Create(ObjectGuid.Create(HighGuid.Corpse, owner.GetMapId(), 0, guidlow));
|
||||
SetPhaseMask(owner.GetPhaseMask(), false);
|
||||
|
||||
SetObjectScale(1);
|
||||
SetGuidValue(CorpseFields.Owner, owner.GetGUID());
|
||||
|
||||
_cellCoord = GridDefines.ComputeCellCoord(GetPositionX(), GetPositionY());
|
||||
|
||||
CopyPhaseFrom(owner);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SaveToDB()
|
||||
{
|
||||
// prevent DB data inconsistence problems and duplicates
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
DeleteFromDB(trans);
|
||||
|
||||
byte index = 0;
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CORPSE);
|
||||
stmt.AddValue(index++, GetOwnerGUID().GetCounter()); // guid
|
||||
stmt.AddValue(index++, GetPositionX()); // posX
|
||||
stmt.AddValue(index++, GetPositionY()); // posY
|
||||
stmt.AddValue(index++, GetPositionZ()); // posZ
|
||||
stmt.AddValue(index++, GetOrientation()); // orientation
|
||||
stmt.AddValue(index++, GetMapId()); // mapId
|
||||
stmt.AddValue(index++, GetUInt32Value(CorpseFields.DisplayId)); // displayId
|
||||
stmt.AddValue(index++, _ConcatFields(CorpseFields.Item, EquipmentSlot.End)); // itemCache
|
||||
stmt.AddValue(index++, GetUInt32Value(CorpseFields.Bytes1)); // bytes1
|
||||
stmt.AddValue(index++, GetUInt32Value(CorpseFields.Bytes2)); // bytes2
|
||||
stmt.AddValue(index++, GetUInt32Value(CorpseFields.Flags)); // flags
|
||||
stmt.AddValue(index++, GetUInt32Value(CorpseFields.DynamicFlags)); // dynFlags
|
||||
stmt.AddValue(index++, (uint)m_time); // time
|
||||
stmt.AddValue(index++, (uint)GetCorpseType()); // corpseType
|
||||
stmt.AddValue(index++, GetInstanceId()); // instanceId
|
||||
trans.Append(stmt);
|
||||
|
||||
foreach (uint phaseId in GetPhases())
|
||||
{
|
||||
index = 0;
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CORPSE_PHASES);
|
||||
stmt.AddValue(index++, GetOwnerGUID().GetCounter()); // OwnerGuid
|
||||
stmt.AddValue(index++, phaseId); // PhaseId
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFromDB(SQLTransaction trans)
|
||||
{
|
||||
DeleteFromDB(GetOwnerGUID(), trans);
|
||||
}
|
||||
|
||||
public static void DeleteFromDB(ObjectGuid ownerGuid, SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CORPSE);
|
||||
stmt.AddValue(0, ownerGuid.GetCounter());
|
||||
DB.Characters.ExecuteOrAppend(trans, stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CORPSE_PHASES);
|
||||
stmt.AddValue(0, ownerGuid.GetCounter());
|
||||
DB.Characters.ExecuteOrAppend(trans, stmt);
|
||||
}
|
||||
|
||||
public bool LoadCorpseFromDB(ulong guid, SQLFields field)
|
||||
{
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||
// SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, flags, dynFlags, time, corpseType, instanceId, guid FROM corpse WHERE mapId = ? AND instanceId = ?
|
||||
|
||||
float posX = field.Read<float>(0);
|
||||
float posY = field.Read<float>(1);
|
||||
float posZ = field.Read<float>(2);
|
||||
float o = field.Read<float>(3);
|
||||
ushort mapId = field.Read<ushort>(4);
|
||||
|
||||
_Create(ObjectGuid.Create(HighGuid.Corpse, mapId, 0, guid));
|
||||
|
||||
SetObjectScale(1.0f);
|
||||
SetUInt32Value(CorpseFields.DisplayId, field.Read<uint>(5));
|
||||
_LoadIntoDataField(field.Read<string>(6), (int)CorpseFields.Item, EquipmentSlot.End);
|
||||
SetUInt32Value(CorpseFields.Bytes1, field.Read<uint>(7));
|
||||
SetUInt32Value(CorpseFields.Bytes2, field.Read<uint>(8));
|
||||
SetUInt32Value(CorpseFields.Flags, field.Read<byte>(9));
|
||||
SetUInt32Value(CorpseFields.DynamicFlags, field.Read<byte>(10));
|
||||
SetGuidValue(CorpseFields.Owner, ObjectGuid.Create(HighGuid.Player, field.Read<ulong>(14)));
|
||||
CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(GetGuidValue(CorpseFields.Owner));
|
||||
if (characterInfo != null)
|
||||
SetUInt32Value(CorpseFields.FactionTemplate, CliDB.ChrRacesStorage.LookupByKey(characterInfo.RaceID).FactionID);
|
||||
|
||||
m_time = field.Read<uint>(11);
|
||||
|
||||
uint instanceId = field.Read<uint>(13);
|
||||
|
||||
// place
|
||||
SetLocationInstanceId(instanceId);
|
||||
SetMapId(mapId);
|
||||
Relocate(posX, posY, posZ, o);
|
||||
|
||||
if (!IsPositionValid())
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Corpse ({0}, owner: {1}) is not created, given coordinates are not valid (X: {2}, Y: {3}, Z: {4})",
|
||||
GetGUID().ToString(), GetOwnerGUID().ToString(), posX, posY, posZ);
|
||||
return false;
|
||||
}
|
||||
|
||||
_cellCoord = GridDefines.ComputeCellCoord(GetPositionX(), GetPositionY());
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsExpired(long t)
|
||||
{
|
||||
// Deleted character
|
||||
if (Global.WorldMgr.GetCharacterInfo(GetOwnerGUID()) == null)
|
||||
return true;
|
||||
|
||||
if (m_type == CorpseType.Bones)
|
||||
return m_time < t - 60 * Time.Minute;
|
||||
else
|
||||
return m_time < t - 3 * Time.Day;
|
||||
}
|
||||
|
||||
public ObjectGuid GetOwnerGUID() { return GetGuidValue(CorpseFields.Owner); }
|
||||
|
||||
public long GetGhostTime() { return m_time; }
|
||||
public void ResetGhostTime() { m_time = Time.UnixTime; }
|
||||
public CorpseType GetCorpseType() { return m_type; }
|
||||
|
||||
public CellCoord GetCellCoord() { return _cellCoord; }
|
||||
public void SetCellCoord(CellCoord cellCoord) { _cellCoord = cellCoord; }
|
||||
|
||||
public Loot loot = new Loot();
|
||||
public Player lootRecipient;
|
||||
public bool lootForBody;
|
||||
|
||||
CorpseType m_type;
|
||||
long m_time;
|
||||
CellCoord _cellCoord; // gride for corpse position for fast search
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Loots;
|
||||
using Game.Spells;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Creature
|
||||
{
|
||||
CreatureTemplate m_creatureInfo;
|
||||
CreatureData m_creatureData;
|
||||
|
||||
Spell _focusSpell; // Locks the target during spell cast for proper facing
|
||||
uint _focusDelay;
|
||||
bool m_shouldReacquireTarget;
|
||||
ObjectGuid m_suppressedTarget; // Stores the creature's "real" target while casting
|
||||
float m_suppressedOrientation; // Stores the creature's "real" orientation while casting
|
||||
|
||||
MultiMap<byte, byte> m_textRepeat = new MultiMap<byte, byte>();
|
||||
|
||||
public ulong m_PlayerDamageReq;
|
||||
public float m_SightDistance;
|
||||
public float m_CombatDistance;
|
||||
public bool m_isTempWorldObject; //true when possessed
|
||||
|
||||
ReactStates reactState; // for AI, not charmInfo
|
||||
public MovementGeneratorType m_defaultMovementType { get; set; }
|
||||
public ulong m_spawnId;
|
||||
byte m_equipmentId;
|
||||
sbyte m_originalEquipmentId; // can be -1
|
||||
|
||||
bool m_AlreadyCallAssistance;
|
||||
bool m_AlreadySearchedAssistance;
|
||||
bool m_regenHealth;
|
||||
bool m_cannotReachTarget;
|
||||
uint m_cannotReachTimer;
|
||||
bool m_AI_locked;
|
||||
|
||||
SpellSchoolMask m_meleeDamageSchoolMask;
|
||||
public uint m_originalEntry;
|
||||
|
||||
Position m_homePosition;
|
||||
Position m_transportHomePosition = new Position();
|
||||
|
||||
bool DisableReputationGain;
|
||||
|
||||
LootModes m_LootMode; // Bitmask (default: LOOT_MODE_DEFAULT) that determines what loot will be lootable
|
||||
|
||||
//WaypointMovementGenerator vars
|
||||
uint m_waypointID;
|
||||
uint m_path_id;
|
||||
|
||||
//Formation var
|
||||
CreatureGroup m_formation;
|
||||
bool TriggerJustRespawned;
|
||||
|
||||
public uint[] m_spells = new uint[SharedConst.MaxCreatureSpells];
|
||||
|
||||
// Timers
|
||||
long _pickpocketLootRestore;
|
||||
public long m_corpseRemoveTime; // (msecs)timer for death or corpse disappearance
|
||||
long m_respawnTime; // (secs) time of next respawn
|
||||
uint m_respawnDelay; // (secs) delay between corpse disappearance and respawning
|
||||
uint m_corpseDelay; // (secs) delay between death and corpse disappearance
|
||||
float m_respawnradius;
|
||||
uint m_boundaryCheckTime; // (msecs) remaining time for next evade boundary check
|
||||
uint m_combatPulseTime; // (msecs) remaining time for next zone-in-combat pulse
|
||||
uint m_combatPulseDelay; // (secs) how often the creature puts the entire zone in combat (only works in dungeons)
|
||||
|
||||
// vendor items
|
||||
List<VendorItemCount> m_vendorItemCounts = new List<VendorItemCount>();
|
||||
|
||||
public Loot loot = new Loot();
|
||||
public uint m_groupLootTimer; // (msecs)timer used for group loot
|
||||
public ObjectGuid lootingGroupLowGUID; // used to find group which is looting corpse
|
||||
ObjectGuid m_lootRecipient;
|
||||
ObjectGuid m_lootRecipientGroup;
|
||||
ObjectGuid _skinner;
|
||||
}
|
||||
|
||||
public enum ObjectCellMoveState
|
||||
{
|
||||
None, // not in move list
|
||||
Active, // in move list
|
||||
Inactive // in move list but should not move
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Collections;
|
||||
using Framework.Constants;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Framework.Dynamic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class CreatureTemplate
|
||||
{
|
||||
public uint Entry;
|
||||
public uint[] DifficultyEntry = new uint[SharedConst.MaxCreatureDifficulties];
|
||||
public uint[] KillCredit = new uint[SharedConst.MaxCreatureKillCredit];
|
||||
public uint ModelId1;
|
||||
public uint ModelId2;
|
||||
public uint ModelId3;
|
||||
public uint ModelId4;
|
||||
public string Name;
|
||||
public string FemaleName;
|
||||
public string SubName;
|
||||
public string IconName;
|
||||
public uint GossipMenuId;
|
||||
public short Minlevel;
|
||||
public Optional<CreatureLevelScaling> levelScaling;
|
||||
public short Maxlevel;
|
||||
public int HealthScalingExpansion;
|
||||
public uint RequiredExpansion;
|
||||
public uint VignetteID; // @todo Read Vignette.db2
|
||||
public uint Faction;
|
||||
public NPCFlags Npcflag;
|
||||
public float SpeedWalk;
|
||||
public float SpeedRun;
|
||||
public float Scale;
|
||||
public CreatureEliteType Rank;
|
||||
public uint DmgSchool;
|
||||
public uint BaseAttackTime;
|
||||
public uint RangeAttackTime;
|
||||
public float BaseVariance;
|
||||
public float RangeVariance;
|
||||
public uint UnitClass;
|
||||
public UnitFlags UnitFlags;
|
||||
public uint UnitFlags2;
|
||||
public uint UnitFlags3;
|
||||
public uint DynamicFlags;
|
||||
public CreatureFamily Family;
|
||||
public Class TrainerClass;
|
||||
public CreatureType CreatureType;
|
||||
public CreatureTypeFlags TypeFlags;
|
||||
public uint TypeFlags2;
|
||||
public uint LootId;
|
||||
public uint PickPocketId;
|
||||
public uint SkinLootId;
|
||||
public int[] Resistance = new int[7];
|
||||
public uint[] Spells = new uint[8];
|
||||
public uint VehicleId;
|
||||
public uint MinGold;
|
||||
public uint MaxGold;
|
||||
public string AIName;
|
||||
public uint MovementType;
|
||||
public InhabitType InhabitType;
|
||||
public float HoverHeight;
|
||||
public float ModHealth;
|
||||
public float ModHealthExtra;
|
||||
public float ModMana;
|
||||
public float ModManaExtra;
|
||||
public float ModArmor;
|
||||
public float ModDamage;
|
||||
public float ModExperience;
|
||||
public bool RacialLeader;
|
||||
public uint MovementId;
|
||||
public bool RegenHealth;
|
||||
public uint MechanicImmuneMask;
|
||||
public CreatureFlagsExtra FlagsExtra;
|
||||
public uint ScriptID;
|
||||
|
||||
public uint GetRandomValidModelId()
|
||||
{
|
||||
byte c = 0;
|
||||
uint[] modelIDs = new uint[4];
|
||||
|
||||
if (ModelId1 != 0)
|
||||
modelIDs[c++] = ModelId1;
|
||||
if (ModelId2 != 0)
|
||||
modelIDs[c++] = ModelId2;
|
||||
if (ModelId3 != 0)
|
||||
modelIDs[c++] = ModelId3;
|
||||
if (ModelId4 != 0)
|
||||
modelIDs[c++] = ModelId4;
|
||||
|
||||
return c > 0 ? modelIDs[RandomHelper.IRand(0, c - 1)] : 0;
|
||||
}
|
||||
public uint GetFirstValidModelId()
|
||||
{
|
||||
if (ModelId1 != 0)
|
||||
return ModelId1;
|
||||
if (ModelId2 != 0)
|
||||
return ModelId2;
|
||||
if (ModelId3 != 0)
|
||||
return ModelId3;
|
||||
if (ModelId4 != 0)
|
||||
return ModelId4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint GetFirstInvisibleModel()
|
||||
{
|
||||
CreatureModelInfo modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId1);
|
||||
if (modelInfo != null && modelInfo.IsTrigger)
|
||||
return ModelId1;
|
||||
|
||||
modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId2);
|
||||
if (modelInfo != null && modelInfo.IsTrigger)
|
||||
return ModelId2;
|
||||
|
||||
modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId3);
|
||||
if (modelInfo != null && modelInfo.IsTrigger)
|
||||
return ModelId3;
|
||||
|
||||
modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId4);
|
||||
if (modelInfo != null && modelInfo.IsTrigger)
|
||||
return ModelId4;
|
||||
|
||||
return 11686;
|
||||
}
|
||||
|
||||
public uint GetFirstVisibleModel()
|
||||
{
|
||||
CreatureModelInfo modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId1);
|
||||
if (modelInfo != null && !modelInfo.IsTrigger)
|
||||
return ModelId1;
|
||||
|
||||
modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId2);
|
||||
if (modelInfo != null && !modelInfo.IsTrigger)
|
||||
return ModelId2;
|
||||
|
||||
modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId3);
|
||||
if (modelInfo != null && !modelInfo.IsTrigger)
|
||||
return ModelId3;
|
||||
|
||||
modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId4);
|
||||
if (modelInfo != null && !modelInfo.IsTrigger)
|
||||
return ModelId4;
|
||||
|
||||
return 17519;
|
||||
}
|
||||
|
||||
public SkillType GetRequiredLootSkill()
|
||||
{
|
||||
if (TypeFlags.HasAnyFlag(CreatureTypeFlags.HerbSkinningSkill))
|
||||
return SkillType.Herbalism;
|
||||
else if (TypeFlags.HasAnyFlag(CreatureTypeFlags.MiningSkinningSkill))
|
||||
return SkillType.Mining;
|
||||
else if (TypeFlags.HasAnyFlag(CreatureTypeFlags.EngineeringSkinningSkill))
|
||||
return SkillType.Engineering;
|
||||
else
|
||||
return SkillType.Skinning; // normal case
|
||||
}
|
||||
|
||||
public bool IsExotic()
|
||||
{
|
||||
return (TypeFlags & CreatureTypeFlags.ExoticPet) != 0;
|
||||
}
|
||||
public bool IsTameable(bool canTameExotic)
|
||||
{
|
||||
if (CreatureType != CreatureType.Beast || Family == CreatureFamily.None || !TypeFlags.HasAnyFlag(CreatureTypeFlags.TameablePet))
|
||||
return false;
|
||||
|
||||
// if can tame exotic then can tame any tameable
|
||||
return canTameExotic || !IsExotic();
|
||||
}
|
||||
|
||||
public static int DifficultyIDToDifficultyEntryIndex(uint difficulty)
|
||||
{
|
||||
switch ((Difficulty)difficulty)
|
||||
{
|
||||
case Difficulty.None:
|
||||
case Difficulty.Normal:
|
||||
case Difficulty.Raid10N:
|
||||
case Difficulty.Raid40:
|
||||
case Difficulty.Scenario3ManN:
|
||||
case Difficulty.NormalRaid:
|
||||
return -1;
|
||||
case Difficulty.Heroic:
|
||||
case Difficulty.Raid25N:
|
||||
case Difficulty.Scenario3ManHC:
|
||||
case Difficulty.HeroicRaid:
|
||||
return 0;
|
||||
case Difficulty.Raid10HC:
|
||||
case Difficulty.MythicKeystone:
|
||||
case Difficulty.MythicRaid:
|
||||
return 1;
|
||||
case Difficulty.Raid25HC:
|
||||
return 2;
|
||||
case Difficulty.LFR:
|
||||
case Difficulty.LFRNew:
|
||||
case Difficulty.EventRaid:
|
||||
case Difficulty.EventDungeon:
|
||||
case Difficulty.EventScenario:
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CreatureBaseStats
|
||||
{
|
||||
public uint[] BaseHealth = new uint[(int)Expansion.Max];
|
||||
public uint BaseMana;
|
||||
public uint BaseArmor;
|
||||
public uint AttackPower;
|
||||
public uint RangedAttackPower;
|
||||
public float[] BaseDamage = new float[(int)Expansion.Max];
|
||||
|
||||
// Helpers
|
||||
public uint GenerateHealth(CreatureTemplate info)
|
||||
{
|
||||
return (uint)Math.Ceiling(BaseHealth[info.HealthScalingExpansion] * info.ModHealth * info.ModHealthExtra);
|
||||
}
|
||||
|
||||
public uint GenerateMana(CreatureTemplate info)
|
||||
{
|
||||
// Mana can be 0.
|
||||
if (BaseMana == 0)
|
||||
return 0;
|
||||
|
||||
return (uint)Math.Ceiling(BaseMana * info.ModMana * info.ModManaExtra);
|
||||
}
|
||||
|
||||
public float GenerateArmor(CreatureTemplate info)
|
||||
{
|
||||
return (float)Math.Ceiling(BaseArmor * info.ModArmor);
|
||||
}
|
||||
|
||||
public float GenerateBaseDamage(CreatureTemplate info)
|
||||
{
|
||||
return BaseDamage[info.HealthScalingExpansion];
|
||||
}
|
||||
}
|
||||
|
||||
public class CreatureLocale
|
||||
{
|
||||
public StringArray Name = new StringArray((int)LocaleConstant.Total);
|
||||
public StringArray NameAlt = new StringArray((int)LocaleConstant.Total);
|
||||
public StringArray Title = new StringArray((int)LocaleConstant.Total);
|
||||
public StringArray TitleAlt = new StringArray((int)LocaleConstant.Total);
|
||||
}
|
||||
|
||||
public struct EquipmentItem
|
||||
{
|
||||
public uint ItemId;
|
||||
public ushort AppearanceModId;
|
||||
public ushort ItemVisual;
|
||||
}
|
||||
|
||||
public class EquipmentInfo
|
||||
{
|
||||
public EquipmentItem[] Items = new EquipmentItem[SharedConst.MaxEquipmentItems];
|
||||
}
|
||||
|
||||
public class CreatureData
|
||||
{
|
||||
public uint id; // entry in creature_template
|
||||
public ushort mapid;
|
||||
public uint phaseMask;
|
||||
public uint displayid;
|
||||
public int equipmentId;
|
||||
public float posX;
|
||||
public float posY;
|
||||
public float posZ;
|
||||
public float orientation;
|
||||
public uint spawntimesecs;
|
||||
public float spawndist;
|
||||
public uint currentwaypoint;
|
||||
public uint curhealth;
|
||||
public uint curmana;
|
||||
public byte movementType;
|
||||
public uint spawnMask;
|
||||
public ulong npcflag;
|
||||
public uint unit_flags; // enum UnitFlags mask values
|
||||
public uint unit_flags2; // enum UnitFlags2 mask values
|
||||
public uint unit_flags3; // enum UnitFlags3 mask values
|
||||
public uint dynamicflags;
|
||||
public uint phaseid;
|
||||
public uint phaseGroup;
|
||||
public uint ScriptId;
|
||||
public bool dbData;
|
||||
}
|
||||
|
||||
public class CreatureModelInfo
|
||||
{
|
||||
public float BoundingRadius;
|
||||
public float CombatReach;
|
||||
public sbyte gender;
|
||||
public uint DisplayIdOtherGender;
|
||||
public bool IsTrigger;
|
||||
}
|
||||
|
||||
public class CreatureAddon
|
||||
{
|
||||
public uint path_id;
|
||||
public uint mount;
|
||||
public uint bytes1;
|
||||
public uint bytes2;
|
||||
public uint emote;
|
||||
public ushort aiAnimKit;
|
||||
public ushort movementAnimKit;
|
||||
public ushort meleeAnimKit;
|
||||
public uint[] auras;
|
||||
}
|
||||
|
||||
public class VendorItem
|
||||
{
|
||||
public VendorItem() { }
|
||||
public VendorItem(uint _item, int _maxcount, uint _incrtime, uint _ExtendedCost, ItemVendorType _Type)
|
||||
{
|
||||
item = _item;
|
||||
maxcount = (uint)_maxcount;
|
||||
incrtime = _incrtime;
|
||||
ExtendedCost = _ExtendedCost;
|
||||
Type = _Type;
|
||||
}
|
||||
|
||||
public uint item;
|
||||
public uint maxcount; // 0 for infinity item amount
|
||||
public uint incrtime; // time for restore items amount if maxcount != 0
|
||||
public uint ExtendedCost;
|
||||
public ItemVendorType Type;
|
||||
public List<uint> BonusListIDs = new List<uint>();
|
||||
public uint PlayerConditionId;
|
||||
public bool IgnoreFiltering;
|
||||
|
||||
//helpers
|
||||
public bool IsGoldRequired(ItemTemplate pProto) { return Convert.ToBoolean(pProto.GetFlags2() & ItemFlags2.DontIgnoreBuyPrice) || ExtendedCost == 0; }
|
||||
}
|
||||
|
||||
public class VendorItemData
|
||||
{
|
||||
List<VendorItem> m_items = new List<VendorItem>();
|
||||
|
||||
public VendorItem GetItem(uint slot)
|
||||
{
|
||||
if (slot >= m_items.Count)
|
||||
return null;
|
||||
|
||||
return m_items[(int)slot];
|
||||
}
|
||||
public bool Empty()
|
||||
{
|
||||
return m_items.Count == 0;
|
||||
}
|
||||
public int GetItemCount()
|
||||
{
|
||||
return m_items.Count;
|
||||
}
|
||||
public void AddItem(VendorItem vItem)
|
||||
{
|
||||
m_items.Add(vItem);
|
||||
}
|
||||
public bool RemoveItem(uint item_id, ItemVendorType type)
|
||||
{
|
||||
int i = m_items.RemoveAll(p => p.item == item_id && p.Type == type);
|
||||
if (i == 0)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
public VendorItem FindItemCostPair(uint item_id, uint extendedCost, ItemVendorType type)
|
||||
{
|
||||
return m_items.Find(p => p.item == item_id && p.ExtendedCost == extendedCost && p.Type == type);
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
m_items.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public struct CreatureLevelScaling
|
||||
{
|
||||
public ushort MinLevel;
|
||||
public ushort MaxLevel;
|
||||
public short DeltaLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Database;
|
||||
using Game.Maps;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Framework.Constants;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class FormationMgr
|
||||
{
|
||||
public FormationMgr() { }
|
||||
|
||||
public static void AddCreatureToGroup(uint groupId, Creature member)
|
||||
{
|
||||
Map map = member.GetMap();
|
||||
if (!map)
|
||||
return;
|
||||
|
||||
var creatureGroup = map.CreatureGroupHolder.LookupByKey(groupId);
|
||||
|
||||
//Add member to an existing group
|
||||
if (creatureGroup != null)
|
||||
{
|
||||
Log.outDebug(LogFilter.Unit, "Group found: {0}, inserting creature GUID: {1}, Group InstanceID {2}", groupId, member.GetGUID().ToString(), member.GetInstanceId());
|
||||
creatureGroup.AddMember(member);
|
||||
}
|
||||
//Create new group
|
||||
else
|
||||
{
|
||||
Log.outDebug(LogFilter.Unit, "Group not found: {0}. Creating new group.", groupId);
|
||||
CreatureGroup group = new CreatureGroup(groupId);
|
||||
map.CreatureGroupHolder[groupId] = group;
|
||||
group.AddMember(member);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveCreatureFromGroup(CreatureGroup group, Creature member)
|
||||
{
|
||||
Log.outDebug(LogFilter.Unit, "Deleting member GUID: {0} from group {1}", group.GetId(), member.GetSpawnId());
|
||||
group.RemoveMember(member);
|
||||
|
||||
if (group.isEmpty())
|
||||
{
|
||||
Map map = member.GetMap();
|
||||
if (!map)
|
||||
return;
|
||||
|
||||
Log.outDebug(LogFilter.Unit, "Deleting group with InstanceID {0}", member.GetInstanceId());
|
||||
map.CreatureGroupHolder.Remove(group.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadCreatureFormations()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
CreatureGroupMap.Clear();
|
||||
|
||||
//Get group data
|
||||
SQLResult result = DB.World.Query("SELECT leaderGUID, memberGUID, dist, angle, groupAI, point_1, point_2 FROM creature_formations ORDER BY leaderGUID");
|
||||
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outError(LogFilter.ServerLoading, "Loaded 0 creatures in formations. DB table `creature_formations` is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
FormationInfo group_member;
|
||||
do
|
||||
{
|
||||
//Load group member data
|
||||
group_member = new FormationInfo();
|
||||
group_member.leaderGUID = result.Read<uint>(0);
|
||||
uint memberGUID = result.Read<uint>(1);
|
||||
group_member.groupAI = result.Read<uint>(4);
|
||||
group_member.point_1 = result.Read<ushort>(5);
|
||||
group_member.point_2 = result.Read<ushort>(6);
|
||||
//If creature is group leader we may skip loading of dist/angle
|
||||
if (group_member.leaderGUID != memberGUID)
|
||||
{
|
||||
group_member.follow_dist = result.Read<float>(2);
|
||||
group_member.follow_angle = result.Read<float>(3) * MathFunctions.PI / 180;
|
||||
}
|
||||
else
|
||||
{
|
||||
group_member.follow_dist = 0;
|
||||
group_member.follow_angle = 0;
|
||||
}
|
||||
|
||||
// check data correctness
|
||||
{
|
||||
if (Global.ObjectMgr.GetCreatureData(group_member.leaderGUID) == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "creature_formations table leader guid {0} incorrect (not exist)", group_member.leaderGUID);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Global.ObjectMgr.GetCreatureData(memberGUID) == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "creature_formations table member guid {0} incorrect (not exist)", memberGUID);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
CreatureGroupMap[memberGUID] = group_member;
|
||||
++count;
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creatures in formations in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
public static Dictionary<ulong, FormationInfo> CreatureGroupMap = new Dictionary<ulong, FormationInfo>();
|
||||
}
|
||||
|
||||
public class FormationInfo
|
||||
{
|
||||
public uint leaderGUID;
|
||||
public float follow_dist;
|
||||
public float follow_angle;
|
||||
public uint groupAI;
|
||||
public ushort point_1;
|
||||
public ushort point_2;
|
||||
}
|
||||
|
||||
public class CreatureGroup
|
||||
{
|
||||
public CreatureGroup(uint id)
|
||||
{
|
||||
m_groupID = id;
|
||||
}
|
||||
|
||||
public void AddMember(Creature member)
|
||||
{
|
||||
Log.outDebug(LogFilter.Unit, "CreatureGroup.AddMember: Adding unit GUID: {0}.", member.GetGUID().ToString());
|
||||
|
||||
//Check if it is a leader
|
||||
if (member.GetSpawnId() == m_groupID)
|
||||
{
|
||||
Log.outDebug(LogFilter.Unit, "Unit GUID: {0} is formation leader. Adding group.", member.GetGUID().ToString());
|
||||
m_leader = member;
|
||||
}
|
||||
|
||||
m_members[member] = FormationMgr.CreatureGroupMap.LookupByKey(member.GetSpawnId());
|
||||
member.SetFormation(this);
|
||||
}
|
||||
|
||||
public void RemoveMember(Creature member)
|
||||
{
|
||||
if (m_leader == member)
|
||||
m_leader = null;
|
||||
|
||||
m_members.Remove(member);
|
||||
member.SetFormation(null);
|
||||
}
|
||||
|
||||
public void MemberAttackStart(Creature member, Unit target)
|
||||
{
|
||||
GroupAIFlags groupAI = (GroupAIFlags)FormationMgr.CreatureGroupMap[member.GetSpawnId()].groupAI;
|
||||
if (groupAI == 0)
|
||||
return;
|
||||
|
||||
if (member == m_leader)
|
||||
{
|
||||
if (!groupAI.HasAnyFlag(GroupAIFlags.MembersAssistLeader))
|
||||
return;
|
||||
}
|
||||
else if (!groupAI.HasAnyFlag(GroupAIFlags.LeaderAssistsMember))
|
||||
return;
|
||||
|
||||
foreach (var pair in m_members)
|
||||
{
|
||||
if (m_leader) // avoid crash if leader was killed and reset.
|
||||
Log.outDebug(LogFilter.Unit, "GROUP ATTACK: group instance id {0} calls member instid {1}", m_leader.GetInstanceId(), member.GetInstanceId());
|
||||
|
||||
Creature other = pair.Key;
|
||||
|
||||
// Skip self
|
||||
if (other == member)
|
||||
continue;
|
||||
|
||||
if (!other.IsAlive())
|
||||
continue;
|
||||
|
||||
if (other.GetVictim())
|
||||
continue;
|
||||
|
||||
if (((other != m_leader && groupAI.HasAnyFlag(GroupAIFlags.MembersAssistLeader)) || (other == m_leader && groupAI.HasAnyFlag(GroupAIFlags.LeaderAssistsMember))) && other.IsValidAttackTarget(target))
|
||||
other.GetAI().AttackStart(target);
|
||||
}
|
||||
}
|
||||
|
||||
public void FormationReset(bool dismiss)
|
||||
{
|
||||
foreach (var creature in m_members.Keys)
|
||||
{
|
||||
if (creature != m_leader && creature.IsAlive())
|
||||
{
|
||||
if (dismiss)
|
||||
creature.GetMotionMaster().Initialize();
|
||||
else
|
||||
creature.GetMotionMaster().MoveIdle();
|
||||
Log.outDebug(LogFilter.Unit, "Set {0} movement for member GUID: {1}", dismiss ? "default" : "idle", creature.GetGUID().ToString());
|
||||
}
|
||||
}
|
||||
m_Formed = !dismiss;
|
||||
}
|
||||
|
||||
public void LeaderMoveTo(float x, float y, float z)
|
||||
{
|
||||
//! To do: This should probably get its own movement generator or use WaypointMovementGenerator.
|
||||
//! If the leader's path is known, member's path can be plotted as well using formation offsets.
|
||||
if (!m_leader)
|
||||
return;
|
||||
|
||||
float pathangle = (float)Math.Atan2(m_leader.GetPositionY() - y, m_leader.GetPositionX() - x);
|
||||
|
||||
foreach (var pair in m_members)
|
||||
{
|
||||
Creature member = pair.Key;
|
||||
if (member == m_leader || !member.IsAlive() || member.GetVictim() || !pair.Value.groupAI.HasAnyFlag((uint)GroupAIFlags.IdleInFormation))
|
||||
continue;
|
||||
|
||||
if (pair.Value.point_1 != 0)
|
||||
if (m_leader.GetCurrentWaypointID() == pair.Value.point_1 - 1 || m_leader.GetCurrentWaypointID() == pair.Value.point_2 - 1)
|
||||
pair.Value.follow_angle = (float)Math.PI * 2 - pair.Value.follow_angle;
|
||||
|
||||
float angle = pair.Value.follow_angle;
|
||||
float dist = pair.Value.follow_dist;
|
||||
|
||||
float dx = x + (float)Math.Cos(angle + pathangle) * dist;
|
||||
float dy = y + (float)Math.Sin(angle + pathangle) * dist;
|
||||
float dz = z;
|
||||
|
||||
GridDefines.NormalizeMapCoord(ref dx);
|
||||
GridDefines.NormalizeMapCoord(ref dy);
|
||||
|
||||
if (!member.IsFlying())
|
||||
member.UpdateGroundPositionZ(dx, dy, ref dz);
|
||||
|
||||
if (member.IsWithinDist(m_leader, dist + 5.0f))
|
||||
member.SetUnitMovementFlags(m_leader.GetUnitMovementFlags());
|
||||
else
|
||||
member.SetWalk(false);
|
||||
|
||||
member.GetMotionMaster().MovePoint(0, dx, dy, dz);
|
||||
member.SetHomePosition(dx, dy, dz, pathangle);
|
||||
}
|
||||
}
|
||||
|
||||
public Creature getLeader() { return m_leader; }
|
||||
public uint GetId() { return m_groupID; }
|
||||
public bool isEmpty() { return m_members.Empty(); }
|
||||
public bool isFormed() { return m_Formed; }
|
||||
|
||||
Creature m_leader;
|
||||
Dictionary<Creature, FormationInfo> m_members = new Dictionary<Creature, FormationInfo>();
|
||||
|
||||
uint m_groupID;
|
||||
bool m_Formed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,882 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Collections;
|
||||
using Framework.Constants;
|
||||
using Framework.GameMath;
|
||||
using Game.Conditions;
|
||||
using Game.DataStorage;
|
||||
using Game.Entities;
|
||||
using Game.Network.Packets;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Misc
|
||||
{
|
||||
public class GossipMenu
|
||||
{
|
||||
public uint AddMenuItem(int optionIndex, GossipOptionIcon icon, string message, uint sender, uint action, string boxMessage, uint boxMoney, bool coded = false)
|
||||
{
|
||||
Contract.Assert(_menuItems.Count <= SharedConst.MaxGossipMenuItems);
|
||||
|
||||
// Find a free new id - script case
|
||||
if (optionIndex == -1)
|
||||
{
|
||||
optionIndex = 0;
|
||||
if (!_menuItems.Empty())
|
||||
{
|
||||
foreach (var item in _menuItems)
|
||||
{
|
||||
if (item.Key > optionIndex)
|
||||
break;
|
||||
|
||||
optionIndex = (int)item.Key + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GossipMenuItem menuItem = new GossipMenuItem();
|
||||
|
||||
menuItem.MenuItemIcon = (byte)icon;
|
||||
menuItem.Message = message;
|
||||
menuItem.IsCoded = coded;
|
||||
menuItem.Sender = sender;
|
||||
menuItem.OptionType = action;
|
||||
menuItem.BoxMessage = boxMessage;
|
||||
menuItem.BoxMoney = boxMoney;
|
||||
|
||||
_menuItems[(uint)optionIndex] = menuItem;
|
||||
return (uint)optionIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a localized gossip menu item from db by menu id and menu item id.
|
||||
/// </summary>
|
||||
/// <param name="menuId">menuId Gossip menu id.</param>
|
||||
/// <param name="menuItemId">menuItemId Gossip menu item id.</param>
|
||||
/// <param name="sender">sender Identifier of the current menu.</param>
|
||||
/// <param name="action">action Custom action given to OnGossipHello.</param>
|
||||
public void AddMenuItem(uint menuId, uint menuItemId, uint sender, uint action)
|
||||
{
|
||||
/// Find items for given menu id.
|
||||
var bounds = Global.ObjectMgr.GetGossipMenuItemsMapBounds(menuId);
|
||||
/// Return if there are none.
|
||||
if (bounds.Empty())
|
||||
return;
|
||||
|
||||
/// Iterate over each of them.
|
||||
foreach (var item in bounds)
|
||||
{
|
||||
// Find the one with the given menu item id.
|
||||
if (item.OptionIndex != menuItemId)
|
||||
continue;
|
||||
|
||||
/// Store texts for localization.
|
||||
string strOptionText = "", strBoxText = "";
|
||||
BroadcastTextRecord optionBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(item.OptionBroadcastTextId);
|
||||
BroadcastTextRecord boxBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(item.BoxBroadcastTextId);
|
||||
|
||||
// OptionText
|
||||
if (optionBroadcastText != null)
|
||||
strOptionText = Global.DB2Mgr.GetBroadcastTextValue(optionBroadcastText, GetLocale());
|
||||
else
|
||||
strOptionText = item.OptionText;
|
||||
|
||||
// BoxText
|
||||
if (boxBroadcastText != null)
|
||||
strBoxText = Global.DB2Mgr.GetBroadcastTextValue(boxBroadcastText, GetLocale());
|
||||
else
|
||||
strBoxText = item.BoxText;
|
||||
|
||||
// Check need of localization.
|
||||
if (GetLocale() != LocaleConstant.enUS)
|
||||
{
|
||||
if (optionBroadcastText == null)
|
||||
{
|
||||
// Find localizations from database.
|
||||
GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItemId);
|
||||
if (gossipMenuLocale != null)
|
||||
ObjectManager.GetLocaleString(gossipMenuLocale.OptionText, GetLocale(), ref strOptionText);
|
||||
}
|
||||
|
||||
if (boxBroadcastText == null)
|
||||
{
|
||||
// Find localizations from database.
|
||||
GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItemId);
|
||||
if (gossipMenuLocale != null)
|
||||
ObjectManager.GetLocaleString(gossipMenuLocale.BoxText, GetLocale(), ref strBoxText);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add menu item with existing method. Menu item id -1 is also used in ADD_GOSSIP_ITEM macro.
|
||||
uint optionIndex = AddMenuItem(-1, item.OptionIcon, strOptionText, sender, action, strBoxText, item.BoxMoney, item.BoxCoded);
|
||||
AddGossipMenuItemData(optionIndex, item.ActionMenuId, item.ActionPoiId, item.TrainerId);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddGossipMenuItemData(uint optionIndex, uint gossipActionMenuId, uint gossipActionPoi, uint trainerId)
|
||||
{
|
||||
GossipMenuItemData itemData = new GossipMenuItemData();
|
||||
|
||||
itemData.GossipActionMenuId = gossipActionMenuId;
|
||||
itemData.GossipActionPoi = gossipActionPoi;
|
||||
itemData.TrainerId = trainerId;
|
||||
|
||||
_menuItemData[optionIndex] = itemData;
|
||||
}
|
||||
|
||||
public uint GetMenuItemSender(uint menuItemId)
|
||||
{
|
||||
if (_menuItems.ContainsKey(menuItemId))
|
||||
return _menuItems.LookupByKey(menuItemId).Sender;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint GetMenuItemAction(uint menuItemId)
|
||||
{
|
||||
if (_menuItems.ContainsKey(menuItemId))
|
||||
return _menuItems.LookupByKey(menuItemId).OptionType;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool IsMenuItemCoded(uint menuItemId)
|
||||
{
|
||||
if (_menuItems.ContainsKey(menuItemId))
|
||||
return _menuItems.LookupByKey(menuItemId).IsCoded;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasMenuItemType(uint optionType)
|
||||
{
|
||||
foreach (var menuItemPair in _menuItems)
|
||||
if (menuItemPair.Value.OptionType == optionType)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ClearMenu()
|
||||
{
|
||||
_menuItems.Clear();
|
||||
_menuItemData.Clear();
|
||||
}
|
||||
|
||||
public void SetMenuId(uint menu_id) { _menuId = menu_id; }
|
||||
public uint GetMenuId() { return _menuId; }
|
||||
public void SetLocale(LocaleConstant locale) { _locale = locale; }
|
||||
LocaleConstant GetLocale() { return _locale; }
|
||||
|
||||
public int GetMenuItemCount()
|
||||
{
|
||||
return _menuItems.Count;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return _menuItems.Empty();
|
||||
}
|
||||
|
||||
public GossipMenuItem GetItem(uint id)
|
||||
{
|
||||
return _menuItems.LookupByKey(id);
|
||||
}
|
||||
|
||||
public GossipMenuItemData GetItemData(uint indexId)
|
||||
{
|
||||
return _menuItemData.LookupByKey(indexId);
|
||||
}
|
||||
|
||||
public Dictionary<uint, GossipMenuItem> GetMenuItems()
|
||||
{
|
||||
return _menuItems;
|
||||
}
|
||||
|
||||
Dictionary<uint, GossipMenuItem> _menuItems = new Dictionary<uint, GossipMenuItem>();
|
||||
Dictionary<uint, GossipMenuItemData> _menuItemData = new Dictionary<uint, GossipMenuItemData>();
|
||||
uint _menuId;
|
||||
LocaleConstant _locale;
|
||||
}
|
||||
|
||||
public class InteractionData
|
||||
{
|
||||
public void Reset()
|
||||
{
|
||||
SourceGuid.Clear();
|
||||
TrainerId = 0;
|
||||
}
|
||||
|
||||
public ObjectGuid SourceGuid;
|
||||
public uint TrainerId;
|
||||
}
|
||||
|
||||
public class PlayerMenu
|
||||
{
|
||||
public PlayerMenu(WorldSession session)
|
||||
{
|
||||
_session = session;
|
||||
if (_session != null)
|
||||
_gossipMenu.SetLocale(_session.GetSessionDbLocaleIndex());
|
||||
}
|
||||
|
||||
public void ClearMenus()
|
||||
{
|
||||
_gossipMenu.ClearMenu();
|
||||
_questMenu.ClearMenu();
|
||||
}
|
||||
|
||||
public void SendGossipMenu(uint titleTextId, ObjectGuid objectGUID)
|
||||
{
|
||||
_interactionData.Reset();
|
||||
_interactionData.SourceGuid = objectGUID;
|
||||
|
||||
GossipMessagePkt packet = new GossipMessagePkt();
|
||||
packet.GossipGUID = objectGUID;
|
||||
packet.GossipID = (int)_gossipMenu.GetMenuId();
|
||||
packet.TextID = (int)titleTextId;
|
||||
|
||||
uint count = 0;
|
||||
foreach (var pair in _gossipMenu.GetMenuItems())
|
||||
{
|
||||
ClientGossipOptions opt = new ClientGossipOptions();
|
||||
GossipMenuItem item = pair.Value;
|
||||
opt.ClientOption = (int)pair.Key;
|
||||
opt.OptionNPC = item.MenuItemIcon;
|
||||
opt.OptionFlags = (byte)(item.IsCoded ? 1 : 0); // makes pop up box password
|
||||
opt.OptionCost = (int)item.BoxMoney; // money required to open menu, 2.0.3
|
||||
opt.Text = item.Message; // text for gossip item
|
||||
opt.Confirm = item.BoxMessage; // accept text (related to money) pop up box, 2.0.3
|
||||
packet.GossipOptions.Add(opt);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (byte i = 0; i < _questMenu.GetMenuItemCount(); ++i)
|
||||
{
|
||||
QuestMenuItem item = _questMenu.GetItem(i);
|
||||
uint questID = item.QuestId;
|
||||
Quest quest = Global.ObjectMgr.GetQuestTemplate(questID);
|
||||
if (quest != null)
|
||||
{
|
||||
ClientGossipText text = new ClientGossipText();
|
||||
text.QuestID = (int)questID;
|
||||
text.QuestType = item.QuestIcon;
|
||||
text.QuestLevel = quest.Level;
|
||||
text.QuestFlags = (int)quest.Flags;
|
||||
text.QuestFlagsEx = (int)quest.FlagsEx;
|
||||
text.Repeatable = quest.IsRepeatable();
|
||||
|
||||
text.QuestTitle = quest.LogTitle;
|
||||
LocaleConstant locale = _session.GetSessionDbLocaleIndex();
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id);
|
||||
if (localeData != null)
|
||||
ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref text.QuestTitle);
|
||||
}
|
||||
|
||||
packet.GossipText.Add(text);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendCloseGossip()
|
||||
{
|
||||
_interactionData.Reset();
|
||||
|
||||
_session.SendPacket(new GossipComplete());
|
||||
}
|
||||
|
||||
public void SendPointOfInterest(uint id)
|
||||
{
|
||||
PointOfInterest pointOfInterest = Global.ObjectMgr.GetPointOfInterest(id);
|
||||
if (pointOfInterest == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Request to send non-existing PointOfInterest (Id: {0}), ignored.", id);
|
||||
return;
|
||||
}
|
||||
|
||||
GossipPOI packet = new GossipPOI();
|
||||
packet.Name = pointOfInterest.Name;
|
||||
|
||||
LocaleConstant locale = _session.GetSessionDbLocaleIndex();
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
PointOfInterestLocale localeData = Global.ObjectMgr.GetPointOfInterestLocale(id);
|
||||
if (localeData != null)
|
||||
ObjectManager.GetLocaleString(localeData.Name, locale, ref packet.Name);
|
||||
}
|
||||
|
||||
packet.Flags = pointOfInterest.Flags;
|
||||
packet.Pos = pointOfInterest.Pos;
|
||||
packet.Icon = pointOfInterest.Icon;
|
||||
packet.Importance = pointOfInterest.Importance;
|
||||
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendQuestGiverQuestListMessage(ObjectGuid guid)
|
||||
{
|
||||
QuestGiverQuestListMessage questList = new QuestGiverQuestListMessage();
|
||||
questList.QuestGiverGUID = guid;
|
||||
|
||||
QuestGreeting questGreeting = Global.ObjectMgr.GetQuestGreeting(guid);
|
||||
if (questGreeting != null)
|
||||
{
|
||||
questList.GreetEmoteDelay = questGreeting.greetEmoteDelay;
|
||||
questList.GreetEmoteType = questGreeting.greetEmoteType;
|
||||
questList.Greeting = questGreeting.greeting;
|
||||
}
|
||||
else
|
||||
Log.outError(LogFilter.Server, "Guid: {0} - No quest greeting found.", guid.ToString());
|
||||
|
||||
for (var i = 0; i < _questMenu.GetMenuItemCount(); ++i)
|
||||
{
|
||||
QuestMenuItem questMenuItem = _questMenu.GetItem(i);
|
||||
|
||||
uint questID = questMenuItem.QuestId;
|
||||
Quest quest = Global.ObjectMgr.GetQuestTemplate(questID);
|
||||
if (quest != null)
|
||||
{
|
||||
string title = quest.LogTitle;
|
||||
|
||||
LocaleConstant locale = _session.GetSessionDbLocaleIndex();
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id);
|
||||
if (localeData != null)
|
||||
ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref title);
|
||||
}
|
||||
|
||||
GossipText text = new GossipText();
|
||||
text.QuestID = questID;
|
||||
text.QuestType = questMenuItem.QuestIcon;
|
||||
text.QuestLevel = (uint)quest.Level;
|
||||
text.QuestFlags = (uint)quest.Flags;
|
||||
text.QuestFlagsEx = (uint)quest.FlagsEx;
|
||||
text.Repeatable = false; // NYI
|
||||
text.QuestTitle = title;
|
||||
questList.QuestDataText.Add(text);
|
||||
}
|
||||
}
|
||||
|
||||
_session.SendPacket(questList);
|
||||
}
|
||||
|
||||
public void SendQuestGiverStatus(QuestGiverStatus questStatus, ObjectGuid npcGUID)
|
||||
{
|
||||
var packet = new QuestGiverStatusPkt();
|
||||
packet.QuestGiver.Guid = npcGUID;
|
||||
packet.QuestGiver.Status = questStatus;
|
||||
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendQuestGiverQuestDetails(Quest quest, ObjectGuid npcGUID, bool activateAccept)
|
||||
{
|
||||
QuestGiverQuestDetails packet = new QuestGiverQuestDetails();
|
||||
|
||||
packet.QuestTitle = quest.LogTitle;
|
||||
packet.LogDescription = quest.LogDescription;
|
||||
packet.DescriptionText = quest.QuestDescription;
|
||||
packet.PortraitGiverText = quest.PortraitGiverText;
|
||||
packet.PortraitGiverName = quest.PortraitGiverName;
|
||||
packet.PortraitTurnInText = quest.PortraitTurnInText;
|
||||
packet.PortraitTurnInName = quest.PortraitTurnInName;
|
||||
|
||||
LocaleConstant locale = _session.GetSessionDbLocaleIndex();
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id);
|
||||
if (localeData != null)
|
||||
{
|
||||
ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref packet.QuestTitle);
|
||||
ObjectManager.GetLocaleString(localeData.LogDescription, locale, ref packet.LogDescription);
|
||||
ObjectManager.GetLocaleString(localeData.QuestDescription, locale, ref packet.DescriptionText);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitGiverText, locale, ref packet.PortraitGiverText);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitGiverName, locale, ref packet.PortraitGiverName);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitTurnInText, locale, ref packet.PortraitTurnInText);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitTurnInName, locale, ref packet.PortraitTurnInName);
|
||||
}
|
||||
}
|
||||
|
||||
packet.QuestGiverGUID = npcGUID;
|
||||
packet.InformUnit = _session.GetPlayer().GetDivider();
|
||||
packet.QuestID = quest.Id;
|
||||
packet.PortraitGiver = quest.QuestGiverPortrait;
|
||||
packet.PortraitTurnIn = quest.QuestTurnInPortrait;
|
||||
packet.AutoLaunched = activateAccept;
|
||||
packet.QuestFlags[0] = (uint)quest.Flags;
|
||||
packet.QuestFlags[1] = (uint)quest.FlagsEx;
|
||||
packet.SuggestedPartyMembers = quest.SuggestedPlayers;
|
||||
|
||||
if (quest.SourceSpellID != 0)
|
||||
packet.LearnSpells.Add(quest.SourceSpellID);
|
||||
|
||||
quest.BuildQuestRewards(packet.Rewards, _session.GetPlayer());
|
||||
|
||||
for (int i = 0; i < SharedConst.QuestEmoteCount; ++i)
|
||||
{
|
||||
var emote = new QuestDescEmote(quest.DetailsEmote[i], quest.DetailsEmoteDelay[i]);
|
||||
packet.DescEmotes.Add(emote);
|
||||
}
|
||||
|
||||
var objs = quest.Objectives;
|
||||
for (int i = 0; i < objs.Count; ++i)
|
||||
{
|
||||
var obj = new QuestObjectiveSimple();
|
||||
obj.ID = objs[i].ID;
|
||||
obj.ObjectID = objs[i].ObjectID;
|
||||
obj.Amount = objs[i].Amount;
|
||||
obj.Type = (byte)objs[i].Type;
|
||||
packet.Objectives.Add(obj);
|
||||
}
|
||||
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendQuestQueryResponse(Quest quest)
|
||||
{
|
||||
QueryQuestInfoResponse packet = new QueryQuestInfoResponse();
|
||||
|
||||
packet.Allow = true;
|
||||
packet.QuestID = quest.Id;
|
||||
|
||||
packet.Info.LogTitle = quest.LogTitle;
|
||||
packet.Info.LogDescription = quest.LogDescription;
|
||||
packet.Info.QuestDescription = quest.QuestDescription;
|
||||
packet.Info.AreaDescription = quest.AreaDescription;
|
||||
packet.Info.QuestCompletionLog = quest.QuestCompletionLog;
|
||||
packet.Info.PortraitGiverText = quest.PortraitGiverText;
|
||||
packet.Info.PortraitGiverName = quest.PortraitGiverName;
|
||||
packet.Info.PortraitTurnInText = quest.PortraitTurnInText;
|
||||
packet.Info.PortraitTurnInName = quest.PortraitTurnInName;
|
||||
|
||||
LocaleConstant locale = _session.GetSessionDbLocaleIndex();
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
QuestTemplateLocale questTemplateLocale = Global.ObjectMgr.GetQuestLocale(quest.Id);
|
||||
if (questTemplateLocale != null)
|
||||
{
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.LogTitle, locale, ref packet.Info.LogTitle);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.LogDescription, locale, ref packet.Info.LogDescription);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.QuestDescription, locale, ref packet.Info.QuestDescription);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.AreaDescription, locale, ref packet.Info.AreaDescription);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.QuestCompletionLog, locale, ref packet.Info.QuestCompletionLog);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.PortraitGiverText, locale, ref packet.Info.PortraitGiverText);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.PortraitGiverName, locale, ref packet.Info.PortraitGiverName);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.PortraitTurnInText, locale, ref packet.Info.PortraitTurnInText);
|
||||
ObjectManager.GetLocaleString(questTemplateLocale.PortraitTurnInName, locale, ref packet.Info.PortraitTurnInName);
|
||||
}
|
||||
}
|
||||
|
||||
packet.Info.QuestID = quest.Id;
|
||||
packet.Info.QuestType = (int)quest.Type;
|
||||
packet.Info.QuestLevel = quest.Level;
|
||||
packet.Info.QuestPackageID = quest.PackageID;
|
||||
packet.Info.QuestMinLevel = quest.MinLevel;
|
||||
packet.Info.QuestSortID = quest.QuestSortID;
|
||||
packet.Info.QuestInfoID = quest.QuestInfoID;
|
||||
packet.Info.SuggestedGroupNum = quest.SuggestedPlayers;
|
||||
packet.Info.RewardNextQuest = quest.NextQuestInChain;
|
||||
packet.Info.RewardXPDifficulty = quest.RewardXPDifficulty;
|
||||
packet.Info.RewardXPMultiplier = quest.RewardXPMultiplier;
|
||||
|
||||
if (!quest.HasFlag(QuestFlags.HiddenRewards))
|
||||
packet.Info.RewardMoney = quest.RewardMoney < 0 ? quest.RewardMoney : (int)_session.GetPlayer().GetQuestMoneyReward(quest);
|
||||
|
||||
packet.Info.RewardMoneyDifficulty = quest.RewardMoneyDifficulty;
|
||||
packet.Info.RewardMoneyMultiplier = quest.RewardMoneyMultiplier;
|
||||
packet.Info.RewardBonusMoney = quest.RewardBonusMoney;
|
||||
for (byte i = 0; i < SharedConst.QuestRewardDisplaySpellCount; ++i)
|
||||
packet.Info.RewardDisplaySpell[i] = quest.RewardDisplaySpell[i];
|
||||
|
||||
packet.Info.RewardSpell = quest.RewardSpell;
|
||||
|
||||
packet.Info.RewardHonor = quest.RewardHonor;
|
||||
packet.Info.RewardKillHonor = quest.RewardKillHonor;
|
||||
|
||||
packet.Info.RewardArtifactXPDifficulty = (int)quest.RewardArtifactXPDifficulty;
|
||||
packet.Info.RewardArtifactXPMultiplier = quest.RewardArtifactXPMultiplier;
|
||||
packet.Info.RewardArtifactCategoryID = (int)quest.RewardArtifactCategoryID;
|
||||
|
||||
packet.Info.StartItem = quest.SourceItemId;
|
||||
packet.Info.Flags = (uint)quest.Flags;
|
||||
packet.Info.FlagsEx = (uint)quest.FlagsEx;
|
||||
packet.Info.RewardTitle = quest.RewardTitleId;
|
||||
packet.Info.RewardArenaPoints = quest.RewardArenaPoints;
|
||||
packet.Info.RewardSkillLineID = quest.RewardSkillId;
|
||||
packet.Info.RewardNumSkillUps = quest.RewardSkillPoints;
|
||||
packet.Info.RewardFactionFlags = quest.RewardReputationMask;
|
||||
packet.Info.PortraitGiver = quest.QuestGiverPortrait;
|
||||
packet.Info.PortraitTurnIn = quest.QuestTurnInPortrait;
|
||||
|
||||
for (byte i = 0; i < SharedConst.QuestItemDropCount; ++i)
|
||||
{
|
||||
packet.Info.ItemDrop[i] = (int)quest.ItemDrop[i];
|
||||
packet.Info.ItemDropQuantity[i] = (int)quest.ItemDropQuantity[i];
|
||||
}
|
||||
|
||||
if (!quest.HasFlag(QuestFlags.HiddenRewards))
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.QuestRewardItemCount; ++i)
|
||||
{
|
||||
packet.Info.RewardItems[i] = quest.RewardItemId[i];
|
||||
packet.Info.RewardAmount[i] = quest.RewardItemCount[i];
|
||||
}
|
||||
for (byte i = 0; i < SharedConst.QuestRewardChoicesCount; ++i)
|
||||
{
|
||||
packet.Info.UnfilteredChoiceItems[i].ItemID = quest.RewardChoiceItemId[i];
|
||||
packet.Info.UnfilteredChoiceItems[i].Quantity = quest.RewardChoiceItemCount[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (byte i = 0; i < SharedConst.QuestRewardReputationsCount; ++i)
|
||||
{
|
||||
packet.Info.RewardFactionID[i] = quest.RewardFactionId[i];
|
||||
packet.Info.RewardFactionValue[i] = quest.RewardFactionValue[i];
|
||||
packet.Info.RewardFactionOverride[i] = quest.RewardFactionOverride[i];
|
||||
packet.Info.RewardFactionCapIn[i] = (int)quest.RewardFactionCapIn[i];
|
||||
}
|
||||
|
||||
packet.Info.POIContinent = quest.POIContinent;
|
||||
packet.Info.POIx = quest.POIx;
|
||||
packet.Info.POIy = quest.POIy;
|
||||
packet.Info.POIPriority = quest.POIPriority;
|
||||
|
||||
packet.Info.AllowableRaces = quest.AllowableRaces;
|
||||
packet.Info.QuestRewardID = (int)quest.QuestRewardID;
|
||||
packet.Info.Expansion = quest.Expansion;
|
||||
|
||||
foreach (QuestObjective questObjective in quest.Objectives)
|
||||
{
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
QuestObjectivesLocale questObjectivesLocaleData = Global.ObjectMgr.GetQuestObjectivesLocale(questObjective.ID);
|
||||
if (questObjectivesLocaleData != null)
|
||||
ObjectManager.GetLocaleString(questObjectivesLocaleData.Description, locale, ref questObjective.Description);
|
||||
}
|
||||
|
||||
packet.Info.Objectives.Add(questObjective);
|
||||
}
|
||||
|
||||
for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i)
|
||||
{
|
||||
packet.Info.RewardCurrencyID[i] = quest.RewardCurrencyId[i];
|
||||
packet.Info.RewardCurrencyQty[i] = quest.RewardCurrencyCount[i];
|
||||
}
|
||||
|
||||
packet.Info.AcceptedSoundKitID = quest.SoundAccept;
|
||||
packet.Info.CompleteSoundKitID = quest.SoundTurnIn;
|
||||
packet.Info.AreaGroupID = quest.AreaGroupID;
|
||||
packet.Info.TimeAllowed = quest.LimitTime;
|
||||
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendQuestGiverOfferReward(Quest quest, ObjectGuid npcGUID, bool enableNext)
|
||||
{
|
||||
QuestGiverOfferRewardMessage packet = new QuestGiverOfferRewardMessage();
|
||||
|
||||
packet.QuestTitle = quest.LogTitle;
|
||||
packet.RewardText = quest.OfferRewardText;
|
||||
packet.PortraitGiverText = quest.PortraitGiverText;
|
||||
packet.PortraitGiverName = quest.PortraitGiverName;
|
||||
packet.PortraitTurnInText = quest.PortraitTurnInText;
|
||||
packet.PortraitTurnInName = quest.PortraitTurnInName;
|
||||
|
||||
LocaleConstant locale = _session.GetSessionDbLocaleIndex();
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id);
|
||||
if (localeData != null)
|
||||
{
|
||||
ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref packet.QuestTitle);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitGiverText, locale, ref packet.PortraitGiverText);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitGiverName, locale, ref packet.PortraitGiverName);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitTurnInText, locale, ref packet.PortraitTurnInText);
|
||||
ObjectManager.GetLocaleString(localeData.PortraitTurnInName, locale, ref packet.PortraitTurnInName);
|
||||
}
|
||||
|
||||
QuestOfferRewardLocale questOfferRewardLocale = Global.ObjectMgr.GetQuestOfferRewardLocale(quest.Id);
|
||||
if (questOfferRewardLocale != null)
|
||||
ObjectManager.GetLocaleString(questOfferRewardLocale.RewardText, locale, ref packet.RewardText);
|
||||
}
|
||||
|
||||
QuestGiverOfferReward offer = new QuestGiverOfferReward();
|
||||
|
||||
quest.BuildQuestRewards(offer.Rewards, _session.GetPlayer());
|
||||
offer.QuestGiverGUID = npcGUID;
|
||||
|
||||
// Is there a better way? what about game objects?
|
||||
Creature creature = ObjectAccessor.GetCreature(_session.GetPlayer(), npcGUID);
|
||||
if (creature)
|
||||
offer.QuestGiverCreatureID = creature.GetCreatureTemplate().Entry;
|
||||
|
||||
offer.QuestID = quest.Id;
|
||||
offer.AutoLaunched = enableNext;
|
||||
offer.SuggestedPartyMembers = quest.SuggestedPlayers;
|
||||
|
||||
for (uint i = 0; i < SharedConst.QuestEmoteCount && quest.OfferRewardEmote[i] != 0; ++i)
|
||||
offer.Emotes.Add(new QuestDescEmote(quest.OfferRewardEmote[i], quest.OfferRewardEmoteDelay[i]));
|
||||
|
||||
offer.QuestFlags[0] = (uint)quest.Flags;
|
||||
offer.QuestFlags[1] = (uint)quest.FlagsEx;
|
||||
|
||||
packet.PortraitTurnIn = quest.QuestTurnInPortrait;
|
||||
packet.PortraitGiver = quest.QuestGiverPortrait;
|
||||
packet.QuestPackageID = quest.PackageID;
|
||||
|
||||
packet.QuestData = offer;
|
||||
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendQuestGiverRequestItems(Quest quest, ObjectGuid npcGUID, bool canComplete, bool closeOnCancel)
|
||||
{
|
||||
// We can always call to RequestItems, but this packet only goes out if there are actually
|
||||
// items. Otherwise, we'll skip straight to the OfferReward
|
||||
|
||||
if (!quest.HasSpecialFlag(QuestSpecialFlags.Deliver) && canComplete)
|
||||
{
|
||||
SendQuestGiverOfferReward(quest, npcGUID, true);
|
||||
return;
|
||||
}
|
||||
|
||||
QuestGiverRequestItems packet = new QuestGiverRequestItems();
|
||||
|
||||
packet.QuestTitle = quest.LogTitle;
|
||||
packet.CompletionText = quest.RequestItemsText;
|
||||
|
||||
LocaleConstant locale = _session.GetSessionDbLocaleIndex();
|
||||
if (locale != LocaleConstant.enUS)
|
||||
{
|
||||
QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id);
|
||||
if (localeData != null)
|
||||
ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref packet.QuestTitle);
|
||||
|
||||
QuestRequestItemsLocale questRequestItemsLocale = Global.ObjectMgr.GetQuestRequestItemsLocale(quest.Id);
|
||||
if (questRequestItemsLocale != null)
|
||||
ObjectManager.GetLocaleString(questRequestItemsLocale.CompletionText, locale, ref packet.CompletionText);
|
||||
}
|
||||
|
||||
packet.QuestGiverGUID = npcGUID;
|
||||
|
||||
// Is there a better way? what about game objects?
|
||||
Creature creature = ObjectAccessor.GetCreature(_session.GetPlayer(), npcGUID);
|
||||
if (creature)
|
||||
packet.QuestGiverCreatureID = creature.GetCreatureTemplate().Entry;
|
||||
|
||||
packet.QuestID = quest.Id;
|
||||
|
||||
if (canComplete)
|
||||
{
|
||||
packet.CompEmoteDelay = quest.EmoteOnCompleteDelay;
|
||||
packet.CompEmoteType = quest.EmoteOnComplete;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.CompEmoteDelay = quest.EmoteOnIncompleteDelay;
|
||||
packet.CompEmoteType = quest.EmoteOnIncomplete;
|
||||
}
|
||||
|
||||
packet.QuestFlags[0] = (uint)quest.Flags;
|
||||
packet.QuestFlags[1] = (uint)quest.FlagsEx;
|
||||
packet.SuggestPartyMembers = quest.SuggestedPlayers;
|
||||
|
||||
// incomplete: FD
|
||||
// incomplete quest with item objective but item objective is complete DD
|
||||
packet.StatusFlags = canComplete ? 0xFF : 0xFD;
|
||||
|
||||
packet.MoneyToGet = 0;
|
||||
foreach (QuestObjective obj in quest.Objectives)
|
||||
{
|
||||
switch (obj.Type)
|
||||
{
|
||||
case QuestObjectiveType.Item:
|
||||
packet.Collect.Add(new QuestObjectiveCollect((uint)obj.ObjectID, obj.Amount, (uint)obj.Flags));
|
||||
break;
|
||||
case QuestObjectiveType.Currency:
|
||||
packet.Currency.Add(new QuestCurrency((uint)obj.ObjectID, obj.Amount));
|
||||
break;
|
||||
case QuestObjectiveType.Money:
|
||||
packet.MoneyToGet += obj.Amount;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
packet.AutoLaunched = closeOnCancel;
|
||||
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public GossipMenu GetGossipMenu() { return _gossipMenu; }
|
||||
public QuestMenu GetQuestMenu() { return _questMenu; }
|
||||
public InteractionData GetInteractionData() { return _interactionData; }
|
||||
|
||||
bool IsEmpty() { return _gossipMenu.IsEmpty() && _questMenu.IsEmpty(); }
|
||||
|
||||
public uint GetGossipOptionSender(uint selection) { return _gossipMenu.GetMenuItemSender(selection); }
|
||||
public uint GetGossipOptionAction(uint selection) { return _gossipMenu.GetMenuItemAction(selection); }
|
||||
public bool IsGossipOptionCoded(uint selection) { return _gossipMenu.IsMenuItemCoded(selection); }
|
||||
|
||||
GossipMenu _gossipMenu = new GossipMenu();
|
||||
QuestMenu _questMenu = new QuestMenu();
|
||||
WorldSession _session;
|
||||
InteractionData _interactionData = new InteractionData();
|
||||
}
|
||||
|
||||
public class QuestMenu
|
||||
{
|
||||
public QuestMenu() { }
|
||||
|
||||
public void AddMenuItem(uint QuestId, byte Icon)
|
||||
{
|
||||
if (Global.ObjectMgr.GetQuestTemplate(QuestId) == null)
|
||||
return;
|
||||
|
||||
QuestMenuItem questMenuItem = new QuestMenuItem();
|
||||
|
||||
questMenuItem.QuestId = QuestId;
|
||||
questMenuItem.QuestIcon = Icon;
|
||||
|
||||
_questMenuItems.Add(questMenuItem);
|
||||
}
|
||||
|
||||
bool HasItem(uint questId)
|
||||
{
|
||||
foreach (var item in _questMenuItems)
|
||||
if (item.QuestId == questId)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ClearMenu()
|
||||
{
|
||||
_questMenuItems.Clear();
|
||||
}
|
||||
|
||||
public int GetMenuItemCount()
|
||||
{
|
||||
return _questMenuItems.Count();
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return _questMenuItems.Empty();
|
||||
}
|
||||
|
||||
public QuestMenuItem GetItem(int index)
|
||||
{
|
||||
return _questMenuItems.LookupByIndex(index);
|
||||
}
|
||||
|
||||
List<QuestMenuItem> _questMenuItems = new List<QuestMenuItem>();
|
||||
}
|
||||
|
||||
public struct QuestMenuItem
|
||||
{
|
||||
public uint QuestId;
|
||||
public byte QuestIcon;
|
||||
}
|
||||
|
||||
public class GossipMenuItem
|
||||
{
|
||||
public byte MenuItemIcon;
|
||||
public bool IsCoded;
|
||||
public string Message;
|
||||
public uint Sender;
|
||||
public uint OptionType;
|
||||
public string BoxMessage;
|
||||
public uint BoxMoney;
|
||||
}
|
||||
|
||||
public class GossipMenuItemData
|
||||
{
|
||||
public uint GossipActionMenuId; // MenuId of the gossip triggered by this action
|
||||
public uint GossipActionPoi;
|
||||
public uint TrainerId;
|
||||
}
|
||||
|
||||
public struct NpcTextData
|
||||
{
|
||||
public float Probability;
|
||||
public uint BroadcastTextID;
|
||||
}
|
||||
|
||||
public class NpcText
|
||||
{
|
||||
public NpcTextData[] Data = new NpcTextData[SharedConst.MaxNpcTextOptions];
|
||||
}
|
||||
|
||||
public class PageTextLocale
|
||||
{
|
||||
public StringArray Text = new StringArray((int)LocaleConstant.Total);
|
||||
}
|
||||
|
||||
public class GossipMenuItems
|
||||
{
|
||||
public uint MenuId;
|
||||
public uint OptionIndex;
|
||||
public GossipOptionIcon OptionIcon;
|
||||
public string OptionText;
|
||||
public uint OptionBroadcastTextId;
|
||||
public GossipOption OptionType;
|
||||
public NPCFlags OptionNpcflag;
|
||||
public uint ActionMenuId;
|
||||
public uint ActionPoiId;
|
||||
public bool BoxCoded;
|
||||
public uint BoxMoney;
|
||||
public string BoxText;
|
||||
public uint BoxBroadcastTextId;
|
||||
public uint TrainerId;
|
||||
public List<Condition> Conditions = new List<Condition>();
|
||||
}
|
||||
|
||||
public class PointOfInterest
|
||||
{
|
||||
public uint ID;
|
||||
public Vector2 Pos;
|
||||
public uint Icon;
|
||||
public uint Flags;
|
||||
public uint Importance;
|
||||
public string Name;
|
||||
}
|
||||
|
||||
public class PointOfInterestLocale
|
||||
{
|
||||
public StringArray Name = new StringArray((int)LocaleConstant.Total);
|
||||
}
|
||||
|
||||
public class GossipMenus
|
||||
{
|
||||
public uint entry;
|
||||
public uint text_id;
|
||||
public List<Condition> conditions = new List<Condition>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Framework.Constants;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class TrainerSpell
|
||||
{
|
||||
public uint SpellId;
|
||||
public uint MoneyCost;
|
||||
public uint ReqSkillLine;
|
||||
public uint ReqSkillRank;
|
||||
public Array<uint> ReqAbility = new Array<uint>(3);
|
||||
public byte ReqLevel;
|
||||
|
||||
public uint LearnedSpellId;
|
||||
public bool IsCastable() { return LearnedSpellId != SpellId; }
|
||||
}
|
||||
|
||||
public class Trainer
|
||||
{
|
||||
public Trainer(uint id, TrainerType type, string greeting, List<TrainerSpell> spells)
|
||||
{
|
||||
_id = id;
|
||||
_type = type;
|
||||
_spells = spells;
|
||||
|
||||
_greeting[(int)LocaleConstant.enUS] = greeting;
|
||||
}
|
||||
|
||||
public void SendSpells(Creature npc, Player player, LocaleConstant locale)
|
||||
{
|
||||
float reputationDiscount = player.GetReputationPriceDiscount(npc);
|
||||
|
||||
TrainerList trainerList = new TrainerList();
|
||||
trainerList.TrainerGUID = npc.GetGUID();
|
||||
trainerList.TrainerType = (int)_type;
|
||||
trainerList.TrainerID = (int)_id;
|
||||
trainerList.Greeting = GetGreeting(locale);
|
||||
|
||||
foreach (TrainerSpell trainerSpell in _spells)
|
||||
{
|
||||
if (!player.IsSpellFitByClassAndRace(trainerSpell.SpellId))
|
||||
continue;
|
||||
|
||||
TrainerListSpell trainerListSpell = new TrainerListSpell();
|
||||
trainerListSpell.SpellID = trainerSpell.SpellId;
|
||||
trainerListSpell.MoneyCost = (uint)(trainerSpell.MoneyCost * reputationDiscount);
|
||||
trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine;
|
||||
trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank;
|
||||
trainerListSpell.ReqAbility = trainerSpell.ReqAbility.ToArray();
|
||||
trainerListSpell.Usable = GetSpellState(player, trainerSpell);
|
||||
trainerListSpell.ReqLevel = trainerSpell.ReqLevel;
|
||||
trainerList.Spells.Add(trainerListSpell);
|
||||
}
|
||||
|
||||
player.SendPacket(trainerList);
|
||||
}
|
||||
|
||||
public void TeachSpell(Creature npc, Player player, uint spellId)
|
||||
{
|
||||
TrainerSpell trainerSpell = GetSpell(spellId);
|
||||
if (trainerSpell == null || !CanTeachSpell(player, trainerSpell))
|
||||
{
|
||||
SendTeachFailure(npc, player, spellId, TrainerFailReason.Unavailable);
|
||||
return;
|
||||
}
|
||||
|
||||
float reputationDiscount = player.GetReputationPriceDiscount(npc);
|
||||
long moneyCost = (long)(trainerSpell.MoneyCost * reputationDiscount);
|
||||
if (!player.HasEnoughMoney(moneyCost))
|
||||
{
|
||||
SendTeachFailure(npc, player, spellId, TrainerFailReason.NotEnoughMoney);
|
||||
return;
|
||||
}
|
||||
|
||||
player.ModifyMoney(-moneyCost);
|
||||
|
||||
npc.SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected
|
||||
player.SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute
|
||||
|
||||
// learn explicitly or cast explicitly
|
||||
if (trainerSpell.IsCastable())
|
||||
player.CastSpell(player, trainerSpell.SpellId, true);
|
||||
else
|
||||
player.LearnSpell(trainerSpell.SpellId, false);
|
||||
}
|
||||
|
||||
TrainerSpell GetSpell(uint spellId)
|
||||
{
|
||||
return _spells.Find(trainerSpell => trainerSpell.SpellId == spellId);
|
||||
}
|
||||
|
||||
bool CanTeachSpell(Player player, TrainerSpell trainerSpell)
|
||||
{
|
||||
TrainerSpellState state = GetSpellState(player, trainerSpell);
|
||||
if (state != TrainerSpellState.Available)
|
||||
return false;
|
||||
|
||||
SpellInfo trainerSpellInfo = Global.SpellMgr.GetSpellInfo(trainerSpell.SpellId);
|
||||
if (trainerSpellInfo.IsPrimaryProfessionFirstRank() && player.GetFreePrimaryProfessionPoints() == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TrainerSpellState GetSpellState(Player player, TrainerSpell trainerSpell)
|
||||
{
|
||||
if (player.HasSpell(trainerSpell.SpellId))
|
||||
return TrainerSpellState.Known;
|
||||
|
||||
// check race/class requirement
|
||||
if (!player.IsSpellFitByClassAndRace(trainerSpell.SpellId))
|
||||
return TrainerSpellState.Unavailable;
|
||||
|
||||
// check skill requirement
|
||||
if (trainerSpell.ReqSkillLine != 0 && player.GetBaseSkillValue((SkillType)trainerSpell.ReqSkillLine) < trainerSpell.ReqSkillRank)
|
||||
return TrainerSpellState.Unavailable;
|
||||
|
||||
foreach (uint reqAbility in trainerSpell.ReqAbility)
|
||||
if (reqAbility != 0 && !player.HasSpell(reqAbility))
|
||||
return TrainerSpellState.Unavailable;
|
||||
|
||||
// check level requirement
|
||||
if (player.getLevel() < trainerSpell.ReqLevel)
|
||||
return TrainerSpellState.Unavailable;
|
||||
|
||||
// check ranks
|
||||
uint previousRankSpellId = Global.SpellMgr.GetPrevSpellInChain(trainerSpell.LearnedSpellId);
|
||||
if (previousRankSpellId != 0)
|
||||
if (!player.HasSpell(previousRankSpellId))
|
||||
return TrainerSpellState.Unavailable;
|
||||
|
||||
// check additional spell requirement
|
||||
foreach (var spellId in Global.SpellMgr.GetSpellsRequiredForSpellBounds(trainerSpell.SpellId))
|
||||
if (!player.HasSpell(spellId))
|
||||
return TrainerSpellState.Unavailable;
|
||||
|
||||
return TrainerSpellState.Available;
|
||||
}
|
||||
|
||||
void SendTeachFailure(Creature npc, Player player, uint spellId, TrainerFailReason reason)
|
||||
{
|
||||
TrainerBuyFailed trainerBuyFailed = new TrainerBuyFailed();
|
||||
trainerBuyFailed.TrainerGUID = npc.GetGUID();
|
||||
trainerBuyFailed.SpellID = spellId;
|
||||
trainerBuyFailed.TrainerFailedReason = reason;
|
||||
player.SendPacket(trainerBuyFailed);
|
||||
}
|
||||
|
||||
string GetGreeting(LocaleConstant locale)
|
||||
{
|
||||
if (_greeting[(int)locale].IsEmpty())
|
||||
return _greeting[(int)LocaleConstant.enUS];
|
||||
|
||||
return _greeting[(int)locale];
|
||||
}
|
||||
|
||||
public void AddGreetingLocale(LocaleConstant locale, string greeting)
|
||||
{
|
||||
_greeting[(int)locale] = greeting;
|
||||
}
|
||||
|
||||
uint _id;
|
||||
TrainerType _type;
|
||||
List<TrainerSpell> _spells;
|
||||
Array<string> _greeting = new Array<string>((int)LocaleConstant.Total);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Spells;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class DynamicObject : WorldObject
|
||||
{
|
||||
public DynamicObject(bool isWorldObject) : base(isWorldObject)
|
||||
{
|
||||
objectTypeMask |= TypeMask.DynamicObject;
|
||||
objectTypeId = TypeId.DynamicObject;
|
||||
|
||||
m_updateFlag = UpdateFlag.StationaryPosition;
|
||||
|
||||
valuesCount = (int)DynamicObjectFields.End;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
// make sure all references were properly removed
|
||||
Contract.Assert(_aura == null);
|
||||
Contract.Assert(!_caster);
|
||||
Contract.Assert(!_isViewpoint);
|
||||
_removedAura = null;
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
public override void AddToWorld()
|
||||
{
|
||||
// Register the dynamicObject for guid lookup and for caster
|
||||
if (!IsInWorld)
|
||||
{
|
||||
GetMap().GetObjectsStore().Add(GetGUID(), this);
|
||||
base.AddToWorld();
|
||||
BindToCaster();
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveFromWorld()
|
||||
{
|
||||
// Remove the dynamicObject from the accessor and from all lists of objects in world
|
||||
if (IsInWorld)
|
||||
{
|
||||
if (_isViewpoint)
|
||||
RemoveCasterViewpoint();
|
||||
|
||||
if (_aura != null)
|
||||
RemoveAura();
|
||||
|
||||
// dynobj could get removed in Aura.RemoveAura
|
||||
if (!IsInWorld)
|
||||
return;
|
||||
|
||||
UnbindFromCaster();
|
||||
base.RemoveFromWorld();
|
||||
GetMap().GetObjectsStore().Remove(GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
public bool CreateDynamicObject(ulong guidlow, Unit caster, SpellInfo spell, Position pos, float radius, DynamicObjectType type, uint spellXSpellVisualId)
|
||||
{
|
||||
_spellXSpellVisualId = spellXSpellVisualId;
|
||||
SetMap(caster.GetMap());
|
||||
Relocate(pos);
|
||||
if (!IsPositionValid())
|
||||
{
|
||||
Log.outError(LogFilter.Server, "DynamicObject (spell {0}) not created. Suggested coordinates isn't valid (X: {1} Y: {2})", spell.Id, GetPositionX(), GetPositionY());
|
||||
return false;
|
||||
}
|
||||
|
||||
_Create(ObjectGuid.Create(HighGuid.DynamicObject, GetMapId(), spell.Id, guidlow));
|
||||
SetPhaseMask(caster.GetPhaseMask(), false);
|
||||
|
||||
SetEntry(spell.Id);
|
||||
SetObjectScale(1f);
|
||||
SetGuidValue(DynamicObjectFields.Caster, caster.GetGUID());
|
||||
|
||||
SetUInt32Value(DynamicObjectFields.Type, (uint)type);
|
||||
SetUInt32Value(DynamicObjectFields.SpellXSpellVisualId, spellXSpellVisualId);
|
||||
SetUInt32Value(DynamicObjectFields.SpellId, spell.Id);
|
||||
SetFloatValue(DynamicObjectFields.Radius, radius);
|
||||
SetUInt32Value(DynamicObjectFields.CastTime, Time.GetMSTime());
|
||||
|
||||
if (IsWorldObject())
|
||||
setActive(true); //must before add to map to be put in world container
|
||||
|
||||
Transport transport = caster.GetTransport();
|
||||
if (transport)
|
||||
{
|
||||
float x, y, z, o;
|
||||
pos.GetPosition(out x, out y, out z, out o);
|
||||
transport.CalculatePassengerOffset(ref x, ref y, ref z, ref o);
|
||||
m_movementInfo.transport.pos.Relocate(x, y, z, o);
|
||||
|
||||
// This object must be added to transport before adding to map for the client to properly display it
|
||||
transport.AddPassenger(this);
|
||||
}
|
||||
|
||||
if (!GetMap().AddToMap(this))
|
||||
{
|
||||
// Returning false will cause the object to be deleted - remove from transport
|
||||
if (transport)
|
||||
transport.RemovePassenger(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(uint diff)
|
||||
{
|
||||
// caster has to be always available and in the same map
|
||||
Contract.Assert(_caster != null);
|
||||
Contract.Assert(_caster.GetMap() == GetMap());
|
||||
|
||||
bool expired = false;
|
||||
|
||||
if (_aura != null)
|
||||
{
|
||||
if (!_aura.IsRemoved())
|
||||
_aura.UpdateOwner(diff, this);
|
||||
|
||||
// _aura may be set to null in Aura.UpdateOwner call
|
||||
if (_aura != null && (_aura.IsRemoved() || _aura.IsExpired()))
|
||||
expired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetDuration() > diff)
|
||||
_duration -= (int)diff;
|
||||
else
|
||||
expired = true;
|
||||
}
|
||||
|
||||
if (expired)
|
||||
Remove();
|
||||
else
|
||||
Global.ScriptMgr.OnDynamicObjectUpdate(this, diff);
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
if (IsInWorld)
|
||||
{
|
||||
RemoveFromWorld();
|
||||
AddObjectToRemoveList();
|
||||
}
|
||||
}
|
||||
|
||||
int GetDuration()
|
||||
{
|
||||
if (_aura == null)
|
||||
return _duration;
|
||||
else
|
||||
return _aura.GetDuration();
|
||||
}
|
||||
|
||||
public void SetDuration(int newDuration)
|
||||
{
|
||||
if (_aura == null)
|
||||
_duration = newDuration;
|
||||
else
|
||||
_aura.SetDuration(newDuration);
|
||||
}
|
||||
|
||||
public void Delay(int delaytime)
|
||||
{
|
||||
SetDuration(GetDuration() - delaytime);
|
||||
}
|
||||
|
||||
public void SetAura(Aura aura)
|
||||
{
|
||||
Contract.Assert(_aura == null && aura != null);
|
||||
_aura = aura;
|
||||
}
|
||||
|
||||
void RemoveAura()
|
||||
{
|
||||
Contract.Assert(_aura != null && _removedAura == null);
|
||||
_removedAura = _aura;
|
||||
_aura = null;
|
||||
if (!_removedAura.IsRemoved())
|
||||
_removedAura._Remove(AuraRemoveMode.Default);
|
||||
}
|
||||
|
||||
public void SetCasterViewpoint()
|
||||
{
|
||||
Player caster = _caster.ToPlayer();
|
||||
if (caster != null)
|
||||
{
|
||||
caster.SetViewpoint(this, true);
|
||||
_isViewpoint = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCasterViewpoint()
|
||||
{
|
||||
Player caster = _caster.ToPlayer();
|
||||
if (caster != null)
|
||||
{
|
||||
caster.SetViewpoint(this, false);
|
||||
_isViewpoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BindToCaster()
|
||||
{
|
||||
Contract.Assert(_caster == null);
|
||||
_caster = Global.ObjAccessor.GetUnit(this, GetCasterGUID());
|
||||
Contract.Assert(_caster != null);
|
||||
Contract.Assert(_caster.GetMap() == GetMap());
|
||||
_caster._RegisterDynObject(this);
|
||||
}
|
||||
|
||||
void UnbindFromCaster()
|
||||
{
|
||||
Contract.Assert(_caster != null);
|
||||
_caster._UnregisterDynObject(this);
|
||||
_caster = null;
|
||||
}
|
||||
|
||||
public SpellInfo GetSpellInfo()
|
||||
{
|
||||
return Global.SpellMgr.GetSpellInfo(GetSpellId());
|
||||
}
|
||||
|
||||
public Unit GetCaster() { return _caster; }
|
||||
public uint GetSpellId() { return GetUInt32Value(DynamicObjectFields.SpellId); }
|
||||
public ObjectGuid GetCasterGUID() { return GetGuidValue(DynamicObjectFields.Caster); }
|
||||
public float GetRadius() { return GetFloatValue(DynamicObjectFields.Radius); }
|
||||
|
||||
Aura _aura;
|
||||
Aura _removedAura;
|
||||
Unit _caster;
|
||||
int _duration; // for non-aura dynobjects
|
||||
uint _spellXSpellVisualId;
|
||||
bool _isViewpoint;
|
||||
}
|
||||
|
||||
public enum DynamicObjectType
|
||||
{
|
||||
Portal = 0x0, // unused
|
||||
AreaSpell = 0x1,
|
||||
FarsightFocus = 0x2
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class Bag : Item
|
||||
{
|
||||
public Bag()
|
||||
{
|
||||
objectTypeMask |= TypeMask.Container;
|
||||
objectTypeId = TypeId.Container;
|
||||
|
||||
valuesCount = (int)ContainerFields.End;
|
||||
_dynamicValuesCount = (int)ItemDynamicFields.End;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
for (byte i = 0; i < ItemConst.MaxBagSize; ++i)
|
||||
{
|
||||
Item item = m_bagslot[i];
|
||||
if (item)
|
||||
{
|
||||
if (item.IsInWorld)
|
||||
{
|
||||
Log.outFatal(LogFilter.PlayerItems, "Item {0} (slot {1}, bag slot {2}) in bag {3} (slot {4}, bag slot {5}, m_bagslot {6}) is to be deleted but is still in world.",
|
||||
item.GetEntry(), item.GetSlot(), item.GetBagSlot(),
|
||||
GetEntry(), GetSlot(), GetBagSlot(), i);
|
||||
item.RemoveFromWorld();
|
||||
}
|
||||
m_bagslot[i].Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
public override void AddToWorld()
|
||||
{
|
||||
base.AddToWorld();
|
||||
|
||||
for (uint i = 0; i < GetBagSize(); ++i)
|
||||
if (m_bagslot[i] != null)
|
||||
m_bagslot[i].AddToWorld();
|
||||
}
|
||||
|
||||
public override void RemoveFromWorld()
|
||||
{
|
||||
for (uint i = 0; i < GetBagSize(); ++i)
|
||||
if (m_bagslot[i] != null)
|
||||
m_bagslot[i].RemoveFromWorld();
|
||||
|
||||
base.RemoveFromWorld();
|
||||
}
|
||||
|
||||
public override bool Create(ulong guidlow, uint itemid, Player owner)
|
||||
{
|
||||
var itemProto = Global.ObjectMgr.GetItemTemplate(itemid);
|
||||
|
||||
if (itemProto == null || itemProto.GetContainerSlots() > ItemConst.MaxBagSize)
|
||||
return false;
|
||||
|
||||
_Create(ObjectGuid.Create(HighGuid.Item, guidlow));
|
||||
|
||||
_bonusData = new BonusData(itemProto);
|
||||
|
||||
SetEntry(itemid);
|
||||
SetObjectScale(1.0f);
|
||||
|
||||
if (owner)
|
||||
{
|
||||
SetGuidValue(ItemFields.Owner, owner.GetGUID());
|
||||
SetGuidValue(ItemFields.Contained, owner.GetGUID());
|
||||
}
|
||||
|
||||
SetUInt32Value(ItemFields.MaxDurability, itemProto.MaxDurability);
|
||||
SetUInt32Value(ItemFields.Durability, itemProto.MaxDurability);
|
||||
SetUInt32Value(ItemFields.StackCount, 1);
|
||||
|
||||
// Setting the number of Slots the Container has
|
||||
SetUInt32Value(ContainerFields.NumSlots, itemProto.GetContainerSlots());
|
||||
|
||||
// Cleaning 20 slots
|
||||
for (byte i = 0; i < ItemConst.MaxBagSize; ++i)
|
||||
SetGuidValue(ContainerFields.Slot1 + (i * 4), ObjectGuid.Empty);
|
||||
|
||||
m_bagslot = new Item[ItemConst.MaxBagSize];
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void SaveToDB(SQLTransaction trans)
|
||||
{
|
||||
base.SaveToDB(trans);
|
||||
|
||||
}
|
||||
|
||||
public override bool LoadFromDB(ulong guid, ObjectGuid owner_guid, SQLFields fields, uint entry)
|
||||
{
|
||||
if (!base.LoadFromDB(guid, owner_guid, fields, entry))
|
||||
return false;
|
||||
|
||||
ItemTemplate itemProto = GetTemplate(); // checked in Item.LoadFromDB
|
||||
SetUInt32Value(ContainerFields.NumSlots, itemProto.GetContainerSlots());
|
||||
// cleanup bag content related item value fields (its will be filled correctly from `character_inventory`)
|
||||
for (byte i = 0; i < ItemConst.MaxBagSize; ++i)
|
||||
{
|
||||
SetGuidValue(ContainerFields.Slot1 + (i * 4), ObjectGuid.Empty);
|
||||
m_bagslot[i] = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void DeleteFromDB(SQLTransaction trans)
|
||||
{
|
||||
for (byte i = 0; i < ItemConst.MaxBagSize; ++i)
|
||||
if (m_bagslot[i] != null)
|
||||
m_bagslot[i].DeleteFromDB(trans);
|
||||
|
||||
base.DeleteFromDB(trans);
|
||||
}
|
||||
|
||||
public uint GetFreeSlots()
|
||||
{
|
||||
uint slots = 0;
|
||||
for (uint i = 0; i < GetBagSize(); ++i)
|
||||
if (m_bagslot[i] == null)
|
||||
++slots;
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
public void RemoveItem(byte slot, bool update)
|
||||
{
|
||||
if (m_bagslot[slot] != null)
|
||||
m_bagslot[slot].SetContainer(null);
|
||||
|
||||
m_bagslot[slot] = null;
|
||||
SetGuidValue(ContainerFields.Slot1 + (slot * 4), ObjectGuid.Empty);
|
||||
}
|
||||
|
||||
public void StoreItem(byte slot, Item pItem, bool update)
|
||||
{
|
||||
if (pItem != null && pItem.GetGUID() != GetGUID())
|
||||
{
|
||||
m_bagslot[slot] = pItem;
|
||||
SetGuidValue(ContainerFields.Slot1 + (slot * 4), pItem.GetGUID());
|
||||
pItem.SetGuidValue(ItemFields.Contained, GetGUID());
|
||||
pItem.SetGuidValue(ItemFields.Owner, GetOwnerGUID());
|
||||
pItem.SetContainer(this);
|
||||
pItem.SetSlot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BuildCreateUpdateBlockForPlayer(UpdateData data, Player target)
|
||||
{
|
||||
base.BuildCreateUpdateBlockForPlayer(data, target);
|
||||
|
||||
for (int i = 0; i < GetBagSize(); ++i)
|
||||
if (m_bagslot[i] != null)
|
||||
m_bagslot[i].BuildCreateUpdateBlockForPlayer(data, target);
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
for (var i = 0; i < GetBagSize(); ++i)
|
||||
if (m_bagslot[i] != null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint GetItemCount(uint item, Item eItem)
|
||||
{
|
||||
Item pItem;
|
||||
uint count = 0;
|
||||
for (var i = 0; i < GetBagSize(); ++i)
|
||||
{
|
||||
pItem = m_bagslot[i];
|
||||
if (pItem != null && pItem != eItem && pItem.GetEntry() == item)
|
||||
count += pItem.GetCount();
|
||||
}
|
||||
|
||||
if (eItem != null && eItem.GetTemplate().GetGemProperties() != 0)
|
||||
{
|
||||
for (var i = 0; i < GetBagSize(); ++i)
|
||||
{
|
||||
pItem = m_bagslot[i];
|
||||
if (pItem != null && pItem != eItem && pItem.GetSocketColor(0) != 0)
|
||||
count += pItem.GetGemCountWithID(item);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public uint GetItemCountWithLimitCategory(uint limitCategory, Item skipItem)
|
||||
{
|
||||
uint count = 0;
|
||||
for (uint i = 0; i < GetBagSize(); ++i)
|
||||
{
|
||||
Item pItem = m_bagslot[i];
|
||||
if (pItem != null)
|
||||
{
|
||||
if (pItem != skipItem)
|
||||
{
|
||||
ItemTemplate pProto = pItem.GetTemplate();
|
||||
if (pProto != null)
|
||||
if (pProto.GetItemLimitCategory() == limitCategory)
|
||||
count += m_bagslot[i].GetCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
byte GetSlotByItemGUID(ObjectGuid guid)
|
||||
{
|
||||
for (byte i = 0; i < GetBagSize(); ++i)
|
||||
if (m_bagslot[i] != null)
|
||||
if (m_bagslot[i].GetGUID() == guid)
|
||||
return i;
|
||||
|
||||
return ItemConst.NullSlot;
|
||||
}
|
||||
|
||||
public Item GetItemByPos(byte slot)
|
||||
{
|
||||
if (slot < GetBagSize())
|
||||
return m_bagslot[slot];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public uint GetBagSize() { return GetUInt32Value(ContainerFields.NumSlots); }
|
||||
|
||||
public static Item NewItemOrBag(ItemTemplate proto)
|
||||
{
|
||||
return (proto.GetInventoryType() == InventoryType.Bag) ? new Bag() : new Item();
|
||||
}
|
||||
|
||||
Item[] m_bagslot = new Item[36];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.DataStorage;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class ItemEnchantment
|
||||
{
|
||||
static ItemEnchantment()
|
||||
{
|
||||
RandomItemEnch = new EnchantmentStore();
|
||||
}
|
||||
|
||||
public static void LoadRandomEnchantmentsTable()
|
||||
{
|
||||
// for reload case
|
||||
RandomItemEnch[ItemRandomEnchantmentType.Property].Clear();
|
||||
RandomItemEnch[ItemRandomEnchantmentType.Suffix].Clear();
|
||||
|
||||
// 0 1 2 3
|
||||
SQLResult result = DB.World.Query("SELECT entry, type, ench, chance FROM item_enchantment_template");
|
||||
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Loaded 0 Item Enchantment definitions. DB table `item_enchantment_template` is empty.");
|
||||
}
|
||||
uint count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
uint entry = result.Read<uint>(0);
|
||||
ItemRandomEnchantmentType type = (ItemRandomEnchantmentType)result.Read<byte>(1);
|
||||
uint ench = result.Read<uint>(2);
|
||||
float chance = result.Read<float>(3);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ItemRandomEnchantmentType.Property:
|
||||
if (!CliDB.ItemRandomPropertiesStorage.ContainsKey(ench))
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Property {0} used in `item_enchantment_template` by entry {1} doesn't have exist in ItemRandomProperties.db2", ench, entry);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case ItemRandomEnchantmentType.Suffix:
|
||||
if (!CliDB.ItemRandomSuffixStorage.ContainsKey(ench))
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Suffix {0} used in `item_enchantment_template` by entry {1} doesn't have exist in ItemRandomSuffix.db2", ench, entry);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case ItemRandomEnchantmentType.BonusList:
|
||||
if (Global.DB2Mgr.GetItemBonusList(ench) == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Bonus list {0} used in `item_enchantment_template` by entry {1} doesn't have exist in ItemBonus.db2", ench, entry);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.outError(LogFilter.Sql, "Invalid random enchantment type specified in `item_enchantment_template` table for `entry` {0} `ench` {1}", entry, ench);
|
||||
break;
|
||||
}
|
||||
|
||||
if (chance < 0.000001f || chance > 100.0f)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Random item enchantment for entry {0} type {1} ench {2} has invalid chance {3}", entry, type, ench, chance);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ItemRandomEnchantmentType.Property:
|
||||
RandomItemEnch[ItemRandomEnchantmentType.Property].Add(entry, new EnchStoreItem(type, ench, chance));
|
||||
break;
|
||||
case ItemRandomEnchantmentType.Suffix:
|
||||
case ItemRandomEnchantmentType.BonusList: // random bonus lists use RandomSuffix field in Item-sparse.db2
|
||||
RandomItemEnch[ItemRandomEnchantmentType.Suffix].Add(entry, new EnchStoreItem(type, ench, chance));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
++count;
|
||||
} while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.Player, "Loaded {0} Item Enchantment definitions", count);
|
||||
}
|
||||
|
||||
public static ItemRandomEnchantmentId GetItemEnchantMod(int entry, ItemRandomEnchantmentType type)
|
||||
{
|
||||
if (entry == 0)
|
||||
return ItemRandomEnchantmentId.Empty;
|
||||
|
||||
if (entry == -1)
|
||||
return ItemRandomEnchantmentId.Empty;
|
||||
|
||||
var tab = RandomItemEnch[type].LookupByKey(entry);
|
||||
if (tab == null)
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Item RandomProperty / RandomSuffix id #{0} used in `item_template` but it does not have records in `item_enchantment_template` table.", entry);
|
||||
return ItemRandomEnchantmentId.Empty;
|
||||
}
|
||||
|
||||
var selectedItem = tab.SelectRandomElementByWeight(enchant => enchant.chance);
|
||||
|
||||
return new ItemRandomEnchantmentId(selectedItem.type, selectedItem.ench);
|
||||
}
|
||||
|
||||
public static ItemRandomEnchantmentId GenerateItemRandomPropertyId(uint item_id)
|
||||
{
|
||||
ItemTemplate itemProto = Global.ObjectMgr.GetItemTemplate(item_id);
|
||||
if (itemProto == null)
|
||||
return ItemRandomEnchantmentId.Empty;
|
||||
|
||||
// item must have one from this field values not null if it can have random enchantments
|
||||
if (itemProto.GetRandomProperty() == 0 && itemProto.GetRandomSuffix() == 0)
|
||||
return ItemRandomEnchantmentId.Empty;
|
||||
|
||||
// item can have not null only one from field values
|
||||
if (itemProto.GetRandomProperty() != 0 && itemProto.GetRandomSuffix() != 0)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Item template {0} have RandomProperty == {1} and RandomSuffix == {2}, but must have one from field =0", itemProto.GetId(), itemProto.GetRandomProperty(), itemProto.GetRandomSuffix());
|
||||
return ItemRandomEnchantmentId.Empty;
|
||||
}
|
||||
|
||||
// RandomProperty case
|
||||
if (itemProto.GetRandomProperty() != 0)
|
||||
return GetItemEnchantMod((int)itemProto.GetRandomProperty(), ItemRandomEnchantmentType.Property);
|
||||
// RandomSuffix case
|
||||
else
|
||||
return GetItemEnchantMod((int)itemProto.GetRandomSuffix(), ItemRandomEnchantmentType.Suffix);
|
||||
}
|
||||
|
||||
public static uint GenerateEnchSuffixFactor(uint item_id)
|
||||
{
|
||||
ItemTemplate itemProto = Global.ObjectMgr.GetItemTemplate(item_id);
|
||||
|
||||
if (itemProto == null)
|
||||
return 0;
|
||||
if (itemProto.GetRandomSuffix() == 0)
|
||||
return 0;
|
||||
|
||||
return GetRandomPropertyPoints(itemProto.GetBaseItemLevel(), itemProto.GetQuality(), itemProto.GetInventoryType(), itemProto.GetSubClass());
|
||||
}
|
||||
|
||||
public static uint GetRandomPropertyPoints(uint itemLevel, ItemQuality quality, InventoryType inventoryType, uint subClass)
|
||||
{
|
||||
uint propIndex;
|
||||
switch (inventoryType)
|
||||
{
|
||||
case InventoryType.Head:
|
||||
case InventoryType.Body:
|
||||
case InventoryType.Chest:
|
||||
case InventoryType.Legs:
|
||||
case InventoryType.Ranged:
|
||||
case InventoryType.Weapon2Hand:
|
||||
case InventoryType.Robe:
|
||||
case InventoryType.Thrown:
|
||||
propIndex = 0;
|
||||
break;
|
||||
case InventoryType.RangedRight:
|
||||
if ((ItemSubClassWeapon)subClass == ItemSubClassWeapon.Wand)
|
||||
propIndex = 3;
|
||||
else
|
||||
propIndex = 0;
|
||||
break;
|
||||
case InventoryType.Weapon:
|
||||
case InventoryType.WeaponMainhand:
|
||||
case InventoryType.WeaponOffhand:
|
||||
propIndex = 3;
|
||||
break;
|
||||
case InventoryType.Shoulders:
|
||||
case InventoryType.Waist:
|
||||
case InventoryType.Feet:
|
||||
case InventoryType.Hands:
|
||||
case InventoryType.Trinket:
|
||||
propIndex = 1;
|
||||
break;
|
||||
case InventoryType.Neck:
|
||||
case InventoryType.Wrists:
|
||||
case InventoryType.Finger:
|
||||
case InventoryType.Shield:
|
||||
case InventoryType.Cloak:
|
||||
case InventoryType.Holdable:
|
||||
propIndex = 2;
|
||||
break;
|
||||
case InventoryType.Relic:
|
||||
propIndex = 4;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
RandPropPointsRecord randPropPointsEntry = CliDB.RandPropPointsStorage.LookupByKey(itemLevel);
|
||||
if (randPropPointsEntry == null)
|
||||
return 0;
|
||||
|
||||
// Select rare/epic modifier
|
||||
switch (quality)
|
||||
{
|
||||
case ItemQuality.Uncommon:
|
||||
return randPropPointsEntry.UncommonPropertiesPoints[propIndex];
|
||||
case ItemQuality.Rare:
|
||||
case ItemQuality.Heirloom:
|
||||
return randPropPointsEntry.RarePropertiesPoints[propIndex];
|
||||
case ItemQuality.Epic:
|
||||
case ItemQuality.Legendary:
|
||||
case ItemQuality.Artifact:
|
||||
return randPropPointsEntry.EpicPropertiesPoints[propIndex];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static EnchantmentStore RandomItemEnch;
|
||||
|
||||
public class EnchStoreItem
|
||||
{
|
||||
public EnchStoreItem()
|
||||
{
|
||||
ench = 0;
|
||||
chance = 0;
|
||||
}
|
||||
public EnchStoreItem(ItemRandomEnchantmentType _type, uint _ench, float _chance)
|
||||
{
|
||||
type = _type;
|
||||
ench = _ench;
|
||||
chance = _chance;
|
||||
}
|
||||
|
||||
public ItemRandomEnchantmentType type;
|
||||
public uint ench;
|
||||
public float chance;
|
||||
}
|
||||
|
||||
class EnchantmentStore
|
||||
{
|
||||
public EnchantmentStore()
|
||||
{
|
||||
_data[(byte)ItemRandomEnchantmentType.Property] = new MultiMap<uint, EnchStoreItem>();
|
||||
_data[(byte)ItemRandomEnchantmentType.Suffix] = new MultiMap<uint, EnchStoreItem>();
|
||||
}
|
||||
|
||||
public MultiMap<uint, EnchStoreItem> this[ItemRandomEnchantmentType type]
|
||||
{
|
||||
get
|
||||
{
|
||||
//(type != ItemRandomEnchantmentType.BonusList, "Random bonus lists do not have their own storage, use Suffix for them");
|
||||
return _data[(byte)type];
|
||||
}
|
||||
}
|
||||
|
||||
MultiMap<uint, EnchStoreItem>[] _data = new MultiMap<uint, EnchStoreItem>[2];
|
||||
}
|
||||
}
|
||||
|
||||
public struct ItemRandomEnchantmentId
|
||||
{
|
||||
public static ItemRandomEnchantmentId Empty = default(ItemRandomEnchantmentId);
|
||||
|
||||
public ItemRandomEnchantmentId(ItemRandomEnchantmentType type, uint id)
|
||||
{
|
||||
Type = type;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public ItemRandomEnchantmentType Type;
|
||||
public uint Id;
|
||||
}
|
||||
|
||||
public enum ItemRandomEnchantmentType
|
||||
{
|
||||
Property = 0,
|
||||
Suffix = 1,
|
||||
BonusList = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class ItemTemplate
|
||||
{
|
||||
public ItemTemplate(ItemRecord item, ItemSparseRecord sparse)
|
||||
{
|
||||
BasicData = item;
|
||||
ExtendedData = sparse;
|
||||
|
||||
Specializations[0] = new BitArray((int)Class.Max * PlayerConst.MaxSpecializations);
|
||||
Specializations[1] = new BitArray((int)Class.Max * PlayerConst.MaxSpecializations);
|
||||
Specializations[2] = new BitArray((int)Class.Max * PlayerConst.MaxSpecializations);
|
||||
}
|
||||
|
||||
public string GetName(LocaleConstant locale = SharedConst.DefaultLocale)
|
||||
{
|
||||
return ExtendedData.Name[locale];
|
||||
}
|
||||
|
||||
public bool CanChangeEquipStateInCombat()
|
||||
{
|
||||
switch (GetInventoryType())
|
||||
{
|
||||
case InventoryType.Relic:
|
||||
case InventoryType.Shield:
|
||||
case InventoryType.Holdable:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (GetClass())
|
||||
{
|
||||
case ItemClass.Weapon:
|
||||
case ItemClass.Projectile:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public SkillType GetSkill()
|
||||
{
|
||||
SkillType[] item_weapon_skills =
|
||||
{
|
||||
SkillType.Axes, SkillType.TwoHandedAxes, SkillType.Bows, SkillType.Guns, SkillType.Maces,
|
||||
SkillType.TwoHandedMaces, SkillType.Polearms, SkillType.Swords, SkillType.TwoHandedSwords, SkillType.Warglaives,
|
||||
SkillType.Staves, 0, 0, SkillType.FistWeapons, 0,
|
||||
SkillType.Daggers, 0, 0, SkillType.Crossbows, SkillType.Wands,
|
||||
SkillType.Fishing
|
||||
};
|
||||
|
||||
SkillType[] item_armor_skills =
|
||||
{
|
||||
0, SkillType.Cloth, SkillType.Leather, SkillType.Mail, SkillType.PlateMail, 0, SkillType.Shield, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
|
||||
switch (GetClass())
|
||||
{
|
||||
case ItemClass.Weapon:
|
||||
if (GetSubClass() >= (int)ItemSubClassWeapon.Max)
|
||||
return 0;
|
||||
else
|
||||
return item_weapon_skills[GetSubClass()];
|
||||
|
||||
case ItemClass.Armor:
|
||||
if (GetSubClass() >= (int)ItemSubClassArmor.Max)
|
||||
return 0;
|
||||
else
|
||||
return item_armor_skills[GetSubClass()];
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetArmor(uint itemLevel)
|
||||
{
|
||||
ItemQuality quality = GetQuality() != ItemQuality.Heirloom ? GetQuality() : ItemQuality.Rare;
|
||||
if (quality > ItemQuality.Artifact)
|
||||
return 0;
|
||||
|
||||
// all items but shields
|
||||
if (GetClass() != ItemClass.Armor || GetSubClass() != (uint)ItemSubClassArmor.Shield)
|
||||
{
|
||||
ItemArmorQualityRecord armorQuality = CliDB.ItemArmorQualityStorage.LookupByKey(itemLevel);
|
||||
ItemArmorTotalRecord armorTotal = CliDB.ItemArmorTotalStorage.LookupByKey(itemLevel);
|
||||
if (armorQuality == null || armorTotal == null)
|
||||
return 0;
|
||||
|
||||
InventoryType inventoryType = GetInventoryType();
|
||||
if (inventoryType == InventoryType.Robe)
|
||||
inventoryType = InventoryType.Chest;
|
||||
|
||||
ArmorLocationRecord location = CliDB.ArmorLocationStorage.LookupByKey(inventoryType);
|
||||
if (location == null)
|
||||
return 0;
|
||||
|
||||
if (GetSubClass() < (uint)ItemSubClassArmor.Cloth || GetSubClass() > (uint)ItemSubClassArmor.Plate)
|
||||
return 0;
|
||||
|
||||
return (uint)(armorQuality.QualityMod[(int)quality] * armorTotal.Value[GetSubClass() - 1] * location.Modifier[GetSubClass() - 1] + 0.5f);
|
||||
}
|
||||
|
||||
// shields
|
||||
ItemArmorShieldRecord shield = CliDB.ItemArmorShieldStorage.LookupByKey(itemLevel);
|
||||
if (shield == null)
|
||||
return 0;
|
||||
|
||||
return (uint)(shield.Quality[(int)quality] + 0.5f);
|
||||
}
|
||||
|
||||
public void GetDamage(uint itemLevel, out float minDamage, out float maxDamage)
|
||||
{
|
||||
minDamage = maxDamage = 0.0f;
|
||||
ItemQuality quality = GetQuality() != ItemQuality.Heirloom ? GetQuality() : ItemQuality.Rare;
|
||||
if (GetClass() != ItemClass.Weapon || quality > ItemQuality.Artifact)
|
||||
return;
|
||||
|
||||
// get the right store here
|
||||
if (GetInventoryType() > InventoryType.RangedRight)
|
||||
return;
|
||||
|
||||
float dps = 0.0f;
|
||||
switch (GetInventoryType())
|
||||
{
|
||||
case InventoryType.Ammo:
|
||||
dps = CliDB.ItemDamageAmmoStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
break;
|
||||
case InventoryType.Weapon2Hand:
|
||||
if (GetFlags2().HasAnyFlag(ItemFlags2.CasterWeapon))
|
||||
dps = CliDB.ItemDamageTwoHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
else
|
||||
dps = CliDB.ItemDamageTwoHandStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
break;
|
||||
case InventoryType.Ranged:
|
||||
case InventoryType.Thrown:
|
||||
case InventoryType.RangedRight:
|
||||
switch ((ItemSubClassWeapon)GetSubClass())
|
||||
{
|
||||
case ItemSubClassWeapon.Wand:
|
||||
dps = CliDB.ItemDamageOneHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
break;
|
||||
case ItemSubClassWeapon.Bow:
|
||||
case ItemSubClassWeapon.Gun:
|
||||
case ItemSubClassWeapon.Crossbow:
|
||||
if (GetFlags2().HasAnyFlag(ItemFlags2.CasterWeapon))
|
||||
dps = CliDB.ItemDamageTwoHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
else
|
||||
dps = CliDB.ItemDamageTwoHandStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case InventoryType.Weapon:
|
||||
case InventoryType.WeaponMainhand:
|
||||
case InventoryType.WeaponOffhand:
|
||||
if (GetFlags2().HasAnyFlag(ItemFlags2.CasterWeapon))
|
||||
dps = CliDB.ItemDamageOneHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
else
|
||||
dps = CliDB.ItemDamageOneHandStorage.LookupByKey(itemLevel).DPS[(int)quality];
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
float avgDamage = dps * GetDelay() * 0.001f;
|
||||
minDamage = (GetStatScalingFactor() * -0.5f + 1.0f) * avgDamage;
|
||||
maxDamage = (float)Math.Floor(avgDamage * (GetStatScalingFactor() * 0.5f + 1.0f) + 0.5f);
|
||||
}
|
||||
|
||||
public bool IsUsableByLootSpecialization(Player player, bool alwaysAllowBoundToAccount)
|
||||
{
|
||||
if (GetFlags().HasAnyFlag(ItemFlags.IsBoundToAccount) && alwaysAllowBoundToAccount)
|
||||
return true;
|
||||
|
||||
uint spec = player.GetUInt32Value(PlayerFields.LootSpecId);
|
||||
if (spec == 0)
|
||||
spec = player.GetUInt32Value(PlayerFields.CurrentSpecId);
|
||||
if (spec == 0)
|
||||
spec = player.GetDefaultSpecId();
|
||||
|
||||
ChrSpecializationRecord chrSpecialization = CliDB.ChrSpecializationStorage.LookupByKey(spec);
|
||||
if (chrSpecialization == null)
|
||||
return false;
|
||||
|
||||
int levelIndex = 0;
|
||||
if (player.getLevel() >= 110)
|
||||
levelIndex = 2;
|
||||
else if (player.getLevel() > 40)
|
||||
levelIndex = 1;
|
||||
|
||||
return Specializations[levelIndex].Get(CalculateItemSpecBit(chrSpecialization));
|
||||
}
|
||||
|
||||
public static int CalculateItemSpecBit(ChrSpecializationRecord spec)
|
||||
{
|
||||
return (int)((spec.ClassID - 1) * PlayerConst.MaxSpecializations + spec.OrderIndex);
|
||||
}
|
||||
|
||||
public uint GetId() { return BasicData.Id; }
|
||||
public ItemClass GetClass() { return (ItemClass)BasicData.Class; }
|
||||
public uint GetSubClass() { return BasicData.SubClass; }
|
||||
public ItemQuality GetQuality() { return (ItemQuality)ExtendedData.Quality; }
|
||||
public ItemFlags GetFlags() { return (ItemFlags)ExtendedData.Flags[0]; }
|
||||
public ItemFlags2 GetFlags2() { return (ItemFlags2)ExtendedData.Flags[1]; }
|
||||
public ItemFlags3 GetFlags3() { return (ItemFlags3)ExtendedData.Flags[2]; }
|
||||
public float GetUnk1() { return ExtendedData.Unk1; }
|
||||
public float GetUnk2() { return ExtendedData.Unk2; }
|
||||
public uint GetBuyCount() { return Math.Max(ExtendedData.BuyCount, 1u); }
|
||||
public uint GetBuyPrice() { return ExtendedData.BuyPrice; }
|
||||
public uint GetSellPrice() { return ExtendedData.SellPrice; }
|
||||
public InventoryType GetInventoryType() { return (InventoryType)ExtendedData.inventoryType; }
|
||||
public int GetAllowableClass() { return ExtendedData.AllowableClass; }
|
||||
public int GetAllowableRace() { return ExtendedData.AllowableRace; }
|
||||
public uint GetBaseItemLevel() { return ExtendedData.ItemLevel; }
|
||||
public int GetBaseRequiredLevel() { return ExtendedData.RequiredLevel; }
|
||||
public uint GetRequiredSkill() { return ExtendedData.RequiredSkill; }
|
||||
public uint GetRequiredSkillRank() { return ExtendedData.RequiredSkillRank; }
|
||||
public uint GetRequiredSpell() { return ExtendedData.RequiredSpell; }
|
||||
public uint GetRequiredReputationFaction() { return ExtendedData.RequiredReputationFaction; }
|
||||
public uint GetRequiredReputationRank() { return ExtendedData.RequiredReputationRank; }
|
||||
public uint GetMaxCount() { return ExtendedData.MaxCount; }
|
||||
public uint GetContainerSlots() { return ExtendedData.ContainerSlots; }
|
||||
public int GetItemStatType(uint index)
|
||||
{
|
||||
Contract.Assert(index < ItemConst.MaxStats);
|
||||
return ExtendedData.ItemStatType[index];
|
||||
}
|
||||
public int GetItemStatValue(uint index)
|
||||
{
|
||||
Contract.Assert(index < ItemConst.MaxStats);
|
||||
return ExtendedData.ItemStatValue[index];
|
||||
}
|
||||
public int GetItemStatAllocation(uint index)
|
||||
{
|
||||
Contract.Assert(index < ItemConst.MaxStats);
|
||||
return ExtendedData.ItemStatAllocation[index];
|
||||
}
|
||||
public float GetItemStatSocketCostMultiplier(uint index)
|
||||
{
|
||||
Contract.Assert(index < ItemConst.MaxStats);
|
||||
return ExtendedData.ItemStatSocketCostMultiplier[index];
|
||||
}
|
||||
public uint GetScalingStatDistribution() { return ExtendedData.ScalingStatDistribution; }
|
||||
public uint GetDamageType() { return ExtendedData.DamageType; }
|
||||
public uint GetDelay() { return ExtendedData.Delay; }
|
||||
public float GetRangedModRange() { return ExtendedData.RangedModRange; }
|
||||
public ItemBondingType GetBonding() { return (ItemBondingType)ExtendedData.Bonding; }
|
||||
public uint GetPageText() { return ExtendedData.PageText; }
|
||||
public uint GetStartQuest() { return ExtendedData.StartQuest; }
|
||||
public uint GetLockID() { return ExtendedData.LockID; }
|
||||
public uint GetRandomProperty() { return ExtendedData.RandomProperty; }
|
||||
public uint GetRandomSuffix() { return ExtendedData.RandomSuffix; }
|
||||
public uint GetItemSet() { return ExtendedData.ItemSet; }
|
||||
public uint GetArea() { return ExtendedData.Area; }
|
||||
public uint GetMap() { return ExtendedData.Map; }
|
||||
public BagFamilyMask GetBagFamily() { return (BagFamilyMask)ExtendedData.BagFamily; }
|
||||
public uint GetTotemCategory() { return ExtendedData.TotemCategory; }
|
||||
public SocketColor GetSocketColor(uint index)
|
||||
{
|
||||
Contract.Assert(index < ItemConst.MaxGemSockets);
|
||||
return (SocketColor)ExtendedData.SocketColor[index];
|
||||
}
|
||||
public uint GetSocketBonus() { return ExtendedData.SocketBonus; }
|
||||
public uint GetGemProperties() { return ExtendedData.GemProperties; }
|
||||
public float GetArmorDamageModifier() { return ExtendedData.ArmorDamageModifier; }
|
||||
public uint GetDuration() { return ExtendedData.Duration; }
|
||||
public uint GetItemLimitCategory() { return ExtendedData.ItemLimitCategory; }
|
||||
public HolidayIds GetHolidayID() { return (HolidayIds)ExtendedData.HolidayID; }
|
||||
public float GetStatScalingFactor() { return ExtendedData.StatScalingFactor; }
|
||||
public byte GetArtifactID() { return ExtendedData.ArtifactID; }
|
||||
|
||||
public bool IsCurrencyToken() { return (GetBagFamily() & BagFamilyMask.CurrencyTokens) != 0; }
|
||||
|
||||
public uint GetMaxStackSize()
|
||||
{
|
||||
return (ExtendedData.Stackable == 2147483647 || ExtendedData.Stackable <= 0) ? (0x7FFFFFFF - 1) : ExtendedData.Stackable;
|
||||
}
|
||||
|
||||
public bool IsPotion() { return GetClass() == ItemClass.Consumable && GetSubClass() == (uint)ItemSubClassConsumable.Potion; }
|
||||
public bool IsVellum() { return GetClass() == ItemClass.TradeGoods && GetSubClass() == (uint)ItemSubClassTradeGoods.Enchantment; }
|
||||
public bool IsConjuredConsumable() { return GetClass() == ItemClass.Consumable && GetFlags().HasAnyFlag(ItemFlags.Conjured); }
|
||||
public bool IsCraftingReagent() { return GetFlags2().HasAnyFlag(ItemFlags2.UsedInATradeskill); }
|
||||
|
||||
public bool IsRangedWeapon()
|
||||
{
|
||||
return GetClass() == ItemClass.Weapon || GetSubClass() == (uint)ItemSubClassWeapon.Bow ||
|
||||
GetSubClass() == (uint)ItemSubClassWeapon.Gun || GetSubClass() == (uint)ItemSubClassWeapon.Crossbow;
|
||||
}
|
||||
|
||||
public uint MaxDurability;
|
||||
public List<ItemEffectRecord> Effects = new List<ItemEffectRecord>();
|
||||
|
||||
// extra fields, not part of db2 files
|
||||
public uint ScriptId;
|
||||
public uint DisenchantID;
|
||||
public uint RequiredDisenchantSkill;
|
||||
public uint FoodType;
|
||||
public uint MinMoneyLoot;
|
||||
public uint MaxMoneyLoot;
|
||||
public ItemFlagsCustom FlagsCu;
|
||||
public float SpellPPMRate;
|
||||
public BitArray[] Specializations = new BitArray[3];
|
||||
public uint ItemSpecClassMask;
|
||||
|
||||
protected ItemRecord BasicData;
|
||||
protected ItemSparseRecord ExtendedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using System;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public struct ObjectGuid : IEquatable<ObjectGuid>
|
||||
{
|
||||
public static ObjectGuid Empty = new ObjectGuid();
|
||||
public static ObjectGuid TradeItem = Create(HighGuid.Uniq, 10ul);
|
||||
|
||||
public ObjectGuid(ulong high, ulong low)
|
||||
{
|
||||
_low = low;
|
||||
_high = high;
|
||||
}
|
||||
|
||||
public static ObjectGuid Create(HighGuid type, ulong counter)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case HighGuid.Uniq:
|
||||
case HighGuid.Party:
|
||||
case HighGuid.WowAccount:
|
||||
case HighGuid.BNetAccount:
|
||||
case HighGuid.GMTask:
|
||||
case HighGuid.RaidGroup:
|
||||
case HighGuid.Spell:
|
||||
case HighGuid.Mail:
|
||||
case HighGuid.UserRouter:
|
||||
case HighGuid.PVPQueueGroup:
|
||||
case HighGuid.UserClient:
|
||||
case HighGuid.UniqUserClient:
|
||||
case HighGuid.BattlePet:
|
||||
case HighGuid.CommerceObj:
|
||||
case HighGuid.ClientSession:
|
||||
return GlobalCreate(type, counter);
|
||||
case HighGuid.Player:
|
||||
case HighGuid.Item: // This is not exactly correct, there are 2 more unknown parts in highguid: (high >> 10 & 0xFF), (high >> 18 & 0xFFFFFF)
|
||||
case HighGuid.Guild:
|
||||
case HighGuid.Transport:
|
||||
return RealmSpecificCreate(type, counter);
|
||||
default:
|
||||
Log.outError(LogFilter.Server, "This guid type cannot be constructed using Create(HighGuid: {0} ulong counter).", type);
|
||||
break;
|
||||
}
|
||||
return ObjectGuid.Empty;
|
||||
}
|
||||
public static ObjectGuid Create(HighGuid type, uint mapId, uint entry, ulong counter)
|
||||
{
|
||||
if (type == HighGuid.Transport)
|
||||
return ObjectGuid.Empty;
|
||||
|
||||
return MapSpecificCreate(type, 0, (ushort)mapId, 0, entry, counter);
|
||||
}
|
||||
|
||||
public static ObjectGuid Create(HighGuid type, SpellCastSource subType, uint mapId, uint entry, ulong counter) { return MapSpecificCreate(type, (byte)subType, (ushort)mapId, 0, entry, counter); }
|
||||
|
||||
static ObjectGuid GlobalCreate(HighGuid type, ulong counter)
|
||||
{
|
||||
return new ObjectGuid(((ulong)type << 58), counter);
|
||||
}
|
||||
static ObjectGuid RealmSpecificCreate(HighGuid type, ulong counter)
|
||||
{
|
||||
return new ObjectGuid(((ulong)type << 58 | (ulong)Global.WorldMgr.GetRealm().Id.Realm << 42), counter);
|
||||
}
|
||||
static ObjectGuid MapSpecificCreate(HighGuid type, byte subType, ushort mapId, uint serverId, uint entry, ulong counter)
|
||||
{
|
||||
return new ObjectGuid((((ulong)type << 58) | ((ulong)(Global.WorldMgr.GetRealm().Id.Realm & 0x1FFF) << 42) | ((ulong)(mapId & 0x1FFF) << 29) | ((ulong)(entry & 0x7FFFFF) << 6) | ((ulong)subType & 0x3F)),
|
||||
(((ulong)(serverId & 0xFFFFFF) << 40) | (counter & 0xFFFFFFFFFF)));
|
||||
}
|
||||
|
||||
public byte[] GetRawValue()
|
||||
{
|
||||
byte[] temp = new byte[16];
|
||||
var hiBytes = BitConverter.GetBytes(_high);
|
||||
var lowBytes = BitConverter.GetBytes(_low);
|
||||
for (var i = 0; i < temp.Length / 2; ++i)
|
||||
{
|
||||
temp[i] = hiBytes[i];
|
||||
temp[8 + i] = lowBytes[i];
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
public void SetRawValue(byte[] bytes)
|
||||
{
|
||||
_high = BitConverter.ToUInt64(bytes, 0);
|
||||
_low = BitConverter.ToUInt64(bytes, 8);
|
||||
}
|
||||
|
||||
public void SetRawValue(ulong high, ulong low) { _high = high; _low = low; }
|
||||
public void Clear() { _high = 0; _low = 0; }
|
||||
public ulong GetHighValue()
|
||||
{
|
||||
return _high;
|
||||
}
|
||||
public ulong GetLowValue()
|
||||
{
|
||||
return _low;
|
||||
}
|
||||
|
||||
public HighGuid GetHigh() { return (HighGuid)(_high >> 58); }
|
||||
byte GetSubType() { return (byte)(_high & 0x3F); }
|
||||
uint GetRealmId() { return (uint)((_high >> 42) & 0x1FFF); }
|
||||
uint GetServerId() { return (uint)((_low >> 40) & 0x1FFF); }
|
||||
uint GetMapId() { return (uint)((_high >> 29) & 0x1FFF); }
|
||||
public uint GetEntry() { return (uint)((_high >> 6) & 0x7FFFFF); }
|
||||
public ulong GetCounter() { return _low & 0xFFFFFFFFFF; }
|
||||
|
||||
public bool IsEmpty() { return _low == 0 && _high == 0; }
|
||||
public bool IsCreature() { return GetHigh() == HighGuid.Creature; }
|
||||
public bool IsPet() { return GetHigh() == HighGuid.Pet; }
|
||||
public bool IsVehicle() { return GetHigh() == HighGuid.Vehicle; }
|
||||
public bool IsCreatureOrPet() { return IsCreature() || IsPet(); }
|
||||
public bool IsCreatureOrVehicle() { return IsCreature() || IsVehicle(); }
|
||||
public bool IsAnyTypeCreature() { return IsCreature() || IsPet() || IsVehicle(); }
|
||||
public bool IsPlayer() { return !IsEmpty() && GetHigh() == HighGuid.Player; }
|
||||
public bool IsUnit() { return IsAnyTypeCreature() || IsPlayer(); }
|
||||
public bool IsItem() { return GetHigh() == HighGuid.Item; }
|
||||
public bool IsGameObject() { return GetHigh() == HighGuid.GameObject; }
|
||||
public bool IsDynamicObject() { return GetHigh() == HighGuid.DynamicObject; }
|
||||
public bool IsCorpse() { return GetHigh() == HighGuid.Corpse; }
|
||||
public bool IsAreaTrigger() { return GetHigh() == HighGuid.AreaTrigger; }
|
||||
public bool IsMOTransport() { return GetHigh() == HighGuid.Transport; }
|
||||
public bool IsAnyTypeGameObject() { return IsGameObject() || IsMOTransport(); }
|
||||
public bool IsParty() { return GetHigh() == HighGuid.Party; }
|
||||
public bool IsGuild() { return GetHigh() == HighGuid.Guild; }
|
||||
bool IsSceneObject() { return GetHigh() == HighGuid.SceneObject; }
|
||||
bool IsConversation() { return GetHigh() == HighGuid.Conversation; }
|
||||
bool IsCast() { return GetHigh() == HighGuid.Cast; }
|
||||
|
||||
public TypeId GetTypeId() { return GetTypeId(GetHigh()); }
|
||||
bool HasEntry() { return HasEntry(GetHigh()); }
|
||||
|
||||
public static bool operator <(ObjectGuid left, ObjectGuid right)
|
||||
{
|
||||
if (left._high < right._high)
|
||||
return true;
|
||||
else if (left._high > right._high)
|
||||
return false;
|
||||
|
||||
return left._low < right._low;
|
||||
}
|
||||
public static bool operator >(ObjectGuid left, ObjectGuid right)
|
||||
{
|
||||
if (left._high > right._high)
|
||||
return true;
|
||||
else if (left._high < right._high)
|
||||
return false;
|
||||
|
||||
return left._low > right._low;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string str = string.Format("GUID Full: 0x{0}, Type: {1}", _low, GetHigh());
|
||||
if (HasEntry())
|
||||
str += (IsPet() ? " Pet number: " : " Entry: ") + GetEntry() + " ";
|
||||
|
||||
str += " Low: " + GetCounter();
|
||||
return str;
|
||||
}
|
||||
|
||||
public static bool operator ==(ObjectGuid first, ObjectGuid other)
|
||||
{
|
||||
if (ReferenceEquals(first, other))
|
||||
return true;
|
||||
|
||||
if ((object)first == null || (object)other == null)
|
||||
return false;
|
||||
|
||||
return first.Equals(other);
|
||||
}
|
||||
|
||||
public static bool operator !=(ObjectGuid first, ObjectGuid other)
|
||||
{
|
||||
return !(first == other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj != null && obj is ObjectGuid && Equals((ObjectGuid)obj);
|
||||
}
|
||||
|
||||
public bool Equals(ObjectGuid other)
|
||||
{
|
||||
return other._high == _high && other._low == _low;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return new { _high, _low }.GetHashCode();
|
||||
}
|
||||
|
||||
//Static Methods
|
||||
static TypeId GetTypeId(HighGuid high)
|
||||
{
|
||||
switch (high)
|
||||
{
|
||||
case HighGuid.Item:
|
||||
return TypeId.Item;
|
||||
case HighGuid.Creature:
|
||||
case HighGuid.Pet:
|
||||
case HighGuid.Vehicle:
|
||||
return TypeId.Unit;
|
||||
case HighGuid.Player:
|
||||
return TypeId.Player;
|
||||
case HighGuid.GameObject:
|
||||
case HighGuid.Transport:
|
||||
return TypeId.GameObject;
|
||||
case HighGuid.DynamicObject:
|
||||
return TypeId.DynamicObject;
|
||||
case HighGuid.Corpse:
|
||||
return TypeId.Corpse;
|
||||
case HighGuid.AreaTrigger:
|
||||
return TypeId.AreaTrigger;
|
||||
case HighGuid.SceneObject:
|
||||
return TypeId.SceneObject;
|
||||
case HighGuid.Conversation:
|
||||
return TypeId.Conversation;
|
||||
default:
|
||||
return TypeId.Object;
|
||||
}
|
||||
}
|
||||
static bool HasEntry(HighGuid high)
|
||||
{
|
||||
switch (high)
|
||||
{
|
||||
case HighGuid.GameObject:
|
||||
case HighGuid.Creature:
|
||||
case HighGuid.Pet:
|
||||
case HighGuid.Vehicle:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static bool IsMapSpecific(HighGuid high)
|
||||
{
|
||||
switch (high)
|
||||
{
|
||||
case HighGuid.Conversation:
|
||||
case HighGuid.Creature:
|
||||
case HighGuid.Vehicle:
|
||||
case HighGuid.Pet:
|
||||
case HighGuid.GameObject:
|
||||
case HighGuid.DynamicObject:
|
||||
case HighGuid.AreaTrigger:
|
||||
case HighGuid.Corpse:
|
||||
case HighGuid.LootObject:
|
||||
case HighGuid.SceneObject:
|
||||
case HighGuid.Scenario:
|
||||
case HighGuid.AIGroup:
|
||||
case HighGuid.DynamicDoor:
|
||||
case HighGuid.Vignette:
|
||||
case HighGuid.CallForHelp:
|
||||
case HighGuid.AIResource:
|
||||
case HighGuid.AILock:
|
||||
case HighGuid.AILockTicket:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static bool IsRealmSpecific(HighGuid high)
|
||||
{
|
||||
switch (high)
|
||||
{
|
||||
case HighGuid.Player:
|
||||
case HighGuid.Item:
|
||||
case HighGuid.Transport:
|
||||
case HighGuid.Guild:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static bool IsGlobal(HighGuid high)
|
||||
{
|
||||
switch (high)
|
||||
{
|
||||
case HighGuid.Uniq:
|
||||
case HighGuid.Party:
|
||||
case HighGuid.WowAccount:
|
||||
case HighGuid.BNetAccount:
|
||||
case HighGuid.GMTask:
|
||||
case HighGuid.RaidGroup:
|
||||
case HighGuid.Spell:
|
||||
case HighGuid.Mail:
|
||||
case HighGuid.UserRouter:
|
||||
case HighGuid.PVPQueueGroup:
|
||||
case HighGuid.UserClient:
|
||||
case HighGuid.UniqUserClient:
|
||||
case HighGuid.BattlePet:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ulong _low;
|
||||
ulong _high;
|
||||
}
|
||||
|
||||
public class ObjectGuidGenerator
|
||||
{
|
||||
public ObjectGuidGenerator(HighGuid highGuid, ulong start = 1)
|
||||
{
|
||||
_highGuid = highGuid;
|
||||
_nextGuid = start;
|
||||
}
|
||||
|
||||
public void Set(ulong val) { _nextGuid = val; }
|
||||
|
||||
public ulong Generate()
|
||||
{
|
||||
if (_nextGuid >= ulong.MaxValue - 1)
|
||||
HandleCounterOverflow();
|
||||
return _nextGuid++;
|
||||
}
|
||||
|
||||
public ulong GetNextAfterMaxUsed() { return _nextGuid; }
|
||||
|
||||
void HandleCounterOverflow()
|
||||
{
|
||||
Log.outFatal(LogFilter.Server, "{0} guid overflow!! Can't continue, shutting down server. ", _highGuid);
|
||||
Global.WorldMgr.StopNow();
|
||||
}
|
||||
|
||||
ulong _nextGuid;
|
||||
HighGuid _highGuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.GameMath;
|
||||
using Game.Maps;
|
||||
using System;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class Position
|
||||
{
|
||||
public Position(float x = 0f, float y = 0f, float z = 0f, float o = 0f)
|
||||
{
|
||||
posX = x;
|
||||
posY = y;
|
||||
posZ = z;
|
||||
Orientation = o;
|
||||
}
|
||||
|
||||
public float GetPositionX()
|
||||
{
|
||||
return posX;
|
||||
}
|
||||
public float GetPositionY()
|
||||
{
|
||||
return posY;
|
||||
}
|
||||
public float GetPositionZ()
|
||||
{
|
||||
return posZ;
|
||||
}
|
||||
public float GetOrientation()
|
||||
{
|
||||
return Orientation;
|
||||
}
|
||||
|
||||
public void Relocate(float x, float y)
|
||||
{
|
||||
posX = x;
|
||||
posY = y;
|
||||
}
|
||||
public void Relocate(float x, float y, float z)
|
||||
{
|
||||
posX = x;
|
||||
posY = y;
|
||||
posZ = z;
|
||||
}
|
||||
public void Relocate(float x, float y, float z, float o)
|
||||
{
|
||||
posX = x;
|
||||
posY = y;
|
||||
posZ = z;
|
||||
SetOrientation(o);
|
||||
}
|
||||
public void Relocate(Position loc)
|
||||
{
|
||||
Relocate(loc.posX, loc.posY, loc.posZ, loc.Orientation);
|
||||
}
|
||||
public void Relocate(Vector3 pos)
|
||||
{
|
||||
Relocate(pos.X, pos.Y, pos.Z);
|
||||
}
|
||||
public void RelocateOffset(Position offset)
|
||||
{
|
||||
posX = (float)(posX + (offset.posX * Math.Cos(Orientation) + offset.posY * Math.Sin(Orientation + MathFunctions.PI)));
|
||||
posY = (float)(posY + (offset.posY * Math.Cos(Orientation) + offset.posX * Math.Sin(Orientation)));
|
||||
posZ = posZ + offset.posZ;
|
||||
SetOrientation(Orientation + offset.Orientation);
|
||||
}
|
||||
|
||||
public bool IsPositionValid()
|
||||
{
|
||||
return GridDefines.IsValidMapCoord(posX, posY, posZ, Orientation);
|
||||
}
|
||||
|
||||
public float GetRelativeAngle(Position pos)
|
||||
{
|
||||
return GetAngle(pos) - Orientation;
|
||||
}
|
||||
public float GetRelativeAngle(float x, float y)
|
||||
{
|
||||
return GetAngle(x, y) - Orientation;
|
||||
}
|
||||
|
||||
public void GetPosition(out float x, out float y)
|
||||
{
|
||||
x = posX; y = posY;
|
||||
}
|
||||
public void GetPosition(out float x, out float y, out float z)
|
||||
{
|
||||
x = posX; y = posY; z = posZ;
|
||||
}
|
||||
public void GetPosition(out float x, out float y, out float z, out float o)
|
||||
{
|
||||
x = posX;
|
||||
y = posY;
|
||||
z = posZ;
|
||||
o = Orientation;
|
||||
}
|
||||
public Position GetPosition()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
public void GetPositionOffsetTo(Position endPos, out Position retOffset)
|
||||
{
|
||||
retOffset = new Position();
|
||||
|
||||
float dx = endPos.GetPositionX() - GetPositionX();
|
||||
float dy = endPos.GetPositionY() - GetPositionY();
|
||||
|
||||
retOffset.posX = (float)(dx * Math.Cos(GetOrientation()) + dy * Math.Sin(GetOrientation()));
|
||||
retOffset.posY = (float)(dy * Math.Cos(GetOrientation()) - dx * Math.Sin(GetOrientation()));
|
||||
retOffset.posZ = endPos.GetPositionZ() - GetPositionZ();
|
||||
retOffset.SetOrientation(endPos.GetOrientation() - GetOrientation());
|
||||
}
|
||||
|
||||
public Position GetPositionWithOffset(Position offset)
|
||||
{
|
||||
Position ret = this;
|
||||
ret.RelocateOffset(offset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static float NormalizeOrientation(float o)
|
||||
{
|
||||
// fmod only supports positive numbers. Thus we have
|
||||
// to emulate negative numbers
|
||||
if (o < 0)
|
||||
{
|
||||
float mod = o * -1;
|
||||
mod = mod % (2.0f * MathFunctions.PI);
|
||||
mod = -mod + 2.0f * MathFunctions.PI;
|
||||
return mod;
|
||||
}
|
||||
return o % (2.0f * MathFunctions.PI);
|
||||
}
|
||||
|
||||
public float GetExactDist(float x, float y, float z)
|
||||
{
|
||||
return (float)Math.Sqrt(GetExactDistSq(x, y, z));
|
||||
}
|
||||
public float GetExactDist(Position pos)
|
||||
{
|
||||
return (float)Math.Sqrt(GetExactDistSq(pos));
|
||||
}
|
||||
public float GetExactDistSq(float x, float y, float z)
|
||||
{
|
||||
float dz = posZ - z;
|
||||
|
||||
return GetExactDist2dSq(x, y) + dz * dz;
|
||||
}
|
||||
public float GetExactDistSq(Position pos)
|
||||
{
|
||||
float dx = posX - pos.posX;
|
||||
float dy = posY - pos.posY;
|
||||
float dz = posZ - pos.posZ;
|
||||
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
public float GetExactDist2d(float x, float y)
|
||||
{
|
||||
return (float)Math.Sqrt(GetExactDist2dSq(x, y));
|
||||
}
|
||||
public float GetExactDist2d(Position pos)
|
||||
{
|
||||
return (float)Math.Sqrt(GetExactDist2dSq(pos));
|
||||
}
|
||||
public float GetExactDist2dSq(float x, float y)
|
||||
{
|
||||
float dx = posX - x;
|
||||
float dy = posY - y;
|
||||
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
public float GetExactDist2dSq(Position pos)
|
||||
{
|
||||
float dx = posX - pos.posX;
|
||||
float dy = posY - pos.posY;
|
||||
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
public float GetAngle(float x, float y)
|
||||
{
|
||||
float dx = x - GetPositionX();
|
||||
float dy = y - GetPositionY();
|
||||
|
||||
float ang = (float)Math.Atan2(dy, dx);
|
||||
ang = ang >= 0 ? ang : 2 * MathFunctions.PI + ang;
|
||||
return ang;
|
||||
}
|
||||
public float GetAngle(Position pos)
|
||||
{
|
||||
if (pos == null)
|
||||
return 0;
|
||||
|
||||
return GetAngle(pos.GetPositionX(), pos.GetPositionY());
|
||||
}
|
||||
|
||||
public bool IsInDist(float x, float y, float z, float dist)
|
||||
{
|
||||
return GetExactDistSq(x, y, z) < dist * dist;
|
||||
}
|
||||
public bool IsInDist(Position pos, float dist)
|
||||
{
|
||||
return GetExactDistSq(pos) < dist * dist;
|
||||
}
|
||||
|
||||
public bool IsInDist2d(float x, float y, float dist)
|
||||
{
|
||||
return GetExactDist2dSq(x, y) < dist * dist;
|
||||
}
|
||||
public bool IsInDist2d(Position pos, float dist)
|
||||
{
|
||||
return GetExactDist2dSq(pos) < dist * dist;
|
||||
}
|
||||
|
||||
public void SetOrientation(float orientation)
|
||||
{
|
||||
Orientation = NormalizeOrientation(orientation);
|
||||
}
|
||||
|
||||
public bool IsWithinBox(Position center, float xradius, float yradius, float zradius)
|
||||
{
|
||||
// rotate the WorldObject position instead of rotating the whole cube, that way we can make a simplified
|
||||
// is-in-cube check and we have to calculate only one point instead of 4
|
||||
|
||||
// 2PI = 360*, keep in mind that ingame orientation is counter-clockwise
|
||||
double rotation = 2 * Math.PI - center.GetOrientation();
|
||||
double sinVal = Math.Sin(rotation);
|
||||
double cosVal = Math.Cos(rotation);
|
||||
|
||||
float BoxDistX = GetPositionX() - center.GetPositionX();
|
||||
float BoxDistY = GetPositionY() - center.GetPositionY();
|
||||
|
||||
float rotX = (float)(center.GetPositionX() + BoxDistX * cosVal - BoxDistY * sinVal);
|
||||
float rotY = (float)(center.GetPositionY() + BoxDistY * cosVal + BoxDistX * sinVal);
|
||||
|
||||
// box edges are parallel to coordiante axis, so we can treat every dimension independently :D
|
||||
float dz = GetPositionZ() - center.GetPositionZ();
|
||||
float dx = rotX - center.GetPositionX();
|
||||
float dy = rotY - center.GetPositionY();
|
||||
if ((Math.Abs(dx) > xradius) || (Math.Abs(dy) > yradius) || (Math.Abs(dz) > zradius))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
public bool HasInArc(float arc, Position obj, float border = 2.0f)
|
||||
{
|
||||
// always have self in arc
|
||||
if (obj == this)
|
||||
return true;
|
||||
|
||||
float angle = GetAngle(obj);
|
||||
angle -= GetOrientation();
|
||||
|
||||
// move angle to range -pi ... +pi
|
||||
if (angle > MathFunctions.PI)
|
||||
angle -= 2.0f * MathFunctions.PI;
|
||||
|
||||
float lborder = -1 * (arc / border); // in range -pi..0
|
||||
float rborder = (arc / border); // in range 0..pi
|
||||
return ((angle >= lborder) && (angle <= rborder));
|
||||
}
|
||||
public void GetSinCos(float x, float y, out float vsin, out float vcos)
|
||||
{
|
||||
float dx = GetPositionX() - x;
|
||||
float dy = GetPositionY() - y;
|
||||
|
||||
if (Math.Abs(dx) < 0.001f && Math.Abs(dy) < 0.001f)
|
||||
{
|
||||
float angle = (float)RandomHelper.NextDouble() * MathFunctions.TwoPi;
|
||||
vcos = (float)Math.Cos(angle);
|
||||
vsin = (float)Math.Sin(angle);
|
||||
}
|
||||
else
|
||||
{
|
||||
float dist = (float)Math.Sqrt((dx * dx) + (dy * dy));
|
||||
vcos = dx / dist;
|
||||
vsin = dy / dist;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("X: {0} Y: {1} Z: {2} O: {3}", posX, posY, posZ, Orientation);
|
||||
}
|
||||
|
||||
public float posX;
|
||||
public float posY;
|
||||
public float posZ;
|
||||
public float Orientation;
|
||||
}
|
||||
|
||||
public class WorldLocation : Position
|
||||
{
|
||||
public WorldLocation(uint mapId = 0xFFFFFFFF, float x = 0, float y = 0, float z = 0, float o = 0)
|
||||
{
|
||||
_mapId = mapId;
|
||||
Relocate(x, y, z, o);
|
||||
}
|
||||
public WorldLocation(uint mapId, Position pos)
|
||||
{
|
||||
_mapId = mapId;
|
||||
Relocate(pos);
|
||||
}
|
||||
public WorldLocation(WorldLocation loc)
|
||||
{
|
||||
_mapId = loc._mapId;
|
||||
Relocate(loc);
|
||||
}
|
||||
public WorldLocation(Position pos)
|
||||
{
|
||||
_mapId = 0xFFFFFFFF;
|
||||
Relocate(pos);
|
||||
}
|
||||
|
||||
public void WorldRelocate(WorldLocation loc)
|
||||
{
|
||||
_mapId = loc._mapId;
|
||||
Relocate(loc);
|
||||
}
|
||||
|
||||
public void WorldRelocate(uint mapId = 0xFFFFFFFF, float x = 0.0f, float y = 0.0f, float z = 0.0f, float o = 0.0f)
|
||||
{
|
||||
_mapId = mapId;
|
||||
Relocate(x, y, z, o);
|
||||
}
|
||||
|
||||
public uint GetMapId() { return _mapId; }
|
||||
public void SetMapId(uint _mapId) { this._mapId = _mapId; }
|
||||
|
||||
public Cell GetCurrentCell()
|
||||
{
|
||||
if (currentCell == null)
|
||||
Log.outError(LogFilter.Server, "Calling currentCell but its null");
|
||||
|
||||
return currentCell;
|
||||
}
|
||||
public void SetCurrentCell(Cell cell) { currentCell = cell; }
|
||||
public void SetNewCellPosition(float x, float y, float z, float o)
|
||||
{
|
||||
_moveState = ObjectCellMoveState.Active;
|
||||
_newPosition.Relocate(x, y, z, o);
|
||||
}
|
||||
|
||||
public WorldLocation GetWorldLocation()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
uint _mapId;
|
||||
Cell currentCell;
|
||||
public ObjectCellMoveState _moveState;
|
||||
|
||||
public Position _newPosition = new Position();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.IO;
|
||||
using Game.Network;
|
||||
using Game.Network.Packets;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class UpdateData
|
||||
{
|
||||
public UpdateData(uint mapId)
|
||||
{
|
||||
MapId = mapId;
|
||||
}
|
||||
|
||||
public void AddOutOfRangeGUID(List<ObjectGuid> guids)
|
||||
{
|
||||
outOfRangeGUIDs.AddRange(guids);
|
||||
}
|
||||
|
||||
public void AddOutOfRangeGUID(ObjectGuid guid)
|
||||
{
|
||||
outOfRangeGUIDs.Add(guid);
|
||||
}
|
||||
|
||||
public void AddUpdateBlock(ByteBuffer block)
|
||||
{
|
||||
data.WriteBytes(block.GetData());
|
||||
++BlockCount;
|
||||
}
|
||||
|
||||
public bool BuildPacket(out UpdateObject packet)
|
||||
{
|
||||
packet = new UpdateObject();
|
||||
|
||||
packet.NumObjUpdates = BlockCount;
|
||||
packet.MapID = (ushort)MapId;
|
||||
|
||||
WorldPacket buffer = new WorldPacket();
|
||||
if (buffer.WriteBit(!outOfRangeGUIDs.Empty()))
|
||||
{
|
||||
buffer.WriteUInt16(0); // object limit to instantly destroy - objects before this index on m_outOfRangeGUIDs list get "smoothly phased out"
|
||||
buffer.WriteUInt32(outOfRangeGUIDs.Count);
|
||||
|
||||
foreach (var guid in outOfRangeGUIDs)
|
||||
buffer.WritePackedGuid(guid);
|
||||
}
|
||||
var bytes = data.GetData();
|
||||
buffer.WriteUInt32(bytes.Length);
|
||||
buffer.WriteBytes(bytes);
|
||||
|
||||
packet.Data = buffer.GetData();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
data.Clear();
|
||||
outOfRangeGUIDs.Clear();
|
||||
BlockCount = 0;
|
||||
MapId = 0;
|
||||
}
|
||||
|
||||
public bool HasData() { return BlockCount > 0 || outOfRangeGUIDs.Count != 0; }
|
||||
|
||||
public List<ObjectGuid> GetOutOfRangeGUIDs() { return outOfRangeGUIDs; }
|
||||
|
||||
public void SetMapId(ushort mapId) { MapId = mapId; }
|
||||
|
||||
uint MapId;
|
||||
uint BlockCount;
|
||||
List<ObjectGuid> outOfRangeGUIDs = new List<ObjectGuid>();
|
||||
ByteBuffer data = new ByteBuffer();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.IO;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class UpdateMask
|
||||
{
|
||||
public UpdateMask(uint valuesCount = 0)
|
||||
{
|
||||
_fieldCount = valuesCount;
|
||||
_blockCount = (valuesCount + 32 - 1) / 32;
|
||||
|
||||
_mask = new BitArray((int)valuesCount, false);
|
||||
}
|
||||
|
||||
public void SetCount(int valuesCount)
|
||||
{
|
||||
_fieldCount = (uint)valuesCount;
|
||||
_blockCount = (uint)(valuesCount + 32 - 1) / 32;
|
||||
|
||||
_mask = new BitArray((int)valuesCount, false);
|
||||
}
|
||||
|
||||
public uint GetCount() { return _fieldCount; }
|
||||
|
||||
public virtual void AppendToPacket(ByteBuffer data)
|
||||
{
|
||||
data.WriteUInt8(_blockCount);
|
||||
var maskArray = new byte[_blockCount << 2];
|
||||
|
||||
_mask.CopyTo(maskArray, 0);
|
||||
data.WriteBytes(maskArray);
|
||||
}
|
||||
|
||||
public bool GetBit(int index)
|
||||
{
|
||||
return _mask.Get(index);
|
||||
}
|
||||
|
||||
public void SetBit(int index)
|
||||
{
|
||||
_mask.Set(index, true);
|
||||
}
|
||||
|
||||
void UnsetBit(int index)
|
||||
{
|
||||
_mask.Set(index, false);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_mask.SetAll(false);
|
||||
}
|
||||
|
||||
protected uint _fieldCount;
|
||||
protected uint _blockCount;
|
||||
protected BitArray _mask;
|
||||
}
|
||||
|
||||
public class DynamicUpdateMask : UpdateMask
|
||||
{
|
||||
public DynamicUpdateMask(uint valuesCount) : base(valuesCount) { }
|
||||
|
||||
public void EncodeDynamicFieldChangeType(DynamicFieldChangeType changeType, UpdateType updateType)
|
||||
{
|
||||
DynamicFieldChangeType = (uint)(_blockCount | ((uint)(changeType & Entities.DynamicFieldChangeType.ValueAndSizeChanged) * ((3 - (int)updateType /*this part evaluates to 0 if update type is not VALUES*/) / 3)));
|
||||
}
|
||||
|
||||
public override void AppendToPacket(ByteBuffer data)
|
||||
{
|
||||
data.WriteUInt16(DynamicFieldChangeType);
|
||||
if (DynamicFieldChangeType.HasAnyFlag((uint)Entities.DynamicFieldChangeType.ValueAndSizeChanged))
|
||||
data.WriteUInt32(_fieldCount);
|
||||
|
||||
var maskArray = new byte[_blockCount << 2];
|
||||
|
||||
_mask.CopyTo(maskArray, 0);
|
||||
data.WriteBytes(maskArray);
|
||||
}
|
||||
|
||||
public uint DynamicFieldChangeType;
|
||||
}
|
||||
|
||||
public enum DynamicFieldChangeType
|
||||
{
|
||||
Unchanged = 0,
|
||||
ValueChanged = 0x7FFF,
|
||||
ValueAndSizeChanged = 0x8000
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class CinematicManager : IDisposable
|
||||
{
|
||||
public CinematicManager(Player playerref)
|
||||
{
|
||||
player = playerref;
|
||||
m_cinematicDiff = 0;
|
||||
m_lastCinematicCheck = 0;
|
||||
m_activeCinematicCameraId = 0;
|
||||
m_cinematicLength = 0;
|
||||
m_cinematicCamera = null;
|
||||
m_remoteSightPosition = new Position(0.0f, 0.0f, 0.0f);
|
||||
m_CinematicObject = null;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (m_cinematicCamera != null && m_activeCinematicCameraId != 0)
|
||||
EndCinematic();
|
||||
}
|
||||
|
||||
public void BeginCinematic()
|
||||
{
|
||||
// Sanity check for active camera set
|
||||
if (m_activeCinematicCameraId == 0)
|
||||
return;
|
||||
|
||||
var list = M2Storage.GetFlyByCameras(m_activeCinematicCameraId);
|
||||
if (!list.Empty())
|
||||
{
|
||||
// Initialize diff, and set camera
|
||||
m_cinematicDiff = 0;
|
||||
m_cinematicCamera = list;
|
||||
|
||||
if (!m_cinematicCamera.Empty())
|
||||
{
|
||||
FlyByCamera firstCamera = m_cinematicCamera.FirstOrDefault();
|
||||
Position pos = new Position(firstCamera.locations.X, firstCamera.locations.Y, firstCamera.locations.Z, firstCamera.locations.W);
|
||||
if (!pos.IsPositionValid())
|
||||
return;
|
||||
|
||||
player.GetMap().LoadGrid(firstCamera.locations.X, firstCamera.locations.Y);
|
||||
m_CinematicObject = player.SummonCreature(1, pos.posX, pos.posY, pos.posZ, 0.0f, TempSummonType.TimedDespawn, 5 * Time.Minute * Time.InMilliseconds);
|
||||
if (m_CinematicObject)
|
||||
{
|
||||
m_CinematicObject.setActive(true);
|
||||
player.SetViewpoint(m_CinematicObject, true);
|
||||
}
|
||||
|
||||
// Get cinematic length
|
||||
m_cinematicLength = m_cinematicCamera.LastOrDefault().timeStamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EndCinematic()
|
||||
{
|
||||
if (m_activeCinematicCameraId == 0)
|
||||
return;
|
||||
|
||||
m_cinematicDiff = 0;
|
||||
m_cinematicCamera = null;
|
||||
m_activeCinematicCameraId = 0;
|
||||
if (m_CinematicObject)
|
||||
{
|
||||
WorldObject vpObject = player.GetViewpoint();
|
||||
if (vpObject)
|
||||
if (vpObject == m_CinematicObject)
|
||||
player.SetViewpoint(m_CinematicObject, false);
|
||||
|
||||
m_CinematicObject.AddObjectToRemoveList();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCinematicLocation(uint diff)
|
||||
{
|
||||
if (m_activeCinematicCameraId == 0 || m_cinematicCamera == null || m_cinematicCamera.Count == 0)
|
||||
return;
|
||||
|
||||
Position lastPosition = new Position();
|
||||
uint lastTimestamp = 0;
|
||||
Position nextPosition = new Position();
|
||||
uint nextTimestamp = 0;
|
||||
|
||||
// Obtain direction of travel
|
||||
foreach (FlyByCamera cam in m_cinematicCamera)
|
||||
{
|
||||
if (cam.timeStamp > m_cinematicDiff)
|
||||
{
|
||||
nextPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
nextTimestamp = cam.timeStamp;
|
||||
break;
|
||||
}
|
||||
lastPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
lastTimestamp = cam.timeStamp;
|
||||
}
|
||||
float angle = lastPosition.GetAngle(nextPosition);
|
||||
angle -= lastPosition.GetOrientation();
|
||||
if (angle < 0)
|
||||
angle += 2 * MathFunctions.PI;
|
||||
|
||||
// Look for position around 2 second ahead of us.
|
||||
int workDiff = (int)m_cinematicDiff;
|
||||
|
||||
// Modify result based on camera direction (Humans for example, have the camera point behind)
|
||||
workDiff += (int)((2 * Time.InMilliseconds) * Math.Cos(angle));
|
||||
|
||||
// Get an iterator to the last entry in the cameras, to make sure we don't go beyond the end
|
||||
var endItr = m_cinematicCamera.LastOrDefault();
|
||||
if (endItr != null && workDiff > endItr.timeStamp)
|
||||
workDiff = (int)endItr.timeStamp;
|
||||
|
||||
// Never try to go back in time before the start of cinematic!
|
||||
if (workDiff < 0)
|
||||
workDiff = (int)m_cinematicDiff;
|
||||
|
||||
// Obtain the previous and next waypoint based on timestamp
|
||||
foreach (FlyByCamera cam in m_cinematicCamera)
|
||||
{
|
||||
if (cam.timeStamp >= workDiff)
|
||||
{
|
||||
nextPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
nextTimestamp = cam.timeStamp;
|
||||
break;
|
||||
}
|
||||
lastPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
lastTimestamp = cam.timeStamp;
|
||||
}
|
||||
|
||||
// Never try to go beyond the end of the cinematic
|
||||
if (workDiff > nextTimestamp)
|
||||
workDiff = (int)nextTimestamp;
|
||||
|
||||
// Interpolate the position for this moment in time (or the adjusted moment in time)
|
||||
uint timeDiff = nextTimestamp - lastTimestamp;
|
||||
uint interDiff = (uint)(workDiff - lastTimestamp);
|
||||
float xDiff = nextPosition.posX - lastPosition.posX;
|
||||
float yDiff = nextPosition.posY - lastPosition.posY;
|
||||
float zDiff = nextPosition.posZ - lastPosition.posZ;
|
||||
Position interPosition = new Position(lastPosition.posX + (xDiff * (interDiff / timeDiff)), lastPosition.posY +
|
||||
(yDiff * (interDiff / timeDiff)), lastPosition.posZ + (zDiff * (interDiff / timeDiff)));
|
||||
|
||||
// Advance (at speed) to this position. The remote sight object is used
|
||||
// to send update information to player in cinematic
|
||||
if (m_CinematicObject && interPosition.IsPositionValid())
|
||||
m_CinematicObject.MonsterMoveWithSpeed(interPosition.posX, interPosition.posY, interPosition.posZ, 500.0f, false, true);
|
||||
|
||||
// If we never received an end packet 10 seconds after the final timestamp then force an end
|
||||
if (m_cinematicDiff > m_cinematicLength + 10 * Time.InMilliseconds)
|
||||
EndCinematic();
|
||||
}
|
||||
|
||||
uint GetActiveCinematicCamera() { return m_activeCinematicCameraId; }
|
||||
public void SetActiveCinematicCamera(uint cinematicCameraId = 0) { m_activeCinematicCameraId = cinematicCameraId; }
|
||||
public bool IsOnCinematic() { return (m_cinematicCamera != null); }
|
||||
|
||||
// Remote location information
|
||||
Player player;
|
||||
|
||||
public uint m_cinematicDiff;
|
||||
public uint m_lastCinematicCheck;
|
||||
public uint m_activeCinematicCameraId;
|
||||
public uint m_cinematicLength;
|
||||
List<FlyByCamera> m_cinematicCamera;
|
||||
Position m_remoteSightPosition;
|
||||
TempSummon m_CinematicObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,862 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class CollectionMgr
|
||||
{
|
||||
public static void LoadMountDefinitions()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
SQLResult result = DB.World.Query("SELECT spellId, otherFactionSpellId FROM mount_definitions");
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 mount definitions. DB table `mount_definitions` is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
uint spellId = result.Read<uint>(0);
|
||||
uint otherFactionSpellId = result.Read<uint>(1);
|
||||
|
||||
if (Global.DB2Mgr.GetMount(spellId) == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Mount spell {0} defined in `mount_definitions` does not exist in Mount.db2, skipped", spellId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (otherFactionSpellId != 0 && Global.DB2Mgr.GetMount(otherFactionSpellId) == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "otherFactionSpellId {0} defined in `mount_definitions` for spell {1} does not exist in Mount.db2, skipped", otherFactionSpellId, spellId);
|
||||
continue;
|
||||
}
|
||||
|
||||
FactionSpecificMounts[spellId] = otherFactionSpellId;
|
||||
} while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} mount definitions in {1} ms", FactionSpecificMounts.Count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
public CollectionMgr(WorldSession owner)
|
||||
{
|
||||
_owner = owner;
|
||||
_appearances = new System.Collections.BitSet(0);
|
||||
}
|
||||
|
||||
public void LoadToys()
|
||||
{
|
||||
foreach (var value in _toys.Keys)
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Toys, value);
|
||||
}
|
||||
|
||||
public bool AddToy(uint itemId, bool isFavourite = false)
|
||||
{
|
||||
if (UpdateAccountToys(itemId, isFavourite))
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Toys, itemId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void LoadAccountToys(SQLResult result)
|
||||
{
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint itemId = result.Read<uint>(0);
|
||||
bool isFavourite = result.Read<bool>(1);
|
||||
|
||||
_toys[itemId] = isFavourite;
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
public void SaveAccountToys(SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
foreach (var pair in _toys)
|
||||
{
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_TOYS);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, pair.Key);
|
||||
stmt.AddValue(2, pair.Value);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateAccountToys(uint itemId, bool isFavourite = false)
|
||||
{
|
||||
if (_toys.ContainsKey(itemId))
|
||||
return false;
|
||||
|
||||
_toys.Add(itemId, isFavourite);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ToySetFavorite(uint itemId, bool favorite)
|
||||
{
|
||||
if (!_toys.ContainsKey(itemId))
|
||||
return;
|
||||
|
||||
_toys[itemId] = favorite;
|
||||
}
|
||||
|
||||
public void OnItemAdded(Item item)
|
||||
{
|
||||
if (Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry()) != null)
|
||||
AddHeirloom(item.GetEntry(), 0);
|
||||
|
||||
AddItemAppearance(item);
|
||||
}
|
||||
|
||||
public void LoadAccountHeirlooms(SQLResult result)
|
||||
{
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint itemId = result.Read<uint>(0);
|
||||
HeirloomPlayerFlags flags = (HeirloomPlayerFlags)result.Read<uint>(1);
|
||||
|
||||
HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(itemId);
|
||||
if (heirloom == null)
|
||||
continue;
|
||||
|
||||
uint bonusId = 0;
|
||||
|
||||
if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel110))
|
||||
bonusId = heirloom.ItemBonusListID[2];
|
||||
else if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel100))
|
||||
bonusId = heirloom.ItemBonusListID[1];
|
||||
else if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel90))
|
||||
bonusId = heirloom.ItemBonusListID[0];
|
||||
|
||||
_heirlooms[itemId] = new HeirloomData(flags, bonusId);
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
public void SaveAccountHeirlooms(SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement stmt;
|
||||
foreach (var heirloom in _heirlooms)
|
||||
{
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_HEIRLOOMS);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, heirloom.Key);
|
||||
stmt.AddValue(2, heirloom.Value.flags);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateAccountHeirlooms(uint itemId, HeirloomPlayerFlags flags)
|
||||
{
|
||||
if (_heirlooms.ContainsKey(itemId))
|
||||
return false;
|
||||
|
||||
_heirlooms.Add(itemId, new HeirloomData(flags, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint GetHeirloomBonus(uint itemId)
|
||||
{
|
||||
var data = _heirlooms.LookupByKey(itemId);
|
||||
if (data != null)
|
||||
return data.bonusId;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void LoadHeirlooms()
|
||||
{
|
||||
foreach (var item in _heirlooms)
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Heirlooms, item.Key);
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.HeirloomsFlags, (uint)item.Value.flags);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddHeirloom(uint itemId, HeirloomPlayerFlags flags)
|
||||
{
|
||||
if (UpdateAccountHeirlooms(itemId, flags))
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Heirlooms, itemId);
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.HeirloomsFlags, (uint)flags);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpgradeHeirloom(uint itemId, uint castItem)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(itemId);
|
||||
if (heirloom == null)
|
||||
return;
|
||||
|
||||
var data = _heirlooms.LookupByKey(itemId);
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
HeirloomPlayerFlags flags = data.flags;
|
||||
uint bonusId = 0;
|
||||
|
||||
if (heirloom.UpgradeItemID[0] == castItem)
|
||||
{
|
||||
flags |= HeirloomPlayerFlags.BonusLevel90;
|
||||
bonusId = heirloom.ItemBonusListID[0];
|
||||
}
|
||||
if (heirloom.UpgradeItemID[1] == castItem)
|
||||
{
|
||||
flags |= HeirloomPlayerFlags.BonusLevel100;
|
||||
bonusId = heirloom.ItemBonusListID[1];
|
||||
}
|
||||
if (heirloom.UpgradeItemID[2] == castItem)
|
||||
{
|
||||
flags |= HeirloomPlayerFlags.BonusLevel110;
|
||||
bonusId = heirloom.ItemBonusListID[2];
|
||||
}
|
||||
|
||||
foreach (Item item in player.GetItemListByEntry(itemId, true))
|
||||
item.AddBonuses(bonusId);
|
||||
|
||||
// Get heirloom offset to update only one part of dynamic field
|
||||
var fields = player.GetDynamicValues(PlayerDynamicFields.Heirlooms);
|
||||
ushort offset = (ushort)Array.IndexOf(fields, itemId);
|
||||
|
||||
player.SetDynamicValue(PlayerDynamicFields.HeirloomsFlags, offset, (uint)flags);
|
||||
data.flags = flags;
|
||||
data.bonusId = bonusId;
|
||||
}
|
||||
|
||||
public void CheckHeirloomUpgrades(Item item)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
// Check already owned heirloom for upgrade kits
|
||||
HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry());
|
||||
if (heirloom != null)
|
||||
{
|
||||
var data = _heirlooms.LookupByKey(item.GetEntry());
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
// Check for heirloom pairs (normal - heroic, heroic - mythic)
|
||||
uint heirloomItemId = heirloom.NextDifficultyItemID;
|
||||
uint newItemId = 0;
|
||||
HeirloomRecord heirloomDiff;
|
||||
while ((heirloomDiff = Global.DB2Mgr.GetHeirloomByItemId(heirloomItemId)) != null)
|
||||
{
|
||||
if (player.GetItemByEntry(heirloomDiff.ItemID))
|
||||
newItemId = heirloomDiff.ItemID;
|
||||
|
||||
HeirloomRecord heirloomSub = Global.DB2Mgr.GetHeirloomByItemId(heirloomDiff.NextDifficultyItemID);
|
||||
if (heirloomSub != null)
|
||||
{
|
||||
heirloomItemId = heirloomSub.ItemID;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (newItemId != 0)
|
||||
{
|
||||
var heirloomFields = player.GetDynamicValues(PlayerDynamicFields.Heirlooms);
|
||||
ushort offset = (ushort)Array.IndexOf(heirloomFields, item.GetEntry());
|
||||
|
||||
player.SetDynamicValue(PlayerDynamicFields.Heirlooms, offset, newItemId);
|
||||
player.SetDynamicValue(PlayerDynamicFields.HeirloomsFlags, offset, 0);
|
||||
|
||||
_heirlooms.Remove(item.GetEntry());
|
||||
_heirlooms[newItemId] = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var fields = item.GetDynamicValues(ItemDynamicFields.BonusListIds);
|
||||
foreach (uint bonusId in fields)
|
||||
if (bonusId != data.bonusId)
|
||||
item.ClearDynamicValue(ItemDynamicFields.BonusListIds);
|
||||
|
||||
if (!fields.Contains(data.bonusId))
|
||||
item.AddBonuses(data.bonusId);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanApplyHeirloomXpBonus(uint itemId, uint level)
|
||||
{
|
||||
if (Global.DB2Mgr.GetHeirloomByItemId(itemId) == null)
|
||||
return false;
|
||||
|
||||
var data = _heirlooms.LookupByKey(itemId);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel110))
|
||||
return level <= 110;
|
||||
if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel100))
|
||||
return level <= 100;
|
||||
if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel90))
|
||||
return level <= 90;
|
||||
|
||||
return level <= 60;
|
||||
}
|
||||
|
||||
public void LoadMounts()
|
||||
{
|
||||
foreach (var m in _mounts.ToList())
|
||||
AddMount(m.Key, m.Value, false, false);
|
||||
}
|
||||
|
||||
public void LoadAccountMounts(SQLResult result)
|
||||
{
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint mountSpellId = result.Read<uint>(0);
|
||||
MountStatusFlags flags = (MountStatusFlags)result.Read<byte>(1);
|
||||
|
||||
if (Global.DB2Mgr.GetMount(mountSpellId) == null)
|
||||
continue;
|
||||
|
||||
_mounts[mountSpellId] = flags;
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
public void SaveAccountMounts(SQLTransaction trans)
|
||||
{
|
||||
foreach (var mount in _mounts)
|
||||
{
|
||||
PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_MOUNTS);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, mount.Key);
|
||||
stmt.AddValue(2, mount.Value);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddMount(uint spellId, MountStatusFlags flags, bool factionMount = false, bool learned = false)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
MountRecord mount = Global.DB2Mgr.GetMount(spellId);
|
||||
if (mount == null)
|
||||
return false;
|
||||
|
||||
var value = FactionSpecificMounts.LookupByKey(spellId);
|
||||
if (value != 0 && !factionMount)
|
||||
AddMount(value, flags, true, learned);
|
||||
|
||||
_mounts[spellId] = flags;
|
||||
|
||||
// Mount condition only applies to using it, should still learn it.
|
||||
if (mount.PlayerConditionId != 0)
|
||||
{
|
||||
PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(mount.PlayerConditionId);
|
||||
if (!ConditionManager.IsPlayerMeetingCondition(player, playerCondition))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!learned)
|
||||
{
|
||||
if (!factionMount)
|
||||
SendSingleMountUpdate(spellId, flags);
|
||||
if (!player.HasSpell(spellId))
|
||||
player.LearnSpell(spellId, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void MountSetFavorite(uint spellId, bool favorite)
|
||||
{
|
||||
if (!_mounts.ContainsKey(spellId))
|
||||
return;
|
||||
|
||||
if (favorite)
|
||||
_mounts[spellId] |= MountStatusFlags.IsFavorite;
|
||||
else
|
||||
_mounts[spellId] &= ~MountStatusFlags.IsFavorite;
|
||||
|
||||
SendSingleMountUpdate(spellId, _mounts[spellId]);
|
||||
}
|
||||
|
||||
void SendSingleMountUpdate(uint spellId, MountStatusFlags mountStatusFlags)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
AccountMountUpdate mountUpdate = new AccountMountUpdate();
|
||||
mountUpdate.IsFullUpdate = false;
|
||||
mountUpdate.Mounts.Add(spellId, mountStatusFlags);
|
||||
player.SendPacket(mountUpdate);
|
||||
}
|
||||
|
||||
public void LoadItemAppearances()
|
||||
{
|
||||
foreach (uint blockValue in _appearances.ToBlockRange())
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Transmog, blockValue);
|
||||
}
|
||||
|
||||
foreach (var value in _temporaryAppearances.Keys)
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.ConditionalTransmog, value);
|
||||
}
|
||||
|
||||
public void LoadAccountItemAppearances(SQLResult knownAppearances, SQLResult favoriteAppearances)
|
||||
{
|
||||
if (!knownAppearances.IsEmpty())
|
||||
{
|
||||
uint[] blocks = new uint[1];
|
||||
do
|
||||
{
|
||||
ushort blobIndex = knownAppearances.Read<ushort>(0);
|
||||
if (blobIndex >= blocks.Length)
|
||||
Array.Resize(ref blocks, blobIndex + 1);
|
||||
|
||||
blocks[blobIndex] = knownAppearances.Read<uint>(1);
|
||||
|
||||
} while (knownAppearances.NextRow());
|
||||
|
||||
_appearances = new System.Collections.BitSet(blocks);
|
||||
}
|
||||
|
||||
if (!favoriteAppearances.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
_favoriteAppearances[favoriteAppearances.Read<uint>(0)] = FavoriteAppearanceState.Unchanged;
|
||||
} while (favoriteAppearances.NextRow());
|
||||
}
|
||||
|
||||
// Static item appearances known by every player
|
||||
uint[] hiddenAppearanceItems =
|
||||
{
|
||||
134110, // Hidden Helm
|
||||
134111, // Hidden Cloak
|
||||
134112, // Hidden Shoulder
|
||||
142503, // Hidden Shirt
|
||||
142504, // Hidden Tabard
|
||||
143539 // Hidden Belt
|
||||
};
|
||||
|
||||
foreach (uint hiddenItem in hiddenAppearanceItems)
|
||||
{
|
||||
ItemModifiedAppearanceRecord hiddenAppearance = Global.DB2Mgr.GetItemModifiedAppearance(hiddenItem, 0);
|
||||
//ASSERT(hiddenAppearance);
|
||||
if (_appearances.Length <= hiddenAppearance.Id)
|
||||
_appearances.Length = (int)hiddenAppearance.Id + 1;
|
||||
|
||||
_appearances.Set((int)hiddenAppearance.Id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveAccountItemAppearances(SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement stmt;
|
||||
ushort blockIndex = 0;
|
||||
foreach (uint blockValue in _appearances.ToBlockRange())
|
||||
{
|
||||
if (blockValue != 0) // this table is only appended/bits are set (never cleared) so don't save empty blocks
|
||||
{
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, blockIndex);
|
||||
stmt.AddValue(2, blockValue);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
|
||||
++blockIndex;
|
||||
}
|
||||
|
||||
foreach (var key in _favoriteAppearances.Keys)
|
||||
{
|
||||
var appearanceState = _favoriteAppearances[key];
|
||||
switch (appearanceState)
|
||||
{
|
||||
case FavoriteAppearanceState.New:
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ITEM_FAVORITE_APPEARANCE);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, key);
|
||||
trans.Append(stmt);
|
||||
appearanceState = FavoriteAppearanceState.Unchanged;
|
||||
break;
|
||||
case FavoriteAppearanceState.Removed:
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_BNET_ITEM_FAVORITE_APPEARANCE);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, key);
|
||||
trans.Append(stmt);
|
||||
_favoriteAppearances.Remove(key);
|
||||
break;
|
||||
case FavoriteAppearanceState.Unchanged:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint[] PlayerClassByArmorSubclass =
|
||||
{
|
||||
(int)Class.ClassMaskAllPlayable, //ITEM_SUBCLASS_ARMOR_MISCELLANEOUS
|
||||
(1 << ((int)Class.Priest - 1)) | (1 << ((int)Class.Mage - 1)) | (1 << ((int)Class.Warlock - 1)), //ITEM_SUBCLASS_ARMOR_CLOTH
|
||||
(1 << ((int)Class.Rogue - 1)) | (1 << ((int)Class.Monk - 1)) | (1 << ((int)Class.Druid - 1)) | (1 << ((int)Class.DemonHunter - 1)), //ITEM_SUBCLASS_ARMOR_LEATHER
|
||||
(1 << ((int)Class.Hunter - 1)) | (1 << ((int)Class.Shaman - 1)), //ITEM_SUBCLASS_ARMOR_MAIL
|
||||
(1 << ((int)Class.Warrior - 1)) | (1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Deathknight - 1)), //ITEM_SUBCLASS_ARMOR_PLATE
|
||||
(int)Class.ClassMaskAllPlayable, //ITEM_SUBCLASS_ARMOR_BUCKLER
|
||||
(1 << ((int)Class.Warrior - 1)) | (1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Shaman - 1)), //ITEM_SUBCLASS_ARMOR_SHIELD
|
||||
1 << ((int)Class.Paladin - 1), //ITEM_SUBCLASS_ARMOR_LIBRAM
|
||||
1 << ((int)Class.Druid - 1), //ITEM_SUBCLASS_ARMOR_IDOL
|
||||
1 << ((int)Class.Shaman - 1), //ITEM_SUBCLASS_ARMOR_TOTEM
|
||||
1 << ((int)Class.Deathknight - 1), //ITEM_SUBCLASS_ARMOR_SIGIL
|
||||
(1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Deathknight - 1)) | (1 << ((int)Class.Shaman - 1)) | (1 << ((int)Class.Druid - 1)), //ITEM_SUBCLASS_ARMOR_RELIC
|
||||
};
|
||||
|
||||
public void AddItemAppearance(Item item)
|
||||
{
|
||||
if (!item.IsSoulBound())
|
||||
return;
|
||||
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = item.GetItemModifiedAppearance();
|
||||
if (!CanAddAppearance(itemModifiedAppearance))
|
||||
return;
|
||||
|
||||
if (Convert.ToBoolean(item.GetUInt32Value(ItemFields.Flags) & (uint)(ItemFieldFlags.BopTradeable | ItemFieldFlags.Refundable)))
|
||||
{
|
||||
AddTemporaryAppearance(item.GetGUID(), itemModifiedAppearance);
|
||||
return;
|
||||
}
|
||||
|
||||
AddItemAppearance(itemModifiedAppearance);
|
||||
}
|
||||
|
||||
public void AddItemAppearance(uint itemId, uint appearanceModId = 0)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = Global.DB2Mgr.GetItemModifiedAppearance(itemId, appearanceModId);
|
||||
if (!CanAddAppearance(itemModifiedAppearance))
|
||||
return;
|
||||
|
||||
AddItemAppearance(itemModifiedAppearance);
|
||||
}
|
||||
|
||||
bool CanAddAppearance(ItemModifiedAppearanceRecord itemModifiedAppearance)
|
||||
{
|
||||
if (itemModifiedAppearance == null)
|
||||
return false;
|
||||
|
||||
if (itemModifiedAppearance.SourceType == 6 || itemModifiedAppearance.SourceType == 9)
|
||||
return false;
|
||||
|
||||
if (!CliDB.ItemSearchNameStorage.ContainsKey(itemModifiedAppearance.ItemID))
|
||||
return false;
|
||||
|
||||
ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemModifiedAppearance.ItemID);
|
||||
if (itemTemplate == null)
|
||||
return false;
|
||||
|
||||
if (_owner.GetPlayer().CanUseItem(itemTemplate) != InventoryResult.Ok)
|
||||
return false;
|
||||
|
||||
if (itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.NoSourceForItemVisual) || itemTemplate.GetQuality() == ItemQuality.Artifact)
|
||||
return false;
|
||||
|
||||
switch (itemTemplate.GetClass())
|
||||
{
|
||||
case ItemClass.Weapon:
|
||||
{
|
||||
if (!Convert.ToBoolean(_owner.GetPlayer().GetWeaponProficiency() & (1 << (int)itemTemplate.GetSubClass())))
|
||||
return false;
|
||||
if (itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Exotic ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Exotic2 ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Miscellaneous ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Thrown ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Spear ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.FishingPole)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case ItemClass.Armor:
|
||||
{
|
||||
switch (itemTemplate.GetInventoryType())
|
||||
{
|
||||
case InventoryType.Body:
|
||||
case InventoryType.Shield:
|
||||
case InventoryType.Cloak:
|
||||
case InventoryType.Tabard:
|
||||
case InventoryType.Holdable:
|
||||
break;
|
||||
case InventoryType.Head:
|
||||
case InventoryType.Shoulders:
|
||||
case InventoryType.Chest:
|
||||
case InventoryType.Waist:
|
||||
case InventoryType.Legs:
|
||||
case InventoryType.Feet:
|
||||
case InventoryType.Wrists:
|
||||
case InventoryType.Hands:
|
||||
case InventoryType.Robe:
|
||||
if ((ItemSubClassArmor)itemTemplate.GetSubClass() == ItemSubClassArmor.Miscellaneous)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (itemTemplate.GetInventoryType() != InventoryType.Cloak)
|
||||
if (!Convert.ToBoolean(PlayerClassByArmorSubclass[itemTemplate.GetSubClass()] & _owner.GetPlayer().getClassMask()))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemTemplate.GetQuality() < ItemQuality.Uncommon)
|
||||
if (!itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.IgnoreQualityForItemVisualSource) || !itemTemplate.GetFlags3().HasAnyFlag(ItemFlags3.ActsAsTransmogHiddenVisualOption))
|
||||
return false;
|
||||
|
||||
if (itemModifiedAppearance.Id < _appearances.Count && _appearances.Get((int)itemModifiedAppearance.Id))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
//todo check this
|
||||
void AddItemAppearance(ItemModifiedAppearanceRecord itemModifiedAppearance)
|
||||
{
|
||||
if (_appearances.Count <= itemModifiedAppearance.Id)
|
||||
{
|
||||
uint numBlocks = (uint)(_appearances.Count << 2);
|
||||
_appearances.Length = (int)itemModifiedAppearance.Id + 1;
|
||||
numBlocks = (uint)(_appearances.Count << 2) - numBlocks;
|
||||
while (numBlocks-- != 0)
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Transmog, 0);
|
||||
}
|
||||
|
||||
_appearances.Set((int)itemModifiedAppearance.Id, true);
|
||||
uint blockIndex = itemModifiedAppearance.Id / 32;
|
||||
uint bitIndex = itemModifiedAppearance.Id % 32;
|
||||
uint currentMask = _owner.GetPlayer().GetDynamicValue(PlayerDynamicFields.Transmog, (ushort)blockIndex);
|
||||
_owner.GetPlayer().SetDynamicValue(PlayerDynamicFields.Transmog, (ushort)blockIndex, (uint)((int)currentMask | (1 << (int)bitIndex)));
|
||||
var temporaryAppearance = _temporaryAppearances.LookupByKey(itemModifiedAppearance.Id);
|
||||
if (!temporaryAppearance.Empty())
|
||||
{
|
||||
_owner.GetPlayer().RemoveDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id);
|
||||
_temporaryAppearances.Remove(itemModifiedAppearance.Id);
|
||||
}
|
||||
|
||||
ItemRecord item = CliDB.ItemStorage.LookupByKey(itemModifiedAppearance.ItemID);
|
||||
if (item != null)
|
||||
{
|
||||
int transmogSlot = Item.ItemTransmogrificationSlots[(int)item.inventoryType];
|
||||
if (transmogSlot >= 0)
|
||||
_owner.GetPlayer().UpdateCriteria(CriteriaTypes.AppearanceUnlockedBySlot, (ulong)transmogSlot, 1);
|
||||
}
|
||||
|
||||
var sets = Global.DB2Mgr.GetTransmogSetsForItemModifiedAppearance(itemModifiedAppearance.Id);
|
||||
foreach (TransmogSetRecord set in sets)
|
||||
if (IsSetCompleted(set.Id))
|
||||
_owner.GetPlayer().UpdateCriteria(CriteriaTypes.TransmogSetUnlocked, set.TransmogSetGroupID);
|
||||
}
|
||||
|
||||
void AddTemporaryAppearance(ObjectGuid itemGuid, ItemModifiedAppearanceRecord itemModifiedAppearance)
|
||||
{
|
||||
var itemsWithAppearance = _temporaryAppearances[itemModifiedAppearance.Id];
|
||||
if (itemsWithAppearance.Empty())
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id);
|
||||
|
||||
itemsWithAppearance.Add(itemGuid);
|
||||
}
|
||||
|
||||
public void RemoveTemporaryAppearance(Item item)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = item.GetItemModifiedAppearance();
|
||||
if (itemModifiedAppearance == null)
|
||||
return;
|
||||
|
||||
var guid = _temporaryAppearances.LookupByKey(itemModifiedAppearance.Id);
|
||||
if (guid.Empty())
|
||||
return;
|
||||
|
||||
guid.Remove(item.GetGUID());
|
||||
if (guid.Empty())
|
||||
{
|
||||
_owner.GetPlayer().RemoveDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id);
|
||||
_temporaryAppearances.Remove(itemModifiedAppearance.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public Tuple<bool, bool> HasItemAppearance(uint itemModifiedAppearanceId)
|
||||
{
|
||||
if (itemModifiedAppearanceId < _appearances.Count && _appearances.Get((int)itemModifiedAppearanceId))
|
||||
return Tuple.Create(true, false);
|
||||
|
||||
if (_temporaryAppearances.ContainsKey(itemModifiedAppearanceId))
|
||||
return Tuple.Create(true, true);
|
||||
|
||||
return Tuple.Create(false, false);
|
||||
}
|
||||
|
||||
public List<ObjectGuid> GetItemsProvidingTemporaryAppearance(uint itemModifiedAppearanceId)
|
||||
{
|
||||
return _temporaryAppearances.LookupByKey(itemModifiedAppearanceId);
|
||||
}
|
||||
|
||||
public void SetAppearanceIsFavorite(uint itemModifiedAppearanceId, bool apply)
|
||||
{
|
||||
var apperanceState = _favoriteAppearances.LookupByKey(itemModifiedAppearanceId);
|
||||
if (apply)
|
||||
{
|
||||
if (!_favoriteAppearances.ContainsKey(itemModifiedAppearanceId))
|
||||
_favoriteAppearances[itemModifiedAppearanceId] = FavoriteAppearanceState.New;
|
||||
else if (apperanceState == FavoriteAppearanceState.Removed)
|
||||
apperanceState = FavoriteAppearanceState.Unchanged;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if (_favoriteAppearances.ContainsKey(itemModifiedAppearanceId))
|
||||
{
|
||||
if (apperanceState == FavoriteAppearanceState.New)
|
||||
_favoriteAppearances.Remove(itemModifiedAppearanceId);
|
||||
else
|
||||
apperanceState = FavoriteAppearanceState.Removed;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
_favoriteAppearances[itemModifiedAppearanceId] = apperanceState;
|
||||
|
||||
TransmogCollectionUpdate transmogCollectionUpdate = new TransmogCollectionUpdate();
|
||||
transmogCollectionUpdate.IsFullUpdate = false;
|
||||
transmogCollectionUpdate.IsSetFavorite = apply;
|
||||
transmogCollectionUpdate.FavoriteAppearances.Add(itemModifiedAppearanceId);
|
||||
|
||||
_owner.SendPacket(transmogCollectionUpdate);
|
||||
}
|
||||
|
||||
public void SendFavoriteAppearances()
|
||||
{
|
||||
TransmogCollectionUpdate transmogCollectionUpdate = new TransmogCollectionUpdate();
|
||||
transmogCollectionUpdate.IsFullUpdate = true;
|
||||
foreach (var pair in _favoriteAppearances)
|
||||
if (pair.Value != FavoriteAppearanceState.Removed)
|
||||
transmogCollectionUpdate.FavoriteAppearances.Add(pair.Key);
|
||||
|
||||
_owner.SendPacket(transmogCollectionUpdate);
|
||||
}
|
||||
|
||||
public void AddTransmogSet(uint transmogSetId)
|
||||
{
|
||||
var items = Global.DB2Mgr.GetTransmogSetItems(transmogSetId);
|
||||
if (items.Empty())
|
||||
return;
|
||||
|
||||
foreach (TransmogSetItemRecord item in items)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = CliDB.ItemModifiedAppearanceStorage.LookupByKey(item.ItemModifiedAppearanceID);
|
||||
if (itemModifiedAppearance == null)
|
||||
continue;
|
||||
|
||||
AddItemAppearance(itemModifiedAppearance);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSetCompleted(uint transmogSetId)
|
||||
{
|
||||
var transmogSetItems = Global.DB2Mgr.GetTransmogSetItems(transmogSetId);
|
||||
if (transmogSetItems.Empty())
|
||||
return false;
|
||||
|
||||
int[] knownPieces = new int[EquipmentSlot.End];
|
||||
for (var i = 0; i < EquipmentSlot.End; ++i)
|
||||
knownPieces[i] = -1;
|
||||
|
||||
foreach (TransmogSetItemRecord transmogSetItem in transmogSetItems)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = CliDB.ItemModifiedAppearanceStorage.LookupByKey(transmogSetItem.ItemModifiedAppearanceID);
|
||||
if (itemModifiedAppearance == null)
|
||||
continue;
|
||||
|
||||
ItemRecord item = CliDB.ItemStorage.LookupByKey(itemModifiedAppearance.ItemID);
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
int transmogSlot = Item.ItemTransmogrificationSlots[(int)item.inventoryType];
|
||||
if (transmogSlot < 0 || knownPieces[transmogSlot] == 1)
|
||||
continue;
|
||||
|
||||
(var hasAppearance, var isTemporary) = HasItemAppearance(transmogSetItem.ItemModifiedAppearanceID);
|
||||
|
||||
knownPieces[transmogSlot] = (hasAppearance && !isTemporary) ? 1 : 0;
|
||||
}
|
||||
|
||||
return !knownPieces.Contains(0);
|
||||
}
|
||||
|
||||
public bool HasToy(uint itemId) { return _toys.ContainsKey(itemId); }
|
||||
public Dictionary<uint, bool> GetAccountToys() { return _toys; }
|
||||
public Dictionary<uint, HeirloomData> GetAccountHeirlooms() { return _heirlooms; }
|
||||
public Dictionary<uint, MountStatusFlags> GetAccountMounts() { return _mounts; }
|
||||
|
||||
WorldSession _owner;
|
||||
|
||||
Dictionary<uint, bool> _toys = new Dictionary<uint, bool>();
|
||||
Dictionary<uint, HeirloomData> _heirlooms = new Dictionary<uint, HeirloomData>();
|
||||
Dictionary<uint, MountStatusFlags> _mounts = new Dictionary<uint, MountStatusFlags>();
|
||||
System.Collections.BitSet _appearances;
|
||||
MultiMap<uint, ObjectGuid> _temporaryAppearances = new MultiMap<uint, ObjectGuid>();
|
||||
Dictionary<uint, FavoriteAppearanceState> _favoriteAppearances = new Dictionary<uint, FavoriteAppearanceState>();
|
||||
|
||||
static Dictionary<uint, uint> FactionSpecificMounts = new Dictionary<uint, uint>();
|
||||
}
|
||||
|
||||
enum FavoriteAppearanceState
|
||||
{
|
||||
New,
|
||||
Removed,
|
||||
Unchanged
|
||||
}
|
||||
|
||||
public class HeirloomData
|
||||
{
|
||||
public HeirloomData(HeirloomPlayerFlags _flags = 0, uint _bonusId = 0)
|
||||
{
|
||||
flags = _flags;
|
||||
bonusId = _bonusId;
|
||||
}
|
||||
|
||||
public HeirloomPlayerFlags flags;
|
||||
public uint bonusId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Groups;
|
||||
using Game.Maps;
|
||||
using Game.Scenarios;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class KillRewarder
|
||||
{
|
||||
public KillRewarder(Player killer, Unit victim, bool isBattleground)
|
||||
{
|
||||
_killer = killer;
|
||||
_victim = victim;
|
||||
_group = killer.GetGroup();
|
||||
_groupRate = 1.0f;
|
||||
_maxNotGrayMember = null;
|
||||
_count = 0;
|
||||
_sumLevel = 0;
|
||||
_xp = 0;
|
||||
_isFullXP = false;
|
||||
_maxLevel = 0;
|
||||
_isBattleground = isBattleground;
|
||||
_isPvP = false;
|
||||
|
||||
// mark the credit as pvp if victim is player
|
||||
if (victim.IsTypeId(TypeId.Player))
|
||||
_isPvP = true;
|
||||
// or if its owned by player and its not a vehicle
|
||||
else if (victim.GetCharmerOrOwnerGUID().IsPlayer())
|
||||
_isPvP = !victim.IsVehicle();
|
||||
|
||||
_InitGroupData();
|
||||
}
|
||||
|
||||
public void Reward()
|
||||
{
|
||||
// 3. Reward killer (and group, if necessary).
|
||||
if (_group)
|
||||
// 3.1. If killer is in group, reward group.
|
||||
_RewardGroup();
|
||||
else
|
||||
{
|
||||
// 3.2. Reward single killer (not group case).
|
||||
// 3.2.1. Initialize initial XP amount based on killer's level.
|
||||
_InitXP(_killer);
|
||||
// To avoid unnecessary calculations and calls,
|
||||
// proceed only if XP is not ZERO or player is not on Battleground
|
||||
// (Battlegroundrewards only XP, that's why).
|
||||
if (!_isBattleground || _xp != 0)
|
||||
// 3.2.2. Reward killer.
|
||||
_RewardPlayer(_killer, false);
|
||||
}
|
||||
|
||||
// 5. Credit instance encounter.
|
||||
// 6. Update guild achievements.
|
||||
// 7. Credit scenario criterias
|
||||
Creature victim = _victim.ToCreature();
|
||||
if (victim != null)
|
||||
{
|
||||
if (victim.IsDungeonBoss())
|
||||
{
|
||||
InstanceScript instance = _victim.GetInstanceScript();
|
||||
if (instance != null)
|
||||
instance.UpdateEncounterStateForKilledCreature(_victim.GetEntry(), _victim);
|
||||
}
|
||||
|
||||
uint guildId = victim.GetMap().GetOwnerGuildId();
|
||||
var guild = Global.GuildMgr.GetGuildById(guildId);
|
||||
if (guild != null)
|
||||
guild.UpdateCriteria(CriteriaTypes.KillCreature, victim.GetEntry(), 1, 0, victim, _killer);
|
||||
|
||||
Scenario scenario = victim.GetScenario();
|
||||
if (scenario != null)
|
||||
scenario.UpdateCriteria(CriteriaTypes.KillCreature, victim.GetEntry(), 1, 0, victim, _killer);
|
||||
}
|
||||
}
|
||||
|
||||
void _InitGroupData()
|
||||
{
|
||||
if (_group)
|
||||
{
|
||||
// 2. In case when player is in group, initialize variables necessary for group calculations:
|
||||
for (GroupReference refe = _group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player member = refe.GetSource();
|
||||
if (member)
|
||||
{
|
||||
if (member.IsAlive() && member.IsAtGroupRewardDistance(_victim))
|
||||
{
|
||||
uint lvl = member.getLevel();
|
||||
// 2.1. _count - number of alive group members within reward distance;
|
||||
++_count;
|
||||
// 2.2. _sumLevel - sum of levels of alive group members within reward distance;
|
||||
_sumLevel += lvl;
|
||||
// 2.3. _maxLevel - maximum level of alive group member within reward distance;
|
||||
if (_maxLevel < lvl)
|
||||
_maxLevel = (byte)lvl;
|
||||
// 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
|
||||
// for whom victim is not gray;
|
||||
uint grayLevel = Formulas.GetGrayLevel(lvl);
|
||||
if (_victim.GetLevelForTarget(member) > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember.getLevel() < lvl))
|
||||
_maxNotGrayMember = member;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2.5. _isFullXP - flag identifying that for all group members victim is not gray,
|
||||
// so 100% XP will be rewarded (50% otherwise).
|
||||
_isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember.getLevel());
|
||||
}
|
||||
else
|
||||
_count = 1;
|
||||
}
|
||||
|
||||
void _InitXP(Player player)
|
||||
{
|
||||
// Get initial value of XP for kill.
|
||||
// XP is given:
|
||||
// * on Battlegrounds;
|
||||
// * otherwise, not in PvP;
|
||||
// * not if killer is on vehicle.
|
||||
if (_isBattleground || (!_isPvP && _killer.GetVehicle() == null))
|
||||
_xp = Formulas.XPGain(player, _victim, _isBattleground);
|
||||
}
|
||||
|
||||
void _RewardHonor(Player player)
|
||||
{
|
||||
// Rewarded player must be alive.
|
||||
if (player.IsAlive())
|
||||
player.RewardHonor(_victim, _count, -1, true);
|
||||
}
|
||||
|
||||
void _RewardXP(Player player, float rate)
|
||||
{
|
||||
uint xp = _xp;
|
||||
if (_group)
|
||||
{
|
||||
// 4.2.1. If player is in group, adjust XP:
|
||||
// * set to 0 if player's level is more than maximum level of not gray member;
|
||||
// * cut XP in half if _isFullXP is false.
|
||||
if (_maxNotGrayMember != null && player.IsAlive() &&
|
||||
_maxNotGrayMember.getLevel() >= player.getLevel())
|
||||
xp = _isFullXP ?
|
||||
(uint)(xp * rate) : // Reward FULL XP if all group members are not gray.
|
||||
(uint)(xp * rate / 2) + 1; // Reward only HALF of XP if some of group members are gray.
|
||||
else
|
||||
xp = 0;
|
||||
}
|
||||
if (xp != 0)
|
||||
{
|
||||
// 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT and SPELL_AURA_MOD_XP_FROM_CREATURE_TYPE).
|
||||
xp = (uint)(xp * player.GetTotalAuraMultiplier(AuraType.ModXpPct));
|
||||
xp = (uint)(xp * player.GetTotalAuraMultiplierByMiscValue(AuraType.ModXpFromCreatureType, (int)_victim.GetCreatureType()));
|
||||
|
||||
// 4.2.3. Give XP to player.
|
||||
player.GiveXP(xp, _victim, _groupRate);
|
||||
Pet pet = player.GetPet();
|
||||
if (pet)
|
||||
// 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case).
|
||||
pet.GivePetXP(_group ? xp / 2 : xp);
|
||||
}
|
||||
}
|
||||
|
||||
void _RewardReputation(Player player, float rate)
|
||||
{
|
||||
// 4.3. Give reputation (player must not be on BG).
|
||||
// Even dead players and corpses are rewarded.
|
||||
player.RewardReputation(_victim, rate);
|
||||
}
|
||||
|
||||
void _RewardKillCredit(Player player)
|
||||
{
|
||||
// 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse).
|
||||
if (!_group || player.IsAlive() || player.GetCorpse() == null)
|
||||
{
|
||||
Creature target = _victim.ToCreature();
|
||||
if (target != null)
|
||||
{
|
||||
player.KilledMonster(target.GetCreatureTemplate(), target.GetGUID());
|
||||
player.UpdateCriteria(CriteriaTypes.KillCreatureType, (ulong)target.GetCreatureType(), 1, 0, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _RewardPlayer(Player player, bool isDungeon)
|
||||
{
|
||||
// 4. Reward player.
|
||||
if (!_isBattleground)
|
||||
{
|
||||
// 4.1. Give honor (player must be alive and not on BG).
|
||||
_RewardHonor(player);
|
||||
// 4.1.1 Send player killcredit for quests with PlayerSlain
|
||||
if (_victim.IsTypeId(TypeId.Player))
|
||||
player.KilledPlayerCredit();
|
||||
}
|
||||
// Give XP only in PvE or in Battlegrounds.
|
||||
// Give reputation and kill credit only in PvE.
|
||||
if (!_isPvP || _isBattleground)
|
||||
{
|
||||
float rate = _group ? _groupRate * player.getLevel() / _sumLevel : 1.0f;
|
||||
if (_xp != 0)
|
||||
// 4.2. Give XP.
|
||||
_RewardXP(player, rate);
|
||||
if (!_isBattleground)
|
||||
{
|
||||
// If killer is in dungeon then all members receive full reputation at kill.
|
||||
_RewardReputation(player, isDungeon ? 1.0f : rate);
|
||||
_RewardKillCredit(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _RewardGroup()
|
||||
{
|
||||
if (_maxLevel != 0)
|
||||
{
|
||||
if (_maxNotGrayMember != null)
|
||||
// 3.1.1. Initialize initial XP amount based on maximum level of group member,
|
||||
// for whom victim is not gray.
|
||||
_InitXP(_maxNotGrayMember);
|
||||
// To avoid unnecessary calculations and calls,
|
||||
// proceed only if XP is not ZERO or player is not on Battleground
|
||||
// (Battlegroundrewards only XP, that's why).
|
||||
if (!_isBattleground || _xp != 0)
|
||||
{
|
||||
bool isDungeon = !_isPvP && CliDB.MapStorage.LookupByKey(_killer.GetMapId()).IsDungeon();
|
||||
if (!_isBattleground)
|
||||
{
|
||||
// 3.1.2. Alter group rate if group is in raid (not for Battlegrounds).
|
||||
bool isRaid = !_isPvP && CliDB.MapStorage.LookupByKey(_killer.GetMapId()).IsRaid() && _group.isRaidGroup();
|
||||
_groupRate = Formulas.XPInGroupRate(_count, isRaid);
|
||||
}
|
||||
// 3.1.3. Reward each group member (even dead or corpse) within reward distance.
|
||||
for (GroupReference refe = _group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player member = refe.GetSource();
|
||||
if (member)
|
||||
{
|
||||
if (member.IsAtGroupRewardDistance(_victim))
|
||||
{
|
||||
_RewardPlayer(member, isDungeon);
|
||||
member.UpdateCriteria(CriteriaTypes.SpecialPvpKill, 1, 0, 0, _victim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Player _killer;
|
||||
Unit _victim;
|
||||
Group _group;
|
||||
float _groupRate;
|
||||
Player _maxNotGrayMember;
|
||||
uint _count;
|
||||
uint _sumLevel;
|
||||
uint _xp;
|
||||
bool _isFullXP;
|
||||
byte _maxLevel;
|
||||
bool _isBattleground;
|
||||
bool _isPvP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Achievements;
|
||||
using Game.DataStorage;
|
||||
using Game.Guilds;
|
||||
using Game.Scenarios;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public void ResetAchievements()
|
||||
{
|
||||
m_achievementSys.Reset();
|
||||
}
|
||||
|
||||
public void SendRespondInspectAchievements(Player player)
|
||||
{
|
||||
m_achievementSys.SendAchievementInfo(player);
|
||||
}
|
||||
|
||||
public uint GetAchievementPoints()
|
||||
{
|
||||
return m_achievementSys.GetAchievementPoints();
|
||||
}
|
||||
public bool HasAchieved(uint achievementId)
|
||||
{
|
||||
return m_achievementSys.HasAchieved(achievementId);
|
||||
}
|
||||
public void StartCriteriaTimer(CriteriaTimedTypes type, uint entry, uint timeLost = 0)
|
||||
{
|
||||
m_achievementSys.StartCriteriaTimer(type, entry, timeLost);
|
||||
}
|
||||
|
||||
public void RemoveCriteriaTimer(CriteriaTimedTypes type, uint entry)
|
||||
{
|
||||
m_achievementSys.RemoveCriteriaTimer(type, entry);
|
||||
}
|
||||
|
||||
public void ResetCriteria(CriteriaTypes type, ulong miscValue1 = 0, ulong miscValue2 = 0, bool evenIfCriteriaComplete = false)
|
||||
{
|
||||
m_achievementSys.ResetCriteria(type, miscValue1, miscValue2, evenIfCriteriaComplete);
|
||||
m_questObjectiveCriteriaMgr.ResetCriteria(type, miscValue1, miscValue2, evenIfCriteriaComplete);
|
||||
}
|
||||
|
||||
public void UpdateCriteria(CriteriaTypes type, ulong miscValue1 = 0, ulong miscValue2 = 0, ulong miscValue3 = 0, Unit unit = null)
|
||||
{
|
||||
m_achievementSys.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
m_questObjectiveCriteriaMgr.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
|
||||
// Update only individual achievement criteria here, otherwise we may get multiple updates
|
||||
// from a single boss kill
|
||||
if (CriteriaManager.IsGroupCriteriaType(type))
|
||||
return;
|
||||
|
||||
Scenario scenario = GetScenario();
|
||||
if (scenario != null)
|
||||
scenario.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
|
||||
Guild guild = Global.GuildMgr.GetGuildById(GetGuildId());
|
||||
if (guild)
|
||||
guild.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
}
|
||||
|
||||
public void CompletedAchievement(AchievementRecord entry)
|
||||
{
|
||||
m_achievementSys.CompletedAchievement(entry, this);
|
||||
}
|
||||
|
||||
public bool ModifierTreeSatisfied(uint modifierTreeId)
|
||||
{
|
||||
return m_achievementSys.ModifierTreeSatisfied(modifierTreeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Groups;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
void SetRegularAttackTime()
|
||||
{
|
||||
for (WeaponAttackType weaponAttackType = 0; weaponAttackType < WeaponAttackType.Max; ++weaponAttackType)
|
||||
{
|
||||
Item tmpitem = GetWeaponForAttack(weaponAttackType, true);
|
||||
if (tmpitem != null && !tmpitem.IsBroken())
|
||||
{
|
||||
ItemTemplate proto = tmpitem.GetTemplate();
|
||||
if (proto.GetDelay() != 0)
|
||||
SetBaseAttackTime(weaponAttackType, proto.GetDelay());
|
||||
}
|
||||
else
|
||||
SetBaseAttackTime(weaponAttackType, SharedConst.BaseAttackTime); // If there is no weapon reset attack time to base (might have been changed from forms)
|
||||
}
|
||||
}
|
||||
|
||||
public void RewardPlayerAndGroupAtKill(Unit victim, bool isBattleground)
|
||||
{
|
||||
new KillRewarder(this, victim, isBattleground).Reward();
|
||||
}
|
||||
|
||||
public void RewardPlayerAndGroupAtEvent(uint creature_id, WorldObject pRewardSource)
|
||||
{
|
||||
if (pRewardSource == null)
|
||||
return;
|
||||
ObjectGuid creature_guid = pRewardSource.IsTypeId(TypeId.Unit) ? pRewardSource.GetGUID() : ObjectGuid.Empty;
|
||||
|
||||
// prepare data for near group iteration
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player player = refe.GetSource();
|
||||
if (!player)
|
||||
continue;
|
||||
|
||||
if (!player.IsAtGroupRewardDistance(pRewardSource))
|
||||
continue; // member (alive or dead) or his corpse at req. distance
|
||||
|
||||
// quest objectives updated only for alive group member or dead but with not released body
|
||||
if (player.IsAlive() || !player.GetCorpse())
|
||||
player.KilledMonsterCredit(creature_id, creature_guid);
|
||||
}
|
||||
}
|
||||
else
|
||||
KilledMonsterCredit(creature_id, creature_guid);
|
||||
}
|
||||
|
||||
public void AddWeaponProficiency(uint newflag) { m_WeaponProficiency |= newflag; }
|
||||
public void AddArmorProficiency(uint newflag) { m_ArmorProficiency |= newflag; }
|
||||
public uint GetWeaponProficiency() { return m_WeaponProficiency; }
|
||||
public uint GetArmorProficiency() { return m_ArmorProficiency; }
|
||||
public void SendProficiency(ItemClass itemClass, uint itemSubclassMask)
|
||||
{
|
||||
SetProficiency packet = new SetProficiency();
|
||||
packet.ProficiencyMask = itemSubclassMask;
|
||||
packet.ProficiencyClass = (byte)itemClass;
|
||||
SendPacket(packet);
|
||||
}
|
||||
|
||||
bool CanTitanGrip() { return m_canTitanGrip; }
|
||||
|
||||
public override bool CanUseAttackType(WeaponAttackType attacktype)
|
||||
{
|
||||
switch (attacktype)
|
||||
{
|
||||
case WeaponAttackType.BaseAttack:
|
||||
return !HasFlag(UnitFields.Flags, UnitFlags.Disarmed);
|
||||
case WeaponAttackType.OffAttack:
|
||||
return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmOffhand);
|
||||
case WeaponAttackType.RangedAttack:
|
||||
return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmRanged);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float GetRatingMultiplier(CombatRating cr)
|
||||
{
|
||||
GtCombatRatingsRecord Rating = CliDB.CombatRatingsGameTable.GetRow(getLevel());
|
||||
if (Rating == null)
|
||||
return 1.0f;
|
||||
|
||||
float value = GetGameTableColumnForCombatRating(Rating, cr);
|
||||
if (value == 0)
|
||||
return 1.0f; // By default use minimum coefficient (not must be called)
|
||||
|
||||
return 1.0f / value;
|
||||
}
|
||||
public float GetRatingBonusValue(CombatRating cr)
|
||||
{
|
||||
float baseResult = GetFloatValue(PlayerFields.CombatRating1 + (int)cr) * GetRatingMultiplier(cr);
|
||||
if (cr != CombatRating.ResiliencePlayerDamage)
|
||||
return baseResult;
|
||||
return (float)(1.0f - Math.Pow(0.99f, baseResult)) * 100.0f;
|
||||
}
|
||||
|
||||
void GetDodgeFromAgility(float diminishing, float nondiminishing)
|
||||
{
|
||||
/*// Table for base dodge values
|
||||
float[] dodge_base =
|
||||
{
|
||||
0.037580f, // Warrior
|
||||
0.036520f, // Paladin
|
||||
-0.054500f, // Hunter
|
||||
-0.005900f, // Rogue
|
||||
0.031830f, // Priest
|
||||
0.036640f, // DK
|
||||
0.016750f, // Shaman
|
||||
0.034575f, // Mage
|
||||
0.020350f, // Warlock
|
||||
0.0f, // ??
|
||||
0.049510f // Druid
|
||||
};
|
||||
// Crit/agility to dodge/agility coefficient multipliers; 3.2.0 increased required agility by 15%
|
||||
float[] crit_to_dodge =
|
||||
{
|
||||
0.85f/1.15f, // Warrior
|
||||
1.00f/1.15f, // Paladin
|
||||
1.11f/1.15f, // Hunter
|
||||
2.00f/1.15f, // Rogue
|
||||
1.00f/1.15f, // Priest
|
||||
0.85f/1.15f, // DK
|
||||
1.60f/1.15f, // Shaman
|
||||
1.00f/1.15f, // Mage
|
||||
0.97f/1.15f, // Warlock (?)
|
||||
0.0f, // ??
|
||||
2.00f/1.15f // Druid
|
||||
};
|
||||
|
||||
uint level = getLevel();
|
||||
uint pclass = (uint)GetClass();
|
||||
|
||||
if (level > CliDB.GtChanceToMeleeCritStorage.GetTableRowCount())
|
||||
level = CliDB.GtChanceToMeleeCritStorage.GetTableRowCount() - 1;
|
||||
|
||||
// Dodge per agility is proportional to crit per agility, which is available from DBC files
|
||||
var dodgeRatio = CliDB.GtChanceToMeleeCritStorage.EvaluateTable(level - 1, pclass - 1);
|
||||
if (dodgeRatio == null || pclass > (int)Class.Max)
|
||||
return;
|
||||
|
||||
// @todo research if talents/effects that increase total agility by x% should increase non-diminishing part
|
||||
float base_agility = GetCreateStat(Stats.Agility) * m_auraModifiersGroup[(int)UnitMods.StatAgility][(int)UnitModifierType.BasePCT];
|
||||
float bonus_agility = GetStat(Stats.Agility) - base_agility;
|
||||
|
||||
// calculate diminishing (green in char screen) and non-diminishing (white) contribution
|
||||
diminishing = 100.0f * bonus_agility * dodgeRatio.Value * crit_to_dodge[(int)pclass - 1];
|
||||
nondiminishing = 100.0f * (dodge_base[(int)pclass - 1] + base_agility * dodgeRatio.Value * crit_to_dodge[pclass - 1]);
|
||||
*/
|
||||
}
|
||||
|
||||
float GetTotalPercentageModValue(BaseModGroup modGroup)
|
||||
{
|
||||
return m_auraBaseMod[(int)modGroup][0] + m_auraBaseMod[(int)modGroup][1];
|
||||
}
|
||||
|
||||
public float GetExpertiseDodgeOrParryReduction(WeaponAttackType attType)
|
||||
{
|
||||
float baseExpertise = 7.5f;
|
||||
switch (attType)
|
||||
{
|
||||
case WeaponAttackType.BaseAttack:
|
||||
return baseExpertise + GetUInt32Value(PlayerFields.Expertise) / 4.0f;
|
||||
case WeaponAttackType.OffAttack:
|
||||
return baseExpertise + GetUInt32Value(PlayerFields.OffhandExpertise) / 4.0f;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public bool IsUseEquipedWeapon(bool mainhand)
|
||||
{
|
||||
// disarm applied only to mainhand weapon
|
||||
return !IsInFeralForm() && (!mainhand || !HasFlag(UnitFields.Flags, UnitFlags.Disarmed));
|
||||
}
|
||||
|
||||
public void SetCanTitanGrip(bool value, uint penaltySpellId = 0)
|
||||
{
|
||||
if (value == m_canTitanGrip)
|
||||
return;
|
||||
|
||||
m_canTitanGrip = value;
|
||||
m_titanGripPenaltySpellId = penaltySpellId;
|
||||
}
|
||||
|
||||
void CheckTitanGripPenalty()
|
||||
{
|
||||
if (!CanTitanGrip())
|
||||
return;
|
||||
|
||||
bool apply = IsUsingTwoHandedWeaponInOneHand();
|
||||
if (apply)
|
||||
{
|
||||
if (!HasAura(m_titanGripPenaltySpellId))
|
||||
CastSpell((Unit)null, m_titanGripPenaltySpellId, true);
|
||||
}
|
||||
else
|
||||
RemoveAurasDueToSpell(m_titanGripPenaltySpellId);
|
||||
}
|
||||
|
||||
bool IsTwoHandUsed()
|
||||
{
|
||||
Item mainItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand);
|
||||
if (!mainItem)
|
||||
return false;
|
||||
|
||||
ItemTemplate itemTemplate = mainItem.GetTemplate();
|
||||
return (itemTemplate.GetInventoryType() == InventoryType.Weapon2Hand && !CanTitanGrip()) ||
|
||||
itemTemplate.GetInventoryType() == InventoryType.Ranged ||
|
||||
(itemTemplate.GetInventoryType() == InventoryType.RangedRight && itemTemplate.GetClass() == ItemClass.Weapon && (ItemSubClassWeapon)itemTemplate.GetSubClass() != ItemSubClassWeapon.Wand);
|
||||
}
|
||||
|
||||
bool IsUsingTwoHandedWeaponInOneHand()
|
||||
{
|
||||
Item offItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand);
|
||||
if (offItem && offItem.GetTemplate().GetInventoryType() == InventoryType.Weapon2Hand)
|
||||
return true;
|
||||
|
||||
Item mainItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand);
|
||||
if (!mainItem || mainItem.GetTemplate().GetInventoryType() == InventoryType.Weapon2Hand)
|
||||
return false;
|
||||
|
||||
if (!offItem)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void _ApplyWeaponDamage(uint slot, Item item, bool apply)
|
||||
{
|
||||
ItemTemplate proto = item.GetTemplate();
|
||||
WeaponAttackType attType = WeaponAttackType.BaseAttack;
|
||||
float damage = 0.0f;
|
||||
|
||||
if (slot == EquipmentSlot.MainHand && (proto.GetInventoryType() == InventoryType.Ranged || proto.GetInventoryType() == InventoryType.RangedRight))
|
||||
attType = WeaponAttackType.RangedAttack;
|
||||
else if (slot == EquipmentSlot.OffHand)
|
||||
attType = WeaponAttackType.OffAttack;
|
||||
|
||||
float minDamage, maxDamage;
|
||||
item.GetDamage(this, out minDamage, out maxDamage);
|
||||
|
||||
if (minDamage > 0)
|
||||
{
|
||||
damage = apply ? minDamage : SharedConst.BaseMinDamage;
|
||||
SetBaseWeaponDamage(attType, WeaponDamageRange.MinDamage, damage);
|
||||
}
|
||||
|
||||
if (maxDamage > 0)
|
||||
{
|
||||
damage = apply ? maxDamage : SharedConst.BaseMaxDamage;
|
||||
SetBaseWeaponDamage(attType, WeaponDamageRange.MaxDamage, damage);
|
||||
}
|
||||
|
||||
SpellShapeshiftFormRecord shapeshift = CliDB.SpellShapeshiftFormStorage.LookupByKey(GetShapeshiftForm());
|
||||
if (proto.GetDelay() != 0 && !(shapeshift != null && shapeshift.CombatRoundTime != 0))
|
||||
SetBaseAttackTime(attType, apply ? proto.GetDelay() : SharedConst.BaseAttackTime);
|
||||
|
||||
if (CanModifyStats() && (damage != 0 || proto.GetDelay() != 0))
|
||||
UpdateDamagePhysical(attType);
|
||||
}
|
||||
|
||||
public void SetCanParry(bool value)
|
||||
{
|
||||
if (m_canParry == value)
|
||||
return;
|
||||
|
||||
m_canParry = value;
|
||||
UpdateParryPercentage();
|
||||
}
|
||||
|
||||
public void SetCanBlock(bool value)
|
||||
{
|
||||
if (m_canBlock == value)
|
||||
return;
|
||||
|
||||
m_canBlock = value;
|
||||
UpdateBlockPercentage();
|
||||
}
|
||||
|
||||
// duel health and mana reset methods
|
||||
public void SaveHealthBeforeDuel() { healthBeforeDuel = (uint)GetHealth(); }
|
||||
public void SaveManaBeforeDuel() { manaBeforeDuel = (uint)GetPower(PowerType.Mana); }
|
||||
public void RestoreHealthAfterDuel() { SetHealth(healthBeforeDuel); }
|
||||
public void RestoreManaAfterDuel() { SetPower(PowerType.Mana, (int)manaBeforeDuel); }
|
||||
|
||||
void UpdateDuelFlag(long currTime)
|
||||
{
|
||||
if (duel == null || duel.startTimer == 0 || currTime < duel.startTimer + 3)
|
||||
return;
|
||||
|
||||
Global.ScriptMgr.OnPlayerDuelStart(this, duel.opponent);
|
||||
|
||||
SetUInt32Value(PlayerFields.DuelTeam, 1);
|
||||
duel.opponent.SetUInt32Value(PlayerFields.DuelTeam, 2);
|
||||
|
||||
duel.startTimer = 0;
|
||||
duel.startTime = currTime;
|
||||
duel.opponent.duel.startTimer = 0;
|
||||
duel.opponent.duel.startTime = currTime;
|
||||
}
|
||||
|
||||
void CheckDuelDistance(long currTime)
|
||||
{
|
||||
if (duel == null)
|
||||
return;
|
||||
|
||||
ObjectGuid duelFlagGUID = GetGuidValue(PlayerFields.DuelArbiter);
|
||||
GameObject obj = GetMap().GetGameObject(duelFlagGUID);
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
if (duel.outOfBound == 0)
|
||||
{
|
||||
if (!IsWithinDistInMap(obj, 50))
|
||||
{
|
||||
duel.outOfBound = currTime;
|
||||
SendPacket(new DuelOutOfBounds());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsWithinDistInMap(obj, 40))
|
||||
{
|
||||
duel.outOfBound = 0;
|
||||
SendPacket(new DuelInBounds());
|
||||
}
|
||||
else if (currTime >= (duel.outOfBound + 10))
|
||||
DuelComplete(DuelCompleteType.Fled);
|
||||
}
|
||||
}
|
||||
public void DuelComplete(DuelCompleteType type)
|
||||
{
|
||||
// duel not requested
|
||||
if (duel == null)
|
||||
return;
|
||||
|
||||
// Check if DuelComplete() has been called already up in the stack and in that case don't do anything else here
|
||||
if (duel.isCompleted || duel.opponent.duel.isCompleted)
|
||||
return;
|
||||
|
||||
duel.isCompleted = true;
|
||||
duel.opponent.duel.isCompleted = true;
|
||||
|
||||
Log.outDebug(LogFilter.Player, "Duel Complete {0} {1}", GetName(), duel.opponent.GetName());
|
||||
|
||||
DuelComplete duelCompleted = new DuelComplete();
|
||||
duelCompleted.Started = type != DuelCompleteType.Interrupted;
|
||||
SendPacket(duelCompleted);
|
||||
|
||||
if (duel.opponent.GetSession() != null)
|
||||
duel.opponent.SendPacket(duelCompleted);
|
||||
|
||||
if (type != DuelCompleteType.Interrupted)
|
||||
{
|
||||
DuelWinner duelWinner = new DuelWinner();
|
||||
duelWinner.BeatenName = (type == DuelCompleteType.Won ? duel.opponent.GetName() : GetName());
|
||||
duelWinner.WinnerName = (type == DuelCompleteType.Won ? GetName() : duel.opponent.GetName());
|
||||
duelWinner.BeatenVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress();
|
||||
duelWinner.WinnerVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress();
|
||||
duelWinner.Fled = type != DuelCompleteType.Won;
|
||||
|
||||
SendMessageToSet(duelWinner, true);
|
||||
}
|
||||
|
||||
Global.ScriptMgr.OnPlayerDuelEnd(duel.opponent, this, type);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case DuelCompleteType.Fled:
|
||||
// if initiator and opponent are on the same team
|
||||
// or initiator and opponent are not PvP enabled, forcibly stop attacking
|
||||
if (duel.initiator.GetTeam() == duel.opponent.GetTeam())
|
||||
{
|
||||
duel.initiator.AttackStop();
|
||||
duel.opponent.AttackStop();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!duel.initiator.IsPvP())
|
||||
duel.initiator.AttackStop();
|
||||
if (!duel.opponent.IsPvP())
|
||||
duel.opponent.AttackStop();
|
||||
}
|
||||
break;
|
||||
case DuelCompleteType.Won:
|
||||
UpdateCriteria(CriteriaTypes.LoseDuel, 1);
|
||||
duel.opponent.UpdateCriteria(CriteriaTypes.WinDuel, 1);
|
||||
|
||||
// Credit for quest Death's Challenge
|
||||
if (GetClass() == Class.Deathknight && duel.opponent.GetQuestStatus(12733) == QuestStatus.Incomplete)
|
||||
duel.opponent.CastSpell(duel.opponent, 52994, true);
|
||||
|
||||
// Honor points after duel (the winner) - ImpConfig
|
||||
int amount = WorldConfig.GetIntValue(WorldCfg.HonorAfterDuel);
|
||||
if (amount != 0)
|
||||
duel.opponent.RewardHonor(null, 1, amount);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Victory emote spell
|
||||
if (type != DuelCompleteType.Interrupted)
|
||||
duel.opponent.CastSpell(duel.opponent, 52852, true);
|
||||
|
||||
//Remove Duel Flag object
|
||||
GameObject obj = GetMap().GetGameObject(GetGuidValue(PlayerFields.DuelArbiter));
|
||||
if (obj)
|
||||
duel.initiator.RemoveGameObject(obj, true);
|
||||
|
||||
//remove auras
|
||||
var itsAuras = duel.opponent.GetAppliedAuras();
|
||||
foreach (var pair in itsAuras)
|
||||
{
|
||||
Aura aura = pair.Value.GetBase();
|
||||
if (!pair.Value.IsPositive() && aura.GetCasterGUID() == GetGUID() && aura.GetApplyTime() >= duel.startTime)
|
||||
duel.opponent.RemoveAura(pair);
|
||||
}
|
||||
|
||||
var myAuras = GetAppliedAuras();
|
||||
foreach (var pair in myAuras)
|
||||
{
|
||||
Aura aura = pair.Value.GetBase();
|
||||
if (!pair.Value.IsPositive() && aura.GetCasterGUID() == duel.opponent.GetGUID() && aura.GetApplyTime() >= duel.startTime)
|
||||
RemoveAura(pair);
|
||||
}
|
||||
|
||||
// cleanup combo points
|
||||
ClearComboPoints();
|
||||
duel.opponent.ClearComboPoints();
|
||||
|
||||
//cleanups
|
||||
SetGuidValue(PlayerFields.DuelArbiter, ObjectGuid.Empty);
|
||||
SetUInt32Value(PlayerFields.DuelTeam, 0);
|
||||
duel.opponent.SetGuidValue(PlayerFields.DuelArbiter, ObjectGuid.Empty);
|
||||
duel.opponent.SetUInt32Value(PlayerFields.DuelTeam, 0);
|
||||
|
||||
duel.opponent.duel = null;
|
||||
duel = null;
|
||||
}
|
||||
|
||||
//PVP
|
||||
public void SetPvPDeath(bool on)
|
||||
{
|
||||
if (on)
|
||||
m_ExtraFlags |= PlayerExtraFlags.PVPDeath;
|
||||
else
|
||||
m_ExtraFlags &= ~PlayerExtraFlags.PVPDeath;
|
||||
}
|
||||
|
||||
public void SetContestedPvPTimer(uint newTime) { m_contestedPvPTimer = newTime; }
|
||||
|
||||
public void ResetContestedPvP()
|
||||
{
|
||||
ClearUnitState(UnitState.AttackPlayer);
|
||||
RemoveFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP);
|
||||
m_contestedPvPTimer = 0;
|
||||
}
|
||||
void UpdateAfkReport(long currTime)
|
||||
{
|
||||
if (m_bgData.bgAfkReportedTimer <= currTime)
|
||||
{
|
||||
m_bgData.bgAfkReportedCount = 0;
|
||||
m_bgData.bgAfkReportedTimer = currTime + 5 * Time.Minute;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateContestedPvP(uint diff)
|
||||
{
|
||||
if (m_contestedPvPTimer == 0 || IsInCombat())
|
||||
return;
|
||||
|
||||
if (m_contestedPvPTimer <= diff)
|
||||
{
|
||||
ResetContestedPvP();
|
||||
}
|
||||
else
|
||||
m_contestedPvPTimer -= diff;
|
||||
}
|
||||
|
||||
public void UpdatePvPFlag(long currTime)
|
||||
{
|
||||
if (!IsPvP())
|
||||
return;
|
||||
|
||||
if (pvpInfo.EndTimer == 0 || currTime < (pvpInfo.EndTimer + 300) || pvpInfo.IsHostile)
|
||||
return;
|
||||
|
||||
UpdatePvP(false);
|
||||
}
|
||||
|
||||
public void UpdatePvP(bool state, bool Override = false)
|
||||
{
|
||||
if (!state || Override)
|
||||
{
|
||||
SetPvP(state);
|
||||
pvpInfo.EndTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pvpInfo.EndTimer = Time.UnixTime;
|
||||
SetPvP(state);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePvPState(bool onlyFFA = false)
|
||||
{
|
||||
// @todo should we always synchronize UNIT_FIELD_BYTES_2, 1 of controller and controlled?
|
||||
// no, we shouldn't, those are checked for affecting player by client
|
||||
if (!pvpInfo.IsInNoPvPArea && !IsGameMaster()
|
||||
&& (pvpInfo.IsInFFAPvPArea || Global.WorldMgr.IsFFAPvPRealm()))
|
||||
{
|
||||
if (!IsFFAPvP())
|
||||
{
|
||||
SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
||||
foreach (var unit in m_Controlled)
|
||||
unit.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, (byte)UnitBytes2Flags.FFAPvp);
|
||||
}
|
||||
}
|
||||
else if (IsFFAPvP())
|
||||
{
|
||||
RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
||||
foreach (var unit in m_Controlled)
|
||||
unit.RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
||||
}
|
||||
|
||||
if (onlyFFA)
|
||||
return;
|
||||
|
||||
if (pvpInfo.IsHostile) // in hostile area
|
||||
{
|
||||
if (!IsPvP() || pvpInfo.EndTimer != 0)
|
||||
UpdatePvP(true, true);
|
||||
}
|
||||
else // in friendly area
|
||||
{
|
||||
if (IsPvP() && !HasFlag(PlayerFields.Flags, PlayerFlags.InPVP) && pvpInfo.EndTimer == 0)
|
||||
pvpInfo.EndTimer = Time.UnixTime; // start toggle-off
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetPvP(bool state)
|
||||
{
|
||||
base.SetPvP(state);
|
||||
foreach (var unit in m_Controlled)
|
||||
unit.SetPvP(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Achievements;
|
||||
using Game.Chat;
|
||||
using Game.DataStorage;
|
||||
using Game.Garrisons;
|
||||
using Game.Groups;
|
||||
using Game.Mails;
|
||||
using Game.Maps;
|
||||
using Game.Misc;
|
||||
using Game.Spells;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public WorldSession GetSession() { return Session; }
|
||||
public PlayerSocial GetSocial() { return m_social; }
|
||||
|
||||
//Gossip
|
||||
public PlayerMenu PlayerTalkClass;
|
||||
PlayerSocial m_social;
|
||||
List<Channel> m_channels = new List<Channel>();
|
||||
List<ObjectGuid> WhisperList = new List<ObjectGuid>();
|
||||
public string autoReplyMsg;
|
||||
|
||||
//Inventory
|
||||
Dictionary<ulong, EquipmentSetInfo> _equipmentSets = new Dictionary<ulong, EquipmentSetInfo>();
|
||||
public List<ItemSetEffect> ItemSetEff = new List<ItemSetEffect>();
|
||||
List<EnchantDuration> m_enchantDuration = new List<EnchantDuration>();
|
||||
List<Item> m_itemDuration = new List<Item>();
|
||||
List<ObjectGuid> m_itemSoulboundTradeable = new List<ObjectGuid>();
|
||||
List<ObjectGuid> m_refundableItems = new List<ObjectGuid>();
|
||||
public List<Item> ItemUpdateQueue = new List<Item>();
|
||||
VoidStorageItem[] _voidStorageItems = new VoidStorageItem[SharedConst.VoidStorageMaxSlot];
|
||||
Item[] m_items = new Item[(int)PlayerSlots.Count];
|
||||
uint m_WeaponProficiency;
|
||||
uint m_ArmorProficiency;
|
||||
uint m_currentBuybackSlot;
|
||||
TradeData m_trade;
|
||||
|
||||
//PVP
|
||||
BgBattlegroundQueueID_Rec[] m_bgBattlegroundQueueID = new BgBattlegroundQueueID_Rec[SharedConst.MaxPlayerBGQueues];
|
||||
BGData m_bgData;
|
||||
bool m_IsBGRandomWinner;
|
||||
public PvPInfo pvpInfo;
|
||||
uint m_ArenaTeamIdInvited;
|
||||
long m_lastHonorUpdateTime;
|
||||
uint m_contestedPvPTimer;
|
||||
|
||||
//Groups/Raids
|
||||
GroupReference m_group = new GroupReference();
|
||||
GroupReference m_originalGroup = new GroupReference();
|
||||
Group m_groupInvite;
|
||||
GroupUpdateFlags m_groupUpdateMask;
|
||||
bool m_bPassOnGroupLoot;
|
||||
GroupUpdateCounter[] m_groupUpdateSequences = new GroupUpdateCounter[2];
|
||||
|
||||
public Dictionary<uint, InstanceBind>[] m_boundInstances = new Dictionary<uint, InstanceBind>[(int)Difficulty.Max];
|
||||
Dictionary<uint, long> _instanceResetTimes = new Dictionary<uint, long>();
|
||||
uint _pendingBindId;
|
||||
uint _pendingBindTimer;
|
||||
public bool m_InstanceValid;
|
||||
|
||||
Difficulty m_dungeonDifficulty;
|
||||
Difficulty m_raidDifficulty;
|
||||
Difficulty m_legacyRaidDifficulty;
|
||||
Difficulty m_prevMapDifficulty;
|
||||
|
||||
//Movement
|
||||
public PlayerTaxi m_taxi = new PlayerTaxi();
|
||||
public byte[] m_forced_speed_changes = new byte[(int)UnitMoveType.Max];
|
||||
uint m_lastFallTime;
|
||||
float m_lastFallZ;
|
||||
WorldLocation teleportDest;
|
||||
TeleportToOptions m_teleport_options;
|
||||
bool mSemaphoreTeleport_Near;
|
||||
bool mSemaphoreTeleport_Far;
|
||||
PlayerDelayedOperations m_DelayedOperations;
|
||||
bool m_bCanDelayTeleport;
|
||||
bool m_bHasDelayedTeleport;
|
||||
|
||||
PlayerUnderwaterState m_MirrorTimerFlags;
|
||||
PlayerUnderwaterState m_MirrorTimerFlagsLast;
|
||||
bool m_isInWater;
|
||||
|
||||
//Stats
|
||||
uint m_baseSpellPower;
|
||||
uint m_baseManaRegen;
|
||||
uint m_baseHealthRegen;
|
||||
int m_spellPenetrationItemMod;
|
||||
uint m_lastPotionId;
|
||||
|
||||
//Spell
|
||||
Dictionary<uint, PlayerSpell> m_spells = new Dictionary<uint, PlayerSpell>();
|
||||
Dictionary<uint, SkillStatusData> mSkillStatus = new Dictionary<uint, SkillStatusData>();
|
||||
Dictionary<uint, PlayerCurrency> _currencyStorage = new Dictionary<uint, PlayerCurrency>();
|
||||
List<SpellModifier>[][] m_spellMods = new List<SpellModifier>[(int)SpellModOp.Max][];
|
||||
MultiMap<uint, uint> m_overrideSpells = new MultiMap<uint, uint>();
|
||||
public Spell m_spellModTakingSpell;
|
||||
uint m_oldpetspell;
|
||||
// Rune type / Rune timer
|
||||
uint[] m_runeGraceCooldown = new uint[PlayerConst.MaxRunes];
|
||||
uint[] m_lastRuneGraceTimers = new uint[PlayerConst.MaxRunes];
|
||||
|
||||
//Mail
|
||||
List<Mail> m_mail = new List<Mail>();
|
||||
Dictionary<ulong, Item> mMitems = new Dictionary<ulong, Item>();
|
||||
public byte unReadMails;
|
||||
long m_nextMailDelivereTime;
|
||||
public bool m_mailsLoaded;
|
||||
public bool m_mailsUpdated;
|
||||
|
||||
//Pets
|
||||
public uint m_stableSlots;
|
||||
uint m_temporaryUnsummonedPetNumber;
|
||||
uint m_lastpetnumber;
|
||||
|
||||
// Player summoning
|
||||
long m_summon_expire;
|
||||
WorldLocation m_summon_location;
|
||||
|
||||
RestMgr _restMgr;
|
||||
|
||||
//Combat
|
||||
int[] baseRatingValue = new int[(int)CombatRating.Max];
|
||||
public float[][] m_auraBaseMod = new float[(int)BaseModGroup.End][];
|
||||
public DuelInfo duel;
|
||||
bool m_canParry;
|
||||
bool m_canBlock;
|
||||
bool m_canTitanGrip;
|
||||
uint m_titanGripPenaltySpellId;
|
||||
uint m_deathTimer;
|
||||
long m_deathExpireTime;
|
||||
byte m_swingErrorMsg;
|
||||
uint m_combatExitTime;
|
||||
uint m_regenTimerCount;
|
||||
uint m_weaponChangeTimer;
|
||||
|
||||
//Quest
|
||||
List<uint> m_timedquests = new List<uint>();
|
||||
List<uint> m_weeklyquests = new List<uint>();
|
||||
List<uint> m_monthlyquests = new List<uint>();
|
||||
MultiMap<uint, uint> m_seasonalquests = new MultiMap<uint, uint>();
|
||||
Dictionary<uint, QuestStatusData> m_QuestStatus = new Dictionary<uint, QuestStatusData>();
|
||||
Dictionary<uint, QuestSaveType> m_QuestStatusSave = new Dictionary<uint, QuestSaveType>();
|
||||
List<uint> m_DFQuests = new List<uint>();
|
||||
List<uint> m_RewardedQuests = new List<uint>();
|
||||
Dictionary<uint, QuestSaveType> m_RewardedQuestsSave = new Dictionary<uint, QuestSaveType>();
|
||||
|
||||
bool m_DailyQuestChanged;
|
||||
bool m_WeeklyQuestChanged;
|
||||
bool m_MonthlyQuestChanged;
|
||||
bool m_SeasonalQuestChanged;
|
||||
long m_lastDailyQuestTime;
|
||||
|
||||
Garrison _garrison;
|
||||
|
||||
CinematicManager _cinematicMgr;
|
||||
|
||||
// variables to save health and mana before duel and restore them after duel
|
||||
ulong healthBeforeDuel;
|
||||
uint manaBeforeDuel;
|
||||
|
||||
bool _advancedCombatLoggingEnabled;
|
||||
|
||||
WorldLocation _corpseLocation;
|
||||
|
||||
//Core
|
||||
WorldSession Session;
|
||||
uint m_nextSave;
|
||||
byte m_cinematic;
|
||||
|
||||
uint m_movie;
|
||||
|
||||
SpecializationInfo _specializationInfo;
|
||||
public List<ObjectGuid> m_clientGUIDs = new List<ObjectGuid>();
|
||||
public WorldObject seerView;
|
||||
// only changed for direct client control (possess, vehicle etc.), not stuff you control using pet commands
|
||||
public Unit m_unitMovedByMe;
|
||||
Team m_team;
|
||||
public Stack<uint> m_timeSyncQueue = new Stack<uint>();
|
||||
uint m_timeSyncTimer;
|
||||
public uint m_timeSyncClient;
|
||||
public uint m_timeSyncServer;
|
||||
ReputationMgr reputationMgr;
|
||||
QuestObjectiveCriteriaManager m_questObjectiveCriteriaMgr;
|
||||
public AtLoginFlags atLoginFlags;
|
||||
public bool m_itemUpdateQueueBlocked;
|
||||
|
||||
PlayerExtraFlags m_ExtraFlags;
|
||||
|
||||
public bool isDebugAreaTriggers { get; set; }
|
||||
uint m_zoneUpdateId;
|
||||
uint m_areaUpdateId;
|
||||
uint m_zoneUpdateTimer;
|
||||
|
||||
uint m_ChampioningFaction;
|
||||
byte m_grantableLevels;
|
||||
byte m_fishingSteps;
|
||||
|
||||
// Recall position
|
||||
WorldLocation m_recall_location;
|
||||
WorldLocation homebind;
|
||||
uint homebindAreaId;
|
||||
uint m_HomebindTimer;
|
||||
|
||||
ResurrectionData _resurrectionData;
|
||||
|
||||
PlayerAchievementMgr m_achievementSys;
|
||||
|
||||
SceneMgr m_sceneMgr;
|
||||
|
||||
Dictionary<ObjectGuid /*LootObject*/, ObjectGuid /*WorldObject*/> m_AELootView = new Dictionary<ObjectGuid, ObjectGuid>();
|
||||
|
||||
CUFProfile[] _CUFProfiles = new CUFProfile[PlayerConst.MaxCUFProfiles];
|
||||
float[] m_powerFraction = new float[(int)PowerType.MaxPerClass];
|
||||
int[] m_MirrorTimer = new int[3];
|
||||
|
||||
ulong m_GuildIdInvited;
|
||||
DeclinedName _declinedname;
|
||||
Runes m_runes = new Runes();
|
||||
uint m_drunkTimer;
|
||||
long m_logintime;
|
||||
long m_Last_tick;
|
||||
uint m_PlayedTimeTotal;
|
||||
uint m_PlayedTimeLevel;
|
||||
|
||||
Dictionary<byte, ActionButton> m_actionButtons = new Dictionary<byte, ActionButton>();
|
||||
ObjectGuid m_divider;
|
||||
uint m_ingametime;
|
||||
|
||||
PlayerCommandStates _activeCheats;
|
||||
}
|
||||
|
||||
public class PlayerInfo
|
||||
{
|
||||
public uint MapId;
|
||||
public uint ZoneId;
|
||||
public float PositionX;
|
||||
public float PositionY;
|
||||
public float PositionZ;
|
||||
public float Orientation;
|
||||
|
||||
public uint DisplayId_m;
|
||||
public uint DisplayId_f;
|
||||
|
||||
public List<PlayerCreateInfoItem> item = new List<PlayerCreateInfoItem>();
|
||||
public List<uint> customSpells = new List<uint>();
|
||||
public List<uint> castSpells = new List<uint>();
|
||||
public List<PlayerCreateInfoAction> action = new List<PlayerCreateInfoAction>();
|
||||
public List<SkillRaceClassInfoRecord> skills = new List<SkillRaceClassInfoRecord>();
|
||||
|
||||
public PlayerLevelInfo[] levelInfo = new PlayerLevelInfo[WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)];
|
||||
}
|
||||
|
||||
public class PlayerCreateInfoItem
|
||||
{
|
||||
public PlayerCreateInfoItem(uint id, uint amount)
|
||||
{
|
||||
item_id = id;
|
||||
item_amount = amount;
|
||||
}
|
||||
|
||||
public uint item_id;
|
||||
public uint item_amount;
|
||||
}
|
||||
|
||||
public class PlayerCreateInfoAction
|
||||
{
|
||||
public PlayerCreateInfoAction() : this(0, 0, 0) { }
|
||||
public PlayerCreateInfoAction(byte _button, uint _action, byte _type)
|
||||
{
|
||||
button = _button;
|
||||
type = _type;
|
||||
action = _action;
|
||||
}
|
||||
|
||||
public byte button;
|
||||
public byte type;
|
||||
public uint action;
|
||||
}
|
||||
|
||||
public class PlayerLevelInfo
|
||||
{
|
||||
public ushort[] stats = new ushort[(int)Stats.Max];
|
||||
}
|
||||
|
||||
public class PlayerCurrency
|
||||
{
|
||||
public PlayerCurrencyState state;
|
||||
public uint Quantity;
|
||||
public uint WeeklyQuantity;
|
||||
public uint TrackedQuantity;
|
||||
public byte Flags;
|
||||
}
|
||||
|
||||
struct PlayerDynamicFieldSpellModByLabel
|
||||
{
|
||||
public uint Mod;
|
||||
public float Value;
|
||||
public uint Label;
|
||||
}
|
||||
|
||||
public class SpecializationInfo
|
||||
{
|
||||
public SpecializationInfo()
|
||||
{
|
||||
for (byte i = 0; i < PlayerConst.MaxSpecializations; ++i)
|
||||
{
|
||||
Talents[i] = new Dictionary<uint, PlayerSpellState>();
|
||||
Glyphs[i] = new List<uint>();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<uint, PlayerSpellState>[] Talents = new Dictionary<uint, PlayerSpellState>[PlayerConst.MaxSpecializations];
|
||||
public List<uint>[] Glyphs = new List<uint>[PlayerConst.MaxSpecializations];
|
||||
public uint ResetTalentsCost;
|
||||
public long ResetTalentsTime;
|
||||
public uint PrimarySpecialization;
|
||||
public byte ActiveGroup;
|
||||
}
|
||||
|
||||
public class Runes
|
||||
{
|
||||
public void SetRuneState(byte index, bool set = true)
|
||||
{
|
||||
var id = CooldownOrder.LookupByIndex(index);
|
||||
if (set)
|
||||
{
|
||||
RuneState |= (byte)(1 << index); // usable
|
||||
if (id == 0)
|
||||
CooldownOrder.Add(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
RuneState &= (byte)~(1 << index); // on cooldown
|
||||
if (id != 0)
|
||||
CooldownOrder.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public List<byte> CooldownOrder = new List<byte>();
|
||||
public uint[] Cooldown = new uint[PlayerConst.MaxRunes];
|
||||
public byte RuneState; // mask of available runes
|
||||
}
|
||||
|
||||
public class ActionButton
|
||||
{
|
||||
public ActionButton()
|
||||
{
|
||||
packedData = 0;
|
||||
uState = ActionButtonUpdateState.New;
|
||||
}
|
||||
|
||||
public ActionButtonType GetButtonType() { return (ActionButtonType)((packedData & 0xFFFFFFFF00000000) >> 56); }
|
||||
public uint GetAction() { return (uint)(packedData & 0x00000000FFFFFFFF); }
|
||||
public void SetActionAndType(ulong action, ActionButtonType type)
|
||||
{
|
||||
ulong newData = action | ((ulong)type << 56);
|
||||
if (newData != packedData || uState == ActionButtonUpdateState.Deleted)
|
||||
{
|
||||
packedData = newData;
|
||||
if (uState != ActionButtonUpdateState.New)
|
||||
uState = ActionButtonUpdateState.Changed;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong packedData;
|
||||
public ActionButtonUpdateState uState;
|
||||
}
|
||||
|
||||
public class ResurrectionData
|
||||
{
|
||||
public ObjectGuid GUID;
|
||||
public WorldLocation Location = new WorldLocation();
|
||||
public uint Health;
|
||||
public uint Mana;
|
||||
public uint Aura;
|
||||
}
|
||||
|
||||
public struct PvPInfo
|
||||
{
|
||||
public bool IsHostile;
|
||||
public bool IsInHostileArea; //> Marks if player is in an area which forces PvP flag
|
||||
public bool IsInNoPvPArea; //> Marks if player is in a sanctuary or friendly capital city
|
||||
public bool IsInFFAPvPArea; //> Marks if player is in an FFAPvP area (such as Gurubashi Arena)
|
||||
public long EndTimer; //> Time when player unflags himself for PvP (flag removed after 5 minutes)
|
||||
}
|
||||
|
||||
public class DuelInfo
|
||||
{
|
||||
public Player initiator;
|
||||
public Player opponent;
|
||||
public long startTimer;
|
||||
public long startTime;
|
||||
public long outOfBound;
|
||||
public bool isMounted;
|
||||
public bool isCompleted;
|
||||
|
||||
public bool IsDueling() { return opponent != null; }
|
||||
}
|
||||
|
||||
public class AccessRequirement
|
||||
{
|
||||
public byte levelMin;
|
||||
public byte levelMax;
|
||||
public uint item;
|
||||
public uint item2;
|
||||
public uint quest_A;
|
||||
public uint quest_H;
|
||||
public uint achievement;
|
||||
public string questFailedText;
|
||||
}
|
||||
|
||||
public class EnchantDuration
|
||||
{
|
||||
public EnchantDuration(Item _item = null, EnchantmentSlot _slot = EnchantmentSlot.Max, uint _leftduration = 0)
|
||||
{
|
||||
item = _item;
|
||||
slot = _slot;
|
||||
leftduration = _leftduration;
|
||||
}
|
||||
|
||||
public Item item;
|
||||
public EnchantmentSlot slot;
|
||||
public uint leftduration;
|
||||
}
|
||||
|
||||
public class VoidStorageItem
|
||||
{
|
||||
public VoidStorageItem(ulong id, uint entry, ObjectGuid creator, ItemRandomEnchantmentId randomPropertyId, uint suffixFactor, uint upgradeId, uint fixedScalingLevel, uint artifactKnowledgeLevel, byte context, ICollection<uint> bonuses)
|
||||
{
|
||||
ItemId = id;
|
||||
ItemEntry = entry;
|
||||
CreatorGuid = creator;
|
||||
ItemRandomPropertyId = randomPropertyId;
|
||||
ItemSuffixFactor = suffixFactor;
|
||||
ItemUpgradeId = upgradeId;
|
||||
FixedScalingLevel = fixedScalingLevel;
|
||||
ArtifactKnowledgeLevel = artifactKnowledgeLevel;
|
||||
Context = context;
|
||||
|
||||
foreach (var value in bonuses)
|
||||
BonusListIDs.Add(value);
|
||||
}
|
||||
|
||||
public ulong ItemId;
|
||||
public uint ItemEntry;
|
||||
public ObjectGuid CreatorGuid;
|
||||
public ItemRandomEnchantmentId ItemRandomPropertyId;
|
||||
public uint ItemSuffixFactor;
|
||||
public uint ItemUpgradeId;
|
||||
public uint FixedScalingLevel;
|
||||
public uint ArtifactKnowledgeLevel;
|
||||
public byte Context;
|
||||
public List<uint> BonusListIDs = new List<uint>();
|
||||
}
|
||||
|
||||
public class EquipmentSetInfo
|
||||
{
|
||||
public EquipmentSetInfo()
|
||||
{
|
||||
state = EquipmentSetUpdateState.New;
|
||||
Data = new EquipmentSetData();
|
||||
}
|
||||
|
||||
public EquipmentSetUpdateState state;
|
||||
public EquipmentSetData Data;
|
||||
|
||||
// Data sent in EquipmentSet related packets
|
||||
public class EquipmentSetData
|
||||
{
|
||||
public EquipmentSetType Type;
|
||||
public ulong Guid; // Set Identifier
|
||||
public uint SetID; // Index
|
||||
public uint IgnoreMask ; // Mask of EquipmentSlot
|
||||
public int AssignedSpecIndex = -1; // Index of character specialization that this set is automatically equipped for
|
||||
public string SetName = "";
|
||||
public string SetIcon = "";
|
||||
public Array<ObjectGuid> Pieces = new Array<ObjectGuid>(EquipmentSlot.End);
|
||||
public Array<int> Appearances = new Array<int>(EquipmentSlot.End); // ItemModifiedAppearanceID
|
||||
public Array<int> Enchants = new Array<int>(2); // SpellItemEnchantmentID
|
||||
}
|
||||
|
||||
public enum EquipmentSetType
|
||||
{
|
||||
Equipment = 0,
|
||||
Transmog = 1
|
||||
}
|
||||
}
|
||||
|
||||
public class BgBattlegroundQueueID_Rec
|
||||
{
|
||||
public BattlegroundQueueTypeId bgQueueTypeId;
|
||||
public uint invitedToInstance;
|
||||
public uint joinTime;
|
||||
}
|
||||
|
||||
// Holder for Battlegrounddata
|
||||
public class BGData
|
||||
{
|
||||
public BGData()
|
||||
{
|
||||
bgTypeID = BattlegroundTypeId.None;
|
||||
ClearTaxiPath();
|
||||
joinPos = new WorldLocation();
|
||||
}
|
||||
|
||||
public uint bgInstanceID; //< This variable is set to bg.m_InstanceID,
|
||||
// when player is teleported to BG - (it is Battleground's GUID)
|
||||
public BattlegroundTypeId bgTypeID;
|
||||
|
||||
public List<ObjectGuid> bgAfkReporter = new List<ObjectGuid>();
|
||||
public byte bgAfkReportedCount;
|
||||
public long bgAfkReportedTimer;
|
||||
|
||||
public uint bgTeam; //< What side the player will be added to
|
||||
|
||||
public uint mountSpell;
|
||||
public uint[] taxiPath = new uint[2];
|
||||
|
||||
public WorldLocation joinPos; //< From where player entered BG
|
||||
|
||||
public void ClearTaxiPath() { taxiPath[0] = taxiPath[1] = 0; }
|
||||
public bool HasTaxiPath() { return taxiPath[0] != 0 && taxiPath[1] != 0; }
|
||||
}
|
||||
|
||||
public class CUFProfile
|
||||
{
|
||||
public CUFProfile()
|
||||
{
|
||||
BoolOptions = new BitArray((int)CUFBoolOptions.BoolOptionsCount);
|
||||
}
|
||||
|
||||
public CUFProfile(string name, ushort frameHeight, ushort frameWidth, byte sortBy, byte healthText, uint boolOptions,
|
||||
byte topPoint, byte bottomPoint, byte leftPoint, ushort topOffset, ushort bottomOffset, ushort leftOffset)
|
||||
{
|
||||
ProfileName = name;
|
||||
BoolOptions = new BitArray(new int[] { (int)boolOptions });
|
||||
|
||||
FrameHeight = frameHeight;
|
||||
FrameWidth = frameWidth;
|
||||
SortBy = sortBy;
|
||||
HealthText = healthText;
|
||||
TopPoint = topPoint;
|
||||
BottomPoint = bottomPoint;
|
||||
LeftPoint = leftPoint;
|
||||
TopOffset = topOffset;
|
||||
BottomOffset = bottomOffset;
|
||||
LeftOffset = leftOffset;
|
||||
}
|
||||
|
||||
public void SetOption(CUFBoolOptions opt, byte arg)
|
||||
{
|
||||
BoolOptions.Set((int)opt, arg != 0);
|
||||
}
|
||||
public bool GetOption(CUFBoolOptions opt)
|
||||
{
|
||||
return BoolOptions.Get((int)opt);
|
||||
}
|
||||
public ulong GetUlongOptionValue()
|
||||
{
|
||||
int[] array = new int[1];
|
||||
BoolOptions.CopyTo(array, 0);
|
||||
return (ulong)array[0];
|
||||
}
|
||||
|
||||
public string ProfileName;
|
||||
public ushort FrameHeight;
|
||||
public ushort FrameWidth;
|
||||
public byte SortBy;
|
||||
public byte HealthText;
|
||||
|
||||
// LeftAlign, TopAlight, BottomAlign
|
||||
public byte TopPoint;
|
||||
public byte BottomPoint;
|
||||
public byte LeftPoint;
|
||||
|
||||
// LeftOffset, TopOffset and BottomOffset
|
||||
public ushort TopOffset;
|
||||
public ushort BottomOffset;
|
||||
public ushort LeftOffset;
|
||||
|
||||
public BitArray BoolOptions;
|
||||
|
||||
// More fields can be added to BoolOptions without changing DB schema (up to 32, currently 27)
|
||||
}
|
||||
|
||||
struct GroupUpdateCounter
|
||||
{
|
||||
public ObjectGuid GroupGuid;
|
||||
public int UpdateSequenceNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Groups;
|
||||
using Game.Maps;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
Player GetNextRandomRaidMember(float radius)
|
||||
{
|
||||
Group group = GetGroup();
|
||||
if (!group)
|
||||
return null;
|
||||
|
||||
List<Player> nearMembers = new List<Player>();
|
||||
|
||||
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player Target = refe.GetSource();
|
||||
|
||||
// IsHostileTo check duel and controlled by enemy
|
||||
if (Target && Target != this && IsWithinDistInMap(Target, radius) &&
|
||||
!Target.HasInvisibilityAura() && !IsHostileTo(Target))
|
||||
nearMembers.Add(Target);
|
||||
}
|
||||
|
||||
if (nearMembers.Empty())
|
||||
return null;
|
||||
|
||||
int randTarget = RandomHelper.IRand(0, nearMembers.Count - 1);
|
||||
return nearMembers[randTarget];
|
||||
}
|
||||
|
||||
public PartyResult CanUninviteFromGroup(ObjectGuid guidMember = default(ObjectGuid))
|
||||
{
|
||||
Group grp = GetGroup();
|
||||
if (!grp)
|
||||
return PartyResult.NotInGroup;
|
||||
|
||||
if (grp.isLFGGroup())
|
||||
{
|
||||
ObjectGuid gguid = grp.GetGUID();
|
||||
if (Global.LFGMgr.GetKicksLeft(gguid) == 0)
|
||||
return PartyResult.PartyLfgBootLimit;
|
||||
|
||||
LfgState state = Global.LFGMgr.GetState(gguid);
|
||||
if (Global.LFGMgr.IsVoteKickActive(gguid))
|
||||
return PartyResult.PartyLfgBootInProgress;
|
||||
|
||||
if (grp.GetMembersCount() <= SharedConst.LFGKickVotesNeeded)
|
||||
return PartyResult.PartyLfgBootTooFewPlayers;
|
||||
|
||||
if (state == LfgState.FinishedDungeon)
|
||||
return PartyResult.PartyLfgBootDungeonComplete;
|
||||
|
||||
if (grp.isRollLootActive())
|
||||
return PartyResult.PartyLfgBootLootRolls;
|
||||
|
||||
// @todo Should also be sent when anyone has recently left combat, with an aprox ~5 seconds timer.
|
||||
for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next())
|
||||
if (refe.GetSource() && refe.GetSource().IsInCombat())
|
||||
return PartyResult.PartyLfgBootInCombat;
|
||||
|
||||
/* Missing support for these types
|
||||
return ERR_PARTY_LFG_BOOT_COOLDOWN_S;
|
||||
return ERR_PARTY_LFG_BOOT_NOT_ELIGIBLE_S;
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!grp.IsLeader(GetGUID()) && !grp.IsAssistant(GetGUID()))
|
||||
return PartyResult.NotLeader;
|
||||
|
||||
if (InBattleground())
|
||||
return PartyResult.InviteRestricted;
|
||||
|
||||
if (grp.IsLeader(guidMember))
|
||||
return PartyResult.NotLeader;
|
||||
}
|
||||
|
||||
return PartyResult.Ok;
|
||||
}
|
||||
|
||||
public bool isUsingLfg()
|
||||
{
|
||||
return Global.LFGMgr.GetState(GetGUID()) != LfgState.None;
|
||||
}
|
||||
|
||||
bool inRandomLfgDungeon()
|
||||
{
|
||||
if (Global.LFGMgr.selectedRandomLfgDungeon(GetGUID()))
|
||||
{
|
||||
Map map = GetMap();
|
||||
return Global.LFGMgr.inLfgDungeonMap(GetGUID(), map.GetId(), map.GetDifficultyID());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetBattlegroundOrBattlefieldRaid(Group group, byte subgroup)
|
||||
{
|
||||
//we must move references from m_group to m_originalGroup
|
||||
SetOriginalGroup(GetGroup(), GetSubGroup());
|
||||
|
||||
m_group.unlink();
|
||||
m_group.link(group, this);
|
||||
m_group.setSubGroup(subgroup);
|
||||
}
|
||||
|
||||
public void RemoveFromBattlegroundOrBattlefieldRaid()
|
||||
{
|
||||
//remove existing reference
|
||||
m_group.unlink();
|
||||
Group group = GetOriginalGroup();
|
||||
if (group)
|
||||
{
|
||||
m_group.link(group, this);
|
||||
m_group.setSubGroup(GetOriginalSubGroup());
|
||||
}
|
||||
SetOriginalGroup(null);
|
||||
}
|
||||
|
||||
public void SetOriginalGroup(Group group, byte subgroup = 0)
|
||||
{
|
||||
if (!group)
|
||||
m_originalGroup.unlink();
|
||||
else
|
||||
{
|
||||
m_originalGroup.link(group, this);
|
||||
m_originalGroup.setSubGroup(subgroup);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGroup(Group group, byte subgroup = 0)
|
||||
{
|
||||
if (!group)
|
||||
m_group.unlink();
|
||||
else
|
||||
{
|
||||
m_group.link(group, this);
|
||||
m_group.setSubGroup(subgroup);
|
||||
}
|
||||
|
||||
UpdateObjectVisibility(false);
|
||||
}
|
||||
|
||||
public void SetPartyType(GroupCategory category, GroupType type)
|
||||
{
|
||||
Contract.Assert(category < GroupCategory.Max);
|
||||
byte value = GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType);
|
||||
value &= (byte)~((byte)0xFF << ((byte)category * 4));
|
||||
value |= (byte)((byte)type << ((byte)category * 4));
|
||||
SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType, value);
|
||||
}
|
||||
|
||||
public void ResetGroupUpdateSequenceIfNeeded(Group group)
|
||||
{
|
||||
GroupCategory category = group.GetGroupCategory();
|
||||
// Rejoining the last group should not reset the sequence
|
||||
if (m_groupUpdateSequences[(int)category].GroupGuid != group.GetGUID())
|
||||
{
|
||||
var groupUpdate = m_groupUpdateSequences[(int)category];
|
||||
groupUpdate.GroupGuid = group.GetGUID();
|
||||
groupUpdate.UpdateSequenceNumber = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int NextGroupUpdateSequenceNumber(GroupCategory category)
|
||||
{
|
||||
var groupUpdate = m_groupUpdateSequences[(int)category];
|
||||
return groupUpdate.UpdateSequenceNumber++;
|
||||
}
|
||||
|
||||
public bool IsAtGroupRewardDistance(WorldObject pRewardSource)
|
||||
{
|
||||
if (!pRewardSource)
|
||||
return false;
|
||||
WorldObject player = GetCorpse();
|
||||
if (!player || IsAlive())
|
||||
player = this;
|
||||
|
||||
if (player.GetMapId() != pRewardSource.GetMapId() || player.GetInstanceId() != pRewardSource.GetInstanceId())
|
||||
return false;
|
||||
|
||||
if (player.GetMap().IsDungeon())
|
||||
return true;
|
||||
|
||||
return pRewardSource.GetDistance(player) <= WorldConfig.GetFloatValue(WorldCfg.GroupXpDistance);
|
||||
}
|
||||
|
||||
public Group GetGroupInvite() { return m_groupInvite; }
|
||||
public void SetGroupInvite(Group group) { m_groupInvite = group; }
|
||||
public Group GetGroup() { return m_group.getTarget(); }
|
||||
public GroupReference GetGroupRef() { return m_group; }
|
||||
public byte GetSubGroup() { return m_group.getSubGroup(); }
|
||||
public GroupUpdateFlags GetGroupUpdateFlag() { return m_groupUpdateMask; }
|
||||
public void SetGroupUpdateFlag(GroupUpdateFlags flag) { m_groupUpdateMask |= flag; }
|
||||
public void RemoveGroupUpdateFlag(GroupUpdateFlags flag) { m_groupUpdateMask &= ~flag; }
|
||||
|
||||
public Group GetOriginalGroup() { return m_originalGroup.getTarget(); }
|
||||
public GroupReference GetOriginalGroupRef() { return m_originalGroup; }
|
||||
public byte GetOriginalSubGroup() { return m_originalGroup.getSubGroup(); }
|
||||
|
||||
public void SetPassOnGroupLoot(bool bPassOnGroupLoot) { m_bPassOnGroupLoot = bPassOnGroupLoot; }
|
||||
public bool GetPassOnGroupLoot() { return m_bPassOnGroupLoot; }
|
||||
|
||||
public bool IsGroupVisibleFor(Player p)
|
||||
{
|
||||
switch (WorldConfig.GetIntValue(WorldCfg.GroupVisibility))
|
||||
{
|
||||
default:
|
||||
return IsInSameGroupWith(p);
|
||||
case 1:
|
||||
return IsInSameRaidWith(p);
|
||||
case 2:
|
||||
return GetTeam() == p.GetTeam();
|
||||
}
|
||||
}
|
||||
public bool IsInSameGroupWith(Player p)
|
||||
{
|
||||
return p == this || (GetGroup() &&
|
||||
GetGroup() == p.GetGroup() && GetGroup().SameSubGroup(this, p));
|
||||
}
|
||||
|
||||
public bool IsInSameRaidWith(Player p)
|
||||
{
|
||||
return p == this || (GetGroup() != null && GetGroup() == p.GetGroup());
|
||||
}
|
||||
|
||||
public void UninviteFromGroup()
|
||||
{
|
||||
Group group = GetGroupInvite();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
group.RemoveInvite(this);
|
||||
|
||||
if (group.GetMembersCount() <= 1) // group has just 1 member => disband
|
||||
{
|
||||
if (group.IsCreated())
|
||||
group.Disband(true);
|
||||
else
|
||||
group.RemoveAllInvites();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFromGroup(RemoveMethod method = RemoveMethod.Default) { RemoveFromGroup(GetGroup(), GetGUID(), method); }
|
||||
public static void RemoveFromGroup(Group group, ObjectGuid guid, RemoveMethod method = RemoveMethod.Default, ObjectGuid kicker = default(ObjectGuid), string reason = null)
|
||||
{
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
group.RemoveMember(guid, method, kicker, reason);
|
||||
}
|
||||
|
||||
void SendUpdateToOutOfRangeGroupMembers()
|
||||
{
|
||||
if (m_groupUpdateMask == GroupUpdateFlags.None)
|
||||
return;
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
group.UpdatePlayerOutOfRange(this);
|
||||
|
||||
m_groupUpdateMask = GroupUpdateFlags.None;
|
||||
|
||||
Pet pet = GetPet();
|
||||
if (pet)
|
||||
pet.ResetGroupUpdateFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,789 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.DataStorage;
|
||||
using Game.Groups;
|
||||
using Game.Guilds;
|
||||
using Game.Maps;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public override void SetMap(Map map)
|
||||
{
|
||||
base.SetMap(map);
|
||||
}
|
||||
|
||||
public Difficulty GetDifficultyID(MapRecord mapEntry)
|
||||
{
|
||||
if (!mapEntry.IsRaid())
|
||||
return m_dungeonDifficulty;
|
||||
|
||||
MapDifficultyRecord defaultDifficulty = Global.DB2Mgr.GetDefaultMapDifficulty(mapEntry.Id);
|
||||
if (defaultDifficulty == null)
|
||||
return m_legacyRaidDifficulty;
|
||||
|
||||
DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(defaultDifficulty.DifficultyID);
|
||||
if (difficulty == null || difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy))
|
||||
return m_legacyRaidDifficulty;
|
||||
|
||||
return m_raidDifficulty;
|
||||
}
|
||||
public Difficulty GetDungeonDifficultyID() { return m_dungeonDifficulty; }
|
||||
public Difficulty GetRaidDifficultyID() { return m_raidDifficulty; }
|
||||
public Difficulty GetLegacyRaidDifficultyID() { return m_legacyRaidDifficulty; }
|
||||
public void SetDungeonDifficultyID(Difficulty dungeon_difficulty) { m_dungeonDifficulty = dungeon_difficulty; }
|
||||
public void SetRaidDifficultyID(Difficulty raid_difficulty) { m_raidDifficulty = raid_difficulty; }
|
||||
public void SetLegacyRaidDifficultyID(Difficulty raid_difficulty) { m_legacyRaidDifficulty = raid_difficulty; }
|
||||
|
||||
public static Difficulty CheckLoadedDungeonDifficultyID(Difficulty difficulty)
|
||||
{
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null)
|
||||
return Difficulty.Normal;
|
||||
|
||||
if (difficultyEntry.InstanceType != MapTypes.Instance)
|
||||
return Difficulty.Normal;
|
||||
|
||||
if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect))
|
||||
return Difficulty.Normal;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
public static Difficulty CheckLoadedRaidDifficultyID(Difficulty difficulty)
|
||||
{
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null)
|
||||
return Difficulty.NormalRaid;
|
||||
|
||||
if (difficultyEntry.InstanceType != MapTypes.Raid)
|
||||
return Difficulty.NormalRaid;
|
||||
|
||||
if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy))
|
||||
return Difficulty.NormalRaid;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
public static Difficulty CheckLoadedLegacyRaidDifficultyID(Difficulty difficulty)
|
||||
{
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null)
|
||||
return Difficulty.Raid10N;
|
||||
|
||||
if (difficultyEntry.InstanceType != MapTypes.Raid)
|
||||
return Difficulty.Raid10N;
|
||||
|
||||
if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || !difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy))
|
||||
return Difficulty.Raid10N;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public void SendRaidGroupOnlyMessage(RaidGroupReason reason, int delay)
|
||||
{
|
||||
RaidGroupOnly raidGroupOnly = new RaidGroupOnly();
|
||||
raidGroupOnly.Delay = delay;
|
||||
raidGroupOnly.Reason = reason;
|
||||
|
||||
SendPacket(raidGroupOnly);
|
||||
}
|
||||
|
||||
void UpdateArea(uint newArea)
|
||||
{
|
||||
// FFA_PVP flags are area and not zone id dependent
|
||||
// so apply them accordingly
|
||||
m_areaUpdateId = newArea;
|
||||
|
||||
AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(newArea);
|
||||
pvpInfo.IsInFFAPvPArea = area != null && area.Flags[0].HasAnyFlag(AreaFlags.Arena);
|
||||
UpdatePvPState(true);
|
||||
|
||||
UpdateAreaDependentAuras(newArea);
|
||||
UpdateAreaAndZonePhase();
|
||||
|
||||
// previously this was in UpdateZone (but after UpdateArea) so nothing will break
|
||||
pvpInfo.IsInNoPvPArea = false;
|
||||
if (area != null && area.IsSanctuary()) // in sanctuary
|
||||
{
|
||||
SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary);
|
||||
pvpInfo.IsInNoPvPArea = true;
|
||||
if (duel == null)
|
||||
CombatStopWithPets();
|
||||
}
|
||||
else
|
||||
RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary);
|
||||
|
||||
AreaFlags areaRestFlag = (GetTeam() == Team.Alliance) ? AreaFlags.RestZoneAlliance : AreaFlags.RestZoneHorde;
|
||||
if (area != null && area.Flags[0].HasAnyFlag(areaRestFlag))
|
||||
_restMgr.SetRestFlag(RestFlag.FactionArea);
|
||||
else
|
||||
_restMgr.RemoveRestFlag(RestFlag.FactionArea);
|
||||
}
|
||||
|
||||
public void UpdateZone(uint newZone, uint newArea)
|
||||
{
|
||||
if (m_zoneUpdateId != newZone)
|
||||
{
|
||||
Global.OutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
|
||||
Global.OutdoorPvPMgr.HandlePlayerEnterZone(this, newZone);
|
||||
Global.BattleFieldMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
|
||||
Global.BattleFieldMgr.HandlePlayerEnterZone(this, newZone);
|
||||
SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange...
|
||||
Guild guild = GetGuild();
|
||||
if (guild)
|
||||
guild.UpdateMemberData(this, GuildMemberData.ZoneId, newZone);
|
||||
}
|
||||
|
||||
// group update
|
||||
if (GetGroup())
|
||||
{
|
||||
SetGroupUpdateFlag(GroupUpdateFlags.Full);
|
||||
|
||||
Pet pet = GetPet();
|
||||
if (pet)
|
||||
pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Full);
|
||||
}
|
||||
|
||||
m_zoneUpdateId = newZone;
|
||||
m_zoneUpdateTimer = 1 * Time.InMilliseconds;
|
||||
|
||||
// zone changed, so area changed as well, update it
|
||||
UpdateArea(newArea);
|
||||
|
||||
AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(newZone);
|
||||
if (zone == null)
|
||||
return;
|
||||
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.Weather))
|
||||
GetMap().GetOrGenerateZoneDefaultWeather(newZone);
|
||||
|
||||
GetMap().SendZoneDynamicInfo(newZone, this);
|
||||
|
||||
Global.ScriptMgr.OnPlayerUpdateZone(this, newZone, newArea);
|
||||
|
||||
// in PvP, any not controlled zone (except zone.team == 6, default case)
|
||||
// in PvE, only opposition team capital
|
||||
switch ((AreaTeams)zone.FactionGroupMask)
|
||||
{
|
||||
case AreaTeams.Ally:
|
||||
pvpInfo.IsInHostileArea = GetTeam() != Team.Alliance && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital));
|
||||
break;
|
||||
case AreaTeams.Horde:
|
||||
pvpInfo.IsInHostileArea = GetTeam() != Team.Horde && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital));
|
||||
break;
|
||||
case AreaTeams.None:
|
||||
// overwrite for Battlegrounds, maybe batter some zone flags but current known not 100% fit to this
|
||||
pvpInfo.IsInHostileArea = Global.WorldMgr.IsPvPRealm() || InBattleground() || zone.Flags[0].HasAnyFlag(AreaFlags.Wintergrasp);
|
||||
break;
|
||||
default: // 6 in fact
|
||||
pvpInfo.IsInHostileArea = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Treat players having a quest flagging for PvP as always in hostile area
|
||||
pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest();
|
||||
|
||||
if (zone.Flags[0].HasAnyFlag(AreaFlags.Capital)) // Is in a capital city
|
||||
{
|
||||
if (!pvpInfo.IsInHostileArea || zone.IsSanctuary())
|
||||
_restMgr.SetRestFlag(RestFlag.City);
|
||||
pvpInfo.IsInNoPvPArea = true;
|
||||
}
|
||||
else
|
||||
_restMgr.RemoveRestFlag(RestFlag.City);
|
||||
|
||||
UpdatePvPState();
|
||||
|
||||
// remove items with area/map limitations (delete only for alive player to allow back in ghost mode)
|
||||
// if player resurrected at teleport this will be applied in resurrect code
|
||||
if (IsAlive())
|
||||
DestroyZoneLimitedItem(true, newZone);
|
||||
|
||||
// check some item equip limitations (in result lost CanTitanGrip at talent reset, for example)
|
||||
AutoUnequipOffhandIfNeed();
|
||||
|
||||
// recent client version not send leave/join channel packets for built-in local channels
|
||||
UpdateLocalChannels(newZone);
|
||||
|
||||
UpdateZoneDependentAuras(newZone);
|
||||
|
||||
UpdateAreaAndZonePhase();
|
||||
}
|
||||
|
||||
public InstanceBind GetBoundInstance(uint mapid, Difficulty difficulty, bool withExpired = false)
|
||||
{
|
||||
// some instances only have one difficulty
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(mapid, ref difficulty);
|
||||
if (mapDiff == null)
|
||||
return null;
|
||||
|
||||
var bind = m_boundInstances[(int)difficulty].LookupByKey(mapid);
|
||||
if (bind != null)
|
||||
if (bind.extendState != 0 || withExpired)
|
||||
return bind;
|
||||
|
||||
return null;
|
||||
}
|
||||
public Dictionary<uint, InstanceBind> GetBoundInstances(Difficulty difficulty) { return m_boundInstances[(int)difficulty]; }
|
||||
|
||||
public InstanceSave GetInstanceSave(uint mapid)
|
||||
{
|
||||
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid);
|
||||
InstanceBind pBind = GetBoundInstance(mapid, GetDifficultyID(mapEntry));
|
||||
InstanceSave pSave = pBind?.save;
|
||||
if (pBind == null || !pBind.perm)
|
||||
{
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
{
|
||||
InstanceBind groupBind = group.GetBoundInstance(GetDifficultyID(mapEntry), mapid);
|
||||
if (groupBind != null)
|
||||
pSave = groupBind.save;
|
||||
}
|
||||
}
|
||||
|
||||
return pSave;
|
||||
}
|
||||
|
||||
public void UnbindInstance(uint mapid, Difficulty difficulty, bool unload = false)
|
||||
{
|
||||
var bound = m_boundInstances[(int)difficulty].LookupByKey(mapid);
|
||||
if (bound != null)
|
||||
{
|
||||
if (!unload)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
|
||||
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, bound.save.GetInstanceId());
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
if (bound.perm)
|
||||
GetSession().SendCalendarRaidLockout(bound.save, false);
|
||||
|
||||
bound.save.RemovePlayer(this); // save can become invalid
|
||||
m_boundInstances[(int)difficulty].Remove(mapid);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnbindInstance(KeyValuePair<uint, InstanceBind> pair, Difficulty difficulty, bool unload)
|
||||
{
|
||||
if (pair.Value != null)
|
||||
{
|
||||
if (!unload)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
|
||||
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, pair.Value.save.GetInstanceId());
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
if (pair.Value.perm)
|
||||
GetSession().SendCalendarRaidLockout(pair.Value.save, false);
|
||||
|
||||
pair.Value.save.RemovePlayer(this); // save can become invalid
|
||||
m_boundInstances[(int)difficulty].Remove(pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public InstanceBind BindToInstance(InstanceSave save, bool permanent, BindExtensionState extendState = BindExtensionState.Normal, bool load = false)
|
||||
{
|
||||
if (save != null)
|
||||
{
|
||||
InstanceBind bind = new InstanceBind();
|
||||
if (m_boundInstances[(int)save.GetDifficultyID()].ContainsKey(save.GetMapId()))
|
||||
bind = m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()];
|
||||
|
||||
if (extendState == BindExtensionState.Keep) // special flag, keep the player's current extend state when updating for new boss down
|
||||
{
|
||||
if (save == bind.save)
|
||||
extendState = bind.extendState;
|
||||
else
|
||||
extendState = BindExtensionState.Normal;
|
||||
}
|
||||
|
||||
if (!load)
|
||||
{
|
||||
PreparedStatement stmt;
|
||||
if (bind.save != null)
|
||||
{
|
||||
// update the save when the group kills a boss
|
||||
if (permanent != bind.perm || save != bind.save || extendState != bind.extendState)
|
||||
{
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_INSTANCE);
|
||||
|
||||
stmt.AddValue(0, save.GetInstanceId());
|
||||
stmt.AddValue(1, permanent);
|
||||
stmt.AddValue(2, extendState);
|
||||
stmt.AddValue(3, GetGUID().GetCounter());
|
||||
stmt.AddValue(4, bind.save.GetInstanceId());
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_INSTANCE);
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, save.GetInstanceId());
|
||||
stmt.AddValue(2, permanent);
|
||||
stmt.AddValue(3, extendState);
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
if (bind.save != save)
|
||||
{
|
||||
if (bind.save != null)
|
||||
bind.save.RemovePlayer(this);
|
||||
save.AddPlayer(this);
|
||||
}
|
||||
|
||||
if (permanent)
|
||||
save.SetCanReset(false);
|
||||
|
||||
bind.save = save;
|
||||
bind.perm = permanent;
|
||||
bind.extendState = extendState;
|
||||
if (!load)
|
||||
Log.outDebug(LogFilter.Maps, "Player.BindToInstance: Player '{0}' ({1}) is now bound to map (ID: {2}, Instance {3}, Difficulty {4})", GetName(), GetGUID().ToString(), save.GetMapId(), save.GetInstanceId(), save.GetDifficultyID());
|
||||
|
||||
Global.ScriptMgr.OnPlayerBindToInstance(this, save.GetDifficultyID(), save.GetMapId(), permanent, extendState);
|
||||
|
||||
m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()] = bind;
|
||||
return bind;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void BindToInstance()
|
||||
{
|
||||
InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(_pendingBindId);
|
||||
if (mapSave == null) //it seems sometimes mapSave is NULL, but I did not check why
|
||||
return;
|
||||
|
||||
InstanceSaveCreated data = new InstanceSaveCreated();
|
||||
data.Gm = IsGameMaster();
|
||||
SendPacket(data);
|
||||
if (!IsGameMaster())
|
||||
{
|
||||
BindToInstance(mapSave, true, BindExtensionState.Keep);
|
||||
GetSession().SendCalendarRaidLockout(mapSave, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPendingBind(uint instanceId, uint bindTimer)
|
||||
{
|
||||
_pendingBindId = instanceId;
|
||||
_pendingBindTimer = bindTimer;
|
||||
}
|
||||
|
||||
public void SendRaidInfo()
|
||||
{
|
||||
InstanceInfoPkt instanceInfo = new InstanceInfoPkt();
|
||||
|
||||
long now = Time.UnixTime;
|
||||
for (byte i = 0; i < (int)Difficulty.Max; ++i)
|
||||
{
|
||||
foreach (var pair in m_boundInstances[i])
|
||||
{
|
||||
InstanceBind bind = pair.Value;
|
||||
if (bind.perm)
|
||||
{
|
||||
InstanceSave save = pair.Value.save;
|
||||
|
||||
InstanceLockInfos lockInfos;
|
||||
|
||||
lockInfos.InstanceID = save.GetInstanceId();
|
||||
lockInfos.MapID = save.GetMapId();
|
||||
lockInfos.DifficultyID = (uint)save.GetDifficultyID();
|
||||
if (bind.extendState != BindExtensionState.Extended)
|
||||
lockInfos.TimeRemaining = (int)(save.GetResetTime() - now);
|
||||
else
|
||||
lockInfos.TimeRemaining = (int)(Global.InstanceSaveMgr.GetSubsequentResetTime(save.GetMapId(), save.GetDifficultyID(), save.GetResetTime()) - now);
|
||||
|
||||
lockInfos.CompletedMask = 0;
|
||||
Map map = Global.MapMgr.FindMap(save.GetMapId(), save.GetInstanceId());
|
||||
if (map != null)
|
||||
{
|
||||
InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript();
|
||||
if (instanceScript != null)
|
||||
lockInfos.CompletedMask = instanceScript.GetCompletedEncounterMask();
|
||||
}
|
||||
|
||||
lockInfos.Locked = bind.extendState != BindExtensionState.Expired;
|
||||
lockInfos.Extended = bind.extendState == BindExtensionState.Extended;
|
||||
|
||||
instanceInfo.LockList.Add(lockInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SendPacket(instanceInfo);
|
||||
}
|
||||
|
||||
public bool Satisfy(AccessRequirement ar, uint target_map, bool report = false)
|
||||
{
|
||||
if (!IsGameMaster() && ar != null)
|
||||
{
|
||||
byte LevelMin = 0;
|
||||
byte LevelMax = 0;
|
||||
|
||||
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(target_map);
|
||||
if (mapEntry == null)
|
||||
return false;
|
||||
|
||||
if (!WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreLevel))
|
||||
{
|
||||
if (ar.levelMin != 0 && getLevel() < ar.levelMin)
|
||||
LevelMin = ar.levelMin;
|
||||
if (ar.levelMax != 0 && getLevel() > ar.levelMax)
|
||||
LevelMax = ar.levelMax;
|
||||
}
|
||||
|
||||
uint missingItem = 0;
|
||||
if (ar.item != 0)
|
||||
{
|
||||
if (!HasItemCount(ar.item) &&
|
||||
(ar.item2 == 0 || !HasItemCount(ar.item2)))
|
||||
missingItem = ar.item;
|
||||
}
|
||||
else if (ar.item2 != 0 && !HasItemCount(ar.item2))
|
||||
missingItem = ar.item2;
|
||||
|
||||
if (Global.DisableMgr.IsDisabledFor(DisableType.Map, target_map, this))
|
||||
{
|
||||
GetSession().SendNotification("{0}", Global.ObjectMgr.GetCypherString(CypherStrings.InstanceClosed));
|
||||
return false;
|
||||
}
|
||||
|
||||
uint missingQuest = 0;
|
||||
if (GetTeam() == Team.Alliance && ar.quest_A != 0 && !GetQuestRewardStatus(ar.quest_A))
|
||||
missingQuest = ar.quest_A;
|
||||
else if (GetTeam() == Team.Horde && ar.quest_H != 0 && !GetQuestRewardStatus(ar.quest_H))
|
||||
missingQuest = ar.quest_H;
|
||||
|
||||
uint missingAchievement = 0;
|
||||
Player leader = this;
|
||||
ObjectGuid leaderGuid = GetGroup() != null ? GetGroup().GetLeaderGUID() : GetGUID();
|
||||
if (leaderGuid != GetGUID())
|
||||
leader = Global.ObjAccessor.FindPlayer(leaderGuid);
|
||||
|
||||
if (ar.achievement != 0)
|
||||
if (leader == null || !leader.HasAchieved(ar.achievement))
|
||||
missingAchievement = ar.achievement;
|
||||
|
||||
Difficulty target_difficulty = GetDifficultyID(mapEntry);
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(target_map, ref target_difficulty);
|
||||
if (LevelMin != 0 || LevelMax != 0 || missingItem != 0 || missingQuest != 0 || missingAchievement != 0)
|
||||
{
|
||||
if (report)
|
||||
{
|
||||
if (missingQuest != 0 && !string.IsNullOrEmpty(ar.questFailedText))
|
||||
SendSysMessage("{0}", ar.questFailedText);
|
||||
else if (mapDiff.Message_lang.HasString(Global.WorldMgr.GetDefaultDbcLocale())) // if (missingAchievement) covered by this case
|
||||
SendTransferAborted(target_map, TransferAbortReason.Difficulty, (byte)target_difficulty);
|
||||
else if (missingItem != 0)
|
||||
GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequiredAndItem), LevelMin, Global.ObjectMgr.GetItemTemplate(missingItem).GetName());
|
||||
else if (LevelMin != 0)
|
||||
GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequired), LevelMin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInstanceLoginGameMasterException()
|
||||
{
|
||||
if (!CanBeGameMaster())
|
||||
return false;
|
||||
|
||||
SendSysMessage(CypherStrings.InstanceLoginGamemasterException);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CheckInstanceValidity(bool isLogin)
|
||||
{
|
||||
// game masters' instances are always valid
|
||||
if (IsGameMaster())
|
||||
return true;
|
||||
|
||||
// non-instances are always valid
|
||||
Map map = GetMap();
|
||||
if (!map || !map.IsDungeon())
|
||||
return true;
|
||||
|
||||
// raid instances require the player to be in a raid group to be valid
|
||||
if (map.IsRaid() && !WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid))
|
||||
if (!GetGroup() || !GetGroup().isRaidGroup())
|
||||
return false;
|
||||
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
{
|
||||
// check if player's group is bound to this instance
|
||||
InstanceBind bind = group.GetBoundInstance(map.GetDifficultyID(), map.GetId());
|
||||
if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId())
|
||||
return false;
|
||||
|
||||
var players = map.GetPlayers();
|
||||
if (!players.Empty())
|
||||
foreach (var otherPlayer in players)
|
||||
{
|
||||
if (otherPlayer.IsGameMaster())
|
||||
continue;
|
||||
if (!otherPlayer.m_InstanceValid) // ignore players that currently have a homebind timer active
|
||||
continue;
|
||||
if (group != otherPlayer.GetGroup())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// instance is invalid if we are not grouped and there are other players
|
||||
if (map.GetPlayersCountExceptGMs() > 1)
|
||||
return false;
|
||||
|
||||
// check if the player is bound to this instance
|
||||
InstanceBind bind = GetBoundInstance(map.GetId(), map.GetDifficultyID());
|
||||
if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CheckInstanceCount(uint instanceId)
|
||||
{
|
||||
if (_instanceResetTimes.Count < WorldConfig.GetIntValue(WorldCfg.MaxInstancesPerHour))
|
||||
return true;
|
||||
return _instanceResetTimes.ContainsKey(instanceId);
|
||||
}
|
||||
|
||||
public void AddInstanceEnterTime(uint instanceId, long enterTime)
|
||||
{
|
||||
if (!_instanceResetTimes.ContainsKey(instanceId))
|
||||
_instanceResetTimes.Add(instanceId, enterTime + Time.Hour);
|
||||
}
|
||||
|
||||
public void SendDungeonDifficulty(int forcedDifficulty = -1)
|
||||
{
|
||||
DungeonDifficultySet dungeonDifficultySet = new DungeonDifficultySet();
|
||||
dungeonDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)GetDungeonDifficultyID() : forcedDifficulty;
|
||||
SendPacket(dungeonDifficultySet);
|
||||
}
|
||||
|
||||
public void SendRaidDifficulty(bool legacy, int forcedDifficulty = -1)
|
||||
{
|
||||
RaidDifficultySet raidDifficultySet = new RaidDifficultySet();
|
||||
raidDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)(legacy ? GetLegacyRaidDifficultyID() : GetRaidDifficultyID()) : forcedDifficulty;
|
||||
raidDifficultySet.Legacy = legacy;
|
||||
SendPacket(raidDifficultySet);
|
||||
}
|
||||
|
||||
public void SendResetFailedNotify(uint mapid)
|
||||
{
|
||||
SendPacket(new ResetFailedNotify());
|
||||
}
|
||||
|
||||
// Reset all solo instances and optionally send a message on success for each
|
||||
public void ResetInstances(InstanceResetMethod method, bool isRaid, bool isLegacy)
|
||||
{
|
||||
// method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN
|
||||
|
||||
// we assume that when the difficulty changes, all instances that can be reset will be
|
||||
Difficulty diff = GetDungeonDifficultyID();
|
||||
if (isRaid)
|
||||
{
|
||||
if (!isLegacy)
|
||||
diff = GetRaidDifficultyID();
|
||||
else
|
||||
diff = GetLegacyRaidDifficultyID();
|
||||
}
|
||||
|
||||
foreach (var pair in m_boundInstances[(int)diff].ToList())
|
||||
{
|
||||
InstanceSave p = pair.Value.save;
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(pair.Key);
|
||||
if (entry == null || entry.IsRaid() != isRaid || !p.CanReset())
|
||||
continue;
|
||||
|
||||
if (method == InstanceResetMethod.All)
|
||||
{
|
||||
// the "reset all instances" method can only reset normal maps
|
||||
if (entry.InstanceType == MapTypes.Raid || diff == Difficulty.Heroic)
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the map is loaded, reset it
|
||||
Map map = Global.MapMgr.FindMap(p.GetMapId(), p.GetInstanceId());
|
||||
if (map != null && map.IsDungeon())
|
||||
if (!map.ToInstanceMap().Reset(method))
|
||||
continue;
|
||||
|
||||
// since this is a solo instance there should not be any players inside
|
||||
if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty)
|
||||
SendResetInstanceSuccess(p.GetMapId());
|
||||
|
||||
p.DeleteFromDB();
|
||||
m_boundInstances[(int)diff].Remove(pair.Key);
|
||||
|
||||
// the following should remove the instance save from the manager and delete it as well
|
||||
p.RemovePlayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendResetInstanceSuccess(uint MapId)
|
||||
{
|
||||
InstanceReset data = new InstanceReset();
|
||||
data.MapID = MapId;
|
||||
SendPacket(data);
|
||||
}
|
||||
|
||||
public void SendResetInstanceFailed(ResetFailedReason reason, uint MapId)
|
||||
{
|
||||
InstanceResetFailed data = new InstanceResetFailed();
|
||||
data.MapID = MapId;
|
||||
data.ResetFailedReason = reason;
|
||||
SendPacket(data);
|
||||
}
|
||||
|
||||
public void SendTransferAborted(uint mapid, TransferAbortReason reason, byte arg = 0)
|
||||
{
|
||||
TransferAborted transferAborted = new TransferAborted();
|
||||
transferAborted.MapID = mapid;
|
||||
transferAborted.Arg = arg;
|
||||
transferAborted.TransfertAbort = reason;
|
||||
SendPacket(transferAborted);
|
||||
}
|
||||
|
||||
public void SendInstanceResetWarning(uint mapid, Difficulty difficulty, uint time, bool welcome)
|
||||
{
|
||||
// type of warning, based on the time remaining until reset
|
||||
InstanceResetWarningType type;
|
||||
if (welcome)
|
||||
type = InstanceResetWarningType.Welcome;
|
||||
else if (time > 21600)
|
||||
type = InstanceResetWarningType.Welcome;
|
||||
else if (time > 3600)
|
||||
type = InstanceResetWarningType.WarningHours;
|
||||
else if (time > 300)
|
||||
type = InstanceResetWarningType.WarningMin;
|
||||
else
|
||||
type = InstanceResetWarningType.WarningMinSoon;
|
||||
|
||||
RaidInstanceMessage raidInstanceMessage = new RaidInstanceMessage();
|
||||
raidInstanceMessage.Type = type;
|
||||
raidInstanceMessage.MapID = mapid;
|
||||
raidInstanceMessage.DifficultyID = difficulty;
|
||||
|
||||
InstanceBind bind = GetBoundInstance(mapid, difficulty);
|
||||
if (bind != null)
|
||||
raidInstanceMessage.Locked = bind.perm;
|
||||
else
|
||||
raidInstanceMessage.Locked = false;
|
||||
raidInstanceMessage.Extended = false;
|
||||
SendPacket(raidInstanceMessage);
|
||||
}
|
||||
|
||||
public override void UpdateUnderwaterState(Map m, float x, float y, float z)
|
||||
{
|
||||
LiquidData liquid_status;
|
||||
ZLiquidStatus res = m.getLiquidStatus(x, y, z, MapConst.MapAllLiquidTypes, out liquid_status);
|
||||
if (res == 0)
|
||||
{
|
||||
m_MirrorTimerFlags &= ~(PlayerUnderwaterState.InWater | PlayerUnderwaterState.InLava | PlayerUnderwaterState.InSlime | PlayerUnderwaterState.InDarkWater);
|
||||
if (_lastLiquid != null && _lastLiquid.SpellID != 0)
|
||||
RemoveAurasDueToSpell(_lastLiquid.SpellID);
|
||||
|
||||
_lastLiquid = null;
|
||||
return;
|
||||
}
|
||||
uint liqEntry = liquid_status.entry;
|
||||
if (liqEntry != 0)
|
||||
{
|
||||
LiquidTypeRecord liquid = CliDB.LiquidTypeStorage.LookupByKey(liqEntry);
|
||||
if (_lastLiquid != null && _lastLiquid.SpellID != 0 && _lastLiquid != liquid)
|
||||
RemoveAurasDueToSpell(_lastLiquid.SpellID);
|
||||
|
||||
if (liquid != null && liquid.SpellID != 0)
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater))
|
||||
{
|
||||
if (!HasAura(liquid.SpellID))
|
||||
CastSpell(this, liquid.SpellID, true);
|
||||
}
|
||||
else
|
||||
RemoveAurasDueToSpell(liquid.SpellID);
|
||||
}
|
||||
|
||||
_lastLiquid = liquid;
|
||||
}
|
||||
else if (_lastLiquid != null && _lastLiquid.SpellID != 0)
|
||||
{
|
||||
RemoveAurasDueToSpell(_lastLiquid.SpellID);
|
||||
_lastLiquid = null;
|
||||
}
|
||||
|
||||
|
||||
// All liquids type - check under water position
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeWater | MapConst.MapLiquidTypeOcean | MapConst.MapLiquidTypeMagma | MapConst.MapLiquidTypeSlime))
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater))
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InWater;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InWater;
|
||||
}
|
||||
|
||||
// Allow travel in dark water on taxi or transport
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeDarkWater) && !IsInFlight() && GetTransport() == null)
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InDarkWater;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InDarkWater;
|
||||
|
||||
// in lava check, anywhere in lava level
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeMagma))
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk))
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InLava;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InLava;
|
||||
}
|
||||
// in slime check, anywhere in slime level
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeSlime))
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk))
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InSlime;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InSlime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,753 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.Arenas;
|
||||
using Game.BattleFields;
|
||||
using Game.BattleGrounds;
|
||||
using Game.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using Game.PvP;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
//PvP
|
||||
public void UpdateHonorFields()
|
||||
{
|
||||
// called when rewarding honor and at each save
|
||||
long now = Time.UnixTime;
|
||||
long today = (Time.UnixTime / Time.Day) * Time.Day;
|
||||
|
||||
if (m_lastHonorUpdateTime < today)
|
||||
{
|
||||
long yesterday = today - Time.Day;
|
||||
|
||||
// update yesterday's contribution
|
||||
if (m_lastHonorUpdateTime >= yesterday)
|
||||
{
|
||||
// this is the first update today, reset today's contribution
|
||||
ushort killsToday = GetUInt16Value(PlayerFields.Kills, 0);
|
||||
SetUInt16Value(PlayerFields.Kills, 0, 0);
|
||||
SetUInt16Value(PlayerFields.Kills, 1, killsToday);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// no honor/kills yesterday or today, reset
|
||||
SetUInt32Value(PlayerFields.Kills, 0);
|
||||
}
|
||||
}
|
||||
|
||||
m_lastHonorUpdateTime = now;
|
||||
}
|
||||
public bool RewardHonor(Unit victim, uint groupsize, int honor = -1, bool pvptoken = false)
|
||||
{
|
||||
// do not reward honor in arenas, but enable onkill spellproc
|
||||
if (InArena())
|
||||
{
|
||||
if (!victim || victim == this || !victim.IsTypeId(TypeId.Player))
|
||||
return false;
|
||||
|
||||
if (GetBGTeam() == victim.ToPlayer().GetBGTeam())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 'Inactive' this aura prevents the player from gaining honor points and BattlegroundTokenizer
|
||||
if (HasAura(BattlegroundConst.SpellAuraPlayerInactive))
|
||||
return false;
|
||||
|
||||
ObjectGuid victim_guid = ObjectGuid.Empty;
|
||||
uint victim_rank = 0;
|
||||
|
||||
// need call before fields update to have chance move yesterday data to appropriate fields before today data change.
|
||||
UpdateHonorFields();
|
||||
|
||||
// do not reward honor in arenas, but return true to enable onkill spellproc
|
||||
if (InBattleground() && GetBattleground() && GetBattleground().isArena())
|
||||
return true;
|
||||
|
||||
// Promote to float for calculations
|
||||
float honor_f = honor;
|
||||
|
||||
if (honor_f <= 0)
|
||||
{
|
||||
if (!victim || victim == this || victim.HasAuraType(AuraType.NoPvpCredit))
|
||||
return false;
|
||||
|
||||
victim_guid = victim.GetGUID();
|
||||
Player plrVictim = victim.ToPlayer();
|
||||
if (plrVictim)
|
||||
{
|
||||
if (GetTeam() == plrVictim.GetTeam() && !Global.WorldMgr.IsFFAPvPRealm())
|
||||
return false;
|
||||
|
||||
byte k_level = (byte)getLevel();
|
||||
byte k_grey = (byte)Formulas.GetGrayLevel(k_level);
|
||||
byte v_level = (byte)victim.GetLevelForTarget(this);
|
||||
|
||||
if (v_level <= k_grey)
|
||||
return false;
|
||||
|
||||
// PLAYER_CHOSEN_TITLE VALUES DESCRIPTION
|
||||
// [0] Just name
|
||||
// [1..14] Alliance honor titles and player name
|
||||
// [15..28] Horde honor titles and player name
|
||||
// [29..38] Other title and player name
|
||||
// [39+] Nothing
|
||||
uint victim_title = victim.GetUInt32Value(PlayerFields.ChosenTitle);
|
||||
// Get Killer titles, CharTitlesEntry.bit_index
|
||||
// Ranks:
|
||||
// title[1..14] . rank[5..18]
|
||||
// title[15..28] . rank[5..18]
|
||||
// title[other] . 0
|
||||
if (victim_title == 0)
|
||||
victim_guid.Clear(); // Don't show HK: <rank> message, only log.
|
||||
else if (victim_title < 15)
|
||||
victim_rank = victim_title + 4;
|
||||
else if (victim_title < 29)
|
||||
victim_rank = victim_title - 14 + 4;
|
||||
else
|
||||
victim_guid.Clear(); // Don't show HK: <rank> message, only log.
|
||||
|
||||
honor_f = (float)Math.Ceiling(Formulas.hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey));
|
||||
|
||||
// count the number of playerkills in one day
|
||||
ApplyModUInt16Value(PlayerFields.Kills, 0, 1, true);
|
||||
// and those in a lifetime
|
||||
ApplyModUInt32Value(PlayerFields.LifetimeHonorableKills, 1, true);
|
||||
UpdateCriteria(CriteriaTypes.EarnHonorableKill);
|
||||
UpdateCriteria(CriteriaTypes.HkClass, (uint)victim.GetClass());
|
||||
UpdateCriteria(CriteriaTypes.HkRace, (uint)victim.GetRace());
|
||||
UpdateCriteria(CriteriaTypes.HonorableKillAtArea, GetAreaId());
|
||||
UpdateCriteria(CriteriaTypes.HonorableKill, 1, 0, 0, victim);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!victim.ToCreature().IsRacialLeader())
|
||||
return false;
|
||||
|
||||
honor_f = 100.0f; // ??? need more info
|
||||
victim_rank = 19; // HK: Leader
|
||||
}
|
||||
}
|
||||
|
||||
if (victim != null)
|
||||
{
|
||||
if (groupsize > 1)
|
||||
honor_f /= groupsize;
|
||||
|
||||
// apply honor multiplier from aura (not stacking-get highest)
|
||||
MathFunctions.AddPct(ref honor_f, GetMaxPositiveAuraModifier(AuraType.ModHonorGainPct));
|
||||
honor_f += _restMgr.GetRestBonusFor(RestTypes.Honor, (uint)honor_f);
|
||||
}
|
||||
|
||||
honor_f *= WorldConfig.GetFloatValue(WorldCfg.RateHonor);
|
||||
// Back to int now
|
||||
honor = (int)honor_f;
|
||||
// honor - for show honor points in log
|
||||
// victim_guid - for show victim name in log
|
||||
// victim_rank [1..4] HK: <dishonored rank>
|
||||
// victim_rank [5..19] HK: <alliance\horde rank>
|
||||
// victim_rank [0, 20+] HK: <>
|
||||
PvPCredit data = new PvPCredit();
|
||||
data.Honor = honor;
|
||||
data.OriginalHonor = honor;
|
||||
data.Target = victim_guid;
|
||||
data.Rank = victim_rank;
|
||||
|
||||
SendPacket(data);
|
||||
|
||||
AddHonorXP((uint)honor);
|
||||
|
||||
if (InBattleground() && honor > 0)
|
||||
{
|
||||
Battleground bg = GetBattleground();
|
||||
if (bg != null)
|
||||
{
|
||||
bg.UpdatePlayerScore(this, ScoreType.BonusHonor, (uint)honor, false); //false: prevent looping
|
||||
}
|
||||
}
|
||||
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.PvpTokenEnable) && pvptoken)
|
||||
{
|
||||
if (!victim || victim == this || victim.HasAuraType(AuraType.NoPvpCredit))
|
||||
return true;
|
||||
|
||||
if (victim.IsTypeId(TypeId.Player))
|
||||
{
|
||||
// Check if allowed to receive it in current map
|
||||
int MapType = WorldConfig.GetIntValue(WorldCfg.PvpTokenMapType);
|
||||
if ((MapType == 1 && !InBattleground() && !IsFFAPvP())
|
||||
|| (MapType == 2 && !IsFFAPvP())
|
||||
|| (MapType == 3 && !InBattleground()))
|
||||
return true;
|
||||
|
||||
uint itemId = WorldConfig.GetUIntValue(WorldCfg.PvpTokenId);
|
||||
uint count = WorldConfig.GetUIntValue(WorldCfg.PvpTokenCount);
|
||||
|
||||
if (AddItem(itemId, count))
|
||||
SendSysMessage("You have been awarded a token for slaying another player.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _InitHonorLevelOnLoadFromDB(uint honor, uint honorLevel, uint prestigeLevel)
|
||||
{
|
||||
SetUInt32Value(PlayerFields.HonorLevel, honorLevel);
|
||||
SetUInt32Value(PlayerFields.Prestige, prestigeLevel);
|
||||
UpdateHonorNextLevel();
|
||||
|
||||
AddHonorXP(honor);
|
||||
if (CanPrestige())
|
||||
Prestige();
|
||||
}
|
||||
|
||||
void RewardPlayerWithRewardPack(uint rewardPackID)
|
||||
{
|
||||
RewardPlayerWithRewardPack(CliDB.RewardPackStorage.LookupByKey(rewardPackID));
|
||||
}
|
||||
|
||||
void RewardPlayerWithRewardPack(RewardPackRecord rewardPackEntry)
|
||||
{
|
||||
if (rewardPackEntry == null)
|
||||
return;
|
||||
|
||||
CharTitlesRecord charTitlesEntry = CliDB.CharTitlesStorage.LookupByKey(rewardPackEntry.TitleID);
|
||||
if (charTitlesEntry != null)
|
||||
SetTitle(charTitlesEntry);
|
||||
|
||||
ModifyMoney(rewardPackEntry.Money);
|
||||
var rewardPackXItems = Global.DB2Mgr.GetRewardPackItemsByRewardID(rewardPackEntry.Id);
|
||||
if (rewardPackXItems != null)
|
||||
{
|
||||
foreach (RewardPackXItemRecord rewardPackXItem in rewardPackXItems)
|
||||
AddItem(rewardPackXItem.ItemID, rewardPackXItem.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddHonorXP(uint xp)
|
||||
{
|
||||
uint currentHonorXP = GetUInt32Value(PlayerFields.Honor);
|
||||
uint nextHonorLevelXP = GetUInt32Value(PlayerFields.HonorNextLevel);
|
||||
uint newHonorXP = currentHonorXP + xp;
|
||||
uint honorLevel = GetHonorLevel();
|
||||
|
||||
if (xp < 1 || getLevel() < PlayerConst.LevelMinHonor || IsMaxHonorLevelAndPrestige())
|
||||
return;
|
||||
|
||||
while (newHonorXP >= nextHonorLevelXP)
|
||||
{
|
||||
newHonorXP -= nextHonorLevelXP;
|
||||
|
||||
if (honorLevel < PlayerConst.MaxHonorLevel)
|
||||
SetHonorLevel((byte)(honorLevel + 1));
|
||||
|
||||
honorLevel = GetHonorLevel();
|
||||
nextHonorLevelXP = GetUInt32Value(PlayerFields.HonorNextLevel);
|
||||
}
|
||||
|
||||
SetUInt32Value(PlayerFields.Honor, IsMaxHonorLevelAndPrestige() ? 0 : newHonorXP);
|
||||
}
|
||||
|
||||
void SetHonorLevel(byte level)
|
||||
{
|
||||
byte oldHonorLevel = (byte)GetHonorLevel();
|
||||
byte prestige = (byte)GetPrestigeLevel();
|
||||
if (level == oldHonorLevel)
|
||||
return;
|
||||
|
||||
uint rewardPackID = Global.DB2Mgr.GetRewardPackIDForPvpRewardByHonorLevelAndPrestige(level, prestige);
|
||||
RewardPlayerWithRewardPack(rewardPackID);
|
||||
|
||||
SetUInt32Value(PlayerFields.HonorLevel, level);
|
||||
UpdateHonorNextLevel();
|
||||
|
||||
UpdateCriteria(CriteriaTypes.HonorLevelReached);
|
||||
|
||||
// This code is here because no link was found between those items and this reward condition in the db2 files.
|
||||
// Interesting CriteriaTree found: Tree ids: 51140, 51156 (criteria id 31773, modifier tree id 37759)
|
||||
if (level == 50 && prestige == 1)
|
||||
{
|
||||
if (GetTeam() == Team.Alliance)
|
||||
AddItem(138992, 1);
|
||||
else
|
||||
AddItem(138996, 1);
|
||||
}
|
||||
|
||||
if (CanPrestige())
|
||||
Prestige();
|
||||
}
|
||||
|
||||
public void Prestige()
|
||||
{
|
||||
SetUInt32Value(PlayerFields.Prestige, GetPrestigeLevel() + 1);
|
||||
SetUInt32Value(PlayerFields.HonorLevel, 1);
|
||||
UpdateHonorNextLevel();
|
||||
|
||||
UpdateCriteria(CriteriaTypes.PrestigeReached);
|
||||
}
|
||||
|
||||
public bool CanPrestige()
|
||||
{
|
||||
if (GetSession().GetExpansion() >= Expansion.Legion && getLevel() >= PlayerConst.LevelMinHonor && GetHonorLevel() >= PlayerConst.MaxHonorLevel && GetPrestigeLevel() < Global.DB2Mgr.GetMaxPrestige())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsMaxPrestige()
|
||||
{
|
||||
return GetPrestigeLevel() == Global.DB2Mgr.GetMaxPrestige();
|
||||
}
|
||||
|
||||
void UpdateHonorNextLevel()
|
||||
{
|
||||
uint prestige = Math.Min(16 - 1, GetPrestigeLevel());
|
||||
SetUInt32Value(PlayerFields.HonorNextLevel, (uint)CliDB.HonorLevelGameTable.GetRow(GetHonorLevel()).Prestige[prestige]);
|
||||
}
|
||||
|
||||
public uint GetPrestigeLevel() { return GetUInt32Value(PlayerFields.Prestige); }
|
||||
public uint GetHonorLevel() { return GetUInt32Value(PlayerFields.HonorLevel); }
|
||||
public bool IsMaxHonorLevelAndPrestige() { return IsMaxPrestige() && GetHonorLevel() == PlayerConst.MaxHonorLevel; }
|
||||
|
||||
|
||||
//BGs
|
||||
public Battleground GetBattleground()
|
||||
{
|
||||
if (GetBattlegroundId() == 0)
|
||||
return null;
|
||||
|
||||
return Global.BattlegroundMgr.GetBattleground(GetBattlegroundId(), m_bgData.bgTypeID);
|
||||
}
|
||||
|
||||
public bool InBattlegroundQueue()
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId != BattlegroundQueueTypeId.None)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public BattlegroundQueueTypeId GetBattlegroundQueueTypeId(uint index)
|
||||
{
|
||||
if (index < SharedConst.MaxPlayerBGQueues)
|
||||
return m_bgBattlegroundQueueID[index].bgQueueTypeId;
|
||||
|
||||
return BattlegroundQueueTypeId.None;
|
||||
}
|
||||
|
||||
public uint GetBattlegroundQueueIndex(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
return i;
|
||||
return SharedConst.MaxPlayerBGQueues;
|
||||
}
|
||||
|
||||
public bool IsInvitedForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
return m_bgBattlegroundQueueID[i].invitedToInstance != 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool InBattlegroundQueueForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
return GetBattlegroundQueueIndex(bgQueueTypeId) < SharedConst.MaxPlayerBGQueues;
|
||||
}
|
||||
|
||||
public void SetBattlegroundId(uint val, BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
m_bgData.bgInstanceID = val;
|
||||
m_bgData.bgTypeID = bgTypeId;
|
||||
}
|
||||
|
||||
public uint AddBattlegroundQueueId(BattlegroundQueueTypeId val)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
{
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == BattlegroundQueueTypeId.None || m_bgBattlegroundQueueID[i].bgQueueTypeId == val)
|
||||
{
|
||||
m_bgBattlegroundQueueID[i].bgQueueTypeId = val;
|
||||
m_bgBattlegroundQueueID[i].invitedToInstance = 0;
|
||||
m_bgBattlegroundQueueID[i].joinTime = Time.GetMSTime();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return SharedConst.MaxPlayerBGQueues;
|
||||
}
|
||||
|
||||
public bool HasFreeBattlegroundQueueId()
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == BattlegroundQueueTypeId.None)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveBattlegroundQueueId(BattlegroundQueueTypeId val)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
{
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == val)
|
||||
{
|
||||
m_bgBattlegroundQueueID[i].bgQueueTypeId = BattlegroundQueueTypeId.None;
|
||||
m_bgBattlegroundQueueID[i].invitedToInstance = 0;
|
||||
m_bgBattlegroundQueueID[i].joinTime = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInviteForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId, uint instanceId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
m_bgBattlegroundQueueID[i].invitedToInstance = instanceId;
|
||||
}
|
||||
|
||||
public bool IsInvitedForBattlegroundInstance(uint instanceId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].invitedToInstance == instanceId)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public WorldLocation GetBattlegroundEntryPoint() { return m_bgData.joinPos; }
|
||||
|
||||
public bool InBattleground() { return m_bgData.bgInstanceID != 0; }
|
||||
public uint GetBattlegroundId() { return m_bgData.bgInstanceID; }
|
||||
public BattlegroundTypeId GetBattlegroundTypeId() { return m_bgData.bgTypeID; }
|
||||
|
||||
public uint GetBattlegroundQueueJoinTime(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
return m_bgBattlegroundQueueID[i].joinTime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool CanUseBattlegroundObject(GameObject gameobject)
|
||||
{
|
||||
// It is possible to call this method with a null pointer, only skipping faction check.
|
||||
if (gameobject)
|
||||
{
|
||||
FactionTemplateRecord playerFaction = GetFactionTemplateEntry();
|
||||
FactionTemplateRecord faction = CliDB.FactionTemplateStorage.LookupByKey(gameobject.GetUInt32Value(GameObjectFields.Faction));
|
||||
|
||||
if (playerFaction != null && faction != null && !playerFaction.IsFriendlyTo(faction))
|
||||
return false;
|
||||
}
|
||||
|
||||
// BUG: sometimes when player clicks on flag in AB - client won't send gameobject_use, only gameobject_report_use packet
|
||||
// Note: Mount, stealth and invisibility will be removed when used
|
||||
return (!isTotalImmune() && // Damage immune
|
||||
!HasAura(BattlegroundConst.SpellRecentlyDroppedFlag) && // Still has recently held flag debuff
|
||||
IsAlive()); // Alive
|
||||
}
|
||||
|
||||
public bool CanCaptureTowerPoint()
|
||||
{
|
||||
return (!HasStealthAura() && // not stealthed
|
||||
!HasInvisibilityAura() && // not invisible
|
||||
IsAlive()); // live player
|
||||
}
|
||||
|
||||
public void SetBattlegroundEntryPoint()
|
||||
{
|
||||
// Taxi path store
|
||||
if (!m_taxi.empty())
|
||||
{
|
||||
m_bgData.mountSpell = 0;
|
||||
m_bgData.taxiPath[0] = m_taxi.GetTaxiSource();
|
||||
m_bgData.taxiPath[1] = m_taxi.GetTaxiDestination();
|
||||
|
||||
// On taxi we don't need check for dungeon
|
||||
m_bgData.joinPos = new WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_bgData.ClearTaxiPath();
|
||||
|
||||
// Mount spell id storing
|
||||
if (IsMounted())
|
||||
{
|
||||
var auras = GetAuraEffectsByType(AuraType.Mounted);
|
||||
if (!auras.Empty())
|
||||
m_bgData.mountSpell = auras[0].GetId();
|
||||
}
|
||||
else
|
||||
m_bgData.mountSpell = 0;
|
||||
|
||||
// If map is dungeon find linked graveyard
|
||||
if (GetMap().IsDungeon())
|
||||
{
|
||||
WorldSafeLocsRecord entry = Global.ObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam());
|
||||
if (entry != null)
|
||||
m_bgData.joinPos = new WorldLocation(entry.MapID, entry.Loc.X, entry.Loc.Y, entry.Loc.Z, 0.0f);
|
||||
else
|
||||
Log.outError(LogFilter.Player, "SetBattlegroundEntryPoint: Dungeon map {0} has no linked graveyard, setting home location as entry point.", GetMapId());
|
||||
}
|
||||
// If new entry point is not BG or arena set it
|
||||
else if (!GetMap().IsBattlegroundOrArena())
|
||||
m_bgData.joinPos = new WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
}
|
||||
|
||||
if (m_bgData.joinPos.GetMapId() == 0xFFFFFFFF) // In error cases use homebind position
|
||||
m_bgData.joinPos = new WorldLocation(GetHomebind());
|
||||
}
|
||||
|
||||
public void SetBGTeam(Team team)
|
||||
{
|
||||
m_bgData.bgTeam = (uint)team;
|
||||
SetByteValue(PlayerFields.Bytes4, PlayerFieldOffsets.Bytes4OffsetArenaFaction, (byte)(team == Team.Alliance ? 1 : 0));
|
||||
}
|
||||
|
||||
public Team GetBGTeam()
|
||||
{
|
||||
return m_bgData.bgTeam != 0 ? (Team)m_bgData.bgTeam : GetTeam();
|
||||
}
|
||||
|
||||
public void LeaveBattleground(bool teleportToEntryPoint = true)
|
||||
{
|
||||
Battleground bg = GetBattleground();
|
||||
if (bg)
|
||||
{
|
||||
bg.RemovePlayerAtLeave(GetGUID(), teleportToEntryPoint, true);
|
||||
|
||||
// call after remove to be sure that player resurrected for correct cast
|
||||
if (bg.isBattleground() && !IsGameMaster() && WorldConfig.GetBoolValue(WorldCfg.BattlegroundCastDeserter))
|
||||
{
|
||||
if (bg.GetStatus() == BattlegroundStatus.InProgress || bg.GetStatus() == BattlegroundStatus.WaitJoin)
|
||||
{
|
||||
//lets check if player was teleported from BG and schedule delayed Deserter spell cast
|
||||
if (IsBeingTeleportedFar())
|
||||
{
|
||||
ScheduleDelayedOperation(PlayerDelayedOperations.SpellCastDeserter);
|
||||
return;
|
||||
}
|
||||
|
||||
CastSpell(this, 26013, true); // Deserter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanJoinToBattleground(Battleground bg)
|
||||
{
|
||||
// check Deserter debuff
|
||||
if (HasAura(26013))
|
||||
return false;
|
||||
|
||||
if (bg.isArena() && !GetSession().HasPermission(RBACPermissions.JoinArenas))
|
||||
return false;
|
||||
|
||||
if (bg.IsRandom() && !GetSession().HasPermission(RBACPermissions.JoinRandomBg))
|
||||
return false;
|
||||
|
||||
if (!GetSession().HasPermission(RBACPermissions.JoinNormalBg))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearAfkReports() { m_bgData.bgAfkReporter.Clear(); }
|
||||
|
||||
bool CanReportAfkDueToLimit()
|
||||
{
|
||||
// a player can complain about 15 people per 5 minutes
|
||||
if (m_bgData.bgAfkReportedCount++ >= 15)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This player has been blamed to be inactive in a Battleground
|
||||
/// </summary>
|
||||
/// <param name="reporter"></param>
|
||||
public void ReportedAfkBy(Player reporter)
|
||||
{
|
||||
ReportPvPPlayerAFKResult reportAfkResult = new ReportPvPPlayerAFKResult();
|
||||
reportAfkResult.Offender = GetGUID();
|
||||
Battleground bg = GetBattleground();
|
||||
// Battleground also must be in progress!
|
||||
if (!bg || bg != reporter.GetBattleground() || GetTeam() != reporter.GetTeam() || bg.GetStatus() != BattlegroundStatus.InProgress)
|
||||
{
|
||||
reporter.SendPacket(reportAfkResult);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if player has 'Idle' or 'Inactive' debuff
|
||||
if (!m_bgData.bgAfkReporter.Contains(reporter.GetGUID()) && !HasAura(43680) && !HasAura(43681) && reporter.CanReportAfkDueToLimit())
|
||||
{
|
||||
m_bgData.bgAfkReporter.Add(reporter.GetGUID());
|
||||
// by default 3 players have to complain to apply debuff
|
||||
if (m_bgData.bgAfkReporter.Count >= WorldConfig.GetIntValue(WorldCfg.BattlegroundReportAfk))
|
||||
{
|
||||
// cast 'Idle' spell
|
||||
CastSpell(this, 43680, true);
|
||||
m_bgData.bgAfkReporter.Clear();
|
||||
reportAfkResult.NumBlackMarksOnOffender = (byte)m_bgData.bgAfkReporter.Count;
|
||||
reportAfkResult.NumPlayersIHaveReported = reporter.m_bgData.bgAfkReportedCount;
|
||||
reportAfkResult.Result = ReportPvPPlayerAFKResult.ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
reporter.SendPacket(reportAfkResult);
|
||||
}
|
||||
|
||||
public bool GetRandomWinner() { return m_IsBGRandomWinner; }
|
||||
public void SetRandomWinner(bool isWinner)
|
||||
{
|
||||
m_IsBGRandomWinner = isWinner;
|
||||
if (m_IsBGRandomWinner)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_BATTLEGROUND_RANDOM);
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetBGAccessByLevel(BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
// get a template bg instead of running one
|
||||
Battleground bg = Global.BattlegroundMgr.GetBattlegroundTemplate(bgTypeId);
|
||||
if (!bg)
|
||||
return false;
|
||||
|
||||
// limit check leel to dbc compatible level range
|
||||
uint level = getLevel();
|
||||
if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
||||
level = WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel);
|
||||
|
||||
if (level < bg.GetMinLevel() || level > bg.GetMaxLevel())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendBGWeekendWorldStates()
|
||||
{
|
||||
foreach (var bl in CliDB.BattlemasterListStorage.Values)
|
||||
{
|
||||
if (bl.HolidayWorldState != 0)
|
||||
{
|
||||
if (Global.BattlegroundMgr.IsBGWeekend((BattlegroundTypeId)bl.Id))
|
||||
SendUpdateWorldState(bl.HolidayWorldState, 1);
|
||||
else
|
||||
SendUpdateWorldState(bl.HolidayWorldState, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SendPvpRewards()
|
||||
{
|
||||
//WorldPacket packet(SMSG_REQUEST_PVP_REWARDS_RESPONSE, 24);
|
||||
//SendPacket(packet);
|
||||
}
|
||||
|
||||
//Battlefields
|
||||
void SendBattlefieldWorldStates()
|
||||
{
|
||||
// Send misc stuff that needs to be sent on every login, like the battle timers.
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.WintergraspEnable))
|
||||
{
|
||||
BattleField wg = Global.BattleFieldMgr.GetBattlefieldByBattleId(1);//Wintergrasp battle
|
||||
if (wg != null)
|
||||
{
|
||||
SendUpdateWorldState(3801, (uint)(wg.IsWarTime() ? 0 : 1));
|
||||
uint timer = wg.IsWarTime() ? 0 : (wg.GetTimer() / 1000); // 0 - Time to next battle
|
||||
SendUpdateWorldState(4354, (uint)(Time.UnixTime + timer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Arenas
|
||||
public void SetArenaTeamInfoField(byte slot, ArenaTeamInfoType type, uint value)
|
||||
{
|
||||
SetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)type, value);
|
||||
}
|
||||
|
||||
public void SetInArenaTeam(uint ArenaTeamId, byte slot, byte type)
|
||||
{
|
||||
SetArenaTeamInfoField(slot, ArenaTeamInfoType.Id, ArenaTeamId);
|
||||
SetArenaTeamInfoField(slot, ArenaTeamInfoType.Type, type);
|
||||
}
|
||||
|
||||
public static uint GetArenaTeamIdFromDB(ObjectGuid guid, byte type)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ARENA_TEAM_ID_BY_PLAYER_GUID);
|
||||
stmt.AddValue(0, guid.GetCounter());
|
||||
stmt.AddValue(1, type);
|
||||
SQLResult result = DB.Characters.Query(stmt);
|
||||
|
||||
if (result.IsEmpty())
|
||||
return 0;
|
||||
|
||||
return result.Read<uint>(0);
|
||||
}
|
||||
|
||||
public static void LeaveAllArenaTeams(ObjectGuid guid)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_ARENA_TEAMS);
|
||||
stmt.AddValue(0, guid.GetCounter());
|
||||
SQLResult result = DB.Characters.Query(stmt);
|
||||
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint arenaTeamId = result.Read<uint>(0);
|
||||
if (arenaTeamId != 0)
|
||||
{
|
||||
ArenaTeam arenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(arenaTeamId);
|
||||
if (arenaTeam != null)
|
||||
arenaTeam.DelMember(guid, true);
|
||||
}
|
||||
}
|
||||
while (result.NextRow());
|
||||
}
|
||||
public uint GetArenaTeamId(byte slot) { return GetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)ArenaTeamInfoType.Id); }
|
||||
public uint GetArenaPersonalRating(byte slot) { return GetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)ArenaTeamInfoType.PersonalRating); }
|
||||
public void SetArenaTeamIdInvited(uint ArenaTeamId) { m_ArenaTeamIdInvited = ArenaTeamId; }
|
||||
public uint GetArenaTeamIdInvited() { return m_ArenaTeamIdInvited; }
|
||||
public uint GetRBGPersonalRating() { return 0; }
|
||||
|
||||
//OutdoorPVP
|
||||
public bool IsOutdoorPvPActive()
|
||||
{
|
||||
return IsAlive() && !HasInvisibilityAura() && !HasStealthAura() && IsPvP() && !HasUnitMovementFlag(MovementFlag.Flying) && !IsInFlight();
|
||||
}
|
||||
public OutdoorPvP GetOutdoorPvP()
|
||||
{
|
||||
return Global.OutdoorPvPMgr.GetOutdoorPvPToZoneId(GetZoneId());
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,555 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public void InitTalentForLevel()
|
||||
{
|
||||
uint level = getLevel();
|
||||
// talents base at level diff (talents = level - 9 but some can be used already)
|
||||
if (level < PlayerConst.MinSpecializationLevel)
|
||||
ResetTalentSpecialization();
|
||||
|
||||
uint talentTiers = CalculateTalentsTiers();
|
||||
if (level < 15)
|
||||
{
|
||||
// Remove all talent points
|
||||
ResetTalents(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!GetSession().HasPermission(RBACPermissions.SkipCheckMoreTalentsThanAllowed))
|
||||
{
|
||||
for (uint t = talentTiers; t < PlayerConst.MaxTalentTiers; ++t)
|
||||
for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c)
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), t, c))
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
}
|
||||
|
||||
SetUInt32Value(PlayerFields.MaxTalentTiers, talentTiers);
|
||||
|
||||
if (!GetSession().PlayerLoading())
|
||||
SendTalentsInfoData(); // update at client
|
||||
}
|
||||
|
||||
public bool AddTalent(TalentRecord talent, byte spec, bool learning)
|
||||
{
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID);
|
||||
if (spellInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Spells, "Player.AddTalent: Spell (ID: {0}) does not exist.", talent.SpellID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Global.SpellMgr.IsSpellValid(spellInfo, this, false))
|
||||
{
|
||||
Log.outError(LogFilter.Spells, "Player.AddTalent: Spell (ID: {0}) is invalid", talent.SpellID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (talent.OverridesSpellID != 0)
|
||||
AddOverrideSpell(talent.OverridesSpellID, talent.SpellID);
|
||||
|
||||
if (GetTalentMap(spec).ContainsKey(talent.Id))
|
||||
GetTalentMap(spec)[talent.Id] = PlayerSpellState.Unchanged;
|
||||
else
|
||||
GetTalentMap(spec)[talent.Id] = learning ? PlayerSpellState.New : PlayerSpellState.Unchanged;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveTalent(TalentRecord talent)
|
||||
{
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID);
|
||||
if (spellInfo == null)
|
||||
return;
|
||||
|
||||
RemoveSpell(talent.SpellID, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
|
||||
if (effect != null && effect.TriggerSpell > 0 && effect.Effect == SpellEffectName.LearnSpell)
|
||||
RemoveSpell(effect.TriggerSpell, true);
|
||||
|
||||
if (talent.OverridesSpellID != 0)
|
||||
RemoveOverrideSpell(talent.OverridesSpellID, talent.SpellID);
|
||||
|
||||
var talentMap = GetTalentMap(GetActiveTalentGroup());
|
||||
// if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
|
||||
if (talentMap.ContainsKey(talent.Id))
|
||||
talentMap[talent.Id] = PlayerSpellState.Removed;
|
||||
}
|
||||
|
||||
public TalentLearnResult LearnTalent(uint talentId, ref int spellOnCooldown)
|
||||
{
|
||||
if (IsInCombat())
|
||||
return TalentLearnResult.FailedAffectingCombat;
|
||||
|
||||
if (IsDead() || GetMap().IsBattlegroundOrArena())
|
||||
return TalentLearnResult.FailedCantDoThatRightNow;
|
||||
|
||||
if (GetUInt32Value(PlayerFields.CurrentSpecId) == 0)
|
||||
return TalentLearnResult.FailedNoPrimaryTreeSelected;
|
||||
|
||||
TalentRecord talentInfo = CliDB.TalentStorage.LookupByKey(talentId);
|
||||
if (talentInfo == null)
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
if (talentInfo.SpecID != 0 && talentInfo.SpecID != GetUInt32Value(PlayerFields.CurrentSpecId))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// prevent learn talent for different class (cheating)
|
||||
if (talentInfo.ClassID != (byte)GetClass())
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// check if we have enough talent points
|
||||
if (talentInfo.TierID >= GetUInt32Value(PlayerFields.MaxTalentTiers))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// TODO: prevent changing talents that are on cooldown
|
||||
|
||||
// Check if there is a different talent for us to learn in selected slot
|
||||
// Example situation:
|
||||
// Warrior talent row 2 slot 0
|
||||
// Talent.dbc has an entry for each specialization
|
||||
// but only 2 out of 3 have SpecID != 0
|
||||
// We need to make sure that if player is in one of these defined specs he will not learn the other choice
|
||||
TalentRecord bestSlotMatch = null;
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), talentInfo.TierID, talentInfo.ColumnIndex))
|
||||
{
|
||||
if (talent.SpecID == 0)
|
||||
bestSlotMatch = talent;
|
||||
else if (talent.SpecID == GetUInt32Value(PlayerFields.CurrentSpecId))
|
||||
{
|
||||
bestSlotMatch = talent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (talentInfo != bestSlotMatch)
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// Check if player doesn't have any talent in current tier
|
||||
for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c)
|
||||
{
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), talentInfo.TierID, c))
|
||||
{
|
||||
//Todo test me
|
||||
if (talent.SpecID != GetUInt32Value(PlayerFields.CurrentSpecId))
|
||||
continue;
|
||||
|
||||
if (HasTalent(talent.Id, GetActiveTalentGroup()) && !HasFlag(PlayerFields.Flags, PlayerFlags.Resting) && HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc))
|
||||
return TalentLearnResult.FailedRestArea;
|
||||
|
||||
if (GetSpellHistory().HasCooldown(talent.SpellID))
|
||||
{
|
||||
spellOnCooldown = (int)talent.SpellID;
|
||||
return TalentLearnResult.FailedCantRemoveTalent;
|
||||
}
|
||||
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
}
|
||||
|
||||
// spell not set in talent.dbc
|
||||
uint spellid = talentInfo.SpellID;
|
||||
if (spellid == 0)
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Player.LearnTalent: Talent.dbc has no spellInfo for talent: {0} (spell id = 0)", talentId);
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
}
|
||||
|
||||
// already known
|
||||
if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
if (!AddTalent(talentInfo, GetActiveTalentGroup(), true))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
LearnSpell(spellid, false);
|
||||
|
||||
Log.outDebug(LogFilter.Misc, "Player.LearnTalent: TalentID: {0} Spell: {1} Group: {2}", talentId, spellid, GetActiveTalentGroup());
|
||||
|
||||
return TalentLearnResult.LearnOk;
|
||||
}
|
||||
|
||||
public void ResetTalentSpecialization()
|
||||
{
|
||||
// Reset only talents that have different spells for each spec
|
||||
Class class_ = GetClass();
|
||||
for (uint t = 0; t < PlayerConst.MaxTalentTiers; ++t)
|
||||
{
|
||||
for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c)
|
||||
{
|
||||
if (Global.DB2Mgr.GetTalentsByPosition(class_, t, c).Count > 1)
|
||||
{
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(class_, t, c))
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoveSpecializationSpells();
|
||||
|
||||
ChrSpecializationRecord defaultSpec = Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass());
|
||||
SetPrimarySpecialization(defaultSpec.Id);
|
||||
SetActiveTalentGroup(defaultSpec.OrderIndex);
|
||||
SetUInt32Value(PlayerFields.CurrentSpecId, defaultSpec.Id);
|
||||
|
||||
LearnSpecializationSpells();
|
||||
|
||||
SendTalentsInfoData();
|
||||
UpdateItemSetAuras(false);
|
||||
}
|
||||
|
||||
bool HasTalent(uint talnetId, byte group)
|
||||
{
|
||||
return GetTalentMap(group).ContainsKey(talnetId) && GetTalentMap(group)[talnetId] != PlayerSpellState.Removed;
|
||||
}
|
||||
|
||||
uint GetTalentResetCost() { return _specializationInfo.ResetTalentsCost; }
|
||||
void SetTalentResetCost(uint cost) { _specializationInfo.ResetTalentsCost = cost; }
|
||||
long GetTalentResetTime() { return _specializationInfo.ResetTalentsTime; }
|
||||
void SetTalentResetTime(long time_) { _specializationInfo.ResetTalentsTime = time_; }
|
||||
uint GetPrimarySpecialization() { return _specializationInfo.PrimarySpecialization; }
|
||||
void SetPrimarySpecialization(uint spec) { _specializationInfo.PrimarySpecialization = spec; }
|
||||
public byte GetActiveTalentGroup() { return _specializationInfo.ActiveGroup; }
|
||||
void SetActiveTalentGroup(byte group) { _specializationInfo.ActiveGroup = group; }
|
||||
|
||||
// Loot Spec
|
||||
public void SetLootSpecId(uint id) { SetUInt32Value(PlayerFields.LootSpecId, id); }
|
||||
uint GetLootSpecId() { return GetUInt32Value(PlayerFields.LootSpecId); }
|
||||
|
||||
public uint GetDefaultSpecId()
|
||||
{
|
||||
return Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass()).Id;
|
||||
}
|
||||
|
||||
public void ActivateTalentGroup(ChrSpecializationRecord spec)
|
||||
{
|
||||
if (GetActiveTalentGroup() == spec.OrderIndex)
|
||||
return;
|
||||
|
||||
if (IsNonMeleeSpellCast(false))
|
||||
InterruptNonMeleeSpells(false);
|
||||
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
_SaveActions(trans);
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
|
||||
// TO-DO: We need more research to know what happens with warlock's reagent
|
||||
Pet pet = GetPet();
|
||||
if (pet)
|
||||
RemovePet(pet, PetSaveMode.NotInSlot);
|
||||
|
||||
ClearAllReactives();
|
||||
UnsummonAllTotems();
|
||||
ExitVehicle();
|
||||
RemoveAllControlled();
|
||||
|
||||
// Let client clear his current Actions
|
||||
SendActionButtons(2);
|
||||
foreach (var talentInfo in CliDB.TalentStorage.Values)
|
||||
{
|
||||
// unlearn only talents for character class
|
||||
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
|
||||
// to prevent unexpected lost normal learned spell skip another class talents
|
||||
if (talentInfo.ClassID != (int)GetClass())
|
||||
continue;
|
||||
|
||||
if (talentInfo.SpellID == 0)
|
||||
continue;
|
||||
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID);
|
||||
if (spellInfo == null)
|
||||
continue;
|
||||
|
||||
RemoveSpell(talentInfo.SpellID, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
|
||||
if (effect != null && effect.TriggerSpell > 0 && effect.Effect == SpellEffectName.LearnSpell)
|
||||
RemoveSpell(effect.TriggerSpell, true);
|
||||
|
||||
if (talentInfo.OverridesSpellID != 0)
|
||||
RemoveOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID);
|
||||
}
|
||||
|
||||
// Remove spec specific spells
|
||||
RemoveSpecializationSpells();
|
||||
|
||||
foreach (uint glyphId in GetGlyphs(GetActiveTalentGroup()))
|
||||
RemoveAurasDueToSpell(CliDB.GlyphPropertiesStorage.LookupByKey(glyphId).SpellID);
|
||||
|
||||
SetActiveTalentGroup(spec.OrderIndex);
|
||||
SetUInt32Value(PlayerFields.CurrentSpecId, spec.Id);
|
||||
if (GetPrimarySpecialization() == 0)
|
||||
SetPrimarySpecialization(spec.Id);
|
||||
|
||||
foreach (var talentInfo in CliDB.TalentStorage.Values)
|
||||
{
|
||||
// learn only talents for character class
|
||||
if (talentInfo.ClassID != (int)GetClass())
|
||||
continue;
|
||||
|
||||
if (talentInfo.SpellID == 0)
|
||||
continue;
|
||||
|
||||
if (HasTalent(talentInfo.SpellID, GetActiveTalentGroup()))
|
||||
{
|
||||
LearnSpell(talentInfo.SpellID, false); // add the talent to the PlayerSpellMap
|
||||
if (talentInfo.OverridesSpellID != 0)
|
||||
AddOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID);
|
||||
}
|
||||
}
|
||||
|
||||
LearnSpecializationSpells();
|
||||
|
||||
if (CanUseMastery())
|
||||
{
|
||||
for (uint i = 0; i < PlayerConst.MaxMasterySpells; ++i)
|
||||
{
|
||||
uint mastery = spec.MasterySpellID[i];
|
||||
if (mastery != 0)
|
||||
LearnSpell(mastery, false);
|
||||
}
|
||||
}
|
||||
|
||||
InitTalentForLevel();
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC);
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, GetActiveTalentGroup());
|
||||
_LoadActions(DB.Characters.Query(stmt));
|
||||
|
||||
SendActionButtons(1);
|
||||
|
||||
UpdateDisplayPower();
|
||||
PowerType pw = getPowerType();
|
||||
if (pw != PowerType.Mana)
|
||||
SetPower(PowerType.Mana, 0); // Mana must be 0 even if it isn't the active power type.
|
||||
|
||||
SetPower(pw, 0);
|
||||
UpdateItemSetAuras(false);
|
||||
|
||||
// update visible transmog
|
||||
for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i)
|
||||
{
|
||||
Item equippedItem = GetItemByPos(InventorySlots.Bag0, i);
|
||||
if (equippedItem)
|
||||
SetVisibleItemSlot(i, equippedItem);
|
||||
}
|
||||
|
||||
foreach (uint glyphId in GetGlyphs(spec.OrderIndex))
|
||||
CastSpell(this, CliDB.GlyphPropertiesStorage.LookupByKey(glyphId).SpellID, true);
|
||||
|
||||
ActiveGlyphs activeGlyphs = new ActiveGlyphs();
|
||||
foreach (uint glyphId in GetGlyphs(spec.OrderIndex))
|
||||
{
|
||||
List<uint> bindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphId);
|
||||
foreach (uint bindableSpell in bindableSpells)
|
||||
if (HasSpell(bindableSpell) && !m_overrideSpells.ContainsKey(bindableSpell))
|
||||
activeGlyphs.Glyphs.Add(new GlyphBinding(bindableSpell, (ushort)glyphId));
|
||||
}
|
||||
|
||||
activeGlyphs.IsFullUpdate = true;
|
||||
SendPacket(activeGlyphs);
|
||||
}
|
||||
|
||||
public Dictionary<uint, PlayerSpellState> GetTalentMap(uint spec) { return _specializationInfo.Talents[spec]; }
|
||||
public List<uint> GetGlyphs(byte spec) { return _specializationInfo.Glyphs[spec]; }
|
||||
|
||||
public uint GetNextResetTalentsCost()
|
||||
{
|
||||
// The first time reset costs 1 gold
|
||||
if (GetTalentResetCost() < 1 * MoneyConstants.Gold)
|
||||
return 1 * MoneyConstants.Gold;
|
||||
// then 5 gold
|
||||
else if (GetTalentResetCost() < 5 * MoneyConstants.Gold)
|
||||
return 5 * MoneyConstants.Gold;
|
||||
// After that it increases in increments of 5 gold
|
||||
else if (GetTalentResetCost() < 10 * MoneyConstants.Gold)
|
||||
return 10 * MoneyConstants.Gold;
|
||||
else
|
||||
{
|
||||
ulong months = (ulong)(Global.WorldMgr.GetGameTime() - GetTalentResetTime()) / Time.Month;
|
||||
if (months > 0)
|
||||
{
|
||||
// This cost will be reduced by a rate of 5 gold per month
|
||||
uint new_cost = (uint)(GetTalentResetCost() - 5 * MoneyConstants.Gold * months);
|
||||
// to a minimum of 10 gold.
|
||||
return new_cost < 10 * MoneyConstants.Gold ? 10 * MoneyConstants.Gold : new_cost;
|
||||
}
|
||||
else
|
||||
{
|
||||
// After that it increases in increments of 5 gold
|
||||
uint new_cost = GetTalentResetCost() + 5 * MoneyConstants.Gold;
|
||||
// until it hits a cap of 50 gold.
|
||||
if (new_cost > 50 * MoneyConstants.Gold)
|
||||
new_cost = 50 * MoneyConstants.Gold;
|
||||
return new_cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ResetTalents(bool noCost = false)
|
||||
{
|
||||
Global.ScriptMgr.OnPlayerTalentsReset(this, noCost);
|
||||
|
||||
// not need after this call
|
||||
if (HasAtLoginFlag(AtLoginFlags.ResetTalents))
|
||||
RemoveAtLoginFlag(AtLoginFlags.ResetTalents, true);
|
||||
|
||||
uint cost = 0;
|
||||
if (!noCost && !WorldConfig.GetBoolValue(WorldCfg.NoResetTalentCost))
|
||||
{
|
||||
cost = GetNextResetTalentsCost();
|
||||
|
||||
if (!HasEnoughMoney(cost))
|
||||
{
|
||||
SendBuyError(BuyResult.NotEnoughtMoney, null, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RemovePet(null, PetSaveMode.NotInSlot, true);
|
||||
|
||||
foreach (var talentInfo in CliDB.TalentStorage.Values)
|
||||
{
|
||||
// unlearn only talents for character class
|
||||
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
|
||||
// to prevent unexpected lost normal learned spell skip another class talents
|
||||
if (talentInfo.ClassID != (uint)GetClass())
|
||||
continue;
|
||||
|
||||
// skip non-existant talent ranks
|
||||
if (talentInfo.SpellID == 0)
|
||||
continue;
|
||||
|
||||
RemoveTalent(talentInfo);
|
||||
}
|
||||
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
_SaveTalents(trans);
|
||||
_SaveSpells(trans);
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
|
||||
if (!noCost)
|
||||
{
|
||||
ModifyMoney(-cost);
|
||||
UpdateCriteria(CriteriaTypes.GoldSpentForTalents, cost);
|
||||
UpdateCriteria(CriteriaTypes.NumberOfTalentResets, 1);
|
||||
|
||||
SetTalentResetCost(cost);
|
||||
SetTalentResetTime(Time.UnixTime);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SendTalentsInfoData()
|
||||
{
|
||||
UpdateTalentData packet = new UpdateTalentData();
|
||||
packet.Info.PrimarySpecialization = GetPrimarySpecialization();
|
||||
packet.Info.ActiveGroup = GetActiveTalentGroup();
|
||||
|
||||
for (byte i = 0; i < PlayerConst.MaxSpecializations; ++i)
|
||||
{
|
||||
ChrSpecializationRecord spec = Global.DB2Mgr.GetChrSpecializationByIndex(GetClass(), i);
|
||||
if (spec == null)
|
||||
continue;
|
||||
|
||||
var talents = GetTalentMap(i);
|
||||
|
||||
|
||||
UpdateTalentData.TalentGroupInfo groupInfoPkt = new UpdateTalentData.TalentGroupInfo();
|
||||
groupInfoPkt.SpecID = spec.Id;
|
||||
|
||||
foreach (var pair in talents)
|
||||
{
|
||||
if (pair.Value == PlayerSpellState.Removed)
|
||||
continue;
|
||||
|
||||
TalentRecord talentInfo = CliDB.TalentStorage.LookupByKey(pair.Key);
|
||||
if (talentInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Player {0} has unknown talent id: {1}", GetName(), pair.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (talentInfo.ClassID != (uint)GetClass())
|
||||
continue;
|
||||
|
||||
SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID);
|
||||
if (spellEntry == null)
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Player {0} has unknown talent spell: {1}", GetName(), talentInfo.SpellID);
|
||||
continue;
|
||||
}
|
||||
|
||||
groupInfoPkt.TalentIDs.Add((ushort)pair.Key);
|
||||
}
|
||||
|
||||
packet.Info.TalentGroups.Add(groupInfoPkt);
|
||||
}
|
||||
|
||||
SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendRespecWipeConfirm(ObjectGuid guid, uint cost)
|
||||
{
|
||||
RespecWipeConfirm respecWipeConfirm = new RespecWipeConfirm();
|
||||
respecWipeConfirm.RespecMaster = guid;
|
||||
respecWipeConfirm.Cost = cost;
|
||||
respecWipeConfirm.RespecType = SpecResetType.Talents;
|
||||
SendPacket(respecWipeConfirm);
|
||||
}
|
||||
|
||||
uint CalculateTalentsTiers()
|
||||
{
|
||||
uint[] rowLevels = new uint[0];
|
||||
switch (GetClass())
|
||||
{
|
||||
case Class.Deathknight:
|
||||
rowLevels = new uint[] { 57, 58, 59, 60, 75, 90, 100 };
|
||||
break;
|
||||
case Class.DemonHunter:
|
||||
rowLevels = new uint[] { 99, 100, 102, 104, 106, 108, 110 };
|
||||
break;
|
||||
default:
|
||||
rowLevels = new uint[] { 15, 30, 45, 60, 75, 90, 100 };
|
||||
break;
|
||||
}
|
||||
|
||||
for (uint i = PlayerConst.MaxTalentTiers; i != 0; --i)
|
||||
if (getLevel() >= rowLevels[i - 1])
|
||||
return i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Collections;
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class PlayerTaxi
|
||||
{
|
||||
public void InitTaxiNodesForLevel(Race race, Class chrClass, uint level)
|
||||
{
|
||||
// class specific initial known nodes
|
||||
var factionMask = Player.TeamForRace(race) == Team.Horde ? CliDB.HordeTaxiNodesMask : CliDB.AllianceTaxiNodesMask;
|
||||
switch (chrClass)
|
||||
{
|
||||
case Class.Deathknight:
|
||||
{
|
||||
for (byte i = 0; i < PlayerConst.TaxiMaskSize; ++i)
|
||||
m_taximask[i] |= (byte)(CliDB.OldContinentsNodesMask[i] & factionMask[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// race specific initial known nodes: capital and taxi hub masks
|
||||
switch (race)
|
||||
{
|
||||
case Race.Human:
|
||||
case Race.Dwarf:
|
||||
case Race.NightElf:
|
||||
case Race.Gnome:
|
||||
case Race.Draenei:
|
||||
case Race.Worgen:
|
||||
case Race.PandarenAlliance:
|
||||
SetTaximaskNode(2); // Stormwind, Elwynn
|
||||
SetTaximaskNode(6); // Ironforge, Dun Morogh
|
||||
SetTaximaskNode(26); // Lor'danel, Darkshore
|
||||
SetTaximaskNode(27); // Rut'theran Village, Teldrassil
|
||||
SetTaximaskNode(49); // Moonglade (Alliance)
|
||||
SetTaximaskNode(94); // The Exodar
|
||||
SetTaximaskNode(456); // Dolanaar, Teldrassil
|
||||
SetTaximaskNode(457); // Darnassus, Teldrassil
|
||||
SetTaximaskNode(582); // Goldshire, Elwynn
|
||||
SetTaximaskNode(589); // Eastvale Logging Camp, Elwynn
|
||||
SetTaximaskNode(619); // Kharanos, Dun Morogh
|
||||
SetTaximaskNode(620); // Gol'Bolar Quarry, Dun Morogh
|
||||
SetTaximaskNode(624); // Azure Watch, Azuremyst Isle
|
||||
break;
|
||||
case Race.Orc:
|
||||
case Race.Undead:
|
||||
case Race.Tauren:
|
||||
case Race.Troll:
|
||||
case Race.BloodElf:
|
||||
case Race.Goblin:
|
||||
case Race.PandarenHorde:
|
||||
SetTaximaskNode(11); // Undercity, Tirisfal
|
||||
SetTaximaskNode(22); // Thunder Bluff, Mulgore
|
||||
SetTaximaskNode(23); // Orgrimmar, Durotar
|
||||
SetTaximaskNode(69); // Moonglade (Horde)
|
||||
SetTaximaskNode(82); // Silvermoon City
|
||||
SetTaximaskNode(384); // The Bulwark, Tirisfal
|
||||
SetTaximaskNode(402); // Bloodhoof Village, Mulgore
|
||||
SetTaximaskNode(460); // Brill, Tirisfal Glades
|
||||
SetTaximaskNode(536); // Sen'jin Village, Durotar
|
||||
SetTaximaskNode(537); // Razor Hill, Durotar
|
||||
SetTaximaskNode(625); // Fairbreeze Village, Eversong Woods
|
||||
SetTaximaskNode(631); // Falconwing Square, Eversong Woods
|
||||
break;
|
||||
}
|
||||
|
||||
// new continent starting masks (It will be accessible only at new map)
|
||||
switch (Player.TeamForRace(race))
|
||||
{
|
||||
case Team.Alliance:
|
||||
SetTaximaskNode(100);
|
||||
break;
|
||||
case Team.Horde:
|
||||
SetTaximaskNode(99);
|
||||
break;
|
||||
}
|
||||
// level dependent taxi hubs
|
||||
if (level >= 68)
|
||||
SetTaximaskNode(213); //Shattered Sun Staging Area
|
||||
}
|
||||
|
||||
public void LoadTaxiMask(string data)
|
||||
{
|
||||
var split = new StringArray(data, ' ');
|
||||
|
||||
byte index = 0;
|
||||
for (var i = 0; index < PlayerConst.TaxiMaskSize && i != split.Length; ++i, ++index)
|
||||
{
|
||||
// load and set bits only for existing taxi nodes
|
||||
m_taximask[index] = (byte)(CliDB.TaxiNodesMask[index] & uint.Parse(split[i]));
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendTaximaskTo(ShowTaxiNodes data, bool all)
|
||||
{
|
||||
if (all)
|
||||
data.Nodes = CliDB.TaxiNodesMask; // all existed nodes
|
||||
else
|
||||
data.Nodes = m_taximask; // known nodes
|
||||
}
|
||||
|
||||
public bool LoadTaxiDestinationsFromString(string values, Team team)
|
||||
{
|
||||
ClearTaxiDestinations();
|
||||
|
||||
var split = new StringArray(values, ' ');
|
||||
for (var i = 0; i < split.Length; ++i)
|
||||
{
|
||||
uint node = uint.Parse(split[i]);
|
||||
AddTaxiDestination(node);
|
||||
}
|
||||
|
||||
if (m_TaxiDestinations.Empty())
|
||||
return true;
|
||||
|
||||
// Check integrity
|
||||
if (m_TaxiDestinations.Count < 2)
|
||||
return false;
|
||||
|
||||
for (int i = 1; i < m_TaxiDestinations.Count; ++i)
|
||||
{
|
||||
uint cost;
|
||||
uint path;
|
||||
Global.ObjectMgr.GetTaxiPath(m_TaxiDestinations[i - 1], m_TaxiDestinations[i], out path, out cost);
|
||||
if (path == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// can't load taxi path without mount set (quest taxi path?)
|
||||
if (Global.ObjectMgr.GetTaxiMountDisplayId(GetTaxiSource(), team, true) == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string SaveTaxiDestinationsToString()
|
||||
{
|
||||
if (m_TaxiDestinations.Empty())
|
||||
return "";
|
||||
|
||||
StringBuilder ss = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < m_TaxiDestinations.Count; ++i)
|
||||
ss.AppendFormat("{0} ", m_TaxiDestinations[i]);
|
||||
|
||||
return ss.ToString();
|
||||
}
|
||||
|
||||
public uint GetCurrentTaxiPath()
|
||||
{
|
||||
if (m_TaxiDestinations.Count < 2)
|
||||
return 0;
|
||||
|
||||
uint path;
|
||||
uint cost;
|
||||
|
||||
Global.ObjectMgr.GetTaxiPath(m_TaxiDestinations[0], m_TaxiDestinations[1], out path, out cost);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public bool RequestEarlyLanding()
|
||||
{
|
||||
if (m_TaxiDestinations.Count <= 2)
|
||||
return false;
|
||||
|
||||
// start from first destination - m_TaxiDestinations[0] is the current starting node
|
||||
for (var i = 1; i < m_TaxiDestinations.Count; ++i)
|
||||
{
|
||||
if (IsTaximaskNodeKnown(m_TaxiDestinations[i]))
|
||||
{
|
||||
if (++i == m_TaxiDestinations.Count - 1)
|
||||
return false; // if we are left with only 1 known node on the path don't change the spline, its our final destination anyway
|
||||
|
||||
m_TaxiDestinations.RemoveRange(i, m_TaxiDestinations.Count - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsTaximaskNodeKnown(uint nodeidx)
|
||||
{
|
||||
byte field = (byte)((nodeidx - 1) / 8);
|
||||
uint submask = (uint)(1 << (int)((nodeidx - 1) % 8));
|
||||
return (m_taximask[field] & submask) == submask;
|
||||
}
|
||||
public bool SetTaximaskNode(uint nodeidx)
|
||||
{
|
||||
byte field = (byte)((nodeidx - 1) / 8);
|
||||
uint submask = (uint)(1 << (int)((nodeidx - 1) % 8));
|
||||
if ((m_taximask[field] & submask) != submask)
|
||||
{
|
||||
m_taximask[field] |= (byte)submask;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ClearTaxiDestinations() { m_TaxiDestinations.Clear(); }
|
||||
public void AddTaxiDestination(uint dest) { m_TaxiDestinations.Add(dest); }
|
||||
void SetTaxiDestination(List<uint> nodes)
|
||||
{
|
||||
m_TaxiDestinations.Clear();
|
||||
m_TaxiDestinations.AddRange(nodes);
|
||||
}
|
||||
public uint GetTaxiSource() { return m_TaxiDestinations.Empty() ? 0 : m_TaxiDestinations[0]; }
|
||||
public uint GetTaxiDestination() { return m_TaxiDestinations.Count < 2 ? 0 : m_TaxiDestinations[1]; }
|
||||
public uint NextTaxiDestination()
|
||||
{
|
||||
m_TaxiDestinations.RemoveAt(0);
|
||||
return GetTaxiDestination();
|
||||
}
|
||||
public List<uint> GetPath() { return m_TaxiDestinations; }
|
||||
public bool empty() { return m_TaxiDestinations.Empty(); }
|
||||
|
||||
public byte[] m_taximask = new byte[PlayerConst.TaxiMaskSize];
|
||||
List<uint> m_TaxiDestinations = new List<uint>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using Framework.Constants;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class RestMgr
|
||||
{
|
||||
public RestMgr(Player player)
|
||||
{
|
||||
_player = player;
|
||||
}
|
||||
|
||||
public void SetRestBonus(RestTypes restType, float restBonus)
|
||||
{
|
||||
byte rest_rested_offset;
|
||||
byte rest_state_offset;
|
||||
PlayerFields next_level_xp_field;
|
||||
bool affectedByRaF = false;
|
||||
|
||||
switch (restType)
|
||||
{
|
||||
case RestTypes.XP:
|
||||
// Reset restBonus (XP only) for max level players
|
||||
if (_player.getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
||||
restBonus = 0;
|
||||
|
||||
rest_rested_offset = PlayerFieldOffsets.RestRestedXp;
|
||||
rest_state_offset = PlayerFieldOffsets.RestStateXp;
|
||||
next_level_xp_field = PlayerFields.NextLevelXp;
|
||||
affectedByRaF = true;
|
||||
break;
|
||||
case RestTypes.Honor:
|
||||
// Reset restBonus (Honor only) for players with max honor level.
|
||||
if (_player.IsMaxHonorLevelAndPrestige())
|
||||
restBonus = 0;
|
||||
|
||||
rest_rested_offset = PlayerFieldOffsets.RestRestedHonor;
|
||||
rest_state_offset = PlayerFieldOffsets.RestStateHonor;
|
||||
next_level_xp_field = PlayerFields.HonorNextLevel;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (restBonus < 0)
|
||||
restBonus = 0;
|
||||
|
||||
float rest_bonus_max = (float)(_player.GetUInt32Value(next_level_xp_field)) * 1.5f / 2;
|
||||
|
||||
if (restBonus > rest_bonus_max)
|
||||
_restBonus[(int)restType] = rest_bonus_max;
|
||||
else
|
||||
_restBonus[(int)restType] = restBonus;
|
||||
|
||||
// update data for client
|
||||
if (affectedByRaF && _player.GetsRecruitAFriendBonus(true) && (_player.GetSession().IsARecruiter() || _player.GetSession().GetRecruiterId() != 0))
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.RAFLinked);
|
||||
else
|
||||
{
|
||||
if (_restBonus[(int)restType] > 10)
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.Rested);
|
||||
else if (_restBonus[(int)restType] <= 1)
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.NotRAFLinked);
|
||||
}
|
||||
|
||||
// RestTickUpdate
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_rested_offset, (uint)_restBonus[(int)restType]);
|
||||
}
|
||||
|
||||
public void AddRestBonus(RestTypes restType, float restBonus)
|
||||
{
|
||||
// Don't add extra rest bonus to max level players. Note: Might need different condition in next expansion for honor XP (PLAYER_LEVEL_MIN_HONOR perhaps).
|
||||
if (_player.getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
||||
restBonus = 0;
|
||||
|
||||
float totalRestBonus = GetRestBonus(restType) + restBonus;
|
||||
SetRestBonus(restType, totalRestBonus);
|
||||
}
|
||||
|
||||
public void SetRestFlag(RestFlag restFlag, uint triggerId = 0)
|
||||
{
|
||||
RestFlag oldRestMask = _restFlagMask;
|
||||
_restFlagMask |= restFlag;
|
||||
|
||||
if (oldRestMask == 0 && _restFlagMask != 0) // only set flag/time on the first rest state
|
||||
{
|
||||
_restTime = Time.UnixTime;
|
||||
_player.SetFlag(PlayerFields.Flags, PlayerFlags.Resting);
|
||||
}
|
||||
|
||||
if (triggerId != 0)
|
||||
_innAreaTriggerId = triggerId;
|
||||
}
|
||||
|
||||
public void RemoveRestFlag(RestFlag restFlag)
|
||||
{
|
||||
RestFlag oldRestMask = _restFlagMask;
|
||||
_restFlagMask &= ~restFlag;
|
||||
|
||||
if (oldRestMask != 0 && _restFlagMask == 0) // only remove flag/time on the last rest state remove
|
||||
{
|
||||
_restTime = 0;
|
||||
_player.RemoveFlag(PlayerFields.Flags, PlayerFlags.Resting);
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetRestBonusFor(RestTypes restType, uint xp)
|
||||
{
|
||||
uint rested_bonus = (uint)GetRestBonus(restType); // xp for each rested bonus
|
||||
|
||||
if (rested_bonus > xp) // max rested_bonus == xp or (r+x) = 200% xp
|
||||
rested_bonus = xp;
|
||||
|
||||
SetRestBonus(restType, GetRestBonus(restType) - rested_bonus);
|
||||
|
||||
Log.outDebug(LogFilter.Player, "RestMgr.GetRestBonus: Player '{0}' ({1}) gain {2} xp (+{3} Rested Bonus). Rested points={4}",
|
||||
_player.GetGUID().ToString(), _player.GetName(), xp + rested_bonus, rested_bonus, GetRestBonus(restType));
|
||||
return rested_bonus;
|
||||
}
|
||||
|
||||
public void Update(uint now)
|
||||
{
|
||||
if (RandomHelper.randChance(3) && _restTime > 0) // freeze update
|
||||
{
|
||||
long timeDiff = now - _restTime;
|
||||
if (timeDiff >= 10)
|
||||
{
|
||||
_restTime = now;
|
||||
|
||||
float bubble = 0.125f * WorldConfig.GetFloatValue(WorldCfg.RateRestIngame);
|
||||
AddRestBonus(RestTypes.XP, timeDiff * CalcExtraPerSec(RestTypes.XP, bubble));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadRestBonus(RestTypes restType, PlayerRestState state, float restBonus)
|
||||
{
|
||||
_restBonus[(int)restType] = restBonus;
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + (int)restType * 2, (uint)state);
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + (int)restType * 2 + 1, (uint)restBonus);
|
||||
}
|
||||
|
||||
public float CalcExtraPerSec(RestTypes restType, float bubble)
|
||||
{
|
||||
switch (restType)
|
||||
{
|
||||
case RestTypes.Honor:
|
||||
return (_player.GetUInt32Value(PlayerFields.HonorNextLevel)) / 72000.0f * bubble;
|
||||
case RestTypes.XP:
|
||||
return (_player.GetUInt32Value(PlayerFields.NextLevelXp)) / 72000.0f * bubble;
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetRestBonus(RestTypes restType) { return _restBonus[(int)restType]; }
|
||||
public bool HasRestFlag(RestFlag restFlag) { return (_restFlagMask & restFlag) != 0; }
|
||||
public uint GetInnTriggerId() { return _innAreaTriggerId; }
|
||||
|
||||
Player _player;
|
||||
long _restTime;
|
||||
uint _innAreaTriggerId;
|
||||
float[] _restBonus = new float[(int)RestTypes.Max];
|
||||
RestFlag _restFlagMask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class SceneMgr
|
||||
{
|
||||
public SceneMgr(Player player)
|
||||
{
|
||||
_player = player;
|
||||
_standaloneSceneInstanceID = 0;
|
||||
_isDebuggingScenes = false;
|
||||
}
|
||||
|
||||
public uint PlayScene(uint sceneId, Position position = null)
|
||||
{
|
||||
SceneTemplate sceneTemplate = Global.ObjectMgr.GetSceneTemplate(sceneId);
|
||||
return PlaySceneByTemplate(sceneTemplate, position);
|
||||
}
|
||||
|
||||
uint PlaySceneByTemplate(SceneTemplate sceneTemplate, Position position = null)
|
||||
{
|
||||
if (sceneTemplate == null)
|
||||
return 0;
|
||||
|
||||
SceneScriptPackageRecord entry = CliDB.SceneScriptPackageStorage.LookupByKey(sceneTemplate.ScenePackageId);
|
||||
if (entry == null)
|
||||
return 0;
|
||||
|
||||
// By default, take player position
|
||||
if (position == null)
|
||||
position = GetPlayer();
|
||||
|
||||
uint sceneInstanceID = GetNewStandaloneSceneInstanceID();
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugPlay, sceneInstanceID, sceneTemplate.ScenePackageId, sceneTemplate.PlaybackFlags);
|
||||
|
||||
PlayScene playScene = new PlayScene();
|
||||
playScene.SceneID = sceneTemplate.SceneId;
|
||||
playScene.PlaybackFlags = (uint)sceneTemplate.PlaybackFlags;
|
||||
playScene.SceneInstanceID = sceneInstanceID;
|
||||
playScene.SceneScriptPackageID = sceneTemplate.ScenePackageId;
|
||||
playScene.Location = position;
|
||||
playScene.TransportGUID = GetPlayer().GetTransGUID();
|
||||
|
||||
GetPlayer().SendPacket(playScene);
|
||||
|
||||
AddInstanceIdToSceneMap(sceneInstanceID, sceneTemplate);
|
||||
|
||||
Global.ScriptMgr.OnSceneStart(GetPlayer(), sceneInstanceID, sceneTemplate);
|
||||
|
||||
return sceneInstanceID;
|
||||
}
|
||||
|
||||
public uint PlaySceneByPackageId(uint sceneScriptPackageId, SceneFlags playbackflags = SceneFlags.Unk16, Position position = null)
|
||||
{
|
||||
SceneTemplate sceneTemplate = new SceneTemplate();
|
||||
sceneTemplate.SceneId = 0;
|
||||
sceneTemplate.ScenePackageId = sceneScriptPackageId;
|
||||
sceneTemplate.PlaybackFlags = playbackflags;
|
||||
sceneTemplate.ScriptId = 0;
|
||||
|
||||
return PlaySceneByTemplate(sceneTemplate, position);
|
||||
}
|
||||
|
||||
void CancelScene(uint sceneInstanceID, bool removeFromMap = true)
|
||||
{
|
||||
if (removeFromMap)
|
||||
RemoveSceneInstanceId(sceneInstanceID);
|
||||
|
||||
CancelScene cancelScene = new CancelScene();
|
||||
cancelScene.SceneInstanceID = sceneInstanceID;
|
||||
GetPlayer().SendPacket(cancelScene);
|
||||
}
|
||||
|
||||
public void OnSceneTrigger(uint sceneInstanceID, string triggerName)
|
||||
{
|
||||
if (!HasScene(sceneInstanceID))
|
||||
return;
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugTrigger, sceneInstanceID, triggerName);
|
||||
|
||||
SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID);
|
||||
Global.ScriptMgr.OnSceneTrigger(GetPlayer(), sceneInstanceID, sceneTemplate, triggerName);
|
||||
}
|
||||
|
||||
public void OnSceneCancel(uint sceneInstanceID)
|
||||
{
|
||||
if (!HasScene(sceneInstanceID))
|
||||
return;
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugCancel, sceneInstanceID);
|
||||
|
||||
SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID);
|
||||
|
||||
// Must be done before removing aura
|
||||
RemoveSceneInstanceId(sceneInstanceID);
|
||||
|
||||
if (sceneTemplate.SceneId != 0)
|
||||
RemoveAurasDueToSceneId(sceneTemplate.SceneId);
|
||||
|
||||
Global.ScriptMgr.OnSceneCancel(GetPlayer(), sceneInstanceID, sceneTemplate);
|
||||
|
||||
if (sceneTemplate.PlaybackFlags.HasAnyFlag(SceneFlags.CancelAtEnd))
|
||||
CancelScene(sceneInstanceID, false);
|
||||
}
|
||||
|
||||
public void OnSceneComplete(uint sceneInstanceID)
|
||||
{
|
||||
if (!HasScene(sceneInstanceID))
|
||||
return;
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugComplete, sceneInstanceID);
|
||||
|
||||
SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID);
|
||||
|
||||
// Must be done before removing aura
|
||||
RemoveSceneInstanceId(sceneInstanceID);
|
||||
|
||||
if (sceneTemplate.SceneId != 0)
|
||||
RemoveAurasDueToSceneId(sceneTemplate.SceneId);
|
||||
|
||||
Global.ScriptMgr.OnSceneComplete(GetPlayer(), sceneInstanceID, sceneTemplate);
|
||||
|
||||
if (sceneTemplate.PlaybackFlags.HasAnyFlag(SceneFlags.CancelAtEnd))
|
||||
CancelScene(sceneInstanceID, false);
|
||||
}
|
||||
|
||||
bool HasScene(uint sceneInstanceID, uint sceneScriptPackageId = 0)
|
||||
{
|
||||
var sceneTempalte = _scenesByInstance.LookupByKey(sceneInstanceID);
|
||||
|
||||
if (sceneTempalte != null)
|
||||
return sceneScriptPackageId == 0 || sceneScriptPackageId == sceneTempalte.ScenePackageId;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddInstanceIdToSceneMap(uint sceneInstanceID, SceneTemplate sceneTemplate)
|
||||
{
|
||||
_scenesByInstance[sceneInstanceID] = sceneTemplate;
|
||||
}
|
||||
|
||||
public void CancelSceneBySceneId(uint sceneId)
|
||||
{
|
||||
List<uint> instancesIds = new List<uint>();
|
||||
|
||||
foreach (var pair in _scenesByInstance)
|
||||
if (pair.Value.SceneId == sceneId)
|
||||
instancesIds.Add(pair.Key);
|
||||
|
||||
foreach (uint sceneInstanceID in instancesIds)
|
||||
CancelScene(sceneInstanceID);
|
||||
}
|
||||
|
||||
public void CancelSceneByPackageId(uint sceneScriptPackageId)
|
||||
{
|
||||
List<uint> instancesIds = new List<uint>();
|
||||
|
||||
foreach (var sceneTemplate in _scenesByInstance)
|
||||
if (sceneTemplate.Value.ScenePackageId == sceneScriptPackageId)
|
||||
instancesIds.Add(sceneTemplate.Key);
|
||||
|
||||
foreach (uint sceneInstanceID in instancesIds)
|
||||
CancelScene(sceneInstanceID);
|
||||
}
|
||||
|
||||
void RemoveSceneInstanceId(uint sceneInstanceID)
|
||||
{
|
||||
_scenesByInstance.Remove(sceneInstanceID);
|
||||
}
|
||||
|
||||
void RemoveAurasDueToSceneId(uint sceneId)
|
||||
{
|
||||
var scenePlayAuras = GetPlayer().GetAuraEffectsByType(AuraType.PlayScene);
|
||||
foreach (var scenePlayAura in scenePlayAuras)
|
||||
{
|
||||
if (scenePlayAura.GetMiscValue() == sceneId)
|
||||
{
|
||||
GetPlayer().RemoveAura(scenePlayAura.GetBase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SceneTemplate GetSceneTemplateFromInstanceId(uint sceneInstanceID)
|
||||
{
|
||||
return _scenesByInstance.LookupByKey(sceneInstanceID);
|
||||
}
|
||||
|
||||
public uint GetActiveSceneCount(uint sceneScriptPackageId = 0)
|
||||
{
|
||||
uint activeSceneCount = 0;
|
||||
|
||||
foreach (var sceneTemplate in _scenesByInstance.Values)
|
||||
if (sceneScriptPackageId == 0 || sceneTemplate.ScenePackageId == sceneScriptPackageId)
|
||||
++activeSceneCount;
|
||||
|
||||
return activeSceneCount;
|
||||
}
|
||||
|
||||
Player GetPlayer() { return _player; }
|
||||
|
||||
void RecreateScene(uint sceneScriptPackageId, SceneFlags playbackflags = SceneFlags.Unk16, Position position = null)
|
||||
{
|
||||
CancelSceneByPackageId(sceneScriptPackageId);
|
||||
PlaySceneByPackageId(sceneScriptPackageId, playbackflags, position);
|
||||
}
|
||||
|
||||
public Dictionary<uint, SceneTemplate> GetSceneTemplateByInstanceMap() { return _scenesByInstance; }
|
||||
|
||||
uint GetNewStandaloneSceneInstanceID() { return ++_standaloneSceneInstanceID; }
|
||||
|
||||
public void ToggleDebugSceneMode() { _isDebuggingScenes = !_isDebuggingScenes; }
|
||||
public bool IsInDebugSceneMode() { return _isDebuggingScenes; }
|
||||
|
||||
Player _player;
|
||||
Dictionary<uint, SceneTemplate> _scenesByInstance = new Dictionary<uint, SceneTemplate>();
|
||||
uint _standaloneSceneInstanceID;
|
||||
bool _isDebuggingScenes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.Network;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class SocialManager : Singleton<SocialManager>
|
||||
{
|
||||
SocialManager() { }
|
||||
|
||||
public const int FriendLimit = 50;
|
||||
public const int IgnoreLimit = 50;
|
||||
|
||||
public void GetFriendInfo(Player player, ObjectGuid friendGUID, FriendInfo friendInfo)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
friendInfo.Status = FriendStatus.Offline;
|
||||
friendInfo.Area = 0;
|
||||
friendInfo.Level = 0;
|
||||
friendInfo.Class = 0;
|
||||
|
||||
Player target = Global.ObjAccessor.FindPlayer(friendGUID);
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
var playerFriendInfo = player.GetSocial()._playerSocialMap.LookupByKey(friendGUID);
|
||||
if (playerFriendInfo != null)
|
||||
friendInfo.Note = playerFriendInfo.Note;
|
||||
|
||||
// PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters
|
||||
// MODERATOR, GAME MASTER, ADMINISTRATOR can see all
|
||||
|
||||
if (!player.GetSession().HasPermission(RBACPermissions.WhoSeeAllSecLevels) &&
|
||||
target.GetSession().GetSecurity() > (AccountTypes)WorldConfig.GetIntValue(WorldCfg.GmLevelInWhoList))
|
||||
return;
|
||||
|
||||
// player can see member of other team only if CONFIG_ALLOW_TWO_SIDE_WHO_LIST
|
||||
if (target.GetTeam() != player.GetTeam() && !player.GetSession().HasPermission(RBACPermissions.TwoSideWhoList))
|
||||
return;
|
||||
|
||||
if (target.IsVisibleGloballyFor(player))
|
||||
{
|
||||
if (target.isDND())
|
||||
friendInfo.Status = FriendStatus.DND;
|
||||
else if (target.isAFK())
|
||||
friendInfo.Status = FriendStatus.AFK;
|
||||
else
|
||||
friendInfo.Status = FriendStatus.Online;
|
||||
|
||||
friendInfo.Area = target.GetZoneId();
|
||||
friendInfo.Level = target.getLevel();
|
||||
friendInfo.Class = target.GetClass();
|
||||
}
|
||||
}
|
||||
|
||||
public void SendFriendStatus(Player player, FriendsResult result, ObjectGuid friendGuid, bool broadcast = false)
|
||||
{
|
||||
FriendInfo fi = new FriendInfo();
|
||||
GetFriendInfo(player, friendGuid, fi);
|
||||
|
||||
FriendStatusPkt friendStatus = new FriendStatusPkt();
|
||||
friendStatus.Initialize(friendGuid, result, fi);
|
||||
|
||||
if (broadcast)
|
||||
BroadcastToFriendListers(player, friendStatus);
|
||||
else
|
||||
player.SendPacket(friendStatus);
|
||||
}
|
||||
|
||||
void BroadcastToFriendListers(Player player, ServerPacket packet)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
AccountTypes gmSecLevel = (AccountTypes)WorldConfig.GetIntValue(WorldCfg.GmLevelInWhoList);
|
||||
foreach (var pair in _socialMap)
|
||||
{
|
||||
var info = pair.Value._playerSocialMap.LookupByKey(player.GetGUID());
|
||||
if (info != null && info.Flags.HasAnyFlag(SocialFlag.Friend))
|
||||
{
|
||||
Player target = Global.ObjAccessor.FindPlayer(pair.Key);
|
||||
if (!target || !target.IsInWorld)
|
||||
continue;
|
||||
|
||||
WorldSession session = target.GetSession();
|
||||
if (!session.HasPermission(RBACPermissions.WhoSeeAllSecLevels) && player.GetSession().GetSecurity() > gmSecLevel)
|
||||
continue;
|
||||
|
||||
if (target.GetTeam() != player.GetTeam() && !session.HasPermission(RBACPermissions.TwoSideWhoList))
|
||||
continue;
|
||||
|
||||
if (player.IsVisibleGloballyFor(target))
|
||||
session.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerSocial LoadFromDB(SQLResult result, ObjectGuid guid)
|
||||
{
|
||||
PlayerSocial social = new PlayerSocial();
|
||||
social.SetPlayerGUID(guid);
|
||||
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
ObjectGuid friendGuid = ObjectGuid.Create(HighGuid.Player, result.Read<ulong>(0));
|
||||
ObjectGuid friendAccountGuid = ObjectGuid.Create(HighGuid.WowAccount, result.Read<uint>(1));
|
||||
SocialFlag flags = (SocialFlag)result.Read<byte>(2);
|
||||
|
||||
social._playerSocialMap[friendGuid] = new FriendInfo(friendAccountGuid, flags, result.Read<string>(3));
|
||||
}
|
||||
while (result.NextRow());
|
||||
}
|
||||
|
||||
_socialMap[guid] = social;
|
||||
|
||||
return social;
|
||||
}
|
||||
|
||||
public void RemovePlayerSocial(ObjectGuid guid) { _socialMap.Remove(guid); }
|
||||
|
||||
Dictionary<ObjectGuid, PlayerSocial> _socialMap = new Dictionary<ObjectGuid, PlayerSocial>();
|
||||
}
|
||||
|
||||
public class PlayerSocial
|
||||
{
|
||||
uint GetNumberOfSocialsWithFlag(SocialFlag flag)
|
||||
{
|
||||
uint counter = 0;
|
||||
foreach (var pair in _playerSocialMap)
|
||||
if (pair.Value.Flags.HasAnyFlag(flag))
|
||||
++counter;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
public bool AddToSocialList(ObjectGuid friendGuid, SocialFlag flag)
|
||||
{
|
||||
// check client limits
|
||||
if (GetNumberOfSocialsWithFlag(flag) >= (((flag & SocialFlag.Friend) != 0) ? SocialManager.FriendLimit : SocialManager.IgnoreLimit))
|
||||
return false;
|
||||
|
||||
var friendInfo = _playerSocialMap.LookupByKey(friendGuid);
|
||||
if (friendInfo != null)
|
||||
{
|
||||
friendInfo.Flags |= flag;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_FLAGS);
|
||||
stmt.AddValue(0, friendInfo.Flags);
|
||||
stmt.AddValue(1, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(2, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
FriendInfo fi = new FriendInfo();
|
||||
fi.Flags |= flag;
|
||||
_playerSocialMap[friendGuid] = fi;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_SOCIAL);
|
||||
stmt.AddValue(0, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(1, friendGuid.GetCounter());
|
||||
stmt.AddValue(2, flag);
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveFromSocialList(ObjectGuid friendGuid, SocialFlag flag)
|
||||
{
|
||||
var friendInfo = _playerSocialMap.LookupByKey(friendGuid);
|
||||
if (friendInfo == null) // not exist
|
||||
return;
|
||||
|
||||
friendInfo.Flags &= ~flag;
|
||||
|
||||
if (friendInfo.Flags == 0)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_SOCIAL);
|
||||
stmt.AddValue(0, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(1, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
|
||||
_playerSocialMap.Remove(friendGuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_FLAGS);
|
||||
stmt.AddValue(0, friendInfo.Flags);
|
||||
stmt.AddValue(1, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(2, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFriendNote(ObjectGuid friendGuid, string note)
|
||||
{
|
||||
if (!_playerSocialMap.ContainsKey(friendGuid)) // not exist
|
||||
return;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_NOTE);
|
||||
stmt.AddValue(0, note);
|
||||
stmt.AddValue(1, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(2, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
|
||||
_playerSocialMap[friendGuid].Note = note;
|
||||
}
|
||||
|
||||
public void SendSocialList(Player player, SocialFlag flags)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
ContactList contactList = new ContactList();
|
||||
contactList.Flags = flags;
|
||||
|
||||
foreach (var v in _playerSocialMap)
|
||||
{
|
||||
if (!v.Value.Flags.HasAnyFlag(flags))
|
||||
continue;
|
||||
|
||||
Global.SocialMgr.GetFriendInfo(player, v.Key, v.Value);
|
||||
|
||||
contactList.Contacts.Add(new ContactInfo(v.Key, v.Value));
|
||||
|
||||
// client's friends list and ignore list limit
|
||||
if (contactList.Contacts.Count >= (((flags & SocialFlag.Friend) != 0) ? SocialManager.FriendLimit : SocialManager.IgnoreLimit))
|
||||
break;
|
||||
}
|
||||
|
||||
player.SendPacket(contactList);
|
||||
}
|
||||
|
||||
bool _HasContact(ObjectGuid guid, SocialFlag flags)
|
||||
{
|
||||
var friendInfo = _playerSocialMap.LookupByKey(guid);
|
||||
if (friendInfo != null)
|
||||
return friendInfo.Flags.HasAnyFlag(flags);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasFriend(ObjectGuid friendGuid)
|
||||
{
|
||||
return _HasContact(friendGuid, SocialFlag.Friend);
|
||||
}
|
||||
|
||||
public bool HasIgnore(ObjectGuid ignoreGuid)
|
||||
{
|
||||
return _HasContact(ignoreGuid, SocialFlag.Ignored);
|
||||
}
|
||||
|
||||
ObjectGuid GetPlayerGUID() { return m_playerGUID; }
|
||||
|
||||
public void SetPlayerGUID(ObjectGuid guid) { m_playerGUID = guid; }
|
||||
|
||||
public Dictionary<ObjectGuid, FriendInfo> _playerSocialMap = new Dictionary<ObjectGuid, FriendInfo>();
|
||||
ObjectGuid m_playerGUID;
|
||||
}
|
||||
|
||||
public class FriendInfo
|
||||
{
|
||||
public FriendInfo()
|
||||
{
|
||||
Status = FriendStatus.Offline;
|
||||
Note = "";
|
||||
}
|
||||
|
||||
public FriendInfo(ObjectGuid accountGuid, SocialFlag flags, string note)
|
||||
{
|
||||
WowAccountGuid = accountGuid;
|
||||
Status = FriendStatus.Offline;
|
||||
Flags = flags;
|
||||
Note = note;
|
||||
}
|
||||
|
||||
public ObjectGuid WowAccountGuid;
|
||||
public FriendStatus Status;
|
||||
public SocialFlag Flags;
|
||||
public uint Area;
|
||||
public uint Level;
|
||||
public Class Class;
|
||||
public string Note;
|
||||
}
|
||||
|
||||
public enum FriendStatus
|
||||
{
|
||||
Offline = 0x00,
|
||||
Online = 0x01,
|
||||
AFK = 0x02,
|
||||
DND = 0x04,
|
||||
RAF = 0x08
|
||||
}
|
||||
|
||||
public enum SocialFlag
|
||||
{
|
||||
Friend = 0x01,
|
||||
Ignored = 0x02,
|
||||
Muted = 0x04, // guessed
|
||||
Unk = 0x08, // Unknown - does not appear to be RaF
|
||||
All = Friend | Ignored | Muted
|
||||
}
|
||||
|
||||
public enum FriendsResult
|
||||
{
|
||||
DbError = 0x00,
|
||||
ListFull = 0x01,
|
||||
Online = 0x02,
|
||||
Offline = 0x03,
|
||||
NotFound = 0x04,
|
||||
Removed = 0x05,
|
||||
AddedOnline = 0x06,
|
||||
AddedOffline = 0x07,
|
||||
Already = 0x08,
|
||||
Self = 0x09,
|
||||
Enemy = 0x0a,
|
||||
IgnoreFull = 0x0b,
|
||||
IgnoreSelf = 0x0c,
|
||||
IgnoreNotFound = 0x0d,
|
||||
IgnoreAlready = 0x0e,
|
||||
IgnoreAdded = 0x0f,
|
||||
IgnoreRemoved = 0x10,
|
||||
IgnoreAmbiguous = 0x11, // That Name Is Ambiguous, Type More Of The Player'S Server Name
|
||||
MuteFull = 0x12,
|
||||
MuteSelf = 0x13,
|
||||
MuteNotFound = 0x14,
|
||||
MuteAlready = 0x15,
|
||||
MuteAdded = 0x16,
|
||||
MuteRemoved = 0x17,
|
||||
MuteAmbiguous = 0x18, // That Name Is Ambiguous, Type More Of The Player'S Server Name
|
||||
Unk1 = 0x19, // no message at client
|
||||
Unk2 = 0x1A,
|
||||
Unk3 = 0x1B,
|
||||
Unknown = 0x1C // Unknown friend response from server
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Network.Packets;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class TradeData
|
||||
{
|
||||
public TradeData(Player player, Player trader)
|
||||
{
|
||||
m_player = player;
|
||||
m_trader = trader;
|
||||
m_clientStateIndex = 1;
|
||||
m_serverStateIndex = 1;
|
||||
}
|
||||
|
||||
public TradeData GetTraderData()
|
||||
{
|
||||
return m_trader.GetTradeData();
|
||||
}
|
||||
|
||||
public Item GetItem(TradeSlots slot)
|
||||
{
|
||||
return !m_items[(int)slot].IsEmpty() ? m_player.GetItemByGuid(m_items[(int)slot]) : null;
|
||||
}
|
||||
|
||||
public bool HasItem(ObjectGuid itemGuid)
|
||||
{
|
||||
for (byte i = 0; i < (byte)TradeSlots.Count; ++i)
|
||||
if (m_items[i] == itemGuid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public TradeSlots GetTradeSlotForItem(ObjectGuid itemGuid)
|
||||
{
|
||||
for (TradeSlots i = 0; i < TradeSlots.Count; ++i)
|
||||
if (m_items[(int)i] == itemGuid)
|
||||
return i;
|
||||
|
||||
return TradeSlots.Invalid;
|
||||
}
|
||||
|
||||
public Item GetSpellCastItem()
|
||||
{
|
||||
return !m_spellCastItem.IsEmpty() ? m_player.GetItemByGuid(m_spellCastItem) : null;
|
||||
}
|
||||
|
||||
public void SetItem(TradeSlots slot, Item item, bool update = false)
|
||||
{
|
||||
ObjectGuid itemGuid = item ? item.GetGUID() : ObjectGuid.Empty;
|
||||
|
||||
if (m_items[(int)slot] == itemGuid && !update)
|
||||
return;
|
||||
|
||||
m_items[(int)slot] = itemGuid;
|
||||
|
||||
SetAccepted(false);
|
||||
GetTraderData().SetAccepted(false);
|
||||
|
||||
UpdateServerStateIndex();
|
||||
|
||||
Update();
|
||||
|
||||
// need remove possible trader spell applied to changed item
|
||||
if (slot == TradeSlots.NonTraded)
|
||||
GetTraderData().SetSpell(0);
|
||||
|
||||
// need remove possible player spell applied (possible move reagent)
|
||||
SetSpell(0);
|
||||
}
|
||||
|
||||
public uint GetSpell() { return m_spell; }
|
||||
|
||||
public void SetSpell(uint spell_id, Item castItem = null)
|
||||
{
|
||||
ObjectGuid itemGuid = castItem ? castItem.GetGUID() : ObjectGuid.Empty;
|
||||
|
||||
if (m_spell == spell_id && m_spellCastItem == itemGuid)
|
||||
return;
|
||||
|
||||
m_spell = spell_id;
|
||||
m_spellCastItem = itemGuid;
|
||||
|
||||
SetAccepted(false);
|
||||
GetTraderData().SetAccepted(false);
|
||||
|
||||
UpdateServerStateIndex();
|
||||
|
||||
Update(true); // send spell info to item owner
|
||||
Update(false); // send spell info to caster self
|
||||
}
|
||||
|
||||
public void SetMoney(ulong money)
|
||||
{
|
||||
if (m_money == money)
|
||||
return;
|
||||
|
||||
if (!m_player.HasEnoughMoney(money))
|
||||
{
|
||||
TradeStatusPkt info = new TradeStatusPkt();
|
||||
info.Status = TradeStatus.Failed;
|
||||
info.BagResult = InventoryResult.NotEnoughMoney;
|
||||
m_player.GetSession().SendTradeStatus(info);
|
||||
return;
|
||||
}
|
||||
m_money = money;
|
||||
|
||||
SetAccepted(false);
|
||||
GetTraderData().SetAccepted(false);
|
||||
|
||||
UpdateServerStateIndex();
|
||||
|
||||
Update(true);
|
||||
}
|
||||
|
||||
void Update(bool forTarget = true)
|
||||
{
|
||||
if (forTarget)
|
||||
m_trader.GetSession().SendUpdateTrade(true); // player state for trader
|
||||
else
|
||||
m_player.GetSession().SendUpdateTrade(false); // player state for player
|
||||
}
|
||||
|
||||
public void SetAccepted(bool state, bool crosssend = false)
|
||||
{
|
||||
m_accepted = state;
|
||||
|
||||
if (!state)
|
||||
{
|
||||
TradeStatusPkt info = new TradeStatusPkt();
|
||||
info.Status = TradeStatus.Unaccepted;
|
||||
if (crosssend)
|
||||
m_trader.GetSession().SendTradeStatus(info);
|
||||
else
|
||||
m_player.GetSession().SendTradeStatus(info);
|
||||
}
|
||||
}
|
||||
|
||||
public Player GetTrader() { return m_trader; }
|
||||
|
||||
public bool HasSpellCastItem() { return !m_spellCastItem.IsEmpty(); }
|
||||
|
||||
public ulong GetMoney() { return m_money; }
|
||||
|
||||
public bool IsAccepted() { return m_accepted; }
|
||||
|
||||
public bool IsInAcceptProcess() { return m_acceptProccess; }
|
||||
|
||||
public void SetInAcceptProcess(bool state) { m_acceptProccess = state; }
|
||||
|
||||
public uint GetClientStateIndex() { return m_clientStateIndex; }
|
||||
public void UpdateClientStateIndex() { ++m_clientStateIndex; }
|
||||
|
||||
public uint GetServerStateIndex() { return m_serverStateIndex; }
|
||||
public void UpdateServerStateIndex() { m_serverStateIndex = RandomHelper.Rand32(); }
|
||||
|
||||
Player m_player;
|
||||
Player m_trader;
|
||||
bool m_accepted;
|
||||
bool m_acceptProccess;
|
||||
ulong m_money;
|
||||
uint m_spell;
|
||||
ObjectGuid m_spellCastItem;
|
||||
ObjectGuid[] m_items = new ObjectGuid[(int)TradeSlots.Count];
|
||||
uint m_clientStateIndex;
|
||||
uint m_serverStateIndex;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// The IndexMinPriorityQueue class represents an indexed priority queue of generic keys.
|
||||
/// </summary>
|
||||
/// <seealso href="http://algs4.cs.princeton.edu/24pq/IndexMinPQ.java.html">IndexMinPQ class from Princeton University's Java Algorithms</seealso>
|
||||
/// <typeparam name="T">Type must implement IComparable interface</typeparam>
|
||||
public class IndexMinPriorityQueue<T> where T : IComparable<T>
|
||||
{
|
||||
private readonly T[] _keys;
|
||||
private readonly int _maxSize;
|
||||
private readonly int[] _pq;
|
||||
private readonly int[] _qp;
|
||||
/// <summary>
|
||||
/// Constructs an empty indexed priority queue with indices between 0 and the specified maxSize - 1
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The maximum size of the indexed priority queue</param>
|
||||
public IndexMinPriorityQueue(int maxSize)
|
||||
{
|
||||
_maxSize = maxSize;
|
||||
Size = 0;
|
||||
_keys = new T[_maxSize + 1];
|
||||
_pq = new int[_maxSize + 1];
|
||||
_qp = new int[_maxSize + 1];
|
||||
for (int i = 0; i < _maxSize; i++)
|
||||
{
|
||||
_qp[i] = -1;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The number of keys on this indexed priority queue
|
||||
/// </summary>
|
||||
public int Size { get; private set; }
|
||||
/// <summary>
|
||||
/// Is the indexed priority queue empty?
|
||||
/// </summary>
|
||||
/// <returns>True if the indexed priority queue is empty, false otherwise</returns>
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return Size == 0;
|
||||
}
|
||||
/// <summary>
|
||||
/// Is the specified parameter i an index on the priority queue?
|
||||
/// </summary>
|
||||
/// <param name="i">An index to check for on the priority queue</param>
|
||||
/// <returns>True if the specified parameter i is an index on the priority queue, false otherwise</returns>
|
||||
public bool Contains(int i)
|
||||
{
|
||||
return _qp[i] != -1;
|
||||
}
|
||||
/// <summary>
|
||||
/// Associates the specified key with the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">The index to associate the key with</param>
|
||||
/// <param name="key">The key to associate with the index</param>
|
||||
public void Insert(int index, T key)
|
||||
{
|
||||
Size++;
|
||||
_qp[index] = Size;
|
||||
_pq[Size] = index;
|
||||
_keys[index] = key;
|
||||
Swim(Size);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an index associated with a minimum key
|
||||
/// </summary>
|
||||
/// <returns>An index associated with a minimum key</returns>
|
||||
public int MinIndex()
|
||||
{
|
||||
return _pq[1];
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a minimum key
|
||||
/// </summary>
|
||||
/// <returns>A minimum key</returns>
|
||||
public T MinKey()
|
||||
{
|
||||
return _keys[_pq[1]];
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a minimum key and returns its associated index
|
||||
/// </summary>
|
||||
/// <returns>An index associated with a minimum key that was removed</returns>
|
||||
public int DeleteMin()
|
||||
{
|
||||
int min = _pq[1];
|
||||
Exchange(1, Size--);
|
||||
Sink(1);
|
||||
_qp[min] = -1;
|
||||
_keys[_pq[Size + 1]] = default(T);
|
||||
_pq[Size + 1] = -1;
|
||||
return min;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the key associated with the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the key to return</param>
|
||||
/// <returns>The key associated with the specified index</returns>
|
||||
public T KeyAt(int index)
|
||||
{
|
||||
return _keys[index];
|
||||
}
|
||||
/// <summary>
|
||||
/// Change the key associated with the specified index to the specified value
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the key to change</param>
|
||||
/// <param name="key">Change the key associated with the specified index to this key</param>
|
||||
public void ChangeKey(int index, T key)
|
||||
{
|
||||
_keys[index] = key;
|
||||
Swim(_qp[index]);
|
||||
Sink(_qp[index]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Decrease the key associated with the specified index to the specified value
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the key to decrease</param>
|
||||
/// <param name="key">Decrease the key associated with the specified index to this key</param>
|
||||
public void DecreaseKey(int index, T key)
|
||||
{
|
||||
_keys[index] = key;
|
||||
Swim(_qp[index]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Increase the key associated with the specified index to the specified value
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the key to increase</param>
|
||||
/// <param name="key">Increase the key associated with the specified index to this key</param>
|
||||
public void IncreaseKey(int index, T key)
|
||||
{
|
||||
_keys[index] = key;
|
||||
Sink(_qp[index]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Remove the key associated with the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the key to remove</param>
|
||||
public void Delete(int index)
|
||||
{
|
||||
int i = _qp[index];
|
||||
Exchange(i, Size--);
|
||||
Swim(i);
|
||||
Sink(i);
|
||||
_keys[index] = default(T);
|
||||
_qp[index] = -1;
|
||||
}
|
||||
private bool Greater(int i, int j)
|
||||
{
|
||||
return _keys[_pq[i]].CompareTo(_keys[_pq[j]]) > 0;
|
||||
}
|
||||
private void Exchange(int i, int j)
|
||||
{
|
||||
int swap = _pq[i];
|
||||
_pq[i] = _pq[j];
|
||||
_pq[j] = swap;
|
||||
_qp[_pq[i]] = i;
|
||||
_qp[_pq[j]] = j;
|
||||
}
|
||||
private void Swim(int k)
|
||||
{
|
||||
while (k > 1 && Greater(k / 2, k))
|
||||
{
|
||||
Exchange(k, k / 2);
|
||||
k = k / 2;
|
||||
}
|
||||
}
|
||||
private void Sink(int k)
|
||||
{
|
||||
while (2 * k <= Size)
|
||||
{
|
||||
int j = 2 * k;
|
||||
if (j < Size && Greater(j, j + 1))
|
||||
{
|
||||
j++;
|
||||
}
|
||||
if (!Greater(k, j))
|
||||
{
|
||||
break;
|
||||
}
|
||||
Exchange(k, j);
|
||||
k = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The EdgeWeightedDigrpah class represents an edge-weighted directed graph of vertices named 0 through V-1, where each directed edge
|
||||
/// is of type DirectedEdge and has real-valued weight.
|
||||
/// </summary>
|
||||
/// <seealso href="http://algs4.cs.princeton.edu/44sp/EdgeWeightedDigraph.java.html">EdgeWeightedDigraph class from Princeton University's Java Algorithms</seealso>
|
||||
public class EdgeWeightedDigraph
|
||||
{
|
||||
private readonly LinkedList<DirectedEdge>[] _adjacent;
|
||||
/// <summary>
|
||||
/// Constructs an empty edge-weighted digraph with the specified number of vertices and 0 edges
|
||||
/// </summary>
|
||||
/// <param name="vertices">Number of vertices in the Graph</param>
|
||||
public EdgeWeightedDigraph(int vertices)
|
||||
{
|
||||
NumberOfVertices = vertices;
|
||||
NumberOfEdges = 0;
|
||||
_adjacent = new LinkedList<DirectedEdge>[NumberOfVertices];
|
||||
for (int v = 0; v < NumberOfVertices; v++)
|
||||
{
|
||||
_adjacent[v] = new LinkedList<DirectedEdge>();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The number of vertices in the edge-weighted digraph
|
||||
/// </summary>
|
||||
public int NumberOfVertices { get; private set; }
|
||||
/// <summary>
|
||||
/// The number of edges in the edge-weighted digraph
|
||||
/// </summary>
|
||||
public int NumberOfEdges { get; private set; }
|
||||
/// <summary>
|
||||
/// Adds the specified directed edge to the edge-weighted digraph
|
||||
/// </summary>
|
||||
/// <param name="edge">The DirectedEdge to add</param>
|
||||
/// <exception cref="ArgumentNullException">DirectedEdge cannot be null</exception>
|
||||
public void AddEdge(DirectedEdge edge)
|
||||
{
|
||||
if (edge == null)
|
||||
{
|
||||
throw new ArgumentNullException("edge", "DirectedEdge cannot be null");
|
||||
}
|
||||
|
||||
_adjacent[edge.From].AddLast(edge);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of the DirectedEdges incident from the specified vertex
|
||||
/// </summary>
|
||||
/// <param name="vertex">The vertex to find incident DirectedEdges from</param>
|
||||
/// <returns>IEnumerable of the DirectedEdges incident from the specified vertex</returns>
|
||||
public IEnumerable<DirectedEdge> Adjacent(int vertex)
|
||||
{
|
||||
return _adjacent[vertex];
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of all directed edges in the edge-weighted digraph
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable of of all directed edges in the edge-weighted digraph</returns>
|
||||
public IEnumerable<DirectedEdge> Edges()
|
||||
{
|
||||
for (int v = 0; v < NumberOfVertices; v++)
|
||||
{
|
||||
foreach (DirectedEdge edge in _adjacent[v])
|
||||
{
|
||||
yield return edge;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the number of directed edges incident from the specified vertex
|
||||
/// This is known as the outdegree of the vertex
|
||||
/// </summary>
|
||||
/// <param name="vertex">The vertex to find find the outdegree of</param>
|
||||
/// <returns>The number of directed edges incident from the specified vertex</returns>
|
||||
public int OutDegree(int vertex)
|
||||
{
|
||||
return _adjacent[vertex].Count;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current edge-weighted digraph
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current edge-weighted digraph
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var formattedString = new StringBuilder();
|
||||
formattedString.AppendFormat("{0} vertices, {1} edges {2}", NumberOfVertices, NumberOfEdges, Environment.NewLine);
|
||||
for (int v = 0; v < NumberOfVertices; v++)
|
||||
{
|
||||
formattedString.AppendFormat("{0}: ", v);
|
||||
foreach (DirectedEdge edge in _adjacent[v])
|
||||
{
|
||||
formattedString.AppendFormat("{0} ", edge.To);
|
||||
}
|
||||
formattedString.AppendLine();
|
||||
}
|
||||
return formattedString.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DirectedEdge class represents a weighted edge in an edge-weighted directed graph.
|
||||
/// </summary>
|
||||
/// <seealso href="http://algs4.cs.princeton.edu/44sp/DirectedEdge.java.html">DirectedEdge class from Princeton University's Java Algorithms</seealso>
|
||||
public class DirectedEdge
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a directed edge from one specified vertex to another with the given weight
|
||||
/// </summary>
|
||||
/// <param name="from">The start vertex</param>
|
||||
/// <param name="to">The destination vertex</param>
|
||||
/// <param name="weight">The weight of the DirectedEdge</param>
|
||||
public DirectedEdge(uint from, uint to, double weight)
|
||||
{
|
||||
From = from;
|
||||
To = to;
|
||||
Weight = weight;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the destination vertex of the DirectedEdge
|
||||
/// </summary>
|
||||
public uint From { get; private set; }
|
||||
/// <summary>
|
||||
/// Returns the start vertex of the DirectedEdge
|
||||
/// </summary>
|
||||
public uint To { get; private set; }
|
||||
/// <summary>
|
||||
/// Returns the weight of the DirectedEdge
|
||||
/// </summary>
|
||||
public double Weight { get; private set; }
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current DirectedEdge
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current DirectedEdge
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("From: {0}, To: {1}, Weight: {2}", From, To, Weight);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DijkstraShortestPath class represents a data type for solving the single-source shortest paths problem
|
||||
/// in edge-weighted digraphs where the edge weights are non-negative
|
||||
/// </summary>
|
||||
/// <seealso href="http://algs4.cs.princeton.edu/44sp/DijkstraSP.java.html">DijkstraSP class from Princeton University's Java Algorithms</seealso>
|
||||
public class DijkstraShortestPath
|
||||
{
|
||||
private readonly double[] _distanceTo;
|
||||
private readonly DirectedEdge[] _edgeTo;
|
||||
private readonly IndexMinPriorityQueue<double> _priorityQueue;
|
||||
/// <summary>
|
||||
/// Computes a shortest paths tree from the specified sourceVertex to every other vertex in the edge-weighted directed graph
|
||||
/// </summary>
|
||||
/// <param name="graph">The edge-weighted directed graph</param>
|
||||
/// <param name="sourceVertex">The source vertex to compute the shortest paths tree from</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws an ArgumentOutOfRangeException if an edge weight is negative</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if EdgeWeightedDigraph is null</exception>
|
||||
public DijkstraShortestPath(EdgeWeightedDigraph graph, int sourceVertex)
|
||||
{
|
||||
if (graph == null)
|
||||
{
|
||||
throw new ArgumentNullException("graph", "EdgeWeightedDigraph cannot be null");
|
||||
}
|
||||
|
||||
foreach (DirectedEdge edge in graph.Edges())
|
||||
{
|
||||
if (edge.Weight < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(string.Format("Edge: '{0}' has negative weight", edge));
|
||||
}
|
||||
}
|
||||
|
||||
_distanceTo = new double[graph.NumberOfVertices];
|
||||
_edgeTo = new DirectedEdge[graph.NumberOfVertices];
|
||||
for (int v = 0; v < graph.NumberOfVertices; v++)
|
||||
{
|
||||
_distanceTo[v] = double.PositiveInfinity;
|
||||
}
|
||||
_distanceTo[sourceVertex] = 0.0;
|
||||
|
||||
_priorityQueue = new IndexMinPriorityQueue<double>(graph.NumberOfVertices);
|
||||
_priorityQueue.Insert(sourceVertex, _distanceTo[sourceVertex]);
|
||||
while (!_priorityQueue.IsEmpty())
|
||||
{
|
||||
int v = _priorityQueue.DeleteMin();
|
||||
foreach (DirectedEdge edge in graph.Adjacent(v))
|
||||
{
|
||||
Relax(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void Relax(DirectedEdge edge)
|
||||
{
|
||||
uint v = edge.From;
|
||||
uint w = edge.To;
|
||||
if (_distanceTo[w] > _distanceTo[v] + edge.Weight)
|
||||
{
|
||||
_distanceTo[w] = _distanceTo[v] + edge.Weight;
|
||||
_edgeTo[w] = edge;
|
||||
if (_priorityQueue.Contains((int)w))
|
||||
{
|
||||
_priorityQueue.DecreaseKey((int)w, _distanceTo[w]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_priorityQueue.Insert((int)w, _distanceTo[w]);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the length of a shortest path from the sourceVertex to the specified destinationVertex
|
||||
/// </summary>
|
||||
/// <param name="destinationVertex">The destination vertex to find a shortest path to</param>
|
||||
/// <returns>The length of a shortest path from the sourceVertex to the specified destinationVertex or double.PositiveInfinity if no such path exists</returns>
|
||||
public double DistanceTo(int destinationVertex)
|
||||
{
|
||||
return _distanceTo[destinationVertex];
|
||||
}
|
||||
/// <summary>
|
||||
/// Is there a path from the sourceVertex to the specified destinationVertex?
|
||||
/// </summary>
|
||||
/// <param name="destinationVertex">The destination vertex to see if there is a path to</param>
|
||||
/// <returns>True if there is a path from the sourceVertex to the specified destinationVertex, false otherwise</returns>
|
||||
public bool HasPathTo(int destinationVertex)
|
||||
{
|
||||
return _distanceTo[destinationVertex] < double.PositiveInfinity;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of DirectedEdges representing a shortest path from the sourceVertex to the specified destinationVertex
|
||||
/// </summary>
|
||||
/// <param name="destinationVertex">The destination vertex to find a shortest path to</param>
|
||||
/// <returns>IEnumerable of DirectedEdges representing a shortest path from the sourceVertex to the specified destinationVertex</returns>
|
||||
public IEnumerable<DirectedEdge> PathTo(int destinationVertex)
|
||||
{
|
||||
if (!HasPathTo(destinationVertex))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var path = new Stack<DirectedEdge>();
|
||||
for (DirectedEdge edge = _edgeTo[destinationVertex]; edge != null; edge = _edgeTo[edge.From])
|
||||
{
|
||||
path.Push(edge);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
// TODO: This method should be private and should be called from the bottom of the constructor
|
||||
/// <summary>
|
||||
/// check optimality conditions:
|
||||
/// </summary>
|
||||
/// <param name="graph">The edge-weighted directed graph</param>
|
||||
/// <param name="sourceVertex">The source vertex to check optimality conditions from</param>
|
||||
/// <returns>True if all optimality conditions are met, false otherwise</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown on null EdgeWeightedDigraph</exception>
|
||||
public bool Check(EdgeWeightedDigraph graph, int sourceVertex)
|
||||
{
|
||||
if (graph == null)
|
||||
{
|
||||
throw new ArgumentNullException("graph", "EdgeWeightedDigraph cannot be null");
|
||||
}
|
||||
|
||||
if (_distanceTo[sourceVertex] != 0.0 || _edgeTo[sourceVertex] != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int v = 0; v < graph.NumberOfVertices; v++)
|
||||
{
|
||||
if (v == sourceVertex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_edgeTo[v] == null && _distanceTo[v] != double.PositiveInfinity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int v = 0; v < graph.NumberOfVertices; v++)
|
||||
{
|
||||
foreach (DirectedEdge edge in graph.Adjacent(v))
|
||||
{
|
||||
uint w = edge.To;
|
||||
if (_distanceTo[v] + edge.Weight < _distanceTo[w])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int w = 0; w < graph.NumberOfVertices; w++)
|
||||
{
|
||||
if (_edgeTo[w] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
DirectedEdge edge = _edgeTo[w];
|
||||
uint v = edge.From;
|
||||
if (w != edge.To)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_distanceTo[v] + edge.Weight != _distanceTo[w])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.GameMath;
|
||||
using Game.DataStorage;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class TaxiPathGraph : Singleton<TaxiPathGraph>
|
||||
{
|
||||
TaxiPathGraph() { }
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (GetVertexCount() > 0)
|
||||
return;
|
||||
|
||||
List<Tuple<Tuple<uint, uint>, uint>> edges = new List<Tuple<Tuple<uint, uint>, uint>>();
|
||||
|
||||
// Initialize here
|
||||
foreach (TaxiPathRecord path in CliDB.TaxiPathStorage.Values)
|
||||
{
|
||||
TaxiNodesRecord from = CliDB.TaxiNodesStorage.LookupByKey(path.From);
|
||||
TaxiNodesRecord to = CliDB.TaxiNodesStorage.LookupByKey(path.To);
|
||||
if (from != null && to != null && from.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde) && to.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde))
|
||||
AddVerticeAndEdgeFromNodeInfo(from, to, path.Id, edges);
|
||||
}
|
||||
|
||||
// create graph
|
||||
m_graph = new EdgeWeightedDigraph(GetVertexCount());
|
||||
|
||||
for (int j = 0; j < edges.Count; ++j)
|
||||
{
|
||||
m_graph.AddEdge(new DirectedEdge(edges[j].Item1.Item1, edges[j].Item1.Item2, edges[j].Item2));
|
||||
}
|
||||
}
|
||||
|
||||
uint GetNodeIDFromVertexID(uint vertexID)
|
||||
{
|
||||
if (vertexID < m_vertices.Length)
|
||||
return m_vertices[vertexID].Id;
|
||||
|
||||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
uint GetVertexIDFromNodeID(TaxiNodesRecord node)
|
||||
{
|
||||
return node.LearnableIndex;
|
||||
}
|
||||
|
||||
int GetVertexCount()
|
||||
{
|
||||
if (m_graph == null)
|
||||
return m_vertices.Length;
|
||||
|
||||
//So we can use this function for readability, we define either max defined vertices or already loaded in graph count
|
||||
return m_vertices.Length;// Math.Max(m_graph.getNumberOfVertices(), m_vertices.Length);
|
||||
}
|
||||
|
||||
void AddVerticeAndEdgeFromNodeInfo(TaxiNodesRecord from, TaxiNodesRecord to, uint pathId, List<Tuple<Tuple<uint, uint>, uint>> edges)
|
||||
{
|
||||
if (from.Id != to.Id)
|
||||
{
|
||||
uint fromVertexID = CreateVertexFromFromNodeInfoIfNeeded(from);
|
||||
uint toVertexID = CreateVertexFromFromNodeInfoIfNeeded(to);
|
||||
|
||||
float totalDist = 0.0f;
|
||||
TaxiPathNodeRecord[] nodes = CliDB.TaxiPathNodesByPath[pathId];
|
||||
if (nodes.Length < 2)
|
||||
{
|
||||
edges.Add(Tuple.Create(Tuple.Create(fromVertexID, toVertexID), 0xFFFFu));
|
||||
return;
|
||||
}
|
||||
|
||||
int last = nodes.Length;
|
||||
int first = 0;
|
||||
if (nodes.Length > 2)
|
||||
{
|
||||
--last;
|
||||
++first;
|
||||
}
|
||||
|
||||
for (int i = first + 1; i < last; ++i)
|
||||
{
|
||||
if (nodes[i - 1].Flags.HasAnyFlag(TaxiPathNodeFlags.Teleport))
|
||||
continue;
|
||||
|
||||
uint map1, map2;
|
||||
Vector2 pos1, pos2;
|
||||
|
||||
Global.DB2Mgr.DeterminaAlternateMapPosition(nodes[i - 1].MapID, nodes[i - 1].Loc.X, nodes[i - 1].Loc.Y, nodes[i - 1].Loc.Z, out map1, out pos1);
|
||||
Global.DB2Mgr.DeterminaAlternateMapPosition(nodes[i].MapID, nodes[i].Loc.X, nodes[i].Loc.Y, nodes[i].Loc.Z, out map2, out pos2);
|
||||
|
||||
if (map1 != map2)
|
||||
continue;
|
||||
|
||||
totalDist += (float)Math.Sqrt((float)Math.Pow(pos2.X - pos1.X, 2) + (float)Math.Pow(pos2.Y - pos1.Y, 2) + (float)Math.Pow(nodes[i].Loc.Z - nodes[i - 1].Loc.Z, 2));
|
||||
}
|
||||
|
||||
uint dist = (uint)totalDist;
|
||||
if (dist > 0xFFFF)
|
||||
dist = 0xFFFF;
|
||||
|
||||
edges.Add(Tuple.Create(Tuple.Create(fromVertexID, toVertexID), dist));
|
||||
}
|
||||
}
|
||||
|
||||
public int GetCompleteNodeRoute(TaxiNodesRecord from, TaxiNodesRecord to, Player player, List<uint> shortestPath)
|
||||
{
|
||||
/*
|
||||
Information about node algorithm from client
|
||||
Since client does not give information about *ALL* nodes you have to pass by when going from sourceNodeID to destinationNodeID, we need to use Dijkstra algorithm.
|
||||
Examining several paths I discovered the following algorithm:
|
||||
* If destinationNodeID has is the next destination, connected directly to sourceNodeID, then, client just pick up this route regardless of distance
|
||||
* else we use dijkstra to find the shortest path.
|
||||
* When early landing is requested, according to behavior on retail, you can never end in a node you did not discovered before
|
||||
*/
|
||||
|
||||
// Find if we have a direct path
|
||||
uint pathId, goldCost;
|
||||
Global.ObjectMgr.GetTaxiPath(from.Id, to.Id, out pathId, out goldCost);
|
||||
if (pathId != 0)
|
||||
{
|
||||
shortestPath.Add(from.Id);
|
||||
shortestPath.Add(to.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
shortestPath.Clear();
|
||||
// We want to use Dijkstra on this graph
|
||||
DijkstraShortestPath g = new DijkstraShortestPath(m_graph, (int)GetVertexIDFromNodeID(from));
|
||||
var path = g.PathTo((int)GetVertexIDFromNodeID(to));
|
||||
// found a path to the goal
|
||||
shortestPath.Add(from.Id);
|
||||
foreach (var edge in path)
|
||||
{
|
||||
//todo test me No clue about this....
|
||||
var To = m_vertices[edge.To];
|
||||
TaxiNodeFlags requireFlag = (player.GetTeam() == Team.Alliance) ? TaxiNodeFlags.Alliance : TaxiNodeFlags.Horde;
|
||||
if (!To.Flags.HasAnyFlag(requireFlag))
|
||||
continue;
|
||||
|
||||
PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(To.ConditionID);
|
||||
if (condition != null)
|
||||
if (!ConditionManager.IsPlayerMeetingCondition(player, condition))
|
||||
continue;
|
||||
|
||||
shortestPath.Add(GetNodeIDFromVertexID(edge.To));
|
||||
}
|
||||
}
|
||||
|
||||
return shortestPath.Count;
|
||||
}
|
||||
|
||||
uint CreateVertexFromFromNodeInfoIfNeeded(TaxiNodesRecord node)
|
||||
{
|
||||
//Check if we need a new one or if it may be already created
|
||||
if (m_vertices.Length <= node.LearnableIndex)
|
||||
Array.Resize(ref m_vertices, (int)node.LearnableIndex + 1);
|
||||
|
||||
m_vertices[node.LearnableIndex] = node;
|
||||
return node.LearnableIndex;
|
||||
}
|
||||
|
||||
TaxiNodesRecord[] m_vertices = new TaxiNodesRecord[0];
|
||||
EdgeWeightedDigraph m_graph;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Groups;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class Totem : Minion
|
||||
{
|
||||
public Totem(SummonPropertiesRecord properties, Unit owner) : base(properties, owner, false)
|
||||
{
|
||||
m_unitTypeMask |= UnitTypeMask.Totem;
|
||||
m_type = TotemType.Passive;
|
||||
}
|
||||
|
||||
public override void Update(uint diff)
|
||||
{
|
||||
if (!GetOwner().IsAlive() || !IsAlive())
|
||||
{
|
||||
UnSummon(); // remove self
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_duration <= diff)
|
||||
{
|
||||
UnSummon(); // remove self
|
||||
return;
|
||||
}
|
||||
else
|
||||
m_duration -= diff;
|
||||
base.Update(diff);
|
||||
|
||||
}
|
||||
|
||||
public override void InitStats(uint duration)
|
||||
{
|
||||
// client requires SMSG_TOTEM_CREATED to be sent before adding to world and before removing old totem
|
||||
Player owner = GetOwner().ToPlayer();
|
||||
if (owner)
|
||||
{
|
||||
if (m_Properties.Slot >= (int)SummonSlot.Totem && m_Properties.Slot < SharedConst.MaxTotemSlot)
|
||||
{
|
||||
TotemCreated packet = new TotemCreated();
|
||||
packet.Totem = GetGUID();
|
||||
packet.Slot = (byte)(m_Properties.Slot - (int)SummonSlot.Totem);
|
||||
packet.Duration = duration;
|
||||
packet.SpellID = GetUInt32Value(UnitFields.CreatedBySpell);
|
||||
owner.ToPlayer().SendPacket(packet);
|
||||
|
||||
|
||||
}
|
||||
|
||||
// set display id depending on caster's race
|
||||
SpellInfo createdBySpell = Global.SpellMgr.GetSpellInfo(GetUInt32Value(UnitFields.CreatedBySpell));
|
||||
if (createdBySpell != null)
|
||||
{
|
||||
SpellEffectInfo[] effects = createdBySpell.GetEffectsForDifficulty(Difficulty.None);
|
||||
var summonEffect = effects.FirstOrDefault(effect =>
|
||||
{
|
||||
return effect != null && effect.IsEffect(SpellEffectName.Summon);
|
||||
});
|
||||
|
||||
if (summonEffect != null)
|
||||
SetDisplayId(owner.GetModelForTotem((PlayerTotemType)summonEffect.MiscValueB));
|
||||
}
|
||||
}
|
||||
|
||||
base.InitStats(duration);
|
||||
|
||||
// Get spell cast by totem
|
||||
SpellInfo totemSpell = Global.SpellMgr.GetSpellInfo(GetSpell());
|
||||
if (totemSpell != null)
|
||||
if (totemSpell.CalcCastTime(getLevel()) != 0) // If spell has cast time . its an active totem
|
||||
m_type = TotemType.Active;
|
||||
|
||||
m_duration = duration;
|
||||
|
||||
SetLevel(GetOwner().getLevel());
|
||||
}
|
||||
|
||||
public override void InitSummon()
|
||||
{
|
||||
if (m_type == TotemType.Passive && GetSpell() != 0)
|
||||
CastSpell(this, GetSpell(), true);
|
||||
|
||||
// Some totems can have both instant effect and passive spell
|
||||
if (GetSpell(1) != 0)
|
||||
CastSpell(this, GetSpell(1), true);
|
||||
}
|
||||
|
||||
public override void UnSummon(uint msTime = 0)
|
||||
{
|
||||
if (msTime != 0)
|
||||
{
|
||||
m_Events.AddEvent(new ForcedUnsummonDelayEvent(this), m_Events.CalculateTime(msTime));
|
||||
return;
|
||||
}
|
||||
|
||||
CombatStop();
|
||||
RemoveAurasDueToSpell(GetSpell(), GetGUID());
|
||||
|
||||
// clear owner's totem slot
|
||||
for (byte i = (int)SummonSlot.Totem; i < SharedConst.MaxTotemSlot; ++i)
|
||||
{
|
||||
if (GetOwner().m_SummonSlot[i] == GetGUID())
|
||||
{
|
||||
GetOwner().m_SummonSlot[i].Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GetOwner().RemoveAurasDueToSpell(GetSpell(), GetGUID());
|
||||
|
||||
// remove aura all party members too
|
||||
Player owner = GetOwner().ToPlayer();
|
||||
if (owner != null)
|
||||
{
|
||||
owner.SendAutoRepeatCancel(this);
|
||||
|
||||
SpellInfo spell = Global.SpellMgr.GetSpellInfo(GetUInt32Value(UnitFields.CreatedBySpell));
|
||||
if (spell != null)
|
||||
GetSpellHistory().SendCooldownEvent(spell, 0, null, false);
|
||||
|
||||
Group group = owner.GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player target = refe.GetSource();
|
||||
if (target && group.SameSubGroup(owner, target))
|
||||
target.RemoveAurasDueToSpell(GetSpell(), GetGUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddObjectToRemoveList();
|
||||
}
|
||||
|
||||
public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, uint index)
|
||||
{
|
||||
// @todo possibly all negative auras immune?
|
||||
if (GetEntry() == 5925)
|
||||
return false;
|
||||
|
||||
SpellEffectInfo effect = spellInfo.GetEffect(GetMap().GetDifficultyID(), index);
|
||||
if (effect == null)
|
||||
return true;
|
||||
|
||||
switch (effect.ApplyAuraName)
|
||||
{
|
||||
case AuraType.PeriodicDamage:
|
||||
case AuraType.PeriodicLeech:
|
||||
case AuraType.ModFear:
|
||||
case AuraType.Transform:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return base.IsImmunedToSpellEffect(spellInfo, index);
|
||||
}
|
||||
|
||||
public uint GetSpell(byte slot = 0) { return m_spells[slot]; }
|
||||
|
||||
public uint GetTotemDuration() { return m_duration; }
|
||||
|
||||
public void SetTotemDuration(uint duration) { m_duration = duration; }
|
||||
|
||||
public TotemType GetTotemType() { return m_type; }
|
||||
|
||||
public override bool UpdateStats(Stats stat) { return true; }
|
||||
|
||||
public override bool UpdateAllStats() { return true; }
|
||||
|
||||
public override void UpdateResistances(SpellSchools school) { }
|
||||
public override void UpdateArmor() { }
|
||||
public override void UpdateMaxHealth() { }
|
||||
public override void UpdateMaxPower(PowerType power) { }
|
||||
public override void UpdateAttackPowerAndDamage(bool ranged = false) { }
|
||||
public override void UpdateDamagePhysical(WeaponAttackType attType) { }
|
||||
|
||||
TotemType m_type;
|
||||
uint m_duration;
|
||||
}
|
||||
|
||||
public enum TotemType
|
||||
{
|
||||
Passive = 0,
|
||||
Active = 1,
|
||||
Statue = 2 // copied straight from MaNGOS, may need more implementation to work
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,810 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.GameMath;
|
||||
using Game.DataStorage;
|
||||
using Game.Maps;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public interface ITransport
|
||||
{
|
||||
// This method transforms supplied transport offsets into global coordinates
|
||||
void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o);
|
||||
|
||||
// This method transforms supplied global coordinates into local offsets
|
||||
void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o);
|
||||
}
|
||||
|
||||
public class TransportPosHelper
|
||||
{
|
||||
public static void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o, float transX, float transY, float transZ, float transO)
|
||||
{
|
||||
float inx = x, iny = y, inz = z;
|
||||
o = Position.NormalizeOrientation(transO + o);
|
||||
|
||||
x = transX + inx * (float)Math.Cos(transO) - iny * (float)Math.Sin(transO);
|
||||
y = transY + iny * (float)Math.Cos(transO) + inx * (float)Math.Sin(transO);
|
||||
z = transZ + inz;
|
||||
}
|
||||
|
||||
public static void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o, float transX, float transY, float transZ, float transO)
|
||||
{
|
||||
o = Position.NormalizeOrientation(o - transO);
|
||||
|
||||
z -= transZ;
|
||||
y -= transY;
|
||||
x -= transX;
|
||||
|
||||
float inx = x, iny = y;
|
||||
y = (iny - inx * (float)Math.Tan(transO)) / ((float)Math.Cos(transO) + (float)Math.Sin(transO) * (float)Math.Tan(transO));
|
||||
x = (inx + iny * (float)Math.Tan(transO)) / ((float)Math.Cos(transO) + (float)Math.Sin(transO) * (float)Math.Tan(transO));
|
||||
}
|
||||
}
|
||||
|
||||
public class Transport : GameObject, ITransport
|
||||
{
|
||||
public Transport()
|
||||
{
|
||||
_isMoving = true;
|
||||
|
||||
m_updateFlag = UpdateFlag.Transport | UpdateFlag.StationaryPosition | UpdateFlag.Rotation;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Contract.Assert(_passengers.Empty());
|
||||
UnloadStaticPassengers();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
public bool Create(ulong guidlow, uint entry, uint mapid, float x, float y, float z, float ang, uint animprogress)
|
||||
{
|
||||
Relocate(x, y, z, ang);
|
||||
|
||||
if (!IsPositionValid())
|
||||
{
|
||||
Log.outError(LogFilter.Transport, "Transport (GUID: {0}) not created. Suggested coordinates isn't valid (X: {1} Y: {2})",
|
||||
guidlow, x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
_Create(ObjectGuid.Create(HighGuid.Transport, guidlow));
|
||||
|
||||
GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(entry);
|
||||
|
||||
if (goinfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Transport not created: entry in `gameobject_template` not found, guidlow: {0} map: {1} (X: {2} Y: {3} Z: {4}) ang: {5}", guidlow, mapid, x, y, z, ang);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_goInfo = goinfo;
|
||||
m_goTemplateAddon = Global.ObjectMgr.GetGameObjectTemplateAddon(entry);
|
||||
|
||||
TransportTemplate tInfo = Global.TransportMgr.GetTransportTemplate(entry);
|
||||
if (tInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Transport {0} (name: {1}) will not be created, missing `transport_template` entry.", entry, goinfo.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
_transportInfo = tInfo;
|
||||
_nextFrame = 0;
|
||||
_currentFrame = tInfo.keyFrames[_nextFrame++];
|
||||
_triggeredArrivalEvent = false;
|
||||
_triggeredDepartureEvent = false;
|
||||
|
||||
if (m_goTemplateAddon != null)
|
||||
{
|
||||
SetFaction(m_goTemplateAddon.faction);
|
||||
SetUInt32Value(GameObjectFields.Flags, m_goTemplateAddon.flags);
|
||||
}
|
||||
|
||||
m_goValue.Transport.PathProgress = 0;
|
||||
SetFloatValue(ObjectFields.ScaleX, goinfo.size);
|
||||
SetPeriod(tInfo.pathTime);
|
||||
SetEntry(goinfo.entry);
|
||||
SetDisplayId(goinfo.displayId);
|
||||
SetGoState(goinfo.MoTransport.allowstopping == 0 ? GameObjectState.Ready : GameObjectState.Active);
|
||||
SetGoType(GameObjectTypes.MapObjTransport);
|
||||
SetGoAnimProgress(animprogress);
|
||||
SetName(goinfo.name);
|
||||
SetWorldRotation(Quaternion.WAxis);
|
||||
SetParentRotation(Quaternion.WAxis);
|
||||
|
||||
m_model = CreateModel();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void CleanupsBeforeDelete(bool finalCleanup)
|
||||
{
|
||||
UnloadStaticPassengers();
|
||||
while (!_passengers.Empty())
|
||||
{
|
||||
WorldObject obj = _passengers.FirstOrDefault();
|
||||
RemovePassenger(obj);
|
||||
}
|
||||
|
||||
base.CleanupsBeforeDelete(finalCleanup);
|
||||
}
|
||||
|
||||
public override void Update(uint diff)
|
||||
{
|
||||
int positionUpdateDelay = 200;
|
||||
|
||||
if (GetAI() != null)
|
||||
GetAI().UpdateAI(diff);
|
||||
else if (!AIM_Initialize())
|
||||
Log.outError(LogFilter.Transport, "Could not initialize GameObjectAI for Transport");
|
||||
|
||||
if (GetKeyFrames().Count <= 1)
|
||||
return;
|
||||
|
||||
if (IsMoving() || !_pendingStop)
|
||||
m_goValue.Transport.PathProgress += diff;
|
||||
|
||||
uint timer = m_goValue.Transport.PathProgress % GetTransportPeriod();
|
||||
|
||||
// Set current waypoint
|
||||
// Desired outcome: _currentFrame.DepartureTime < timer < _nextFrame.ArriveTime
|
||||
// ... arrive | ... delay ... | departure
|
||||
// event / event /
|
||||
for (; ; )
|
||||
{
|
||||
if (timer >= _currentFrame.ArriveTime)
|
||||
{
|
||||
if (!_triggeredArrivalEvent)
|
||||
{
|
||||
DoEventIfAny(_currentFrame, false);
|
||||
_triggeredArrivalEvent = true;
|
||||
}
|
||||
|
||||
if (timer < _currentFrame.DepartureTime)
|
||||
{
|
||||
SetMoving(false);
|
||||
if (_pendingStop && GetGoState() != GameObjectState.Ready)
|
||||
{
|
||||
SetGoState(GameObjectState.Ready);
|
||||
m_goValue.Transport.PathProgress = (m_goValue.Transport.PathProgress / GetTransportPeriod());
|
||||
m_goValue.Transport.PathProgress *= GetTransportPeriod();
|
||||
m_goValue.Transport.PathProgress += _currentFrame.ArriveTime;
|
||||
}
|
||||
break; // its a stop frame and we are waiting
|
||||
}
|
||||
}
|
||||
|
||||
if (timer >= _currentFrame.DepartureTime && !_triggeredDepartureEvent)
|
||||
{
|
||||
DoEventIfAny(_currentFrame, true); // departure event
|
||||
_triggeredDepartureEvent = true;
|
||||
}
|
||||
|
||||
// not waiting anymore
|
||||
SetMoving(true);
|
||||
|
||||
// Enable movement
|
||||
if (GetGoInfo().MoTransport.allowstopping != 0)
|
||||
SetGoState(GameObjectState.Active);
|
||||
|
||||
if (timer >= _currentFrame.DepartureTime && timer < _currentFrame.NextArriveTime)
|
||||
break; // found current waypoint
|
||||
|
||||
MoveToNextWaypoint();
|
||||
|
||||
Global.ScriptMgr.OnRelocate(this, _currentFrame.Node.NodeIndex, _currentFrame.Node.MapID, _currentFrame.Node.Loc.X, _currentFrame.Node.Loc.Y, _currentFrame.Node.Loc.Z);
|
||||
|
||||
Log.outDebug(LogFilter.Transport, "Transport {0} ({1}) moved to node {2} {3} {4} {5} {6}", GetEntry(), GetName(), _currentFrame.Node.NodeIndex, _currentFrame.Node.MapID,
|
||||
_currentFrame.Node.Loc.X, _currentFrame.Node.Loc.Y, _currentFrame.Node.Loc.Z);
|
||||
|
||||
// Departure event
|
||||
var nextframe = GetKeyFrames()[_nextFrame];
|
||||
if (_currentFrame.IsTeleportFrame())
|
||||
if (TeleportTransport(nextframe.Node.MapID, nextframe.Node.Loc.X, nextframe.Node.Loc.Y, nextframe.Node.Loc.Z, nextframe.InitialOrientation))
|
||||
return;
|
||||
}
|
||||
|
||||
// Add model to map after we are fully done with moving maps
|
||||
if (_delayedAddModel)
|
||||
{
|
||||
_delayedAddModel = false;
|
||||
if (m_model != null)
|
||||
GetMap().InsertGameObjectModel(m_model);
|
||||
}
|
||||
|
||||
// Set position
|
||||
_positionChangeTimer.Update((int)diff);
|
||||
if (_positionChangeTimer.Passed())
|
||||
{
|
||||
_positionChangeTimer.Reset(positionUpdateDelay);
|
||||
if (IsMoving())
|
||||
{
|
||||
float t = CalculateSegmentPos(timer * 0.001f);
|
||||
Vector3 pos, dir;
|
||||
_currentFrame.Spline.Evaluate_Percent((int)_currentFrame.Index, t, out pos);
|
||||
_currentFrame.Spline.Evaluate_Derivative((int)_currentFrame.Index, t, out dir);
|
||||
UpdatePosition(pos.X, pos.Y, pos.Z, (float)Math.Atan2(dir.Y, dir.X) + MathFunctions.PI);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* There are four possible scenarios that trigger loading/unloading passengers:
|
||||
1. transport moves from inactive to active grid
|
||||
2. the grid that transport is currently in becomes active
|
||||
3. transport moves from active to inactive grid
|
||||
4. the grid that transport is currently in unloads
|
||||
*/
|
||||
bool gridActive = GetMap().IsGridLoaded(GetPositionX(), GetPositionY());
|
||||
|
||||
if (_staticPassengers.Empty() && gridActive) // 2.
|
||||
LoadStaticPassengers();
|
||||
else if (!_staticPassengers.Empty() && !gridActive)
|
||||
// 4. - if transports stopped on grid edge, some passengers can remain in active grids
|
||||
// unload all static passengers otherwise passengers won't load correctly when the grid that transport is currently in becomes active
|
||||
UnloadStaticPassengers();
|
||||
}
|
||||
}
|
||||
|
||||
Global.ScriptMgr.OnTransportUpdate(this, diff);
|
||||
}
|
||||
|
||||
public void DelayedUpdate(uint diff)
|
||||
{
|
||||
if (GetKeyFrames().Count <= 1)
|
||||
return;
|
||||
|
||||
DelayedTeleportTransport();
|
||||
}
|
||||
|
||||
public void AddPassenger(WorldObject passenger)
|
||||
{
|
||||
if (!IsInWorld)
|
||||
return;
|
||||
|
||||
_passengers.Add(passenger);
|
||||
passenger.SetTransport(this);
|
||||
passenger.m_movementInfo.transport.guid = GetGUID();
|
||||
|
||||
if (passenger.IsTypeId(TypeId.Player))
|
||||
Global.ScriptMgr.OnAddPassenger(this, passenger.ToPlayer());
|
||||
}
|
||||
|
||||
public void RemovePassenger(WorldObject passenger)
|
||||
{
|
||||
bool erased = _passengers.Remove(passenger);
|
||||
|
||||
if (erased || _staticPassengers.Remove(passenger))
|
||||
{
|
||||
passenger.SetTransport(null);
|
||||
passenger.m_movementInfo.transport.Reset();
|
||||
Log.outDebug(LogFilter.Transport, "Object {0} removed from transport {1}.", passenger.GetName(), GetName());
|
||||
|
||||
if (passenger.IsTypeId(TypeId.Player))
|
||||
Global.ScriptMgr.OnRemovePassenger(this, passenger.ToPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
public Creature CreateNPCPassenger(ulong guid, CreatureData data)
|
||||
{
|
||||
Map map = GetMap();
|
||||
Creature creature = new Creature();
|
||||
|
||||
if (!creature.LoadCreatureFromDB(guid, map, false))
|
||||
return null;
|
||||
|
||||
float x = data.posX;
|
||||
float y = data.posY;
|
||||
float z = data.posZ;
|
||||
float o = data.orientation;
|
||||
|
||||
creature.SetTransport(this);
|
||||
creature.m_movementInfo.transport.guid = GetGUID();
|
||||
creature.m_movementInfo.transport.pos.Relocate(x, y, z, o);
|
||||
creature.m_movementInfo.transport.seat = -1;
|
||||
CalculatePassengerPosition(ref x, ref y, ref z, ref o);
|
||||
creature.Relocate(x, y, z, o);
|
||||
creature.SetHomePosition(creature.GetPositionX(), creature.GetPositionY(), creature.GetPositionZ(), creature.GetOrientation());
|
||||
creature.SetTransportHomePosition(creature.m_movementInfo.transport.pos);
|
||||
|
||||
// @HACK - transport models are not added to map's dynamic LoS calculations
|
||||
// because the current GameObjectModel cannot be moved without recreating
|
||||
creature.AddUnitState(UnitState.IgnorePathfinding);
|
||||
|
||||
if (!creature.IsPositionValid())
|
||||
{
|
||||
Log.outError(LogFilter.Transport, "Creature (guidlow {0}, entry {1}) not created. Suggested coordinates aren't valid (X: {2} Y: {3})", creature.GetGUID().ToString(), creature.GetEntry(), creature.GetPositionX(), creature.GetPositionY());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.phaseid != 0)
|
||||
creature.SetInPhase(data.phaseid, false, true);
|
||||
else if (data.phaseGroup != 0)
|
||||
{
|
||||
foreach (var phase in Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup))
|
||||
creature.SetInPhase(phase, false, true);
|
||||
}
|
||||
else
|
||||
creature.CopyPhaseFrom(this);
|
||||
|
||||
if (!map.AddToMap(creature))
|
||||
return null;
|
||||
|
||||
_staticPassengers.Add(creature);
|
||||
Global.ScriptMgr.OnAddCreaturePassenger(this, creature);
|
||||
return creature;
|
||||
}
|
||||
|
||||
GameObject CreateGOPassenger(ulong guid, GameObjectData data)
|
||||
{
|
||||
Map map = GetMap();
|
||||
GameObject go = new GameObject();
|
||||
|
||||
if (!go.LoadGameObjectFromDB(guid, map, false))
|
||||
return null;
|
||||
|
||||
float x = data.posX;
|
||||
float y = data.posY;
|
||||
float z = data.posZ;
|
||||
float o = data.orientation;
|
||||
|
||||
go.SetTransport(this);
|
||||
go.m_movementInfo.transport.guid = GetGUID();
|
||||
go.m_movementInfo.transport.pos.Relocate(x, y, z, o);
|
||||
go.m_movementInfo.transport.seat = -1;
|
||||
CalculatePassengerPosition(ref x, ref y, ref z, ref o);
|
||||
go.Relocate(x, y, z, o);
|
||||
go.RelocateStationaryPosition(x, y, z, o);
|
||||
|
||||
if (!go.IsPositionValid())
|
||||
{
|
||||
Log.outError(LogFilter.Transport, "GameObject (guidlow {0}, entry {1}) not created. Suggested coordinates aren't valid (X: {2} Y: {3})", go.GetGUID().ToString(), go.GetEntry(), go.GetPositionX(), go.GetPositionY());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!map.AddToMap(go))
|
||||
return null;
|
||||
|
||||
_staticPassengers.Add(go);
|
||||
return go;
|
||||
}
|
||||
|
||||
public TempSummon SummonPassenger(uint entry, Position pos, TempSummonType summonType, SummonPropertiesRecord properties = null, uint duration = 0, Unit summoner = null, uint spellId = 0, uint vehId = 0)
|
||||
{
|
||||
Map map = GetMap();
|
||||
if (map == null)
|
||||
return null;
|
||||
|
||||
UnitTypeMask mask = UnitTypeMask.Summon;
|
||||
if (properties != null)
|
||||
{
|
||||
switch (properties.Category)
|
||||
{
|
||||
case SummonCategory.Pet:
|
||||
mask = UnitTypeMask.Guardian;
|
||||
break;
|
||||
case SummonCategory.Puppet:
|
||||
mask = UnitTypeMask.Puppet;
|
||||
break;
|
||||
case SummonCategory.Vehicle:
|
||||
mask = UnitTypeMask.Minion;
|
||||
break;
|
||||
case SummonCategory.Wild:
|
||||
case SummonCategory.Ally:
|
||||
case SummonCategory.Unk:
|
||||
{
|
||||
switch (properties.Type)
|
||||
{
|
||||
case SummonType.Minion:
|
||||
case SummonType.Guardian:
|
||||
case SummonType.Guardian2:
|
||||
mask = UnitTypeMask.Guardian;
|
||||
break;
|
||||
case SummonType.Totem:
|
||||
case SummonType.LightWell:
|
||||
mask = UnitTypeMask.Totem;
|
||||
break;
|
||||
case SummonType.Vehicle:
|
||||
case SummonType.Vehicle2:
|
||||
mask = UnitTypeMask.Summon;
|
||||
break;
|
||||
case SummonType.Minipet:
|
||||
mask = UnitTypeMask.Minion;
|
||||
break;
|
||||
default:
|
||||
if (properties.Flags.HasAnyFlag<uint>(512)) // Mirror Image, Summon Gargoyle
|
||||
mask = UnitTypeMask.Guardian;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<uint> phases = new List<uint>();
|
||||
if (summoner)
|
||||
phases = summoner.GetPhases();
|
||||
else
|
||||
phases = GetPhases(); // If there was no summoner, try to use the transport phases
|
||||
|
||||
TempSummon summon = null;
|
||||
switch (mask)
|
||||
{
|
||||
case UnitTypeMask.Summon:
|
||||
summon = new TempSummon(properties, summoner, false);
|
||||
break;
|
||||
case UnitTypeMask.Guardian:
|
||||
summon = new Guardian(properties, summoner, false);
|
||||
break;
|
||||
case UnitTypeMask.Puppet:
|
||||
summon = new Puppet(properties, summoner);
|
||||
break;
|
||||
case UnitTypeMask.Totem:
|
||||
summon = new Totem(properties, summoner);
|
||||
break;
|
||||
case UnitTypeMask.Minion:
|
||||
summon = new Minion(properties, summoner, false);
|
||||
break;
|
||||
}
|
||||
|
||||
float x, y, z, o;
|
||||
pos.GetPosition(out x, out y, out z, out o);
|
||||
CalculatePassengerPosition(ref x, ref y, ref z, ref o);
|
||||
|
||||
if (!summon.Create(map.GenerateLowGuid(HighGuid.Creature), map, 0, entry, x, y, z, o, null, vehId))
|
||||
return null;
|
||||
|
||||
foreach (var phase in phases)
|
||||
summon.SetInPhase(phase, false, true);
|
||||
|
||||
summon.SetUInt32Value(UnitFields.CreatedBySpell, spellId);
|
||||
|
||||
summon.SetTransport(this);
|
||||
summon.m_movementInfo.transport.guid = GetGUID();
|
||||
summon.m_movementInfo.transport.pos.Relocate(pos);
|
||||
summon.Relocate(x, y, z, o);
|
||||
summon.SetHomePosition(x, y, z, o);
|
||||
summon.SetTransportHomePosition(pos);
|
||||
|
||||
// @HACK - transport models are not added to map's dynamic LoS calculations
|
||||
// because the current GameObjectModel cannot be moved without recreating
|
||||
summon.AddUnitState(UnitState.IgnorePathfinding);
|
||||
|
||||
summon.InitStats(duration);
|
||||
|
||||
if (!map.AddToMap(summon))
|
||||
return null;
|
||||
|
||||
_staticPassengers.Add(summon);
|
||||
|
||||
summon.InitSummon();
|
||||
summon.SetTempSummonType(summonType);
|
||||
|
||||
return summon;
|
||||
}
|
||||
|
||||
public void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o)
|
||||
{
|
||||
TransportPosHelper.CalculatePassengerPosition(ref x, ref y, ref z, ref o, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
}
|
||||
|
||||
public void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o)
|
||||
{
|
||||
TransportPosHelper.CalculatePassengerOffset(ref x, ref y, ref z, ref o, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
}
|
||||
|
||||
public void UpdatePosition(float x, float y, float z, float o)
|
||||
{
|
||||
bool newActive = GetMap().IsGridLoaded(x, y);
|
||||
|
||||
Relocate(x, y, z, o);
|
||||
UpdateModelPosition();
|
||||
|
||||
UpdatePassengerPositions(_passengers);
|
||||
|
||||
/* There are four possible scenarios that trigger loading/unloading passengers:
|
||||
1. transport moves from inactive to active grid
|
||||
2. the grid that transport is currently in becomes active
|
||||
3. transport moves from active to inactive grid
|
||||
4. the grid that transport is currently in unloads
|
||||
*/
|
||||
if (_staticPassengers.Empty() && newActive) // 1. and 2.
|
||||
LoadStaticPassengers();
|
||||
else if (!_staticPassengers.Empty() && !newActive && new Cell(x, y).DiffGrid(new Cell(GetPositionX(), GetPositionY()))) // 3.
|
||||
UnloadStaticPassengers();
|
||||
else
|
||||
UpdatePassengerPositions(_staticPassengers);
|
||||
// 4. is handed by grid unload
|
||||
}
|
||||
|
||||
void LoadStaticPassengers()
|
||||
{
|
||||
uint mapId = (uint)GetGoInfo().MoTransport.SpawnMap;
|
||||
var cells = Global.ObjectMgr.GetMapObjectGuids(mapId, (byte)GetMap().GetSpawnMode());
|
||||
if (cells == null)
|
||||
return;
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
// Creatures on transport
|
||||
foreach (var npc in cell.Value.creatures)
|
||||
CreateNPCPassenger(npc, Global.ObjectMgr.GetCreatureData(npc));
|
||||
|
||||
// GameObjects on transport
|
||||
foreach (var go in cell.Value.gameobjects)
|
||||
CreateGOPassenger(go, Global.ObjectMgr.GetGOData(go));
|
||||
}
|
||||
}
|
||||
|
||||
void UnloadStaticPassengers()
|
||||
{
|
||||
while (!_staticPassengers.Empty())
|
||||
{
|
||||
WorldObject obj = _staticPassengers.First();
|
||||
obj.AddObjectToRemoveList(); // also removes from _staticPassengers
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableMovement(bool enabled)
|
||||
{
|
||||
if (GetGoInfo().MoTransport.allowstopping == 0)
|
||||
return;
|
||||
|
||||
_pendingStop = !enabled;
|
||||
}
|
||||
|
||||
public void SetDelayedAddModelToMap() { _delayedAddModel = true; }
|
||||
|
||||
void MoveToNextWaypoint()
|
||||
{
|
||||
// Clear events flagging
|
||||
_triggeredArrivalEvent = false;
|
||||
_triggeredDepartureEvent = false;
|
||||
|
||||
// Set frames
|
||||
_currentFrame = GetKeyFrames()[_nextFrame++];
|
||||
if (_nextFrame == GetKeyFrames().Count)
|
||||
_nextFrame = 0;
|
||||
}
|
||||
|
||||
float CalculateSegmentPos(float now)
|
||||
{
|
||||
KeyFrame frame = _currentFrame;
|
||||
float speed = GetGoInfo().MoTransport.moveSpeed;
|
||||
float accel = GetGoInfo().MoTransport.accelRate;
|
||||
float timeSinceStop = frame.TimeFrom + (now - (1.0f / Time.InMilliseconds) * frame.DepartureTime);
|
||||
float timeUntilStop = frame.TimeTo - (now - (1.0f / Time.InMilliseconds) * frame.DepartureTime);
|
||||
float segmentPos, dist;
|
||||
float accelTime = _transportInfo.accelTime;
|
||||
float accelDist = _transportInfo.accelDist;
|
||||
// calculate from nearest stop, less confusing calculation...
|
||||
if (timeSinceStop < timeUntilStop)
|
||||
{
|
||||
if (timeSinceStop < accelTime)
|
||||
dist = 0.5f * accel * timeSinceStop * timeSinceStop;
|
||||
else
|
||||
dist = accelDist + (timeSinceStop - accelTime) * speed;
|
||||
segmentPos = dist - frame.DistSinceStop;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (timeUntilStop < _transportInfo.accelTime)
|
||||
dist = (0.5f * accel) * (timeUntilStop * timeUntilStop);
|
||||
else
|
||||
dist = accelDist + (timeUntilStop - accelTime) * speed;
|
||||
segmentPos = frame.DistUntilStop - dist;
|
||||
}
|
||||
|
||||
return segmentPos / frame.NextDistFromPrev;
|
||||
}
|
||||
|
||||
bool TeleportTransport(uint newMapid, float x, float y, float z, float o)
|
||||
{
|
||||
Map oldMap = GetMap();
|
||||
|
||||
if (oldMap.GetId() != newMapid)
|
||||
{
|
||||
_delayedTeleport = true;
|
||||
UnloadStaticPassengers();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Teleport players, they need to know it
|
||||
foreach (var obj in _passengers)
|
||||
{
|
||||
if (obj.IsTypeId(TypeId.Player))
|
||||
{
|
||||
// will be relocated in UpdatePosition of the vehicle
|
||||
Unit veh = obj.ToUnit().GetVehicleBase();
|
||||
if (veh)
|
||||
if (veh.GetTransport() == this)
|
||||
continue;
|
||||
|
||||
float destX, destY, destZ, destO;
|
||||
obj.m_movementInfo.transport.pos.GetPosition(out destX, out destY, out destZ, out destO);
|
||||
TransportPosHelper.CalculatePassengerPosition(ref destX, ref destY, ref destZ, ref destO, x, y, z, o);
|
||||
|
||||
obj.ToUnit().NearTeleportTo(destX, destY, destZ, destO);
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(x, y, z, o);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DelayedTeleportTransport()
|
||||
{
|
||||
if (!_delayedTeleport)
|
||||
return;
|
||||
|
||||
var nextFrame = GetKeyFrames()[_nextFrame];
|
||||
|
||||
_delayedTeleport = false;
|
||||
Map newMap = Global.MapMgr.CreateBaseMap(nextFrame.Node.MapID);
|
||||
GetMap().RemoveFromMap(this, false);
|
||||
SetMap(newMap);
|
||||
|
||||
float x = nextFrame.Node.Loc.X,
|
||||
y = nextFrame.Node.Loc.Y,
|
||||
z = nextFrame.Node.Loc.Z,
|
||||
o = nextFrame.InitialOrientation;
|
||||
|
||||
foreach (var obj in _passengers)
|
||||
{
|
||||
float destX, destY, destZ, destO;
|
||||
obj.m_movementInfo.transport.pos.GetPosition(out destX, out destY, out destZ, out destO);
|
||||
TransportPosHelper.CalculatePassengerPosition(ref destX, ref destY, ref destZ, ref destO, x, y, z, o);
|
||||
|
||||
switch (obj.GetTypeId())
|
||||
{
|
||||
case TypeId.Player:
|
||||
if (!obj.ToPlayer().TeleportTo(nextFrame.Node.MapID, destX, destY, destZ, destO, TeleportToOptions.NotLeaveTransport))
|
||||
RemovePassenger(obj);
|
||||
break;
|
||||
case TypeId.DynamicObject:
|
||||
case TypeId.AreaTrigger:
|
||||
obj.AddObjectToRemoveList();
|
||||
break;
|
||||
default:
|
||||
RemovePassenger(obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Relocate(x, y, z, o);
|
||||
GetMap().AddToMap(this);
|
||||
}
|
||||
|
||||
void UpdatePassengerPositions(List<WorldObject> passengers)
|
||||
{
|
||||
foreach (var passenger in passengers)
|
||||
{
|
||||
// transport teleported but passenger not yet (can happen for players)
|
||||
if (passenger.GetMap() != GetMap())
|
||||
continue;
|
||||
|
||||
// if passenger is on vehicle we have to assume the vehicle is also on transport
|
||||
// and its the vehicle that will be updating its passengers
|
||||
Unit unit = passenger.ToUnit();
|
||||
if (unit)
|
||||
if (unit.GetVehicle() != null)
|
||||
continue;
|
||||
|
||||
// Do not use Unit.UpdatePosition here, we don't want to remove auras
|
||||
// as if regular movement occurred
|
||||
float x, y, z, o;
|
||||
passenger.m_movementInfo.transport.pos.GetPosition(out x, out y, out z, out o);
|
||||
CalculatePassengerPosition(ref x, ref y, ref z, ref o);
|
||||
switch (passenger.GetTypeId())
|
||||
{
|
||||
case TypeId.Unit:
|
||||
{
|
||||
Creature creature = passenger.ToCreature();
|
||||
GetMap().CreatureRelocation(creature, x, y, z, o, false);
|
||||
creature.GetTransportHomePosition(out x, out y, out z, out o);
|
||||
CalculatePassengerPosition(ref x, ref y, ref z, ref o);
|
||||
creature.SetHomePosition(x, y, z, o);
|
||||
break;
|
||||
}
|
||||
case TypeId.Player:
|
||||
if (passenger.IsInWorld)
|
||||
GetMap().PlayerRelocation(passenger.ToPlayer(), x, y, z, o);
|
||||
break;
|
||||
case TypeId.GameObject:
|
||||
GetMap().GameObjectRelocation(passenger.ToGameObject(), x, y, z, o, false);
|
||||
passenger.ToGameObject().RelocateStationaryPosition(x, y, z, o);
|
||||
break;
|
||||
case TypeId.DynamicObject:
|
||||
GetMap().DynamicObjectRelocation(passenger.ToDynamicObject(), x, y, z, o);
|
||||
break;
|
||||
case TypeId.AreaTrigger:
|
||||
GetMap().AreaTriggerRelocation(passenger.ToAreaTrigger(), x, y, z, o);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (unit != null)
|
||||
{
|
||||
Vehicle vehicle = unit.GetVehicleKit();
|
||||
if (vehicle != null)
|
||||
vehicle.RelocatePassengers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DoEventIfAny(KeyFrame node, bool departure)
|
||||
{
|
||||
uint eventid = departure ? node.Node.DepartureEventID : node.Node.ArrivalEventID;
|
||||
if (eventid != 0)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "Taxi {0} event {1} of node {2} of {3} path", departure ? "departure" : "arrival", eventid, node.Node.NodeIndex, GetName());
|
||||
GetMap().ScriptsStart(ScriptsType.Event, eventid, this, this);
|
||||
EventInform(eventid);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BuildUpdate(Dictionary<Player, UpdateData> data_map)
|
||||
{
|
||||
var players = GetMap().GetPlayers();
|
||||
if (players.Empty())
|
||||
return;
|
||||
|
||||
foreach (var pl in players)
|
||||
BuildFieldsUpdate(pl, data_map);
|
||||
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
public List<WorldObject> GetPassengers() { return _passengers; }
|
||||
|
||||
public override uint GetTransportPeriod() { return GetUInt32Value(GameObjectFields.Level); }
|
||||
public void SetPeriod(uint period) { SetUInt32Value(GameObjectFields.Level, period); }
|
||||
uint GetTimer() { return m_goValue.Transport.PathProgress; }
|
||||
|
||||
public List<KeyFrame> GetKeyFrames() { return _transportInfo.keyFrames; }
|
||||
public TransportTemplate GetTransportTemplate() { return _transportInfo; }
|
||||
|
||||
//! Helpers to know if stop frame was reached
|
||||
bool IsMoving() { return _isMoving; }
|
||||
void SetMoving(bool val) { _isMoving = val; }
|
||||
|
||||
TransportTemplate _transportInfo;
|
||||
|
||||
KeyFrame _currentFrame;
|
||||
int _nextFrame;
|
||||
TimeTrackerSmall _positionChangeTimer = new TimeTrackerSmall();
|
||||
bool _isMoving;
|
||||
bool _pendingStop;
|
||||
|
||||
//! These are needed to properly control events triggering only once for each frame
|
||||
bool _triggeredArrivalEvent;
|
||||
bool _triggeredDepartureEvent;
|
||||
|
||||
List<WorldObject> _passengers = new List<WorldObject>();
|
||||
List<WorldObject> _staticPassengers = new List<WorldObject>();
|
||||
|
||||
bool _delayedAddModel;
|
||||
bool _delayedTeleport;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Collections;
|
||||
using Framework.Constants;
|
||||
using Framework.GameMath;
|
||||
using Game.Network;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class CharmInfo
|
||||
{
|
||||
public CharmInfo(Unit unit)
|
||||
{
|
||||
_unit = unit;
|
||||
_CommandState = CommandStates.Follow;
|
||||
_petnumber = 0;
|
||||
_oldReactState = ReactStates.Passive;
|
||||
for (byte i = 0; i < SharedConst.MaxSpellCharm; ++i)
|
||||
{
|
||||
_charmspells[i] = new UnitActionBarEntry();
|
||||
_charmspells[i].SetActionAndType(0, ActiveStates.Disabled);
|
||||
}
|
||||
|
||||
for (var i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
||||
PetActionBar[i] = new UnitActionBarEntry();
|
||||
|
||||
if (_unit.IsTypeId(TypeId.Unit))
|
||||
{
|
||||
_oldReactState = _unit.ToCreature().GetReactState();
|
||||
_unit.ToCreature().SetReactState(ReactStates.Passive);
|
||||
}
|
||||
}
|
||||
|
||||
public void RestoreState()
|
||||
{
|
||||
if (_unit.IsTypeId(TypeId.Unit))
|
||||
{
|
||||
Creature creature = _unit.ToCreature();
|
||||
if (creature)
|
||||
creature.SetReactState(_oldReactState);
|
||||
}
|
||||
}
|
||||
|
||||
public void InitPetActionBar()
|
||||
{
|
||||
|
||||
// the first 3 SpellOrActions are attack, follow and stay
|
||||
for (byte i = 0; i < SharedConst.ActionBarIndexPetSpellStart - SharedConst.ActionBarIndexStart; ++i)
|
||||
SetActionBar((byte)(SharedConst.ActionBarIndexStart + i), (uint)CommandStates.Attack - i, ActiveStates.Command);
|
||||
|
||||
// middle 4 SpellOrActions are spells/special attacks/abilities
|
||||
for (byte i = 0; i < SharedConst.ActionBarIndexPetSpellEnd - SharedConst.ActionBarIndexPetSpellStart; ++i)
|
||||
SetActionBar((byte)(SharedConst.ActionBarIndexPetSpellStart + i), 0, ActiveStates.Passive);
|
||||
|
||||
// last 3 SpellOrActions are reactions
|
||||
for (byte i = 0; i < SharedConst.ActionBarIndexEnd - SharedConst.ActionBarIndexPetSpellEnd; ++i)
|
||||
SetActionBar((byte)(SharedConst.ActionBarIndexPetSpellEnd + i), (uint)CommandStates.Attack - i, ActiveStates.Reaction);
|
||||
}
|
||||
|
||||
public void InitEmptyActionBar(bool withAttack = true)
|
||||
{
|
||||
if (withAttack)
|
||||
SetActionBar(SharedConst.ActionBarIndexStart, (uint)CommandStates.Attack, ActiveStates.Command);
|
||||
else
|
||||
SetActionBar(SharedConst.ActionBarIndexStart, 0, ActiveStates.Passive);
|
||||
for (byte x = SharedConst.ActionBarIndexStart + 1; x < SharedConst.ActionBarIndexEnd; ++x)
|
||||
SetActionBar(x, 0, ActiveStates.Passive);
|
||||
}
|
||||
|
||||
public void InitPossessCreateSpells()
|
||||
{
|
||||
if (_unit.IsTypeId(TypeId.Unit))
|
||||
{
|
||||
// Adding switch until better way is found. Malcrom
|
||||
// Adding entrys to this switch will prevent COMMAND_ATTACK being added to pet bar.
|
||||
switch (_unit.GetEntry())
|
||||
{
|
||||
case 23575: // Mindless Abomination
|
||||
case 24783: // Trained Rock Falcon
|
||||
case 27664: // Crashin' Thrashin' Racer
|
||||
case 40281: // Crashin' Thrashin' Racer
|
||||
break;
|
||||
default:
|
||||
InitEmptyActionBar();
|
||||
break;
|
||||
}
|
||||
|
||||
for (byte i = 0; i < SharedConst.MaxCreatureSpells; ++i)
|
||||
{
|
||||
uint spellId = _unit.ToCreature().m_spells[i];
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
|
||||
if (spellInfo != null)
|
||||
{
|
||||
if (spellInfo.IsPassive())
|
||||
_unit.CastSpell(_unit, spellInfo, true);
|
||||
else
|
||||
AddSpellToActionBar(spellInfo, ActiveStates.Passive, i % SharedConst.ActionBarIndexMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
InitEmptyActionBar();
|
||||
}
|
||||
|
||||
public void InitCharmCreateSpells()
|
||||
{
|
||||
if (_unit.IsTypeId(TypeId.Player)) // charmed players don't have spells
|
||||
{
|
||||
InitEmptyActionBar();
|
||||
return;
|
||||
}
|
||||
|
||||
InitPetActionBar();
|
||||
|
||||
for (uint x = 0; x < SharedConst.MaxSpellCharm; ++x)
|
||||
{
|
||||
uint spellId = _unit.ToCreature().m_spells[x];
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
|
||||
|
||||
if (spellInfo == null)
|
||||
{
|
||||
_charmspells[x].SetActionAndType(spellId, ActiveStates.Disabled);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (spellInfo.IsPassive())
|
||||
{
|
||||
_unit.CastSpell(_unit, spellInfo, true);
|
||||
_charmspells[x].SetActionAndType(spellId, ActiveStates.Passive);
|
||||
}
|
||||
else
|
||||
{
|
||||
_charmspells[x].SetActionAndType(spellId, ActiveStates.Disabled);
|
||||
|
||||
ActiveStates newstate = ActiveStates.Passive;
|
||||
|
||||
if (!spellInfo.IsAutocastable())
|
||||
newstate = ActiveStates.Passive;
|
||||
else
|
||||
{
|
||||
if (spellInfo.NeedsExplicitUnitTarget())
|
||||
{
|
||||
newstate = ActiveStates.Enabled;
|
||||
ToggleCreatureAutocast(spellInfo, true);
|
||||
}
|
||||
else
|
||||
newstate = ActiveStates.Disabled;
|
||||
}
|
||||
|
||||
AddSpellToActionBar(spellInfo, newstate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddSpellToActionBar(SpellInfo spellInfo, ActiveStates newstate = ActiveStates.Decide, int preferredSlot = 0)
|
||||
{
|
||||
uint spell_id = spellInfo.Id;
|
||||
uint first_id = spellInfo.GetFirstRankSpell().Id;
|
||||
|
||||
// new spell rank can be already listed
|
||||
for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
||||
{
|
||||
uint action = PetActionBar[i].GetAction();
|
||||
if (action != 0)
|
||||
{
|
||||
if (PetActionBar[i].IsActionBarForSpell() && Global.SpellMgr.GetFirstSpellInChain(action) == first_id)
|
||||
{
|
||||
PetActionBar[i].SetAction(spell_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// or use empty slot in other case
|
||||
for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
||||
{
|
||||
byte j = (byte)((preferredSlot + i) % SharedConst.ActionBarIndexMax);
|
||||
if (PetActionBar[j].GetAction() == 0 && PetActionBar[j].IsActionBarForSpell())
|
||||
{
|
||||
SetActionBar(j, spell_id, newstate == ActiveStates.Decide ? spellInfo.IsAutocastable() ? ActiveStates.Disabled : ActiveStates.Passive : newstate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool RemoveSpellFromActionBar(uint spell_id)
|
||||
{
|
||||
uint first_id = Global.SpellMgr.GetFirstSpellInChain(spell_id);
|
||||
|
||||
for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
||||
{
|
||||
uint action = PetActionBar[i].GetAction();
|
||||
if (action != 0)
|
||||
{
|
||||
if (PetActionBar[i].IsActionBarForSpell() && Global.SpellMgr.GetFirstSpellInChain(action) == first_id)
|
||||
{
|
||||
SetActionBar(i, 0, ActiveStates.Passive);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ToggleCreatureAutocast(SpellInfo spellInfo, bool apply)
|
||||
{
|
||||
if (spellInfo.IsPassive())
|
||||
return;
|
||||
|
||||
for (uint x = 0; x < SharedConst.MaxSpellCharm; ++x)
|
||||
if (spellInfo.Id == _charmspells[x].GetAction())
|
||||
_charmspells[x].SetType(apply ? ActiveStates.Enabled : ActiveStates.Disabled);
|
||||
}
|
||||
|
||||
public void SetPetNumber(uint petnumber, bool statwindow)
|
||||
{
|
||||
_petnumber = petnumber;
|
||||
if (statwindow)
|
||||
_unit.SetUInt32Value(UnitFields.PetNumber, _petnumber);
|
||||
else
|
||||
_unit.SetUInt32Value(UnitFields.PetNumber, 0);
|
||||
}
|
||||
|
||||
public void LoadPetActionBar(string data)
|
||||
{
|
||||
InitPetActionBar();
|
||||
|
||||
var tokens = new StringArray(data, ' ');
|
||||
if (tokens.Length != (SharedConst.ActionBarIndexEnd - SharedConst.ActionBarIndexStart) * 2)
|
||||
return; // non critical, will reset to default
|
||||
|
||||
byte index = 0;
|
||||
for (byte i = 0; i < tokens.Length && index < SharedConst.ActionBarIndexEnd; ++i, ++index)
|
||||
{
|
||||
ActiveStates type = tokens[i++].ToEnum<ActiveStates>();
|
||||
uint action = uint.Parse(tokens[i]);
|
||||
|
||||
PetActionBar[index].SetActionAndType(action, type);
|
||||
|
||||
// check correctness
|
||||
if (PetActionBar[index].IsActionBarForSpell())
|
||||
{
|
||||
SpellInfo spelInfo = Global.SpellMgr.GetSpellInfo(PetActionBar[index].GetAction());
|
||||
if (spelInfo == null)
|
||||
SetActionBar(index, 0, ActiveStates.Passive);
|
||||
else if (!spelInfo.IsAutocastable())
|
||||
SetActionBar(index, PetActionBar[index].GetAction(), ActiveStates.Passive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void BuildActionBar(WorldPacket data)
|
||||
{
|
||||
for (int i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
||||
data.WriteUInt32(PetActionBar[i].packedData);
|
||||
}
|
||||
|
||||
public void SetSpellAutocast(SpellInfo spellInfo, bool state)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
||||
{
|
||||
if (spellInfo.Id == PetActionBar[i].GetAction() && PetActionBar[i].IsActionBarForSpell())
|
||||
{
|
||||
PetActionBar[i].SetType(state ? ActiveStates.Enabled : ActiveStates.Disabled);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetIsCommandAttack(bool val)
|
||||
{
|
||||
_isCommandAttack = val;
|
||||
}
|
||||
|
||||
public bool IsCommandAttack()
|
||||
{
|
||||
return _isCommandAttack;
|
||||
}
|
||||
|
||||
public void SetIsCommandFollow(bool val)
|
||||
{
|
||||
_isCommandFollow = val;
|
||||
}
|
||||
|
||||
public bool IsCommandFollow()
|
||||
{
|
||||
return _isCommandFollow;
|
||||
}
|
||||
|
||||
public void SaveStayPosition()
|
||||
{
|
||||
//! At this point a new spline destination is enabled because of Unit.StopMoving()
|
||||
Vector3 stayPos = _unit.moveSpline.FinalDestination();
|
||||
|
||||
if (_unit.moveSpline.onTransport)
|
||||
{
|
||||
float o = 0;
|
||||
ITransport transport = _unit.GetDirectTransport();
|
||||
if (transport != null)
|
||||
transport.CalculatePassengerPosition(ref stayPos.X, ref stayPos.Y, ref stayPos.Z, ref o);
|
||||
}
|
||||
|
||||
_stayX = stayPos.X;
|
||||
_stayY = stayPos.Y;
|
||||
_stayZ = stayPos.Z;
|
||||
}
|
||||
|
||||
public void GetStayPosition(out float x, out float y, out float z)
|
||||
{
|
||||
x = _stayX;
|
||||
y = _stayY;
|
||||
z = _stayZ;
|
||||
}
|
||||
|
||||
public void SetIsAtStay(bool val)
|
||||
{
|
||||
_isAtStay = val;
|
||||
}
|
||||
|
||||
public bool IsAtStay()
|
||||
{
|
||||
return _isAtStay;
|
||||
}
|
||||
|
||||
public void SetIsFollowing(bool val)
|
||||
{
|
||||
_isFollowing = val;
|
||||
}
|
||||
|
||||
public bool IsFollowing()
|
||||
{
|
||||
return _isFollowing;
|
||||
}
|
||||
|
||||
public void SetIsReturning(bool val)
|
||||
{
|
||||
_isReturning = val;
|
||||
}
|
||||
|
||||
public bool IsReturning()
|
||||
{
|
||||
return _isReturning;
|
||||
}
|
||||
|
||||
public uint GetPetNumber() { return _petnumber; }
|
||||
public void SetCommandState(CommandStates st) { _CommandState = st; }
|
||||
public CommandStates GetCommandState() { return _CommandState; }
|
||||
public bool HasCommandState(CommandStates state) { return (_CommandState == state); }
|
||||
|
||||
public void SetActionBar(byte index, uint spellOrAction, ActiveStates type)
|
||||
{
|
||||
PetActionBar[index].SetActionAndType(spellOrAction, type);
|
||||
}
|
||||
public UnitActionBarEntry GetActionBarEntry(byte index) { return PetActionBar[index]; }
|
||||
|
||||
public UnitActionBarEntry GetCharmSpell(byte index) { return _charmspells[index]; }
|
||||
|
||||
Unit _unit;
|
||||
UnitActionBarEntry[] PetActionBar = new UnitActionBarEntry[SharedConst.ActionBarIndexMax];
|
||||
UnitActionBarEntry[] _charmspells = new UnitActionBarEntry[4];
|
||||
CommandStates _CommandState;
|
||||
uint _petnumber;
|
||||
|
||||
ReactStates _oldReactState;
|
||||
|
||||
bool _isCommandAttack;
|
||||
bool _isCommandFollow;
|
||||
bool _isAtStay;
|
||||
bool _isFollowing;
|
||||
bool _isReturning;
|
||||
float _stayX;
|
||||
float _stayY;
|
||||
float _stayZ;
|
||||
}
|
||||
|
||||
public class UnitActionBarEntry
|
||||
{
|
||||
public UnitActionBarEntry()
|
||||
{
|
||||
packedData = (uint)ActiveStates.Disabled << 24;
|
||||
}
|
||||
|
||||
public ActiveStates GetActiveState() { return (ActiveStates)UNIT_ACTION_BUTTON_TYPE(packedData); }
|
||||
|
||||
public uint GetAction() { return UNIT_ACTION_BUTTON_ACTION(packedData); }
|
||||
|
||||
public bool IsActionBarForSpell()
|
||||
{
|
||||
ActiveStates Type = GetActiveState();
|
||||
return Type == ActiveStates.Disabled || Type == ActiveStates.Enabled || Type == ActiveStates.Passive;
|
||||
}
|
||||
|
||||
public void SetActionAndType(uint action, ActiveStates type)
|
||||
{
|
||||
packedData = MAKE_UNIT_ACTION_BUTTON(action, (uint)type);
|
||||
}
|
||||
|
||||
public void SetType(ActiveStates type)
|
||||
{
|
||||
packedData = MAKE_UNIT_ACTION_BUTTON(UNIT_ACTION_BUTTON_ACTION(packedData), (uint)type);
|
||||
}
|
||||
|
||||
public void SetAction(uint action)
|
||||
{
|
||||
packedData = (packedData & 0xFF000000) | UNIT_ACTION_BUTTON_ACTION(action);
|
||||
}
|
||||
|
||||
public uint packedData;
|
||||
|
||||
public static uint MAKE_UNIT_ACTION_BUTTON(uint action, uint type)
|
||||
{
|
||||
return (action | (type << 24));
|
||||
}
|
||||
public static uint UNIT_ACTION_BUTTON_ACTION(uint packedData)
|
||||
{
|
||||
return (packedData & 0x00FFFFFF);
|
||||
}
|
||||
public static uint UNIT_ACTION_BUTTON_TYPE(uint packedData)
|
||||
{
|
||||
return ((packedData & 0xFF000000) >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class PowerPctOrderPred : IComparer<WorldObject>
|
||||
{
|
||||
public PowerPctOrderPred(PowerType power, bool ascending = true)
|
||||
{
|
||||
m_power = power;
|
||||
m_ascending = ascending;
|
||||
}
|
||||
|
||||
public int Compare(WorldObject objA, WorldObject objB)
|
||||
{
|
||||
Unit a = objA.ToUnit();
|
||||
Unit b = objB.ToUnit();
|
||||
float rA = a.GetMaxPower(m_power) != 0 ? a.GetPower(m_power) / (float)a.GetMaxPower(m_power) : 0.0f;
|
||||
float rB = b.GetMaxPower(m_power) != 0 ? b.GetPower(m_power) / (float)b.GetMaxPower(m_power) : 0.0f;
|
||||
return Convert.ToInt32(m_ascending ? rA < rB : rA > rB);
|
||||
}
|
||||
|
||||
PowerType m_power;
|
||||
bool m_ascending;
|
||||
}
|
||||
|
||||
public class HealthPctOrderPred : IComparer<WorldObject>
|
||||
{
|
||||
public HealthPctOrderPred(bool ascending = true)
|
||||
{
|
||||
m_ascending = ascending;
|
||||
}
|
||||
|
||||
public int Compare(WorldObject objA, WorldObject objB)
|
||||
{
|
||||
Unit a = objA.ToUnit();
|
||||
Unit b = objB.ToUnit();
|
||||
float rA = a.GetMaxHealth() != 0 ? a.GetHealth() / (float)a.GetMaxHealth() : 0.0f;
|
||||
float rB = b.GetMaxHealth() != 0 ? b.GetHealth() / (float)b.GetMaxHealth() : 0.0f;
|
||||
return Convert.ToInt32(m_ascending ? rA < rB : rA > rB);
|
||||
}
|
||||
|
||||
bool m_ascending;
|
||||
}
|
||||
|
||||
public class ObjectDistanceOrderPred : IComparer<WorldObject>
|
||||
{
|
||||
public ObjectDistanceOrderPred(WorldObject pRefObj, bool ascending = true)
|
||||
{
|
||||
m_refObj = pRefObj;
|
||||
m_ascending = ascending;
|
||||
}
|
||||
|
||||
public int Compare(WorldObject pLeft, WorldObject pRight)
|
||||
{
|
||||
return (m_ascending ? m_refObj.GetDistanceOrder(pLeft, pRight) : !m_refObj.GetDistanceOrder(pLeft, pRight)) ? 1 : 0;
|
||||
}
|
||||
|
||||
WorldObject m_refObj;
|
||||
bool m_ascending;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Collections;
|
||||
using Framework.Constants;
|
||||
using Framework.Dynamic;
|
||||
using Game.AI;
|
||||
using Game.Combat;
|
||||
using Game.DataStorage;
|
||||
using Game.Maps;
|
||||
using Game.Movement;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Unit
|
||||
{
|
||||
//AI
|
||||
protected UnitAI i_AI;
|
||||
protected UnitAI i_disabledAI;
|
||||
public bool IsAIEnabled { get; set; }
|
||||
public bool NeedChangeAI { get; set; }
|
||||
|
||||
//Movement
|
||||
protected float[] m_speed_rate = new float[(int)UnitMoveType.Max];
|
||||
RefManager<Unit, TargetedMovementGeneratorBase> m_FollowingRefManager;
|
||||
public MoveSpline moveSpline { get; set; }
|
||||
MotionMaster i_motionMaster;
|
||||
public uint m_movementCounter; //< Incrementing counter used in movement packets
|
||||
TimeTrackerSmall movesplineTimer;
|
||||
public Player m_playerMovingMe;
|
||||
|
||||
//Combat
|
||||
protected List<Unit> attackerList = new List<Unit>();
|
||||
Dictionary<ReactiveType, uint> m_reactiveTimer = new Dictionary<ReactiveType, uint>();
|
||||
protected float[][] m_weaponDamage = new float[(int)WeaponAttackType.Max][];
|
||||
public float[] m_threatModifier = new float[(int)SpellSchools.Max];
|
||||
|
||||
uint[] m_baseAttackSpeed = new uint[(int)WeaponAttackType.Max];
|
||||
float[] m_modAttackSpeedPct = new float[(int)WeaponAttackType.Max];
|
||||
protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max];
|
||||
|
||||
ThreatManager threatManager;
|
||||
HostileRefManager m_HostileRefManager;
|
||||
RedirectThreatInfo _redirectThreatInfo;
|
||||
protected Unit m_attacking;
|
||||
|
||||
public float m_modMeleeHitChance { get; set; }
|
||||
public float m_modRangedHitChance { get; set; }
|
||||
public float m_modSpellHitChance { get; set; }
|
||||
long _lastDamagedTime;
|
||||
bool m_canDualWield;
|
||||
public int m_baseSpellCritChance { get; set; }
|
||||
public uint m_regenTimer { get; set; }
|
||||
uint m_CombatTimer;
|
||||
public uint m_extraAttacks { get; set; }
|
||||
|
||||
//Charm
|
||||
public List<Unit> m_Controlled = new List<Unit>();
|
||||
List<Player> m_sharedVision = new List<Player>();
|
||||
CharmInfo m_charmInfo;
|
||||
protected bool m_ControlledByPlayer;
|
||||
public ObjectGuid LastCharmerGUID { get; set; }
|
||||
|
||||
uint _oldFactionId; // faction before charm
|
||||
bool _isWalkingBeforeCharm; // Are we walking before we were charmed?
|
||||
|
||||
//Spells
|
||||
protected Dictionary<CurrentSpellTypes, Spell> m_currentSpells = new Dictionary<CurrentSpellTypes, Spell>((int)CurrentSpellTypes.Max);
|
||||
Dictionary<SpellValueMod, int> CustomSpellValueMod = new Dictionary<SpellValueMod, int>();
|
||||
MultiMap<SpellImmunity, SpellImmune> m_spellImmune = new MultiMap<SpellImmunity, SpellImmune>();
|
||||
uint m_interruptMask;
|
||||
protected int m_procDeep;
|
||||
bool m_AutoRepeatFirstCast;
|
||||
SpellHistory _spellHistory;
|
||||
|
||||
//Auras
|
||||
public List<PetAura> m_petAuras = new List<PetAura>();
|
||||
List<AuraEffect> AuraEffectList = new List<AuraEffect>();
|
||||
MultiMap<AuraType, AuraEffect> m_modAuras = new MultiMap<AuraType, AuraEffect>();
|
||||
List<Aura> m_removedAuras = new List<Aura>();
|
||||
List<AuraApplication> m_interruptableAuras = new List<AuraApplication>(); // auras which have interrupt mask applied on unit
|
||||
MultiMap<AuraStateType, AuraApplication> m_auraStateAuras = new MultiMap<AuraStateType, AuraApplication>(); // Used for improve performance of aura state checks on aura apply/remove
|
||||
SortedSet<AuraApplication> m_visibleAuras = new SortedSet<AuraApplication>(new VisibleAuraSlotCompare());
|
||||
SortedSet<AuraApplication> m_visibleAurasToUpdate = new SortedSet<AuraApplication>(new VisibleAuraSlotCompare());
|
||||
MultiMap<uint, AuraApplication> m_appliedAuras = new MultiMap<uint, AuraApplication>();
|
||||
MultiMap<uint, Aura> m_ownedAuras = new MultiMap<uint, Aura>();
|
||||
List<Aura> m_scAuras = new List<Aura>();
|
||||
protected float[][] m_auraModifiersGroup = new float[(int)UnitMods.End][];
|
||||
uint m_removedAurasCount;
|
||||
|
||||
//General
|
||||
Array<DiminishingReturn> m_Diminishing = new Array<DiminishingReturn>((int)DiminishingGroup.Max);
|
||||
protected List<GameObject> m_gameObj = new List<GameObject>();
|
||||
List<AreaTrigger> m_areaTrigger = new List<AreaTrigger>();
|
||||
protected List<DynamicObject> m_dynObj = new List<DynamicObject>();
|
||||
protected float[] CreateStats = new float[(int)Stats.Max];
|
||||
public ObjectGuid[] m_SummonSlot = new ObjectGuid[7];
|
||||
public ObjectGuid[] m_ObjectSlot = new ObjectGuid[4];
|
||||
public EventSystem m_Events = new EventSystem();
|
||||
public UnitTypeMask m_unitTypeMask { get; set; }
|
||||
UnitState m_state;
|
||||
protected LiquidTypeRecord _lastLiquid;
|
||||
protected DeathState m_deathState;
|
||||
public Vehicle m_vehicle { get; set; }
|
||||
public Vehicle m_vehicleKit { get; set; }
|
||||
bool canModifyStats;
|
||||
public uint m_lastSanctuaryTime { get; set; }
|
||||
uint m_transform;
|
||||
bool m_cleanupDone; // lock made to not add stuff after cleanup before delete
|
||||
bool m_duringRemoveFromWorld; // lock made to not add stuff after begining removing from world
|
||||
|
||||
ushort _aiAnimKitId;
|
||||
ushort _movementAnimKitId;
|
||||
ushort _meleeAnimKitId;
|
||||
}
|
||||
|
||||
public struct SpellImmune
|
||||
{
|
||||
public uint spellType;
|
||||
public uint spellId;
|
||||
}
|
||||
|
||||
public class DiminishingReturn
|
||||
{
|
||||
public DiminishingReturn(uint hitTime, DiminishingLevels hitCount)
|
||||
{
|
||||
Stack = 0;
|
||||
HitTime = hitTime;
|
||||
HitCount = hitCount;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Stack = 0;
|
||||
HitTime = 0;
|
||||
HitCount = DiminishingLevels.Level1;
|
||||
}
|
||||
|
||||
public uint Stack;
|
||||
public uint HitTime;
|
||||
public DiminishingLevels HitCount;
|
||||
}
|
||||
|
||||
public class ProcEventInfo
|
||||
{
|
||||
public ProcEventInfo(Unit actor, Unit actionTarget, Unit procTarget, ProcFlags typeMask, ProcFlagsSpellType spellTypeMask,
|
||||
ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo)
|
||||
{
|
||||
_actor = actor;
|
||||
_actionTarget = actionTarget;
|
||||
_procTarget = procTarget;
|
||||
_typeMask = typeMask;
|
||||
_spellTypeMask = spellTypeMask;
|
||||
_spellPhaseMask = spellPhaseMask;
|
||||
_hitMask = hitMask;
|
||||
_spell = spell;
|
||||
_damageInfo = damageInfo;
|
||||
_healInfo = healInfo;
|
||||
}
|
||||
|
||||
public Unit GetActor() { return _actor; }
|
||||
public Unit GetActionTarget() { return _actionTarget; }
|
||||
public Unit GetProcTarget() { return _procTarget; }
|
||||
|
||||
public ProcFlags GetTypeMask() { return _typeMask; }
|
||||
public ProcFlagsSpellType GetSpellTypeMask() { return _spellTypeMask; }
|
||||
public ProcFlagsSpellPhase GetSpellPhaseMask() { return _spellPhaseMask; }
|
||||
public ProcFlagsHit GetHitMask() { return _hitMask; }
|
||||
|
||||
public SpellInfo GetSpellInfo()
|
||||
{
|
||||
if (_spell)
|
||||
return _spell.GetSpellInfo();
|
||||
if (_damageInfo != null)
|
||||
return _damageInfo.GetSpellInfo();
|
||||
if (_healInfo != null)
|
||||
return _healInfo.GetSpellInfo();
|
||||
|
||||
return null;
|
||||
}
|
||||
public SpellSchoolMask GetSchoolMask()
|
||||
{
|
||||
if (_spell)
|
||||
return _spell.GetSpellInfo().GetSchoolMask();
|
||||
if (_damageInfo != null)
|
||||
return _damageInfo.GetSchoolMask();
|
||||
if (_healInfo != null)
|
||||
return _healInfo.GetSchoolMask();
|
||||
|
||||
return SpellSchoolMask.None;
|
||||
}
|
||||
|
||||
public DamageInfo GetDamageInfo() { return _damageInfo; }
|
||||
public HealInfo GetHealInfo() { return _healInfo; }
|
||||
|
||||
public Spell GetProcSpell() { return _spell; }
|
||||
|
||||
Unit _actor;
|
||||
Unit _actionTarget;
|
||||
Unit _procTarget;
|
||||
ProcFlags _typeMask;
|
||||
ProcFlagsSpellType _spellTypeMask;
|
||||
ProcFlagsSpellPhase _spellPhaseMask;
|
||||
ProcFlagsHit _hitMask;
|
||||
Spell _spell;
|
||||
DamageInfo _damageInfo;
|
||||
HealInfo _healInfo;
|
||||
}
|
||||
|
||||
public class DamageInfo
|
||||
{
|
||||
public DamageInfo(Unit attacker, Unit victim, uint damage, SpellInfo spellInfo, SpellSchoolMask schoolMask, DamageEffectType damageType, WeaponAttackType attackType)
|
||||
{
|
||||
m_attacker = attacker;
|
||||
m_victim = victim;
|
||||
m_damage = damage;
|
||||
m_spellInfo = spellInfo;
|
||||
m_schoolMask = schoolMask;
|
||||
m_damageType = damageType;
|
||||
m_attackType = attackType;
|
||||
}
|
||||
|
||||
public DamageInfo(CalcDamageInfo dmgInfo)
|
||||
{
|
||||
m_attacker = dmgInfo.attacker;
|
||||
m_victim = dmgInfo.target;
|
||||
m_damage = dmgInfo.damage;
|
||||
m_spellInfo = null;
|
||||
m_schoolMask = (SpellSchoolMask)dmgInfo.damageSchoolMask;
|
||||
m_damageType = DamageEffectType.Direct;
|
||||
m_attackType = dmgInfo.attackType;
|
||||
m_absorb = dmgInfo.absorb;
|
||||
m_resist = dmgInfo.resist;
|
||||
m_block = dmgInfo.blocked_amount;
|
||||
|
||||
switch (dmgInfo.TargetState)
|
||||
{
|
||||
case VictimState.Immune:
|
||||
m_hitMask |= ProcFlagsHit.Immune;
|
||||
break;
|
||||
case VictimState.Blocks:
|
||||
m_hitMask |= ProcFlagsHit.FullBlock;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_absorb != 0)
|
||||
m_hitMask |= ProcFlagsHit.Absorb;
|
||||
|
||||
if (dmgInfo.HitInfo.HasAnyFlag(HitInfo.FullResist))
|
||||
m_hitMask |= ProcFlagsHit.FullResist;
|
||||
|
||||
if (m_block != 0)
|
||||
m_hitMask |= ProcFlagsHit.Block;
|
||||
|
||||
switch (dmgInfo.hitOutCome)
|
||||
{
|
||||
case MeleeHitOutcome.Miss:
|
||||
m_hitMask |= ProcFlagsHit.Miss;
|
||||
break;
|
||||
case MeleeHitOutcome.Dodge:
|
||||
m_hitMask |= ProcFlagsHit.Dodge;
|
||||
break;
|
||||
case MeleeHitOutcome.Parry:
|
||||
m_hitMask |= ProcFlagsHit.Parry;
|
||||
break;
|
||||
case MeleeHitOutcome.Evade:
|
||||
m_hitMask |= ProcFlagsHit.Evade;
|
||||
break;
|
||||
case MeleeHitOutcome.Crushing:
|
||||
case MeleeHitOutcome.Glancing:
|
||||
case MeleeHitOutcome.Normal:
|
||||
m_hitMask |= ProcFlagsHit.Normal;
|
||||
break;
|
||||
case MeleeHitOutcome.Crit:
|
||||
m_hitMask |= ProcFlagsHit.Critical;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public DamageInfo(SpellNonMeleeDamage spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, ProcFlagsHit hitMask)
|
||||
{
|
||||
m_attacker = spellNonMeleeDamage.attacker;
|
||||
m_victim = spellNonMeleeDamage.target;
|
||||
m_damage = spellNonMeleeDamage.damage;
|
||||
m_spellInfo = Global.SpellMgr.GetSpellInfo(spellNonMeleeDamage.SpellId);
|
||||
m_schoolMask = spellNonMeleeDamage.schoolMask;
|
||||
m_damageType = damageType;
|
||||
m_attackType = attackType;
|
||||
m_absorb = spellNonMeleeDamage.absorb;
|
||||
m_resist = spellNonMeleeDamage.resist;
|
||||
m_block = spellNonMeleeDamage.blocked;
|
||||
m_hitMask = hitMask;
|
||||
|
||||
if (spellNonMeleeDamage.blocked != 0)
|
||||
m_hitMask |= ProcFlagsHit.Block;
|
||||
if (spellNonMeleeDamage.absorb != 0)
|
||||
m_hitMask |= ProcFlagsHit.Absorb;
|
||||
}
|
||||
|
||||
public void ModifyDamage(int amount)
|
||||
{
|
||||
amount = Math.Min(amount, (int)GetDamage());
|
||||
m_damage += (uint)amount;
|
||||
}
|
||||
public void AbsorbDamage(uint amount)
|
||||
{
|
||||
amount = Math.Min(amount, GetDamage());
|
||||
m_absorb += amount;
|
||||
m_damage -= amount;
|
||||
m_hitMask |= ProcFlagsHit.Absorb;
|
||||
}
|
||||
public void ResistDamage(uint amount)
|
||||
{
|
||||
amount = Math.Min(amount, GetDamage());
|
||||
m_resist += amount;
|
||||
m_damage -= amount;
|
||||
if (m_damage == 0)
|
||||
m_hitMask |= ProcFlagsHit.FullResist;
|
||||
}
|
||||
void BlockDamage(uint amount)
|
||||
{
|
||||
amount = Math.Min(amount, GetDamage());
|
||||
m_block += amount;
|
||||
m_damage -= amount;
|
||||
m_hitMask |= ProcFlagsHit.Block;
|
||||
if (m_damage == 0)
|
||||
m_hitMask |= ProcFlagsHit.FullBlock;
|
||||
}
|
||||
|
||||
public Unit GetAttacker() { return m_attacker; }
|
||||
public Unit GetVictim() { return m_victim; }
|
||||
public SpellInfo GetSpellInfo() { return m_spellInfo; }
|
||||
public SpellSchoolMask GetSchoolMask() { return m_schoolMask; }
|
||||
DamageEffectType GetDamageType() { return m_damageType; }
|
||||
public WeaponAttackType GetAttackType() { return m_attackType; }
|
||||
public uint GetDamage() { return m_damage; }
|
||||
public uint GetAbsorb() { return m_absorb; }
|
||||
public uint GetResist() { return m_resist; }
|
||||
uint GetBlock() { return m_block; }
|
||||
public ProcFlagsHit GetHitMask() { return m_hitMask; }
|
||||
|
||||
Unit m_attacker;
|
||||
Unit m_victim;
|
||||
uint m_damage;
|
||||
SpellInfo m_spellInfo;
|
||||
SpellSchoolMask m_schoolMask;
|
||||
DamageEffectType m_damageType;
|
||||
WeaponAttackType m_attackType;
|
||||
uint m_absorb;
|
||||
uint m_resist;
|
||||
uint m_block;
|
||||
ProcFlagsHit m_hitMask;
|
||||
}
|
||||
|
||||
public class HealInfo
|
||||
{
|
||||
public HealInfo(Unit healer, Unit target, uint heal, SpellInfo spellInfo, SpellSchoolMask schoolMask)
|
||||
{
|
||||
_healer = healer;
|
||||
_target = target;
|
||||
_heal = heal;
|
||||
_spellInfo = spellInfo;
|
||||
_schoolMask = schoolMask;
|
||||
}
|
||||
|
||||
public void AbsorbHeal(uint amount)
|
||||
{
|
||||
amount = Math.Min(amount, GetHeal());
|
||||
_absorb += amount;
|
||||
_heal -= amount;
|
||||
amount = Math.Min(amount, GetEffectiveHeal());
|
||||
_effectiveHeal -= amount;
|
||||
_hitMask |= ProcFlagsHit.Absorb;
|
||||
}
|
||||
public void SetEffectiveHeal(uint amount) { _effectiveHeal = amount; }
|
||||
|
||||
public Unit GetHealer() { return _healer; }
|
||||
public Unit GetTarget() { return _target; }
|
||||
public uint GetHeal() { return _heal; }
|
||||
public uint GetEffectiveHeal() { return _effectiveHeal; }
|
||||
public uint GetAbsorb() { return _absorb; }
|
||||
public SpellInfo GetSpellInfo() { return _spellInfo; }
|
||||
public SpellSchoolMask GetSchoolMask() { return _schoolMask; }
|
||||
ProcFlagsHit GetHitMask() { return _hitMask; }
|
||||
|
||||
Unit _healer;
|
||||
Unit _target;
|
||||
uint _heal;
|
||||
uint _effectiveHeal;
|
||||
uint _absorb;
|
||||
SpellInfo _spellInfo;
|
||||
SpellSchoolMask _schoolMask;
|
||||
ProcFlagsHit _hitMask;
|
||||
}
|
||||
|
||||
public class CalcDamageInfo
|
||||
{
|
||||
public Unit attacker { get; set; } // Attacker
|
||||
public Unit target { get; set; } // Target for damage
|
||||
public uint damageSchoolMask { get; set; }
|
||||
public uint damage;
|
||||
public uint absorb;
|
||||
public uint resist;
|
||||
public uint blocked_amount { get; set; }
|
||||
public HitInfo HitInfo { get; set; }
|
||||
public VictimState TargetState { get; set; }
|
||||
// Helper
|
||||
public WeaponAttackType attackType { get; set; }
|
||||
public ProcFlags procAttacker { get; set; }
|
||||
public ProcFlags procVictim { get; set; }
|
||||
public uint cleanDamage { get; set; } // Used only for rage calculation
|
||||
public MeleeHitOutcome hitOutCome { get; set; } // TODO: remove this field (need use TargetState)
|
||||
}
|
||||
|
||||
public class SpellNonMeleeDamage
|
||||
{
|
||||
public SpellNonMeleeDamage(Unit _attacker, Unit _target, uint _SpellID, uint _SpellXSpellVisualID, SpellSchoolMask _schoolMask, ObjectGuid _castId = default(ObjectGuid))
|
||||
{
|
||||
target = _target;
|
||||
attacker = _attacker;
|
||||
SpellId = _SpellID;
|
||||
SpellXSpellVisualID = _SpellXSpellVisualID;
|
||||
schoolMask = _schoolMask;
|
||||
castId = _castId;
|
||||
preHitHealth = (uint)_target.GetHealth();
|
||||
}
|
||||
|
||||
public Unit target;
|
||||
public Unit attacker;
|
||||
public ObjectGuid castId;
|
||||
public uint SpellId;
|
||||
public uint SpellXSpellVisualID;
|
||||
public uint damage;
|
||||
public SpellSchoolMask schoolMask;
|
||||
public uint absorb;
|
||||
public uint resist;
|
||||
public bool periodicLog;
|
||||
public uint blocked;
|
||||
public SpellHitType HitInfo;
|
||||
// Used for help
|
||||
public uint cleanDamage;
|
||||
public uint preHitHealth;
|
||||
}
|
||||
|
||||
public class CleanDamage
|
||||
{
|
||||
public CleanDamage(uint mitigated, uint absorbed, WeaponAttackType _attackType, MeleeHitOutcome _hitOutCome)
|
||||
{
|
||||
absorbed_damage = absorbed;
|
||||
mitigated_damage = mitigated;
|
||||
attackType = _attackType;
|
||||
hitOutCome = _hitOutCome;
|
||||
}
|
||||
|
||||
public uint absorbed_damage { get; set; }
|
||||
public uint mitigated_damage { get; set; }
|
||||
|
||||
public WeaponAttackType attackType { get; set; }
|
||||
public MeleeHitOutcome hitOutCome { get; set; }
|
||||
}
|
||||
|
||||
public class DispelInfo
|
||||
{
|
||||
public DispelInfo(Unit dispeller, uint dispellerSpellId, byte chargesRemoved)
|
||||
{
|
||||
_dispellerUnit = dispeller;
|
||||
_dispellerSpell = dispellerSpellId;
|
||||
_chargesRemoved = chargesRemoved;
|
||||
}
|
||||
|
||||
public Unit GetDispeller() { return _dispellerUnit; }
|
||||
uint GetDispellerSpellId() { return _dispellerSpell; }
|
||||
public byte GetRemovedCharges() { return _chargesRemoved; }
|
||||
void SetRemovedCharges(byte amount)
|
||||
{
|
||||
_chargesRemoved = amount;
|
||||
}
|
||||
|
||||
Unit _dispellerUnit;
|
||||
uint _dispellerSpell;
|
||||
byte _chargesRemoved;
|
||||
}
|
||||
|
||||
public struct RedirectThreatInfo
|
||||
{
|
||||
ObjectGuid _targetGUID;
|
||||
uint _threatPct;
|
||||
|
||||
public ObjectGuid GetTargetGUID() { return _targetGUID; }
|
||||
public uint GetThreatPct() { return _threatPct; }
|
||||
|
||||
public void Set(ObjectGuid guid, uint pct)
|
||||
{
|
||||
_targetGUID = guid;
|
||||
_threatPct = pct;
|
||||
}
|
||||
|
||||
public void ModifyThreatPct(int amount)
|
||||
{
|
||||
amount += (int)_threatPct;
|
||||
_threatPct = (uint)(Math.Max(0, amount));
|
||||
}
|
||||
}
|
||||
|
||||
public class SpellPeriodicAuraLogInfo
|
||||
{
|
||||
public SpellPeriodicAuraLogInfo(AuraEffect _auraEff, uint _damage, int _overDamage, uint _absorb, uint _resist, float _multiplier, bool _critical)
|
||||
{
|
||||
auraEff = _auraEff;
|
||||
damage = _damage;
|
||||
overDamage = _overDamage;
|
||||
absorb = _absorb;
|
||||
resist = _resist;
|
||||
multiplier = _multiplier;
|
||||
critical = _critical;
|
||||
}
|
||||
|
||||
public AuraEffect auraEff;
|
||||
public uint damage;
|
||||
public int overDamage; // overkill/overheal
|
||||
public uint absorb;
|
||||
public uint resist;
|
||||
public float multiplier;
|
||||
public bool critical;
|
||||
}
|
||||
|
||||
class VisibleAuraSlotCompare : IComparer<AuraApplication>
|
||||
{
|
||||
public int Compare(AuraApplication x, AuraApplication y)
|
||||
{
|
||||
return x.GetSlot().CompareTo(y.GetSlot());
|
||||
}
|
||||
}
|
||||
|
||||
public class DeclinedName
|
||||
{
|
||||
public StringArray name = new StringArray(SharedConst.MaxDeclinedNameCases);
|
||||
}
|
||||
|
||||
class CombatLogSender : Notifier
|
||||
{
|
||||
public CombatLogSender(WorldObject src, CombatLogServerPacket msg, float dist)
|
||||
{
|
||||
i_source = src;
|
||||
i_message = msg;
|
||||
i_distSq = dist * dist;
|
||||
}
|
||||
|
||||
bool IsInRangeHelper(WorldObject obj)
|
||||
{
|
||||
if (!obj.IsInPhase(i_source))
|
||||
return false;
|
||||
|
||||
return obj.GetExactDist2dSq(i_source) <= i_distSq;
|
||||
}
|
||||
|
||||
public override void Visit(ICollection<Player> objs)
|
||||
{
|
||||
foreach (var target in objs)
|
||||
{
|
||||
if (!IsInRangeHelper(target))
|
||||
continue;
|
||||
|
||||
// Send packet to all who are sharing the player's vision
|
||||
if (target.HasSharedVision())
|
||||
{
|
||||
foreach (var visionTarget in target.GetSharedVisionList())
|
||||
if (visionTarget.seerView == target)
|
||||
SendPacket(visionTarget);
|
||||
}
|
||||
|
||||
if (target.seerView == target || target.GetVehicle())
|
||||
SendPacket(target);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Visit(ICollection<Creature> objs)
|
||||
{
|
||||
foreach (var target in objs)
|
||||
{
|
||||
if (!IsInRangeHelper(target))
|
||||
continue;
|
||||
|
||||
// Send packet to all who are sharing the creature's vision
|
||||
if (target.HasSharedVision())
|
||||
{
|
||||
foreach (var visionTarget in target.GetSharedVisionList())
|
||||
if (visionTarget.seerView == target)
|
||||
SendPacket(visionTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void Visit(ICollection<DynamicObject> objs)
|
||||
{
|
||||
foreach (var target in objs)
|
||||
{
|
||||
if (!IsInRangeHelper(target))
|
||||
continue;
|
||||
|
||||
Unit caster = target.GetCaster();
|
||||
if (caster)
|
||||
{
|
||||
// Send packet back to the caster if the caster has vision of dynamic object
|
||||
Player player = caster.ToPlayer();
|
||||
if (player && player.seerView == target)
|
||||
SendPacket(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendPacket(Player player)
|
||||
{
|
||||
if (!player.HaveAtClient(i_source))
|
||||
return;
|
||||
|
||||
if (!player.IsAdvancedCombatLoggingEnabled())
|
||||
i_message.DisableAdvancedCombatLogging();
|
||||
|
||||
player.SendPacket(i_message);
|
||||
}
|
||||
|
||||
WorldObject i_source;
|
||||
CombatLogServerPacket i_message;
|
||||
float i_distSq;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.AI;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Unit
|
||||
{
|
||||
public void AddPetAura(PetAura petSpell)
|
||||
{
|
||||
if (!IsTypeId(TypeId.Player))
|
||||
return;
|
||||
|
||||
m_petAuras.Add(petSpell);
|
||||
Pet pet = ToPlayer().GetPet();
|
||||
if (pet)
|
||||
pet.CastPetAura(petSpell);
|
||||
}
|
||||
|
||||
public void RemovePetAura(PetAura petSpell)
|
||||
{
|
||||
if (!IsTypeId(TypeId.Player))
|
||||
return;
|
||||
|
||||
m_petAuras.Remove(petSpell);
|
||||
Pet pet = ToPlayer().GetPet();
|
||||
if (pet)
|
||||
pet.RemoveAurasDueToSpell(petSpell.GetAura(pet.GetEntry()));
|
||||
}
|
||||
|
||||
public CharmInfo GetCharmInfo() { return m_charmInfo; }
|
||||
|
||||
public CharmInfo InitCharmInfo()
|
||||
{
|
||||
if (m_charmInfo == null)
|
||||
m_charmInfo = new CharmInfo(this);
|
||||
|
||||
return m_charmInfo;
|
||||
}
|
||||
|
||||
void DeleteCharmInfo()
|
||||
{
|
||||
if (m_charmInfo == null)
|
||||
return;
|
||||
|
||||
m_charmInfo.RestoreState();
|
||||
m_charmInfo = null;
|
||||
}
|
||||
|
||||
public void UpdateCharmAI()
|
||||
{
|
||||
switch (GetTypeId())
|
||||
{
|
||||
case TypeId.Unit:
|
||||
if (i_disabledAI != null) // disabled AI must be primary AI
|
||||
{
|
||||
if (!IsCharmed())
|
||||
{
|
||||
i_AI = i_disabledAI;
|
||||
i_disabledAI = null;
|
||||
|
||||
if (IsTypeId(TypeId.Unit))
|
||||
ToCreature().GetAI().OnCharmed(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsCharmed())
|
||||
{
|
||||
i_disabledAI = i_AI;
|
||||
if (isPossessed() || IsVehicle())
|
||||
i_AI = new PossessedAI(ToCreature());
|
||||
else
|
||||
i_AI = new PetAI(ToCreature());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TypeId.Player:
|
||||
{
|
||||
if (IsCharmed()) // if we are currently being charmed, then we should apply charm AI
|
||||
{
|
||||
i_disabledAI = i_AI;
|
||||
|
||||
UnitAI newAI = null;
|
||||
// first, we check if the creature's own AI specifies an override playerai for its owned players
|
||||
Unit charmer = GetCharmer();
|
||||
if (charmer)
|
||||
{
|
||||
Creature creatureCharmer = charmer.ToCreature();
|
||||
if (creatureCharmer)
|
||||
{
|
||||
PlayerAI charmAI = creatureCharmer.IsAIEnabled ? creatureCharmer.GetAI().GetAIForCharmedPlayer(ToPlayer()) : null;
|
||||
if (charmAI != null)
|
||||
newAI = charmAI;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.outError(LogFilter.Misc, "Attempt to assign charm AI to player {0} who is charmed by non-creature {1}.", GetGUID().ToString(), GetCharmerGUID().ToString());
|
||||
}
|
||||
}
|
||||
if (newAI == null) // otherwise, we default to the generic one
|
||||
newAI = new SimpleCharmedPlayerAI(ToPlayer());
|
||||
i_AI = newAI;
|
||||
newAI.OnCharmed(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i_AI != null)
|
||||
{
|
||||
// we allow the charmed PlayerAI to clean up
|
||||
i_AI.OnCharmed(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.outError(LogFilter.Misc, "Attempt to remove charm AI from player {0} who doesn't currently have charm AI.", GetGUID().ToString());
|
||||
}
|
||||
// and restore our previous PlayerAI (if we had one)
|
||||
i_AI = i_disabledAI;
|
||||
i_disabledAI = null;
|
||||
// IsAIEnabled gets handled in the caller
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log.outError(LogFilter.Misc, "Attempt to update charm AI for unit {0}, which is neither player nor creature.", GetGUID().ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SetMinion(Minion minion, bool apply)
|
||||
{
|
||||
Log.outDebug(LogFilter.Unit, "SetMinion {0} for {1}, apply {2}", minion.GetEntry(), GetEntry(), apply);
|
||||
|
||||
if (apply)
|
||||
{
|
||||
if (!minion.GetOwnerGUID().IsEmpty())
|
||||
{
|
||||
Log.outFatal(LogFilter.Unit, "SetMinion: Minion {0} is not the minion of owner {1}", minion.GetEntry(), GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
minion.SetOwnerGUID(GetGUID());
|
||||
|
||||
m_Controlled.Add(minion);
|
||||
|
||||
if (IsTypeId(TypeId.Player))
|
||||
{
|
||||
minion.m_ControlledByPlayer = true;
|
||||
minion.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable);
|
||||
}
|
||||
|
||||
// Can only have one pet. If a new one is summoned, dismiss the old one.
|
||||
if (minion.IsGuardianPet())
|
||||
{
|
||||
Guardian oldPet = GetGuardianPet();
|
||||
if (oldPet)
|
||||
{
|
||||
if (oldPet != minion && (oldPet.IsPet() || minion.IsPet() || oldPet.GetEntry() != minion.GetEntry()))
|
||||
{
|
||||
// remove existing minion pet
|
||||
if (oldPet.IsPet())
|
||||
((Pet)oldPet).Remove(PetSaveMode.AsCurrent);
|
||||
else
|
||||
oldPet.UnSummon();
|
||||
SetPetGUID(minion.GetGUID());
|
||||
SetMinionGUID(ObjectGuid.Empty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPetGUID(minion.GetGUID());
|
||||
SetMinionGUID(ObjectGuid.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (minion.HasUnitTypeMask(UnitTypeMask.Guardian))
|
||||
AddGuidValue(UnitFields.Summon, minion.GetGUID());
|
||||
|
||||
if (minion.m_Properties != null && minion.m_Properties.Type == SummonType.Minipet)
|
||||
SetCritterGUID(minion.GetGUID());
|
||||
|
||||
// PvP, FFAPvP
|
||||
minion.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag));
|
||||
|
||||
// FIXME: hack, speed must be set only at follow
|
||||
if (IsTypeId(TypeId.Player) && minion.IsPet())
|
||||
for (UnitMoveType i = 0; i < UnitMoveType.Max; ++i)
|
||||
minion.SetSpeedRate(i, m_speed_rate[(int)i]);
|
||||
|
||||
// Ghoul pets have energy instead of mana (is anywhere better place for this code?)
|
||||
if (minion.IsPetGhoul())
|
||||
minion.setPowerType(PowerType.Energy);
|
||||
|
||||
// Send infinity cooldown - client does that automatically but after relog cooldown needs to be set again
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(minion.GetUInt32Value(UnitFields.CreatedBySpell));
|
||||
if (spellInfo != null && spellInfo.IsCooldownStartedOnEvent())
|
||||
GetSpellHistory().StartCooldown(spellInfo, 0, null, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (minion.GetOwnerGUID() != GetGUID())
|
||||
{
|
||||
Log.outFatal(LogFilter.Unit, "SetMinion: Minion {0} is not the minion of owner {1}", minion.GetEntry(), GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
m_Controlled.Remove(minion);
|
||||
|
||||
if (minion.m_Properties != null && minion.m_Properties.Type == SummonType.Minipet)
|
||||
{
|
||||
if (GetCritterGUID() == minion.GetGUID())
|
||||
SetCritterGUID(ObjectGuid.Empty);
|
||||
}
|
||||
|
||||
if (minion.IsGuardianPet())
|
||||
{
|
||||
if (GetPetGUID() == minion.GetGUID())
|
||||
SetPetGUID(ObjectGuid.Empty);
|
||||
}
|
||||
else if (minion.IsTotem())
|
||||
{
|
||||
// All summoned by totem minions must disappear when it is removed.
|
||||
SpellInfo spInfo = Global.SpellMgr.GetSpellInfo(minion.ToTotem().GetSpell());
|
||||
if (spInfo != null)
|
||||
{
|
||||
foreach (SpellEffectInfo effect in spInfo.GetEffectsForDifficulty(Difficulty.None))
|
||||
{
|
||||
if (effect == null || effect.Effect != SpellEffectName.Summon)
|
||||
continue;
|
||||
|
||||
RemoveAllMinionsByEntry((uint)effect.MiscValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(minion.GetUInt32Value(UnitFields.CreatedBySpell));
|
||||
// Remove infinity cooldown
|
||||
if (spellInfo != null && spellInfo.IsCooldownStartedOnEvent())
|
||||
GetSpellHistory().SendCooldownEvent(spellInfo);
|
||||
|
||||
if (RemoveGuidValue(UnitFields.Summon, minion.GetGUID()))
|
||||
{
|
||||
// Check if there is another minion
|
||||
foreach (var unit in m_Controlled)
|
||||
{
|
||||
// do not use this check, creature do not have charm guid
|
||||
if (GetGUID() == unit.GetCharmerGUID())
|
||||
continue;
|
||||
|
||||
Contract.Assert(unit.GetOwnerGUID() == GetGUID());
|
||||
if (unit.GetOwnerGUID() != GetGUID())
|
||||
{
|
||||
Contract.Assert(false);
|
||||
}
|
||||
Contract.Assert(unit.IsTypeId(TypeId.Unit));
|
||||
|
||||
if (!unit.HasUnitTypeMask(UnitTypeMask.Guardian))
|
||||
continue;
|
||||
|
||||
if (AddGuidValue(UnitFields.Summon, unit.GetGUID()))
|
||||
{
|
||||
// show another pet bar if there is no charm bar
|
||||
if (IsTypeId(TypeId.Player) && GetCharmGUID().IsEmpty())
|
||||
{
|
||||
if (unit.IsPet())
|
||||
ToPlayer().PetSpellInitialize();
|
||||
else
|
||||
ToPlayer().CharmSpellInitialize();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetCharmedBy(Unit charmer, CharmType type, AuraApplication aurApp = null)
|
||||
{
|
||||
if (!charmer)
|
||||
return false;
|
||||
|
||||
// dismount players when charmed
|
||||
if (IsTypeId(TypeId.Player))
|
||||
RemoveAurasByType(AuraType.Mounted);
|
||||
|
||||
if (charmer.IsTypeId(TypeId.Player))
|
||||
charmer.RemoveAurasByType(AuraType.Mounted);
|
||||
|
||||
Contract.Assert(type != CharmType.Possess || charmer.IsTypeId(TypeId.Player));
|
||||
Contract.Assert((type == CharmType.Vehicle) == IsVehicle());
|
||||
|
||||
Log.outDebug(LogFilter.Unit, "SetCharmedBy: charmer {0} (GUID {1}), charmed {2} (GUID {3}), type {4}.", charmer.GetEntry(), charmer.GetGUID().ToString(), GetEntry(), GetGUID().ToString(), type);
|
||||
|
||||
if (this == charmer)
|
||||
{
|
||||
Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: Unit {0} (GUID {1}) is trying to charm itself!", GetEntry(), GetGUID().ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsTypeId(TypeId.Player) && ToPlayer().GetTransport())
|
||||
{
|
||||
Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: Player on transport is trying to charm {0} (GUID {1})", GetEntry(), GetGUID().ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Already charmed
|
||||
if (!GetCharmerGUID().IsEmpty())
|
||||
{
|
||||
Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: {0} (GUID {1}) has already been charmed but {2} (GUID {3}) is trying to charm it!", GetEntry(), GetGUID().ToString(), charmer.GetEntry(), charmer.GetGUID().ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
CastStop();
|
||||
CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells)
|
||||
DeleteThreatList();
|
||||
|
||||
Player playerCharmer = charmer.ToPlayer();
|
||||
|
||||
// Charmer stop charming
|
||||
if (playerCharmer)
|
||||
{
|
||||
playerCharmer.StopCastingCharm();
|
||||
playerCharmer.StopCastingBindSight();
|
||||
}
|
||||
|
||||
// Charmed stop charming
|
||||
if (IsTypeId(TypeId.Player))
|
||||
{
|
||||
ToPlayer().StopCastingCharm();
|
||||
ToPlayer().StopCastingBindSight();
|
||||
}
|
||||
|
||||
// StopCastingCharm may remove a possessed pet?
|
||||
if (!IsInWorld)
|
||||
{
|
||||
Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: {0} (GUID {1}) is not in world but {2} (GUID {3}) is trying to charm it!", GetEntry(), GetGUID().ToString(), charmer.GetEntry(), charmer.GetGUID().ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// charm is set by aura, and aura effect remove handler was called during apply handler execution
|
||||
// prevent undefined behaviour
|
||||
if (aurApp != null && aurApp.GetRemoveMode() != 0)
|
||||
return false;
|
||||
|
||||
_oldFactionId = getFaction();
|
||||
SetFaction(charmer.getFaction());
|
||||
|
||||
// Set charmed
|
||||
charmer.SetCharm(this, true);
|
||||
|
||||
Player player;
|
||||
if (IsTypeId(TypeId.Unit))
|
||||
{
|
||||
ToCreature().GetAI().OnCharmed(true);
|
||||
GetMotionMaster().MoveIdle();
|
||||
}
|
||||
else if (player = ToPlayer())
|
||||
{
|
||||
if (player.isAFK())
|
||||
player.ToggleAFK();
|
||||
|
||||
Creature creatureCharmer = charmer.ToCreature();
|
||||
if (charmer.IsTypeId(TypeId.Unit)) // we are charmed by a creature
|
||||
{
|
||||
// change AI to charmed AI on next Update tick
|
||||
NeedChangeAI = true;
|
||||
if (IsAIEnabled)
|
||||
{
|
||||
IsAIEnabled = false;
|
||||
player.GetAI().OnCharmed(true);
|
||||
}
|
||||
}
|
||||
|
||||
player.SetClientControl(this, false);
|
||||
}
|
||||
|
||||
// charm is set by aura, and aura effect remove handler was called during apply handler execution
|
||||
// prevent undefined behaviour
|
||||
if (aurApp != null && aurApp.GetRemoveMode() != 0)
|
||||
return false;
|
||||
|
||||
// Pets already have a properly initialized CharmInfo, don't overwrite it.
|
||||
if (type != CharmType.Vehicle && GetCharmInfo() == null)
|
||||
{
|
||||
InitCharmInfo();
|
||||
if (type == CharmType.Possess)
|
||||
GetCharmInfo().InitPossessCreateSpells();
|
||||
else
|
||||
GetCharmInfo().InitCharmCreateSpells();
|
||||
}
|
||||
|
||||
if (playerCharmer)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CharmType.Vehicle:
|
||||
SetFlag(UnitFields.Flags, UnitFlags.PlayerControlled);
|
||||
playerCharmer.SetClientControl(this, true);
|
||||
playerCharmer.VehicleSpellInitialize();
|
||||
break;
|
||||
case CharmType.Possess:
|
||||
AddUnitState(UnitState.Possessed);
|
||||
SetFlag(UnitFields.Flags, UnitFlags.PlayerControlled);
|
||||
charmer.SetFlag(UnitFields.Flags, UnitFlags.RemoveClientControl);
|
||||
playerCharmer.SetClientControl(this, true);
|
||||
playerCharmer.PossessSpellInitialize();
|
||||
break;
|
||||
case CharmType.Charm:
|
||||
if (IsTypeId(TypeId.Unit) && charmer.GetClass() == Class.Warlock)
|
||||
{
|
||||
CreatureTemplate cinfo = ToCreature().GetCreatureTemplate();
|
||||
if (cinfo != null && cinfo.CreatureType == CreatureType.Demon)
|
||||
{
|
||||
// to prevent client crash
|
||||
SetByteValue(UnitFields.Bytes0, 1, (byte)Class.Mage);
|
||||
|
||||
// just to enable stat window
|
||||
if (GetCharmInfo() != null)
|
||||
GetCharmInfo().SetPetNumber(Global.ObjectMgr.GeneratePetNumber(), true);
|
||||
|
||||
// if charmed two demons the same session, the 2nd gets the 1st one's name
|
||||
SetUInt32Value(UnitFields.PetNameTimestamp, (uint)Time.UnixTime); // cast can't be helped
|
||||
}
|
||||
}
|
||||
playerCharmer.CharmSpellInitialize();
|
||||
break;
|
||||
default:
|
||||
case CharmType.Convert:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveCharmedBy(Unit charmer)
|
||||
{
|
||||
if (!IsCharmed())
|
||||
return;
|
||||
|
||||
if (!charmer)
|
||||
charmer = GetCharmer();
|
||||
if (charmer != GetCharmer()) // one aura overrides another?
|
||||
return;
|
||||
|
||||
CharmType type;
|
||||
if (HasUnitState(UnitState.Possessed))
|
||||
type = CharmType.Possess;
|
||||
else if (charmer && charmer.IsOnVehicle(this))
|
||||
type = CharmType.Vehicle;
|
||||
else
|
||||
type = CharmType.Charm;
|
||||
|
||||
CastStop();
|
||||
CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells)
|
||||
getHostileRefManager().deleteReferences();
|
||||
DeleteThreatList();
|
||||
|
||||
if (_oldFactionId != 0)
|
||||
{
|
||||
SetFaction(_oldFactionId);
|
||||
_oldFactionId = 0;
|
||||
}
|
||||
else
|
||||
RestoreFaction();
|
||||
|
||||
GetMotionMaster().InitDefault();
|
||||
|
||||
Creature creature = ToCreature();
|
||||
if (creature)
|
||||
{
|
||||
// Creature will restore its old AI on next update
|
||||
if (creature.GetAI() != null)
|
||||
creature.GetAI().OnCharmed(false);
|
||||
|
||||
// Vehicle should not attack its passenger after he exists the seat
|
||||
if (type != CharmType.Vehicle)
|
||||
LastCharmerGUID = charmer ? charmer.GetGUID() : ObjectGuid.Empty;
|
||||
}
|
||||
|
||||
// If charmer still exists
|
||||
if (!charmer)
|
||||
return;
|
||||
|
||||
Contract.Assert(type != CharmType.Possess || charmer.IsTypeId(TypeId.Player));
|
||||
Contract.Assert(type != CharmType.Vehicle || (IsTypeId(TypeId.Unit) && IsVehicle()));
|
||||
|
||||
charmer.SetCharm(this, false);
|
||||
|
||||
Player playerCharmer = charmer.ToPlayer();
|
||||
if (playerCharmer)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CharmType.Vehicle:
|
||||
playerCharmer.SetClientControl(this, false);
|
||||
playerCharmer.SetClientControl(charmer, true);
|
||||
RemoveFlag(UnitFields.Flags, UnitFlags.PlayerControlled);
|
||||
break;
|
||||
case CharmType.Possess:
|
||||
playerCharmer.SetClientControl(this, false);
|
||||
playerCharmer.SetClientControl(charmer, true);
|
||||
charmer.RemoveFlag(UnitFields.Flags, UnitFlags.RemoveClientControl);
|
||||
RemoveFlag(UnitFields.Flags, UnitFlags.PlayerControlled);
|
||||
ClearUnitState(UnitState.Possessed);
|
||||
break;
|
||||
case CharmType.Charm:
|
||||
if (IsTypeId(TypeId.Unit) && charmer.GetClass() == Class.Warlock)
|
||||
{
|
||||
CreatureTemplate cinfo = ToCreature().GetCreatureTemplate();
|
||||
if (cinfo != null && cinfo.CreatureType == CreatureType.Demon)
|
||||
{
|
||||
SetByteValue(UnitFields.Bytes0, 1, (byte)cinfo.UnitClass);
|
||||
if (GetCharmInfo() != null)
|
||||
GetCharmInfo().SetPetNumber(0, true);
|
||||
else
|
||||
Log.outError(LogFilter.Unit, "Aura:HandleModCharm: target={0} with typeid={1} has a charm aura but no charm info!", GetGUID(), GetTypeId());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CharmType.Convert:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Player player = ToPlayer();
|
||||
if (player)
|
||||
{
|
||||
if (charmer.IsTypeId(TypeId.Unit)) // charmed by a creature, this means we had PlayerAI
|
||||
{
|
||||
NeedChangeAI = true;
|
||||
IsAIEnabled = false;
|
||||
}
|
||||
player.SetClientControl(this, true);
|
||||
}
|
||||
|
||||
// a guardian should always have charminfo
|
||||
if (playerCharmer && this != charmer.GetFirstControlled())
|
||||
playerCharmer.SendRemoveControlBar();
|
||||
else if (IsTypeId(TypeId.Player) || (IsTypeId(TypeId.Unit) && !IsGuardian()))
|
||||
DeleteCharmInfo();
|
||||
}
|
||||
|
||||
public void GetAllMinionsByEntry(List<TempSummon> Minions, uint entry)
|
||||
{
|
||||
foreach (var unit in m_Controlled)
|
||||
{
|
||||
if (unit.GetEntry() == entry && unit.IsSummon()) // minion, actually
|
||||
Minions.Add(unit.ToTempSummon());
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveAllMinionsByEntry(uint entry)
|
||||
{
|
||||
foreach (var unit in m_Controlled.ToList())
|
||||
{
|
||||
if (unit.GetEntry() == entry && unit.IsTypeId(TypeId.Unit)
|
||||
&& unit.ToCreature().IsSummon()) // minion, actually
|
||||
unit.ToTempSummon().UnSummon();
|
||||
// i think this is safe because i have never heard that a despawned minion will trigger a same minion
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCharm(Unit charm, bool apply)
|
||||
{
|
||||
if (apply)
|
||||
{
|
||||
if (IsTypeId(TypeId.Player))
|
||||
{
|
||||
if (!AddGuidValue(UnitFields.Charm, charm.GetGUID()))
|
||||
Log.outFatal(LogFilter.Unit, "Player {0} is trying to charm unit {1}, but it already has a charmed unit {2}", GetName(), charm.GetEntry(), GetCharmGUID());
|
||||
|
||||
charm.m_ControlledByPlayer = true;
|
||||
// @todo maybe we can use this flag to check if controlled by player
|
||||
charm.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable);
|
||||
}
|
||||
else
|
||||
charm.m_ControlledByPlayer = false;
|
||||
|
||||
// PvP, FFAPvP
|
||||
charm.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag));
|
||||
|
||||
if (!charm.AddGuidValue(UnitFields.CharmedBy, GetGUID()))
|
||||
Log.outFatal(LogFilter.Unit, "Unit {0} is being charmed, but it already has a charmer {1}", charm.GetEntry(), charm.GetCharmerGUID());
|
||||
|
||||
_isWalkingBeforeCharm = charm.IsWalking();
|
||||
if (_isWalkingBeforeCharm)
|
||||
charm.SetWalk(false);
|
||||
|
||||
m_Controlled.Add(charm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsTypeId(TypeId.Player))
|
||||
{
|
||||
if (!RemoveGuidValue(UnitFields.Charm, charm.GetGUID()))
|
||||
Log.outFatal(LogFilter.Unit, "Player {0} is trying to uncharm unit {1}, but it has another charmed unit {2}", GetName(), charm.GetEntry(), GetCharmGUID());
|
||||
}
|
||||
|
||||
if (!charm.RemoveGuidValue(UnitFields.CharmedBy, GetGUID()))
|
||||
Log.outFatal(LogFilter.Unit, "Unit {0} is being uncharmed, but it has another charmer {1}", charm.GetEntry(), charm.GetCharmerGUID());
|
||||
Player player = charm.GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (charm.IsTypeId(TypeId.Player))
|
||||
{
|
||||
charm.m_ControlledByPlayer = true;
|
||||
charm.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable);
|
||||
charm.ToPlayer().UpdatePvPState();
|
||||
}
|
||||
else if (player)
|
||||
{
|
||||
charm.m_ControlledByPlayer = true;
|
||||
charm.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable);
|
||||
charm.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, player.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag));
|
||||
}
|
||||
else
|
||||
{
|
||||
charm.m_ControlledByPlayer = false;
|
||||
charm.RemoveFlag(UnitFields.Flags, UnitFlags.PvpAttackable);
|
||||
charm.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, 0);
|
||||
}
|
||||
|
||||
if (charm.IsWalking() != _isWalkingBeforeCharm)
|
||||
charm.SetWalk(_isWalkingBeforeCharm);
|
||||
|
||||
if (charm.IsTypeId(TypeId.Player) || !charm.ToCreature().HasUnitTypeMask(UnitTypeMask.Minion)
|
||||
|| charm.GetOwnerGUID() != GetGUID())
|
||||
{
|
||||
m_Controlled.Remove(charm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Unit GetFirstControlled()
|
||||
{
|
||||
// Sequence: charmed, pet, other guardians
|
||||
Unit unit = GetCharm();
|
||||
if (!unit)
|
||||
{
|
||||
ObjectGuid guid = GetMinionGUID();
|
||||
if (!guid.IsEmpty())
|
||||
unit = Global.ObjAccessor.GetUnit(this, guid);
|
||||
}
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void RemoveCharmAuras()
|
||||
{
|
||||
RemoveAurasByType(AuraType.ModCharm);
|
||||
RemoveAurasByType(AuraType.ModPossessPet);
|
||||
RemoveAurasByType(AuraType.ModPossess);
|
||||
RemoveAurasByType(AuraType.AoeCharm);
|
||||
}
|
||||
|
||||
public void RemoveAllControlled()
|
||||
{
|
||||
// possessed pet and vehicle
|
||||
if (IsTypeId(TypeId.Player))
|
||||
ToPlayer().StopCastingCharm();
|
||||
|
||||
while (!m_Controlled.Empty())
|
||||
{
|
||||
Unit target = m_Controlled.First();
|
||||
m_Controlled.RemoveAt(0);
|
||||
if (target.GetCharmerGUID() == GetGUID())
|
||||
target.RemoveCharmAuras();
|
||||
else if (target.GetOwnerGUID() == GetGUID() && target.IsSummon())
|
||||
target.ToTempSummon().UnSummon();
|
||||
else
|
||||
Log.outError(LogFilter.Unit, "Unit {0} is trying to release unit {1} which is neither charmed nor owned by it", GetEntry(), target.GetEntry());
|
||||
}
|
||||
if (!GetPetGUID().IsEmpty())
|
||||
Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its pet {1}", GetEntry(), GetPetGUID());
|
||||
if (!GetMinionGUID().IsEmpty())
|
||||
Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its minion {1}", GetEntry(), GetMinionGUID());
|
||||
if (!GetCharmGUID().IsEmpty())
|
||||
Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its charm {1}", GetEntry(), GetCharmGUID());
|
||||
}
|
||||
|
||||
public void SendPetActionFeedback(uint spellId, ActionFeedback msg)
|
||||
{
|
||||
Unit owner = GetOwner();
|
||||
if (!owner || !owner.IsTypeId(TypeId.Player))
|
||||
return;
|
||||
|
||||
PetActionFeedback petActionFeedback = new PetActionFeedback();
|
||||
petActionFeedback.SpellID = spellId;
|
||||
petActionFeedback.Response = msg;
|
||||
owner.ToPlayer().SendPacket(petActionFeedback);
|
||||
}
|
||||
|
||||
public void SendPetTalk(PetTalk pettalk)
|
||||
{
|
||||
Unit owner = GetOwner();
|
||||
if (!owner || !owner.IsTypeId(TypeId.Player))
|
||||
return;
|
||||
|
||||
PetActionSound petActionSound = new PetActionSound();
|
||||
petActionSound.UnitGUID = GetGUID();
|
||||
petActionSound.Action = pettalk;
|
||||
owner.ToPlayer().SendPacket(petActionSound);
|
||||
}
|
||||
|
||||
public void SendPetAIReaction(ObjectGuid guid)
|
||||
{
|
||||
Unit owner = GetOwner();
|
||||
if (!owner || !owner.IsTypeId(TypeId.Player))
|
||||
return;
|
||||
|
||||
AIReaction packet = new AIReaction();
|
||||
packet.UnitGUID = guid;
|
||||
packet.Reaction = AiReaction.Hostile;
|
||||
|
||||
owner.ToPlayer().SendPacket(packet);
|
||||
}
|
||||
|
||||
public Pet CreateTamedPetFrom(Creature creatureTarget, uint spell_id = 0)
|
||||
{
|
||||
if (!IsTypeId(TypeId.Player))
|
||||
return null;
|
||||
|
||||
Pet pet = new Pet(ToPlayer(), PetType.Hunter);
|
||||
|
||||
if (!pet.CreateBaseAtCreature(creatureTarget))
|
||||
return null;
|
||||
|
||||
uint level = creatureTarget.GetLevelForTarget(this) + 5 < getLevel() ? (getLevel() - 5) : creatureTarget.GetLevelForTarget(this);
|
||||
|
||||
InitTamedPet(pet, level, spell_id);
|
||||
|
||||
return pet;
|
||||
}
|
||||
|
||||
public Pet CreateTamedPetFrom(uint creatureEntry, uint spell_id = 0)
|
||||
{
|
||||
if (!IsTypeId(TypeId.Player))
|
||||
return null;
|
||||
|
||||
CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creatureEntry);
|
||||
if (creatureInfo == null)
|
||||
return null;
|
||||
|
||||
Pet pet = new Pet(ToPlayer(), PetType.Hunter);
|
||||
|
||||
if (!pet.CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, getLevel(), spell_id))
|
||||
return null;
|
||||
|
||||
return pet;
|
||||
}
|
||||
|
||||
bool InitTamedPet(Pet pet, uint level, uint spell_id)
|
||||
{
|
||||
pet.SetCreatorGUID(GetGUID());
|
||||
pet.SetFaction(getFaction());
|
||||
pet.SetUInt32Value(UnitFields.CreatedBySpell, spell_id);
|
||||
|
||||
if (IsTypeId(TypeId.Player))
|
||||
pet.SetUInt32Value(UnitFields.Flags, (uint)UnitFlags.PvpAttackable);
|
||||
|
||||
if (!pet.InitStatsForLevel(level))
|
||||
{
|
||||
Log.outError(LogFilter.Unit, "Pet:InitStatsForLevel() failed for creature (Entry: {0})!", pet.GetEntry());
|
||||
return false;
|
||||
}
|
||||
|
||||
pet.CopyPhaseFrom(this);
|
||||
|
||||
pet.GetCharmInfo().SetPetNumber(Global.ObjectMgr.GeneratePetNumber(), true);
|
||||
// this enables pet details window (Shift+P)
|
||||
pet.InitPetCreateSpells();
|
||||
pet.SetFullHealth();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,696 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Dynamic;
|
||||
using Game.BattleGrounds;
|
||||
using Game.DataStorage;
|
||||
using Game.Movement;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class Vehicle : ITransport, IDisposable
|
||||
{
|
||||
public Vehicle(Unit unit, VehicleRecord vehInfo, uint creatureEntry)
|
||||
{
|
||||
_me = unit;
|
||||
_vehicleInfo = vehInfo;
|
||||
_creatureEntry = creatureEntry;
|
||||
_status = Status.None;
|
||||
_lastShootPos = new Position();
|
||||
|
||||
for (uint i = 0; i < SharedConst.MaxVehicleSeats; ++i)
|
||||
{
|
||||
uint seatId = _vehicleInfo.SeatID[i];
|
||||
if (seatId != 0)
|
||||
{
|
||||
VehicleSeatRecord veSeat = CliDB.VehicleSeatStorage.LookupByKey(seatId);
|
||||
if (veSeat != null)
|
||||
{
|
||||
Seats.Add((sbyte)i, new VehicleSeat(veSeat));
|
||||
if (veSeat.CanEnterOrExit())
|
||||
++UsableSeatNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set or remove correct flags based on available seats. Will overwrite db data (if wrong).
|
||||
if (UsableSeatNum != 0)
|
||||
_me.SetFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick));
|
||||
else
|
||||
_me.RemoveFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick));
|
||||
|
||||
InitMovementInfoForBase();
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
/// @Uninstall must be called before this.
|
||||
Contract.Assert(_status == Status.UnInstalling);
|
||||
foreach (var pair in Seats)
|
||||
Contract.Assert(pair.Value.IsEmpty());
|
||||
}
|
||||
|
||||
public void Install()
|
||||
{
|
||||
if (_me.IsTypeId(TypeId.Unit))
|
||||
{
|
||||
PowerDisplayRecord powerDisplay = CliDB.PowerDisplayStorage.LookupByKey(_vehicleInfo.PowerDisplayID[0]);
|
||||
if (powerDisplay != null)
|
||||
_me.setPowerType((PowerType)powerDisplay.PowerType);
|
||||
else if (_me.GetClass() == Class.Rogue)
|
||||
_me.setPowerType(PowerType.Energy);
|
||||
}
|
||||
|
||||
_status = Status.Installed;
|
||||
if (GetBase().IsTypeId(TypeId.Unit))
|
||||
Global.ScriptMgr.OnInstall(this);
|
||||
}
|
||||
|
||||
public void InstallAllAccessories(bool evading)
|
||||
{
|
||||
if (GetBase().IsTypeId(TypeId.Player) || !evading)
|
||||
RemoveAllPassengers(); // We might have aura's saved in the DB with now invalid casters - remove
|
||||
|
||||
List<VehicleAccessory> accessories = Global.ObjectMgr.GetVehicleAccessoryList(this);
|
||||
if (accessories == null)
|
||||
return;
|
||||
|
||||
foreach (var acc in accessories)
|
||||
if (!evading || acc.IsMinion) // only install minions on evade mode
|
||||
InstallAccessory(acc.AccessoryEntry, acc.SeatId, acc.IsMinion, acc.SummonedType, acc.SummonTime);
|
||||
}
|
||||
|
||||
public void Uninstall()
|
||||
{
|
||||
// @Prevent recursive uninstall call. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
|
||||
if (_status == Status.UnInstalling && !GetBase().HasUnitTypeMask(UnitTypeMask.Minion))
|
||||
{
|
||||
Log.outError(LogFilter.Vehicle, "Vehicle GuidLow: {0}, Entry: {1} attempts to uninstall, but already has STATUS_UNINSTALLING! " +
|
||||
"Check Uninstall/PassengerBoarded script hooks for errors.", _me.GetGUID().ToString(), _me.GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
_status = Status.UnInstalling;
|
||||
Log.outDebug(LogFilter.Vehicle, "Vehicle.Uninstall Entry: {0}, GuidLow: {1}", _creatureEntry, _me.GetGUID().ToString());
|
||||
RemoveAllPassengers();
|
||||
|
||||
if (GetBase().IsTypeId(TypeId.Unit))
|
||||
Global.ScriptMgr.OnUninstall(this);
|
||||
}
|
||||
|
||||
public void Reset(bool evading = false)
|
||||
{
|
||||
if (!GetBase().IsTypeId(TypeId.Unit))
|
||||
return;
|
||||
|
||||
Log.outDebug(LogFilter.Vehicle, "Vehicle.Reset (Entry: {0}, GuidLow: {1}, DBGuid: {2})", GetCreatureEntry(), _me.GetGUID().ToString(), _me.ToCreature().GetSpawnId());
|
||||
|
||||
ApplyAllImmunities();
|
||||
InstallAllAccessories(evading);
|
||||
|
||||
Global.ScriptMgr.OnReset(this);
|
||||
}
|
||||
|
||||
void ApplyAllImmunities()
|
||||
{
|
||||
// This couldn't be done in DB, because some spells have MECHANIC_NONE
|
||||
|
||||
// Vehicles should be immune on Knockback ...
|
||||
_me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBack, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBackDest, true);
|
||||
|
||||
// Mechanical units & vehicles ( which are not Bosses, they have own immunities in DB ) should be also immune on healing ( exceptions in switch below )
|
||||
if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().GetCreatureTemplate().CreatureType == CreatureType.Mechanical && !_me.ToCreature().isWorldBoss())
|
||||
{
|
||||
// Heal & dispel ...
|
||||
_me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.Heal, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.HealPct, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.Dispel, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.PeriodicHeal, true);
|
||||
|
||||
// ... Shield & Immunity grant spells ...
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SchoolImmunity, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModUnattackable, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SchoolAbsorb, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.Mechanic, Mechanics.Shield, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.Mechanic, Mechanics.Immune_Shield, true);
|
||||
|
||||
// ... Resistance, Split damage, Change stats ...
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.DamageShield, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SplitDamagePct, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModResistance, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModStat, true);
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModDamagePercentTaken, true);
|
||||
}
|
||||
|
||||
// Different immunities for vehicles goes below
|
||||
switch (GetVehicleInfo().Id)
|
||||
{
|
||||
// code below prevents a bug with movable cannons
|
||||
case 160: // Strand of the Ancients
|
||||
case 244: // Wintergrasp
|
||||
case 452: // Isle of Conquest
|
||||
case 510: // Isle of Conquest
|
||||
case 543: // Isle of Conquest
|
||||
_me.SetControlled(true, UnitState.Root);
|
||||
// why we need to apply this? we can simple add immunities to slow mechanic in DB
|
||||
_me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModDecreaseSpeed, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAllPassengers()
|
||||
{
|
||||
Log.outDebug(LogFilter.Vehicle, "Vehicle.RemoveAllPassengers. Entry: {0}, GuidLow: {1}", _creatureEntry, _me.GetGUID().ToString());
|
||||
|
||||
// Setting to_Abort to true will cause @VehicleJoinEvent.Abort to be executed on next @Unit.UpdateEvents call
|
||||
// This will properly "reset" the pending join process for the passenger.
|
||||
{
|
||||
// Update vehicle in every pending join event - Abort may be called after vehicle is deleted
|
||||
Vehicle eventVehicle = _status != Status.UnInstalling ? this : null;
|
||||
|
||||
while (!_pendingJoinEvents.Empty())
|
||||
{
|
||||
VehicleJoinEvent e = _pendingJoinEvents.First();
|
||||
e.ScheduleAbort();
|
||||
e.Target = eventVehicle;
|
||||
_pendingJoinEvents.Remove(_pendingJoinEvents.First());
|
||||
}
|
||||
}
|
||||
|
||||
// Passengers always cast an aura with SPELL_AURA_CONTROL_VEHICLE on the vehicle
|
||||
// We just remove the aura and the unapply handler will make the target leave the vehicle.
|
||||
// We don't need to iterate over Seats
|
||||
_me.RemoveAurasByType(AuraType.ControlVehicle);
|
||||
}
|
||||
|
||||
public bool HasEmptySeat(sbyte seatId)
|
||||
{
|
||||
var seat = Seats.LookupByKey(seatId);
|
||||
if (seat == null)
|
||||
return false;
|
||||
return seat.IsEmpty();
|
||||
}
|
||||
|
||||
public Unit GetPassenger(sbyte seatId)
|
||||
{
|
||||
var seat = Seats.LookupByKey(seatId);
|
||||
if (seat == null)
|
||||
return null;
|
||||
|
||||
return Global.ObjAccessor.GetUnit(GetBase(), seat.Passenger.Guid);
|
||||
}
|
||||
|
||||
public VehicleSeat GetNextEmptySeat(sbyte seatId, bool next)
|
||||
{
|
||||
var seat = Seats.LookupByKey(seatId);
|
||||
if (seat == null)
|
||||
return seat;
|
||||
|
||||
foreach (var sea in Seats)
|
||||
{
|
||||
if (!seat.IsEmpty() || (!seat.SeatInfo.CanEnterOrExit() && !seat.SeatInfo.IsUsableByOverride()))
|
||||
continue;
|
||||
|
||||
seat = sea.Value;
|
||||
}
|
||||
|
||||
return seat;
|
||||
}
|
||||
|
||||
void InstallAccessory(uint entry, sbyte seatId, bool minion, byte type, uint summonTime)
|
||||
{
|
||||
// @Prevent adding accessories when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
|
||||
|
||||
if (_status == Status.UnInstalling)
|
||||
{
|
||||
Log.outError(LogFilter.Vehicle, "Vehicle ({0}, Entry: {1}) attempts to install accessory (Entry: {2}) on seat {3} with STATUS_UNINSTALLING! " +
|
||||
"Check Uninstall/PassengerBoarded script hooks for errors.", _me.GetGUID().ToString(), GetCreatureEntry(), entry, seatId);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Vehicle, "Vehicle ({0}, Entry {1}): installing accessory (Entry: {2}) on seat: {3}", _me.GetGUID().ToString(), GetCreatureEntry(), entry, seatId);
|
||||
|
||||
TempSummon accessory = _me.SummonCreature(entry, _me, (TempSummonType)type, summonTime);
|
||||
Contract.Assert(accessory);
|
||||
|
||||
if (minion)
|
||||
accessory.AddUnitTypeMask(UnitTypeMask.Accessory);
|
||||
|
||||
_me.HandleSpellClick(accessory, seatId);
|
||||
|
||||
// If for some reason adding accessory to vehicle fails it will unsummon in
|
||||
// @VehicleJoinEvent.Abort
|
||||
}
|
||||
|
||||
public bool AddPassenger(Unit unit, sbyte seatId)
|
||||
{
|
||||
// @Prevent adding passengers when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
|
||||
if (_status == Status.UnInstalling)
|
||||
{
|
||||
Log.outError(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, attempting to board vehicle GuidLow: {2}, Entry: {3} during uninstall! SeatId: {4}",
|
||||
unit.GetGUID().ToString(), unit.GetEntry(), _me.GetGUID().ToString(), _me.GetEntry(), seatId);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Vehicle, "Unit {0} scheduling enter vehicle (entry: {1}, vehicleId: {2}, guid: {3} (dbguid: {4}) on seat {5}",
|
||||
unit.GetName(), _me.GetEntry(), _vehicleInfo.Id, _me.GetGUID().ToString(),
|
||||
(_me.IsTypeId(TypeId.Unit) ? _me.ToCreature().GetSpawnId() : 0), seatId);
|
||||
|
||||
// The seat selection code may kick other passengers off the vehicle.
|
||||
// While the validity of the following may be arguable, it is possible that when such a passenger
|
||||
// exits the vehicle will dismiss. That's why the actual adding the passenger to the vehicle is scheduled
|
||||
// asynchronously, so it can be cancelled easily in case the vehicle is uninstalled meanwhile.
|
||||
VehicleJoinEvent e = new VehicleJoinEvent(this, unit);
|
||||
unit.m_Events.AddEvent(e, unit.m_Events.CalculateTime(0));
|
||||
|
||||
KeyValuePair<sbyte, VehicleSeat> seat = new KeyValuePair<sbyte, VehicleSeat>();
|
||||
if (seatId < 0) // no specific seat requirement
|
||||
{
|
||||
foreach (var _seat in Seats)
|
||||
{
|
||||
seat = _seat;
|
||||
if (seat.Value.IsEmpty() && (_seat.Value.SeatInfo.CanEnterOrExit() || _seat.Value.SeatInfo.IsUsableByOverride()))
|
||||
break;
|
||||
}
|
||||
|
||||
if (seat.Value == null) // no available seat
|
||||
{
|
||||
e.ScheduleAbort();
|
||||
return false;
|
||||
}
|
||||
|
||||
e.Seat = seat;
|
||||
_pendingJoinEvents.Add(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
seat = new KeyValuePair<sbyte, VehicleSeat>(seatId, Seats.LookupByKey(seatId));
|
||||
if (seat.Value == null)
|
||||
{
|
||||
e.ScheduleAbort();
|
||||
return false;
|
||||
}
|
||||
|
||||
e.Seat = seat;
|
||||
_pendingJoinEvents.Add(e);
|
||||
if (!seat.Value.IsEmpty())
|
||||
{
|
||||
Unit passenger = Global.ObjAccessor.GetUnit(GetBase(), seat.Value.Passenger.Guid);
|
||||
Contract.Assert(passenger != null);
|
||||
passenger.ExitVehicle();
|
||||
}
|
||||
|
||||
Contract.Assert(seat.Value.IsEmpty());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Vehicle RemovePassenger(Unit unit)
|
||||
{
|
||||
if (unit.GetVehicle() != this)
|
||||
return null;
|
||||
|
||||
var seat = GetSeatKeyValuePairForPassenger(unit);
|
||||
Contract.Assert(seat.Value != null);
|
||||
|
||||
Log.outDebug( LogFilter.Vehicle, "Unit {0} exit vehicle entry {1} id {2} dbguid {3} seat {4}",
|
||||
unit.GetName(), _me.GetEntry(), _vehicleInfo.Id, _me.GetGUID().ToString(), seat.Key);
|
||||
|
||||
if (seat.Value.SeatInfo.CanEnterOrExit() && ++UsableSeatNum != 0)
|
||||
_me.SetFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick));
|
||||
|
||||
// Remove UNIT_FLAG_NOT_SELECTABLE if passenger did not have it before entering vehicle
|
||||
if (seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.PassengerNotSelectable) && !seat.Value.Passenger.IsUnselectable)
|
||||
unit.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable);
|
||||
|
||||
seat.Value.Passenger.Reset();
|
||||
|
||||
if (_me.IsTypeId(TypeId.Unit) && unit.IsTypeId(TypeId.Player) && seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanControl))
|
||||
_me.RemoveCharmedBy(unit);
|
||||
|
||||
if (_me.IsInWorld)
|
||||
unit.m_movementInfo.ResetTransport();
|
||||
|
||||
// only for flyable vehicles
|
||||
if (unit.IsFlying())
|
||||
_me.CastSpell(unit, SharedConst.VehicleSpellParachute, true);
|
||||
|
||||
if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().IsAIEnabled)
|
||||
_me.ToCreature().GetAI().PassengerBoarded(unit, seat.Key, false);
|
||||
|
||||
if (GetBase().IsTypeId(TypeId.Unit))
|
||||
Global.ScriptMgr.OnRemovePassenger(this, unit);
|
||||
|
||||
unit.SetVehicle(null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void RelocatePassengers()
|
||||
{
|
||||
Contract.Assert(_me.GetMap() != null);
|
||||
|
||||
List<Tuple<Unit, Position>> seatRelocation = new List<Tuple<Unit, Position>>();
|
||||
|
||||
// not sure that absolute position calculation is correct, it must depend on vehicle pitch angle
|
||||
foreach (var pair in Seats)
|
||||
{
|
||||
Unit passenger = Global.ObjAccessor.GetUnit(GetBase(), pair.Value.Passenger.Guid);
|
||||
if (passenger != null)
|
||||
{
|
||||
Contract.Assert(passenger.IsInWorld);
|
||||
|
||||
float px, py, pz, po;
|
||||
passenger.m_movementInfo.transport.pos.GetPosition(out px, out py, out pz, out po);
|
||||
CalculatePassengerPosition(ref px, ref py, ref pz, ref po);
|
||||
|
||||
seatRelocation.Add(Tuple.Create(passenger, new Position(px, py, pz, po)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in seatRelocation)
|
||||
pair.Item1.UpdatePosition(pair.Item2);
|
||||
}
|
||||
|
||||
public bool IsVehicleInUse()
|
||||
{
|
||||
foreach (var pair in Seats)
|
||||
if (!pair.Value.IsEmpty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InitMovementInfoForBase()
|
||||
{
|
||||
VehicleFlags vehicleFlags = (VehicleFlags)GetVehicleInfo().Flags;
|
||||
|
||||
if (vehicleFlags.HasAnyFlag(VehicleFlags.NoStrafe))
|
||||
_me.AddUnitMovementFlag2(MovementFlag2.NoStrafe);
|
||||
if (vehicleFlags.HasAnyFlag(VehicleFlags.NoJumping))
|
||||
_me.AddUnitMovementFlag2(MovementFlag2.NoJumping);
|
||||
if (vehicleFlags.HasAnyFlag(VehicleFlags.Fullspeedturning))
|
||||
_me.AddUnitMovementFlag2(MovementFlag2.FullSpeedTurning);
|
||||
if (vehicleFlags.HasAnyFlag(VehicleFlags.AllowPitching))
|
||||
_me.AddUnitMovementFlag2(MovementFlag2.AlwaysAllowPitching);
|
||||
if (vehicleFlags.HasAnyFlag(VehicleFlags.Fullspeedpitching))
|
||||
_me.AddUnitMovementFlag2(MovementFlag2.FullSpeedPitching);
|
||||
}
|
||||
|
||||
public VehicleSeatRecord GetSeatForPassenger(Unit passenger)
|
||||
{
|
||||
foreach (var pair in Seats)
|
||||
if (pair.Value.Passenger.Guid == passenger.GetGUID())
|
||||
return pair.Value.SeatInfo;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyValuePair<sbyte, VehicleSeat> GetSeatKeyValuePairForPassenger(Unit passenger)
|
||||
{
|
||||
foreach (var pair in Seats)
|
||||
if (pair.Value.Passenger.Guid == passenger.GetGUID())
|
||||
return pair;
|
||||
|
||||
return Seats.Last();
|
||||
}
|
||||
|
||||
public byte GetAvailableSeatCount()
|
||||
{
|
||||
byte ret = 0;
|
||||
foreach (var pair in Seats)
|
||||
if (pair.Value.IsEmpty() && (pair.Value.SeatInfo.CanEnterOrExit() || pair.Value.SeatInfo.IsUsableByOverride()))
|
||||
++ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o)
|
||||
{
|
||||
TransportPosHelper.CalculatePassengerPosition(ref x, ref y, ref z, ref o,
|
||||
GetBase().GetPositionX(), GetBase().GetPositionY(),
|
||||
GetBase().GetPositionZ(), GetBase().GetOrientation());
|
||||
}
|
||||
|
||||
public void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o)
|
||||
{
|
||||
TransportPosHelper.CalculatePassengerOffset(ref x, ref y, ref z, ref o,
|
||||
GetBase().GetPositionX(), GetBase().GetPositionY(),
|
||||
GetBase().GetPositionZ(), GetBase().GetOrientation());
|
||||
}
|
||||
|
||||
public void RemovePendingEvent(VehicleJoinEvent e)
|
||||
{
|
||||
foreach (var Event in _pendingJoinEvents)
|
||||
{
|
||||
if (Event == e)
|
||||
{
|
||||
_pendingJoinEvents.Remove(Event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemovePendingEventsForSeat(sbyte seatId)
|
||||
{
|
||||
foreach (var Event in _pendingJoinEvents.ToList())
|
||||
{
|
||||
if (Event.Seat.Key == seatId)
|
||||
{
|
||||
Event.ScheduleAbort();
|
||||
_pendingJoinEvents.Remove(Event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemovePendingEventsForPassenger(Unit passenger)
|
||||
{
|
||||
foreach (var Event in _pendingJoinEvents.ToList())
|
||||
{
|
||||
if (Event.Passenger == passenger)
|
||||
{
|
||||
Event.ScheduleAbort();
|
||||
_pendingJoinEvents.Remove(Event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Unit GetBase() { return _me; }
|
||||
public VehicleRecord GetVehicleInfo() { return _vehicleInfo; }
|
||||
public uint GetCreatureEntry() { return _creatureEntry; }
|
||||
|
||||
public void SetLastShootPos(Position pos) { _lastShootPos.Relocate(pos); }
|
||||
Position GetLastShootPos() { return _lastShootPos; }
|
||||
|
||||
Unit _me;
|
||||
VehicleRecord _vehicleInfo; //< DBC data for vehicle
|
||||
List<ulong> vehiclePlayers = new List<ulong>();
|
||||
|
||||
uint _creatureEntry; //< Can be different than the entry of _me in case of players
|
||||
Status _status; //< Internal variable for sanity checks
|
||||
Position _lastShootPos;
|
||||
|
||||
List<VehicleJoinEvent> _pendingJoinEvents = new List<VehicleJoinEvent>();
|
||||
public Dictionary<sbyte, VehicleSeat> Seats = new Dictionary<sbyte, VehicleSeat>();
|
||||
public uint UsableSeatNum; //< Number of seats that match VehicleSeatEntry.UsableByPlayer, used for proper display flags
|
||||
|
||||
public static implicit operator bool(Vehicle vehicle)
|
||||
{
|
||||
return vehicle != null;
|
||||
}
|
||||
|
||||
public enum Status
|
||||
{
|
||||
None,
|
||||
Installed,
|
||||
UnInstalling,
|
||||
}
|
||||
}
|
||||
|
||||
public class VehicleJoinEvent : BasicEvent
|
||||
{
|
||||
public VehicleJoinEvent(Vehicle v, Unit u)
|
||||
{
|
||||
Target = v;
|
||||
Passenger = u;
|
||||
Seat = Target.Seats.Last();
|
||||
}
|
||||
|
||||
public override bool Execute(ulong e_time, uint p_time)
|
||||
{
|
||||
Contract.Assert(Passenger.IsInWorld);
|
||||
Contract.Assert(Target != null && Target.GetBase().IsInWorld);
|
||||
Contract.Assert(Target.GetBase().HasAuraTypeWithCaster(AuraType.ControlVehicle, Passenger.GetGUID()));
|
||||
|
||||
Target.RemovePendingEventsForSeat(Seat.Key);
|
||||
Target.RemovePendingEventsForPassenger(Passenger);
|
||||
|
||||
Passenger.SetVehicle(Target);
|
||||
Seat.Value.Passenger.Guid = Passenger.GetGUID();
|
||||
Seat.Value.Passenger.IsUnselectable = Passenger.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable);
|
||||
if (Seat.Value.SeatInfo.CanEnterOrExit())
|
||||
{
|
||||
Contract.Assert(Target.UsableSeatNum != 0);
|
||||
--Target.UsableSeatNum;
|
||||
if (Target.UsableSeatNum == 0)
|
||||
{
|
||||
if (Target.GetBase().IsTypeId(TypeId.Player))
|
||||
Target.GetBase().RemoveFlag64(UnitFields.NpcFlags, NPCFlags.PlayerVehicle);
|
||||
else
|
||||
Target.GetBase().RemoveFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick);
|
||||
}
|
||||
}
|
||||
|
||||
Passenger.InterruptNonMeleeSpells(false);
|
||||
Passenger.RemoveAurasByType(AuraType.Mounted);
|
||||
|
||||
VehicleSeatRecord veSeat = Seat.Value.SeatInfo;
|
||||
|
||||
Player player = Passenger.ToPlayer();
|
||||
if (player != null)
|
||||
{
|
||||
// drop flag
|
||||
Battleground bg = player.GetBattleground();
|
||||
if (bg)
|
||||
bg.EventPlayerDroppedFlag(player);
|
||||
|
||||
player.StopCastingCharm();
|
||||
player.StopCastingBindSight();
|
||||
player.SendOnCancelExpectedVehicleRideAura();
|
||||
if (!veSeat.Flags[1].HasAnyFlag((uint)VehicleSeatFlagsB.KeepPet))
|
||||
player.UnsummonPetTemporaryIfAny();
|
||||
}
|
||||
|
||||
if (Seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.PassengerNotSelectable))
|
||||
Passenger.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable);
|
||||
|
||||
Passenger.m_movementInfo.transport.pos.Relocate(veSeat.AttachmentOffset.X, veSeat.AttachmentOffset.Y, veSeat.AttachmentOffset.Z);
|
||||
Passenger.m_movementInfo.transport.time = 0;
|
||||
Passenger.m_movementInfo.transport.seat = Seat.Key;
|
||||
Passenger.m_movementInfo.transport.guid = Target.GetBase().GetGUID();
|
||||
|
||||
if (Target.GetBase().IsTypeId(TypeId.Unit) && Passenger.IsTypeId(TypeId.Player) &&
|
||||
Seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanControl))
|
||||
Contract.Assert(Target.GetBase().SetCharmedBy(Passenger, CharmType.Vehicle)); // SMSG_CLIENT_CONTROL
|
||||
|
||||
Passenger.SendClearTarget(); // SMSG_BREAK_TARGET
|
||||
Passenger.SetControlled(true, UnitState.Root); // SMSG_FORCE_ROOT - In some cases we send SMSG_SPLINE_MOVE_ROOT here (for creatures)
|
||||
// also adds MOVEMENTFLAG_ROOT
|
||||
|
||||
MoveSplineInit init = new MoveSplineInit(Passenger);
|
||||
init.DisableTransportPathTransformations();
|
||||
init.MoveTo(veSeat.AttachmentOffset.X, veSeat.AttachmentOffset.Y, veSeat.AttachmentOffset.Z, false, true);
|
||||
init.SetFacing(0.0f);
|
||||
init.SetTransportEnter();
|
||||
init.Launch();
|
||||
|
||||
Creature creature = Target.GetBase().ToCreature();
|
||||
if (creature != null)
|
||||
{
|
||||
if (creature.IsAIEnabled)
|
||||
creature.GetAI().PassengerBoarded(Passenger, Seat.Key, true);
|
||||
|
||||
Global.ScriptMgr.OnAddPassenger(Target, Passenger, Seat.Key);
|
||||
|
||||
// Actually quite a redundant hook. Could just use OnAddPassenger and check for unit typemask inside script.
|
||||
if (Passenger.HasUnitTypeMask(UnitTypeMask.Accessory))
|
||||
Global.ScriptMgr.OnInstallAccessory(Target, Passenger.ToCreature());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Abort(ulong e_time)
|
||||
{
|
||||
// Check if the Vehicle was already uninstalled, in which case all auras were removed already
|
||||
if (Target != null)
|
||||
{
|
||||
Log.outDebug(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, board on vehicle GuidLow: {2}, Entry: {3} SeatId: {4} cancelled",
|
||||
Passenger.GetGUID().ToString(), Passenger.GetEntry(), Target.GetBase().GetGUID().ToString(), Target.GetBase().GetEntry(), Seat.Key);
|
||||
|
||||
/// Remove the pending event when Abort was called on the event directly
|
||||
Target.RemovePendingEvent(this);
|
||||
|
||||
// @SPELL_AURA_CONTROL_VEHICLE auras can be applied even when the passenger is not (yet) on the vehicle.
|
||||
// When this code is triggered it means that something went wrong in @Vehicle.AddPassenger, and we should remove
|
||||
// the aura manually.
|
||||
Target.GetBase().RemoveAurasByType(AuraType.ControlVehicle, Passenger.GetGUID());
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, board on uninstalled vehicle SeatId: {2} cancelled",
|
||||
Passenger.GetGUID().ToString(), Passenger.GetEntry(), Seat.Key);
|
||||
|
||||
if (Passenger.IsInWorld && Passenger.HasUnitTypeMask(UnitTypeMask.Accessory))
|
||||
Passenger.ToCreature().DespawnOrUnsummon();
|
||||
}
|
||||
|
||||
public Vehicle Target;
|
||||
public Unit Passenger;
|
||||
public KeyValuePair<sbyte, VehicleSeat> Seat;
|
||||
}
|
||||
|
||||
public struct PassengerInfo
|
||||
{
|
||||
public ObjectGuid Guid;
|
||||
public bool IsUnselectable;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Guid = ObjectGuid.Empty;
|
||||
IsUnselectable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public class VehicleSeat
|
||||
{
|
||||
public VehicleSeat(VehicleSeatRecord seatInfo)
|
||||
{
|
||||
SeatInfo = seatInfo;
|
||||
Passenger.Reset();
|
||||
}
|
||||
|
||||
public bool IsEmpty() { return Passenger.Guid.IsEmpty(); }
|
||||
|
||||
public VehicleSeatRecord SeatInfo;
|
||||
public PassengerInfo Passenger;
|
||||
}
|
||||
|
||||
public struct VehicleAccessory
|
||||
{
|
||||
public VehicleAccessory(uint entry, sbyte seatId, bool isMinion, byte summonType, uint summonTime)
|
||||
{
|
||||
AccessoryEntry = entry;
|
||||
IsMinion = isMinion;
|
||||
SummonTime = summonTime;
|
||||
SeatId = seatId;
|
||||
SummonedType = summonType;
|
||||
}
|
||||
public uint AccessoryEntry;
|
||||
public bool IsMinion;
|
||||
public uint SummonTime;
|
||||
public sbyte SeatId;
|
||||
public byte SummonedType;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user