Jump to content

[WIP] Furniture Interaction Framework - for NPCs and Player


Roggvir

Recommended Posts

Posted

So now that I'm back to waiting for this with bated breath, I would like to clarify something: this won't actually be adding animations for the overlooked zaz furniture correct? just a way to interact with it and use the animations if/when someone makes them?

Posted

good work and please give some selectable situations for rapist such as SEXTOOLS . but in ST we change sex positions after start sex but we must select sex posiions befor start sex scenes . 

thanks a lot 

 

  • 2 weeks later...
Posted

Looks very promising :)

If i understand this right, it doesn't matter where the furnitures came from? (Prison Overhaul, Maria Eden,...) Or will only mods which use this framework as requirement support actions?

Posted

Interesting !

 

bet Zaz & Xaz would have a big smile after watching your video...

 

LoL on Carlotta, 1:05 till end. come on, come on huh, huh, your nothing, huh ! i take that dialog/voices are from your framework since i never heard them before...

 

Wonderful work. And if i understand correctly what your FIF trying to do, your FIF would be a great help for any modder that use furniture/s as part of their mod (DCL,ME,MME,PO, wolfclub and more) Since they can focus on their mod and let furniture interaction being handle by your FIF. 

  • 2 weeks later...
  • 3 weeks later...
  • 1 month later...
  • 1 month later...
Posted

So now that I'm back to waiting for this with bated breath, I would like to clarify something: this won't actually be adding animations for the overlooked zaz furniture correct? just a way to interact with it and use the animations if/when someone makes them?

 

Nope, it wont be adding any new animations, think of this as something like SexLab - a framework that uses existing animations (depending on ppls personal choices - it will be possible to create custom animation sets).

 

 

Looks very promising :)

If i understand this right, it doesn't matter where the furnitures came from? (Prison Overhaul, Maria Eden,...) Or will only mods which use this framework as requirement support actions?

 

If you mean whether it depends on which mod added that furniture to the worl/interior, then no, it doesnt matter.

As long as the furniture is "registered" in the framework, it will be able to recognize it and use it - ie. as we know, Prison Overhaul adds some pillories to Whiterun, and as long as the same kind of pillory is registered in the framework, it will know how to use it.

If lets say Zaz adds brand new furniture (as in "a new object in CK, with new formId"), which obviously wont be known and registered by the framework, then such furniture wont be available UNTIL you register it via a custom plugin, or until the framework default furniture registration plugin gets updated.

But once the framework "knows" certain furniture object, it doesnt matter which mod adds it to the world - it will be available.

(umm, i am trying my best, did i confuse you even more, or is this explanation working? :))

 

 

Interesting !

 

bet Zaz & Xaz would have a big smile after watching your video...

 

LoL on Carlotta, 1:05 till end. come on, come on huh, huh, your nothing, huh ! i take that dialog/voices are from your framework since i never heard them before...

 

Wonderful work. And if i understand correctly what your FIF trying to do, your FIF would be a great help for any modder that use furniture/s as part of their mod (DCL,ME,MME,PO, wolfclub and more) Since they can focus on their mod and let furniture interaction being handle by your FIF. 

 

ALL of those voices are actually in Vanilla game.

I just created a new dialog where i duplicated quite an amount of carefully selected dialog lines/voices (basically, i went over ALL dialog lines in the whole game, including all DLCs, listened to each one, and made selection for every voice type there is, and put that in my special dialog), and the animator thread is forcing the NPC to use that special dialog durign these interactions.

That way, every NPC is still speaking with their normal voice.

It is still very simple - i need to split those selected lines into more categories, depending on which situation i want them to be used in (ie. you obviously want different set of sounds/voices for consensual soft/hard vs. non-consensual soft/hard, vs. torture/whipping vs. ...whatever else).

 

Yes, the point of this FIF is to make it take care of furniture stuff, allowing modders to focus on their actual work - they will just throw this plugin in, and either let it do its default job, or they can craft custom furniture registrations and custom animations/interaction sets, and even use simple functions to micromanage things in case they need.

 

 

Any progress? Alpha files?

 

Yeah... about that... as everyone surely noticed, i was away for some time.

Not only was i awol, but i wasnt even working on it.

I got pissed of by Papyrus and just couldnt go on.

It hapens to me quite frequently, i find the Papyrus horrendously lacking, and it is VERY frustrating for me, because i am a perfectionist and i HATE all the workarounds and crazy stuff i have to do in order to make this all work.

And even after you do everything you can, there are still things that just wont budge.

I just had to take a long break, and reevalute it all again, see if i can do some things differently, etc.

It is also very difficult for me to not have second opinion regarding the code, i often wish i had someone who would be working on this together with me, if only so that person could steer me in right direction after i take one of gadzillion detours working on something that may not be really needed but for some reason it bugs the hell out of me.

 

So, sorry, not much progress - i started reviewing all code today again (after my absence, i forgot many things and need to refresh my memory yet again).

And no alpha - i do have a kind of working version, that was used in making that video, but i fear releasing it to public, even as Alpha, because i am afraid it would result in tons of question that i'd rather not deal with until i finalize and solve some remaining problems.

