Can a script create multiple objects in a fixed position?

Post » Tue Nov 20, 2012 8:07 am

Okay, this one may take some explanation... I'm an absolute newbie at scripting, so please don't assume I know immediately what you're referring to. I'm good at looking things up, though!

I have a mod that places apple trees in the game. The apples on each tree are not part of the tree mesh; they're vanilla apple objects that I've hand-placed right up close to the branches so that it appears the fruit is growing on the tree when it's really suspended in thin air. This way you can take as many apples as you want from one tree, or you can shoot them out of the tree with a bow.

I've had several people ask me if I'm going to make this mod compatible with Hearthfire's planting system. I'd like to, but I'm not sure it's possible. My original thinking is along the lines of, "at some point in the mechanics, the seed (or whatever you plant) needs to be swapped out and substituted with a tree, which is problematic because in this case "a tree" isn't one object. It's about fifteen -- the tree mesh and each individual apple are all separate items."

So what I'm wondering is whether there's a way to do this at all. I don't want to keep telling people I can't do it just because I don't know enough to be able to.

What I'd need to do is have a way to place a whole bunch of objects -- the tree and the apples -- into the game so that each is at a fixed position relative to the other objects. Is that something possible to do? I'm assuming it's not, but I wanted to ask just in case.

Edit: Wait, let me clarify. I know that I could just plop the tree and apples into the game, disable them, then have the player do something that enables them. But that would mean player wouldn't have a choice about where the trees went. I'm looking for something more along the lines of being able to take your apple seed, drop it somewhere, activate it, and have it turn into a tree with apples. Like how the camping equipment works in myriad mods that allow you to take a pot, place it somewhere and activate it to make a full-size roasting spit appear, which you can then rotate around and higher\lower until it's in the right spot. Except I want to do it with multiple objects that will spring into being at fixed positions.
User avatar
Grace Francis
 
Posts: 3431
Joined: Wed Jul 19, 2006 2:51 pm

Post » Tue Nov 20, 2012 3:41 pm

This may not be what you are after and this may not work, but you can enable/disable objects through a quest. Everytime the quest stage progresses, it disables the previous object and enables the next.
You would still have to manually place the seed, the tree and the apples in the CK.

For example,
Quest Stage 10 - Enables the seed
Quest stage 20 - Disables the seed, Enables a sapling
Quest stage 30 - Disables the sapling, Enables the tree
Quest stage 40 - Enables the apples.

Problems that i can think of, this may work for one tree, but may need multiple quests for multiple trees.
User avatar
Katie Samuel
 
Posts: 3384
Joined: Tue Oct 10, 2006 5:20 am

Post » Tue Nov 20, 2012 12:58 pm

