Some Useful Functions

Make games! Discuss those games here.

Moderators: Bob the Hamster, marionline, SDHawk

Post Reply
User avatar
Pepsi Ranger
Liquid Metal Slime
Posts: 1457
Joined: Thu Nov 22, 2007 6:25 am
Location: South Florida

Some Useful Functions

Post by Pepsi Ranger »

Hey guys,

I'm still hard at work updating my wacko scripts for Entrepreneur: The Beginning into more manageable templates and functions so that updates can come out faster and better optimized, and I am getting close to the end of the first round. Normally I would post an update related to the game in Entrepreneur Central, but this one's different because this one is about giving something more general back to you guys.

By updating the scripting for the game, I've come up with some simple functions to make common practices much easier and faster to manage. Here are some of the frequently-used functions (well, frequently in my scripts) that would benefit any game. Chances are you already have something similar in place, but for those who don't, or for those who are still learning plotscripting, or for those who want to jump into a new game and save time doing so, here are some script functions you might like for your own games (with description). They should be easily copied and pasted into whatever script you're working on. Remember, these are designed as functions, not triggered scripts, so they should be used as a line in your script once you've defined them.

Note: Some scripts use constants and global variables. I'll make a note which ones you'll need to add to your constants definitions and global variables list.

Here is Part 1 of this feature.

______________________

Function:
Simple Textbox Display

Name:
Basic Text (textbox)

Description:
Shortens textbox display commands to a single line. This should be used only when you want the player to manually advance text and only when nothing is supposed to happen while the text is displayed. It's extremely common.

Usage Example:
script,talk to me,begin
walk hero (0,north,1)
wait for hero (0)
basic text (2)
walk hero (0,west,1)
wait for hero (0)
basic text (3)
end

Script for Function:

Code: Select all

script,basic text,which box,begin
show text box (which box)
wait for text box
end
Note: This same type of function can also be used for simplifying walking. Example: "basic walk (0,north,1)" It shouldn't be difficult to guess how to script a basic walk function. Hint: Additional shortcuts like this coming next.

______________________

Function:
Simple Camera Pan

Name:
Basic Camera (x,y,speed)

Description:
Simplify camera movement down to a single line. Functions like "basic text."

Usage Example:
script,talk to me,begin
walk hero (0,north,1)
wait for hero (0)
basic text (2)
walk hero (0,west,1)
wait for hero (0)
basic text (3)
basic camera (10,10,10)
basic text (4)
end

Script for Function:

Code: Select all

script,basic camera,x,y,speed,begin
focus camera (x,y,speed)
wait for camera
end
Similar Function:
Pan Camera Deluxe (direction,distance,speed)

Note: This is the "pan camera" equivalent to the above script function.

Code: Select all

script,pan camera deluxe,direction,distance,speed,begin
pan camera (direction,distance,speed)
wait for camera
end
______________________

Function:
Simplified Walking

Names:
(heroes)
Basic Hero Walk (who,place,direction)
Walk Hero Deluxe (who,direction,distance)
Walk Hero Advanced (who,direction,distance,new direction)
Basic Hero Walk Advanced (who,place,direction,new direction)
Move Hero Backward (who,direction,distance)
(npcs)
Basic NPC Walk (who,place,direction)
Walk NPC Deluxe (who,direction,distance)
Walk NPC Advanced (who,direction,distance,new direction)
Basic NPC Walk Advanced (who,place,direction,new direction)
Move NPC Backward (who,direction,distance)

Description:
Simplify hero and NPC movement down to a single line. Functions like "basic text."

Usage Example:
script,talk to me,begin
walk hero deluxe (0,north,1)
basic text (2)
walk hero deluxe (0,west,1)
basic text (3)
basic camera (10,10,10)
basic text (4)
move hero backward (0,east,1)
end

Scripts for Function:

Note: The following script function simplifies walking to a coordinate, namely "walk hero to x" or "walk hero to y." "axis:xline" and "axis:yline" should be defined as constants. These axis lines will serve as the point to which you want the hero or npc (defined in a separate script function) to walk.

Looks Like:
basic hero walk (0,12,axis:xline)
basic hero walk (0,15,axis:yline)

Note: This would send the hero to (12, 15) on the map.

Note 2: The script function could be simplified even further if you want to merge all x and y arguments and destinations together.

Code: Select all

script,basic hero walk,who,place,direction,begin
if (direction==axis:xline) then(
 walk hero to x (who,place)
)
if (direction==axis:yline) then(
 walk hero to y (who,place)
)
wait for hero (who)
end
Note: This script function is the simplified version of "walk hero."

Code: Select all

script,walk hero deluxe,who,direction,distance,begin
walk hero (who,direction,distance)
wait for hero (who)
end
Note: This script function sets a direction at the end of the hero's walk, so the hero might end his walk on a stance.

Code: Select all

script,walk hero advanced,who,direction,distance,new direction,begin
walk hero (who,direction,distance)
wait for hero (who)
set hero direction (who,new direction)
end
Note: This script function walks a hero to an x or y coordinate and ends on a directional stance.

Code: Select all

script,basic hero walk advanced,who,place,direction,new direction,begin
if (direction==axis:xline) then(
 walk hero to x (who,place)
)
if (direction==axis:yline) then(
 walk hero to y (who,place)
)
wait for hero (who)
set hero direction (who,new direction)
end
Note: This script function allows a hero to walk backward via a single command.

Code: Select all

script,move hero backward,who,direction,distance,begin
variable (opposite direction)
switch (direction) do(
 case (north) do(
  opposite direction := south
 )
 case (east) do(
  opposite direction := west
 )
 case (south) do(
  opposite direction := north
 )
 case (west) do(
  opposite direction := east
 )
)
walk hero (who,opposite direction,distance)
set hero direction (who,direction)
wait for hero (who)
end
Note: The following script functions are the NPC equivalents of the ones above. Most of these can be merged or modified to suit your needs.

Code: Select all

script,basic npc walk,who,place,direction,begin
if (direction==axis:xline) then(
 walk npc to x (who,place)
)
if (direction==axis:yline) then(
 walk npc to y (who,place)
)
wait for npc (who)
end

