Jump to content

[Papyrus Code] Help with adding success chance


Recommended Posts

Posted

Beware, I m a total noob. 
I am currently in the process of learning to understand papyrus and managed to change some MCM values and poses, that is all.

But now I want to dig deeper and start modifying existing mods to learn. 

I want to add a "Success Chance" to a function that is currently only causing stamina damage and then has automatic success.

 

Mod is Naked Dungeons (link below).

1. the PC gets locked into Zaz Furniture
2. the PC can press Space to pop up a dialogue with three options. And one option is to "wiggle free"

This must be the following code lines. 
They apply 100 stamina damage and then automatically issue a "success". 

I want to change ONE thing (another if possible only). 
 

1. I want to add a success chance to the script.

So if I press the option and the stamina damage is applied, I want XX% chance of success. For example 10% (so each time I choose the option I have 10% chance of success but also lose 100 stamina even when I fail. 

If Success, go to                         Debug.MessageBox("Your bindings finally feel looser than before... ")

If no:    Debug.MessageBox("Your bindings are stronger than you thought... ")

I suspect it can maybe be changed in this part, but as I said... me no clue :D'

EDIT: In the meantime I understand as much as that this is meant to be a skill check that references DD. 

But it does not look like the skill check code I found when searching, so my original request will be the same (if possible) to make it a fixed chance not dependend on skill or a DD difficulty setting.

If that is not possible of course restoring this to work as skill check might be a possibility :(
 

Quote

                    endif
                    Success = Success || (Utility.RandomInt(1, MaxVal) <= Skill)
                    if Success
                        Debug.MessageBox("Your bindings finally feel looser than before... ")
                    else


2. if possible, I would like to see a cooldown added in reallife seconds (for example 60 seconds) before I can try again. This is a highly optional and secondary target, tho. 

Code in spoiler, source in attachment


Thx a hole damn fucking lot. 
(I can compile the script myself if there are no errors, but I cannot change code that complex yet sadly, sorry) 
 

Spoiler

Event OnKeyDown(Int KeyCode)
    if KeyCode == 57
        Int exposure

        if CanShowMenu && !UI.IsMenuOpen("Dialogue Menu") && !UI.IsMenuOpen("BarterMenu") && !UI.IsMenuOpen("ContainerMenu") && !UI.IsMenuOpen("Sleep/Wait Menu") 
            CanShowMenu = false
            Float stamina = libs.PlayerRef.GetActorValue("Stamina")
            Float damage = StaminaChange*libs.PlayerRef.GetBaseActorValue("Stamina")

            int choice = ndun_CaptMsg.Show()
            if choice == 0
                ;take off
                if Success
                    SetObjectiveCompleted(20, true)
                    SetStage(100)
                else
                    Debug.MessageBox("Your bindings are too tight to break out of them.")
                    CanShowMenu = true
                endif
            elseif choice == 1
                ;struggle
                if stamina < damage
                    Debug.MessageBox("You are too tired to move your body.")
                else
                    libs.PlayerRef.DamageActorValue("Stamina", damage)
                    Utility.Wait(2)
                    libs.SexLabMoan(libs.PlayerRef)
                    if !(sla.IsActorArousalLocked(libs.PlayerRef) || sla.IsActorArousalBlocked(libs.PlayerRef))
                        exposure = sla.GetActorExposure(libs.PlayerRef) + cfgqst.ExposureChange[0]
                        if exposure > 100
                            exposure = 100
                        endif
                        sla.SetActorExposure(libs.PlayerRef, exposure)
                    endif
                    Success = Success || (Utility.RandomInt(1, MaxVal) <= Skill)
                    if Success
                        Debug.MessageBox("Your bindings finally feel looser than before... ")
                    else
                        Skill += 10
                        Debug.MessageBox("Your bindings are stronger than you thought... ")
                    endif        
                endif
                CanShowMenu = true
            endif
        endif
    endif
EndEvent
 


Mod:

 

ndun_captivequest_qf_scr.psc

Posted

Random chances via papyrus are generally going to use Utility.RandomInt().  Within the function, you specify a list of values, so Utility.RandomInt(0, 11) will give a random int from 0 to 11, so 12 total options.

 

With that, you just separate out what you want to happen based on the result.

 

Sample for 1-in-10 chance:

int iRND = Utility.RandomInt(0, 9)
If iRND == 9 ; 1 chance in 10
    DoTheThing()
EndIf

 

Posted
29 minutes ago, Seijin8 said:

Random chances via papyrus are generally going to use Utility.RandomInt().  Within the function, you specify a list of values, so Utility.RandomInt(0, 11) will give a random int from 0 to 11, so 12 total options.

 

With that, you just separate out what you want to happen based on the result.

 

Sample for 1-in-10 chance:


int iRND = Utility.RandomInt(0, 9)
If iRND == 9 ; 1 chance in 10
    DoTheThing()
EndIf

 


I found something in the meantime and put it like this:
 

Quote

                    endif
                    Success = Success || (Utility.RandomInt(0, 100) <= 50)
                    if Success
                        Debug.MessageBox("Your bindings finally feel looser than before... ")
                    else
                        Skill += 10
                        Debug.MessageBox("Your bindings are stronger than you thought... ")
                    endif        

 

It looks different than urs, but if I understand it correctly it will generate a random number between 0 and 100 and if 50 is same or higher it will succeed, correct? 

I tried it ingame and at least I did not have alot of success, but also cannot see the "roll" so can only guess how high the chances actually are.

Posted
15 minutes ago, Nymra said:

Success = Success || (Utility.RandomInt(0, 100) <= 50)

I have no idea what that will generate.  I'm a little surprised that compiled.

 

At minimum, I'd rewrite it as:

If !Success
   int iRND = Utility.RandomInt(0, 100) ; <--- that is finding 101 numbers, btw...
   If iRND <= 50
      Success = true
   EndIf
EndIf

 

16 minutes ago, Nymra said:

but also cannot see the "roll"

Add a line like:

Debug.Notification("Success is " +Success+ ", and random is "+iRND+ ".")

 

So then you will be able to see what transpired.  Take it out once you are satisfied everything is working.

Posted

Generally you'll compare it to an Mcm variable (if you want the user to be able to configure the chance of success)

If Menu.SuccessChance >= Utility.RandomInt(0, 100)
	DoTheThing()
Else
	DoTheOtherThing()
EndIf

 

You might also want to use:

Utility.IsInMenuMode()

As key presses can occur while in console as well (which is really annoying)  and that line above should (I think) cover all menus except dialogue. 

Posted

If you are using it for percentage values, use RandomFloat with no arguments.

float roll = Utility.RandomFloat()

MiscUtil.PrintConsole("Roll: " + (roll * 100) as int)

if roll >= 0.75 ; 25% chance
    do something
elseif roll >= 0.5 ; 50% chance
    do something
elseif roll >= 0.25 ; 75% chance
    do something
elseif roll >= 0.05 ; 95% chance
    do something
else
    do something else
endif

if roll < 0.25 ; 25% chance
    do something
elseif roll < 0.5 ; 50% chance
    do something
elseif roll < 0.75 ; 75% chance
    do something
elseif roll < 0.95 ; 95% chance
    do something
else
    do something else
endif

Inline conditionals such as Success = Success || (Utility.RandomInt(0, 100) <= 50) is fine too.

Success = Success || (Utility.RandomFloat() >= 0.5)

Mind you, it will always be a success if Success is already true, the second conditional will only be evaluated if Success is currently false.

Posted
1 hour ago, Monoman1 said:

Generally you'll compare it to an Mcm variable (if you want the user to be able to configure the chance of success)


If Menu.SuccessChance >= Utility.RandomInt(0, 100)
	DoTheThing()
Else
	DoTheOtherThing()
EndIf

 

You might also want to use:

Utility.IsInMenuMode()

As key presses can occur while in console as well (which is really annoying)  and that line above should (I think) cover all menus except dialogue. 

this is true. the keys trigger while in console. 

but sorry I have to ask how  "Utility.IsInMenuMode()" is integrated. 

I have no idea where to put that in this script part. 
The Key press is at the start but there is no utlity?!

like : "Event OnKeyDown.Utility.IsInMenuMode(Int KeyCode) ?! sorry :D 

Quote

Event OnKeyDown(Int KeyCode)
    if KeyCode == 57
        Int exposure

        if CanShowMenu && !UI.IsMenuOpen("Dialogue Menu") && !UI.IsMenuOpen("BarterMenu") && !UI.IsMenuOpen("ContainerMenu") && !UI.IsMenuOpen("Sleep/Wait Menu") 
            CanShowMenu = false
            Float stamina = libs.PlayerRef.GetActorValue("Stamina")
            Float damage = StaminaChange*libs.PlayerRef.GetBaseActorValue("Stamina")

            int choice = ndun_CaptMsg.Show()
            if choice == 0
                ;take off
                if Success
                    SetObjectiveCompleted(20, true)
                    SetStage(100)
                else
                    Debug.Notification("Your bindings are too tight to break out of them.")
                    CanShowMenu = true
                endif
            elseif choice == 1
                ;struggle
                if stamina < damage
                    Debug.Notification("You are too tired to move your body.")
                else
                    libs.PlayerRef.DamageActorValue("Stamina", damage)
                    Utility.Wait(2)
                    libs.SexLabMoan(libs.PlayerRef)
                    if !(sla.IsActorArousalLocked(libs.PlayerRef) || sla.IsActorArousalBlocked(libs.PlayerRef))
                        exposure = sla.GetActorExposure(libs.PlayerRef) + cfgqst.ExposureChange[0]
                        if exposure > 100
                            exposure = 100
                        endif
                        sla.SetActorExposure(libs.PlayerRef, exposure)
                    endif
                    
                    If !Success
                       int iRND = Utility.RandomInt(0, 100) ; <--- that is finding 101 numbers, btw...
                       If iRND <= 10
                          Success = true
                          Debug.Notification("Success is " +Success+ ", and random is "+iRND+ ".")
                       EndIf
                    EndIf
                    if Success
                        Debug.Notification("Your bindings finally feel looser than before... ")
                    else
                        libs.PlayerRef.DamageActorValue("Stamina", damage)
                        Utility.Wait(10)
                        libs.SexLabMoan(libs.PlayerRef)
                        Debug.Notification("Your bindings are stronger than you thought... ")
                    endif        
                endif
                CanShowMenu = true
            endif
        endif
    endif
EndEvent

 

 

13 minutes ago, Hawk9969 said:

If you are using it for percentage values, use RandomFloat with no arguments.


float roll = Utility.RandomFloat()

MiscUtil.PrintConsole("Roll: " + (roll * 100) as int)

if roll >= 0.75 ; 25% chance
    do something
elseif roll >= 0.5 ; 50% chance
    do something
elseif roll >= 0.25 ; 75% chance
    do something
elseif roll >= 0.05 ; 95% chance
    do something
else
    do something else
endif

if roll < 0.95 ; 95% chance
    do something
elseif roll < 0.75 ; 75% chance
    do something
elseif roll < 0.5 ; 50% chance
    do something
elseif roll < 0.25 ; 25% chance
    do something
else
    do something else
endif

Inline conditionals such as Success = Success || (Utility.RandomInt(0, 100) <= 50) is fine too.

are there any advantages/disadvantegs in the different methods? 
Like is your method better for consecutive rolls? 
At least it looks on point and needs only one line. 

It seems all three worked by my rough game tests. 
the printconsole seems to be elegant :)
 

13 minutes ago, Hawk9969 said:

Success = Success || (Utility.RandomFloat() >= 0.5)

Mind you, it will always be a success if Success is already true, the second conditional will only be evaluated if Success is currently false.

yeah I know and it is all that is required for the task at hand at least. 
much appreciated. 

 

Posted
8 minutes ago, Nymra said:

are there any advantages/disadvantegs in the different methods? 
Like is your method better for consecutive rolls? 
At least it looks on point and needs only one line. 

Most languages these days will have a function that generates a random floating point ranging from 0.0 to 1.0. The main usage for those functions are as the backbone for other PRNG algorithms and as a random percentage function.

Since most PRNG algorithms will call that anyway, it will be faster than the other random range functions.

There is also an advantage if you need a percentage value with digits after the decimal point. With RandomInt, you would need to always have a fixed set of digits after the decimal point while at the same time risking going over the scope of a signed 32-bits int.

 

This is Python's crypto-safe PRNG implementation:

    def random(self):
        """Get the next random number in the range [0.0, 1.0)."""
        return (long(_hexlify(_urandom(7)), 16) >> 3) * RECIP_BPF

Every other range function calls random() to get a floating point from 0.0 to 1.0 to use as base for the  algorithm.

Posted
50 minutes ago, Nymra said:

like : "Event OnKeyDown.Utility.IsInMenuMode(Int KeyCode) ?! sorry

I mean instead of:

Event OnKeyDown(Int KeyCode)
    if KeyCode == 57
        Int exposure
        if CanShowMenu && !UI.IsMenuOpen("Dialogue Menu") && !UI.IsMenuOpen("BarterMenu") && !UI.IsMenuOpen("ContainerMenu") && !UI.IsMenuOpen("Sleep/Wait Menu") 

Do:

Event OnKeyDown(Int KeyCode)
    if KeyCode == 57
        Int exposure
        if CanShowMenu && !UI.IsMenuOpen("Dialogue Menu") && !Utility.IsInMenuMode() 

Now I will say it might be a good thing to test this with the mod 'souls menu' - that mod that unpauses all the menus because apparently IsInMenuMode() returns true if you're in a menu and the game is paused. However, I'm nearly sure I've used it before ok.

 

Also, just a personal preference but I would create my own PlayerRef property instead of constantly accessing it through devious devices like: lib.PlayerRef.Blah

I don't think it makes a huge difference but you are accessing an external script to get something that is freely available. Just create your own property for the player

 

Actor Property PlayerRef Auto

 

fill in the property and change all the:

libs.PlayerRef

to just:

PlayerRef.

Posted
38 minutes ago, Monoman1 said:

I mean instead of:


Event OnKeyDown(Int KeyCode)
    if KeyCode == 57
        Int exposure
        if CanShowMenu && !UI.IsMenuOpen("Dialogue Menu") && !UI.IsMenuOpen("BarterMenu") && !UI.IsMenuOpen("ContainerMenu") && !UI.IsMenuOpen("Sleep/Wait Menu") 

Do:


Event OnKeyDown(Int KeyCode)
    if KeyCode == 57
        Int exposure
        if CanShowMenu && !UI.IsMenuOpen("Dialogue Menu") && !Utility.IsInMenuMode() 

Now I will say it might be a good thing to test this with the mod 'souls menu' - that mod that unpauses all the menus because apparently IsInMenuMode() returns true if you're in a menu and the game is paused. However, I'm nearly sure I've used it before ok.

I m just now booting to see if I failed or not :D

 

 

38 minutes ago, Monoman1 said:

Also, just a personal preference but I would create my own PlayerRef property instead of constantly accessing it through devious devices like: lib.PlayerRef.Blah

I don't think it makes a huge difference but you are accessing an external script to get something that is freely available. Just create your own property for the player

 

Actor Property PlayerRef Auto

 

fill in the property and change all the:

libs.PlayerRef

to just:

PlayerRef.

 

hmm, I dont know why this is in the code. I m just editing existing code its not written by me. 
I dont know if it is causing trouble if I change the player lib because there are 100 other scripts in the mod 

Posted

Yay!

I might not look so pretty but it works. (also the in console block for the activation!)

I completly rearranged the code, removed the pop up menu compeltly (they are the bane of my existence in skyrim mods lol) and managed to find a logic to achieve the same goal with only one repeated key press. 
More Moaning, more stamina damage, more time consumption, yay!

Following questions remain:

 

1. How can I prevent spamming they hotkey? The function to "wiggle free" takes rougly 15 seconds now. But when u press the hotkey several times it starts the function parallel to each other. 

2. How can I add phonemes to the code? 
I found the mfg console source but I cannot get it to compile. 

Guess I need to "bool" and "int" something someplace first i guess... 

Here is the code without MFG console stuff yet:

 

Spoiler

Event OnKeyDown(Int KeyCode)
    if KeyCode == 57
        Int exposure
        Float stamina = libs.PlayerRef.GetActorValue("Stamina")
        Float damage = StaminaChange*libs.PlayerRef.GetBaseActorValue("Stamina")
        if CanShowMenu && !UI.IsMenuOpen("Dialogue Menu") && !Utility.IsInMenuMode() 
                            ;struggle
                if stamina < damage
                    Debug.Notification("You are too tired to move your body.")
                else
                    libs.PlayerRef.DamageActorValue("Stamina", damage)
                    Utility.Wait(2)
                    libs.SexLabMoan(libs.PlayerRef)
                    if !(sla.IsActorArousalLocked(libs.PlayerRef) || sla.IsActorArousalBlocked(libs.PlayerRef))
                        exposure = sla.GetActorExposure(libs.PlayerRef) + cfgqst.ExposureChange[0]
                        if exposure > 100
                            exposure = 100
                        endif
                        sla.SetActorExposure(libs.PlayerRef, exposure)
                    endif
                    
                    If !Success
                       int iRND = Utility.RandomInt(0, 100)
                       If iRND <= 10
                          Success = true
                          Debug.Notification("Success is " +Success+ ", and random is "+iRND+ ".")
                       EndIf
                    EndIf
                    if Success
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        Debug.Notification("Your bindings finally feel looser than before... ")
                        Free = true
                    else
                        libs.PlayerRef.DamageActorValue("Stamina", damage)
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        libs.PlayerRef.DamageActorValue("Stamina", damage)
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        Utility.Wait(2)
                        libs.SexLabMoan(libs.PlayerRef)
                        Debug.Notification("Your bindings are stronger than you thought... ")
                    endif
                endif    
                if Free
                    libs.SexLabMoan(libs.PlayerRef)
                    SetObjectiveCompleted(20, true)
                    Utility.Wait(10)
                    SetStage(100)
                endif                    
        endif
    endif
EndEvent

 

Posted
bool bLocked = false

Event OnKeyDown(Int KeyCode)
    if KeyCode != 57 || bLocked
        return
    endif

    bLocked = true

    if !CanShowMenu || Utility.IsInMenuMode() || UI.IsMenuOpen("Dialogue Menu")
        bLocked = false
        return
    endif

    Actor player = libs.PlayerRef

    Float stamina = player.GetActorValue("Stamina")
    Float damage = StaminaChange * player.GetBaseActorValue("Stamina")

    ;struggle
    if stamina < damage
        Debug.Notification("You are too tired to move your body.")

        bLocked = false
        return
    endif

    player.DamageActorValue("Stamina", damage)
    Utility.Wait(2)
    libs.SexLabMoan(player)

    if !sla.IsActorArousalLocked(player) && !sla.IsActorArousalBlocked(player)
        Int exposure = sla.GetActorExposure(player) + cfgqst.ExposureChange[0]

        if exposure > 100
            exposure = 100
        endif

        sla.SetActorExposure(player, exposure)
    endif
    
    If Success || Utility.RandomFloat() < 0.1
    	Success = true ; Is this a global variable?
        Utility.Wait(2)
        libs.SexLabMoan(player)
        Debug.Notification("Your bindings finally feel looser than before... ")

        Free = true ; Is this a global variable?
        SetObjectiveCompleted(20, true)
        Utility.Wait(10)
        SetStage(100)
    else
        Int i = 2

        while i
            i -= 1

            player.DamageActorValue("Stamina", damage)

            Int j = 3

            while j
                j -= 1

                Utility.Wait(2)
                libs.SexLabMoan(player)
            endwhile
        endwhile

        Debug.Notification("Your bindings are stronger than you thought... ")
    endif

    bLocked = false
EndEvent

Changed some things a starting programmer should take into account:

  1. Flat is better than nested.
  2. Sparse is better than dense.
  3. Avoid repetition. Loops exist for a reason.

Also showed how you can do a non blocking lock for the hotkey in Papyrus.

For phonemes just add:

MfgConsoleFunc.SetPhoneme(player, 0-15, 0-100)

 

Posted
39 minutes ago, Hawk9969 said:

Change somethings a starting programmer should take into account:

 

Very cool, thx. So elegant. 

Quote
  1. Flat is better than nested.
  2. Sparse is better than dense.
  3. Avoid repetition. Loops exist for a reason.

I knew something like that Loops existed but this is virtually my first 24 hours of doing this and I just try to get my head arround the stuff bit by bit. 
 

Quote

Also showed how you can do a non blocking lock for the hotkey in Papyrus.

For phonemes just add:


MfgConsoleFunc.SetPhoneme(player, 0-15, 0-100)

 


so easy... wow. Now I wonder what I failed to ask google because nothing even remotely close to that showed up o_O 

AS for the "Success" and "Free" part.

The Sucess was there before. It was a... what is it called, a whatever I guess:
I made the "Free" thing up myself to allow the function to work at the same hotkey with progressing conditions. Sorry for my talk, I have zero knowlegde of the "professional" language and terms
 

Quote

CanShowMenu = false
Success = false
Free = false

libs.GlobalEventFlag = false
StaminaChange = cfgqst.GetStruggleStaminaChange()
Skill = Math.Floor(libs.PlayerRef.GetActorValue("Lockpicking"))
MaxVal = zcfg.UnlockThreshold

 

Quote


ndun_configquest_scr Property cfgqst Auto
Message Property ndun_CaptMsg Auto
slaFrameworkScr Property sla Auto
sslSystemConfig property Config auto
zadlibs Property libs Auto
zadConfig Property zcfg Auto
Quest Property calmqst Auto
Bool CanShowMenu = false
Bool Success = false
Bool Free = false

Int Skill = 0
Int MaxVal = 185
Float StaminaChange = 1.0

the last source file (before I copy paste ur stuff and start playing with MFG

ndun_captivequest_qf_scr.psc

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...