#Calling C-sharp functions in a LUA cutscene (making Madeline dash)

136 messages · Page 1 of 1 (latest)

toxic falcon
#

Can I make Madeline dash in a LUA cutscene? How do I call the C# functions in LUA? Additionally can I slow down time in the LUA cutscene like the variant option? Thanks for any help

cold apex
# toxic falcon Can I make Madeline dash in a LUA cutscene? How do I call the C# functions in LU...

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:

toxic falcon
tulip tinsel
#
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

toxic falcon
tulip tinsel
#

no guarantee it'll work but it works in my head so widegladeline2

toxic falcon
tulip tinsel
#

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

toxic falcon
tulip tinsel
#

hopefully disabling gravity will do that then

toxic falcon
#

👍

toxic falcon
tulip tinsel
#

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

toxic falcon
tulip tinsel
#

what's not working about it?

cold apex
toxic falcon
#

it still begins the cutscene though, it just does nothing

toxic falcon
tulip tinsel
#

im genius

#

thank you for catching that laugheline not used to that syntax still

cold apex
#

i looked at it for a while and thought "ah yes, code" and then only now noticed haha

toxic falcon
#

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?

tulip tinsel
#

are you editing it in a zip?

toxic falcon
#

no

#

UHH okay it is now... clueless I didn't change anything

tulip tinsel
#

right lmao

#

was trying to figure out what would be wrong

toxic falcon
#

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

cold apex
# toxic falcon

monocle.minput should be #monocle.minput as it's a c# import

toxic falcon
#

ohhhhhhhhhhhhhhhhhhh

#

yeah i wouldn't have picked up on that thanks

tulip tinsel
#

god I missed that too, I'm on fire

cold apex
#

also player.StartDash()player:StartDash() i think

tulip tinsel
#

I thought : was for methods that returned void, or is it for static methods

#

player.StartDash() returns int at least

cold apex
#

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
tulip tinsel
#

okay in that case celeste.Input:Rumble() should be celeste.Input.Rumble()

cold apex
#

good catch

tulip tinsel
#

if i'm understanding right then yeah

cold apex
#

yea
pretty sure IL instance methods are actually basically static ones with self as the first argument, and lua treats them that way

tulip tinsel
#

TIL

cold apex
#

might be good to have a wiki page on this stuff

toxic falcon
#

I understand about 40% of the terminology being used cluelesseline

tulip tinsel
#

six months ago I would've understood 0% widegladeline2

toxic falcon
#

Thats really impressive! I plan to practice alot in the near future as well

tulip tinsel
toxic falcon
#

also i still cant get it to work lol derpeline, and also i have to hop off my computer now so i cant really test it, my bad

toxic falcon
#

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```
cold apex
# toxic falcon I understand about 40% of the terminology being used <:cluelesseline:90218019710...

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

tulip tinsel
#

@toxic falcon solved it, it was literally a case sensitivity issue catfacepalm 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

toxic falcon
tulip tinsel
#

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

toxic falcon
#

it might be missing something obvious though, my bad lol

#

oh wait

#

is the cutscene not ended?

tulip tinsel
#

can you send the file?

#

the .lua file

toxic falcon
#

its literally identically to what you sent in here lol

tulip tinsel
#

it is yeah, do you have multiple cutscene triggers over that area at all?

tulip tinsel
#

makes sense laugheline

toxic falcon
#

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 laugheline (it still wasn't working and i got confused for a sec)

tulip tinsel
#

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

toxic falcon
#

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 adorabline

#

I should be all good from here, thank you!

tulip tinsel
#

no problem, thanks microlith as well supergladeline was good for me to learn lua syntax too so I got something out of it

toxic falcon
#

I definitely will thank them, just not sure if I should ping them rn

toxic falcon
#

engine.TimeRate -= engine.RawDeltaTime * 2f

#

could i not just use that exactly?

tulip tinsel
#

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?

toxic falcon
tulip tinsel
#

yeah, I mean when do you want time to hit 0

#

time rate*

toxic falcon
#

yeah just the peak of the dash

#

or maybe just a couple frames before it, roughly that area

tulip tinsel
#

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

toxic falcon
tulip tinsel
#

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

toxic falcon
#

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

tulip tinsel
#

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

toxic falcon
#

oh

toxic falcon
#

got an error log lol (crash)

tulip tinsel
#

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

toxic falcon
tulip tinsel
#

correct, basically with the first if timerate hits 0 before madeline stops moving upwards then you get stuck there forever

toxic falcon
#

okay makes sense

tulip tinsel
#

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?

toxic falcon
#

And actually surprisingly accurate to what I wanted!

tulip tinsel
#

sweet!

toxic falcon
#

(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

tulip tinsel
#

that genuinely made me smile that was so wholesome

#

looks really pretty too, love the transitions

toxic falcon
tulip tinsel
#

since you’ve got two dashes, it probably makes more sense to set player.Dashes to 1 instead of 0