Jump to content

Photo

Can a script change Link's walk speed?


  • Please log in to reply
14 replies to this topic

#1 hideous_kojima

hideous_kojima

    Newbie

  • Members
  • Location:Somewhere in hyperbolic space...

Posted 14 June 2018 - 06:18 PM

The short of it: is there any way to change Link's walk speed via script I does is have to know...

 

The context: I'm working on a Four Swords Adventures -style bow script, where you can strafe around at half-speed while nocking and charging the arrow shot. Thing is, I can't figure out how to slow Link down! (You'd think there'd be a WalkSpeed property to Link or something... unless I am missing something?)

 

I have some ideas for "creative" workarounds already, but I'd like the community's more experienced perspective and guidance. Thanks!



#2 klop422

klop422

    Guess I'm full of monsters and treasure

  • Members
  • Real Name:Not George
  • Location:Planet Earth

Posted 14 June 2018 - 06:47 PM

I did get from ZoriaRPG a running boots script (i.e. a script that makes Link run faster if you have a certain item and hold R), so it's definitely possible. I don't know for sure, though, so that's all I can help you with.



#3 Moosh

Moosh

    Tiny Little Questmaker

  • ZC Developers

Posted 14 June 2018 - 06:49 PM

https://www.purezc.n...=scripts&id=284

I wrote a header/global script for moving Link around by sub pixel increments and also changing his movement speed. It's not a perfect system, but it should work well enough for your purposes.

 

Another simpler way you could slow him down would be cutting off direction inputs every other frame.


  • hideous_kojima likes this

#4 Avaro

Avaro

    o_o

  • Members
  • Real Name:Robin
  • Location:Germany

Posted 14 June 2018 - 06:49 PM

Use this script library by Moosh: https://www.purezc.n...=scripts&id=284

Do a negative speed boost to slow Link down.

 

edit: whoops


Edited by Avataro, 14 June 2018 - 06:49 PM.

  • hideous_kojima likes this

#5 Anthus

Anthus

    Lord of Liquids

  • Members
  • Location:Ohio

Posted 14 June 2018 - 07:20 PM

Another simpler way you could slow him down would be cutting off direction inputs every other frame.

 

I was actually going to suggest that, but I didn't know if that was a real thing that could be done. :P

 

Awesome username, hideous_kojima. Welcome to PureZC, btw.


  • Matthew and hideous_kojima like this

#6 hideous_kojima

hideous_kojima

    Newbie

  • Members
  • Location:Somewhere in hyperbolic space...

Posted 15 June 2018 - 12:34 AM

I managed to hack up a relatively simple global script that modifies Link's walkspeed (assuming "continuous" movement between frames) and can handle the resulting subpixel positions via fraction storage:

const float WALK_SPD_PERCENT = 0.5; // Put whatever percentage of normal walking speed 
                                    // you want Link to walk at here.

global script slot2 {
    void run() {
	float x = Link->X; float y = Link->Y;
	float vx = 0; float vy = 0;
	float vx_frac = 0; float vy_frac = 0;
	bool moved_continuously = true;
	int dmp = Game->GetCurDMap();
	int scr = Game->GetCurDMapScreen();

	while (true) {
            // Determine whether the change in Link's position from the last frame to this
            // frame is appropriate for scaling, a.k.a. "continuous":
	    moved_continuously = true;
	    if (Game->GetCurDMap() != dmp || Game->GetCurDMapScreen() != scr)
		moved_continuously = false;  // if warping outside of current screen, except by
                                             // side warp, we know movement is discontinuous.
	    
            // If appropriate for scaling...		
	    if (moved_continuously) {
                // Get velocity components, i.e. changes in x and y position b/w frames:
		vx = Link->X - x; vy = Link->Y - y;
		
                // Scale velocities by desired walk speed percentage:
		vx *= WALK_SPD_PERCENT; vy *= WALK_SPD_PERCENT;
		
                // Handle non-integer velocities:
		vx += vx_frac; vy += vy_frac;  // apply stored fractions from prev frame
		vx_frac = 0; vy_frac = 0;  // get current velocity fractions
		if (vx != 0) vx_frac = vx - Floor(Abs(vx)) * (vx/Abs(vx));
		if (vy != 0) vy_frac = vy - Floor(Abs(vy)) * (vy/Abs(vy));
		vx -= vx_frac; vy -= vy_frac;  // trim and store new velocity fractions
		
                // Apply resulting scaled, integer velocities to position components:
		Link->X = x + vx; Link->Y = y + vy;
		
                // Store resulting position components for use in next frame:
		x = Link->X; y = Link->Y;
	    }

	    Waitframe();
	}
    }
}