BUT, if any modder PMs me and asks for this pre-pre-pre-pre-pre-Alpha version, i will gladly give it to them, so they may look at how it works (maybe they might provide some useful pointers or ideas - that would be great), or even take it to dissect it and build something of their own out of it (i am all about sharing - thats how we learn), but dont expect me to serve as a 24/7 support line (i will of course try to explain and help, but i'd rather work than answer questions).

Posted

In case anyone would find this interesting...
This is an example of how an "interaction" is defined:

 

scriptname RoggMagicFx_PilloryAssFuck extends ActiveMagicEffect

event OnEffectStart(actor akActorA, actor akActorB)

	; Get furniture interaction thread by actorA
	RoggFF_Thread t = RoggLib.GetThreadByActor(akActorA as objectReference)
	if !t
		ERROR("MagicFX: Pillory Ass Fuck - failed to get furniture thread!")
		return
	endif

	; Prepare reference to the furniture library whose after-act callbacks we register for animation end event
	RoggFL_DefaultFurniture furnLib = Quest.GetQuest("RoggFL_DefaultFurniture") as RoggFL_DefaultFurniture
	if !furnLib
		ERROR("MagicFX: Pillory Ass Fuck - failed to get furniture library.")
		return
	endif

	akActorA.SetFactionRank(t.RoggFF.RoggFactInThread, _Rank_ThreadAnimating)
	akActorB.SetFactionRank(t.RoggFF.RoggFactInThread, _Rank_ThreadAnimating)
	akActorB.SetFactionRank(t.RoggFF.RoggFactHighHeelsOff, 0)

	; Set animations
	t.ClearAnimationStages()

	faction RoggFctTouchedDislikedFirst = Game.GetFormFromFile(0x00000D69, "RoggVoiceover.esp") as faction
	faction RoggFctTouchedDislikedSeq = Game.GetFormFromFile(0x00005915, "RoggVoiceover.esp") as faction
	faction RoggFctFuckingViolent = Game.GetFormFromFile(0x00000D62, "RoggVoiceover.esp") as faction
	faction RoggFctFuckedViolent = Game.GetFormFromFile(0x00000D64, "RoggVoiceover.esp") as faction


int i = 0
int c = 0

;==================== Rubbing the pussy (80% chance)
if Utility.RandomInt(0, 100) < 50
	;-------------------- First stage is to make actorA play the surprised 'first contact' voiceover
	t.SetAnimation(i, akActorA, "ZazAPPillTorPussRub1B",	timeout=2)
	t.SetAnimation(i, akActorB, "ZazAPPillTorPussRub1A")
	t.SetAnimVoice(i, akActorA, RoggFctTouchedDislikedFirst, 999) ; actorA startled response ('first contact' response)
	; TODO: implement actorB intro voiceover (things like "what do we have here?" and "Aren't you a sight for sore eyes", etc.)
	i += 1
	;-------------------- Add at least one more stage after the first surprise, now with voiceover for begging, laments, cries for help
	c = Utility.RandomInt(1, 4)
	while (c > 0)
		c -= 1
		t.SetAnimation(i, akActorA, "ZazAPPillTorPussRub1B",	timeout=4)
		t.SetAnimation(i, akActorB, "ZazAPPillTorPussRub1A")
		t.SetAnimVoice(i, akActorA, RoggFctTouchedDislikedSeq, 3) ; actorA threatens, lamments, begs or cries for help
		; TODO: implement actorB taunts/dominant-talk voiceover
		i += 1
	endWhile
endif

;==================== Entering
int r = Utility.RandomInt(0, 100)
if r < 40
	Debug.Notification("Slow entry")
	;-------------------- Slow entry
	t.SetAnimation(i, akActorA, "ZazAPPillSXHip01B", timeout=4) ; actorB entering actorA while holding hips (must play only once, duration=3.333)
	t.SetAnimation(i, akActorB, "ZazAPPillSXHip01A")
	t.SetAnimVoice(i, akActorA, RoggFctTouchedDislikedSeq, 0.3) ; actorA threatens, lamments, begs or cries for help
	t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.3) ; actorB grunts, heavy breaths, insults, threats, taunts, laughs, etc.
	; TODO: implement slightly gentler voiceover for actorB here
	i += 1
elseif r < 70
	Debug.Notification("Hard entry")
	;-------------------- Hard entry
	t.SetAnimation(i, akActorA, "ZazAPPillSXEnterB", timeout=2)
	t.SetAnimation(i, akActorB, "ZazAPPillSXEnterA")
	t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.3) ; actorA grunts, heavy breaths, insults, threats, begging or crying for help
	t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.3) ; actorB grunts, heavy breaths, insults, threats, taunts, laughs, etc.
	i += 1
else
	;-------------------- Violent entry
	r = Utility.RandomInt(0, 100)
	if r < 33
		Debug.Notification("Violent entry 1")
		; SLOW and HARD fucking while holding hips (duration=1)
		t.SetAnimation(i, akActorA, "ZazAPPillSXHip03B", timeout=3)
		t.SetAnimation(i, akActorB, "ZazAPPillSXHip03A")
		t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.3) ; actorA grunts, heavy breaths, insults, threats, begging or crying for help
		t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.3) ; actorB grunts, heavy breaths, insults, threats, taunts, laughs, etc.
	elseif r < 66
		Debug.Notification("Violent entry 2")
		; NOT-SO-FAST fucking with HARD and FAST pushes while holding right arm (duration=1)
		t.SetAnimation(i, akActorA, "ZazAPPillSXHand02B", timeout=3)
		t.SetAnimation(i, akActorB, "ZazAPPillSXHand02A")
		t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.4) ; actorA grunts, heavy breaths, insults, threats, begging or crying for help
		t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.4) ; actorB grunts, heavy breaths, insults, threats, taunts, laughs, etc.
	else
		Debug.Notification("Violent entry 3")
		; SLOWER fucking while holding across belly (duration=1)
		t.SetAnimation(i, akActorA, "ZazAPPillSXHold02B", timeout=3)
		t.SetAnimation(i, akActorB, "ZazAPPillSXHold02A")
		t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.4) ; actorA grunts, heavy breaths, insults, threats, begging or crying for help
		t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.4) ; actorB grunts, heavy breaths, insults, threats, taunts, laughs, etc.
	endif
	; TODO: implement voiceovers for both actors, more suited for a violent/forced entry (or at least the victim, actorA, could use a more vocal reaction)
	i += 1
endif