Thanks for helping me, it's appreciated. Unfortunately as I said, enable\disable won't allow players to place the trees wherever they want. :( (You probably didn't see that note all the way at the end, I tend to ramble on in my posts!)
User avatar
Da Missz
 
Posts: 3438
Joined: Fri Mar 30, 2007 4:42 pm

Post » Tue Nov 20, 2012 12:24 pm

Apologies, i didn't see the edit. Nothing to do with rambling, i was in the post for ages before posting.
User avatar
Abi Emily
 
Posts: 3435
Joined: Wed Aug 09, 2006 7:59 am

Post » Tue Nov 20, 2012 1:29 pm

Have a look at this page. Some of the functions might do what you want

http://www.creationkit.com/ObjectReference_Script
User avatar
Harry Leon
 
Posts: 3381
Joined: Tue Jun 12, 2007 3:53 am

Post » Tue Nov 20, 2012 6:03 am

I'll check it out, thank you again!

It's not going to absolutely break my heart if this isn't possible to do, really. I would just feel dishonest if I told people "that isn't possible" when the reality may actually be "I'm not good enough to figure out how to do it," y'know? I'd like to at least look into it and find out one way or another.
User avatar
Kelly Tomlinson
 
Posts: 3503
Joined: Sat Jul 08, 2006 11:57 pm

Post » Tue Nov 20, 2012 12:52 pm

What I'd need to do is have a way to place a whole bunch of objects -- the tree and the apples -- into the game so that each is at a fixed position relative to the other objects. Is that something possible to do?

It absolutely is. I had to do something very similar for Frostfall. And don't feel bad, because this is a non-trivial problem to solve; it took me several days to come up with a working solution.

The problem you are probably encountering is that, you know where all of the apples are supposed to go when the Z rotation of the tree is something that you know. Well, if you rotate the tree, or put the tree somewhere else, how do you know where all the apples go now, when you can only express the apple's position in terms of absolute world X Y Z values, not relative to the tree itself?

First, Make a tree with apples on it exactly the way you want it, with a Z rotation on the tree of 0 (not entirely necessary, but it helps reduce confusion). This is now your "reference tree". All trees after this will be exact copies of this tree.

Next, you need to record the X, Y, and Z positions of every single apple on the tree. This is tedious but you'll need this data later to feed into the script. You will also need the X, Y, and Z position of the tree itself. It does not matter where your reference tree "is", what cell it's in, so on. You just need to know this position data.

Next, you need to record the delta between each apple's position and the position of the tree. So, for an apple of X = 75.0 and a tree of X = 100, you would record an X delta of -25.0. (ApplePos - TreePos, never the other way around). Excel formulas can really make your life easier here: just make a set of cells for the tree's X Y Z, and a set of cells where you input your apple's X Y Z, and have it spit out your delta X Y Z for each apple.

So, you have all of your delta values. You know where each apple is relative to the tree now. Next, you need to write a script to drive what's going to happen. Some event is going to occur which causes your tree to come into existence. Right now we'll leave that ambiguous, it's only important to know that this event happens at some time.

For your application, it probably needs to look something like this:

Spoiler

import utilityMiscObject property AppleObject autoStatic property XMarker auto;Apple object referencesObjectReference myApple1ObjectReference myApple2ObjectReference myApple3ObjectReference myApple4ObjectReference myApple5ObjectReference myApple6ObjectReference myApple7ObjectReference myApple8ObjectReference myApple9ObjectReference myApple10ObjectReference myApple11ObjectReference myApple12ObjectReference myApple13ObjectReference myApple14ObjectReference myApple15;Position data variablesfloat myPosXfloat myPosYfloat myPosZfloat myAngXfloat myAngYfloat myAngZEvent SomeEvent()	Get3DData()	;Retrieve and store the tree's X Y Z data	;We need some arrays to contain the position of our apples.	myApple1_Pos = new float[3]	myApple2_Pos = new float[3]	myApple3_Pos = new float[3]	;....(and so on, all of your apples)	ObjectReference myTempMarker = TreeRef.PlaceAtMe(XMarker)   ;Place an invisible marker at the tree's origin. TreeRef can be replaced with "self" if the tree object itself is running this script (which is most likely the case)	;Apple 1 Setup	myTempMarker.MoveTo(TreeRef, 71.1249, -3.1863, -3.0791)  ;Insert Apple 1's relative X Y Z coordinates here, the deltas that you computed earlier.	myApple1_Pos = GetPosXYZRotationAroundRef(TreeRef, myTempMarker, myAngX, myAngY, myAngZ)  	myTempMarker.SplineTranslateTo(myApple1_Pos[0], myApple1_Pos[1], myApple1_Pos[2], myAngX + 0.0, myAngY + 0.0, myAngZ + 0.0, 1.0, 5000.0)	;Move the temp marker to where the apple is supposed to go. I'm not happy with using SplineTranslateTo here, but I couldn't determine a better way to move the marker, input is appreciated here	wait(0.25) ;Wait for the marker to finish moving, again, this is why I don't like SplineTranslateTo	myApple1 = myTempMarker.PlaceAtMe(AppleObject) ;Place an AppleObject at myTempMarker, and store the reference in myApple1	;Apple 2 Setup	;...same as above, repeat for all apples	;Destroy the placement marker	myTempMarker.Disable()	myTempMarker.Delete()endEventfunction Get3DData()	;Get 3D data	myPosX = TreeRef.GetPositionX()	;Again, replace TreeRef with self if the tree is running the script.	myPosY = TreeRef.GetPositionY()	myPosZ = TreeRef.GetPositionZ()	myAngX = TreeRef.GetAngleX()	myAngY = TreeRef.GetAngleY()	myAngZ = TreeRef.GetAngleZ()endFunctionfloat[] function GetPosXYZRotateAroundRef(ObjectReference akOrigin, ObjectReference akObject, float fAngleX, float fAngleY, float fAngleZ)	;-----------\	;Description \ Author: Chesko	;----------------------------------------------------------------	;Rotates a point (akObject offset from the center of	;rotation (akOrigin) by the supplied degrees fAngleX, fAngleY,	;fAngleZ, and returns the new position of the point.	;-------------\	;Return Values \	;----------------------------------------------------------------	;		fNewPos[0]	=	 The new X position of the point	;		fNewPos[1]	=	 The new Y position of the point	;		fNewPos[2]	=	 The new Z position of the point	;				|1			0			0		|	;Rx(t) =		 |0			cos(t)		-sin(t)	|	;				|0			sin(t)		cos(t)	|	;	;				|cos(t)		0			sin(t)	|	;Ry(t) =		|0			1			0		|	;				|-sin(t)	0			cos(t)	|	;	;				|cos(t)		-sin(t)		0		|	;Rz(t) =		 |sin(t)		cos(t)		0		|	;				|0			0			1		|	;R * v = Rv, where R = rotation matrix, v = column vector of point [ x y z ], Rv = column vector of point after rotation	fAngleX = -(fAngleX)	fAngleY = -(fAngleY)	fAngleZ = -(fAngleZ)	float myOriginPosX = akOrigin.GetPositionX()	float myOriginPosY = akOrigin.GetPositionY()	float myOriginPosZ = akOrigin.GetPositionZ()	float fInitialX = akObject.GetPositionX() - myOriginPosX	float fInitialY = akObject.GetPositionY() - myOriginPosY	float fInitialZ = akObject.GetPositionZ() - myOriginPosZ	float fNewX	float fNewY	float fNewZ	;Objects in Skyrim are rotated in order of Z, Y, X, so we will do that here as well.	;Z-axis rotation matrix	float fVectorX = fInitialX	float fVectorY = fInitialY	float fVectorZ = fInitialZ	fNewX = (fVectorX * cos(fAngleZ)) + (fVectorY * sin(-fAngleZ)) + (fVectorZ * 0)	fNewY = (fVectorX * sin(fAngleZ)) + (fVectorY * cos(fAngleZ)) + (fVectorZ * 0)	fNewZ = (fVectorX * 0) + (fVectorY * 0) + (fVectorZ * 1)		;Y-axis rotation matrix	fVectorX = fNewX	fVectorY = fNewY	fVectorZ = fNewZ	fNewX = (fVectorX * cos(fAngleY)) + (fVectorY * 0) + (fVectorZ * sin(fAngleY))	fNewY = (fVectorX * 0) + (fVectorY * 1) + (fVectorZ * 0)	fNewZ = (fVectorX * sin(-fAngleY)) + (fVectorY * 0) + (fVectorZ * cos(fAngleY))	;X-axis rotation matrix	fVectorX = fNewX	fVectorY = fNewY	fVectorZ = fNewZ		fNewX = (fVectorX * 1) + (fVectorY * 0) + (fVectorZ * 0)	fNewY = (fVectorX * 0) + (fVectorY * cos(fAngleX)) + (fVectorZ * sin(-fAngleX))	fNewZ = (fVectorX * 0) + (fVectorY * sin(fAngleX)) + (fVectorZ * cos(fAngleX))	;Return result	float[] fNewPos = new float[3]	fNewPos[0] = fNewX + myOriginPosX	fNewPos[1] = fNewY + myOriginPosY	fNewPos[2] = fNewZ + myOriginPosZ	return fNewPosendFunction

Take some time to read over that, I know there's a lot to absorb (especially if you're very new to Papyrus scripting, if so my sincerest apologies; I can help you with this as much as you need to understand what's going on and how to finish it).

The main piece of this that really enables this to work is GetPosXYZRotateAroundRef(). You don't really need to read or understand the contents of that function; just know that if you provide it an ObjectReference to serve as an origin and an ObjectReference to serve as the object you want to rotate, it will rotate that object around the origin by the angles that you specify (as if the object were "orbiting" it), and return the object's new world X Y Z coordinates so that you can place the object in the world relative to the object's positioning.

(I need to run to lunch, I will expand on this when I get back, I hope this is enough to point you in the right direction for now)
User avatar
Lilit Ager
 
Posts: 3444
Joined: Thu Nov 23, 2006 9:06 pm

Post » Tue Nov 20, 2012 4:28 pm

Wow, a full rotation matrix. That's a nice piece of code to have given that to write one, one has to guess which of the twelve possible conventions (6 possible axis orderings + mirror symmetry) Bethesda choose and deal with their non-conventiional angles.
I'm going to add a link to this one on some wiki pages.

EDIT: For future readers, you may want to modify the beginning of the code to suit your needs. So here is a short description of the variables that may interest you.
* myOriginPosX/Y/Z describes the origin, the point around which you "want to rotate".
* fAngleX/Y/Z are the rotation angles around the origin.
* fVectorX/Y/Z describes the desired position relatively to the origin, before the rotation is applied.

Now I am a bit surprised by the fact you just used GetAngleX/Y/Z outside of this function to get angleX, angleY, angleZ. So that means that the matrix is designed for Bethesda's conventions, which includes a CW Z angle.

EDIT2: Actually I am going to wait for your opinion before I edit the wiki. Maybe you would like to change it, or maybe you would prefere if we had a page such as "Spatial functions" on the wiki where other could add more examples, etc.
User avatar
Trent Theriot
 
Posts: 3395
Joined: Sat Oct 13, 2007 3:37 am

Post » Tue Nov 20, 2012 9:23 am

Chesko, you're either my new idol, or someone who needs to be murdered and hidden away very quickly before anyone finds out that this IS possible to do, if I'm willing to bang my head against a proverbial tree trunk for the next few weeks to make it work.

Ah, well. I'm a firm believer that with enough coffee, I can accomplish anything. I'll give it a go because when I grow up I want to be a real modder just like you! Today Papyrus, tomorrow... 3ds Max?

You may seriously regret your offer to help me with this, so feel free to back out at any point after I've said or asked something particularly dumb. The good news is that (if I do say so myself) I'm very patient, a quick learner, reasonably intelligent, and willing to look for answers on my own before screaming for help. The bad news is that I only figured out how to modify a script, compile it, and attach it to an object about a week ago...

Edit: I'll get started by creating my reference tree and getting the delta values to plug into a script, then deciding how I want it to be activated so I know what I'm dealing with. That will likely take me the weekend because I've got some RL stuff to deal with. When I've got it that far, I'll see what needs doing from there.
User avatar
Robert Jr
 
Posts: 3447
Joined: Fri Nov 23, 2007 7:49 pm

Post » Tue Nov 20, 2012 4:36 pm

Wow, a full rotation matrix. That's a nice piece of code to have given that to write one, one has to guess which of the twelve possible conventions (6 possible axis orderings + mirror symmetry) Bethesda choose and deal with their non-conventiional angles.
I'm going to add a link to this one on some wiki pages.
Thanks!

Now I am a bit surprised by the fact you just used GetAngleX/Y/Z outside of this function to get angleX, angleY, angleZ. So that means that the matrix is designed for Bethesda's conventions, which includes a CW Z angle.

Yea. To preserve orthogonality I didn't want the caller to have to "know" about the backwards system Beth uses for Z rotation. I suppose you sort-of do since you have to know which way you want to turn, but, since it takes the direct result of a GetAngleBlah in previous lines, that seemed like the easiest-to-understand thing to do.

Actually I am going to wait for your opinion before I edit the wiki. Maybe you would like to change it, or maybe you would prefere if we had a page such as "Spatial functions" on the wiki where other could add more examples, etc.

This function is well-tested, so I'm pretty hesitant to make very many changes to it at this point, and I think the inputs are straight-forward enough. And I think a wiki page for something like that would be pretty neat, since Papyrus can be used to solve some rather complex problems but there isn't a good repository for things like that right now.

Edit: I'll get started by creating my reference tree and getting the delta values to plug into a script, then deciding how I want it to be activated so I know what I'm dealing with. That will likely take me the weekend because I've got some RL stuff to deal with. When I've got it that far, I'll see what needs doing from there.

Sounds great. By all technicality, you could write a script to automate that for you as well, but I don't know if that's something you want to get into right now. If you wrote another script to do it, you would have more autonomy in terms of being able to move things later without having to change a bunch of hard-coded values. Up to you.

Once you get this working, we can start your 500-level Papyrus class and get you to refactor most of the above into a base "FruitTree" script that "AppleTree" will extend... :smile:

The good news is that (if I do say so myself) I'm very patient, a quick learner, reasonably intelligent, and willing to look for answers on my own before screaming for help. The bad news is that I only figured out how to modify a script, compile it, and attach it to an object about a week ago...

I think that's how everybody starts if you don't come from a formal programming background. Spoiler: I don't come from a formal programming background >_> We'll work it out.
User avatar
ladyflames
 
Posts: 3355
Joined: Sat Nov 25, 2006 9:45 am

Post » Tue Nov 20, 2012 3:01 am

I replaced some of the placement code up top with a new function...

ObjectReference function PlaceAtMeRelative(ObjectReference akOrigin, Form akFormToPlace, float fOriginAngleX, float OriginAngleY, float OriginAngleZ, float fRelativePosX, float fRelativePosY, float fRelativePosZ)	ObjectReference myTempMarker = akOrigin.PlaceAtMe(XMarker, fRelativePosX, fRelativePosY, fRelativePosZ)	float[] myNewPos = new float[3]	myNewPos = GetPosXYZRotationAroundRef(akOrigin, myTempMarker, fOriginAngleX, fOriginAngleY, fOriginAngleZ)  	myTempMarker.MoveTo(akOrigin, myNewPos[0], myNewPos[1], myNewPos[2])	ObjectReference myObject = myTempMarker.PlaceAtMe(akBaseObjectToPlace)	myTempMarker.Disable()	myTempMarker.Delete()	return myObjectendFunction

That should work a heck of a lot faster. If the angle of the fruit needs to be changed or randomized or something I could factor that in. I was mainly trying to get away from SplineTranslateTo.

I'm working on a base FruitTree script. Work in progress...
User avatar
Peetay
 
Posts: 3303
Joined: Sun Jul 22, 2007 10:33 am

Post » Tue Nov 20, 2012 5:04 pm

Ok. I have something built that I think will be pretty plug-and-play.

There are two scripts: _CP_FruitTree.psc and _CP_AppleTree.psc. You will attach _CP_AppleTree to your apple tree. It will place the fruit around when the tree itself is created. If this needs to happen at some other time (such as, if the tree is disabled and then later enabled or something), the event will need to be different.

You will need to Fill the following properties in the CK. You can re-name the properties to fit your ObjectReference's names, or you can use the Render Window Target option to select them manually when filling the properties.
AppleTreeReference: Should point to your reference apple tree, wherever that happens to be.
AppleReference01: The first reference apple on the reference tree.
AppleReference02: The second reference apple on the reference tree.
...
AppleReference15: The 15th reference apple on the reference tree.
AppleObject: The actual base object that corresponds to your apple.

XMarker: The static by the same name. Should auto-fill.

_CP_FruitTree.psc
Spoiler
scriptname _CP_FruitTree extends ObjectReferenceStatic property XMarker autofloat property myAngX auto hiddenfloat property myAngY auto hiddenfloat property myAngZ auto hidden;Fruit locationsfloat[] property FruitPos01 auto hiddenfloat[] property FruitPos02 auto hiddenfloat[] property FruitPos03 auto hiddenfloat[] property FruitPos04 auto hiddenfloat[] property FruitPos05 auto hiddenfloat[] property FruitPos06 auto hiddenfloat[] property FruitPos07 auto hiddenfloat[] property FruitPos08 auto hiddenfloat[] property FruitPos09 auto hiddenfloat[] property FruitPos10 auto hiddenfloat[] property FruitPos11 auto hiddenfloat[] property FruitPos12 auto hiddenfloat[] property FruitPos13 auto hiddenfloat[] property FruitPos14 auto hiddenfloat[] property FruitPos15 auto hiddenfloat[] property FruitPos16 auto hiddenfloat[] property FruitPos17 auto hiddenfloat[] property FruitPos18 auto hiddenfloat[] property FruitPos19 auto hiddenfloat[] property FruitPos20 auto hiddenfloat[] property FruitPos21 auto hiddenfloat[] property FruitPos22 auto hiddenfloat[] property FruitPos23 auto hiddenfloat[] property FruitPos24 auto hiddenfloat[] property FruitPos25 auto hiddenfloat[] property FruitPos26 auto hiddenfloat[] property FruitPos27 auto hiddenfloat[] property FruitPos28 auto hiddenfloat[] property FruitPos29 auto hiddenfloat[] property FruitPos30 auto hiddenObjectReference property Fruit01 auto hiddenObjectReference property Fruit02 auto hiddenObjectReference property Fruit03 auto hiddenObjectReference property Fruit04 auto hiddenObjectReference property Fruit05 auto hiddenObjectReference property Fruit06 auto hiddenObjectReference property Fruit07 auto hiddenObjectReference property Fruit08 auto hiddenObjectReference property Fruit09 auto hiddenObjectReference property Fruit10 auto hiddenObjectReference property Fruit11 auto hiddenObjectReference property Fruit12 auto hiddenObjectReference property Fruit13 auto hiddenObjectReference property Fruit14 auto hiddenObjectReference property Fruit15 auto hiddenObjectReference property Fruit16 auto hiddenObjectReference property Fruit17 auto hiddenObjectReference property Fruit18 auto hiddenObjectReference property Fruit19 auto hiddenObjectReference property Fruit20 auto hiddenObjectReference property Fruit21 auto hiddenObjectReference property Fruit22 auto hiddenObjectReference property Fruit23 auto hiddenObjectReference property Fruit24 auto hiddenObjectReference property Fruit25 auto hiddenObjectReference property Fruit26 auto hiddenObjectReference property Fruit27 auto hiddenObjectReference property Fruit28 auto hiddenObjectReference property Fruit29 auto hiddenObjectReference property Fruit30 auto hiddenfunction[] GetRelativePosition(ObjectReference akOrigin, ObjectReference akObject)    float[] myRelativePosition = new float[3]    myRelativePosition[0] = akObject.GetPositionX() - akOrigin.GetPositionX()    myRelativePosition[1] = akObject.GetPositionY() - akOrigin.GetPositionZ()    myRelativePosition[2] = akObject.GetPositionZ() - akOrigin.GetPositionZ()    return myRelativePositionendFunctionfunction GetAngleData(ObjectReference akObjectReference)    myAngX = akObjectReference.GetAngleX()    myAngY = akObjectReference.GetAngleY()    myAngZ = akObjectReference.GetAngleZ()endFunctionfunction CreatePositionArrays()    FruitPos01 = new float[3]    FruitPos02 = new float[3]    FruitPos03 = new float[3]    FruitPos04 = new float[3]    FruitPos05 = new float[3]    FruitPos06 = new float[3]    FruitPos07 = new float[3]    FruitPos08 = new float[3]    FruitPos09 = new float[3]    FruitPos10 = new float[3]    FruitPos11 = new float[3]    FruitPos12 = new float[3]    FruitPos13 = new float[3]    FruitPos14 = new float[3]    FruitPos15 = new float[3]    FruitPos16 = new float[3]    FruitPos17 = new float[3]    FruitPos18 = new float[3]    FruitPos19 = new float[3]    FruitPos20 = new float[3]    FruitPos21 = new float[3]    FruitPos22 = new float[3]    FruitPos23 = new float[3]    FruitPos24 = new float[3]    FruitPos25 = new float[3]    FruitPos26 = new float[3]    FruitPos27 = new float[3]    FruitPos28 = new float[3]    FruitPos29 = new float[3]    FruitPos30 = new float[3]endFunctionObjectReference function PlaceAtMeRelative(ObjectReference akOrigin, Form akFormToPlace, float fOriginAngleX, float OriginAngleY, float OriginAngleZ, float fRelativePosX, float fRelativePosY, float fRelativePosZ)    ObjectReference myTempMarker = akOrigin.PlaceAtMe(XMarker, fRelativePosX, fRelativePosY, fRelativePosZ)    float[] myNewPos = new float[3]    myNewPos = GetPosXYZRotationAroundRef(akOrigin, myTempMarker, fOriginAngleX, fOriginAngleY, fOriginAngleZ)      myTempMarker.MoveTo(akOrigin, myNewPos[0], myNewPos[1], myNewPos[2])    ObjectReference myObject = myTempMarker.PlaceAtMe(akBaseObjectToPlace)    myTempMarker.Disable()    myTempMarker.Delete()    return myObjectendFunctionfloat[] function GetPosXYZRotateAroundRef(ObjectReference akOrigin, ObjectReference akObject, float fAngleX, float fAngleY, float fAngleZ)    ;-----------\    ;Description \ Author: Chesko    ;----------------------------------------------------------------    ;Rotates a point (akObject offset from the center of    ;rotation (akOrigin) by the supplied degrees fAngleX, fAngleY,    ;fAngleZ, and returns the new position of the point.    ;-------------\    ;Return Values \    ;----------------------------------------------------------------    ;        fNewPos[0]    =     The new X position of the point    ;        fNewPos[1]    =     The new Y position of the point    ;        fNewPos[2]    =     The new Z position of the point    ;                |1            0            0        |    ;Rx(t) =         |0            cos(t)        -sin(t)    |    ;                |0            sin(t)        cos(t)    |    ;    ;                |cos(t)        0            sin(t)    |    ;Ry(t) =        |0            1            0        |    ;                |-sin(t)    0            cos(t)    |    ;    ;                |cos(t)        -sin(t)        0        |    ;Rz(t) =         |sin(t)        cos(t)        0        |    ;                |0            0            1        |    ;R * v = Rv, where R = rotation matrix, v = column vector of point [ x y z ], Rv = column vector of point after rotation    fAngleX = -(fAngleX)    fAngleY = -(fAngleY)    fAngleZ = -(fAngleZ)    float myOriginPosX = akOrigin.GetPositionX()    float myOriginPosY = akOrigin.GetPositionY()    float myOriginPosZ = akOrigin.GetPositionZ()    float fInitialX = akObject.GetPositionX() - myOriginPosX    float fInitialY = akObject.GetPositionY() - myOriginPosY    float fInitialZ = akObject.GetPositionZ() - myOriginPosZ    float fNewX    float fNewY    float fNewZ    ;Objects in Skyrim are rotated in order of Z, Y, X, so we will do that here as well.    ;Z-axis rotation matrix    float fVectorX = fInitialX    float fVectorY = fInitialY    float fVectorZ = fInitialZ    fNewX = (fVectorX * cos(fAngleZ)) + (fVectorY * sin(-fAngleZ)) + (fVectorZ * 0)    fNewY = (fVectorX * sin(fAngleZ)) + (fVectorY * cos(fAngleZ)) + (fVectorZ * 0)    fNewZ = (fVectorX * 0) + (fVectorY * 0) + (fVectorZ * 1)        ;Y-axis rotation matrix    fVectorX = fNewX    fVectorY = fNewY    fVectorZ = fNewZ    fNewX = (fVectorX * cos(fAngleY)) + (fVectorY * 0) + (fVectorZ * sin(fAngleY))    fNewY = (fVectorX * 0) + (fVectorY * 1) + (fVectorZ * 0)    fNewZ = (fVectorX * sin(-fAngleY)) + (fVectorY * 0) + (fVectorZ * cos(fAngleY))    ;X-axis rotation matrix    fVectorX = fNewX    fVectorY = fNewY    fVectorZ = fNewZ        fNewX = (fVectorX * 1) + (fVectorY * 0) + (fVectorZ * 0)    fNewY = (fVectorX * 0) + (fVectorY * cos(fAngleX)) + (fVectorZ * sin(-fAngleX))    fNewZ = (fVectorX * 0) + (fVectorY * sin(fAngleX)) + (fVectorZ * cos(fAngleX))    ;Return result    float[] fNewPos = new float[3]    fNewPos[0] = fNewX + myOriginPosX    fNewPos[1] = fNewY + myOriginPosY    fNewPos[2] = fNewZ + myOriginPosZ    return fNewPosendFunction

_CP_AppleTree.psc
Spoiler
scriptname _CP_AppleTree extends _CP_FruitTreeObjectReference property AppleTreeReference auto{The tree to reference.}MiscObject property AppleObject auto{The fruit for this tree.};Fruit references on the reference treeObjectReference property AppleReference01 autoObjectReference property AppleReference02 autoObjectReference property AppleReference03 autoObjectReference property AppleReference04 autoObjectReference property AppleReference05 autoObjectReference property AppleReference06 autoObjectReference property AppleReference07 autoObjectReference property AppleReference08 autoObjectReference property AppleReference09 autoObjectReference property AppleReference10 autoObjectReference property AppleReference11 autoObjectReference property AppleReference12 autoObjectReference property AppleReference13 autoObjectReference property AppleReference14 autoObjectReference property AppleReference15 autoEvent OnInit()    CreatePositionArrays()    GetAngleData(self)    GetRelativePositions()    Fruit01 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos01[0], FruitPos01[1], FruitPos01[2])    Fruit02 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos02[0], FruitPos02[1], FruitPos02[2])    Fruit03 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos03[0], FruitPos03[1], FruitPos03[2])    Fruit04 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos04[0], FruitPos04[1], FruitPos04[2])    Fruit05 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos05[0], FruitPos05[1], FruitPos05[2])    Fruit06 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos06[0], FruitPos06[1], FruitPos06[2])    Fruit07 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos07[0], FruitPos07[1], FruitPos07[2])    Fruit08 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos08[0], FruitPos08[1], FruitPos08[2])    Fruit09 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos09[0], FruitPos09[1], FruitPos09[2])    Fruit10 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos10[0], FruitPos10[1], FruitPos10[2])    Fruit11 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos11[0], FruitPos11[1], FruitPos11[2])    Fruit12 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos12[0], FruitPos12[1], FruitPos12[2])    Fruit13 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos13[0], FruitPos13[1], FruitPos13[2])    Fruit14 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos14[0], FruitPos14[1], FruitPos14[2])    Fruit15 = PlaceAtMeRelative(AppleTreeReference, AppleObject, myAngX, myAngY, myAngZ, FruitPos15[0], FruitPos15[1], FruitPos15[2])    endEventfunction GetRelativePositions()    FruitPos01 = GetRelativePosition(AppleTreeReference, AppleReference01)    FruitPos02 = GetRelativePosition(AppleTreeReference, AppleReference02)    FruitPos03 = GetRelativePosition(AppleTreeReference, AppleReference03)    FruitPos04 = GetRelativePosition(AppleTreeReference, AppleReference04)    FruitPos05 = GetRelativePosition(AppleTreeReference, AppleReference05)    FruitPos06 = GetRelativePosition(AppleTreeReference, AppleReference06)    FruitPos07 = GetRelativePosition(AppleTreeReference, AppleReference07)    FruitPos08 = GetRelativePosition(AppleTreeReference, AppleReference08)    FruitPos09 = GetRelativePosition(AppleTreeReference, AppleReference09)    FruitPos10 = GetRelativePosition(AppleTreeReference, AppleReference10)    FruitPos11 = GetRelativePosition(AppleTreeReference, AppleReference11)    FruitPos12 = GetRelativePosition(AppleTreeReference, AppleReference12)    FruitPos13 = GetRelativePosition(AppleTreeReference, AppleReference13)    FruitPos14 = GetRelativePosition(AppleTreeReference, AppleReference14)    FruitPos15 = GetRelativePosition(AppleTreeReference, AppleReference15)endFunction

