/*
===========================================================================
Copyright (C) 2015-2019 Project Meteor Dev Team
This file is part of Project Meteor Server.
Project Meteor Server is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Project Meteor Server 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Project Meteor Server. If not, see .
===========================================================================
*/
using Meteor.Common;
using Meteor.Map.actors.area;
using Meteor.Map.actors.chara.npc;
using Meteor.Map.lua;
using Meteor.Map.packets.send.actor;
using System;
using System.Collections.Generic;
using System.Linq;
using Meteor.Map.packets.send;
using Meteor.Map.actors.director;
namespace Meteor.Map.Actors
{
class Area : Actor
{
public string zoneName;
public ushort regionId;
public bool isIsolated, canStealth, isInn, canRideChocobo, isInstanceRaid;
public ushort weatherNormal, weatherCommon, weatherRare;
public ushort bgmDay, bgmNight, bgmBattle;
protected string classPath;
public int boundingGridSize = 50;
public int minX = -5000, minY = -5000, maxX = 5000, maxY = 5000;
protected int numXBlocks, numYBlocks;
protected int halfWidth, halfHeight;
private Dictionary currentDirectors = new Dictionary();
private object directorLock = new object();
private uint directorIdCount = 0;
protected Director mWeatherDirector;
protected List mSpawnLocations = new List();
protected Dictionary mActorList = new Dictionary();
protected List[,] mActorBlock;
LuaScript areaScript;
public Area(uint id, string zoneName, ushort regionId, string classPath, ushort bgmDay, ushort bgmNight, ushort bgmBattle, bool isIsolated, bool isInn, bool canRideChocobo, bool canStealth, bool isInstanceRaid)
: base(id)
{
this.zoneName = zoneName;
this.regionId = regionId;
this.canStealth = canStealth;
this.isIsolated = isIsolated;
this.isInn = isInn;
this.canRideChocobo = canRideChocobo;
this.isInstanceRaid = isInstanceRaid;
this.bgmDay = bgmDay;
this.bgmNight = bgmNight;
this.bgmBattle = bgmBattle;
this.displayNameId = 0;
this.customDisplayName = "_areaMaster";
this.actorName = string.Format("_areaMaster@{0:X5}", id << 8);
this.classPath = classPath;
this.className = classPath.Substring(classPath.LastIndexOf("/") + 1);
numXBlocks = (maxX - minX) / boundingGridSize;
numYBlocks = (maxY - minY) / boundingGridSize;
mActorBlock = new List[numXBlocks, numYBlocks];
halfWidth = numXBlocks / 2;
halfHeight = numYBlocks / 2;
for (int y = 0; y < numYBlocks; y++)
{
for (int x = 0; x < numXBlocks; x++)
{
mActorBlock[x, y] = new List();
}
}
}
public override SubPacket CreateScriptBindPacket()
{
List lParams;
lParams = LuaUtils.CreateLuaParamList(classPath, false, true, zoneName, "/Area/Zone/ZoneDefault", -1, (byte)1, true, false, false, false, false, false, false, false);
return ActorInstantiatePacket.BuildPacket(actorId, actorName, "ZoneDefault", lParams);
}
public override List GetSpawnPackets()
{
List subpackets = new List();
subpackets.Add(CreateAddActorPacket(0));
subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(0x1));
subpackets.Add(CreateNamePacket());
subpackets.Add(CreateStatePacket());
subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket());
return subpackets;
}
// todo: handle instance areas in derived class? (see virtuals)
#region Actor Management
public void AddActorToZone(Actor actor)
{
lock (mActorList)
{
if (actor is Character)
((Character)actor).ResetTempVars();
if (!mActorList.ContainsKey(actor.actorId))
mActorList.Add(actor.actorId, actor);
int gridX = (int)actor.positionX / boundingGridSize;
int gridY = (int)actor.positionZ / boundingGridSize;
gridX += halfWidth;
gridY += halfHeight;
//Boundries
if (gridX < 0)
gridX = 0;
if (gridX >= numXBlocks)
gridX = numXBlocks - 1;
if (gridY < 0)
gridY = 0;
if (gridY >= numYBlocks)
gridY = numYBlocks - 1;
lock (mActorBlock)
mActorBlock[gridX, gridY].Add(actor);
}
}
public void RemoveActorFromZone(Actor actor)
{
if (actor != null)
lock (mActorList)
{
mActorList.Remove(actor.actorId);
int gridX = (int)actor.positionX / boundingGridSize;
int gridY = (int)actor.positionZ / boundingGridSize;
gridX += halfWidth;
gridY += halfHeight;
//Boundries
if (gridX < 0)
gridX = 0;
if (gridX >= numXBlocks)
gridX = numXBlocks - 1;
if (gridY < 0)
gridY = 0;
if (gridY >= numYBlocks)
gridY = numYBlocks - 1;
lock (mActorBlock)
mActorBlock[gridX, gridY].Remove(actor);
}
}
public void UpdateActorPosition(Actor actor)
{
int gridX = (int)actor.positionX / boundingGridSize;
int gridY = (int)actor.positionZ / boundingGridSize;
gridX += halfWidth;
gridY += halfHeight;
//Boundries
if (gridX < 0)
gridX = 0;
if (gridX >= numXBlocks)
gridX = numXBlocks - 1;
if (gridY < 0)
gridY = 0;
if (gridY >= numYBlocks)
gridY = numYBlocks - 1;
int gridOldX = (int)actor.oldPositionX / boundingGridSize;
int gridOldY = (int)actor.oldPositionZ / boundingGridSize;
gridOldX += halfWidth;
gridOldY += halfHeight;
//Boundries
if (gridOldX < 0)
gridOldX = 0;
if (gridOldX >= numXBlocks)
gridOldX = numXBlocks - 1;
if (gridOldY < 0)
gridOldY = 0;
if (gridOldY >= numYBlocks)
gridOldY = numYBlocks - 1;
//Still in same block
if (gridX == gridOldX && gridY == gridOldY)
return;
lock (mActorBlock)
{
mActorBlock[gridOldX, gridOldY].Remove(actor);
mActorBlock[gridX, gridY].Add(actor);
}
}
public virtual List GetActorsAroundPoint(float x, float y, int checkDistance) where T : Actor
{
checkDistance /= boundingGridSize;
int gridX = (int)x / boundingGridSize;
int gridY = (int)y / boundingGridSize;
gridX += halfWidth;
gridY += halfHeight;
//Boundries
if (gridX < 0)
gridX = 0;
if (gridX >= numXBlocks)
gridX = numXBlocks - 1;
if (gridY < 0)
gridY = 0;
if (gridY >= numYBlocks)
gridY = numYBlocks - 1;
List result = new List();
lock (mActorBlock)
{
for (int gx = gridX - checkDistance; gx <= gridX + checkDistance; gx++)
{
for (int gy = gridY - checkDistance; gy <= gridY + checkDistance; gy++)
{
result.AddRange(mActorBlock[gx, gy].OfType());
}
}
}
//Remove players if isolation zone
if (isIsolated)
{
for (int i = 0; i < result.Count; i++)
{
if (result[i] is Player)
result.RemoveAt(i);
}
}
return result;
}
public virtual List GetActorsAroundPoint(float x, float y, int checkDistance)
{
return GetActorsAroundPoint(x, y, checkDistance);
}
public virtual List GetActorsAroundActor(Actor actor, int checkDistance)
{
return GetActorsAroundActor(actor, checkDistance);
}
public virtual List GetActorsAroundActor(Actor actor, int checkDistance) where T : Actor
{
checkDistance /= boundingGridSize;
int gridX = (int)actor.positionX / boundingGridSize;
int gridY = (int)actor.positionZ / boundingGridSize;
gridX += halfWidth;
gridY += halfHeight;
//Boundries
if (gridX < 0)
gridX = 0;
if (gridX >= numXBlocks)
gridX = numXBlocks - 1;
if (gridY < 0)
gridY = 0;
if (gridY >= numYBlocks)
gridY = numYBlocks - 1;
var result = new List();
lock (mActorBlock)
{
for (int gy = ((gridY - checkDistance) < 0 ? 0 : (gridY - checkDistance)); gy <= ((gridY + checkDistance) >= numYBlocks ? numYBlocks - 1 : (gridY + checkDistance)); gy++)
{
for (int gx = ((gridX - checkDistance) < 0 ? 0 : (gridX - checkDistance)); gx <= ((gridX + checkDistance) >= numXBlocks ? numXBlocks - 1 : (gridX + checkDistance)); gx++)
{
result.AddRange(mActorBlock[gx, gy].OfType());
}
}
}
//Remove players if isolation zone
if (isIsolated)
{
for (int i = 0; i < result.Count; i++)
{
if (result[i] is Player)
result.RemoveAt(i);
}
}
return result;
}
#endregion
public Actor FindActorInArea(uint id)
{
lock (mActorList)
{
if (!mActorList.ContainsKey(id))
return null;
return mActorList[id];
}
}
public T FindActorInArea(uint id) where T : Actor
{
return FindActorInArea(id) as T;
}
public Actor FindActorInZoneByUniqueID(string uniqueId)
{
lock (mActorList)
{
foreach (Actor a in mActorList.Values)
{
if (a is Npc)
{
if (((Npc)a).GetUniqueId().ToLower().Equals(uniqueId))
return a;
}
}
}
return null;
}
public Player FindPCInZone(string name)
{
lock (mActorList)
{
foreach (Player player in mActorList.Values.OfType())
{
if (player.customDisplayName.ToLower().Equals(name.ToLower()))
return player;
}
return null;
}
}
public Player FindPCInZone(uint id)
{
lock (mActorList)
{
if (!mActorList.ContainsKey(id))
return null;
return (Player)mActorList[id];
}
}
public void Clear()
{
lock (mActorList)
{
//Clear All
mActorList.Clear();
lock (mActorBlock)
{
for (int y = 0; y < numYBlocks; y++)
{
for (int x = 0; x < numXBlocks; x++)
{
mActorBlock[x, y].Clear();
}
}
}
}
}
// todo: for zones override this to search contentareas (assuming flag is passed)
public virtual List GetAllActors() where T : Actor
{
lock (mActorList)
{
List actorList = new List(mActorList.Count);
actorList.AddRange(mActorList.Values.OfType());
return actorList;
}
}
public int GetActorCount()
{
lock (mActorList)
{
return mActorList.Count;
}
}
public virtual List GetAllActors()
{
return GetAllActors();
}
public virtual List GetPlayers()
{
return GetAllActors();
}
public virtual List GetMonsters()
{
return GetAllActors();
}
public virtual List GetAllies()
{
return GetAllActors();
}
public void BroadcastPacketsAroundActor(Actor actor, List packets)
{
foreach (SubPacket packet in packets)
BroadcastPacketAroundActor(actor, packet);
}
public void BroadcastPacketAroundActor(Actor actor, SubPacket packet)
{
if (isIsolated)
return;
List aroundActor = GetActorsAroundActor(actor, 50);
foreach (Actor a in aroundActor)
{
if (a is Player)
{
if (isIsolated)
continue;
SubPacket clonedPacket = new SubPacket(packet, a.actorId);
Player p = (Player)a;
p.QueuePacket(clonedPacket);
}
}
}
public void SpawnActor(SpawnLocation location)
{
lock (mActorList)
{
ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId);
if (actorClass == null)
return;
uint zoneId;
if (this is PrivateArea)
zoneId = ((PrivateArea)this).GetParentZone().actorId;
else
zoneId = actorId;
Npc npc = new Npc(mActorList.Count + 1, actorClass, location.uniqueId, this, location.x, location.y, location.z, location.rot, location.state, location.animId, null);
npc.LoadEventConditions(actorClass.eventConditions);
AddActorToZone(npc);
}
}
public Npc SpawnActor(uint classId, string uniqueId, float x, float y, float z, float rot = 0, ushort state = 0, uint animId = 0, bool isMob = false)
{
lock (mActorList)
{
ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId);
if (actorClass == null)
return null;
uint zoneId;
if (this is PrivateArea)
zoneId = ((PrivateArea)this).GetParentZone().actorId;
else
zoneId = actorId;
Npc npc;
if (isMob)
npc = new BattleNpc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null);
else
npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null);
npc.LoadEventConditions(actorClass.eventConditions);
npc.SetMaxHP(100);
npc.SetHP(100);
npc.ResetMoveSpeeds();
AddActorToZone(npc);
return npc;
}
}
public Npc SpawnActor(uint classId, string uniqueId, float x, float y, float z, uint regionId, uint layoutId)
{
lock (mActorList)
{
ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId);
if (actorClass == null)
return null;
uint zoneId;
if (this is PrivateArea)
zoneId = ((PrivateArea)this).GetParentZone().actorId;
else
zoneId = actorId;
Npc npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, 0, regionId, layoutId);
npc.LoadEventConditions(actorClass.eventConditions);
AddActorToZone(npc);
return npc;
}
}
public BattleNpc GetBattleNpcById(uint id)
{
foreach (var bnpc in GetAllActors())
{
if (bnpc.GetBattleNpcId() == id)
return bnpc;
}
return null;
}
public void DespawnActor(string uniqueId)
{
RemoveActorFromZone(FindActorInZoneByUniqueID(uniqueId));
}
public void DespawnActor(Actor actor)
{
RemoveActorFromZone(actor);
}
public Director GetWeatherDirector()
{
return mWeatherDirector;
}
public void ChangeWeather(ushort weather, ushort transitionTime, Player player, bool zoneWide = false)
{
weatherNormal = weather;
if (player != null && !zoneWide)
{
player.QueuePacket(SetWeatherPacket.BuildPacket(player.actorId, weather, transitionTime));
}
if (zoneWide)
{
lock (mActorList)
{
foreach (var actor in mActorList)
{
if (actor.Value is Player)
{
player = ((Player)actor.Value);
player.QueuePacket(SetWeatherPacket.BuildPacket(player.actorId, weather, transitionTime));
}
}
}
}
}
public Director CreateDirector(string path, bool hasContentGroup, params object[] args)
{
lock (directorLock)
{
Director director = new Director(directorIdCount, this, path, hasContentGroup, args);
currentDirectors.Add(director.actorId, director);
directorIdCount++;
return director;
}
}
public Director CreateGuildleveDirector(uint glid, byte difficulty, Player owner, params object[] args)
{
string directorScriptPath = "";
uint type = Server.GetGuildleveGamedata(glid).plateId;
if (glid == 10801 || glid == 12401 || glid == 11601)
directorScriptPath = "Guildleve/PrivateGLBattleTutorial";
else
{
switch (type)
{
case 20021:
directorScriptPath = "Guildleve/PrivateGLBattleSweepNormal";
break;
case 20022:
directorScriptPath = "Guildleve/PrivateGLBattleChaseNormal";
break;
case 20023:
directorScriptPath = "Guildleve/PrivateGLBattleOrbNormal";
break;
case 20024:
directorScriptPath = "Guildleve/PrivateGLBattleHuntNormal";
break;
case 20025:
directorScriptPath = "Guildleve/PrivateGLBattleGatherNormal";
break;
case 20026:
directorScriptPath = "Guildleve/PrivateGLBattleRoundNormal";
break;
case 20027:
directorScriptPath = "Guildleve/PrivateGLBattleSurviveNormal";
break;
case 20028:
directorScriptPath = "Guildleve/PrivateGLBattleDetectNormal";
break;
}
}
lock (directorLock)
{
GuildleveDirector director = new GuildleveDirector(directorIdCount, this, directorScriptPath, glid, difficulty, owner, args);
currentDirectors.Add(director.actorId, director);
directorIdCount++;
return director;
}
}
public void DeleteDirector(uint id)
{
lock (directorLock)
{
if (currentDirectors.ContainsKey(id))
{
if (!currentDirectors[id].IsDeleted())
currentDirectors[id].EndDirector();
currentDirectors.Remove(id);
}
}
}
public Director GetDirectorById(uint id)
{
if (currentDirectors.ContainsKey(id))
return currentDirectors[id];
return null;
}
public ushort GetCurrentMusic()
{
return bgmDay;
}
public override void Update(DateTime tick)
{
lock (mActorList)
{
foreach (Actor a in mActorList.Values.ToList())
a.Update(tick);
if ((tick - lastUpdateScript).TotalMilliseconds > 1500)
{
//LuaEngine.GetInstance().CallLuaFunctionForReturn(LuaEngine.GetScriptPath(this), "onUpdate", true, this, tick);
lastUpdateScript = tick;
}
}
}
}
}