project-meteor-server/Map Server/Actors/Chara/Npc/Npc.cs

477 lines
20 KiB
C#

/*
===========================================================================
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 <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Common;
using Meteor.Map.actors;
using Meteor.Map.actors.chara.npc;
using Meteor.Map.Actors.Chara;
using Meteor.Map.lua;
using Meteor.Map.packets.send.actor;
using Meteor.Map.utils;
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using Meteor.Map.actors.chara.ai;
using Meteor.Map.packets.send.actor.events;
namespace Meteor.Map.Actors
{
[Flags]
enum NpcSpawnType : ushort
{
Normal = 0x00,
Scripted = 0x01,
Nighttime = 0x02,
Evening = 0x04,
Daytime = 0x08,
Weather = 0x10,
}
class Npc : Character
{
private uint actorClassId;
private string uniqueIdentifier;
private bool IsMapObjChara = false;
private uint MapObjLayoutId, MapObjInstanceId;
public NpcWork npcWork = new NpcWork();
public NpcSpawnType npcSpawnType;
public Npc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, ushort actorState, uint animationId, string customDisplayName, uint mapObjLayoutId = 0, uint mapObjInstanceId = 0)
: base((4 << 28 | spawnedArea.Id << 19 | ((uint)actorNumber + 5)))
{
this.positionX = posX;
this.positionY = posY;
this.positionZ = posZ;
this.rotation = rot;
this.currentMainState = actorState;
this.animationId = animationId;
this.LocalizedDisplayName = actorClass.displayNameId;
this.DisplayName = customDisplayName;
this.uniqueIdentifier = uniqueId;
CurrentArea = spawnedArea;
this.actorClassId = actorClass.actorClassId;
this.currentSubState.motionPack = (ushort) animationId;
LoadNpcAppearance(actorClass.actorClassId);
className = actorClass.classPath.Substring(actorClass.classPath.LastIndexOf("/") + 1);
this.classPath = String.Format("{0}/{1}", actorClass.classPath.Substring(0, actorClass.classPath.LastIndexOf('/')).ToLower(), className);
charaWork.battleSave.potencial = 1.0f;
// todo: these really need to be read from db etc
{
charaWork.parameterSave.state_mainSkill[0] = 3;
charaWork.parameterSave.state_mainSkill[2] = 3;
charaWork.parameterSave.state_mainSkillLevel = 1;
charaWork.parameterSave.hp[0] = 80;
charaWork.parameterSave.hpMax[0] = 80;
}
for (int i = 0; i < 32; i++ )
charaWork.property[i] = (byte)(((int)actorClass.propertyFlags >> i) & 1);
npcWork.pushCommand = actorClass.pushCommand;
npcWork.pushCommandSub = actorClass.pushCommandSub;
npcWork.pushCommandPriority = actorClass.pushCommandPriority;
if (mapObjLayoutId != 0 && mapObjInstanceId != 0)
{
isStatic = true;
IsMapObjChara = true;
MapObjLayoutId = mapObjLayoutId;
MapObjInstanceId = mapObjInstanceId;
}
GenerateActorName((int)actorNumber);
this.aiContainer = new AIContainer(this, null, new PathFind(this), new TargetFind(this));
}
public Npc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, uint mapObjLayoutId = 0, uint mapObjInstanceId = 0)
: base((4 << 28 | spawnedArea.Id << 19 | (uint)actorNumber))
{
this.positionX = posX;
this.positionY = posY;
this.positionZ = posZ;
this.rotation = rot;
this.currentMainState = 0;
this.animationId = 0;
this.LocalizedDisplayName = actorClass.displayNameId;
this.uniqueIdentifier = uniqueId;
CurrentArea = spawnedArea;
this.actorClassId = actorClass.actorClassId;
LoadNpcAppearance(actorClass.actorClassId);
this.classPath = actorClass.classPath;
className = classPath.Substring(classPath.LastIndexOf("/") + 1);
for (int i = 0; i < 32; i++)
charaWork.property[i] = (byte)(((int)actorClass.propertyFlags >> i) & 1);
npcWork.pushCommand = actorClass.pushCommand;
npcWork.pushCommandSub = actorClass.pushCommandSub;
npcWork.pushCommandPriority = actorClass.pushCommandPriority;
if (mapObjLayoutId != 0 && mapObjInstanceId != 0)
{
isStatic = true;
IsMapObjChara = true;
MapObjLayoutId = mapObjLayoutId;
MapObjInstanceId = mapObjInstanceId;
}
GenerateActorName((int)actorNumber);
this.aiContainer = new AIContainer(this, null, new PathFind(this), new TargetFind(null));
}
public SubPacket CreateAddActorPacket()
{
return AddActorPacket.BuildPacket(Id, 8);
}
// actorClassId, [], [], numBattleCommon, [battleCommon], numEventCommon, [eventCommon], args for either initForBattle/initForEvent
public override SubPacket CreateScriptBindPacket(Player player)
{
List<LuaParam> lParams;
lParams = LuaEngine.GetInstance().CallLuaFunctionForReturn(player, this, "init", false);
if (lParams != null && lParams.Count >= 3 && lParams[2].typeID == 0 && (int)lParams[2].value == 0)
isStatic = true;
else
{
//charaWork.property[2] = 1;
//npcWork.hateType = 1;
}
if (lParams == null)
{
string classPathFake = "/Chara/Npc/Populace/PopulaceStandard";
string classNameFake = "PopulaceStandard";
lParams = LuaUtils.CreateLuaParamList(classPathFake, false, false, false, false, false, 0xF47F6, false, false, 0, 0);
isStatic = true;
//ActorInstantiatePacket.BuildPacket(actorId, actorName, classNameFake, lParams).DebugPrintSubPacket();
return ActorInstantiatePacket.BuildPacket(Id, Name, classNameFake, lParams);
}
else
{
lParams.Insert(0, new LuaParam(2, classPath));
lParams.Insert(1, new LuaParam(4, 4));
lParams.Insert(2, new LuaParam(4, 4));
lParams.Insert(3, new LuaParam(4, 4));
lParams.Insert(4, new LuaParam(4, 4));
lParams.Insert(5, new LuaParam(4, 4));
lParams.Insert(6, new LuaParam(0, (int)actorClassId));
}
//ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams).DebugPrintSubPacket();
return ActorInstantiatePacket.BuildPacket(Id, Name, className, lParams);
}
public override List<SubPacket> GetSpawnPackets(Player player, ushort spawnType)
{
List<SubPacket> subpackets = new List<SubPacket>();
subpackets.Add(CreateAddActorPacket());
subpackets.AddRange(GetEventConditionPackets());
subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(0x0));
if (IsMapObjChara)
subpackets.Add(SetActorBGPropertiesPacket.BuildPacket(Id, MapObjLayoutId, MapObjInstanceId));
else
subpackets.Add(CreateAppearancePacket());
subpackets.Add(CreateNamePacket());
subpackets.Add(CreateStatePacket());
subpackets.Add(CreateSubStatePacket());
subpackets.Add(CreateInitStatusPacket());
subpackets.Add(CreateSetActorIconPacket());
subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket(player));
return subpackets;
}
public override List<SubPacket> GetInitPackets()
{
ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("/_init", this);
//Potential
propPacketUtil.AddProperty("charaWork.battleSave.potencial");
//Properties
for (int i = 0; i < charaWork.property.Length; i++)
{
if (charaWork.property[i] != 0)
propPacketUtil.AddProperty(String.Format("charaWork.property[{0}]", i));
}
//Parameters
propPacketUtil.AddProperty("charaWork.parameterSave.hp[0]");
propPacketUtil.AddProperty("charaWork.parameterSave.hpMax[0]");
propPacketUtil.AddProperty("charaWork.parameterSave.mp");
propPacketUtil.AddProperty("charaWork.parameterSave.mpMax");
propPacketUtil.AddProperty("charaWork.parameterTemp.tp");
if (charaWork.parameterSave.state_mainSkill[0] != 0)
propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkill[0]");
if (charaWork.parameterSave.state_mainSkill[1] != 0)
propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkill[1]");
if (charaWork.parameterSave.state_mainSkill[2] != 0)
propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkill[2]");
if (charaWork.parameterSave.state_mainSkill[3] != 0)
propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkill[3]");
propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkillLevel");
//Status Times
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
{
if (charaWork.statusShownTime[i] != 0)
propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
}
//General Parameters
for (int i = 3; i < charaWork.battleTemp.generalParameter.Length; i++)
{
if (charaWork.battleTemp.generalParameter[i] != 0)
propPacketUtil.AddProperty(String.Format("charaWork.battleTemp.generalParameter[{0}]", i));
}
propPacketUtil.AddProperty("npcWork.hateType");
if (npcWork.pushCommand != 0)
{
propPacketUtil.AddProperty("npcWork.pushCommand");
if (npcWork.pushCommandSub != 0)
propPacketUtil.AddProperty("npcWork.pushCommandSub");
propPacketUtil.AddProperty("npcWork.pushCommandPriority");
}
return propPacketUtil.Done();
}
public string GetUniqueId()
{
return uniqueIdentifier;
}
public uint GetActorClassId()
{
return actorClassId;
}
public void ChangeNpcAppearance(uint id)
{
LoadNpcAppearance(id);
CurrentArea.BroadcastPacketAroundActor(this, CreateAppearancePacket());
}
public void LoadNpcAppearance(uint id)
{
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
{
try
{
conn.Open();
string query = @"
SELECT
base,
size,
hairStyle,
hairHighlightColor,
hairVariation,
faceType,
characteristics,
characteristicsColor,
faceEyebrows,
faceIrisSize,
faceEyeShape,
faceNose,
faceFeatures,
faceMouth,
ears,
hairColor,
skinColor,
eyeColor,
voice,
mainHand,
offHand,
spMainHand,
spOffHand,
throwing,
pack,
pouch,
head,
body,
legs,
hands,
feet,
waist,
neck,
leftEar,
rightEar,
leftIndex,
rightIndex,
leftFinger,
rightFinger
FROM gamedata_actor_appearance
WHERE id = @templateId
";
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@templateId", id);
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
//Handle Appearance
modelId = reader.GetUInt32("base");
appearanceIds[SIZE] = reader.GetUInt32("size");
appearanceIds[COLORINFO] = (uint)(reader.GetUInt32("skinColor") | (reader.GetUInt32("hairColor") << 10) | (reader.GetUInt32("eyeColor") << 20));
appearanceIds[FACEINFO] = PrimitiveConversion.ToUInt32(CharacterUtils.GetFaceInfo(
reader.GetByte("characteristics"),
reader.GetByte("characteristicsColor"),
reader.GetByte("faceType"),
reader.GetByte("ears"),
reader.GetByte("faceMouth"),
reader.GetByte("faceFeatures"),
reader.GetByte("faceNose"),
reader.GetByte("faceEyeShape"),
reader.GetByte("faceIrisSize"),
reader.GetByte("faceEyebrows")));
appearanceIds[HIGHLIGHT_HAIR] = (uint)(reader.GetUInt32("hairHighlightColor") | reader.GetUInt32("hairVariation") << 5 | reader.GetUInt32("hairStyle") << 10);
appearanceIds[VOICE] = reader.GetUInt32("voice");
appearanceIds[MAINHAND] = reader.GetUInt32("mainHand");
appearanceIds[OFFHAND] = reader.GetUInt32("offHand");
appearanceIds[SPMAINHAND] = reader.GetUInt32("spMainHand");
appearanceIds[SPOFFHAND] = reader.GetUInt32("spOffHand");
appearanceIds[THROWING] = reader.GetUInt32("throwing");
appearanceIds[PACK] = reader.GetUInt32("pack");
appearanceIds[POUCH] = reader.GetUInt32("pouch");
appearanceIds[MAINHAND] = reader.GetUInt32("mainHand");
appearanceIds[OFFHAND] = reader.GetUInt32("offHand");
appearanceIds[HEADGEAR] = reader.GetUInt32("head");
appearanceIds[BODYGEAR] = reader.GetUInt32("body");
appearanceIds[LEGSGEAR] = reader.GetUInt32("legs");
appearanceIds[HANDSGEAR] = reader.GetUInt32("hands");
appearanceIds[FEETGEAR] = reader.GetUInt32("feet");
appearanceIds[WAISTGEAR] = reader.GetUInt32("waist");
appearanceIds[NECKGEAR] = reader.GetUInt32("neck");
appearanceIds[R_EAR] = reader.GetUInt32("rightEar");
appearanceIds[L_EAR] = reader.GetUInt32("leftEar");
appearanceIds[R_INDEXFINGER] = reader.GetUInt32("leftIndex");
appearanceIds[L_INDEXFINGER] = reader.GetUInt32("rightIndex");
appearanceIds[R_RINGFINGER] = reader.GetUInt32("rightFinger");
appearanceIds[L_RINGFINGER] = reader.GetUInt32("leftFinger");
}
}
}
catch (MySqlException e)
{ Console.WriteLine(e); }
finally
{
conn.Dispose();
}
}
}
public void LoadEventConditions(string eventConditions)
{
EventList conditions = JsonConvert.DeserializeObject<EventList>(eventConditions);
this.eventConditions = conditions;
}
public void DoOnActorSpawn(Player player)
{
LuaEngine.GetInstance().CallLuaFunction(player, this, "onSpawn", true);
}
public void PlayMapObjAnimation(Player player, string animationName)
{
player.QueuePacket(PlayBGAnimation.BuildPacket(Id, animationName));
}
public void Despawn()
{
CurrentArea.DespawnActor(this);
}
public override void Update(DateTime tick)
{
// todo: can normal npcs have status effects?
aiContainer.Update(tick);
}
public override void PostUpdate(DateTime tick, List<SubPacket> packets = null)
{
packets = packets ?? new List<SubPacket>();
if ((updateFlags & ActorUpdateFlags.Work) != 0)
{
}
base.PostUpdate(tick, packets);
}
public override void OnSpawn()
{
base.OnSpawn();
}
public override void OnDeath()
{
base.OnDeath();
}
public override void OnDespawn()
{
CurrentArea.BroadcastPacketAroundActor(this, RemoveActorPacket.BuildPacket(this.Id));
QueuePositionUpdate(spawnX, spawnY, spawnZ);
LuaEngine.CallLuaBattleFunction(this, "onDespawn", this);
}
//A party member list packet came, set the party
/* public void SetParty(MonsterPartyGroup group)
{
if (group is MonsterPartyGroup)
currentParty = group;
}
*/
}
}