;==================== Fucking in progress
string  animStr  ; selected animation name
int     animRpt  ; how many times to repeat selected anim
float   animDur  ; duration of the selected anim until it start to repeat
float   voiceRpt ; timeout for repeating voice sounds for selected anim
int sets = Utility.RandomInt(5, 8) ; how many anim sets will have the whole fucking process
while (sets > 4) ; TODO: this should be ZERO! we want 5 sets minimum and 8 max
	; choose random anim
	int a = Utility.RandomInt(0, 6);
	if a == 0
		animStr = "ZazAPPillSXHip02"; FAST and HARD fucking while holding hips (duration=4.666)
		animDur = 4.666
		voiceRpt = 0.2; Grunt and moan every 0.2 sec
	elseIf a == 1
		animStr = "ZazAPPillSXHip03"; SLOW and HARD fucking while holding hips (duration=1)
		animDur = 1.0
		voiceRpt = 0.4; Grunt and moan every 0.4 sec
	elseIf a == 2
		animStr = "ZazAPPillSXHand01"; FAST and HARD rythmic fucking while holding right arm (duration=2.333)
		animDur = 2.333
		voiceRpt = 1.1; Grunt and moan every 1.1 sec
	elseIf a == 3
		animStr = "ZazAPPillSXHand02"; NOT-SO-FAST fucking with HARD and FAST pushes while holding right arm (duration=1)
		animDur = 1.0
		voiceRpt = 0.4; Grunt and moan every 0.4 sec
	elseIf a == 4
		animStr = "ZazAPPillSXHold01"; NOT-SO-FAST fucking while holding across belly (duration=0.633)
		animDur = 0.633
		voiceRpt = 0.3; Grunt and moan every 0.3 sec
	elseIf a == 5
		animStr = "ZazAPPillSXHold02"; SLOWER fucking while holding across belly (duration=1)
		animDur = 1.0
		voiceRpt = 0.4; Grunt and moan every 0.4 sec
	elseIf a == 6 && i > 10
		animStr = "ZazAPPillSXSpank01"; FAST fucking while spanking with right hand (duration=2.333)
		animDur = 2.333
		voiceRpt = 1.1; Grunt and moan every 1.1 sec
	endif
	animRpt = Utility.RandomInt(10, 20); how many times to repeat the animation
	while (animRpt > 0)
		; let actorA randomly alternate fast anims for more variety, lets see how it will look...
		r = Utility.RandomInt(0, 100)
		if animStr == "ZazAPPillSXHip02" && r < 50
			if Utility.RandomInt(0, 100) < 50
				t.SetAnimation(i, akActorA, "ZazAPPillSXHand01B", timeout=animDur)
			else
				t.SetAnimation(i, akActorA, "ZazAPPillSXSpank01B", timeout=animDur)
			endIf
		elseIf animStr == "ZazAPPillSXHand01" && r < 50
			if Utility.RandomInt(0, 100) < 50
				t.SetAnimation(i, akActorA, "ZazAPPillSXHip02B", timeout=animDur)
			else
				t.SetAnimation(i, akActorA, "ZazAPPillSXSpank01B", timeout=animDur)
			endIf
		elseIf animStr == "ZazAPPillSXSpank01" && r < 50
			if Utility.RandomInt(0, 100) < 50
				t.SetAnimation(i, akActorA, "ZazAPPillSXHip02B", timeout=animDur)
			else
				t.SetAnimation(i, akActorA, "ZazAPPillSXHand01B", timeout=animDur)
			endIf
		else
			t.SetAnimation(i, akActorA, animStr+"B", timeout=animDur)
		endif
		t.SetAnimation(i, akActorB, animStr+"A")
		t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, voiceRpt)
		t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, voiceRpt)
		i += 1
		animRpt -= 1
	endWhile
	sets -= 1
endWhile

;==================== Climax (combines several anims for better effect)
; TODO: implement climax voiceover (at least for actorB here)
; ZazAPPillSXHip03, SLOW and HARD fucking while holding hips (duration=1)
t.SetAnimation(i, akActorA, "ZazAPPillSXHip03B", timeout=0.6) ; make it last only for the push-in
t.SetAnimation(i, akActorB, "ZazAPPillSXHip03A")
t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.4) ; Grunt and moan every 0.4 sec
t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.4)
i += 1
; ZazAPPillSXEND01, Climax while holding hips (duration=3.300, should be preceeded by other hip anim, and can be quickly switched with it a few times while prolonging duration of this one for better effect)
t.SetAnimation(i, akActorA, "ZazAPPillSXEND01B", timeout=0.3) ; make it last only 0.3 secs
t.SetAnimation(i, akActorB, "ZazAPPillSXEND01A")
i += 1
; ZazAPPillSXHip03, SLOW and HARD fucking while holding hips (duration=1)
t.SetAnimation(i, akActorA, "ZazAPPillSXHip03B", timeout=0.6) ; make it last only for the push-in
t.SetAnimation(i, akActorB, "ZazAPPillSXHip03A")
t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.4) ; Grunt and moan every 0.4 sec
t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.4)
i += 1
; ZazAPPillSXEND01, Climax while holding hips (duration=3.300, should be preceeded by other hip anim, and can be quickly switched with it a few times while prolonging duration of this one for better effect)
t.SetAnimation(i, akActorA, "ZazAPPillSXEND01B", timeout=0.7) ; make it last a bit longer than last time
t.SetAnimation(i, akActorB, "ZazAPPillSXEND01A")
i += 1
; ZazAPPillSXHip03, SLOW and HARD fucking while holding hips (duration=1)
t.SetAnimation(i, akActorA, "ZazAPPillSXHip03B", timeout=0.6) ; make it last only for the push-in
t.SetAnimation(i, akActorB, "ZazAPPillSXHip03A")
t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.4) ; Grunt and moan every 0.4 sec
t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.4)
i += 1
; ZazAPPillSXEND01, Climax while holding hips (duration=3.300, should be preceeded by other hip anim, and can be quickly switched with it a few times while prolonging duration of this one for better effect)
t.SetAnimation(i, akActorA, "ZazAPPillSXEND01B", timeout=0.3) ; a short one again
t.SetAnimation(i, akActorB, "ZazAPPillSXEND01A")
i += 1
; ZazAPPillSXHip03, SLOW and HARD fucking while holding hips (duration=1)
t.SetAnimation(i, akActorA, "ZazAPPillSXHip03B", timeout=0.5) ; make it last only for the push-in
t.SetAnimation(i, akActorB, "ZazAPPillSXHip03A")
t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.4) ; Grunt and moan every 0.4 sec
t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.4)
i += 1
; ZazAPPillSXEND01, Climax while holding hips (duration=3.300, should be preceeded by other hip anim, and can be quickly switched with it a few times while prolonging duration of this one for better effect)
t.SetAnimation(i, akActorA, "ZazAPPillSXEND01B", timeout=0.9) ; make it last a bit longer than the long one before
t.SetAnimation(i, akActorB, "ZazAPPillSXEND01A")
i += 1
; ZazAPPillSXHip03, SLOW and HARD fucking while holding hips (duration=1)
t.SetAnimation(i, akActorA, "ZazAPPillSXHip03B", timeout=0.5) ; make it last only for the push-in
t.SetAnimation(i, akActorB, "ZazAPPillSXHip03A")
t.SetAnimVoice(i, akActorA, RoggFctFuckedViolent, 0.4) ; Grunt and moan every 0.4 sec
t.SetAnimVoice(i, akActorB, RoggFctFuckingViolent, 0.4)
i += 1
; ZazAPPillSXEND01, Climax while holding hips (duration=3.300, should be preceeded by other hip anim, and can be quickly switched with it a few times while prolonging duration of this one for better effect)
t.SetAnimation(i, akActorA, "ZazAPPillSXEND01B", timeout=Utility.RandomInt(2, 7)) ; finally climaxing, make it last a longer but random duration
t.SetAnimation(i, akActorB, "ZazAPPillSXEND01A")
i += 1