Code: Select all

script,walk npc deluxe,who,direction,distance,begin
walk npc (who,direction,distance)
wait for npc (who)
end

Code: Select all

script,walk npc advanced,who,direction,distance,new direction,begin
walk npc (who,direction,distance)
wait for npc (who)
set npc direction (who,new direction)
end

Code: Select all

script,basic npc walk advanced,who,place,direction,new direction,begin
if (direction==axis:xline) then(
 walk npc to x (who,place)
)
if (direction==axis:yline) then(
 walk npc to y (who,place)
)
wait for npc (who)
set npc direction (who,new direction)
end

Code: Select all

script,move npc backward,who,direction,distance,begin
variable (opposite direction)
switch (direction) do(
 case (north) do(
  opposite direction := south
 )
 case (east) do(
  opposite direction := west
 )
 case (south) do(
  opposite direction := north
 )
 case (west) do(
  opposite direction := east
 )
)
walk npc (who,opposite direction,distance)
set npc direction (who,direction)
wait for hero (who)
end
______________________

Function:
Automatic NPC Facing

Name:
NPC Face Forward (who,direction)

Description:
NPCs have several interaction attributes like "change direction," "face player," and "do not face player" that can be defined in CUSTOM. Because NPCs don't automatically face heroes through scripting, however, conversations between heroes and npcs may look awkward if they're not facing each other. Here's a quick way to fix that issue.

Usage Example:
script,talk to me,begin
variable (d)
walk hero deluxe (0,north,1)
d := hero direction (0)
npc face forward (1,d)

basic text (2)
walk hero deluxe (0,west,1)
basic text (3)
basic camera (10,10,10)
basic text (4)
move hero backward (0,east,1)
end

Script for Function:

Code: Select all

script,npc face forward,who,direction,begin
if (direction==north) then(
 set npc direction (who,south)
)
if (direction==east) then(
 set npc direction (who,west)
)
if (direction==south) then(
 set npc direction (who,north)
)
if (direction==west) then(
 set npc direction (who,east)
)
end
______________________

Function:
Simple Range Checks

Name:
In Range (v,min,max)

Description:
Instead of writing "if ((object>=10) && (object<=99)) then(" to show a range, which can get messy if you're nesting, you can simplify range checks by plugging variables and values into the "in range" arguments.

Usage Example:
script,talk to me,begin
variable (d)
if (in range (object,10,99)) then(
walk hero deluxe (0,north,1)
d := hero direction (0)
npc face forward (1,d)
basic text (2)
walk hero deluxe (0,west,1)
basic text (3)
basic camera (10,10,10)
basic text (4)
move hero backward (0,east,1)
)
end

Script for Function:

Code: Select all

script,in range,v,min,max,begin
if &#40;v < min&#41; then&#40;
 exit returning &#40;false&#41;
&#41;
if &#40;v > max&#41; then&#40;
 exit returning &#40;false&#41;
&#41;
exit returning &#40;true&#41;
end
Note: TMC gave me this script. You're welcome.

Okay, that covers Part 1. I'll post Part 2 after I've had lunch. It will feature slightly more complicated things than the above. If you're keeping score, the functions above are merely designed as shortcuts to using built-in waits or excessive typing. Use them if you want. They can shorten your code considerably if you have a lot of textboxes or hero and npc movement.
Place Obligatory Signature Here
User avatar
guo
Metal Slime
Posts: 749
Joined: Fri Dec 04, 2009 9:12 pm

Post by guo »

This is a treasure trove, and much appreciated. Thankyou.
vvight.wordpress.com
User avatar
Pepsi Ranger
Liquid Metal Slime
Posts: 1457
Joined: Thu Nov 22, 2007 6:25 am
Location: South Florida

Post by Pepsi Ranger »

Sure thing.

Okay, I'm back with Part 2. This set will simplify tracking your heroes and NPCs around the map.

______________________

Function:
Storing Coordinates for Quick Access

Names:
Store Current Position (target hero)
Store Current NPC Position (target npc)

Description:
Writes important (x,y) information to global variables related to hero's or NPC's position, as well as the coordinates that span up to two tiles out in all four directions. This is useful for reading or writing conditions that affect NPCs as much as two tiles away in a single line. Really useful for flavor text dependent on objects in relation to hero direction.

Usage Example:
script,look at me,begin
walk hero deluxe (0,north,1)
store current position
if (read map block (forwardx,forwardy)==1) then( #check that tile ahead of hero (that hero is facing) is a bookshelf
basic text (10)
)
end

Note: All variables used in the following script should be defined as global variables. "Fixed" variables are the ones that store the x/y values directly under the hero. "Fixed direction" stores the hero's current direction. "Forward(x/y), 1, 2" are the three-tiles out coordinates from where the hero is standing. "Forwardx" and "forwardy" would automatically read the coordinates directly in front of the hero, where he's facing.

Note 2: If you want to store the information passed to a non-leading hero, then feel free to write the number for that hero as an argument. Example: store current position (1).

The following script function is for storing hero coordinates and related adjacent tiles.

Script for Function:

Code: Select all

script,store current position,target hero = 0,begin
fixedx &#58;= hero x &#40;target hero&#41;
fixedy &#58;= hero y &#40;target hero&#41;
fixeddirection &#58;= hero direction &#40;target hero&#41;
northx &#58;= fixedx
northy &#58;= fixedy--1
eastx &#58;= fixedx + 1
easty &#58;= fixedy
southx &#58;= fixedx
southy &#58;= fixedy + 1
westx &#58;= fixedx--1
westy &#58;= fixedy
if &#40;fixeddirection==north&#41; then&#40;
 forwardx &#58;= northx
 forwardy &#58;= northy
 forwardx1 &#58;= northx
 forwardx2 &#58;= northx
 forwardy1 &#58;= northy--1
 forwardy2 &#58;= northy--2
