Bending Ropes     (Moderator: Quib Mask) Previous Topic | Next Topic
Page 1 of 2 Goto page 1, 2  Next
Post 13-Aug-2008 07:05        

I, uh, made this a long time ago, and thought I uploaded it, but I couldn't find it anywhere on the hub. Cleaning up the version I dug up and will upload it eventually. In optimizing some of the piece handling I've hit floating point precision issues quite a few times, which I've never had trouble with before; silly JK.

The numbers at the top of the image say 32 and are the number of pulse messages that fire per second. It's also important to note that a multi-hundred-piece rope doesn't cause a dip in framerate, at least in Canyon Oasis; if it did the rope processing load would automatically throttle back a little. That number is calculated automatically and used to scale various values (like forces for pushing the player if the rope is used with a grappling hook).

Also, the entire rope is handled by the hook projectile's class COG and properly handles an unlimited number of hooks and corresponding ropes (thing limit withstanding). Error handling for hitting the thing limit is quite acceptable. Thing destruction uses SetLifeLeft() instead of DestroyThing() for safety's sake. The code SHOULD be multiplayer compatible if a custom template (via static.jkl) is used for the rope pieces, but I haven't tested it. I've been using +force_ltpeice for simplicity's sake (all it takes is 00_smoketrail.cog to test, well, and a Rail Detonator...).

The main thing I'm working on is fun grappling physics (which is tougher than you might think when you need the player reeling in to the bend points rather than the destination). For example, I can have the player just fly right along the rope path like a zip line, but it gives you little interaction with the trip and in my opinion swinging is the best part of JK hooks. When bend points are over a cliff edge, reeling up to the edge and getting up on to the ledge can be a pain, and it shouldn't be; that's what I'm working on. I also have a mechanism for a maximum rope length but am still trying to decide on a best way to handle keeping the player at the end of the rope.

Post 13-Aug-2008 23:51        

Perhaps just move the player in the direction of the rope at the end of the 'chain'?
Post 13-Aug-2008 23:58        

So is this a grapling hook mod? It sure looks cool. I can't wait until I can add it to my mods, or atleast play around with it.
Site Admin
Post 14-Aug-2008 01:43        

Sounds and looks cool.

I should be able to help.

Don't forget my work on a *swinging max-length* grapple mod in my Tidbits project. (Yes, it works in MP; several of us used it recently in a Dralloc CTF game. It was a bit awkward to use, but it worked.)

Post 14-Aug-2008 07:58        

Descent Pilot, I've tried that as well as moving the player towards the second and third to last pieces of chain but it just hasn't proven to be enjoyable. It feels more like you're just getting dragged along for the ride. I've also tried having the player teleport to each piece of chain, which is pretty fun when the rope has been snaked all over the place, but it doesn't make for a grappling experience that's particularly useful. I'm toying around with it though and am confident I'll come up with something good. Maybe just getting stronger player movement input will improve things...

Sith Lord, technically it's primarily code to make a rope that turns corners but, in my mind, ropes and grappling hooks go hand in hand, and I enjoy nothing more in JK than a good grappling hook. Soo... long answer, sorta, short answer, yes.

Zeq, I've definitely looked at your hooks. Mainly, I've really wanted to make a hook that doesn't turn off gravity for the player; one that takes gravity and the pulse rate into account and just works. Unfortunately, it never works as fluidly as I want it to and I'll probably just have to fall back on the old tricks. Time will tell though, I've actually had a few tests work acceptably with gravity still on but not consistently and having to sort've fight with the rope to climb up over a ledge gets frustrating really quickly.

I've also been fighting with the process of generating the rope while the hook is still in flight; things get kinda dicey if you get out of line of sight before the hook has stopped and the rope has finished generating. If the player (or hook, technically) teleports, that can really confuse the rope as well. Basically, the rope starts off knowing the full path between the hook and player and just wraps around objects as necessary, it doesn't start off blind and find a path between them on its own.

These aren't new problems mind you, this was stuff I wrestled with a few years ago when I started on this and having to re-figure out how I was doing things has been half of the work. I also have been amazed at some of the things that have been hard freezing JK. Would you believe a VectorLen() or VectorDist() used on a particular vector has been consistently locking things up for me? It's been dumbfounding.