t.SetAnimation(i, akActorA, "ZazAPPillSXExitB",		timeout=2)
t.SetAnimation(i, akActorB, "ZazAPPillSXExitA")

	; Register the appropriate library after-act maintenance callback function for primary actor
	t.InteractionEndEventName = "PilloryAssFuckEnd" + t.id + "X" + akActorA.GetFormID()
	Debug.Trace("MagicFX: Pillory Ass Fuck - thread InteractionEndEventName set.")
	furnLib.RegisterForModEvent(t.InteractionEndEventName, "OnPilloryAssFuck_End")
	Debug.Trace("MagicFX: Pillory Ass Fuck - end event registered.")

	t.reposActors = "AB" ; Allow repositioning actors A and B while animating
	t.aliasScriptA.Undress(0x00000004+0x00080000+0x00400000)
	t.aliasScriptB.Undress(0x00000001+0x00000004+0x00000008+0x00000010+0x00000200+0x00080000+0x00080000+0x00400000)
	t.StartAnimating()

endEvent

 

 

Yeah, i know it looks horrible, and long.
This is kind of crappy WIP full of stuff that is not neccessary.
What it does is to prepare arrays that tell the animator thread what to do and when - different stages with different animations and even different voices (controled via assigning actors into special "voice factions").
As you can see it generates somewhat randomized flow, so no interaction is exactly the same nor exactly the same length.
Player cannot influence it much, except via optional manual stage advancement (similar to sexlab).
 
I am now trying to think about all possible scenarios and their requirements (for example, the whipping interaction is slightly more complicated, because it requires the actor to be given a weapon -cane- and make the actor to draw it, and then take it away at the end), and a way how to make it possible to define all this in simpler ways (as a JSON maybe?).

Posted

For those of you that are wondering whats going on right now...

 

After my long absence, it took me quite some time to get my development environment working again, and reinstalling everything, using latest versions of all neccessary or optional mods.

Then i got a bit sidetracked for almost two days, working on a proof of concept for something i'd like ZAP to add, and after that got lost in time a bit while pushing my luck with some attempts at modeling in 3ds Max (a great fiasco!).

 

And to top it off, after finally getting back to what i am supposed to work on, i found that even after successfully recompiling all scripts, some crucial functionality doesnt work.

And that is because last time i didn't make any notes about what i just started rewriting at that time and how.

So, i am now in the process of rewriting those broken parts, instead of trying to fix them (which seems more difficult).

 

...but you shouldn't feel sad or angry - at least its not dead, right? :)

Posted

