Tutorial:Lua Conversion Guide
From PMDOWiki
(Redirected from Lua Conversion Guide)
While writing your own custom Lua scripts, there could be several instances where you might replicate the engine source code and modify it from there. However, it can be tricky translating C# into Lua, especially with C# methods and classes involving generics.
Examples
Below are some C# to Lua conversion cases you might encounter:
Calling C# Enum
C#:
Dir8 dir = Dir8.DownLeft;
Lua:
local dir = RogueElements.Dir8.DownLeft
- Notice how RogueElements is prepended in the Lua implementation. This is because it is located under the RogueElements namespace. This is essential to keep notice when working with the engine's C# classes.
Creating C# Class
C#:
Loc origin = new Loc(10, 10); Loc origin2 = new Loc(25); Console.WriteLine(origin.X); Console.WriteLine(Loc.Dot(origin, origin2)); Console.WriteLine(origin2.ToString());
Lua:
local origin = RogueElements.Loc(10, 10) local origin2 = RogueElements.Loc(25) PrintInfo(tostring(origin.X)) PrintInfo(tostring(RogueElements.Loc.Dot(origin, origin2))) PrintInfo(origin:ToString())
- Note that you can call any of the class's constructor
- In Lua, you can access a class member variable through the "." operator
- You also use the "." operator for static methods
- To call a class method (not static) is different. You use the ":" operator in Lua instead
Calling C# Generic Method
C#:
AbilityLearnContext learn = context.ContextStates.GetWithDefault<AbilityLearnContext>();
Lua:
AbilityLearnContextType = luanet.import_type('PMDC.Dungeon.AbilityLearnContext')
local learn = context.ContextStates:GetWithDefault(luanet.ctype(AbilityLearnContextType))
- Notice that in Lua, you have to import the type directly using luanet.import_type.
- It is also important to note that PMDC.Dungeon is prepended because AbilityLearnContext is located at under the namespace here.
C#
int contextVal = context.GetContextStateInt<TargetAtkBoost>(0);
Lua
TargetAtkBoostType = luanet.import_type('PMDC.Dungeon.TargetAtkBoost')
local context_val = context:GetContextStateInt(luanet.ctype(TargetAtkBoostType), 0)
Create a Generic C# Class
C#:
List<MobSpawn> allSpawns = new List<MobSpawn>();
Lua:
ListType = luanet.import_type('System.Collections.Generic.List`1')
MobSpawnType = luanet.import_type('RogueEssence.LevelGen.MobSpawn')
local all_spawns = LUA_ENGINE:MakeGenericType( ListType, { MobSpawnType }, { })
C#:
SpecificTeamSpawner specificTeam = SpecificTeamSpawner();
MobSpawn postMob = MobSpawn();
postMob.BaseForm = MonsterID("oshawott", 0, "normal", Gender.Unknown);
postMob.Tactic = "boss";
mostMob.Level = RandRange(50);
specificTeam.Spawns.Add(postMob);
PresetMultiTeamSpawner picker = new PresetMultiTeamSpawner<MapGenContext>();
picker.Spawns.Add(specificTeam);
PlaceEntranceMobsStep mobPlacement = new PlaceEntranceMobsStep<MapGenContext, MapGenEntrance>(picker);
Lua:
PresetMultiTeamSpawnerType = luanet.import_type('RogueEssence.LevelGen.PresetMultiTeamSpawner`1')
EntranceType = luanet.import_type('RogueEssence.LevelGen.MapGenEntrance')
PlaceEntranceMobsStepType = luanet.import_type('RogueEssence.LevelGen.PlaceEntranceMobsStep`2')
MapGenContextType = luanet.import_type('RogueEssence.LevelGen.ListMapGenContext')
local specific_team = RogueEssence.LevelGen.SpecificTeamSpawner()
local post_mob = RogueEssence.LevelGen.MobSpawn()
post_mob.BaseForm = RogueEssence.Dungeon.MonsterID("oshawott", 0, "normal", Gender.Unknown)
post_mob.Tactic = "boss"
post_mob.Level = RogueElements.RandRange(50)
specific_team.Spawns:Add(post_mob)
local picker = LUA_ENGINE:MakeGenericType(PresetMultiTeamSpawnerType, { MapGenContextType }, { })
picker.Spawns:Add(specific_team)
local mob_placement = LUA_ENGINE:MakeGenericType(PlaceEntranceMobsStepType, { MapGenContextType, EntranceType }, { picker })
- Notice the `1 and `2 appended at the end of the import_type. These represent how many generics the class requires.
- The LUA_ENGINE:MakeGenericType method takes in three parameters:
- The 1st parameter is what generic type to create
- The 2nd parameter is a table of types necessary to create the generic type
- The 3rd parameter is a table of values that is called with the constructor of the generic type
Source Code Conversion Example
C#:
public override IEnumerator<YieldInstruction> Apply(GameEventOwner owner, Character ownerChar, SingleCharContext context)
{
if (context.User.CharStates.Contains<MagicGuardState>())
yield break;
//check for someone within 4 tiles away; if there's no one, then remove the status
List<Character> targets = AreaAction.GetTargetsInArea(context.User, context.User.CharLoc, Alignment.Foe, 4);
int lowestDist = Int32.MaxValue;
Character target = null;
for (int ii = 0; ii < targets.Count; ii++)
{
int newDist = (targets[ii].CharLoc - context.User.CharLoc).DistSquared();
if (newDist < lowestDist)
{
target = targets[ii];
lowestDist = newDist;
}
}
if (target == null)
yield return CoroutineManager.Instance.StartCoroutine(context.User.RemoveStatusEffect(((StatusEffect)owner).ID));
else
{
int seeddmg = Math.Max(1, context.User.MaxHP / 12);
DungeonScene.Instance.LogMsg(Text.FormatGrammar(new StringKey("MSG_LEECH_SEED").ToLocal(), context.User.GetDisplayName(false)));
GameManager.Instance.BattleSE("DUN_Hit_Neutral");
if (!context.User.Unidentifiable)
{
SingleEmitter endEmitter = new SingleEmitter(new AnimData("Hit_Neutral", 3));
endEmitter.SetupEmit(context.User.MapLoc, context.User.MapLoc, context.User.CharDir);
DungeonScene.Instance.CreateAnim(endEmitter, DrawLayer.NoDraw);
}
yield return CoroutineManager.Instance.StartCoroutine(context.User.InflictDamage(seeddmg, false));
if (context.User.CharStates.Contains<DrainDamageState>())
{
GameManager.Instance.BattleSE("DUN_Toxic");
DungeonScene.Instance.LogMsg(Text.FormatGrammar(new StringKey("MSG_LIQUID_OOZE").ToLocal(), target.GetDisplayName(false)));
yield return CoroutineManager.Instance.StartCoroutine(target.InflictDamage(seeddmg * 4, false));
}
else if (target.HP < target.MaxHP)
{
yield return CoroutineManager.Instance.StartCoroutine(target.RestoreHP(seeddmg, false));
}
}
}
Lua:
SINGLE_CHAR_SCRIPT = {}
DrainDamageStateType = luanet.import_type('PMDC.Dungeon.DrainDamageState')
MagicGuardStateType = luanet.import_type('PMDC.Dungeon.MagicGuardState')
function SINGLE_CHAR_SCRIPT.LeechSeedEvent(owner, ownerChar, context, args)
if context.User.CharStates:Contains(luanet.ctype(MagicGuardStateType)) then
return
end
local targets = RogueEssence.Dungeon.AreaAction.GetTargetsInArea(context.User, context.User.CharLoc, RogueEssence.Dungeon.Alignment.Foe, 4)
local lowestDist = 10000
local target = nil
for ii = 0, targets.Count - 1, 1 do
local newDist = (targets[ii].CharLoc - context.User.CharLoc):DistSquared()
if newDist < lowestDist then
target = targets[ii]
lowestDist = newDist
end
end
if target == nil then
TASK:WaitTask(context.User:RemoveStatusEffect(owner:GetID()))
return
else
local seeddmg = math.max(1, context.User.MaxHP / 12)
_DUNGEON:LogMsg(STRINGS:Format(RogueEssence.StringKey("MSG_LEECH_SEED"):ToLocal(), context.User:GetDisplayName(false)))
SOUND:PlayBattleSE('DUN_Hit_Neutral')
if not context.User.Unidentifiable then
local endEmitter = RogueEssence.Content.SingleEmitter(RogueEssence.Content.AnimData("Hit_Neutral", 3))
endEmitter:SetupEmit(context.User.MapLoc, context.User.MapLoc, context.User.CharDir)
_DUNGEON:CreateAnim(endEmitter, RogueEssence.Content.DrawLayer.NoDraw)
end
TASK:WaitTask(context.User:InflictDamage(seeddmg, false))
if context.User.CharStates:Contains(luanet.ctype(MagicGuardStateType)) then
SOUND:PlayBattleSE('DUN_Toxic')
_DUNGEON:LogMsg(STRINGS:Format(RogueEssence.StringKey("MSG_LIQUID_OOZE"):ToLocal(), target:GetDisplayName(false)))
TASK:WaitTask(target:InflictDamage(seeddmg * 4, false))
elseif target.HP < target.MaxHP then
TASK:WaitTask(target:RestoreHP(seeddmg, false))
end
end
end
This concludes converting Lua to C# code! Knowing how and when to call and use the source engine's classes, methods, and enums allows you write more complex battle events beyond the Dev menu!