I am pretty sure this will work. I have not tried to compile this yet. Let me know if there are errors.

Using this system, it should be evident that you can create other scripts that extend _CP_FruitTree. Maybe you'd like _CP_OrangeTree, and you'd just need to follow the template provided by _CP_AppleTree to set it up. Given that anything could technically be a "fruit", you could do something crazy, like make a tree that has daggers as fruit. The maximum number of fruit coded here is 30, but more could be added in easily.

Additional tree behavior (such as handling growth, fruit creation, randomized chance of different kinds of fruit, so on) could also be added in.
User avatar
Christine Pane
 
Posts: 3306
Joined: Mon Apr 23, 2007 2:14 am

Post » Tue Nov 20, 2012 3:30 am

Yea. To preserve orthogonality I didn't want the caller to have to "know" about the backwards system Beth uses for Z rotation. I suppose you sort-of do since you have to know which way you want to turn, but, since it takes the direct result of a GetAngleBlah in previous lines, that seemed like the easiest-to-understand thing to do.
Thank you for clarifying it. I guess it was indeed the best possible choice.

This function is well-tested, so I'm pretty hesitant to make very many changes to it at this point, and I think the inputs are straight-forward enough. And I think a wiki page for something like that would be pretty neat, since Papyrus can be used to solve some rather complex problems but there isn't a good repository for things like that right now.
Fine for me, I just created http://www.creationkit.com/Spatial_functions_and_snippets and added links to it in the pages for SetPosition, TranslateTo and SplineTranslateTo.
User avatar
Motionsharp
 