Far from perfect, definitely incomplete (necessary and sufficient conditions for continuous movement being only partially defined), and almost certain to break as the script ventures from simple script test project to legitimate quest, but it's relatively short and sweet and I feel like I will be able to apply the approach to my bow problem, as well as other problems that may crop up in the future!

 

That said, I have been attempting to use Moosh's library - as I really don't want to reinvent the wheel on this - but I just can't seem to get it to work for some reason...? It won't compile unless I completely nix the LinkMovement_UglyReverseMovementFix() method, because of the undeclared array 'G' ... and even when I do so the script just has Link fly into the upper left of the screen in a convex arc!

 

Has anyone experienced or does anyone understand that behavior, and can clue me in on a fix? Help'd be much appreciated ;D



#7 Moosh

Moosh

    Tiny Little Questmaker

  • ZC Developers

Posted 15 June 2018 - 12:55 AM

That said, I have been attempting to use Moosh's library - as I really don't want to reinvent the wheel on this - but I just can't seem to get it to work for some reason...? It won't compile unless I completely nix the LinkMovement_UglyReverseMovementFix() method, because of the undeclared array 'G' ... and even when I do so the script just has Link fly into the upper left of the screen in a convex arc!

Whoops! So it turns out while fixing stuff in the header I was working in a quest that had some similarly named and functioning global variables. I accidentally used the ones from the quest instead of the ones from the header. I just put through a silent update and it should be fixed now. Not sure about this convex arc behavior, I might have to see how you're using the functions in order to help.



#8 Timelord

Timelord

    The Timelord

  • Banned
  • Location:Prydon Academy

Posted 15 June 2018 - 03:38 AM

Be aware when making these things, that you need a read-ahead walkability check prior to repositioning Link, and that you need to both perform this check, and reposition link before Waitdraw().

You must call Waitdraw(), unless you like massive gameplay bugs.