Progress update...

  • Remove all DLC dependency: DONE
    I think there was some furniture added by some of the DLCs, but support for any of those will be added by separate plugins.
    Main reason behind this decision was to make testing easier for me (if only to make tes5edit load faster).
    After i acually release something, i might reconsider and merge it.
     
  • Replace spell and faction based functionality by keywords: CANCELED
    For reasons i dont remember now ('twas probably mere stupidity), i used spells and factions to drive some of the mechanisms like which interaction options are available based on furniture the actor is in, etc. by adding some special spells to actors and checking if they have them, or assigning actors to factions and checking later whether they are member of this and that, or which rank it is, etc. Using keywords will be cleaner and much more lightweight in comparison.
     
  • Rewriting Actor Script (yep, again): WIP
    As i already mentioned in some post, i started making some changes before i took the last long break, changes that breaks everything.
    Instead of fixing it, i decided it will be better to rewrite the affected parts from scratch, as i have some new ideas and improvements on my mind anyway.
    (last update: 2016-06-20)
     
  • Unplanned rewrite of Furniture Scanner (for player): WIP (98% done)
    Moved almost all functionality from scripts to native game functions (should run better, and its way easier to maintain).
    Now using Find Matching Reference to fill up to 10 aliases with nearest furniture objects of any of the registered types, and then setting appropriate global variables according to whats in the aliases.
    (last update: 2016-06-22)
     
  • Unplanned rewrite of Mod Configuration Menu: WIP (90% done)
    Unplanned, but very much needed, now providing following features:
    - stop/restart the framework and all of its parts (usefull when uninstalling or before updating)
    - clear/refresh/unregister selected furniture library (usefull when updating or uninstalling libraries)
    - removing individual furniture registrations (useful if you want to keep given library but get rid of some furniture that causes any problems)
    - releasing individual framework threads (mostly for testing purposes, but may find its use in normal play too)
    - releasing individual actors from a framework thread (mostly for testing purposes - usually to get rid of the cow, when the randomizer tries to match her with a nearby farmer)
    (last update: 2016-06-24)
     
  • Added Main Controler: DONE
    Required by the unplanned MCM rewrite to allow clean stop/restart of all parts of the framework.
    All quests/script are now started/stopped from this main controler via simple functions it provides,
    ensuring the proper startup/stopping sequence will be always followed.
    (last update: 2016-06-22)

 

Once i am done with the above, we'll see...
I wanted to do some more major changes, like move some furniture and interaction related settings/definitions to JSON to make it supposedly easier to work with, but i guess it's better to leave that for later once at least the pre-release is out (need to draw a line somewhere, otherwise there wouldn't be end to these neverending changes i do).
-------------------------------------------------------------------------------------------------------------------------
EDIT 1:
While rewriting the actor script, i got a brilliant idea how to simplify handling of packages, and let few more things to the game native functions instead.
I'll spare you of the details, but it will a bit more lightweight and much cleaner, so it will also make it easier to maintain.
And since i had to rewrite those parts anyway, it shouldn't cost any additional time in comparison to rewriting it the old way.
-------------------------------------------------------------------------------------------------------------------------
EDIT 2:
In regards to "Replace spell and faction based functionality by keywords"
Now i know why i didnt do it this way before - because its freaking impossible!
I did find it very suspicious, that all this time i completely missed the AddKeyword function, but i was so happy about it i didn't think!
So i didn't noticed i am not looking at Skyrim CK wiki at that moment, but Fallout 4 CK wiki instead.
Fortunately it didn't take long until i had to compile and test the code and discovered my stupid mistake.
I am gonna keep few subtle changes, but otherwise these things will have to be done the same old way.
At least the other new stuff, mentioned in EDIT 1, works great.

Posted

Progress update...

 

See previous post, look for orange date-stamps.

(should i consolidate and move these lists into the first post to keep it all in one place? will probably try when i have next progress update)

Posted

Not really a progress update, but more like a query for opinions...

 

Made a new system for registering Furnitre Library Extensions.

And i wonder what any modders thinks about this...

 

How does it work for casual mod user:

  1. Install any 3rd party furniture library extensions
    (ie. put their esp plugins into Data dir, and activate them - via NMM/MO/manually, the usual stuff)
     
  2. On starting new game, or loading save that didn't have the Framework running when saving...
    Framework automatically checks loaded plugins to discover any new extensions (since its a new game, all of the existing extensions will be flagged as new), registers them with an Extensions Library, and shows notification to player, telling him to use the MCM menu to activate the new extensions.
    Player opens MCM menu for the Extensions Library, where he can activate all new discovered extensions with one click, or activate only individual selected extensions.
    When any extension is activated, its quest is started, firing the registration process for actual furniture and interactions the extension provides, registering them in the Furniture Library.
     
  3. On loading save that had the Framework running at the time of saving...
    Player must go to Extensions Library MCM menu, and click 'Discover' to make the framework run its discovery process.
    Other than that it works the same as described by #2.
     
  4. Uninstalling extension...
    - Player goes to Extensions Library MCM menu, selects page dedicated to the extension he wants to uninstall, and clicks the "Disable" button on it.
    - Framework switches the extension quest script to "stopping" state, making it refuse any new interaction requests, and marks all its registered furniture/interactions as disabled so any processes like the furniture scanner will simply ignore them.
    - Then it releases all threads and actors currently using any furniture or interactions registered by this extension.
    - And stops this extension's quest.

    After that, player can keep the extension still registered and inactive.
    Or he can click the "Unregister" button in the extension MCM Menu, purging any trace of it from both the Extensions Library and Furniture Library
    - until a discovery process is run again (automaticlaly on new game start, or manually at any time), it will be like this extension doesn't even exist.
    If unregistered, player can Save, Quit, and uninstall/remove this extension's esp.
     

What is required from modders (apart from the usuall mod making procedures involved):

  1. List extension quest names in esp plugin's description
    Let's say you made a new esp plugin with two Furniture Library Extensions, or you merged two extension esp's into one, so you have now two extension quests, one having EditorID "fooChairsAndBeds" and the other one is "barThrone" (of course, best way would be to keep only one quest, and merge the contents of the two quest scripts into one -player can still disable individual furniture forms or interactions, so there is no reason for having several extension quests in one esp, except for ppl being lazy).
    So, you write the following anywhere in your esp plugin's description field...

    Furniture Library Extensions: fooChairsAndBeds, barThrone

    ...its case insensitive, the order of the formIDs doesn't matter, the whole string can be at the start of the description, or in the middle, or the last thing, whitespace between formIDs doesn't matter.

    What does matter:
    - the string "Furniture Library Extensions:" must be written exactly as you see here (whitespace matters in this case)
    - formIDs cannot contain any white space (*not sure if CK even allows it, but if your formID would be "fooChairs and Beds", it wouldn't work here)

 

What do you people think about this?

I find it nice, that the user has full control over the in-game extension installation process - nothing is automatically started.

The description thing is a bit annoying, but i couldn't find any other way, so i think modders can survive that requirement, right?

Posted

It sounds easy enough!

 

Are you already using PapyrusUtils by any chance? If I recall correctly, that has options for sharing data between plugins, like having global variables across mods. Your mod could simply have a global array of candidate plugins, and any mod could announce itself into that array, after which your mod optionally installs the plugin's furniture. And I think SL already installs PapyrusUtils so it's not like you;'re adding something extra just for the registration process.

Posted

It sounds easy enough!

 

Are you already using PapyrusUtils by any chance? If I recall correctly, that has options for sharing data between plugins, like having global variables across mods. Your mod could simply have a global array of candidate plugins, and any mod could announce itself into that array, after which your mod optionally installs the plugin's furniture. And I think SL already installs PapyrusUtils so it's not like you;'re adding something extra just for the registration process.

 

Yes, i am using PapyrusUtil, but i dont see there anything explicitly for sharing data (globals or not).

There is a bunch of functions for various operations, mostly for working with arrays, etc.

I can of course use those and other functions to implement such solution, but i guess thats not what you mean, right?

Can you go into a bit more details to explain?

 

Anyway, regarding the global array of candidate plugins - would there be some nice advantage over what i did?

 

Right now, i like my solution quite a lot, mostly just because its cool ;-) but also because it takes away some of the usuall baggage carried by similar systems.