&#41;
if &#40;fixeddirection==east&#41; then&#40;
 forwardx &#58;= eastx
 forwardy &#58;= easty
 forwardx1 &#58;= eastx + 1
 forwardx2 &#58;= eastx + 2
 forwardy1 &#58;= easty
 forwardy2 &#58;= easty
&#41;
if &#40;fixeddirection==south&#41; then&#40;
 forwardx &#58;= southx
 forwardy &#58;= southy
 forwardx1 &#58;= southx
 forwardx2 &#58;= southx
 forwardy1 &#58;= southy + 1
 forwardy2 &#58;= southy + 2
&#41;
if &#40;fixeddirection==west&#41; then&#40;
 forwardx &#58;= westx
 forwardy &#58;= westy
 forwardx1 &#58;= westx--1
 forwardx2 &#58;= westx--2
 forwardy1 &#58;= westy
 forwardy2 &#58;= westy
&#41;
end
And this one is for NPCs.

Code: Select all

script,store current npc position,target npc = 0,begin
fixednpcx &#58;= npc x &#40;target npc&#41;
fixednpcy &#58;= npc y &#40;target npc&#41;
fixednpcdirection &#58;= npc direction &#40;target npc&#41;
northnpcx &#58;= fixednpcx
northnpcy &#58;= fixednpcy--1
eastnpcx &#58;= fixednpcx + 1
eastnpcy &#58;= fixednpcy
southnpcx &#58;= fixednpcx
southnpcy &#58;= fixednpcy + 1
westnpcx &#58;= fixednpcx--1
westnpcy &#58;= fixednpcy
if &#40;fixednpcdirection==north&#41; then&#40;
 forwardnpcx &#58;= northnpcx
 forwardnpcy &#58;= northnpcy
 forwardnpcx1 &#58;= northnpcx
 forwardnpcx2 &#58;= northnpcx
 forwardnpcy1 &#58;= northnpcy--1
 forwardnpcy2 &#58;= northnpcy--2
&#41;
if &#40;fixednpcdirection==east&#41; then&#40;
 forwardnpcx &#58;= eastnpcx
 forwardnpcy &#58;= eastnpcy
 forwardnpcx1 &#58;= eastnpcx + 1
 forwardnpcx2 &#58;= eastnpcx + 2
 forwardnpcy1 &#58;= eastnpcy
 forwardnpcy2 &#58;= eastnpcy
&#41;
if &#40;fixednpcdirection==south&#41; then&#40;
 forwardnpcx &#58;= southnpcx
 forwardnpcy &#58;= southnpcy
 forwardnpcx1 &#58;= southnpcx
 forwardnpcx2 &#58;= southnpcx
 forwardnpcy1 &#58;= southnpcy + 1
 forwardnpcy2 &#58;= southnpcy + 2
&#41;
if &#40;fixednpcdirection==west&#41; then&#40;
 forwardnpcx &#58;= westnpcx
 forwardnpcy &#58;= westnpcy
 forwardnpcx1 &#58;= westnpcx--1
 forwardnpcx2 &#58;= westnpcx--2
 forwardnpcy1 &#58;= westnpcy
 forwardnpcy2 &#58;= westnpcy
&#41;
end
______________________

Function:
Storing Coordinates for Condition Checks

Names:
Check Position (tx,ty)
Check Forward (tx,ty)
Check NPC Position (npc,tx,ty)

Description:
Similar to the previous examples, but this time uses the results of the position below or in front of facing direction in a conditional. Script functions can be modified to suit needs based on relative position.

Usage Example:
script,look at me,begin
walk hero deluxe (0,north,1)
store current position (0)
if (read map block (forwardx,forwardy)==1) then( #check that tile ahead of hero (that hero is facing) is a bookshelf
basic text (10)
)
if (check forward (10,13)) then( #look for a specific bookshelf at coordinate 10,13
basic text (11)
move hero backward (0,east,1)
)
end

Note: The following script function compares current location with target location (the tx,ty coordinates).

Script for Function:

Code: Select all

script,check position,tx,ty,begin
variable &#40;x,y&#41;
x &#58;= hero x &#40;0&#41;
y &#58;= hero y &#40;0&#41;
if &#40;&#40;x==tx&#41; && &#40;y==ty&#41;&#41; then&#40;
 exit returning &#40;true&#41;
&#41;
exit returning &#40;false&#41;
end
Note: Can be modified with a third argument denoting hero position if comparison should be made using someone other than the lead.

Code: Select all

script,check position,tx,ty,who = 0,begin
variable &#40;x,y&#41;
x &#58;= hero x &#40;who&#41;
y &#58;= hero y &#40;who&#41;
if &#40;&#40;x==tx&#41; && &#40;y==ty&#41;&#41; then&#40;
 exit returning &#40;true&#41;
&#41;
exit returning &#40;false&#41;
end
Note: The following script function works like the previous, but checks for the position ahead of the hero. Same modifications apply. Adjust accordingly. Can also be called "check forward position" for continuity.

Code: Select all

script,check forward,tx,ty,begin
variable &#40;x,y,d&#41;
x &#58;= hero x &#40;0&#41;
y &#58;= hero y &#40;0&#41;
d &#58;= hero direction &#40;0&#41;
if &#40;d==north&#41; then&#40;
 if &#40;&#40;x==tx&#41; && &#40;y--1==ty&#41;&#41; then&#40;
  exit returning &#40;true&#41;
 &#41;
&#41;
if &#40;d==east&#41; then&#40;
 if &#40;&#40;x+1==tx&#41; && &#40;y==ty&#41;&#41; then&#40;
  exit returning &#40;true&#41;
 &#41;
&#41;
if &#40;d==south&#41; then&#40;
 if &#40;&#40;x==tx&#41; && &#40;y+1==ty&#41;&#41; then&#40;
  exit returning &#40;true&#41;
 &#41;
&#41;
if &#40;d==west&#41; then&#40;
 if &#40;&#40;x--1==tx&#41; && &#40;y==ty&#41;&#41; then&#40;
  exit returning &#40;true&#41;
 &#41;
&#41;
exit returning &#40;false&#41;
end
Note: The next script function is a check position for NPCs.

