diff --git a/README.md b/README.md
index b7cebe9..1e0f6a1 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,11 @@ Tibia OT Monster Converter is a tool for converting monster files between the va
| Format | Input % Complete | Output % Complete | Notes |
| - | - | - | - |
-| TFS XML | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Input-Status) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Output-Status) | - Most common OT Monster format which has been around for over a decade |
+| [TFS XML](https://github.com/otland/forgottenserver) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Input-Status) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Output-Status) | - Most common OT Monster format which has been around for over a decade |
| [PyOT](https://bitbucket.org/vapus/pyot/) | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/PyOT-Input-Status) | [80%](https://github.com/soul4soul/ot-monster-converter/wiki/PyOT-Output-Status) | - This format can be consider dead as PyOT development has ceased. Unless development is picked back up support for this format is unlikely to be completed. |
-| TFS revscriptsys | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Input-Status) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Output-Status) | - Very new OT monster format that was theorized many years ago. In the future there is a good chance it will replace TFS XML completely. This is likely the output type that most users of this program will use.
- Opentibiabr RevScriptSys format is not completely compatible with TFS RevScriptSys format |
-| [TibiaWiki](https://tibia.fandom.com/wiki/Main_Page) | [85%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Input-Status) | [90%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Output-Status) | - Helpful for keeping monsters up to date with cipbia
- See The [Infobox Creature Template](https://tibia.fandom.com/wiki/Template:Infobox_Creature) for information about TibiaWiki Format
- Monsters created from TibiaWiki will require corpse id, looktype, and spells to be created manually |
-| Cip Mon | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/Cip-Mon-Input-Status) | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/Cip-Mon-Output-Status) | - Format is for input purposes to easily generte monsters as they were in the 7.7 days |
+| [TFS revscriptsys](https://github.com/otland/forgottenserver) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Input-Status) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Output-Status) | - Very new OT monster format that was theorized many years ago. In the future there is a good chance it will replace TFS XML completely. This is likely the output type that most users of this program will use.
- Opentibiabr RevScriptSys format is not completely compatible with TFS RevScriptSys format |
+| [TibiaWiki](https://tibia.fandom.com/wiki/Main_Page) | [85%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Input-Status) | [90%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Output-Status) | - Helpful for keeping monsters up to date with cipbia
- See The [Infobox Creature Template](https://tibia.fandom.com/wiki/Template:Infobox_Creature) for information about TibiaWiki Format
- Monsters created from TibiaWiki will require some information to be added manually |
+| Cip Mon | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/Cip-Mon-Input-Status) | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/Cip-Mon-Output-Status) | - Format is for input purposes to easily generate monsters as they were in the 7.7 days |
## Graphical User Interface
@@ -49,7 +49,7 @@ Options:
otherwise flat folder structure is output
-h, --help show this message and exit
-Input Formats: TibiaWiki, TFS XML, Cip Mon
+Input Formats: TFS RevScriptSys, TibiaWiki, TFS XML, Cip Mon
Output Formats: TFS RevScriptSys, TibiaWiki, TFS XML, pyOT
Item Id Formats: KeepSourceIds, UseServerIds, UseClientIds
```
diff --git a/app/MonsterConverterTfsRevScriptSys/LuaGlue/MockTfsGame.cs b/app/MonsterConverterTfsRevScriptSys/LuaGlue/MockTfsGame.cs
new file mode 100644
index 0000000..6a65a7b
--- /dev/null
+++ b/app/MonsterConverterTfsRevScriptSys/LuaGlue/MockTfsGame.cs
@@ -0,0 +1,31 @@
+using MonsterConverterInterface;
+using MonsterConverterInterface.MonsterTypes;
+using MoonSharp.Interpreter;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MonsterConverterTfsRevScriptSys
+{
+ ///
+ /// The purpose of this class is to immitate the `Game` TFS class used to create monsterTypes that are used to register monsters
+ ///
+ [MoonSharpUserData]
+ class MockTfsGame
+ {
+ static Queue> convertedMonsters = new Queue>(1);
+
+ public static Queue> ConvertedMonsters
+ {
+ private set { ; }
+ get { return convertedMonsters; }
+ }
+
+ public static MockTfsMonsterType createMonsterType(string name)
+ {
+ return new MockTfsMonsterType(name);
+ }
+ }
+}
diff --git a/app/MonsterConverterTfsRevScriptSys/LuaGlue/MockTfsMonsterType.cs b/app/MonsterConverterTfsRevScriptSys/LuaGlue/MockTfsMonsterType.cs
new file mode 100644
index 0000000..6d557b0
--- /dev/null
+++ b/app/MonsterConverterTfsRevScriptSys/LuaGlue/MockTfsMonsterType.cs
@@ -0,0 +1,1108 @@
+using MonsterConverterInterface;
+using MonsterConverterInterface.MonsterTypes;
+using MoonSharp.Interpreter;
+using System;
+
+namespace MonsterConverterTfsRevScriptSys
+{
+ [MoonSharpUserData]
+ internal class MockTfsMonsterType
+ {
+ const int MAX_LOOTCHANCE = 100000;
+
+ private Monster mon = new Monster();
+
+ public MockTfsMonsterType(string name)
+ {
+ mon.RegisteredName = name;
+ }
+
+ public object onThink { get; set; }
+ public object onAppear { get; set; }
+ public object onDisappear { get; set; }
+ public object onMove { get; set; }
+ public object onSay { get; set; }
+
+ public void register(Table t)
+ {
+ DynValue dv;
+ ConvertResultEventArgs result = new ConvertResultEventArgs("temp");
+
+ dv = t.Get("name");
+ if (dv.Type == DataType.String)
+ {
+ mon.Name = dv.String;
+ }
+ else
+ {
+ mon.Name = mon.RegisteredName;
+ }
+
+ dv = t.Get("description");
+ if (dv.Type == DataType.String)
+ {
+ mon.Description = dv.String;
+ }
+
+ dv = t.Get("experience");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Experience = (int)dv.Number;
+ }
+
+ dv = t.Get("maxHealth");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Health = (int)dv.Number;
+ }
+
+ dv = t.Get("health");
+ if (dv.Type == DataType.Number)
+ {
+ result.AppendMessage("Health field not supported defaulting to max health");
+ result.IncreaseError(ConvertError.Warning);
+ }
+
+ dv = t.Get("runHealth");
+ if (dv.Type == DataType.Number)
+ {
+ mon.RunOnHealth = (int)dv.Number;
+ }
+
+ dv = t.Get("speed");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Speed = (int)dv.Number;
+ }
+
+ dv = t.Get("light");
+ if (dv.Type == DataType.Table)
+ {
+ Table light = dv.Table;
+ dv = light.Get("color");
+ if (dv.Type == DataType.Number)
+ {
+ mon.LightColor = (int)dv.Number;
+ }
+
+ dv = light.Get("level");
+ if (dv.Type == DataType.Number)
+ {
+ mon.LightLevel = (int)dv.Number;
+ }
+ }
+
+ dv = t.Get("changeTarget");
+ if (dv.Type == DataType.Table)
+ {
+ Table light = dv.Table;
+ dv = light.Get("chance");
+ if (dv.Type == DataType.Number)
+ {
+ mon.RetargetChance = (int)dv.Number;
+ }
+
+ dv = light.Get("interval");
+ if (dv.Type == DataType.Number)
+ {
+ mon.RetargetInterval = (int)dv.Number;
+ }
+ }
+
+ int summonCost = 0;
+ dv = t.Get("manaCost");
+ if (dv.Type == DataType.Number)
+ {
+ summonCost = (int)dv.Number;
+ }
+
+ dv = t.Get("flags");
+ if (dv.Type == DataType.Table)
+ {
+ Table flags = dv.Table;
+ dv = flags.Get("attackable");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.Attackable = dv.Boolean;
+ }
+
+ dv = flags.Get("healthHidden");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.HideHealth = dv.Boolean;
+ }
+
+ dv = flags.Get("boss");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.IsBoss = dv.Boolean;
+ }
+
+ dv = flags.Get("challengeable");
+ if (dv.Type == DataType.Boolean)
+ {
+ // not supported
+ }
+
+ dv = flags.Get("convinceable");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.ConvinceCost = summonCost;
+ }
+
+ dv = flags.Get("summonable");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.SummonCost = summonCost;
+ }
+
+ dv = flags.Get("ignoreSpawnBlock");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.IgnoreSpawnBlock = dv.Boolean;
+ }
+
+ dv = flags.Get("illusionable");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.IsIllusionable = dv.Boolean;
+ }
+
+ dv = flags.Get("hostile");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.IsHostile = dv.Boolean;
+ }
+
+ dv = flags.Get("pushable");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.IsPushable = dv.Boolean;
+ }
+
+ dv = flags.Get("canPushItems");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.PushItems = dv.Boolean;
+ }
+
+ dv = flags.Get("canPushCreatures");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.PushCreatures = dv.Boolean;
+ }
+
+ dv = flags.Get("targetDistance");
+ if (dv.Type == DataType.Number)
+ {
+ mon.TargetDistance = (int)dv.Number;
+ }
+
+ dv = flags.Get("staticAttackChance");
+ if (dv.Type == DataType.Number)
+ {
+ mon.StaticAttackChance = (int)dv.Number;
+ }
+
+ dv = flags.Get("canWalkOnEnergy");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.AvoidEnergy = !dv.Boolean;
+ }
+
+ dv = flags.Get("canWalkOnFire");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.AvoidFire = !dv.Boolean;
+ }
+
+ dv = flags.Get("canWalkOnPoison");
+ if (dv.Type == DataType.Boolean)
+ {
+ mon.AvoidPoison = dv.Boolean;
+ }
+ }
+
+ dv = t.Get("skull");
+ if (dv.Type == DataType.Number)
+ {
+ result.AppendMessage("Skull field not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+
+ dv = t.Get("corpse");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.CorpseId = (ushort)dv.Number;
+ }
+ dv = t.Get("outfit");
+ if (dv.Type == DataType.Table)
+ {
+ Table outfit = dv.Table;
+
+ dv = outfit.Get("lookTypeEx");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.LookType = LookType.Item;
+ mon.Look.LookId = (int)dv.Number;
+ }
+
+ dv = outfit.Get("lookType");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.LookType = LookType.Outfit;
+ mon.Look.LookId = (int)dv.Number;
+ }
+ dv = outfit.Get("lookHead");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.Head = (int)dv.Number;
+ }
+ dv = outfit.Get("lookBody");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.Body = (int)dv.Number;
+ }
+ dv = outfit.Get("lookLegs");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.Legs = (int)dv.Number;
+ }
+ dv = outfit.Get("lookFeet");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.Feet = (int)dv.Number;
+ }
+ dv = outfit.Get("lookAddons");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.Addons = (int)dv.Number;
+ }
+ dv = outfit.Get("lookMount");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Look.Mount = (int)dv.Number;
+ }
+ }
+
+ dv = t.Get("maxSummons");
+ if (dv.Type == DataType.Number)
+ {
+ mon.MaxSummons = (ushort)dv.Number;
+ }
+ dv = t.Get("summons");
+ if (dv.Type == DataType.Table)
+ {
+ Table summons = dv.Table;
+ for (int i = 1; i <= summons.Length; i++)
+ {
+ dv = summons.Get(i);
+ if (dv.Type == DataType.Table)
+ {
+ mon.Summons.Add(GetSummon(dv.Table));
+ }
+ }
+ }
+
+ mon.Race = TfsRevScriptSysRaceToGenericBlood(t.Get("race"));
+
+ dv = t.Get("voices");
+ if (dv.Type == DataType.Table)
+ {
+ Table voices = dv.Table;
+
+ dv = voices.Get("interval");
+ if (dv.Type == DataType.Number)
+ {
+ mon.VoiceInterval = (int)dv.Number;
+ }
+
+ dv = voices.Get("chance");
+ if (dv.Type == DataType.Number)
+ {
+ mon.VoiceChance = (int)dv.Number;
+ }
+
+ for (int i = 1; i <= voices.Length; i++)
+ {
+ dv = voices.Get(i);
+ if (dv.Type == DataType.Table)
+ {
+ mon.Voices.Add(GetVoice(dv.Table));
+ }
+ }
+ }
+
+ // Valid Elements are from enum CombatType_t
+ dv = t.Get("elements");
+ if (dv.Type == DataType.Table)
+ {
+ Table elements = dv.Table;
+
+ for (int i = 1; i <= elements.Length; i++)
+ {
+ dv = elements.Get(i);
+ if (dv.Type == DataType.Table)
+ {
+ Table element = dv.Table;
+ double percent = 0;
+
+ dv = element.Get("percent");
+ if (dv.Type == DataType.Number)
+ {
+ percent = dv.Number;
+ }
+
+ dv = element.Get("type");
+ if (dv.Type == DataType.Number)
+ {
+ CombatDamage damageType = (CombatDamage)dv.Number;
+ if (damageType == CombatDamage.Physical)
+ {
+ mon.PhysicalDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Energy)
+ {
+ mon.EnergyDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Earth)
+ {
+ mon.EarthDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Fire)
+ {
+ mon.FireDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.LifeDrain)
+ {
+ mon.LifeDrainDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.ManaDrain)
+ {
+ mon.ManaDrainDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Healing)
+ {
+ mon.HealingMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Drown)
+ {
+ mon.DrownDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Ice)
+ {
+ mon.IceDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Holy)
+ {
+ mon.HolyDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ else if (damageType == CombatDamage.Death)
+ {
+ mon.DeathDmgMod = TfsRevScriptSysToGenericElementalPercent(dv.Number);
+ }
+ }
+ }
+ }
+ }
+
+ // Valid immunities
+ // For combat LuaScriptInterface::luaMonsterTypeCombatImmunities
+ // For condition LuaScriptInterface::luaMonsterTypeConditionImmunities
+ dv = t.Get("immunities");
+ if (dv.Type == DataType.Table)
+ {
+ Table immunities = dv.Table;
+
+ for (int i = 1; i <= immunities.Length; i++)
+ {
+ dv = immunities.Get(i);
+ if (dv.Type == DataType.Table)
+ {
+ Table immunity = dv.Table;
+ Boolean combatImmune = false;
+ Boolean conditionImmune = false;
+
+ dv = immunity.Get("combat");
+ if (dv.Type == DataType.Boolean)
+ {
+ combatImmune = dv.Boolean;
+ }
+
+ dv = immunity.Get("condition");
+ if (dv.Type == DataType.Boolean)
+ {
+ conditionImmune = dv.Boolean;
+ }
+
+ dv = immunity.Get("type");
+ if (dv.Type == DataType.String)
+ {
+ if (dv.String == "physical")
+ {
+ if (combatImmune)
+ {
+ mon.PhysicalDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "energy")
+ {
+ if (combatImmune)
+ {
+ mon.EnergyDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "fire")
+ {
+ if (combatImmune)
+ {
+ mon.FireDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "poison" || dv.String == "earth")
+ {
+ if (combatImmune)
+ {
+ mon.EarthDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "drown")
+ {
+ if (combatImmune)
+ {
+ mon.DrownDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "ice")
+ {
+ if (combatImmune)
+ {
+ mon.IceDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "holy")
+ {
+ if (combatImmune)
+ {
+ mon.HolyDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "death")
+ {
+ if (combatImmune)
+ {
+ mon.DeathDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "lifedrain")
+ {
+ if (combatImmune)
+ {
+ mon.LifeDrainDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "manadrain")
+ {
+ if (combatImmune)
+ {
+ mon.ManaDrainDmgMod = 0;
+ }
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ else if (dv.String == "outfit")
+ {
+ mon.IgnoreOutfit = conditionImmune;
+ }
+ else if (dv.String == "drunk")
+ {
+ mon.IgnoreDrunk = conditionImmune;
+ }
+ else if (dv.String == "invisible" || dv.String == "invisibility")
+ {
+ mon.IgnoreInvisible = conditionImmune;
+ }
+ else if (dv.String == "bleed")
+ {
+ if (conditionImmune)
+ {
+ result.AppendMessage($"Condition immunity {dv.String} not supported");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ dv = t.Get("loot");
+ if (dv.Type == DataType.Table)
+ {
+ Table loot = dv.Table;
+
+ for (int i = 1; i <= loot.Length; i++)
+ {
+ dv = loot.Get(i);
+ if (dv.Type == DataType.Table)
+ {
+ Table lootItemTable = dv.Table;
+ LootItem lootItem = ParseLootItem(lootItemTable);
+
+ dv = lootItemTable.Get("child");
+ if (dv.Type == DataType.Table)
+ {
+ Table child = dv.Table;
+
+ for (int j = 1; j <= child.Length; j++)
+ {
+ dv = child.Get(j);
+ lootItem.NestedLoot.Add(ParseLootItem(dv.Table));
+ }
+ }
+
+ mon.Items.Add(lootItem);
+ }
+ }
+ }
+
+ dv = t.Get("attacks");
+ if (dv.Type == DataType.Table)
+ {
+ Table attacks = dv.Table;
+
+ for (int i = 1; i <= attacks.Length; i++)
+ {
+ dv = attacks.Get(i);
+ if (dv.Type == DataType.Table)
+ {
+ Table attack = dv.Table;
+ mon.Attacks.Add(ParseSpell(attack, SpellCategory.Offensive, result));
+ }
+ }
+ }
+
+ dv = t.Get("defenses");
+ if (dv.Type == DataType.Table)
+ {
+ Table defenses = dv.Table;
+
+ dv = defenses.Get("defense");
+ if (dv.Type == DataType.Number)
+ {
+ mon.Shielding = (int)dv.Number;
+ }
+
+ dv = defenses.Get("armor");
+ if (dv.Type == DataType.Number)
+ {
+ mon.TotalArmor = (int)dv.Number;
+ }
+
+ for (int i = 1; i <= defenses.Length; i++)
+ {
+ dv = defenses.Get(i);
+ if (dv.Type == DataType.Table)
+ {
+ Table defense = dv.Table;
+ mon.Attacks.Add(ParseSpell(defense, SpellCategory.Defensive, result));
+ }
+ }
+ }
+
+ dv = t.Get("events");
+ if (dv.Type == DataType.Table)
+ {
+ result.AppendMessage("Events script can't be converted");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ if (onAppear != null)
+ {
+ result.AppendMessage("OnAppear script can't be converted");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ if (onDisappear != null)
+ {
+ result.AppendMessage("onDisappear script can't be converted");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ if (onMove != null)
+ {
+ result.AppendMessage("onMove script can't be converted");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ if (onThink != null)
+ {
+ result.AppendMessage("onThink script can't be converted");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ if (onSay != null)
+ {
+ result.AppendMessage("onSay script can't be converted");
+ result.IncreaseError(ConvertError.Warning);
+ }
+
+ MockTfsGame.ConvertedMonsters.Enqueue(new Tuple(mon, result));
+
+ return;
+ }
+
+ private Spell ParseSpell(Table t, SpellCategory category, ConvertResultEventArgs result)
+ {
+ DynValue dv;
+ Spell spell = new Spell();
+ spell.SpellCategory = category;
+
+ dv = t.Get("chance");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Chance = dv.Number / 100.0;
+ }
+
+ dv = t.Get("interval");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Interval = (int)dv.Number;
+ }
+
+ dv = t.Get("minDamage");
+ if (dv.Type == DataType.Number)
+ {
+ spell.MinDamage = (int)dv.Number;
+ }
+
+ dv = t.Get("maxDamage");
+ if (dv.Type == DataType.Number)
+ {
+ spell.MaxDamage = (int)dv.Number;
+ }
+
+ dv = t.Get("name");
+ if (dv.Type == DataType.String)
+ {
+ spell.DefinitionStyle = SpellDefinition.Raw;
+ spell.Name = dv.String;
+
+ if (spell.Name == "melee")
+ {
+ dv = t.Get("attack");
+ if (dv.Type == DataType.Number)
+ {
+ spell.AttackValue = (int)dv.Number;
+ }
+
+ dv = t.Get("skill");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Skill = (int)dv.Number;
+ }
+
+ dv = t.Get("effect");
+ if (dv.Type == DataType.Number)
+ {
+ spell.AreaEffect = (Effect)dv.Number;
+ }
+ }
+ else
+ {
+ dv = t.Get("type");
+ if (dv.Type == DataType.Number)
+ {
+ spell.DamageElement = (CombatDamage)dv.Number;
+ }
+
+ dv = t.Get("range");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Range = (int)dv.Number;
+ }
+
+ dv = t.Get("duration");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Duration = (int)dv.Number;
+ }
+
+ dv = t.Get("speed");
+ if (dv.Type == DataType.Table)
+ {
+ Table speed = dv.Table;
+
+ dv = speed.Get("max");
+ if (dv.Type == DataType.Number)
+ {
+ spell.MaxSpeedChange = (int)dv.Number;
+ }
+ dv = speed.Get("min");
+ if (dv.Type == DataType.Number)
+ {
+ spell.MinSpeedChange = (int)dv.Number;
+ }
+ }
+ else if (dv.Type == DataType.Number)
+ {
+ spell.MinSpeedChange = (int)dv.Number;
+ spell.MaxSpeedChange = (int)dv.Number;
+ }
+
+ dv = t.Get("target");
+ if (dv.Type == DataType.Boolean)
+ {
+ spell.OnTarget = dv.Boolean;
+ }
+
+ dv = t.Get("length");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Length = (int)dv.Number;
+ }
+
+ dv = t.Get("spread");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Spread = (int)dv.Number;
+ }
+
+ dv = t.Get("radius");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Radius = (int)dv.Number;
+ }
+
+ dv = t.Get("ring");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Ring = (int)dv.Number;
+ }
+
+ dv = t.Get("effect");
+ if (dv.Type == DataType.Number)
+ {
+ spell.AreaEffect = (Effect)dv.Number;
+ }
+
+ dv = t.Get("shootEffect");
+ if (dv.Type == DataType.Number)
+ {
+ spell.ShootEffect = (Missile)dv.Number;
+ }
+
+ dv = t.Get("outfit");
+ if (dv.Type == DataType.Table)
+ {
+ result.AppendMessage("Can't convert full outfits");
+ result.IncreaseError(ConvertError.Warning);
+ }
+ else if (dv.Type == DataType.Number)
+ {
+ spell.ItemId = (ushort)dv.Number;
+ }
+ else if (dv.Type == DataType.String)
+ {
+ spell.MonsterName = dv.String;
+ }
+
+ dv = t.Get("drunk");
+ if (dv.Type != DataType.Nil)
+ {
+ spell.Condition = ConditionType.Drunk;
+ }
+
+ dv = t.Get("drunkenness");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Drunkenness = dv.Number / 100.0;
+ }
+ }
+ }
+
+ dv = t.Get("condition");
+ if (dv.Type == DataType.Table)
+ {
+ Table condition = dv.Table;
+
+ dv = condition.Get("type");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Condition = (ConditionType)dv.Number;
+ }
+
+ dv = condition.Get("startDamage");
+ if (dv.Type == DataType.Number)
+ {
+ spell.StartDamage = (int)dv.Number;
+ }
+
+ dv = condition.Get("minDamage");
+ if (dv.Type == DataType.Number)
+ {
+ spell.MinDamage = (int)dv.Number;
+ }
+
+ dv = condition.Get("maxDamage");
+ if (dv.Type == DataType.Number)
+ {
+ spell.MaxDamage = (int)dv.Number;
+ }
+
+ dv = condition.Get("duration");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Duration = (int)dv.Number;
+ }
+
+ dv = condition.Get("interval");
+ if (dv.Type == DataType.Number)
+ {
+ spell.Interval = (int)dv.Number;
+ }
+ }
+
+ dv = t.Get("script");
+ if (dv.Type == DataType.String)
+ {
+ spell.DefinitionStyle = SpellDefinition.TfsLuaScript;
+ spell.Name = dv.String;
+
+ dv = t.Get("target");
+ if (dv.Type == DataType.Boolean)
+ {
+ spell.OnTarget = dv.Boolean;
+ }
+
+ dv = t.Get("direction");
+ if (dv.Type == DataType.Boolean)
+ {
+ spell.IsDirectional = dv.Boolean;
+ }
+ }
+
+ return spell;
+ }
+
+ private LootItem ParseLootItem(Table t)
+ {
+ DynValue dv;
+ LootItem lootItem = new LootItem();
+
+ dv = t.Get("id");
+ if (dv.Type == DataType.Number)
+ {
+ lootItem.Id = (ushort)dv.Number;
+ }
+ else if (dv.Type == DataType.String)
+ {
+ lootItem.Name = dv.String;
+ }
+
+ dv = t.Get("chance");
+ if (dv.Type == DataType.Number)
+ {
+ lootItem.Chance = (decimal)dv.Number / MAX_LOOTCHANCE;
+ }
+
+ dv = t.Get("maxCount");
+ if (dv.Type == DataType.Number)
+ {
+ lootItem.Count = (int)dv.Number;
+ }
+
+ dv = t.Get("aid");
+ if (dv.Type == DataType.Number)
+ {
+ lootItem.ActionId = (int)dv.Number;
+ }
+
+ dv = t.Get("actionId");
+ if (dv.Type == DataType.Number)
+ {
+ lootItem.ActionId = (int)dv.Number;
+ }
+
+ dv = t.Get("subType");
+ if (dv.Type == DataType.Number)
+ {
+ lootItem.SubType = (int)dv.Number;
+ }
+
+ dv = t.Get("charges");
+ if (dv.Type == DataType.Number)
+ {
+ lootItem.SubType = (int)dv.Number;
+ }
+
+ dv = t.Get("text");
+ if (dv.Type == DataType.String)
+ {
+ lootItem.Text = dv.String;
+ }
+
+ dv = t.Get("description");
+ if (dv.Type == DataType.String)
+ {
+ lootItem.Text = dv.String;
+ }
+
+ return lootItem;
+ }
+
+ private Voice GetVoice(Table t)
+ {
+ DynValue dv;
+ Voice voice = null;
+
+ dv = t.Get("text");
+ if (dv.Type == DataType.String)
+ {
+ voice = new Voice(dv.String);
+ }
+ dv = t.Get("yell");
+ if (dv.Type == DataType.String)
+ {
+ voice.SoundLevel = dv.Boolean ? SoundLevel.Yell : SoundLevel.Say;
+ }
+
+ return voice;
+ }
+
+ private Summon GetSummon(Table t)
+ {
+ DynValue dv;
+ Summon summon = new Summon();
+
+ dv = t.Get("name");
+ if (dv.Type == DataType.String)
+ {
+ summon.Name = dv.String;
+ }
+ dv = t.Get("interval");
+ if (dv.Type == DataType.Number)
+ {
+ summon.Interval = (int)dv.Number;
+ }
+ dv = t.Get("chance");
+ if (dv.Type == DataType.Number)
+ {
+ summon.Chance = dv.Number;
+ }
+ dv = t.Get("max");
+ if (dv.Type == DataType.Number)
+ {
+ summon.Max = (int)dv.Number;
+ }
+
+ return summon;
+ }
+
+ private double TfsRevScriptSysToGenericElementalPercent(double percent)
+ {
+ return (1 - (percent / 100.0));
+ }
+
+ private Blood TfsRevScriptSysRaceToGenericBlood(DynValue blood)
+ {
+ Blood race = Blood.blood; //default
+
+ if (blood.Type == DataType.String)
+ {
+ switch (blood.String)
+ {
+ case "venom":
+ race = Blood.venom;
+ break;
+
+ case "blood":
+ race = Blood.blood;
+ break;
+
+ case "undead":
+ race = Blood.undead;
+ break;
+
+ case "fire":
+ race = Blood.fire;
+ break;
+
+ case "energy":
+ race = Blood.venom;
+ break;
+ }
+ }
+ else if (blood.Type == DataType.Number)
+ {
+ switch (blood.Number)
+ {
+ case 1:
+ race = Blood.venom;
+ break;
+
+ case 2:
+ race = Blood.blood;
+ break;
+
+ case 3:
+ race = Blood.undead;
+ break;
+
+ case 4:
+ race = Blood.fire;
+ break;
+
+ case 5:
+ race = Blood.venom;
+ break;
+ }
+ }
+
+ return race;
+ }
+
+ }
+}
diff --git a/app/MonsterConverterTfsRevScriptSys/MonsterConverterTfsRevScriptSys.csproj b/app/MonsterConverterTfsRevScriptSys/MonsterConverterTfsRevScriptSys.csproj
index cc57348..92870cb 100644
--- a/app/MonsterConverterTfsRevScriptSys/MonsterConverterTfsRevScriptSys.csproj
+++ b/app/MonsterConverterTfsRevScriptSys/MonsterConverterTfsRevScriptSys.csproj
@@ -16,6 +16,7 @@
+
diff --git a/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs b/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs
index 9834312..0958a34 100644
--- a/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs
+++ b/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs
@@ -1,16 +1,21 @@
using MonsterConverterInterface;
using MonsterConverterInterface.MonsterTypes;
+using MoonSharp.Interpreter;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.IO;
+using MoonSharp.Interpreter.Loaders;
+using System.Diagnostics;
namespace MonsterConverterTfsRevScriptSys
{
[Export(typeof(IMonsterConverter))]
public class TfsRevScriptSysConverter : MonsterConverter
{
+ MoonSharp.Interpreter.Script script = null;
+
const uint MAX_LOOTCHANCE = 100000;
IDictionary ConditionToTfsConstant = new Dictionary
@@ -19,7 +24,7 @@ public class TfsRevScriptSysConverter : MonsterConverter
{ConditionType.Fire, "CONDITION_FIRE"},
{ConditionType.Energy, "CONDITION_ENERGY"},
{ConditionType.Bleeding, "CONDITION_BLEEDING"},
- {ConditionType.Paralyze, "CONDITION_POISON"},
+ {ConditionType.Paralyze, "CONDITION_PARALYZE"},
{ConditionType.Drown, "CONDITION_DROWN"},
{ConditionType.Freezing, "CONDITION_FREEZING"},
{ConditionType.Dazzled, "CONDITION_DAZZLED"},
@@ -41,6 +46,7 @@ public class TfsRevScriptSysConverter : MonsterConverter
IDictionary CombatDamageNames = new Dictionary
{
+ {CombatDamage.None, "COMBAT_NONE"},
{CombatDamage.Physical, "COMBAT_PHYSICALDAMAGE"},
{CombatDamage.Energy, "COMBAT_ENERGYDAMAGE"},
{CombatDamage.Earth, "COMBAT_EARTHDAMAGE"},
@@ -274,10 +280,43 @@ public class TfsRevScriptSysConverter : MonsterConverter
public override ItemIdType ItemIdType { get => ItemIdType.Server; }
- public override bool IsReadSupported { get => false; }
+ public override bool IsReadSupported { get => true; }
public override bool IsWriteSupported { get => true; }
+ public override string[] GetFilesForConversion(string directory)
+ {
+ // Init lua environment
+ if (script == null)
+ {
+ UserData.RegisterType();
+ UserData.RegisterType();
+ script = new MoonSharp.Interpreter.Script();
+ script.Options.DebugPrint = s => { Debug.WriteLine(s); };
+
+ script.Globals["Game"] = typeof(MockTfsGame);
+
+ foreach (var kv in CombatDamageNames)
+ {
+ script.Globals[kv.Value] = kv.Key;
+ }
+ foreach (var kv in magicEffectNames)
+ {
+ script.Globals[kv.Value] = kv.Key;
+ }
+ foreach (var kv in shootTypeNames)
+ {
+ script.Globals[kv.Value] = kv.Key;
+ }
+ foreach (var kv in ConditionToTfsConstant)
+ {
+ script.Globals[kv.Value] = kv.Key;
+ }
+ }
+
+ return base.GetFilesForConversion(directory);
+ }
+
public override ConvertResultEventArgs WriteMonster(string directory, ref Monster monster)
{
string fileName = Path.Combine(directory, monster.FileName + "." + FileExt);
@@ -641,7 +680,30 @@ private static string LootItemToRevScriptSysFormat(LootItem loot, int tabDepth)
public override ConvertResultEventArgs ReadMonster(string filename, out Monster monster)
{
- throw new NotImplementedException();
+ MockTfsGame.ConvertedMonsters.Clear();
+
+ script.DoFile(filename);
+
+ if (MockTfsGame.ConvertedMonsters.TryDequeue(out var result))
+ {
+ if (MockTfsGame.ConvertedMonsters.Count >= 1)
+ {
+ monster = null;
+ return new ConvertResultEventArgs(filename, ConvertError.Error, "Unable to convert multiple monsters from the same file");
+ }
+ else
+ {
+ result.Item1.FileName = Path.GetFileNameWithoutExtension(filename);
+ monster = result.Item1;
+ result.Item2.File = filename;
+ return result.Item2;
+ }
+ }
+ else
+ {
+ monster = null;
+ return new ConvertResultEventArgs(filename, ConvertError.Error, "No monster data found within file");
+ }
}
double GenericToTfsRevScriptSysElemementPercent(double percent)