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:
Fabian
2017-10-26 17:23:44 +02:00
parent 227702e19c
commit a3dc7b3f48
844 changed files with 26064 additions and 1824 deletions
@@ -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;
}
}
+189
View File
@@ -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;
}
}
+218
View File
@@ -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;
}
}
+882
View File
@@ -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>();
}
}
+171
View File
@@ -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);
}
}
+265
View File
@@ -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
+261
View File
@@ -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
}
}
+336
View File
@@ -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;
}
}
+349
View File
@@ -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;
}
}
+372
View File
@@ -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;
}
}
+281
View File
@@ -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
+789
View File
@@ -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;
}
}
}
}
+753
View File
@@ -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
+244
View File
@@ -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>();
}
}
+165
View File
@@ -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;
}
}
+246
View File
@@ -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;
}
}
+360
View File
@@ -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
}
}
+186
View File
@@ -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
+515
View File
@@ -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;
}
}
}
+187
View File
@@ -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
+211
View File
@@ -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
}
}
+810
View File
@@ -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;
}
}
+443
View File
@@ -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);
}
}
}
+80
View File
@@ -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
+646
View File
@@ -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
+796
View File
@@ -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
+696
View File
@@ -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;
}
}