The plugins do not need to be proactive in any way.

They do not need to contain anything to automatically start some script, however tiny, just so they can announce themselves (even if that "script" is just a simple AddForm one-liner, etc).

Also, the fewer things a modder needs to do, the better.

If i can take away the "burden" of bundling in some activating script, and trade it for the requirement of listing a quest name(s) in the plugin description, then i like doing it - a small difference, but in my opinion every tiny bit counts.

 

Of course, if someone makes a plugin that automatically starts something, i cannot prevent it, and i dont see a reason to (i'd say they have they reasons to do that).

It gives people optional flexibility, but it isn't neccessary by default.

 

I dont really mind changing it to make the plugins announce themselves, but it should provide some advantage at this point, because current system is already fully implemented and working.

Still, thats why i did asked for opinions - if your or someone elses idea actually does have some advantage, or even if just more people say that writing a string into plugin description bothers them more than adding few lines of code to a script, then it will be changed.

 

So... what was that about PapyrusUtil? (didn't you maybe meant Storage/JsonUtil instead?)

Posted

Yeah StorageUtil. That's part of PapyrusUtil, no?

 

I don't think it matters much one way or the other. As long as your system can cope with other crap being present in the description, and if the description can be long enough, then that's fine. If you are "misusing" the description field in this manner, you have to be prepared for other mods doing the same :)

Posted

Yeah StorageUtil. That's part of PapyrusUtil, no?

 

I don't think it matters much one way or the other. As long as your system can cope with other crap being present in the description, and if the description can be long enough, then that's fine. If you are "misusing" the description field in this manner, you have to be prepared for other mods doing the same :)

 

Regarding position, it doesnt matter, it can be mixed in anywhere (but it must have a space or comma after the last quest name).

Regarding the length constraint, that depends on how long the quest's FormID is, and how many are there (ideally there should be just one).

The description is limited to 512 bytes (ie. 512 characters).

For example, this is exactly 512 chars long:

(CR/LF new lines included - i am not sure if CK uses CR/LF or what, but most probably yes)

 

Donec feugiat dolor vitae urna tincidunt finibus.

Nullam vestibulum massa in nisl ullamcorper, in

scelerisque ante porttitor. Donec sit amet tempus

quam, non euismod quam. Proin non erat rutrum,

placerat turpis a, maximus magna.

Aenean at tristique ante, ultricies accumsan massa.

Quisque finibus cursus neque at pharetra.

SuspendissFFLibExt: fooQuest1, barQuest2 at mi nisi

lacinia odio, ut aliquet sem velit vel odio.

Donec in ligula eleifend, tempor neque nec, posuere

risus. Suspendisse lacinia in.

 

...that isn't much to begin with.

Do people really put anything in there? anything long enough so they would struggle with the length?

Remember, we're talking about special purpose plugin - i dont think there is any need for ppl to put there a list of all provided interactions, or furniture, etc. (i can provide other means for that, if requested), and what else would they put there if not that.

 

I can make the special recognization token shorter, like in the spoiler above, but any less could introduce a problem with falsely matching mods having some other strange abbreviations there as part of their description.

Posted

Ok, i am gonna keep the plugin discovery system that is using the desription, at least as a default method.
As i wrote, auto-registering by plugins themselves is still possible if needed, so its not an issue.

But i have a different question to ask now...

The way how the furniture and interactions are defined in a library extension plugins
Currently, the following is an example of a complete script that is used for furniture/interaction definitions in a plugin.
This is all your plugin needs, apart from small magic effect fragments that are used to start individual interactions.
(example contains only two furniture types registrations and two interactions, just enough to give you an idea, its by far not a complete default library script)
I have two questions for you:

  • Any better idea how to store the registrations? (the data inside all the REGISTRATION_ states)
    I like having them in script, because its more flexible - for example, you can make things conditional - register or do something depending on other addons, or other registered plugins.
    But maybe it is not needed (i didnt found a real reason for it myself yet), and maybe people would prefer to set it all via CK? (except of course the starting/event handling functions which should remain where they are).
  • Any general improvement tips?
    Like using different ways to do things, different types (forlists vs. arrays of forms, etc.), or anything to make things either more efficient and optimized, or easier for plugin creators.

 

 

scriptname rffDefaultFurniture extends rffFurnitureLibrary
{Register default furniture types}

;// Name of a furniture group currently being registered
;// Value must be set everytime in each of your registering states
string groupName

;// Formlists containing furniture forms - assign in CK
;// Each formlist must only contain furniture forms allowing same types of interactions.
formList property rffFmlVanillaChairs    auto ;// All kinds of standard sized Vanilla chairs
formList property rffFmlZbfPillorySingle auto ;// Zap pillory single and other similar types fitting same animations, including static versions

;// Global variables used for signaling when player is near any type of registered furniture - assign in CK
;// One global per furniture group
globalVariable property rffNearVanillaChairs    auto
globalVariable property rffNearZbfPillorySingle auto

;// Spells used to signal which furniture is being currently used by an actor - assing in CK
;// Used mostly just to filter player dialogue of interaction choices, when talking to actors in furniture or while player is in furniture
spell property rffInVanillaChairs    auto
spell property rffInZbfPillorySingle auto