Code: Select all

script,check npc position,npc,tx,ty,begin
variable &#40;x,y&#41;
x &#58;= npc x &#40;npc&#41;
y &#58;= npc y &#40;npc&#41;
if &#40;&#40;x==tx&#41; && &#40;y==ty&#41;&#41; then&#40;
 exit returning &#40;true&#41;
&#41;
exit returning &#40;false&#41;
end
______________________

Function:
Wait (as Written for FPS Adjustments)

Name:
Wait Equivalent (tick)

Description:
Keep your animations and waits intact regardless of FPS settings. Requires a definition of the global variable "fpstimer" at the start of the game matching the FPS speed.

Note: The following usage example has two options for game speed. The hashtag blocks out the line you don't want running. Hopefully, if we're ever able to change FPS via scripting, then you can update this script to run on a condition.

Usage Example:
script,designate wait speed,begin
fpstimer := 18 #for games running at 18 FPS
#fpstimer := 30 #for games running at 30 FPS
end

script,look at me,begin
walk hero deluxe (0,north,1)
store current position (0)
if (read map block (forwardx,forwardy)==1) then( #check that tile ahead of hero (that hero is facing) is a bookshelf
basic text (10)
)
if (check forward (10,13)) then( #look for a specific bookshelf at coordinate 10,13
basic text (11)
wait equivalent (5)
move hero backward (0,east,1)
)
end

Script for Function:

Code: Select all

script,designate wait speed,begin
fpstimer &#58;= 18 #for games running at 18 FPS
#fpstimer &#58;= 30 #for games running at 30 FPS
end

script,wait equivalent,tick,begin
wait &#40;&#40;tick*fpstimer&#41;/18&#41;
end
Note: Just a reminder, this script function would guarantee same speed waits regardless of FPS setting, as long as the correct FPS value is passed to the "fpstimer" variable. It should also be noted that the "fpstimer" should always be passed as the argument for timers where you want a countdown ever second. Example: set timer (0,5,fpstimer,@launch thing) would run the script "launch thing" in five seconds.

Okay, I have more, but I'll post those in Part 3. Hope these help.
Last edited by Pepsi Ranger on Tue Feb 06, 2018 3:02 am, edited 2 times in total.
Place Obligatory Signature Here
TMC
Metal King Slime
Posts: 4308
Joined: Sun Apr 10, 2011 9:19 am

Post by TMC »

Cool, I've added a link to these from Script dump (I regret using such an unflattering name for that article!)

