#Calling C-sharp functions in a LUA cutscene (making Madeline dash)
136 messages · Page 1 of 1 (latest)
i'm assuming you want to do something like what prologue does in its ending cutscene. this cutscene is the CS00_Ending class, which has a Cutscene method like:
// while time is still going:
while (Engine.TimeRate > 0f) {
// wait one frame
yield return null;
// do stuff with the bridge
if (Engine.TimeRate < 0.5f && bridge != null) {
bridge.StopCollapseLoop();
}
// stop screenshake & rumble
level.StopShake();
MInput.GamePads[Input.Gamepad].StopRumble();
// slow down time
Engine.TimeRate -= Engine.RawDeltaTime * 2f;
}
// make sure time is stopped completely, and make the player not be in control
Engine.TimeRate = 0f;
player.StateMachine.State = Player.StDummy;
// do bird stuff...
// wait until the player tries to dash in the up-right direction
while (true) {
Vector2 aimVector = Input.GetAimVector();
if (aimVector.X > 0f && aimVector.Y < 0f && Input.Dash.Pressed) {
break;
}
yield return null;
}
// make the player be in the bird dash tutorial state, and resume time
player.StateMachine.State = Player.StBirdDashTutorial;
player.Dashes = 0;
level.Session.Inventory.Dashes = 1;
Engine.TimeRate = 1f;
// more bird stuff etc.
unfortunately the StBirdDashTutorial state is specific to dashing up-right, but you could leave it out and use StDummy instead, and handle forcing the dash yourself by other means (look at Player.BirdDashTutorialBegin and Player.BirdDashTutorialCoroutine for how to do this)
effectively you just want to convert this c# code to lua; that's not too hard but there's a few things to keep in mind:
CelesteandEnginebecomecelesteandengine, becausehelpersexports them (https://github.com/Cruor/LuaCutscenes/blob/master/LuaCutscenes/Assets/LuaCutscenes/helper_functions.lua#L22)Player.St<something>becomesceleste.Player.St<something>- when working with an instance, like
playeras opposed toceleste.Player, sometimes you access things withplayer:thingrather thanplayer.thing yield return nullbecomeswait()
thank you for all the converted stuff! i would have no idea otherwise
local vector2 = require("#microsoft.xna.framework.vector2")
local minput = require("#monocle.minput")
local wasDashAssistOn = false
function onEnter()
getLevel():RegisterAreaComplete() -- You want this to stop the timer
wasDashAssistOn = celeste.SaveData.Instance.Assists.DashAssist
end
function onBegin()
player.ForceCameraUpdate = true
walkTo(2838)
walkTo(2808)
wait(0.4)
walkTo(2778)
walkTo(2808)
playSprite("duck")
wait(0.5)
jump()
wait(0.3)
dashUp()
end
function onEnd(level, wasSkipped)
celeste.SaveData.Instance.Assists.DashAssist = wasDashAssistOn
if wasSkipped then
completeArea(false, wasSkipped)
end
end
function playSprite(sprite, duration)
player.DummyAutoAnimate = false
player.Sprite:Play(sprite, false, false)
if (duration) then
wait(duration)
player.DummyAutoAnimate = true
end
end
function dashUp()
celeste.SaveData.Instance.Assists.DashAssist = false
celeste.Input.Rumble(celeste.RumbleStrength.Strong, celeste.RumbleLength.Medium)
minput.Disabled = true
player.OverrideDashDirection = vector2(0, -1)
player.StateMachine.Locked = false
player.StateMachine.State = player:StartDash()
player.Dashes = 0
wait(0.1)
while (player.Speed.Y < 0) do -- Waits till maddy stops moving up
player.Dashes = 0
celeste.Input.MoveY.Value = -1
celeste.Input.MoveX.Value = 0
wait()
end
player.OverrideDashDirection = nil
player.StateMachine.State = 11
player.StateMachine.Locked = true
minput.Disabled = false
end
from #map_making - this is how I'd translate the dashing up, for the slowing down time you'd probably rework the while (player...) loop to instead slow down like prologue but you could also maybe just put a shroom helper time modulation trigger over where maddy will be dashing if you didn't want to worry about it
also not sure why you're checking if wasskipped to complete area, if this is the final cutscene then you'd always want to complete area I assume
Yeah i might use shroom helper to save some effort, also THANK YOU SO MUCH, I really appreciate this help, you are a legend!
no guarantee it'll work but it works in my head so 
Yeah you are right
better than what my head would come up with 
also if you wanted her to hang there in midair then you might need to do player.DummyGravity = false at the end of dashUp(), I'm just not sure how the cutscene is playing out in your head
yeah that would be helpful
I think you have it pretty accurate, cause i do want her to hang there mid air while the spotlight happens
hopefully disabling gravity will do that then
👍
what is minput instead of just input? is it for just movement?
it's probably shorthand for monocle input, it's a separate thing to celeste's input class that's direct from the monocle engine the game uses
I've tested and looked over this code for a while now and i can not figure out why it doesn't work, mind you I'm probably underqualified to understand much of the code but still, I can't figure out the issue
what's not working about it?
does it log anything to log.txt?
it still begins the cutscene though, it just does nothing
i will check now
onEnter doesn't have an end
i looked at it for a while and thought "ah yes, code" and then only now noticed haha
oh, im actually surprised i missed that as well haha, good catch
hmmm, its still not running it
sorry for really small image
its not even begining the cutscene now?
are you editing it in a zip?
nevermind
my bad
i deleted this to test function onEnter() getLevel():RegisterAreaComplete() -- You want this to stop the timer wasDashAssistOn = celeste.SaveData.Instance.Assists.DashAssist end
and forgot to resave after putting it back in
so its that code that is stopping the cutscene from even beginning
monocle.minput should be #monocle.minput as it's a c# import
god I missed that too, I'm on fire
also player.StartDash() → player:StartDash() i think
I thought : was for methods that returned void, or is it for static methods
player.StartDash() returns int at least
pretty sure it's for instance methods, and obj:method(args) is shorthand for type.method(obj, args) or something
i don't remember the specifics
i know that helpers has
function helpers.waitUntilOnGround()
while not player:OnGround(1) do
wait()
end
end
okay in that case celeste.Input:Rumble() should be celeste.Input.Rumble()
good catch
if i'm understanding right then yeah
yea
pretty sure IL instance methods are actually basically static ones with self as the first argument, and lua treats them that way
TIL
might be good to have a wiki page on this stuff
I understand about 40% of the terminology being used 
six months ago I would've understood 0% 
Thats really impressive! I plan to practice alot in the near future as well
i've updated this block with the various fixes if you want to copy-paste again
also i still cant get it to work lol
, and also i have to hop off my computer now so i cant really test it, my bad
oh, thank you!
sorry i gtg from computer now, this is what i have rn and it wont even begin the cutscene at all (it doesn't even register it like its begun, as in you cant skip it in the pause menu)
local vector2 = require("#microsoft.xna.framework.vector2")
local minput = require("#monocle.minput")
local wasDashAssistOn = false
function onEnter()
getLevel():RegisterAreaComplete() -- You want this to stop the timer
wasDashAssistOn = celeste.SaveData.Instance.Assists.DashAssist
end
function onBegin()
player.ForceCameraUpdate = true
walkTo(2838)
walkTo(2808)
wait(0.4)
walkTo(2778)
walkTo(2808)
playSprite("duck")
wait(0.5)
jump()
wait(0.3)
dashUp()
end
function onEnd(level, wasSkipped)
celeste.SaveData.Instance.Assists.DashAssist = wasDashAssistOn
if wasSkipped then
completeArea(false, wasSkipped)
end
end
function playSprite(sprite, duration)
player.DummyAutoAnimate = false
player.Sprite:Play(sprite, false, false)
if (duration) then
wait(duration)
player.DummyAutoAnimate = true
end
end
function dashUp()
celeste.SaveData.Instance.Assists.DashAssist = false
celeste.Input.Rumble(celeste.RumbleStrength.Strong, celeste.RumbleLength.Medium)
minput.Disabled = true
player.OverrideDashDirection = vector2(0, -1)
player.StateMachine.Locked = false
player.StateMachine.State = player:StartDash()
player.Dashes = 0
wait(0.1)
while (player.Speed.Y < 0) do -- Waits till maddy stops moving up
player.Dashes = 0
celeste.Input.MoveY.Value = -1
celeste.Input.MoveX.Value = 0
wait()
end
player.OverrideDashDirection = nil
player.StateMachine.State = 11
player.StateMachine.Locked = true
minput.Disabled = false
end```
type a type of thing, like the Celeste.Player type
static method: like a function, but inside a type (eg. the Celeste.Input type has a Rumble static method)
instance: a thing (like an individual player), that has a type (like Celeste.Player)
instance method: like a static method, but specific to an instance (eg. the Player type has a StartDash instance method, which causes the specific player instance to dash); that instance is called self or this because it's the one the code is doing stuff to
argument: something you put (<here>), that "goes into" the method
void: a method is said to return void if it doesn't return anything, like Celeste.Input.Rumble
⠀
also quick thing:
function onEnd(level, wasSkipped)
celeste.SaveData.Instance.Assists.DashAssist = wasDashAssistOn
completeArea(true, wasSkipped)
end
this completes the area even if the cutscene wasn't skipped, and the true means it'll be a spotlight wipe rather than any other animation
@toxic falcon solved it, it was literally a case sensitivity issue
it wasn't liking monocle.minput, it wanted Monocle.MInput
give me a sec and i'll upload the file I was using to test
hahah, thats always the way isn't it. Thank you heaps! will test it out right now
this should be it
I added one extra line at the end with a note explaining it, basically if you leave maddy hanging there then she wobbles about like she's falling, this will make her stop (and basically t-pose lol)
first is with the player.DummyAutoAnimate = false, second is without
(spoiler cause unreleased map) I'm not sure why, but it seems like the moment it does the dash everything kind of loops? I simply just copied your code and read through it, and as far as my understanding goes i think it should work as yours demonstrated, but it does this instead
it might be missing something obvious though, my bad lol
oh wait
is the cutscene not ended?
yeah sure
its literally identically to what you sent in here lol
it is yeah, do you have multiple cutscene triggers over that area at all?
haha
yep
my bad
makes sense 
accidentally left that second trigger on the right in testing
i will remove that and see if it works
and i probably need to check the only once box check as well
(it still wasn't working and i got confused for a sec)
ah yeah I had that checked, that's probably going to re-trigger it since you're walking around - you should only need it once anyway
yeah
yep, it works all good now 👍
thank you thank you thank you THANK YOU so much for helping me with this problem, I really appreciate it a lot you are a legend. Your the best 
I should be all good from here, thank you!
no problem, thanks microlith as well
was good for me to learn lua syntax too so I got something out of it
I definitely will thank them, just not sure if I should ping them rn
oh yeah thats the one last thing i needed for the cutscene, was the time slow down, which was in the prologue cutscene microlith sent at the start
engine.TimeRate -= engine.RawDeltaTime * 2f
could i not just use that exactly?
you’d need to do engine.TimeRate = engine.TimeRate - (engine.RawDeltaTime * 2) for starters
if you wanted to implement that then I think the best place to do it would be the while loop in dashUp() - do you want time to stop as madeline reaches her peak?
I was thinking more the moment she begins the dash, begin slowing time
yeah just the peak of the dash
or maybe just a couple frames before it, roughly that area
you could try putting that line in the while loop and then engine.TimeRate = 0 before player.OverrideDash… but I’m just wary of hitting TimeRate = 0 before madeline reaches her peak, cause then you softlock lol
so the other option is to change the while condition to engine.TimeRate > 0 and then adjusting the 2 to time hitting 0 at the right point
the first might work fine though
I just can’t remember how long it’d take for Madeline to hit her peak
yeah i just got a softlock haha
yeah, you just need to adjust the 2 to get the proper rate - you could add and engine.TimeRate > 0 to the while condition but then you’re not gonna hit the peak of the dash
so where should this while loop be exactly? i thought here, but wont it just go until zero before the dash?
actually the moment that while loop run the same frame the game froze
no, you either want to place that engine.TimeRate = … line in the while player.speed… loop and adjust the 2 OR move the code in the while player loop into what you’ve got there and get rid of the while player loop
what’s happening in that image is you drop timerate down to 0, then the game gets stuck because it moves to the while player loop since player y speed is still < 0, but it can’t do anything to change that because time is no longer moving
so you’re looping infinitely
oh
when you say adjust the 2 you mean the value 2, the value being multiplied?
got an error log lol (crash)
so you either want
while (player.Speed.Y < 0) do
player.Dashes = 0
celeste.Input.MoveY.Value = -1
celeste.Input.MoveX.Value = 0
engine.TimeRate = engine.TimeRate - (engine.RawDeltaTime * 2) -- This 2 is what you want to adjust
wait()
end
engine.TimeRate = 0
player.OverrideDashDirection = nil
…
or
while (engine.TimeRate > 0) do
player.Dashes = 0
celeste.Input.MoveY.Value = -1
celeste.Input.MoveX.Value = 0
engine.TimeRate = engine.TimeRate - (engine.RawDeltaTime * 2)
wait()
end
engine.TimeRate = 0
player.OverrideDashDirection = nil
…
the second is safer so I’d go with that
thats because if you get the 2 value adjustment too big it could softlock, right?
correct, basically with the first if timerate hits 0 before madeline stops moving upwards then you get stuck there forever
okay makes sense
I’m not 100% sure how timerate works but if this also messes up then you might need to set it back to 1 or whatever it normally is in onend?
I used this one and it works!!!
And actually surprisingly accurate to what I wanted!
sweet!
(ignore lack of decoration and bad bg, will also continue to tweak the dance i just had this one as a draft), I will mess with the values a bit more to get it were I want it but this turned out really good imo! Thanks a ton
that genuinely made me smile that was so wholesome
looks really pretty too, love the transitions
Thanks! its far from final though, might post a more polished version later in #map_making
since you’ve got two dashes, it probably makes more sense to set player.Dashes to 1 instead of 0
I didn't change anything