Something to think about while I get the COG cleaned up enough to upload: The hook's userdata stores a thing number to a ''data storage thing'' (which is hidden); the data storage thing's userdata stores a thing number for the first piece of the chain (and each subsequent piece's userdata stores the next piece in the chain's thing number); the data storage thing's position vector is used to store three values that are manipulated constantly (X is the current chain piece being worked on, Y is the number of chain pieces and Z is the thing number of the current bend point in the rope).

Post 14-Aug-2008 14:42        

Creating a dummy thing and manipulating its position to store values instead of a location, brilliant! Cheers to you there.

Having each piece of rope's user data point to the next piece is brilliant too, but not as unorthodox as your awesome dummy thingy.

Zeq says his swinging hook was awkward to use, but I found the only thing awkward about it was having to hold down a button to be pulled up the rope. I suggested that holding down the fire grapple button instead of tapping it should auto-pull you up the rope.
Site Admin
Post 14-Aug-2008 15:29        

I tried to use JK's built-in gravity, but I also could not get it to work consistently. Then I found in the exe where the gravity is used, and that's why my latest grapple mod uses the code it does for gravity. In other words, I used cog to replace the built-in gravity calculation, and it is consistent with what the engine uses.

As for holding down a hotkey for going up (shortening) a rope, I think that holding down a key simulates the "real-world" awkwardness of using a grapple. However, this is a game and the grapple is high-tech, so having a "auto-pull" by tapping the hotkey would be nice. (I think Sith Lord got the "holding down" and the "tapping" backwards.)
Post 16-Aug-2008 03:24        

I'm a fan of the press-and-hold style hook, probably with forward adding extra lift and down letting you resist the pull. I'll likely wind up basing my gravity code directly off of yours Zeq; I'd seen it before and was curious as to why you used the multiplier you did (it was a mix of looking in the executable and a little bit of fine-tuning by feel, right?).

Anyhow, a new teaser image is up. I was shocked when the rope actually stopped the moving sky lift dealie in Asylum of Mortality. Humorously, the wooden planks kept right on moving and flew away. I'm trying to pin down a very rare flaw that's causing JK to freeze; I can go hours without a freeze and then one will come out of nowhere. Thanks to the added logging to file ability (of JKUP) and a couple Print()s in the only two places that an infinite loop could crop up (but shouldn't be able to ) I hope to track the problem down. I've hit some freezing errors when working with vectors though and am praying that's not what this rare issue is.

Once I get this freezing error solved I'll upload a rope example COG. It won't involve any grappling; it'll simply make the secondary fire of the Rail Detonator have a rope (and an unlimited timer). I figure that'll be good enough while I get some good grappling nailed down.

Post 19-Aug-2008 02:40        

Alpha preview time! Put the COG in a cog subfolder of your Resource folder, throw it in a GOB yourself, or in a cog subfolder of a -path folder. Pull out a Rail Detonator and use secondary fire.

No grappling effect yet; I finally got rid of any freezing and wanted to give people a chance to try it out. This is a semi-stripped down version: I took out debugging messages, grappling related code, raised the rope piece limit and a couple path-finding attempts. It's also slightly unoptimized in regards to functions; many if() statements are repeated rather than clumping some of the checks for better performance. This version actually has a little performance hit due to the ''Remove clumps'' and ''Move piece to middle'' sections (which I've just started working on) but only once the rope hits 100+ pieces. If you comment that section out, a rope consisting of 200+ pieces should run just dandy.

You'll notice a few things I'm working on I'm sure... the stupid pieces don't always look exactly at the next piece in the rope if you're moving quickly; I'm not sure why, they used to as far as I recall. Also, if you're moving away from the end of the rope quickly, sometimes new rope doesn't generate quickly enough.

Theorhetically you could still hit an infinite loop but it would require another COG deleting pieces out of the middle of the chain AND a userdata and thing number would have to match up in a way to cause a loop. Basically it's not gonna happen; working on a safety mechanism just in case it ever does though.

Also, good luck sifting through the code. I recommend trying it out in JK before cracking it open in Notepad.


P.S. - Don't use it in multiplayer yet.
Site Admin
Post 22-Aug-2008 04:53        

I could not resist playing around with the rope cog and using it with my grapple mod. The current result is in my Tidbits project.

I edited Quib Mask's rope cog to send the bend position (closest to player) to my grapple_shoot cog. I edited my grapple_shoot cog to use the bend position in the calculations instead of always using the hook position. Fortunately, after understanding enough of the rope cog, it was straightforward to make the changes to both cogs.