Creating wrapper scripts which run some command and then wait is extremely common. Probably we should just add include lots commands like that, such as "walk hero wait", "pan camera wait". And maybe add a "create alias" meta command to make it super easy to add shorter aliases if you want them.
A lot of people use a script for "show textbox", "wait" called just "tb" (it's in the 3rd party hsi and reinvented many times).

However, I have to complain that the names you've used for your scripts... really don't make any sense. They don't even try to explain what the scripts do, and they're also inconsistent (e.g. "basic camera" and "pan camera deluxe"). You've discovered that in programming, naming things is hard. It's often joked that it's one of the hardest parts of programming, and it's actually true. For example, instead of "Walk Hero Advanced" how about "walk hero face" or "walk hero wait face" or "walk hero wait and face" or "walk and turn hero" (gosh this naming thing is hard!) Likewise, I suggest "walk npc backward" rather than "move npc backward".

Also, you have a "wait for hero" command in "move npc backward".

The fact that you have the same 'switch' block repeated multiple times to find the opposite direction to a given one suggests you should put that in a script!

Now "wait equivalent", that's definitely an important script. I haven't decided yet whether to have a global switch to make all wait commands count in 18th's of a second, or just add the equivalent of "wait equivalent" and have people do a search-replace themselves.

BTW, I find the dir x, dir y scripts posted here (scroll down) very useful. For example the way I would implement "check forward" is:

Code: Select all

script,check forward,tx,ty,begin
 variable &#40;x,y,d&#41;
 d &#58;= hero direction &#40;0&#41;
 x &#58;= hero x &#40;0&#41; + dir x&#40;d&#41;
 y &#58;= hero y &#40;0&#41; + dir y&#40;d&#41;
 return&#40;tx == x && ty == y&#41;
end
And if you want to get the position 5 tiles ahead of the hero you can write:

Code: Select all

 d &#58;= hero direction &#40;0&#41;
 x &#58;= hero x &#40;0&#41; + 5 * dir x&#40;d&#41;
 y &#58;= hero y &#40;0&#41; + 5 * dir y&#40;d&#41;
Last edited by TMC on Tue Feb 06, 2018 3:59 pm, edited 5 times in total.
User avatar
Pepsi Ranger
Liquid Metal Slime
Posts: 1457
Joined: Thu Nov 22, 2007 6:25 am
Location: South Florida

Post by Pepsi Ranger »

Ah, that "dir x," "dir y" series is quite nice (and more efficient than whatever I have lying around). I may have to adopt that. I also like the "diverse random."

I'm willing to bet most of these script functions can be done a little better, and one of the reasons I'm posting them here is to give users something to work with in case they want to simplify their scripts. It would make sense if they also wanted to simplify these functions. Same goes for naming conventions. I'm using these names for my own game, but they could certainly be changed, and I may still change them myself. I just wrote a few more functions that call out the hero's various "poses" for ease of labeling. It's actually quite fun once you let the imagination take over.

I still have the next series coming soon. I haven't posted them yet because it takes a little while to write the posts that describe them, and I've been doing other things this week. But they're coming, and they'll be prime for modifications, if needed.
Place Obligatory Signature Here
User avatar
Pepsi Ranger
Liquid Metal Slime
Posts: 1457
Joined: Thu Nov 22, 2007 6:25 am
Location: South Florida

Post by Pepsi Ranger »

Got a new one for all'a yous.

For years, I've been using a clunky sound distribution hub for my games called "Scenery Sounds." In its heyday, this script would seek out maps and coordinate ranges and sometimes specific tiles that it might find the player standing in or on and play a corresponding sound or sound loop that he might hear. For example, if he's in the park, he might hear birds. If he's near a lake, he might hear the sloshing of the shoreline. This script was designed to make sure he heard them, and only when he was in the places where he would hear them. When zones were implemented (I think in part to make this process easier), I was able to do away with ranges and look for specific zones. But, even with zones, the interface and methods for updating the script with each new map were a pain to maintain, and sometimes things would get a little wacky.

But now, I've modified my old script so significantly that I can now use the whole thing as a function and simply update my sound design with simple commands, reducing any new headache it might've produced down to none.

And now I'd like to share it with the community, as it is quite portable.

Introducing "Scenery Sounds":

Purpose:

This script will make your maps' sound design as effortless as CUSTOM makes map graphics effortless through tile design.

How It Works:

This script series will keep track of three sound types: map, local, and limited.

This will be done through three specific functions:

map sound loop (id, sound, tag off condition, tag on condition)
-given the loop id and the sound id (the latter defined in CUSTOM under sound effects), this function will play a continuous ambient sound effect relevant to the entire map.

local sound loop (id, sound, zone, tag off condition, tag on condition)
-like "map sound loop," but plays a continuous ambient sound effect for as long as you're in the defined zone where it's supposed to play.

limited sound loop (id, sound, zone, tag off condition, tag on condition)
-this is similar to "local sound loop," but it plays the sound only once per step within the defined zone; perfect for footfalls on surfaces.

Regarding tag conditions, you'll define which tags should be off or on in order for the sound to play. These default to zero and aren't necessary to define unless a specific tag state is needed for that sound.

Note: Loop IDs should start with 1, not zero.

Using It in the Game:

Once you've ported the principals to your plotscript, you'll need to set "Scenery Sounds" as your primary each-step script for each map.

You'll also need to update three functional scripts with instructions specific for your game, which I'll explain in a moment.

Lastly, you'll need to draw your zones wherever you want your local and limited sounds to play on the map.

Before you can use this script and function series, you must have these two global variables defined:

Code: Select all

global variable &#40;1, fixed sound starter&#41;
global variable &#40;2, fixed sound range&#41;
And, you also need to have this general function defined:

Code: Select all

script, validate zone position, zone, begin
variable &#40;x,y&#41;
x &#58;= hero x &#40;0&#41;
y &#58;= hero y &#40;0&#41;
if &#40;read zone &#40;zone, x, y&#41;&#41; then&#40;
 exit returning &#40;true&#41;
&#41;
exit returning &#40;false&#41;
end
The above function is useful for checking whether you're in the required zone.

The Core Scripts:

The following three scripts are necessary for maintaining sound control for your game's specific situations.

The first is the main hub. This is what you'll need to define for CUSTOM and place on your maps' each-step script slots.

Code: Select all

define script &#40;1, Scenery Sounds, none&#41;

script, Scenery Sounds, begin
fixed sound starter &#58;= 10000
fixed sound range &#58;= 8
sound range
local sounds for map &#40;current map&#41;
check for running sounds
validate sounds for map
additional step actions
end
You can alternatively use "plotscript, Scenery Sounds, begin" if you'd like.

Most of the functions in "Scenery Sounds" are fixed. The only two you're changing for your game are "fixed sound starter" and "fixed sound range."

"Fixed sound starter" should be the number ID of the unnamed global variable you want to use as your base sound storage. It should be a global variable ID number that you're certain you'll never reach. I'd recommend a number divisible by 100 or 1000 so you can section off a variable range that won't overlap any other unnamed global variables you might have sectioned for other purposes.

"Fixed sound range" is the maximum number of sounds in use of your most used sound type for any map. For example, if you think you'll need at least four map sounds in one of your maps, even though you might need only three locals or three map sounds for another map, you should set your range to 4. In my scripts, I've set my value to 8 to ensure that I'm well accounted for on my sound needs. This number is not all-inclusive. It represents only the amount of the most used sound type on a specific map where it's used the most. It doesn't need to be, nor should it be, a high number. More than likely, you could set this number blindly (I'd keep it at at least 4 but under 10) and not cause any trouble.

Note: There are nine categories of sound elements defined in this script series. When you're defining your sound starter and range, be sure to have enough global variables available to account for "fixed sound range" * 9. So, if you start your global variable section (or fixed sound starter) with global ID 1000, and you have a range of 4, you'll need variables 1000 - 1035 available to cover the needs of these scripts.

Next is the bread and butter of "Scenery Sounds." Local sounds for map is where you will define which maps play which sounds and how.

Code: Select all

script, local sounds for map, map, begin
switch &#40;map&#41; do&#40;
 case &#40;1, 9&#41; map sound loop &#40;1, 2&#41;
 case &#40;2, 11, 13&#41; map sound loop &#40;1, 1&#41;
 case &#40;3&#41; do&#40;
  map sound loop &#40;1, 74&#41;
  local sound loop &#40;1, 75, 1&#41;
 &#41;
 case &#40;4&#41; do&#40;
  map sound loop &#40;1, 58&#41;
  local sound loop &#40;1, 24, 1&#41;
 &#41;
 case &#40;6&#41; map sound loop &#40;1, 18&#41;
 case &#40;7&#41; do&#40;
  map sound loop &#40;1, 3&#41;
  local sound loop &#40;1, 35, 1, 127&#41;
 &#41;
 case &#40;10&#41; do&#40;
  map sound loop &#40;1, 3&#41;
  map sound loop &#40;2, 5&#41;
  local sound loop &#40;1, 15, 1&#41;
 &#41;
 case &#40;12&#41; map sound loop &#40;1, 4&#41;
 case &#40;14&#41; map sound loop &#40;1, 48&#41;
&#41;
end
In the above example, you can see that each "case" is representative of the map number. Within each case (or beside it--however you like to use the switch/case protocol), you'll write which sound loop functions you want the map to use. You'll see that each function starts with the loop ID (start with 1 and count from there), then gives the sound effect ID that should be played at that particular location, and for local and limited sounds (the latter not pictured in this example) the zone where that sound should play. In cases where a special condition should apply, you can fill in the fourth and fifth arguments with tag numbers you either want off (fourth argument) or on (fifth argument) before it will play.