;// Interaction spells - assign in CK
;// A special spells with own specific magicEffects whose activeMagicEffect script starts particular interaction
spell property rffDoChairSpank  auto
spell property rffDoPilloryFuck auto
spell property rffDoPilloryWhip auto


;/=============================================================================
//  Furniture registration states
//  NOTE: States must be named REGISTER_#, where # is a number ranging from 0
//        up to 127. Gaps in the number sequence are allowed.
=============================================================================/;

state REGISTER_0
    event OnBeginState()
        groupName = "Zbf Pillory"
        RegisterGroup(groupName)
    endEvent
    event OnRffFL_Registered(int id, string evtGroupName)
        if evtGroupName != groupName ;// not our group name, means this event was not meant for us here
            return
        endIf
        rffFurnitureInfo info = PickById(id) ;// get info alias script this group has been assigned to
        SetForms(info, None, rffFmlZbfPillorySingle) ;// push the listed furniture forms into array of the info 'object' (TODO: maybe change the array into a formlist - easier usage? benefits?)
        info.idSpell = rffInZbfPillorySingle
        info.globalVar = rffNearZbfPillorySingle
        info.actionSpells = new spell[2]
        info.actionSpells[0] = rffDoPilloryFuck
        info.actionSpells[1] = rffDoPilloryWhip
        info.idles = new string[5] ;// list of randomly played animations while idle in furniture
        info.idles[0] = "ZazAPFPillSingle01"
        info.idles[1] = "ZazAPFPillSingle02"
        info.idles[2] = "ZazAPFPillSingle03"
        info.idles[3] = "ZazAPFPillSingle04"
        info.idles[4] = "ZazAPFPillSingle05"
        info.hitIdles = new string[3] ;// list of animations played when actor is hurt while in furniture (ie. when being whipped)
        info.hitIdles[0] = "ZazAPFPillSingle03"
        info.hitIdles[1] = "ZazAPPillTorSpank1B"
        info.hitIdles[2] = "ZazAPPillTorSpank2B"
        GoToState("")
    endEvent
endState

state REGISTER_23
    event OnBeginState()
        groupName = "Vanilla Chair"
        RegisterGroup(groupName)
    endEvent
    event OnRffFL_Registered(int id, string evtGroupName)
        if evtGroupName != groupName ;// not our group name, means this event was not meant for us here
            return
        endIf
        rffFurnitureInfo info = PickById(id) ;// get info alias script this group has been assigned to
        SetForms(info, None, rffFmlVanillaChairs) ;// push the listed furniture forms into array of the info 'object' (TODO: maybe change the array into a formlist - easier usage? benefits?)
        info.idSpell = rffInZbfVanillaChairs
        info.globalVar = rffNearVanillaChairs
        info.actionSpells = new spell[1]
        info.actionSpells[0] = rffDoChairSpank
;//        info.idles = new string[1] ; list of randomly played animations while idle in furniture (TODO: find out which ones go with the chairs)
;//        info.idles[0] = ""
;//        info.hitIdles = new string[1] ; list of animations played when actor is hurt while in furniture (no use for chairs, leave empty)
;//        info.hitIdles[0] = ""
        GoToState("")
    endEvent
endState


;/=============================================================================
//  Interactions
//  These functions are to be called from external sripts, or dialog fragments.
//  For most, we could simply call the rffLib.StartInteraction() directly, from
//  wherever is the interaction initiated, but until this library is finalized,
//  i like to keep them here in case any happens to require any pre-processing
//  before the actual StartInteraction() call.
//  TODO: maybe make it a requirement for the functions to be here?
//        could be a good idea to have them listed in the plugin script if only
//        for the sake of order and clarity, for everyone to easily find them.
=============================================================================/;

;// Starts the "spanking chair" interaction between given actors (actorA is the one being spanked)
function ChairSpank(actor actorA, actor actorB)
    rffLib.StartInteraction(rffDoChairSpank, actorA, actorB)
endFunction

;// Callback to do some post-interaction maintenance or cleanup
event OnChairSpank_End(form formActor, int threadId)
    UnRegisterForModEvent("ChairSpankEnd"+ threadId +"_"+ formActor.GetFormID())
    rffMain rff = rffLib.GetAPI()
    rffThread t = rff.GetThread(threadId)
    if t
        t.Release() ;// for now, just release the whole thread (TODO: consider adding a bit of micromanagement)
    endIf
endEvent

;// Starts the "fucking in pillory" interaction between given actors (actorA is the one locked in pillory)
function PilloryFuck(actor actorA, actor actorB)
    rffLib.StartInteraction(rffDoPilloryFuck, actorA, actorB)
endFunction

;// Callback to do some post-interaction maintenance or cleanup
event OnPilloryFuck_End(form formActor, int threadId)
    UnRegisterForModEvent("PilloryFuckEnd"+ threadId +"_"+ formActor.GetFormID())
    rffMain rff = rffLib.GetAPI()
    rffThread t = rff.GetThread(threadId)
    if t
        actor actorA = t.actorA
        actor actorB = t.actorB
        actorA.SetFactionRank(t.rff.RoggFactInThread, 6)
        actorB.SetFactionRank(t.rff.RoggFactInThread, 5)
        Debug.SendAnimationEvent(actorA, "ZazAPPillSolo01") ;// reset animation while keeping actor in pillory
        t.ResetActorPosition(actorA)
        if !t.actorScriptA.isReleasing
            t.actorScriptA.GotoState("IN_POSITION") ;// Switch back to sitting state (if not being released)
            ;// Make actorA (locked in pillory) play a specific idle reflecting his state after this interaction
            Debug.SendAnimationEvent(actorA, t.furnitureInfo.idles[2])
            t.actorScriptA.RegisterForSingleUpdate(20) ;// make this idle last at least 20 seconds
        endif
        t.RequestActorRelease(actorB)
    endif
