Quests and MPQ work.
Fixed quest to find 2 orbs on the rotten forest;
Fixed load actors to load an actor file that could not be found - as many actors rely on a name which is not always provided on the enum by using a [SnoFileName("TheActual SnoFileName")];
Changed FileFormats.Actor to FileFormats.ActorData
This commit is contained in:
parent
bce8acfaf3
commit
97a8bfa8a3
@ -11,10 +11,21 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace DiIiS_NA.Core.MPQ
|
namespace DiIiS_NA.Core.MPQ
|
||||||
{
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
|
||||||
|
public class SnoFileNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string FileName { get; }
|
||||||
|
|
||||||
|
public SnoFileNameAttribute(string fileName)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
public class Data : MPQPatchChain
|
public class Data : MPQPatchChain
|
||||||
{
|
{
|
||||||
public Dictionary<SNOGroup, ConcurrentDictionary<int, Asset>> Assets = new Dictionary<SNOGroup, ConcurrentDictionary<int, Asset>>();
|
public Dictionary<SNOGroup, ConcurrentDictionary<int, Asset>> Assets = new Dictionary<SNOGroup, ConcurrentDictionary<int, Asset>>();
|
||||||
@ -56,12 +67,15 @@ namespace DiIiS_NA.Core.MPQ
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
private static new readonly Logger Logger = LogManager.CreateLogger("DataBaseWorker");
|
private new static readonly Logger Logger = LogManager.CreateLogger("MPQWorker");
|
||||||
|
|
||||||
public Data()
|
public Data()
|
||||||
//: base(0, new List<string> { "CoreData.mpq", "ClientData.mpq" }, "/base/d3-update-base-(?<version>.*?).mpq")
|
//: base(0, new List<string> { "CoreData.mpq", "ClientData.mpq" }, "/base/d3-update-base-(?<version>.*?).mpq")
|
||||||
: base(0, new List<string> { "Core.mpq", "Core1.mpq", "Core2.mpq", "Core3.mpq", "Core4.mpq" }, "/base/d3-update-base-(?<version>.*?).mpq")
|
: base(0, new List<string> { "Core.mpq", "Core1.mpq", "Core2.mpq", "Core3.mpq", "Core4.mpq" },
|
||||||
{ }
|
"/base/d3-update-base-(?<version>.*?).mpq")
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -202,7 +202,29 @@ namespace DiIiS_NA.Core.MPQ
|
|||||||
}
|
}
|
||||||
public static Dictionary<string, int> LoadActors()
|
public static Dictionary<string, int> LoadActors()
|
||||||
{
|
{
|
||||||
return Enum.GetValues<ActorSno>().Where(x => x != ActorSno.__NONE).ToDictionary(x => x.ToString().Substring(1), x => (int)x);
|
var dict = Enum.GetValues<ActorSno>().Where(x => x != ActorSno.__NONE).ToDictionary(x => x.ToString().Substring(1), x => (int)x);
|
||||||
|
|
||||||
|
// TODO: merge with LINQ above.
|
||||||
|
// this parses enum values that has SnoFileNameAttribute, in case the dict linq above didn't get a correct name
|
||||||
|
// for the actor file.
|
||||||
|
foreach (var d in Enum.GetValues<ActorSno>())
|
||||||
|
{
|
||||||
|
var enumType = typeof(ActorSno);
|
||||||
|
var memberInfos =
|
||||||
|
enumType.GetMember(d.ToString());
|
||||||
|
if (memberInfos.Length == 0)
|
||||||
|
continue;
|
||||||
|
var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == enumType);
|
||||||
|
if (enumValueMemberInfo == null) continue;
|
||||||
|
var valueAttributes = enumValueMemberInfo.GetCustomAttributes(typeof(SnoFileNameAttribute), false)
|
||||||
|
.Select(s=>(SnoFileNameAttribute)s)
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (valueAttributes != null)
|
||||||
|
{
|
||||||
|
dict.Add(valueAttributes.FileName, (int)d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dict;
|
||||||
}
|
}
|
||||||
public static Dictionary<string, int> LoadAdventure()
|
public static Dictionary<string, int> LoadAdventure()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -12,7 +12,7 @@ using DiIiS_NA.GameServer.Core.Types.TagMap;
|
|||||||
namespace DiIiS_NA.Core.MPQ.FileFormats
|
namespace DiIiS_NA.Core.MPQ.FileFormats
|
||||||
{
|
{
|
||||||
[FileFormat(SNOGroup.Actor)]
|
[FileFormat(SNOGroup.Actor)]
|
||||||
public class Actor : FileFormat
|
public class ActorData : FileFormat
|
||||||
{
|
{
|
||||||
public Header Header { get; private set; }
|
public Header Header { get; private set; }
|
||||||
public int Flags { get; private set; }
|
public int Flags { get; private set; }
|
||||||
@ -42,7 +42,7 @@ namespace DiIiS_NA.Core.MPQ.FileFormats
|
|||||||
public string CastingNotes { get; private set; }
|
public string CastingNotes { get; private set; }
|
||||||
public string VoiceOverRole { get; private set; }
|
public string VoiceOverRole { get; private set; }
|
||||||
|
|
||||||
public Actor(MpqFile file)
|
public ActorData(MpqFile file)
|
||||||
{
|
{
|
||||||
var stream = file.Open();
|
var stream = file.Open();
|
||||||
Header = new Header(stream);
|
Header = new Header(stream);
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
|
||||||
|
using DiIiS_NA.LoginServer.AccountsSystem;
|
||||||
|
using DiIiS_NA.LoginServer.Battle;
|
||||||
|
|
||||||
|
namespace DiIiS_NA.GameServer.CommandManager;
|
||||||
|
|
||||||
|
[CommandGroup("actors",
|
||||||
|
"Actors info (does not include Players, Minions and Monsters). This is useful for testing purposes.",
|
||||||
|
Account.UserLevels.Tester)]
|
||||||
|
public class ActorsCommand : CommandGroup
|
||||||
|
{
|
||||||
|
[Command("all", "Lists all actors.", Account.UserLevels.Tester)]
|
||||||
|
public string All(string[] @params, BattleClient invokerClient)
|
||||||
|
{
|
||||||
|
if (invokerClient?.InGameClient?.Player is not {} player)
|
||||||
|
return "You are not in game.";
|
||||||
|
|
||||||
|
return $"World [{player.World.SNO}]\nAll actors:" + string.Join("\n", player.World.Actors
|
||||||
|
.OrderBy(a =>
|
||||||
|
{
|
||||||
|
var position = player.Position;
|
||||||
|
return a.Value.Position.DistanceSquared(ref position);
|
||||||
|
}).Select(a =>
|
||||||
|
{
|
||||||
|
var position = player.Position;
|
||||||
|
var distance = a.Value.Position.DistanceSquared(ref position);
|
||||||
|
return $"[{a.Value.GetType().Name}] - {a.Value.SNO}\n" +
|
||||||
|
$" > Distance: {distance}\n" +
|
||||||
|
$" > SnoId: {(int)a.Value.SNO}";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("revealed", "Lists all revealed actors.", Account.UserLevels.Tester)]
|
||||||
|
public string Revealed(string[] @params, BattleClient invokerClient)
|
||||||
|
{
|
||||||
|
if (invokerClient?.InGameClient?.Player is not {} player)
|
||||||
|
return "You are not in game.";
|
||||||
|
|
||||||
|
return $"World [{player.World.SNO}]\nVisible actors:" + string.Join("\n", player.World.Actors
|
||||||
|
.Where(a => a.Value.IsRevealedToPlayer(player))
|
||||||
|
.OrderBy(a =>
|
||||||
|
{
|
||||||
|
var position = player.Position;
|
||||||
|
return a.Value.Position.DistanceSquared(ref position);
|
||||||
|
}).Select(a =>
|
||||||
|
{
|
||||||
|
var position = player.Position;
|
||||||
|
var distance = a.Value.Position.DistanceSquared(ref position);
|
||||||
|
return $"[{a.Value.GetType().Name}] - {a.Value.SNO}\n" +
|
||||||
|
$" > Distance: {distance}\n" +
|
||||||
|
$" > SnoId: {(int)a.Value.SNO}";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("setoperable", "Sets all actors operable (not the wisest invention).", Account.UserLevels.Tester)]
|
||||||
|
public string Operable(string[] @params, BattleClient invokerClient)
|
||||||
|
{
|
||||||
|
if (invokerClient?.InGameClient?.Player is not {} player)
|
||||||
|
return "You are not in game.";
|
||||||
|
|
||||||
|
if (@params is { Length: > 0 })
|
||||||
|
{
|
||||||
|
if (!Enum.TryParse<ActorSno>(@params[0].AsSpan(), out var actorSno))
|
||||||
|
{
|
||||||
|
return "Invalid actor SNO.";
|
||||||
|
}
|
||||||
|
|
||||||
|
var actor = player.World.Actors.FirstOrDefault(a => a.Value.SNO == actorSno);
|
||||||
|
if (actor.Value is null)
|
||||||
|
return "Actor not found.";
|
||||||
|
actor.Value.SetVisible(true);
|
||||||
|
actor.Value.SetUsable(true);
|
||||||
|
}
|
||||||
|
var actors = player.World.Actors.Select(s=>s.Value).ToArray();
|
||||||
|
foreach (var actor in actors)
|
||||||
|
{
|
||||||
|
actor.SetVisible(true);
|
||||||
|
actor.SetUsable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"All {actors.Length} world actors are now operable.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,8 +5,8 @@ using DiIiS_NA.LoginServer.AccountsSystem;
|
|||||||
|
|
||||||
namespace DiIiS_NA.GameServer.CommandManager;
|
namespace DiIiS_NA.GameServer.CommandManager;
|
||||||
|
|
||||||
[CommandGroup("doors", "Information about all doors in the vicinity. This is useful for testing purposes.. Useful for testing.", Account.UserLevels.Tester)]
|
[CommandGroup("doors", "Information about all doors in the vicinity. This is useful for testing purposes.", Account.UserLevels.Tester)]
|
||||||
public class OpenDoorCommand : CommandGroup
|
public class DoorsCommand : CommandGroup
|
||||||
{
|
{
|
||||||
[Command("all", "Activate all doors. This is useful for testing purposes.\nUsage: !open all", Account.UserLevels.Tester)]
|
[Command("all", "Activate all doors. This is useful for testing purposes.\nUsage: !open all", Account.UserLevels.Tester)]
|
||||||
public string OpenAllDoors(string[] @params, BattleClient invokerClient)
|
public string OpenAllDoors(string[] @params, BattleClient invokerClient)
|
||||||
@ -9,7 +9,6 @@ using DiIiS_NA.GameServer.GSSystem.ActorSystem;
|
|||||||
using DiIiS_NA.GameServer.GSSystem.ObjectsSystem;
|
using DiIiS_NA.GameServer.GSSystem.ObjectsSystem;
|
||||||
using DiIiS_NA.GameServer.MessageSystem;
|
using DiIiS_NA.GameServer.MessageSystem;
|
||||||
using DiIiS_NA.LoginServer.Battle;
|
using DiIiS_NA.LoginServer.Battle;
|
||||||
using Actor = DiIiS_NA.Core.MPQ.FileFormats.Actor;
|
|
||||||
|
|
||||||
namespace DiIiS_NA.GameServer.CommandManager;
|
namespace DiIiS_NA.GameServer.CommandManager;
|
||||||
|
|
||||||
@ -194,7 +193,7 @@ public class ModifySpeedCommand : CommandGroup
|
|||||||
|
|
||||||
return matches.Aggregate(matches.Count >= 1 ? "Actor Matches:\n" : "No match found.",
|
return matches.Aggregate(matches.Count >= 1 ? "Actor Matches:\n" : "No match found.",
|
||||||
(current, match) => current +
|
(current, match) => current +
|
||||||
$"[{match.SNOId:D6}] {match.Name} ({((Actor)match.Data).Type} {(((Actor)match.Data).Type == ActorType.Gizmo ? ((int)((Actor)match.Data).TagMap[ActorKeys.GizmoGroup]).ToString() : "")})\n");
|
$"[{match.SNOId:D6}] {match.Name} ({((ActorData)match.Data).Type} {(((ActorData)match.Data).Type == ActorType.Gizmo ? ((int)((ActorData)match.Data).TagMap[ActorKeys.GizmoGroup]).ToString() : "")})\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("rope", "Allows you to search for an rope.\nUsage: lookup rope <pattern>")]
|
[Command("rope", "Allows you to search for an rope.\nUsage: lookup rope <pattern>")]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using DiIiS_NA.Core.MPQ;
|
||||||
|
|
||||||
namespace DiIiS_NA.D3_GameServer.Core.Types.SNO
|
namespace DiIiS_NA.D3_GameServer.Core.Types.SNO
|
||||||
{
|
{
|
||||||
@ -7535,12 +7536,14 @@ namespace DiIiS_NA.D3_GameServer.Core.Types.SNO
|
|||||||
_wizard_tornado_golden = 215324,
|
_wizard_tornado_golden = 215324,
|
||||||
_inviscylindercollisionsmall = 215351,
|
_inviscylindercollisionsmall = 215351,
|
||||||
_wizard_waveofforce_runecrimson_shell = 215420,
|
_wizard_waveofforce_runecrimson_shell = 215420,
|
||||||
|
[SnoFileName("a1dun_Caves_Nephalem Altar_A_Chest_03")]
|
||||||
_a1dun_caves_nephalem_altar_a_chest_03 = 215434,
|
_a1dun_caves_nephalem_altar_a_chest_03 = 215434,
|
||||||
_arcanumorb_model = 215444,
|
_arcanumorb_model = 215444,
|
||||||
_fallenshaman_a_unique01whipple = 215445,
|
_fallenshaman_a_unique01whipple = 215445,
|
||||||
_wizard_waveofforce_runeobsidian_shell = 215488,
|
_wizard_waveofforce_runeobsidian_shell = 215488,
|
||||||
_cow_gem_flippy = 215500,
|
_cow_gem_flippy = 215500,
|
||||||
_wizard_waveofforce_runegolden_shell = 215511,
|
_wizard_waveofforce_runegolden_shell = 215511,
|
||||||
|
[SnoFileName("a1dun_Caves_Nephalem Altar_A_Chest_03_B")]
|
||||||
_a1dun_caves_nephalem_altar_a_chest_03_b = 215512,
|
_a1dun_caves_nephalem_altar_a_chest_03_b = 215512,
|
||||||
_wizard_frostnova_critbuff_swipe = 215516,
|
_wizard_frostnova_critbuff_swipe = 215516,
|
||||||
_monk_hol_stage03_ribbongeo = 215635,
|
_monk_hol_stage03_ribbongeo = 215635,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
using DiIiS_NA.Core.Logging;
|
using DiIiS_NA.Core.Logging;
|
||||||
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
|
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
|
||||||
using DiIiS_NA.GameServer.Core.Types.Math;
|
using DiIiS_NA.GameServer.Core.Types.Math;
|
||||||
using DiIiS_NA.GameServer.Core.Types.Misc;
|
|
||||||
using DiIiS_NA.GameServer.Core.Types.SNO;
|
using DiIiS_NA.GameServer.Core.Types.SNO;
|
||||||
using DiIiS_NA.GameServer.Core.Types.TagMap;
|
using DiIiS_NA.GameServer.Core.Types.TagMap;
|
||||||
using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations;
|
using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations;
|
||||||
@ -9,7 +8,6 @@ using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Hirelings;
|
|||||||
using DiIiS_NA.GameServer.GSSystem.GameSystem;
|
using DiIiS_NA.GameServer.GSSystem.GameSystem;
|
||||||
using DiIiS_NA.GameServer.GSSystem.GeneratorsSystem;
|
using DiIiS_NA.GameServer.GSSystem.GeneratorsSystem;
|
||||||
using DiIiS_NA.GameServer.GSSystem.ItemsSystem;
|
using DiIiS_NA.GameServer.GSSystem.ItemsSystem;
|
||||||
using DiIiS_NA.GameServer.GSSystem.MapSystem;
|
|
||||||
using DiIiS_NA.GameServer.GSSystem.ObjectsSystem;
|
using DiIiS_NA.GameServer.GSSystem.ObjectsSystem;
|
||||||
using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
|
using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
|
||||||
using DiIiS_NA.GameServer.GSSystem.PowerSystem;
|
using DiIiS_NA.GameServer.GSSystem.PowerSystem;
|
||||||
@ -26,8 +24,12 @@ using System.Collections.Generic;
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DiIiS_NA.Core.MPQ;
|
using DiIiS_NA.Core.MPQ;
|
||||||
|
using DiIiS_NA.Core.MPQ.FileFormats;
|
||||||
using DiIiS_NA.D3_GameServer.GSSystem.GameSystem;
|
using DiIiS_NA.D3_GameServer.GSSystem.GameSystem;
|
||||||
|
using Circle = DiIiS_NA.GameServer.Core.Types.Misc.Circle;
|
||||||
using Player = DiIiS_NA.GameServer.GSSystem.PlayerSystem.Player;
|
using Player = DiIiS_NA.GameServer.GSSystem.PlayerSystem.Player;
|
||||||
|
using Scene = DiIiS_NA.GameServer.GSSystem.MapSystem.Scene;
|
||||||
|
using World = DiIiS_NA.GameServer.GSSystem.MapSystem.World;
|
||||||
|
|
||||||
namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
||||||
{
|
{
|
||||||
@ -168,13 +170,8 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The info set for actor. (erekose)
|
/// The info set for actor. (erekose)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DiIiS_NA.Core.MPQ.FileFormats.Actor ActorData
|
public DiIiS_NA.Core.MPQ.FileFormats.ActorData ActorData
|
||||||
{
|
=> (DiIiS_NA.Core.MPQ.FileFormats.ActorData)MPQStorage.Data.Assets[SNOGroup.Actor][(int)SNO].Data;
|
||||||
get
|
|
||||||
{
|
|
||||||
return (DiIiS_NA.Core.MPQ.FileFormats.Actor)MPQStorage.Data.Assets[SNOGroup.Actor][(int)SNO].Data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The animation set for actor.
|
/// The animation set for actor.
|
||||||
@ -235,8 +232,8 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
|||||||
|
|
||||||
AffixList = new List<Affix>();
|
AffixList = new List<Affix>();
|
||||||
|
|
||||||
//if (tags != null && tags.ContainsKey(MarkerKeys.OnActorSpawnedScript) && tags[MarkerKeys.OnActorSpawnedScript].Id == 178440)
|
// if (tags != null && tags.ContainsKey(MarkerKeys.OnActorSpawnedScript) && tags[MarkerKeys.OnActorSpawnedScript].Id == 178440)
|
||||||
// this.AnimationSet = (Mooege.Common.MPQ.FileFormats.AnimSet)Mooege.Common.MPQ.MPQStorage.Data.Assets[SNOGroup.AnimSet][11849].Data; //OminNPC_Male (Wounded)
|
// AnimationSet = (AnimSet)MPQStorage.Data.Assets[SNOGroup.AnimSet][11849].Data; //OminNPC_Male (Wounded)
|
||||||
//else
|
//else
|
||||||
// if (this.ActorData.AnimSetSNO != -1)
|
// if (this.ActorData.AnimSetSNO != -1)
|
||||||
// this.AnimationSet = (Mooege.Common.MPQ.FileFormats.AnimSet)Mooege.Common.MPQ.MPQStorage.Data.Assets[SNOGroup.AnimSet][this.ActorData.AnimSetSNO].Data;
|
// this.AnimationSet = (Mooege.Common.MPQ.FileFormats.AnimSet)Mooege.Common.MPQ.MPQStorage.Data.Assets[SNOGroup.AnimSet][this.ActorData.AnimSetSNO].Data;
|
||||||
@ -261,7 +258,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
|||||||
// Listen for quest progress if the actor has a QuestRange attached to it
|
// Listen for quest progress if the actor has a QuestRange attached to it
|
||||||
//foreach (var quest in World.Game.QuestManager.Quests)
|
//foreach (var quest in World.Game.QuestManager.Quests)
|
||||||
if (_questRange != null)
|
if (_questRange != null)
|
||||||
World.Game.QuestManager.OnQuestProgress += new QuestManager.QuestProgressDelegate(quest_OnQuestProgress);
|
World.Game.QuestManager.OnQuestProgress += quest_OnQuestProgress;
|
||||||
UpdateQuestRangeVisibility();
|
UpdateQuestRangeVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
|||||||
{
|
{
|
||||||
if (!MPQStorage.Data.Assets[SNOGroup.Actor].ContainsKey((int)sno))
|
if (!MPQStorage.Data.Assets[SNOGroup.Actor].ContainsKey((int)sno))
|
||||||
{
|
{
|
||||||
//Logger.Warn("Actor asset not found, Id: {0}", snoId);
|
Logger.Error("$[underline on white]$Actor asset not found$[/]$, Id: $[underline white]${0}$[/]$ - $[underline white]${1}$[/]$", (int)sno, sno.ToString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var actorAsset = MPQStorage.Data.Assets[SNOGroup.Actor][(int)sno];
|
var actorAsset = MPQStorage.Data.Assets[SNOGroup.Actor][(int)sno];
|
||||||
var actorData = actorAsset.Data as DiIiS_NA.Core.MPQ.FileFormats.Actor;
|
var actorData = actorAsset.Data as DiIiS_NA.Core.MPQ.FileFormats.ActorData;
|
||||||
if (actorData == null)
|
if (actorData == null)
|
||||||
{
|
{
|
||||||
Logger.Warn("Actor data not found, Id: {0}", sno);
|
Logger.Warn("Actor data not found, Id: {0}", sno);
|
||||||
|
|||||||
@ -48,7 +48,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
|||||||
|
|
||||||
Attributes[GameAttribute.MinimapActive] = true;
|
Attributes[GameAttribute.MinimapActive] = true;
|
||||||
Attributes[GameAttribute.Untargetable] = false;
|
Attributes[GameAttribute.Untargetable] = false;
|
||||||
var bossEncounter = ((ActorSNO.Target as DiIiS_NA.Core.MPQ.FileFormats.Actor).TagMap[MarkerKeys.BossEncounter].Target as DiIiS_NA.Core.MPQ.FileFormats.BossEncounter);
|
var bossEncounter = ((ActorSNO.Target as DiIiS_NA.Core.MPQ.FileFormats.ActorData).TagMap[MarkerKeys.BossEncounter].Target as DiIiS_NA.Core.MPQ.FileFormats.BossEncounter);
|
||||||
DestWorld = bossEncounter.Worlds[0];
|
DestWorld = bossEncounter.Worlds[0];
|
||||||
switch (DestWorld)
|
switch (DestWorld)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,7 +4,6 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MonsterFF = DiIiS_NA.Core.MPQ.FileFormats.Monster;
|
using MonsterFF = DiIiS_NA.Core.MPQ.FileFormats.Monster;
|
||||||
using ActorFF = DiIiS_NA.Core.MPQ.FileFormats.Actor;
|
|
||||||
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Pet;
|
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Pet;
|
||||||
using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
|
using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
|
||||||
using DiIiS_NA.GameServer.MessageSystem;
|
using DiIiS_NA.GameServer.MessageSystem;
|
||||||
|
|||||||
@ -96,7 +96,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
|||||||
};
|
};
|
||||||
|
|
||||||
//this.World.Game.WorldGenerator.Actions.Enqueue(() =>
|
//this.World.Game.WorldGenerator.Actions.Enqueue(() =>
|
||||||
World.Game.WorldGenerator.LoadActor(ActorToSpawnSNO, location, World, ((DiIiS_NA.Core.MPQ.FileFormats.Actor)ActorToSpawnSNO.Target).TagMap);
|
World.Game.WorldGenerator.LoadActor(ActorToSpawnSNO, location, World, ((DiIiS_NA.Core.MPQ.FileFormats.ActorData)ActorToSpawnSNO.Target).TagMap);
|
||||||
//Mooege.Core.GS.Generators.WorldGenerator.loadActor(ActorToSpawnSNO, location, this.World, ((Mooege.Common.MPQ.FileFormats.Actor)ActorToSpawnSNO.Target).TagMap);
|
//Mooege.Core.GS.Generators.WorldGenerator.loadActor(ActorToSpawnSNO, location, this.World, ((Mooege.Common.MPQ.FileFormats.Actor)ActorToSpawnSNO.Target).TagMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2226,7 +2226,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GeneratorsSystem
|
|||||||
/// <param name="world">The world to which to add loaded actors</param>
|
/// <param name="world">The world to which to add loaded actors</param>
|
||||||
private void LoadLevelAreas(Dictionary<int, List<Scene>> levelAreas, World world)
|
private void LoadLevelAreas(Dictionary<int, List<Scene>> levelAreas, World world)
|
||||||
{
|
{
|
||||||
Dictionary<PRTransform, DiIiS_NA.Core.MPQ.FileFormats.Actor> dict = new Dictionary<PRTransform, DiIiS_NA.Core.MPQ.FileFormats.Actor>();
|
Dictionary<PRTransform, DiIiS_NA.Core.MPQ.FileFormats.ActorData> dict = new Dictionary<PRTransform, DiIiS_NA.Core.MPQ.FileFormats.ActorData>();
|
||||||
foreach (int la in levelAreas.Keys)
|
foreach (int la in levelAreas.Keys)
|
||||||
{
|
{
|
||||||
SNOHandle levelAreaHandle = new SNOHandle(SNOGroup.LevelArea, la);
|
SNOHandle levelAreaHandle = new SNOHandle(SNOGroup.LevelArea, la);
|
||||||
@ -2270,7 +2270,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GeneratorsSystem
|
|||||||
{
|
{
|
||||||
var handle = new SNOHandle(207706);
|
var handle = new SNOHandle(207706);
|
||||||
if (handle == null || gizmoLocations.Count == 0) continue;
|
if (handle == null || gizmoLocations.Count == 0) continue;
|
||||||
LazyLoadActor(handle, gizmoLocations.PickRandom(), world, ((DiIiS_NA.Core.MPQ.FileFormats.Actor)handle.Target).TagMap);
|
LazyLoadActor(handle, gizmoLocations.PickRandom(), world, ((DiIiS_NA.Core.MPQ.FileFormats.ActorData)handle.Target).TagMap);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
foreach (var location in gizmoLocations)
|
foreach (var location in gizmoLocations)
|
||||||
@ -2290,7 +2290,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GeneratorsSystem
|
|||||||
seed -= pair.Value;
|
seed -= pair.Value;
|
||||||
}
|
}
|
||||||
if (gizmoHandle == null) continue;
|
if (gizmoHandle == null) continue;
|
||||||
LazyLoadActor(gizmoHandle, location, world, ((DiIiS_NA.Core.MPQ.FileFormats.Actor)gizmoHandle.Target).TagMap);
|
LazyLoadActor(gizmoHandle, location, world, ((DiIiS_NA.Core.MPQ.FileFormats.ActorData)gizmoHandle.Target).TagMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2298,7 +2298,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GeneratorsSystem
|
|||||||
{
|
{
|
||||||
var handleChest = new SNOHandle(96993); //leg chest
|
var handleChest = new SNOHandle(96993); //leg chest
|
||||||
if (handleChest == null) continue;
|
if (handleChest == null) continue;
|
||||||
var goldenChest = LoadActor(handleChest, gizmoLocations.PickRandom(), world, ((DiIiS_NA.Core.MPQ.FileFormats.Actor)handleChest.Target).TagMap);
|
var goldenChest = LoadActor(handleChest, gizmoLocations.PickRandom(), world, ((DiIiS_NA.Core.MPQ.FileFormats.ActorData)handleChest.Target).TagMap);
|
||||||
if (goldenChest > 0)
|
if (goldenChest > 0)
|
||||||
(world.GetActorByGlobalId(goldenChest) as LegendaryChest).ChestActive = true;
|
(world.GetActorByGlobalId(goldenChest) as LegendaryChest).ChestActive = true;
|
||||||
}
|
}
|
||||||
@ -2657,43 +2657,55 @@ namespace DiIiS_NA.GameServer.GSSystem.GeneratorsSystem
|
|||||||
//TODO: Move this out as loading actors can happen even after world was generated
|
//TODO: Move this out as loading actors can happen even after world was generated
|
||||||
public uint LoadActor(SNOHandle actorHandle, PRTransform location, World world, TagMap tagMap, MonsterType monsterType = MonsterType.Default, int groupId = 0)
|
public uint LoadActor(SNOHandle actorHandle, PRTransform location, World world, TagMap tagMap, MonsterType monsterType = MonsterType.Default, int groupId = 0)
|
||||||
{
|
{
|
||||||
var actorSno = (ActorSno)actorHandle.Id; // TODO: maybe we can replace SNOHandle
|
try
|
||||||
if (world.QuadTree.Query<Waypoint>(new Core.Types.Misc.Circle(location.Vector3D.X, location.Vector3D.Y, 60f)).Count > 0 ||
|
|
||||||
world.QuadTree.Query<Portal>(new Core.Types.Misc.Circle(location.Vector3D.X, location.Vector3D.Y, 5f)).Count > 0)
|
|
||||||
{
|
{
|
||||||
Logger.Debug("Load actor {0} ignored - waypoint nearby.", actorSno);
|
var actorSno = (ActorSno)actorHandle.Id; // TODO: maybe we can replace SNOHandle
|
||||||
|
if (world.QuadTree
|
||||||
|
.Query<Waypoint>(new Core.Types.Misc.Circle(location.Vector3D.X, location.Vector3D.Y, 60f))
|
||||||
|
.Count > 0 ||
|
||||||
|
world.QuadTree
|
||||||
|
.Query<Portal>(new Core.Types.Misc.Circle(location.Vector3D.X, location.Vector3D.Y, 5f)).Count >
|
||||||
|
0)
|
||||||
|
{
|
||||||
|
Logger.Debug("Load actor {0} ignored - waypoint nearby.", actorSno);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actor = ActorFactory.Create(world, actorSno, tagMap);
|
||||||
|
|
||||||
|
switch (monsterType)
|
||||||
|
{
|
||||||
|
case MonsterType.Champion:
|
||||||
|
actor = new Champion(world, actorSno, tagMap);
|
||||||
|
actor.GroupId = groupId;
|
||||||
|
break;
|
||||||
|
case MonsterType.Elite:
|
||||||
|
actor = new Rare(world, actorSno, tagMap);
|
||||||
|
actor.GroupId = groupId;
|
||||||
|
break;
|
||||||
|
case MonsterType.EliteMinion:
|
||||||
|
actor = new RareMinion(world, actorSno, tagMap);
|
||||||
|
actor.GroupId = groupId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor == null)
|
||||||
|
{
|
||||||
|
if (actorSno != ActorSno.__NONE)
|
||||||
|
Logger.Warn("ActorFactory did not load actor {0}", actorHandle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.RotationW = location.Quaternion.W;
|
||||||
|
actor.RotationAxis = location.Quaternion.Vector3D;
|
||||||
|
actor.EnterWorld(location.Vector3D);
|
||||||
|
return actor.GlobalID;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error("Error loading actor {0} at {1}", actorHandle.Id, location);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var actor = ActorFactory.Create(world, actorSno, tagMap);
|
|
||||||
|
|
||||||
switch (monsterType)
|
|
||||||
{
|
|
||||||
case MonsterType.Champion:
|
|
||||||
actor = new Champion(world, actorSno, tagMap);
|
|
||||||
actor.GroupId = groupId;
|
|
||||||
break;
|
|
||||||
case MonsterType.Elite:
|
|
||||||
actor = new Rare(world, actorSno, tagMap);
|
|
||||||
actor.GroupId = groupId;
|
|
||||||
break;
|
|
||||||
case MonsterType.EliteMinion:
|
|
||||||
actor = new RareMinion(world, actorSno, tagMap);
|
|
||||||
actor.GroupId = groupId;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actor == null)
|
|
||||||
{
|
|
||||||
if (actorSno != ActorSno.__NONE)
|
|
||||||
Logger.Warn("ActorFactory did not load actor {0}", actorHandle);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
actor.RotationW = location.Quaternion.W;
|
|
||||||
actor.RotationAxis = location.Quaternion.Vector3D;
|
|
||||||
actor.EnterWorld(location.Vector3D);
|
|
||||||
return actor.GlobalID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LazyLoadActor(SNOHandle actorHandle, PRTransform location, World world, TagMap tagMap, MonsterType monsterType = MonsterType.Default)
|
public void LazyLoadActor(SNOHandle actorHandle, PRTransform location, World world, TagMap tagMap, MonsterType monsterType = MonsterType.Default)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem.Implementations
|
|||||||
public Book(MapSystem.World world, DiIiS_NA.Core.MPQ.FileFormats.GameBalance.ItemTable definition, int cork = -1, bool cork2 = false, int cork3 = -1)
|
public Book(MapSystem.World world, DiIiS_NA.Core.MPQ.FileFormats.GameBalance.ItemTable definition, int cork = -1, bool cork2 = false, int cork3 = -1)
|
||||||
: base(world, definition)
|
: base(world, definition)
|
||||||
{
|
{
|
||||||
var actorData = ActorSNO.Target as DiIiS_NA.Core.MPQ.FileFormats.Actor;
|
var actorData = ActorSNO.Target as DiIiS_NA.Core.MPQ.FileFormats.ActorData;
|
||||||
|
|
||||||
if (actorData.TagMap.ContainsKey(ActorKeys.Lore))
|
if (actorData.TagMap.ContainsKey(ActorKeys.Lore))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -443,7 +443,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
|
|||||||
{
|
{
|
||||||
foreach (var asset in MPQStorage.Data.Assets[SNOGroup.Actor].Values)
|
foreach (var asset in MPQStorage.Data.Assets[SNOGroup.Actor].Values)
|
||||||
{
|
{
|
||||||
Actor data = asset.Data as Actor;
|
ActorData data = asset.Data as ActorData;
|
||||||
if (data != null && data.TagMap.ContainsKey(ActorKeys.Lore))
|
if (data != null && data.TagMap.ContainsKey(ActorKeys.Lore))
|
||||||
{
|
{
|
||||||
if (Lore.ContainsKey(data.TagMap[ActorKeys.Lore].Id)) continue;
|
if (Lore.ContainsKey(data.TagMap[ActorKeys.Lore].Id)) continue;
|
||||||
|
|||||||
@ -14,13 +14,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ObjectsSystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public uint GlobalID
|
public uint GlobalID
|
||||||
{
|
{
|
||||||
get
|
get => GlobalIDOverride > 0 ? GlobalIDOverride : _globalID;
|
||||||
{
|
|
||||||
if (GlobalIDOverride > 0)
|
|
||||||
return GlobalIDOverride;
|
|
||||||
else
|
|
||||||
return _globalID;
|
|
||||||
}
|
|
||||||
private set
|
private set
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1680,7 +1680,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
|
|||||||
public void OnHirelingSwapAgreeMessage()
|
public void OnHirelingSwapAgreeMessage()
|
||||||
{
|
{
|
||||||
Hireling hireling = null;
|
Hireling hireling = null;
|
||||||
DiIiS_NA.Core.MPQ.FileFormats.Actor Data = null;
|
DiIiS_NA.Core.MPQ.FileFormats.ActorData Data = null;
|
||||||
if (World.Game.Players.Count > 1) return;
|
if (World.Game.Players.Count > 1) return;
|
||||||
|
|
||||||
|
|
||||||
@ -1688,20 +1688,20 @@ public class Player : Actor, IMessageConsumer, IUpdateable
|
|||||||
{
|
{
|
||||||
case 72061:
|
case 72061:
|
||||||
//Templar
|
//Templar
|
||||||
Data = (DiIiS_NA.Core.MPQ.FileFormats.Actor)MPQStorage.Data.Assets[SNOGroup.Actor][52693].Data;
|
Data = (DiIiS_NA.Core.MPQ.FileFormats.ActorData)MPQStorage.Data.Assets[SNOGroup.Actor][52693].Data;
|
||||||
hireling = new Templar(World, ActorSno._hireling_templar, Data.TagMap);
|
hireling = new Templar(World, ActorSno._hireling_templar, Data.TagMap);
|
||||||
hireling.GBHandle.GBID = StringHashHelper.HashItemName("Templar");
|
hireling.GBHandle.GBID = StringHashHelper.HashItemName("Templar");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 72738:
|
case 72738:
|
||||||
//Scoundrel
|
//Scoundrel
|
||||||
Data = (DiIiS_NA.Core.MPQ.FileFormats.Actor)MPQStorage.Data.Assets[SNOGroup.Actor][52694].Data;
|
Data = (DiIiS_NA.Core.MPQ.FileFormats.ActorData)MPQStorage.Data.Assets[SNOGroup.Actor][52694].Data;
|
||||||
hireling = new Templar(World, ActorSno._hireling_scoundrel, Data.TagMap);
|
hireling = new Templar(World, ActorSno._hireling_scoundrel, Data.TagMap);
|
||||||
hireling.GBHandle.GBID = StringHashHelper.HashItemName("Scoundrel");
|
hireling.GBHandle.GBID = StringHashHelper.HashItemName("Scoundrel");
|
||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
//Enchantress
|
//Enchantress
|
||||||
Data = (DiIiS_NA.Core.MPQ.FileFormats.Actor)MPQStorage.Data.Assets[SNOGroup.Actor][4482].Data;
|
Data = (DiIiS_NA.Core.MPQ.FileFormats.ActorData)MPQStorage.Data.Assets[SNOGroup.Actor][4482].Data;
|
||||||
hireling = new Templar(World, ActorSno._hireling_enchantress, Data.TagMap);
|
hireling = new Templar(World, ActorSno._hireling_enchantress, Data.TagMap);
|
||||||
hireling.GBHandle.GBID = StringHashHelper.HashItemName("Enchantress");
|
hireling.GBHandle.GBID = StringHashHelper.HashItemName("Enchantress");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1426,13 +1426,14 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
|
|||||||
Completed = false,
|
Completed = false,
|
||||||
Saveable = true,
|
Saveable = true,
|
||||||
NextStep = 14,
|
NextStep = 14,
|
||||||
Objectives = new List<Objective> { Objective.Default(), Objective.Default() },
|
Objectives = new List<Objective> { Objective.WithLimit(2) },
|
||||||
OnAdvance = () =>
|
OnAdvance = () =>
|
||||||
{ //find 2 Orbs
|
{ //find 2 Orbs
|
||||||
DestroyFollower(ActorSno._leah);
|
DestroyFollower(ActorSno._leah);
|
||||||
AddFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
|
var world = Game.GetWorld(WorldSno.trout_town);
|
||||||
|
AddFollower(world, ActorSno._leah);
|
||||||
ListenInteract(ActorSno._a1dun_caves_nephalem_altar_a_chest_03, 1, new CompleteObjective(0));
|
ListenInteract(ActorSno._a1dun_caves_nephalem_altar_a_chest_03, 1, new CompleteObjective(0));
|
||||||
ListenInteract(ActorSno._a1dun_caves_nephalem_altar_a_chest_03_b, 1, new CompleteObjective(1));
|
ListenInteract(ActorSno._a1dun_caves_nephalem_altar_a_chest_03_b, 1, new CompleteObjective(0));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1441,7 +1442,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
|
|||||||
Completed = false,
|
Completed = false,
|
||||||
Saveable = true,
|
Saveable = true,
|
||||||
NextStep = 30,
|
NextStep = 30,
|
||||||
Objectives = new List<Objective> { new Objective { Limit = 2, Counter = 0 } },
|
Objectives = new List<Objective> { Objective.WithLimit(2) },
|
||||||
OnAdvance = () =>
|
OnAdvance = () =>
|
||||||
{ //use 2 stones
|
{ //use 2 stones
|
||||||
var world = Game.GetWorld(WorldSno.trout_town);
|
var world = Game.GetWorld(WorldSno.trout_town);
|
||||||
|
|||||||
@ -68,7 +68,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem.QuestEvents.Implementations
|
|||||||
{
|
{
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var monsterSNOHandle = new Core.Types.SNO.SNOHandle(SnoId);
|
var monsterSNOHandle = new Core.Types.SNO.SNOHandle(SnoId);
|
||||||
var monsterActor = monsterSNOHandle.Target as DiIiS_NA.Core.MPQ.FileFormats.Actor;
|
var monsterActor = monsterSNOHandle.Target as DiIiS_NA.Core.MPQ.FileFormats.ActorData;
|
||||||
|
|
||||||
foreach (Vector3D coords in Coordinates)
|
foreach (Vector3D coords in Coordinates)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -18,7 +18,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem.QuestEvents.Implementations
|
|||||||
//Logger.Trace("SpawnSnakemans event started");
|
//Logger.Trace("SpawnSnakemans event started");
|
||||||
var point = new Vector3D { X = 835.331f, Y = 410.121f, Z = 161.842f };
|
var point = new Vector3D { X = 835.331f, Y = 410.121f, Z = 161.842f };
|
||||||
var snakeManHandle = new Core.Types.SNO.SNOHandle((int)ActorSno._khamsin_snakeman_melee);
|
var snakeManHandle = new Core.Types.SNO.SNOHandle((int)ActorSno._khamsin_snakeman_melee);
|
||||||
var snakeManActor = snakeManHandle.Target as DiIiS_NA.Core.MPQ.FileFormats.Actor;
|
var snakeManActor = snakeManHandle.Target as DiIiS_NA.Core.MPQ.FileFormats.ActorData;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var caldeumGuard = world.FindActorAt(ActorSno._caldeumguard_cleaver_a, point, 20.0f);
|
var caldeumGuard = world.FindActorAt(ActorSno._caldeumguard_cleaver_a, point, 20.0f);
|
||||||
|
|||||||
@ -55,6 +55,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
|
|||||||
public int Counter;
|
public int Counter;
|
||||||
|
|
||||||
public static Objective Default() => new () { Limit = 1, Counter = 0 };
|
public static Objective Default() => new () { Limit = 1, Counter = 0 };
|
||||||
|
public static Objective WithLimit(int limit) => new () { Limit = limit, Counter = 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// key can be ActorSno (also multiplied), DestLevelAreaSno, ConversationSno
|
// key can be ActorSno (also multiplied), DestLevelAreaSno, ConversationSno
|
||||||
|
|||||||
Loading…
Reference in New Issue
user.block.title