Remember, the relevant functions here are (required elements in boldface, optional elements in regular type):

map sound loop (id, sound, tag off, tag on)
local sound loop (id, sound, zone, tag off, tag on)
limited sound loop (id, sound, zone, tag off, tag on)

Finally, you'll need to have a script in place where you can piggyback other each-step scripts to trigger after the sounds are chosen. This may include graphics changes depending on where you step or triggered events if you're found in a specific zone. This script, "additional step actions," can be whatever you want it or need it to be. This is simply the central hub for additional actions or map changes or checks necessary at each step.

Code: Select all

script,additional step actions,begin
#specific scripts necessary to affect actions, events, or map changes after sounds are chosen
end
My advice is to structure it in a way similar to what I did for "local sounds for map," but you can do what you want. Here's how I use it in Entrepreneur: The Beginning:

Code: Select all

script,additional step actions,begin
switch &#40;current map&#41; do&#40;
 case &#40;13&#41; do&#40;
  if &#40;yardcheck==1&#41; then&#40;
   offlawn += 1
  &#41;
  if &#40;checktag &#40;414&#41;&#41; then&#40;
   switch &#40;check tile underfoot &#40;0&#41;&#41; do&#40;
    case &#40;0,119&#41; Finish Netting
   &#41;
  &#41;
 &#41;
&#41;
Hands-On Training
end
Anyway, do whatever you need to keep the checks balanced.

The Functional Scripts:

The remaining scripts relevant to "Scenery Sounds" working properly can be found below. These scripts should be copied directly into your plotscript as they are written, preferably below the scripts that require modification to fit your game specifically. Changing any of them might break functionality, unless you have additional features you want your scripts to achieve through sound, and you can figure out how to add that without messing the other stuff up.

I've tested this. It works for me. It should work for you, too.

Code: Select all

script, sound range, begin
variable &#40;sound types, specific sounds, sound zones, specific zones, tag series, specific tags, base&#41;
base &#58;= fixed sound starter
for &#40;sound types, 1, 3&#41; do&#40;
 for &#40;specific sounds, base, base + &#40;fixed sound range -- 1&#41;&#41; do&#40;
  write global &#40;specific sounds, -1&#41;
 &#41;
 base += fixed sound range
&#41;
for &#40;sound zones, 1, 2&#41; do&#40;
 for &#40;specific zones, base, base + &#40;fixed sound range -- 1&#41;&#41; do&#40;
  write global &#40;specific zones, 0&#41;
 &#41;
 base += fixed sound range
&#41;
base &#58;= fixed sound starter + &#40;fixed sound range * 7&#41;
for &#40;tag series, 1, 2&#41; do&#40;
 for &#40;specific tags, base, base + &#40;fixed sound range -- 1&#41;&#41; do&#40;
  write global &#40;specific tags, 0&#41;
 &#41;
 base += fixed sound range
&#41;
end

script, validate sounds for map, begin
variable &#40;map sound start, local sound start, limited sound start, zone start local, zone start limited, sound condition tag off start, sound condition tag on start&#41;
map sound start &#58;= fixed sound starter
local sound start &#58;= fixed sound starter + fixed sound range
limited sound start &#58;= fixed sound starter + &#40;fixed sound range * 2&#41;
zone start local &#58;= fixed sound starter + &#40;fixed sound range * 3&#41;
zone start limited &#58;= fixed sound starter + &#40;fixed sound range * 4&#41;
sound condition tag off start &#58;= fixed sound starter + &#40;fixed sound range * 7&#41;
sound condition tag on start &#58;= fixed sound starter + &#40;fixed sound range * 8&#41;
play map sounds &#40;map sound start, fixed sound range, true, sound condition tag off start, sound condition tag on start&#41;
play local sounds &#40;local sound start, fixed sound range, zone start local, true, sound condition tag off start, sound condition tag on start&#41;
play local sounds &#40;limited sound start, fixed sound range, zone start limited, false, sound condition tag off start, sound condition tag on start&#41;
match sounds to map
end

script, play map sounds, starter, range, state, tagoff, tagon, begin
variable &#40;specific sounds, temp tag off, temp tag on&#41;
temp tag off &#58;= tagoff
temp tag on &#58;= tagon
for &#40;specific sounds, starter, starter + &#40;range -- 1&#41;&#41; do&#40;
 if &#40;read global &#40;specific sounds&#41; >> -1&#41; then&#40;
  check for map sounds &#40;read global &#40;specific sounds&#41;, state, read global &#40;temp tag off&#41;, read global &#40;temp tag on&#41;&#41;
 &#41;
 temp tag off += 1
 temp tag on += 1
&#41;
end

script, play local sounds, starter, range, zn, state, tagoff, tagon, begin
variable &#40;specific sounds, zone start, temp tag off, temp tag on&#41;
zone start &#58;= zn
temp tag off &#58;= tagoff
temp tag on &#58;= tagon
for &#40;specific sounds, starter, starter + &#40;range -- 1&#41;&#41; do&#40;
 if &#40;read global &#40;specific sounds&#41; >> -1&#41; then&#40;
  check for local sounds &#40;read global &#40;zone start&#41;, read global &#40;specific sounds&#41;, state, read global &#40;temp tag off&#41;, read global &#40;temp tag on&#41;&#41;
  zone start += 1
  temp tag off += 1
  temp tag on += 1
 &#41;
&#41;
end

script, check for map sounds, loc, state, tagoff, tagon, begin
if &#40;validate sound condition &#40;tagoff, tagon&#41;&#41; then&#40;
 play sound &#40;loc, state&#41;
&#41;
else&#40;
 stop sound &#40;loc&#41;
&#41;
end

script, check for local sounds, zn, loc, state, tagoff, tagon, begin
if &#40;zn<>0&#41; then&#40;
 if &#40;validate zone position &#40;zn&#41;&#41; then&#40;
  if &#40;validate sound condition &#40;tagoff, tagon&#41;&#41; then&#40;
   play sound &#40;loc,state&#41;
  &#41;
 &#41;
 else&#40;
  stop sound &#40;loc&#41;
 &#41;