Posts: 3437
Joined: Sun Aug 06, 2006 1:33 am

Post » Tue Nov 20, 2012 6:27 am

I really don't know what to say. Thank you, Chesko. You're incredible.

I haven't had a chance to try it out yet, I'm elbow deep in flour and German chocolate today (the aforementioned "RL stuff" I had to take care of) but I'll report back the very first moment I have free.
User avatar
Nany Smith
 
Posts: 3419
Joined: Sat Mar 17, 2007 5:36 pm

Post » Tue Nov 20, 2012 5:49 pm

Instead of pre-calculation the positions of the apples, couldn't you link the template tree to the template apples and then using GetLInkedRef, read in and the set positions?

For an apple tree, there would have to be something like this in a script (maybe the tree can be an activator?):
Spoiler

ObjectReference[] arAppleRefs = new ObjectReference[10]Event SomeEvent()	int iMaxLinkedRef = 10 ; max apples	Keyword kLinkedKeyword = None	ObjectReference rTmpParentRef = rTemplateTreeRef ; the template tree linked to the apples	ObjectReference rNewParentRef = Self ; the position the new apples will be placed at	int iIndex = (rTmpParentRef.CountLinkedRefChain(kLinkedKeyword, iMaxLinkedRef) - 1)	ObjectReference rTmpObjRef = rTmpParentRef.GetNthLinkedRef(iIndex) as ObjectReference ; template obj ref	While (iIndex > 0) ; skip the first link		If (rTmpObjRef)			ObjectReference rNewObjRef = rNewParentRef.PlaceAtMe(rTmpObjRef.GetBaseObject()) ; new obj ref			If (rNewObjRef)			SetRelativePosition(rTmpParentRef, rTmpObjRef, rNewParentRef, rNewObjRef)			; Now rotate it			arAppleRefs[(iIndex - 1)] = rNewObjRef			EndIf		EndIf		iIndex -= 1 ; dec		rTmpObjRef = rTmpParentRef.GetNthLinkedRef(iIndex) as ObjectReference ; next obj ref	EndWhileEndEventFunction SetRelativePosition(ObjectReference prTmpParentRef, ObjectReference prTmpObjRef, ObjectReference prNewParentRef, ObjectReference prNewObjRef)     prNewObjRef.MoveTo(prNewParentRef)		int iX = 0	int iY = 1	int iZ = 2		float[] afTmpParentPos = new float[3]	float[] afTmpObjPos = new float[3]	float[] afNewParentPos = new float[3]	float[] afNewObjPos = new float[3]		afTmpParentPos[iX] = prTmpParentRef.GetPositionX()	afTmpParentPos[iY] = prTmpParentRef.GetPositionY()	afTmpParentPos[iZ] = prTmpParentRef.GetPositionZ()		afTmpObjPos[iX] = prTmpObjRef.GetPositionX()	afTmpObjPos[iY] = prTmpObjRef.GetPositionY()	afTmpObjPos[iZ] = prTmpObjRef.GetPositionZ()		afNewParentPos[iX] = prNewParentRef.GetPositionX()	afNewParentPos[iY] = prNewParentRef.GetPositionY()	afNewParentPos[iZ] = prNewParentRef.GetPositionZ()		afNewObjPos[iX] = afNewParentPos[iX]	afNewObjPos[iY] = afNewParentPos[iY]	afNewObjPos[iZ] = afNewParentPos[iZ]		If (afTmpParentPos[iX] > afTmpObjPos[iX]) ; parent left of obj		afNewObjPos[iX] = (afNewObjPos[iX] - (afTmpParentPos[iX] - afTmpObjPos[iX]))	ElseIf (afTmpParentPos[iX] < afTmpObjPos[iX]) ; parent right of obj		afNewObjPos[iX] = (afNewObjPos[iX] + (afTmpObjPos[iX] - afTmpParentPos[iX]))	EndIf		If (afTmpParentPos[iY] > afTmpObjPos[iY]) ; parent in front of obj		afNewObjPos[iY] = (afNewObjPos[iY] - (afTmpParentPos[iY] - afTmpObjPos[iY])	ElseIf (afTmpParentPos[iY] < afTmpObjPos[iY]) ; parent behind obj		afNewObjPos[iY] = (afNewObjPos[iY] + (afTmpObjPos[iY] - afTmpParentPos[iY])	EndIf		If (afTmpParentPos[iZ] > afTmpObjPos[iZ]) ; parent above obj		afNewObjPos[iZ] = (afNewObjPos[iZ] - (afTmpParentPos[iZ] - afTmpObjPos[iZ])	ElseIf (afTmpParentPos[iZ] < afTmpObjPos[iZ]) ; parent below obj		afNewObjPos[iZ] = (afNewObjPos[iZ] + (afTmpObjPos[iZ] - afTmpParentPos[iZ])	EndIf		prNewObjRef.SetPosition(afNewObjPos[iX], afNewObjPos[iY], afNewObjPos[iZ])	EndFunction
Maybe there's a better way to get and set the relative position?
User avatar
Leonie Connor
 
Posts: 3434
Joined: Mon Mar 12, 2007 4:18 pm

Post » Tue Nov 20, 2012 2:59 am

Okay, finally finished up all my RL commitments. I'l be working on the trees tonight, so tune in later for the inevitable slew of "help!" posts as I try to figure out what to replace in each script, which objects to place the scripts on, and what an Xmarker is. This should be fun.

Well... no, strike that. I do actually know what a marker IS (and even how they work, go me!), I just haven't looked at Chesko's work closely enough yet to determine why there will evidently be an Xmarker somewhere in the process, where to put it, and what it shall mark.

Now taking wagers on how much of this I'll be able to figure out on my own before I resort to slinking back here, head hung in shame, and asking something that is so intrinsicly obvious to everyone else that no one thought to mention it.
User avatar
A Boy called Marilyn
 
Posts: 3391
Joined: Sat May 26, 2007 7:17 am

Post » Tue Nov 20, 2012 9:03 am

All right. I figure I may as well document what I'm doing as I go along, so if I've done anything dumb, it'll be fairly easy to spot what went wrong.

Having looked at both scripts, I *think* how it works is that the CP_Apple_Tree script goes onto the object that is created when you activate your seed or whatever it's going to be. The Fruit_Tree script... uh, hmm. Doesn't get attached to anything, just hangs out in the void to be called by the other script, do its calculations, and return the right values for the other script to use?

If anyone is laughing, kindly laugh quietly. I'm trying to work this out without admitting defeat.

Assuming the above theories are right, what I need to do now is first create my reference tree, and my seed, then see about putting together a script for the seed that will create the tree when you activate it. Which for me means scouring the wiki for a code snippet that does something on activation so I can figure out how to do it.

Edit: Removed first example of me being an idiot. Okay, off we go.

At some point I need to console up some gold and purchase a lovely plot of Hearthfire land for TestGuy to own, so I can mess with the planting mechanics, as I suspect that it works by opening a menu and selecting a seed rather than physically activating the seed. If that's true, I need to find out how to add new plantables to the list of what it'll recognize. And hope that there's an easy way to tack a script onto the newly created plants that doesn't involve getting elbow-deep into the Hearthfire scripts.

If there isn't, it's going to be much easier to bypass the Hearthfire mechanics altogether and just let players plant trees wherever the hell they want to. HF only allows for planting on particular squares of fertile soil, I *think*. If I end up doing it that way I'll probably make a quest or something to prepare the seeds for planting, or limit the number of seeds, otherwise I just know somebody will plant 500 trees in a three cell area and then scream at me when their game explodes.
User avatar
NEGRO
 
Posts: 3398
Joined: Sat Sep 01, 2007 12:14 am

Post » Tue Nov 20, 2012 6:56 am

All right, a few unintended interruptions later, my quest to create plantable trees continues.

I was able to check out Hearthfire, finally. Seeing the mechanics in action confirmed my suspicion that I'm going to have to bypass the Hearthfire system altogether instead of integrating with it. First off the planting menu system is awkward and doesn't involve seeds. And it's very difficult to tell if you've already planted something in a particular soil patch. Not to mention the enclosed garden is too small a space to plant trees in; they'd end up clipping with both the house and each other.

It's probably a good thing altogether, since I wasn't looking forward to messing with the Hearthfire scripts. I like the idea of a seed-based planting system better. The issue now is that I'll need to implement a way to limit the number of seeds you can plant, otherwise we're right back to somebody inevitably planting 50 trees in one cell and screaming at me when their entire game catches fire and explodes. I'm sorry, but I just don't feel comfortable creating a mod that could easily crash the user's game just by doing exactly what the mod is designed to do, and simply trusting that everyone who downloads it will have read the warnings not to cover the entire landscape with apples.

The best way I can think of to do it is with the original idea to build some kind of quest into it. Either a quest to get ahold of the proper seeds, or to properly prepare a seed to be planted. I was thinking that would also solve the realism dilemma of planting a seed and having a tree grow and produce fruit within a very short timespan. It's a magic seed, duh.

For now I'll worry about creating a seed object and making it turn into a tree, and deal with the method of getting seeds into players' hands once I've actually got the seeds to work...
User avatar
Jonathan Braz
 
Posts: 3459
Joined: Wed Aug 22, 2007 10:29 pm


Return to V - Skyrim