endEvent

;// Starts "whipping in pillory" interaction between given actors (actorA is the one locked in pillory)
function PilloryWhip(actor actorA, actor actorB)
    rffLib.StartInteraction(rffDoPilloryWhip, actorA, actorB)
endFunction

;// Callback to do some after-act maintenance or cleanup
event OnPilloryWhip_End(form formActor, int threadId)
    UnRegisterForModEvent("PilloryWhipEnd"+ threadId +"_"+ formActor.GetFormID())
    rffMain rff = rffLib.GetAPI()
    rffThread t = rff.GetThreadById(threadId)
    if t
        actor actorA = t.actorA
        actor actorB = t.actorB
        actorA.RemoveFromFaction(Game.GetFormFromFile(0x00000D64, "RoggVoiceover.esp") as faction) ;// TODO: fix this ugly thing!
        actorA.SetFactionRank(t.rff.RoggFactInThread, 6)
        actorB.SetFactionRank(t.rff.RoggFactInThread, 5)
        Debug.SendAnimationEvent(actorA, "ZazAPPillSolo01")    ;// reset animation while keeping actor in pillory
        t.ResetActorPosition(actorA)
        if !t.actorScriptA.isReleasing ; if the actor isn't being released
            t.actorScriptA.GotoState("IN_POSITION") ; switch back to "sitting" state
            ;// Make actorA (locked in pillory) play a specific idle reflecting his state after being whipped
            Debug.SendAnimationEvent(actorA, t.furnitureInfo.idles[2])
            t.actorScriptA.RegisterForSingleUpdate(20)
        endif
        ;// remove the cane and package we added in the activeMagicEffect script
        t.actorScriptB.RemovePackage(rff.RffPkgWhipping)
        ;// Wait a bit - hoping it would give the game some time to sort out packages before we remove
        ;// the cane, to prevent NPC from sometimes unsheating their default weapon afterwards
        Utility.Wait(0.2)
        actorB.EvaluatePackage() ;// not even this seem to be help with removing weapon unsheathing completely
        actorB.ClearLookAt()
        ;// Final fix for the weapon unsheathing glitch, if everything else failed...
        if actorB.IsWeaponDrawn()
            actorB.SheatheWeapon()
        endif
        ;// Remove the cane we added in magix effect script
        if actorB.GetItemCount(rff.zbfWeaponCane) > 0
            if actorB.IsEquipped(rff.zbfWeaponCane)
                actorB.UnequipItem(rff.zbfWeaponCane)
            endif
            actorB.RemoveItem(rff.zbfWeaponCane, 1, true)
        endif
        t.RequestActorRelease(actorB) ;// release secondary actor from thread
    endif
endEvent

 

 

Posted

This looks easy enough to use (especially with he example script :) ). The real challenge is going to be to find nice animations to go with different furniture

 

1) I think the setup method is fine; other mods seem to use arrays and methods to fill them the same way. It would definitely be nice to be able to control the registration programmatically so things can be made optional

 

2) The _End events contain a lot off stuff that smells like it belongs in the framework rather than the mod. Having the actor remain seated or unequipping stuff should either be done outside the framework with generic functions, or done through very simple, single function calls into the framework with little or no framework specific logic around them. Looking at the script, that may require a few more callbacks though.

Posted

 

2) The _End events contain a lot off stuff that smells like it belongs in the framework rather than the mod. Having the actor remain seated or unequipping stuff should either be done outside the framework with generic functions, or done through very simple, single function calls into the framework with little or no framework specific logic around them. Looking at the script, that may require a few more callbacks though.

 

Well, the _End events will be usually very interaction specific.

They cannot be all written as a few generic functions, unless you want to account for infinite number of things that all modders might need there.

 

Unequipping of stuff handled "outside of framework"? what do you mean? i dont understand, where should it be?

What you see there, is unequipping cane that was added to the actor by the interaction starter script which is part of this extension (in the activeMagicEffect script for that particular interaction), so it makes sense that it gets removed again from a function that is again part of this extension.

 

Take that pillory example - you want to keep the locked actor in pillory, even after the interaction finished, and only let go the other actor, but then other interactions, especially (but not only) those not involving any furniture, will require to let go all actors.

That's simple - we could just make two functions for it, but then what about the post-interaction animation that we might want to play?

...you may want the actor to keep shaking for a while, or lying still breathing heavily, or something like that, depending on what you want and what animations you have available.

So, that would require passing some parameter into that function, plus another one to specify how long the anim/pose should last.

And then maybe AFTER that you want to release the actor, or maybe not - another complication a universal function must handle and we must somehow tell it to do so.

Or what if you want to make one of the actor run away after, or you want to immediately initiate other interaction (maybe foreplay just ended, and now you want to finally do the "deed").

Or maybe there were more than two actors involved - A was fucking B while C was getting blowjob from B - maybe A is done, and you want him leave, while C will want to go around to try some fucking for a change (which would be ideally done by immediately starting new different interaction between B and C).

Maybe you want to add some special package to some actor, to make him do something complicated like running around, picking flowers, and throwing them at the lady ...i dont know,

Maybe you want to do something based on a random chance.

There is an infinite number of possibilities.

 

What i want to say is, that simple interactions will have simple endings, probably having only the t.Release() call, or t.Release("A"/"AB"/"DCBA"/...) to release only specific actors in specific order.

Any other more complicated interactions will need specific code like you see in that example, and i cannot predict what it should be, so i cannot create a universal functions for that.

Posted

I'll have to get some hands on with this before I can see the design patterns :)  So when is it ready? :P

 

I can't really tell - everything always looks like it will be a quick work, and then its days of coding.

But i am very intensively working on it. It should be soon - any more precise estimate and i would be lying :)

I wont be adding any new features from now on, until it is out, so that should help speed things up.

There is only one thing missing now - finishing the rewrite of the actor script, which isn't a small task.

Soon.

Archived

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

  • Recently Browsing   0 members

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