&#41;
end

script,validate sound condition, tagoff, tagon, begin
variable &#40;temp tag off, temp tag on&#41;
temp tag off &#58;= tagoff
temp tag on &#58;= tagon
if &#40;&#40;temp tag off == 0&#41; && &#40;temp tag on == 0&#41;&#41; then&#40;
 exit returning &#40;true&#41;
&#41;
if &#40;temp tag off >> 0&#41; then&#40;
 if &#40;checktag &#40;temp tag off&#41; == false&#41; then&#40;
  exit returning &#40;true&#41;
 &#41;
&#41;
if &#40;temp tag on >> 0&#41; then&#40;
 if &#40;checktag &#40;temp tag on&#41;&#41; then&#40;
  exit returning &#40;true&#41;
 &#41;
&#41;
exit returning &#40;false&#41;
end

script,check for running sounds, begin
variable &#40;specific sounds,map sound start, local sound start, running map sound, running local sound&#41;
map sound start &#58;= fixed sound starter
local sound start &#58;= fixed sound starter + fixed sound range
running map sound &#58;= fixed sound starter + &#40;fixed sound range * 5&#41;
running local sound &#58;= fixed sound starter + &#40;fixed sound range * 6&#41;
compare new and old sounds &#40;map sound start, running map sound, fixed sound range&#41;
compare new and old sounds &#40;local sound start, running local sound, fixed sound range&#41;
end

script,compare new and old sounds, starter, running, range, begin
variable &#40;specific sounds, running sound&#41;
running sound &#58;= running
for &#40;specific sounds, starter, starter + &#40;range -- 1&#41;&#41; do&#40;
 if &#40;read global &#40;specific sounds&#41; <> read global &#40;running sound&#41;&#41; then&#40;
  if &#40;read global &#40;running sound&#41; >> -1&#41; then&#40;
   stop sound &#40;read global &#40;running sound&#41;&#41;
  &#41;
 &#41;
 running sound += 1
&#41;
end

script,match sounds to map, begin
variable &#40;specific sounds, map sound start, local sound start, running map start, running local start&#41;
map sound start &#58;= fixed sound starter
local sound start &#58;= fixed sound starter + fixed sound range
running map start &#58;= fixed sound starter + &#40;fixed sound range * 5&#41;
running local start &#58;= fixed sound starter + &#40;fixed sound range * 6&#41;
for &#40;specific sounds, map sound start, map sound start + &#40;fixed sound range -- 1&#41;&#41; do&#40;
 write global &#40;running map start, read global &#40;specific sounds&#41;&#41;
 running map start += 1
&#41;
for &#40;specific sounds, local sound start, local sound start + &#40;fixed sound range -- 1&#41;&#41; do&#40;
 write global &#40;running local start, read global &#40;specific sounds&#41;&#41;
 running local start += 1
&#41;
end

script, map sound loop, id, map sound id, tagoff = 0, tagon = 0, begin 
variable &#40;map sound start, sound condition tag off start, sound condition tag on start&#41;
map sound start &#58;= fixed sound starter
sound condition tag off start &#58;= fixed sound starter + &#40;fixed sound range * 7&#41;
sound condition tag on start &#58;= fixed sound starter + &#40;fixed sound range * 8&#41;
write global &#40;&#40;map sound start + id&#41; -- 1, map sound id&#41;
write global &#40;&#40;sound condition tag off start + id&#41; -- 1, tagoff&#41;
write global &#40;&#40;sound condition tag on start + id&#41; -- 1, tagon&#41;
end

script, local sound loop, id, local sound id, local zone id, tagoff = 0, tagon = 0, begin 
variable &#40;local sound start, zone start local, sound condition tag off start, sound condition tag on start&#41;
local sound start &#58;= fixed sound starter + fixed sound range
zone start local &#58;= fixed sound starter + &#40;fixed sound range * 3&#41;
sound condition tag off start &#58;= fixed sound starter + &#40;fixed sound range * 7&#41;
sound condition tag on start &#58;= fixed sound starter + &#40;fixed sound range * 8&#41;
write global &#40;&#40;local sound start + id&#41; -- 1, local sound id&#41;
write global &#40;&#40;zone start local + id&#41; -- 1, local zone id&#41;
write global &#40;&#40;sound condition tag off start + id&#41; -- 1, tagoff&#41;
write global &#40;&#40;sound condition tag on start + id&#41; -- 1, tagon&#41;
end

script, limited sound loop, id, limited sound id, limited zone id, tagoff = 0, tagon = 0, begin 
variable &#40;limited sound start, zone start limited, sound condition tag off start, sound condition tag on start&#41;
limited sound start &#58;= fixed sound starter + &#40;fixed sound range * 2&#41;
zone start limited &#58;= fixed sound starter + &#40;fixed sound range * 4&#41;
sound condition tag off start &#58;= fixed sound starter + &#40;fixed sound range * 7&#41;
sound condition tag on start &#58;= fixed sound starter + &#40;fixed sound range * 8&#41;
write global &#40;&#40;limited sound start + id&#41; -- 1, limited sound id&#41;
write global &#40;&#40;zone start limited + id&#41; -- 1, limited zone id&#41;
write global &#40;&#40;sound condition tag off start + id&#41; -- 1, tagoff&#41;
write global &#40;&#40;sound condition tag on start + id&#41; -- 1, tagon&#41;
end
So, let me know in this thread if these scripts help simplify your ambient and local sound needs. Hope you'll use them.

Note: If we're ever given the ability to adjust sound effects volume within scripts, I'll see if I can post an addendum to include that functionality.

EDIT: I'd forgotten to update "sound range" to include conditions to clear globals related to tags (that had been a last-minute addition before I uploaded everything over here). Fixed now. Recopy that one script to your games if you've already ported this.
Last edited by Pepsi Ranger on Sat Jun 09, 2018 3:45 am, edited 1 time in total.
Place Obligatory Signature Here
User avatar
Pepsi Ranger
Liquid Metal Slime
Posts: 1457
Joined: Thu Nov 22, 2007 6:25 am
Location: South Florida