Checking if !(Screen->isSolid(vx,vy) should suffice for this in most cases.

You're welcome to clone how I've done this (elsewhere).

You may also want truncation, instead of Floor().

#9 Deedee

Deedee

    Bug Frog Dragon Girl

  • Moderators
  • Real Name:Deedee
  • Pronouns:She / Her, They / Them
  • Location:Canada

Posted 15 June 2018 - 12:05 PM

Waitdraw isn't required to be called, but it is recommended. Global Scripts can be split into 3 parts: Initialization (before the main while loop), Pre-Waitdraw (inside the while loop, before Waitdraw()), and Post-Waitdraw (inside the while loop, after Waitdraw() and before Waitframe()). If you don't call Waitdraw(), it will be handled by Waitframe() and the code inside the while loop will be entirely Pre-Waitdraw(). 

Waitdraw() essentially handles movements, inputs, and the likes (basically all engine-based movement), while Waitframe() draws the result. If you want to cancel an input, you do so Pre-Waitdraw, or else it won't do anything. If you want to move Link manually, you want to do so Post-Waitdraw, or else you'll get some janky effects with the engine moving Link after you move Link (of course, there's some exceptions; sometimes you want to move Link before something occurs at Waitdraw). 

Moosh's header relies on some functions being called after Waitdraw, so you'll want to make sure you have that.


Edited by Dimentio, 15 June 2018 - 12:06 PM.


#10 hideous_kojima

hideous_kojima

    Newbie

  • Members
  • Location:Somewhere in hyperbolic space...

Posted 15 June 2018 - 11:47 PM

Ah I wasn't aware that collision checks were up to me in this case (as the collisions seem to be working in my test rooms just fine), nor was I aware of the full importance/purpose of Waitdraw()! The collisions seem to be working just fine in my test rooms... though I will keep the caution in mind as I proceed. And from the sound of it calling Waitdraw() at the right time may be the key to eliminating the subtle backwards jerking movement Link does when he halts his movement at low speeds...

 

Also, as for how I've been using LinkMovement.zh: I simply loaded the global script LinkMovement_Example into my test quest's Active global script slot. Is this not the way to demo the library... or is it some sort of template not meant to be used directly at all or...? Haha sorry I'm still kinda pretty bad at this!



#11 Deedee

Deedee

    Bug Frog Dragon Girl

  • Moderators
  • Real Name:Deedee
  • Pronouns:She / Her, They / Them
  • Location:Canada

Posted 16 June 2018 - 12:05 AM

Sort of. You need to call the  AddLinkSpeedBoost function somewhere to actually give him a speed boost (or speed slow, if negative). It needs to be called every frame, so call it within the code that runs while the bow is being charged. Also that's fine, you're doing a whole lot better than most people new to scripts (most can't even compile database scripts :P )


Edited by Dimentio, 16 June 2018 - 12:05 AM.

  • Binx likes this

#12 hideous_kojima

hideous_kojima

    Newbie

  • Members
  • Location:Somewhere in hyperbolic space...

Posted 16 June 2018 - 01:14 AM

Sooooo after much toil and trouble I figured out that the bad behavior I was getting from LinkMovement.zh was due to some corruption in my Zelda Classic's runtime that for some reason prevented operations on global arrays (like LinkMovement) in local scopes. I know this because when I upgraded from 2.50.1 to 2.50.2 everything works just fine! No weird convex arcs or anything! Weird...

 

Anyway, finally getting to try out the library, I found that I can reduce Link to (what I assume is) half-speed by calling LinkMovement_SetLinkSpeedBoost(-0.5) prior to LinkMovement_Update1().

 

...by the way, what are the units here exactly? Like, in general, if I wanted to multiply Link's speed by n, what would I have to pass into LinkMovement_SetLinkSpeedBoost()? Just for future reference, thanks!


Edited by hideous_kojima, 16 June 2018 - 03:21 AM.


#13 Timelord

Timelord

    The Timelord

  • Banned
  • Location:Prydon Academy

Posted 16 June 2018 - 07:22 AM

Waitdraw isn't required to be called, but it is recommended. Global Scripts can be split into 3 parts: Initialization (before the main while loop), Pre-Waitdraw (inside the while loop, before Waitdraw()), and Post-Waitdraw (inside the while loop, after Waitdraw() and before Waitframe()). If you don't call Waitdraw(), it will be handled by Waitframe() and the code inside the while loop will be entirely Pre-Waitdraw(). 

Waitdraw() essentially handles movements, inputs, and the likes (basically all engine-based movement), while Waitframe() draws the result. If you want to cancel an input, you do so Pre-Waitdraw, or else it won't do anything. If you want to move Link manually, you want to do so Post-Waitdraw, or else you'll get some janky effects with the engine moving Link after you move Link (of course, there's some exceptions; sometimes you want to move Link before something occurs at Waitdraw). 

Moosh's header relies on some functions being called after Waitdraw, so you'll want to make sure you have that.

(Emphasis, mine.)
 
I'm pretty sure that's completely wrong.
 
As I recall, without an explicit call to Waitdraw() all instructions occur after drawing by default. I might need to verify this, but that's what I understand to be true:
 
The call to Waitdraw() ensures that your instructions are enqueued to run prior to engine drawing, and it's absolutely needed if you are doing things that affect collision, and making solidity checks.
 
Drawing occurs immediately after Waitdraw(), not Waitframe(). The Waitframe() call syncs the loop with the internal advance_frame() in the main() loop in ZC. Waitdraw() is, IIRC, the internal variable global_wait.
 
I'm pretty sure that it's mandatory to assure correct system timing.
 
It'd probably be best to check this using Link->Dir, based on when it is set.


//================================================== =====
//--- Instruction Processing Order and General Timing ---
================================================== =======

1.     Instructions in Global script Init (if starting a new game)
2.     Instructions in Global Script OnContinue (is resuming a game)
3.     Instructions from FFCs that run on screen init, excluding draw instructions.
        Note: Instructions are handled on a per-ffc basis, in ID order; so a script on ffc ID 1 runs,
        then a script on ffc ID 2, up to ffc ID 32. If an ffc has no script, it is skipped.
4.     Instructions immediately inside the run() function of a global active script, if the game has just loaded.
5.     Instructions in the global active script's infinite loop prior to Waitdraw,
6.     Instructions from an ffc script positioned after (an illegal)
    Waitdraw() instruction in that script from the previous frame.
        Note: Requires being on at least the second frame of a game session.
7.     Screen bitmap drawing.
8.     Enqueued Script Drawing from the global active script, (and from ffcs on the previous frame).
9.     Drawing Instructions in the global active script prior to Waitdraw().
10.     Instructions in an ffc script, excluding draw commands.
        Note: Instructions are handled on a per-ffc basis, in ID order; so a script on ffc ID 1 runs,
        then a script on ffc ID 2, up to ffc ID 32. If an ffc has no script, it is skipped.
11.     Screen Scrolling (2.50.2, or later)
12.     Waitdraw() in a global active script.
13.     Engine writing to Link->Dir and Link->Tile.
14.     FFCs enqueue draws for the next frame.
        Note: Instructions are handled on a per-ffc basis, in ID order; so a script on ffc ID 1 runs,
        then a script on ffc ID 2, up to ffc ID 32. If an ffc has no script, it is skipped.
15.     Instructions from item action scripts.
16.     Instructions from item collect scripts.
17.     Instructions in the global active script, called after Waitdraw()
17(b).     Screen Scrolling ( 2.50.0, and 2.50.1 )]
18.     Instructions in an OnExit script, if the game is exiting.
19.     Return to (3).



#14 Moosh

Moosh

    Tiny Little Questmaker

  • ZC Developers

Posted 16 June 2018 - 08:14 AM

Sooooo after much toil and trouble I figured out that the bad behavior I was getting from LinkMovement.zh was due to some corruption in my Zelda Classic's runtime that for some reason prevented operations on global arrays (like LinkMovement) in local scopes. I know this because when I upgraded from 2.50.1 to 2.50.2 everything works just fine! No weird convex arcs or anything! Weird...

This is a common problem when using scripts. Any time you change global variables around, be it name, order, or size will break old saves. It's best to make a new save file any time you change global variables or add a new header as an extra precaution. 

 

Anyway, finally getting to try out the library, I found that I can reduce Link to (what I assume is) half-speed by calling LinkMovement_SetLinkSpeedBoost(-0.5) prior to LinkMovement_Update1().

This would be 1/3 speed. Link's base speed is 1.5.

 

...by the way, what are the units here exactly? Like, in general, if I wanted to multiply Link's speed by n, what would I have to pass into LinkMovement_SetLinkSpeedBoost()? Just for future reference, thanks!

The base unit here is one pixel. Link moves 1.5 pixels per frame and the speed boost is added to Link's base so to double it you'd set it to 1.5.

responses in bold.


  • hideous_kojima likes this

#15 Deedee

Deedee

    Bug Frog Dragon Girl

  • Moderators
  • Real Name:Deedee
  • Pronouns:She / Her, They / Them
  • Location:Canada

Posted 16 June 2018 - 10:35 AM

(Emphasis, mine.)
 
I'm pretty sure that's completely wrong.

If this were wrong, then literally every database global script without a waitdraw (read: many of them) would be completely broken and trying to merge them with scripts that have Waitdraw would also be broken. Waitdraw does the engine stuff. Without it the engine stuff is handled at Waitframe. There is literally no evidence to suggest otherwise, whether through trial/error, past script attempts, etc. 




1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users