Post 22-Aug-2008 11:47        

Quib, this would make a sweet hack...
Post 22-Aug-2008 18:19        

Zeq, I'm not too surprised you already put it to use. =D Trying your new hook version, the main problems I encountered were rope-length related (when swinging under an overhang for example). It seemed like the rope length you have control over versus your current swing point were fighting a little. Also, the rope drags quite a bit behind the player, though that's my fault (and largely a limitation of JK), not yours. Try it with slow motion on and watch how much tighter the rope stays.

Triscuit, have you looked at the code? It's made up of a jillion verbs! I would love to make something like this pass checksum, but there's a couple limitations: the first being even if I went with a normal COG instead of a thing's class COG and so didn't need such a silly way to store variables (if anyone's curious, thing thrust also provides another good set of 3 storage variables) it would still consume a ton of checksum units (or whatever you want to call them) and probably have to span multiple COGs, and second, it would either be client-side only so no one else would see the rope/chain or it would be sync'd but cause such insane network traffic that it would probably lag games out. Though, powerup class COGs do contain a fair amount of usable code and exist nearly universally (due to the Force Pull powerup templates), so maybe they'd have some potential.

I've got rope generation slighty snappier on the player's end, but I found turning the rope around (so what is currently the hook end of the rope is at the player and vice versa) is better at generating new rope as the player moves about. That said, what would truly improve rope generation would be SetThingPosEx() and GetSectorAtPos().

Doing some code optimizations primarily revolving around the Rebend operation (the initial Bend calculation works pretty efficiently) and think I've figured out something good. I aim to improve the anti-clumping and piece moving code as well eventually. Also, the code seems to function just fine in multiplayer, though I've only tested with two people. The main problem is the rope is generated independently on each client and if they wind up making different rope paths, one client can hit the thing limit far before the other. Multiplayer functionality may be improved slightly by using a static.jkl template for the rope pieces rather than +force_ltpeice.

I also made a simple chain 3do so new screenshots will likely feature it.

Site Admin
Post 22-Aug-2008 21:43        

Quib Mask, would you post a description of the algorithm used in the pulse? I had a difficult time figuring out most of it, and still haven't looked at parts of it. It also looked like there was some redundancy in the bend code. For example, checking HasLos for next link, then the next one after that, even though that second check would be checked in the next iteration of the loop (or something like that). It just didn't look quite right to me (and I am an active professional software developer dealing with this kind of stuff often).

Even though it is a class cog, you can use symbols for much of that verb nonsense in the pulse. A pulse (or any other handler) will not be interrupted; the cog will not run any other handlers until the pulse is done (except when Sleep verb is used). So, you can set initial values for symbols at the start of the pulse and change them within the pulse, like what you have commented out as "Reference" near the start of the pulse.

Note that thing indexes start at 0, and that the player thing is not always at thing index 0. FireProjectile() returns -1 when it can't create a thing, so I recommend using -1 instead of 0 for all the places where you have thing index 0 in the code. This would also allow you to remove such unneeded code such as this, methinks:
if((GetThingTemplate(GetThingUserData(GetSenderRef())) != rope_tpl) || (GetThingParent(GetThingUserData(GetSenderRef())) != GetThingParent(GetSenderRef())))

That code could be changed into:
if (GetThingUserData(GetSenderRef()) == -1)

I have several other code suggestions, but I am tired of typing this out. Perhaps, you would be willing to chat (say in IRC room or IM) about the code. Or, I could modify the code to show how I think it should look, and you could then see what I mean, more easily.

Why do you say that MP functionality might be improved using a static.jkl template? If you mean because of FireProjectile, then you gain nothing. I and Hell Raiser have tested this, and I have looked at the actual jk.exe code--FireProjectile ALWAYS sends a network message to all other players, no matter what cog flags or which cog it is used in or what template is used. I have even found the one-byte assembly instruction code which causes the problem of not creating a static.jkl thing on other computers.

Post 22-Aug-2008 23:26        

I thought the static.jkl templates were causing no traffic, my mistake.

I don't even remember why I was using the painful faux-variables, that framework was something I barfed up a few years ago and I don't recall exactly why now. My guess is I was having issues with the rope and suspected it was different iterations of the pulse fighting over shared variables.

I know the player isn't always 0, but UserData always defaults to 0 for a thing so that's what I use as the null value. I'm assuming something permanent (the host or a piece of level architecture, etc.) will always fill thing 0 and if a rope piece ends up as zero it should wind up ignored and a new piece created anyhow.

I used GetThingTemplate because it seems to guarantee a solid check if the right thing still exists at that number. For some reason I never trusted checking a FireProjectile() return to -1; likely due to some newbie error I made 10 years ago and just stuck to the, uh, superstition that it wasn't reliable.

Anyhow, let's see if I can break down how it all works.

A few abbreviations:
hook = the +raildet2 we're working with
player = the thing that fired hook
X = the current piece of rope
XX = the child of the current piece of rope
XXX = the child of XX
Z = the current bend point
rope_len = the length of one piece of rope

Also, all pieces of rope are FireProjectile()d from the hook so if the hook is destroyed, they all lose their parent assignment.

: start of pulse
- set new thing pulse rate based on the automatically calculated pulse messages per second
- if data storage thing doesn't exist create it and the first piece of rope
- if first piece of rope doesn't exist create it
- if first piece of rope is farther from the hook than the length of a piece of rope, make a new first piece of rope and set its UserData to the old first piece of rope
- prepare variables, X and Z equal the first piece of rope
: start of rope management
- while X is a valid piece of rope
-- max piece count check (and hook realignment if attached for the first time)
-- clump removal
--- if X is within rope_len distance of XXX, remove XX (if it's not the current bend point, Z)
-- move piece if rope is stuck/too split up
--- if XX isn't moving or doesn't have LOS to XXX, and XX is farther than 0.75 rope_len from X, and XXX is farther than 1.5 rope_len from XX, teleport XXX to XX
-- find the bend point
--- if X equals Z
---- if XX isn't valid, Z equals player
---- Z equals XX
----- while X has LOS to Z, increment Z to the child of Z until X loses LOS with the child of Z, which makes that Z the new bend point
------ if Z ever becomes invalid, Z equals player
------ however, if X has LOS to the child of Z (the new bend point), continue on with bend point finding; this allows the rope to unbend when you peel it back away from a corner

-- rebend
--- if XX equals Z, check LOS between XX and X, if true, goto the start of the bend procedure; the allows the rope to bend closer to a physical corner, 1 piece per pulse, until it's wrapped as tightly to a wall as possible
-- set facing of X to look at XX (unless it's the last piece, in which case make it look at player)
-- check the distance from X to player; if closer than rope_len, remove XX and any children of XX, otherwise:
--- if XX isn't valid, create a new XX on the end of the rope, setting X's UserData to this new XX, otherwise:
---- make XX start moving correctly into place based on X's position and LVec
-- by here, X is set to XX and the rope management section repeats

That's everything. If you want to see what effect the Unbend and Rebend sections have, they're safe to comment out (the same goes for clump removal and moving a stuck piece). Breaking the piece removal section can cause infinite loops in the Bend section (mostly the Unbend section).

So, in other words, the bend code works like this:
If X equals Z, check down each piece of the rope until you lose LOS with the child of a piece; that's the new bend point (the piece, not the child of the piece).
After finding Z, if XX equals Z and XX has LOS to X, restart the bend procedure from X to find the next bend point. This lets X move into position with the rest of the rope, rather than remaining stiff.
If the piece after a bend point has LOS to the piece before a bend point, the rope's trying to unbend, so continue on to find the next bend point.

Erm, I think that's right. Also, the Parent check you mentioned is to clean up orphaned rope pieces; if a rope piece is its own parent it means the hook it was associated with is gone.

Post 23-Aug-2008 21:25        

Quib Mask wrote:

Triscuit, have you looked at the code? It's made up of a jillion verbs! I would love to make something like this pass checksum, but there's a couple limitations: the first being even if I went with a normal COG instead of a thing's class COG and so didn't need such a silly way to store variables (if anyone's curious, thing thrust also provides another good set of 3 storage variables) it would still consume a ton of checksum units (or whatever you want to call them) and probably have to span multiple COGs, and second, it would either be client-side only so no one else would see the rope/chain or it would be sync'd but cause such insane network traffic that it would probably lag games out. Though, powerup class COGs do contain a fair amount of usable code and exist nearly universally (due to the Force Pull powerup templates), so maybe they'd have some potential.

I only gave it a quick glance, I figured there was room for optimization.
Maybe I'll just make a standard grapple using the +force_ltpeice for rope and forget about the bend points.
*** Post commands are unavailable for guests. ***