Post by Pepsi Ranger »

Not sure if we have anything like this in the library yet, but here's something I wrote last night to improve the random number generator a bit:

Random Rounding:

An updated version of "random."

Purpose:

Allows you to round your random number to the nearest value of your choosing.

How It Works:

Simply attach the function "by random rounding" to any variable you want to store the result in, and make sure to include your low value, high value, and the nearest whole number you want to round up or down to in the function's arguments.

Example:

Variable := by random rounding (1, 1000, 10)

If the RNG calls value 561 (a value between 1 and 1000), it will round DOWN to the nearest 10, resulting in 560.

This is useful for currency and population values in your game, as well as anything that shouldn't end with a hard-to-quantify number.

The Code:

Code: Select all

script,by random rounding, min = 0, max = 0, whole = 1,begin
variable &#40;base, round, result&#41;
base &#58;= random &#40;min, max&#41;
round &#58;= base,mod,whole
if &#40;round << whole / 2&#41; then&#40;
 result &#58;= base -- round
&#41;
if &#40;round >= whole / 2&#41; then&#40;
 result &#58;= base + &#40;whole -- round&#41;
&#41;
exit returning &#40;result&#41;
end
Random Rounding or Random Calling by Case:

Simplify calling for ranges of random numbers when involving lists or switch cases.

Purpose:

Minimizes or eliminates the headache of manually determining where one number range ends and another begins when creating lists of random numbers or choosing ranges by switch case functions.

How It Works:

Depending on your goal, you would assign one variable for cases or multiple variables for lists, and attach the function "random value calculations" to the variable to return the result of each range to the assigned variable(s). Note that only one result will return from each range. If you want more than one result per range, you'll have to modify the script to return multiple results through a for/do block. Not sure if that's feasible yet. Maybe? I'll modify this post if it turns out it is.

To make sure it doesn't return a compiling failure, be sure to include the previously mentioned script "by random rounding" to your plotscript file.

The function has four arguments: slot, base, variance, and whole.

Slot: This is your range number. It's important that you start with 1 and assign the next instance of "random value calculations" with whatever's next in your preferred sequence. So, if you want the next range to immediately follow the first, then the next instance should be numbered slot 2. If you want the next range to leave a gap, then you should number it 3 instead (or whatever achieves the gap you want).

Base: This is the initial value you want your specific list or case series to begin with. This should be the lowest possible value in your entire series.

Variance: This is the size of each range in the series. This is a uniform number, and every range in the list will keep this number. If you want different ranges for each list item or case, then you'll need to go back to the manual method, or modify the function to call for random variance. It's also the maximum number in the initial range.

Whole: This tells the script how to round up or down the final result. If you want a pure result, then leave this argument blank. It will default to 1, which should leave the final number unspoiled. Someone let me know if my logic is wrong.

Using It in the Game:

Let's say you have five categories of items you want to assign numbers to, but each item comes from a different range of numbers (for example, rusty swords would have an attack value far different than a gold sword, and the two should not risk an overlap). We'll assume we're assigning attack values to five different types of swords, but none of them should overlap. We might do this.

(Assuming we've defined "sword type" constants first...)

Code: Select all

variable &#40;open slot, sword attack value&#41;
switch &#40;sword type&#41; do&#40;
 case &#40;sword type&#58;wooden&#41; open slot &#58;= 1
 case &#40;sword type&#58;rusty&#41; open slot &#58;= 2
 case &#40;sword type&#58;metal&#41; open slot &#58;= 3
 case &#40;sword type&#58;silver&#41; open slot &#58;= 4
 case &#40;sword type&#58;gold&#41; open slot &#58;= 5
&#41;
sword attack value &#58;= random value calculations &#40;open slot, 1, 20, 5&#41;
if &#40;sword attack value << 5&#41; then&#40;
 sword attack value &#58;= 5
&#41;
In this example, depending on which sword we have, the value of "sword attack value" will round up or down to the nearest division of 5 (0, 5, 10, 15, 20) in a range spanning from 1 to 100.

Note: We included the "if" conditional at the end to prevent the worst sword from potentially attacking with zero power. We acknowledge that it should have at least a value of 5. Of course, we could skip that step entirely if we change the "base" argument from 1 to 5. Things worth noting.

The Code:

Code: Select all

script,random value calculations, slot = 1, base = 0, variance = 0, whole = 1,begin
variable &#40;val1, val2, result&#41;
val1 &#58;= base + &#40;variance * &#40;slot -- 1&#41;&#41;
val2 &#58;= base + &#40;variance * &#40;slot -- 1&#41;&#41; + variance
result &#58;= by random rounding &#40;val1, val2, whole&#41;
exit returning &#40;result&#41;
end
If you want a version of this that calls for random ranges, then you can modify it by adding the "flux = 0" argument and "new range" variable, and changing each instance of "variance" to "new range":

Code: Select all

script,random value calculations, slot = 1, base = 0, variance = 0, whole = 1, flux = 0,begin
variable &#40;val1, val2, result, new range&#41;
new range &#58;= random &#40;variance -- flux, variance + flux&#41;
val1 &#58;= base + &#40;new range * &#40;slot -- 1&#41;&#41;
val2 &#58;= base + &#40;new range * &#40;slot -- 1&#41;&#41; + new range
result &#58;= by random rounding &#40;val1, val2, whole&#41;
exit returning &#40;result&#41;
end
Note: Remember that "whole" should be assigned a value of 1 if you want a pure result. You can also switch these two arguments around in the function's definition if you know you want a pure result every time.

Also, you could probably modify the script to account for overlapping ranges, but my attempt to do that while writing this post has proven a bit more complicated than I would have expected, so I may have to post that later, if I come up with a reasonable solution. Ideally, you won't need to do anything like that, however.

That's it for now. Let me know if these are useful.
Place Obligatory Signature Here
Post Reply