Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Topics - erwin

Pages: [1] 2 3
1
Scripting Board / Function triggers / Function of the Day
« on: February 20, 2017, 03:22:49 pm »
Function triggers are something special in 4 Dimensions MUD, as they provide many opportunities to builders and scripters alike. Unlike specific type triggers, eg MOB triggers, OBJ triggers, and ROOM triggers, function triggers can be attached to MOBs, ROOMs, and OBJs without any errors.

The most common usage of function triggers is to "extend" a script. For example, we have a mob that tells a super long story (trigger modified):

Code: [Select]
Name: 'story',  VNum: [ 2350], RNum: [  706]
Trigger Intended Assignment: Mobiles
Trigger Type: Speech , Numeric Arg: 1, Arg list: story stories
Commands:
if %actor.is_pc%
  wait 5s
  %send% %actor% The mob tells a story...
  * ...
  * ...
  * some time later
  function 2356
endif

What does function 2356 do? Well, it continues the story

Code: [Select]
Name: 'Story Conclusion',  VNum: [ 2356], RNum: [  712]
Trigger Intended Assignment: Mobiles
Trigger Type: FUNCTION , Numeric Arg: 0, Arg list:
Commands:

emote takes a deep breath.  Will he finally finish?
wait 10s
%send% %actor% blah blah blah
*...
*...

So you could replace "function 2356" in trigger 2350 by the entire DG script in 2356.

For those with some background in programming, you might say: "That's not all functions can do", and you'd be right. So we'll give examples on how to use functions in this forum post

2
Scripting Board / Questjournal OLC
« on: February 09, 2017, 08:18:03 pm »
Here's a brief description of what to do (in practice) with the Questjournal OLC. If there are any questions, feel free to shoot Roland, Maco, or me a note.

Current Questcards will be in this post ; New questcards will be in a separate post.

The goal of the questjournal is to:
  • Prevent spam / too many single questflags when you vstat a player
  • Provide some information to any imms when trying to debug a quest. Eg, is it a logic problem in a quest, or typo problem in a quest, or some other issue?
  • Allow players to track their quest status
  • Allow players to track their trading status
  • Allow players to reset questflags

The following post gives design suggestions on how to transfer current questcards to the quest journal.

Essentially, you need to know the zone in and out.

First, you need to know what ALL the flags are, and how they should be replaced with a new flag. The new flag should be something of the form "questcard<num>_<questname>" etc so it's easier to track flags.

Second, you need to plan how many questflags you want to have, and how the variables in the questflag will change based on the player's status in the quest, and which variables to reset. You should do this before editing any triggers, etc.

Third, while planning the questflags, you should also think of constructing a debug mode trigger at the same time. This helps to prevent errors.

Fourth, you want a mechanism that replaces the flags properly. Remember that questflags are checked when a player interacts with any mob, room, or object that is part of the quest. Function triggers are useful here if you don't want to duplicate code, and prevent errors.

Fifth - extensive testing on 6001 to make sure that every step of the quest / every possible combination works (or doesn't work). Ensure that the debug trigger tells the imm who is debugging where the player is, and what the player needs to do. More information is always better.

A warning for old-> new flag replacing.

Old->New flag replacing

If a player types qcheck <num> <something>, the quest journal will check if the player has the questflags given in the OLC. If not, the quest journal will auto set them. This is great for new quests, but bad for old quests.

Why is this? For example, we will look at Quest 6 (Thetis' Lover). The old quest has 11 questflags, thetis1, thetis2, ... thetis11.

Previously, at a stage, the code would look something like this:

Code: [Select]
if %actor.varexists(thetis2)%
  %echo% You are on stage 2!
else
  %echo% Good day, %actor.name%
endif

Of course, what we really need to do is to create a variable (maybe thetis) - with and set it to different values for each stage, eg
Code: [Select]
set thetis 2
remote thetis %actor.id%
because then we could just do:
Code: [Select]
if %actor.varexists(thetis)%
  if %actor.thetis% == 2
    %echo% You are on stage 2
  endif
endif

So far - this makes sense, right?

In the previous version where we had a quest journal, we put the replacing of flags in a function. Here is how this would have looked like.
Code: [Select]
* Assume this is trigger 12345 as a function

if !%actor.varexists(questcard6_thetis)%
  set questcard6_thetis 0
  if %actor.varexists(thetis1)%
    set questcard6_thetis 1
    rdelete thetis1 %actor.id%
  endif
  if %actor.varexists(thetis2)%
    set questcard6_thetis 2
    rdelete thetis2 %actor.id%
  endif
  if %actor.varexists(thetis3)%
    set questcard6_thetis 3
    rdelete thetis3 %actor.id%
  endif

  * etc etc

  remote questcard6_thetis %actor.id%
endif
etc all the way till thetis11. Note that while the code looks long, and would have eleven if checks (for thetis1 to thetis 11), the first if branch is only ever executed once.

So what would usually be done is this:

Code: [Select]
* Create Thetis flag, and replace questflag
function 12345
if %actor.questcard6_thetis% == 2
  %echo% You are on stage 2
endif

Notice that in the above code, we did not check if the variable questcard6_thetis existed, because our function 12345 would have created this for us automatically if it did not exist. Furthermore, the time taken for the MUD to execute this is almost the same time if we had just replaced function 12345 by
Code: [Select]
if %actor.varexists(questcard6_thetis)%

However: We have a problem when the quest journal auto puts the flags on players. The replacing does not take place. So players may have duplicate flags - both old flags and new flags.

Ahhah, you say. But that is an easy fix - we can move the inner if statements out, eg:
Code: [Select]
* new function

if !%actor.varexists(questcard6_thetis)%
  set questcard6_thetis 0
  remote questcard6_thetis %actor.id%
endif

if %actor.varexists(thetis1)%
  set questcard6_thetis 1
  remote questcard6_thetis %actor.id% 
  rdelete thetis1 %actor.id%
endif

if %actor.varexists(thetis2)%
  set questcard6_thetis 2
  remote questcard6_thetis %actor.id% 
  rdelete thetis2 %actor.id%
endif

if %actor.varexists(thetis3)%
  set questcard6_thetis 3
  remote questcard6_thetis %actor.id% 
  rdelete thetis3 %actor.id%
endif

* etc etc

But in this case - each time the actor is on any part of the quest chain and this function is called, eleven if statements will be checked, potentially adding lag to the MUD.

What's the solution?

We have come up with a nifty way to add all questflags on the player once they: go into Limbo, walk into Recall, die, or use the Time Guardian. Of course, you have to send a note to Pix (or Roland) what the new questflags are, and how the old questflags should be replaced. It's even better if you give us a trigger that does this in the first place.

PS: If you are a player when a current quest is added to the quest journal, we suggest you walk to Recall / use the Time Guardian. Otherwise, you might get the new set of flags which signal that you haven't done a quest, but without the replacing of old flags done. While this might allow you to get more token rewards, it also means if you remove and wear a quest equip, it might purge itself.

3
Suggestions & Ideas / Questjournal with cards 1-22
« on: January 07, 2017, 05:38:50 am »
Hi all,

Molly says if we get 50% of questcards done, we can post this on Mudconnect as this is something unique. Currently, Questcards 1-22 are done. So, instead of sitting at Recall, you can go 2 s to Bart, and say 'journal', or 'javert and chauvelin' to get a quest journal.

You can then (for the first 1-22 quests), check the following, and post here if there's any problems, etc.

1. Do all commands with questcard work?
   - questcheck card<num>
   - questcheck card<num> status
   - questcheck card<num> reset
   - questcheck card<num> {things particular to this card?}

2. Does the Quest Tree work?
   - can you complete the quest? (major)
   - does quest journal update correctly? - check this for every step you take.
   - are flags transferred over from previous quest (some old quests could be repeated multiple times for same reward, so there was never a flag)
   - does doing "wrong" things screw up the quest (this is probably more for builder of original quest rather than questjournal - we have reset. but good to debug)

3. Formatting (low priority)
   - Is the formatting okay? Typos?

4. Debug mode / Documentation (imm only)
   - Is it clear?
   - Is it easy to fix errors?

4
Suggestions & Ideas / Questjournal - Understanding and Debugging Quests
« on: January 05, 2017, 08:29:52 pm »
Hi all,

This post is meant to illustrate how to debug quest scripts with the questjournal. Writing triggers for the questjournal itself (and debugging them)will be in another post.

PS (sorry Roland, Thotter in advance) - feel free to add on to this post, clarify it if I skip some points.

questflags now look like this: questcardx_questname, for example, there's:

Code: [Select]
questcard7_viking
questcard19_riddle

To find all quest related triggers pertaining to that questcard, you can simply do
Code: [Select]
tsearch all questcardxx, example:
Code: [Select]
*find triggers related to questcard 19
tsearch all questcard19

All flags related to this quest will be in this flag. Sometimes, there may be subflags, eg
Code: [Select]
questcard19a
questcard19b
...

Most flags follow the below style. Here, we have a fake flag (questcard200_myquest), and show how it might look like, when you search for this flag on a player.

Code: [Select]
questcard200_myquest: noprog 0 notdone inprog 2 notdone inprog 3 done noprog 0 done
How do you read this? You can think of this in "groups of 3", so in this zone, there are four quests.

For the first quest, the player is not on the quest, is at step 0, and hasn't completed it.
For the second quest, the player is on the quest, is at step 2, and hasn't completed it.
For the third quest, the player is on the quest, is at step 3, and has completed the quest at least once.
For the fourth quest, the player has completed the quest, and hasn't restarted it again.

Think of noprog / inprog as noprogress and inprogress, notdone and done as whether the player has completed the quest at least once.

So we have two benefits already: First, flags are consolidated into one flag (less spam when you vstat a player's flags). Secondly, you can tsearch questflags easily by tsearch questcardxx.

However, because there are "multiple flags" on one flag (multiple variables in one array), things are a bit trickier.

Previously, you could do something like:
Code: [Select]
if !%actor.varexists(hanna)%
  * Player doesn't have this flag
  %send% %actor% You are not on this quest!
  %send% %actor% I will put you on this quest!
  set hanna 1
  remote hanna %actor.id%
else
  * Player has this flag
  set hanna %actor.hanna%
  if %hanna% == 1
    %send% %actor% You are on step 1. Let me progress you to step 2
    set hanna 2
    remote hanna %actor.id%
  elseif %hanna% == 2
    %send% %actor% You are on step 2. Let me progress you to step 3
    set hanna 2
    remote hanna %actor.id%
  endif
endif

How can you do this now? Let's imagine Hanna is the third quest for this zone, so we need to look at the third group, i.e. the group in bold here.

questcard200_myquest: noprog 0 notdone inprog 2 notdone inprog 3 done noprog 0 done

Let's look at a WRONG way to do this first.

Code: [Select]
* A wrong way
if !%actor.varexists(questcard200_myquest)%
  %send% %actor% You are not on this quest!
  %send% %actor% I will put you on this quest!
  set questcard200_myquest noprog 0 notdone noprog 0 notdone inprog 1 notdone noprog 0 notdone
  remote questcard200_myquest %actor.id%
endif

You might think the above code makes sense, since you're setting just the variables for the third quest. But, what if the player was on the other quests? You'd be resetting his or her progress.

Thus, every questcard now has up to three FUNCTIONS. A room function, a obj function, and a mob function. You can tsearch to find these functions, which look like this, and each have identical code (only the type - room, obj, mob differs).

Code: [Select]
* Example of an object function
Name: 'Questcard200 obj function',  VNum: [12345], RNum: [----]
Trigger Intended Assignment: Objects
Trigger Type: FUNCTION , Numeric Arg: 100, Arg list: None
Commands:
* Create this questflag if it doesn't exist
if !%actor.varexists(questcard200_quest)%
  set questcard200_quest noprog 0 notdone noprog 0 notdone noprog 0 notdone noprog 0 notdone
  remote questcard200_quest %actor.id%

  * To reassure people that their old flags aren't deleted
  * suppose they completed quest 1 before
  if %actor.varexists(oldflag1)%
    replace_word done 3 questcard200_quest
    rdelete oldflag1 %actor.id% 
  endif
 
  remote questcard200_quest %actor.id%
endif

Now, this is what you can do:
Code: [Select]
* Need to check if is PC before calling any functions.
if %actor.is_pc%
  *For any trigger which deals with questflags 
  *Call the appropriate function (room, mob or obj?)
  *Pretend it's an object so call the function above

  * This function sets the questflag ONCE, if it doesn't exist.
  function 12345
  * The below line can't bug, because we've created the flag if it didn't exist.
  set questcard200_quest %actor.questcard200_quest%

  * Now, proceed as normal, eg:

  if %questcard200_quest.wordat(7)% == noprog
    %send% %actor% You are not on this quest.
    %send% %actor% I will put you on this quest
    %send% %actor% I will replace the 7th word in your flag to show you are now in progress
    replace_word inprog 7 questcard200_quest
    %send% %actor% I will replace the 8th word by 1 to show you are now on step 1 for this quest
    replace_word 1 8 questcard200_quest
    remote questcard200_quest %actor.id%
  else
    if %questcard200_quest.wordat(8)% == 1
      %send% %actor% You are on step 1
      replace_word 2 8 questcard200_quest
      remote questcard200_quest %actor.id%
      %send% %actor% Now you are on step 2
    elseif %questcard200_quest.wordat(8)% == 2
      %send% %actor perfect, let's give you a reward
      if %questcard200_quest.wordat(9)% == notdone
        %send% %actor% You haven't done this quest before, so I'll give you a big reward!
        replace_word done 9 questcard200_quest
      else
        %send% %actor% You've done this quest before, so a smaller reward!
      endif
      * Reset all other questflags for this quest so
      replace_word 7 noprog questcard200_quest
      replace_word 8 0 questcard200_quest
      remote questcard200_quest %actor.id%
    endif
  endif
endif

So this is how the code would look like. Finally - how can we debug?

Here's some common errors.

Firstly, the trigger is not enclosed within:
Code: [Select]
if %actor.is_pc%
  ...
endif

Previously, you didn't need to have this check. Now you always need to have it.

Secondly, the WRONG type of function is called. We called function 12345, which was an Object function. If the trigger was a Mobile trigger, then you must call an Mobile function. If the trigger was a Room trigger, then you must call a Room function. Again, these three functions would have identical code, just different types.

Thirdly, logic wise. In this case, this is usually a problem with the old quest. As you can see, quests have the same logic. However, we have just used replace_word and wordat to work with arrays.



Finally, you may have a question. Hold on: How do I know if this player's quest is the THIRD quest or the FOURTH quest? Good question! Imms have a command (note me if you can't get this to work): questcheck card<num> debug <playername>, which actually goes into way more detail what each variable represents.





5
Scripting Board / Suggested Practices for Scripting
« on: December 05, 2015, 11:42:32 pm »
In the past, we had lots of good practices for scripting.

For example, here's some best practices in the past:

1. The difference between set and eval and when to use them
2. The difference between %actor% versus %actor.name%, and when to use them.
3. mjunk an object (instantly) when the player gives it to a mob so he can't steal it back
4. Setting a flag when the player completes the quest, so the rewards are lesser the second / subsequent time the player completes it

However, I think we can have more "suggested practices for scripting", as well as use the 4D only DG functions which Mordecai / Rynald / others have coded in for future best practices.

These "suggested practices" will be focused on debugging and understanding scripts, in the context of the builder fixing his/her own scripts a few weeks / months after he/she worked on it, as well as someone else fixing the script if the previous builder has retired.

I will first describe what imms face when debugging. For example, Molly made a post here

http://4dimensions.org/forum/index.php/topic,682.msg5045.html#msg5045

which shows the questflags a player has. Here's some potential issues (maybe):

a) The list is quite long
b) Some variable names are not intuitive. For example, the variable zeddletter versus jack versus asking. For the first variable, this depends on how much the person knows about the MUD. He might make a guess that it's from the Midlands zone, and is given to the player when he interacts with Zedd about a letter (giving a letter / receiving a letter / asking him to write a letter?). Having knowledge about the quests in the MUD would give the player the knowledge of what the variable does, so that's fine.

How about the other two variables? Well, a solution would be to tsearch all jack, or tsearch all asking to find the respective scripts, and then try to figure out what they do. That takes effort, but can we do better?

Thus: Suggested Practices for Scripting shall be about intuitive variable names, and intuitive variable values. I'll try to present this in a way of "Problem", and then the "Solution", as suggested best practices.

Problem: Variable names are not intuitive. For example, asking is less intuitive than zeddletter

Solution: Create intuitive variable names

Problem: There are five helper vouchers. Suppose upon picking each one up, we store the value as 1, and we have the five variables

Code: [Select]
helper_voucher_1: 1
helper_voucher_2: 1
helper_voucher_3: 1
helper_voucher_4: 1
helper_voucher_5: 1

on the player. Once goes through the Newbie School, he is convinced he picked up all 5 vouchers, but lost them in a DT. Poor Once. He tells Kvetch: You can vstat me, and search for these 5 variables. Unfortunately, vstat doesn't show the variables in alphabetical order, nor the time the variable was created. Must Kvetch painstakingly look at all his variables and find the 5 of them in 15 pages? What if Once didn't get the 4th voucher, but Kvetch thinks he has, so she tries to find it? Or worse, what if Once did get the 4th voucher, but because of the spam, Kvetch didn't see it?

Solution 1: Ask a coder to fix vstat to show whether the player has the variable or not.
Solution 2: Use an array and store the values in one variable, called helper_voucher

Code: [Select]
helper_voucher: found found not_found found not_foundor
Code: [Select]
helper_voucher: 1 0 1 1 0

Problem: Once goes to the Midlands, and does a quest. He thinks it's bugged, and he wants to know the variables relating to the zone, so he can fix it himself in the BP. Unfortunately, apart from zeddletter, he doesn't know what other variables on his pfile relate to the Midlands. He wonders if he should search through the "quest actions" done, or the names of the mobs in the Midlands just in case the builder used that as a variable?

Solution: Have the variable name include the zone as well. Even if the builder / imm online doesn't know anything about the quest, he or she can say: "This is the variable / these are the variables related to the zone, I'll send this on to another imm."

In other words, incorporate the zone name in the variable.

Examples of two variables:
Code: [Select]
crete_zone: not_killed_minotaur killed_theseus bullgirl_not_done
and
Code: [Select]
crete_zone: killed_minotaur killed_theseus bullgirl_done

Most imms / players who see that flag would immediately know what the player has done (or not done) in the zone. 

Thus, a suggested practice for scripting is to minimize the number of variables by using arrays, and replace_word.

6
Scripting Board / Trading Scripts
« on: December 01, 2015, 03:09:43 am »
Here's one way to do a trading script, which would account for lots of different items being traded, and rejected.

However, before the script is posted, let's think about how the script would work. In the old days, the script would be something like (in pseudo code):
Code: [Select]
if %item.vnum% == 100 || %item.vnum% == 101
  %send% %actor% Thank you for trading item number 100 or item 101!
  %send% %actor% Let me give you a reward
elseif %item.vnum% == 103
  %send% %actor% Thank you for trading item number 103!
  %send% %actor% Let me give you a reward
elseif %item.vnum% == 105
  %send% %actor% Thank you for trading item number 105!
  %send% %actor% Let me give you a reward!
elseif %item.vnum% == 110 || %item.vnum% == 114 || %item.vnum% == 120
  %send% %actor% Sorry, I don't take this type of items
  %send% %actor% Go away!
endif

However, we see that a lot of things are repeated. For example, for a correct item, the code is always of the form:
Code: [Select]
if %item.vnum% == "correct vnum"
  mob gives some reward for the item
  mob says thank you
endif

For example, if the mob can receive 10 different items, then for each item, we might have the (duplicated) code
Code: [Select]
wait 1s
mjunk %object%
say thank you
give TP reward
give XP reward
10 items would mean 50 lines of duplicated code. This takes up a lot of space. Can we do better? (yes).

So here's a way to do it

Code: [Select]
* First, set the number of items that the mob trades. Suppose the mob is a jeweller, and trades the six items only
* The numbers correspond to the correct vnums
* Speech is optional - could have lots of variables for this, just make sure the last "character" of the speech is the
* same number of the item. But if you want the mob to say the same thing for each item
* then in the loop below, could just "say thank you".

* Item 1 corresponds to diamonds
set item1 111 112 113 114 120
set speecha1 thank you for the diamonds
set speechb1 blah blah love you

* Item 2 corresponds to rubies
set item2 200 201 214 206
set speecha2 thank you for the rubies
set speechb2 blah blah love you


* Item 3 corresponds to garnets
set item3 300 301 333 399
set speecha3 thank you for the garnets
set speechb3 blah blah love you

* Item 4 corresponds to sapphires
set item4 400 420 490
set speecha4 thank you for the sapphires
set speechb4 blah blah love you


* Item 5 corresponds to quartz
set item5 500
set speecha5 thank you for the quartz
set speechb5 blah blah love you


* Item 6 corresponds to topaz
set item6 666 669
set speecha6 thank you for the topaz
set speechb6 blah blah love you


* Set the maximum items the mob trades, in this case 6
set max_items 6

* Set the rewards for each of them. So item 1 (diamonds) would get 1 TP and 1000 xp, and item 6 (topaz)
* would get 9 TP and 4000 xp

set tp_reward_vec 1 4 3 2 4 9
set exp_reward_vec 1000 3000 1000 1000 4000 4000

* You can set rejected items as well, for example, suppose the mob rejects fool's gold and iron pyrite

* rej_item 1 corresponds to fool's gold
set rej_item1 1000 1005 1110
set rej_speech1 I don't want fool's gold.

* rej_item 2 corresponds to iron pyrite
set rej_item2 2000 2222
set rej_speech2 I don't want iron pyrite.

* Set maximum number of rej items. In this case, 2
set rej_max_items 2

* Now comes the main part

set currvnum %object.vnum%

* We want to find which item it is, so we loop through all items (in item1 to item 6)
set traded_item 0
set i 1

while %i% <= %max_items%
  * look at first itemvec
  * Need to eval this!
  eval item_to_check %%item%i%%%
 
  set j 1
 
  while %j% != 0
    extract curr_item %j% %item_to_check%
    if !%curr_item%
      * Loop will stop
      set j 0
    else
      if %curr_item% == %currvnum%
        set traded_item %i%
        break
      endif
      eval j %j% + 1
    endif
  done
  if %traded_item% != 0
    eval i %max_items% + 1
  endif
  eval i %i% + 1
done

* This will create a variable traded_item which will tell you what item it is
* For example, if traded_item is 4, then the player gave sapphires
* If the traded_item was 0, means it was none of the 6 possible items

if %traded_item% != 0
  *Great, no rejected items. Now to find which one
  wait 1
  mjunk %object%
  * evals the correct speech corresponding to the traded item
  eval tosaypartone %%speecha%traded_item%%%
  eval tosayparttwo %%speechb%traded_item%%%

  say %tosaypartone%
  say %tosayparttwo%
  wait 1
  * Reward goes here, corresponding to the traded_item
  set tp_reward %tp_reward_vec.wordat(%traded_item%)%
  nop %actor.trade(%tp_reward%)%
  eval d %actor.level%*%exp_reward_vec.wordat(%traded_item%)%
  nop %actor.exp(%d%)%
 
  * Halt this script if we have successfully traded! 
  halt
endif

* If traded_item is zero,  then it's a rejected item
return 0
wait 1
emote makes a refusing gesture.

* Check if it was either fool's gold or iron pyrite, same loop as above
set rej_traded_item 0
set i 1

while %i% <= %rej_max_items%
  * look at first itemvec
  * Need to eval this!
  eval item_to_check %%rej_item%i%%%
  set j 1 
  while %j% != 0
    extract curr_item %j% %item_to_check%
    if !%curr_item%
      * Loop will stop
      set j 0
    else
      if %curr_item% == %currvnum%
        set rej_traded_item %i%
        break
      endif
      eval j %j% + 1
    endif
  done
  if %rej_traded_item% != 0
    eval i %rej_max_items% + 1
  endif
  eval i %i% + 1
done

* same thing

if %rej_traded_item% != 0 
  eval tosay %%rej_speech%rej_traded_item%%%
  say %tosay%
  halt
else
  say This is a generic statement saying I don't like your item.
endif

What are the benefits of this script?

1. You can trade LOTS of different items. So if we traded 50 items, writing the code the long way might mean duplicating 50*5 = 250 lines of code.

With this script, we save on 250 lines of code (there is extra code because of the loop, but that has a constant number of lines whether you trade 5 items or 500 items).

2. You don't need lots of ifchecks like
Code: [Select]
if %object.vnum% == 1000 || %object.vnum% == 1001 || %object.vnum == 1002
for every item. For example, did you notice in the code above, I left out a % in %object.vnum == 1002? Hard to spot such errors! However, with the above script, all a builder needs to do is to edit the first few lines. Fewer chances of mistakes.

Yay!

7
Scripting Board / How to build quests using arrays
« on: November 30, 2015, 07:51:55 pm »
To cut down on flag spam, here's how you can build quests using arrays, and a suggested method of doing so.

First - decide on the logic of the quest, as well as the variables needed for the quest. All this goes in one array.

Second - create a function (or three functions - a mob function, a room function, and a obj function) with the same script, which sets up the array. Here's an example (for a quest that wants you to kill the captain, find one helper's voucher, and to rescue a dog). Create the "basic" quest array which is when nothing is done for the quest.


Code: [Select]
if !%actor.varexists(my_quest)%
  set my_quest no_captain no_voucher no_rescue no_prog not_done
  remote my_quest %actor.id%
endif

The array (when someone vstats a player) will look like this:

Code: [Select]
my_quest: no_captain no_voucher no_rescue no_prog not_done

All such quest arrays should have two extra variables at the end, namely no_prog, and not_done, to signify the quest has not been started (no progress), and the quest has not been completed at least once (not_done). Obviously, you can re-name these variables to be anything you like, but it makes it easier for future people to debug your quest logic if you are not around on the MUD.

Now, suppose:

function 1 - corresponded to a ROOM function
function 2 - corresponded to a MOB function
function 3 - corresponded to an OBJ function

To start the quest, suppose it was a ROOM trigger that started the quest.
Code: [Select]
function 1
%echo% The room says: I will give you a quest.
set my_quest %actor.my_quest%
replace_word in_prog 4 my_quest

Equivalently:
Code: [Select]
function 1
%echo% The room says: I will give you a quest.
set my_quest %actor.my_quest%
if %my_quest.wordat(4)% != in_prog
  replace_word in_prog 4 my_quest
  remote my_quest %actor.id%
endif

To kill the captain, suppose it was a death script on the mob. This means that we are going to use the function corresponding to a mob function. Code would just be:

Code: [Select]
function 2
* If the actor did not have the variable, he has it now
set my_quest %actor.my_quest%
* replaces killed_captain with the first word in my_quest
replace_word killed_captain 1 my_quest
remote my_quest %actor.id%

See - no messy if statements needed. Or, to be a bit more efficient,
Code: [Select]
function 2
* If the actor did not have the variable, he has it now
set my_quest %actor.my_quest%
if %my_quest.wordat(1)% != killed_captain
  * replaces killed_captain with the first word in my_quest
  replace_word killed_captain 1 my_quest
  remote my_quest %actor.id%
endif

Or better still:
Code: [Select]
function 2
* If the actor did not have the variable, he has it now
set my_quest %actor.my_quest%
* Only sets the flag if actor started the quest - else killing the captain won't register, muahahahaha
if %my_quest.wordat(4)% == in_prog
  * replaces killed_captain with the first word in my_quest
  replace_word killed_captain 1 my_quest
  remote my_quest %actor.id%
endif

Note that the the snippets of code above for giving the quest / killing the captain are the same. One snippet doesn't do an if-check and updates the variables regardless of the value, but the other does an if-check. So for builders who doesn't understand if-checks, they can choose the first option for both and no bugs!

You do the same for other scripts. In fact, for any script which checks for that quest variable, calling the appropriate function (function 1 if it's a room script, function 2 if it is a mob script, and function 3 which is an obj script) and checking for the quest variables and replacing them works.

Then finally (suppose this script was on a mob):

Code: [Select]
function 2
set my_quest %actor.my_quest%
if %my_quest.wordat(1)% == killed_captain && %my_quest.wordat(2)% == got_voucher && %my_quest.wordat(3)% == rescued_dog
  * yay, let's check
  if %my_quest.wordat(5)% != done
    * first time doing this quest
    * %echo% great job, have this big reward
    replace_word done 5 my_quest
  else
    %echo% You have done this before, so here's this small reward
  endif
  * now, reset all questflags (in practice, good to have this as a function as well)
  replace_word 1 no_captain my_quest
  replace_word 2 no_voucher my_quest
  replace_word 3 no_rescue my_quest
  replace_word 4 no_prog my_quest
  remote my_quest %actor.id%
endif

Equivalently, instead of replacing all 4 words, you can just do
Code: [Select]
set my_quest no_captain no_voucher no_rescue no_prog done
remote my_quest %actor.id%
but - be careful if you store other variables on this flag (eg, maybe whether a person has a zoneflag or not. Then it gets messy - so replace_word is the better option)

What is the in_prog / no_prog variable for? Well, the quest journal shows active quests. So to check if a quest is in progress, the quest journal just checks for a in_prog / no_prog flag. in_prog flag means the player is doing this quest, else the player isn't. Basically, when the player starts off the quest, but hasn't completed anything yet.

8
Suggestions & Ideas / Questjournal v1
« on: November 23, 2015, 12:35:00 pm »
The first batch of questcards for the quest journal has been released. They're also all quests in the Prehistoric era. Now's the time to test and see if you can find any errors / try to break them and abuse loopholes if any :)

To get the quest journal, go to Bart (in recall, not in SF), and say javert and chauvelin. He will give you the quest journal.

The current questcards that are available are:

Questcard 33
Questcard 35
Questcard 39
Questcard 81
Questcard 118
Questcard 121 

Have fun, and happy questing!

If there are any bugs / typos / suggestions / etc feel free to post them here, unless it involves a quest - in that case send pix a note.


9
Scripting Board / What are arrays, and why should we use them?
« on: October 24, 2015, 02:40:34 am »
Molly asked!

The easiest way to think of arrays (in a non coding context) is to think of sentences. For example, most variables are set to "one word / one number / one character" values.

PS. I'm not sure if they exist, but are there dg "replace" code which replaces items / characters in an array/string, as well as dg_code to find the length of the array? If yes, could any coders give the syntax, much appreciated! If not, would it be possible to code it in? Thanks!

PPS. Hashsets in DG?

Code: [Select]
* Disclaimer: These are not real quest variables, but how things could be
robinhooddagger: done
killedgreenie: 1
highscore: 12345
gotvoucher1: yes
gotvoucher2: yes
gotvoucher3: yes
gotvoucher4: yes
gotvoucher5: yes

But variables with "one word / one number / one character" entries give too much clutter - eg type vstat player virisin. One solution would be to store arrays as variables, and make the array as intuitive sounding as possible.

For example, here are some variables as arrays. Which one do you think is more intuitive?

Code: [Select]
helpervoucher: to be or not to be
helpervoucher: 1 0 0 1 1
helpervoucher: yes yes no no yes
helpervoucher: found found notfound found notfound


So here are two ways how to use arrays, for simple quests.

One way is to store items found on one variable. Eg, you might have a quest that wanted you to find five items.
Code: [Select]
*Do something to trigger the quest
set myfirstquest notfound notfound notfound notfound notfound incomplete
remote myfirstquest %actor.id%

* This gives a variable that looks like this
myfirstquest: notfound notfound notfound notfound notfound incomplete

Now, we simply change the appropriate "word" in the variable. If the player finds the third item, then we just change the third notfound to found. If the player has completed this quest at least once, then we change the incomplete to complete.


A second way that arrays can be used is to store things on a trigger. For example, you might have a quest that wants you to give the mob some grapes. But there are lots of grapes. One possible trigger could be:
Code: [Select]
if (%object.vnum == 10) || (%object.vnum == 11) || (%object.vnum% == 12)
  do something
endif

However, this becomes clunky if there are lots of possible objects. Furthermore, there's a max number of characters per line. Here's a cleaner way
Code: [Select]
set correctobjects 10 11 12

* Technically, don't need numobjects unless you want to be fancy
* Could always set numobjects = 3 manually below
set numobjects 3


set j 0
while %j% < %numobjects%
  eval j %j% + 1
  * This extracts the jth number of correctobjects.
  * If j is 1, it will get the number 10. If j = 2, it will get the number 11. If j = 3, it will get 12
  extract objecttocheck %j% %correctobjects%
  if %objecttocheck% == %object.vnum%
    %echo% Correct object, hooray
    set gotcorrect 1
  endif
done

if !%gotcorrect%
  %echo% We didn't get a correct object.
endif

You might say: "Hey, this actually results in a more lines." Yes, you're right, it does. But there are three advantages to this.

First advantage - You only need to edit the variables numobjects and correctobjects. For example, if you added a new vnum 12345 you just need
Code: [Select]
set correctobjects 10 11 12 12345
set numobjects 4
compared to
Code: [Select]
if (%object.vnum == 10) || (%object.vnum == 11) || (%object.vnum% == 12) || (%object.vnum% == 12345)
There's fewer chances of mistakes, even though it's just changing one line. Furthermore, some clients have terrible wrapping, and you might get
Code: [Select]
if (%object.vnum == 10) || (%object.vnum == 11) || (%object.vnum%
 == 12) || (%object.vnum% == 12345)
which screws up the code.

Second advantage: There's only a maximum number of characters per line. It's harder to go over the limit using arrays.

Third advantage: If the quest wasn't just to give grapes, but to give grapes, nuts, clothes, gems, etc, then you'd be repeating a lot of code. Take a look at http://4dimensions.org/forum/index.php/topic,682.msg6927.html#msg6927

It's just easier to edit the first part - everything else follows automatically from the code. For example:
Code: [Select]
Note Number:   9
       From:  Tor
    Subject:  Quest doesn't work
     Posted:  Tue Oct 21 03:23:42 2015
         To:  imm
=================================================

The questmob wanted a list of items.
1. An emerald
2. A ruby
3. A garnet
4. An onyx

However, I gave the mob some items, such as
emerald from Thandar, Arabian Desert, Flying Fortress
ruby from Dragon Island ...
garnet from ....
onyx from ...

If you've received this note, and the builder didn't use arrays you could edit the trigger, which would take extremely long - because you want to search through the script to find where the if statements occur, find the syntax the builder used, and add them in.

Or, if the builder just used an array like the trigger of the day, all you need to do is to just
Code: [Select]
set questitem1 123 153 167 14678 21563
set questitem2 642 7434 2359 4928 9872
set questitem3 6982 7939 1765 53

and it's fixed!

10
Scripting Board / Card Game Functions
« on: October 18, 2015, 04:00:34 pm »
There's been talk about scripting card games (here, a standard deck of 52 cards), and I think it would be nice to consolidate all functions we would need for all possible card games. This means reusable functions for games (not just limited to Poker).

Since there are two main types of games (suit taking games - easier to script), and hand winning games (pairs, two pairs, straights, etc - harder to script), here's what I propose for functions in order to build such games. Feel free to add / suggest better functions, with the idea that "using these functions will allow anyone to script any kind of card game - not just limited to poker / hearts"

1. A shuffle function

with two inputs. These inputs are two (variables) arrays, corresponding to

Code: [Select]
SuitArray:  2 3 4 5 6 7 8 9 10 J K Q A 2 3 4 5 ....
NumArray: C C C ... C D  .. D S .. S  H .. H

The function SHUFFLES these cards, and gives the shuffled output as two arrays:

Code: [Select]
ShuffledSuitArray:  ....
ShuffledNumArray: ....

The purpose of this function is to shuffle cards randomly. Scripts can be used to "divide" the shuffled cards up according to player.

2. An "arrangement" function.

Takes two inputs SuitArray and NumArray (both must be of the same length, but not necessarily 52. These would correspond to cards in a 2 player game (26 each)  4 player game (13 each), poker (5 each), etc).

Gives four outputs, namely:

Code: [Select]
ArrangedBySuitSuits:  C C C D D
ArrangedBySuitNums: 4 5 6 2 8
ArrangedByNumSuits: 2 4 5 6 8
ArrangedByNumNums: D C C C D


The purpose of this function is to cater for different type of games, where arranging by numbers / arranging by suits would be more convenient.

3. A "presentation" function for the above arrangement. Basically, the function takes in any of the above four inputs, and presents the cards to the players in a readable way. This will probably differ based on the type of game


Functions 4-12

With the above three functions, we can now customize our functions. For hand winning games, I suggest scripting the nine functions which correspond to the nine poker hands - Straight Flush, etc ... with the following four inputs - note that this function is NOT restricted to 5 elements in an array. Obviously with checks, eg, can't get a Flush if you had only 3 cards to compare, etc:

Code: [Select]
FirstPlayerSuit   H H H S S
FirstPlayerNum  2 5 6  3 6
SecondPlayerSuit:  D D D D D
SecondPlayerNum: 4 5 6 7 8

These functions would compare the hands of two players, and output a string with the player which has a higher hand. If neither player has a hand of that type, output null (or NA). Furthermore, make these 9 functions in running order - hopefully for some vnum xxx01 to xxx09 (for games which rely on placing a same poker hand, or one with higher value) - so if a person placed a hand of type 05, we can loop through for 06-09.


With the above 12 functions, we can script most games. For example, if we wanted to script a Poker Game, then we write a comparison function, eg below.

13. A comparison function. Given the arrays of X number of players, and an array PlayerName, eg:

Code: [Select]
XerxesSuit   H H H S S
XerxesNum  2 5 6  3 6
TorSuit:  D D D D D
TorNum: 4 5 6 7 8
ErwinSuit: C D H S C
ErwinNum: A 2 3 4 5

PlayerName: Xerxes Tor Erwin


run this function together with the previous 9 functions in order to see which player wins.

The logic would be: Run these functions from highest poker hand to lowest poker hand, until one poker hand is found. Compare with all other players to see who has the highest hand. Output the player name.


Any other functions do you think we need?










11
Scripting Board / Some very advanced scripts
« on: October 15, 2015, 09:54:21 am »
Some very advanced scripts.

I'm trying to create a document which details {more} advanced scripts, and with proper syntax as well - hopefully to give people ideas on what scripts can do. Any suggestions / ideas for scripts welcome. Particularly if you also provide the script :)

12
Scripting Board / Scripted Quest Rewards you would like to see
« on: October 04, 2015, 05:04:02 pm »
Post scripted quest rewards you would like to see here.

I've three of them:

a) DT detector (tells you if there is a DT in the zone).
b) "Unlimited" usage of ID scrolls - basically an object to store ID scrolls, but casts identify (reason behind this is that ID scrolls are weightless, but there are people who carry over 500+ of them, which used to cause the MUD to lag when they log in / log out)
c) Scriptable games - like Battleship, but more fun


Any others?

13
General Discussions / Random idea about crafting
« on: September 03, 2014, 03:21:46 pm »
We could have players craft equipment / stats and have an algorithm that determines this.

The algorithm will have a basic dictionary of words (positive, negative), and can tell sentence structure (positive or negative sentence). Longer positive descriptions generally will cost "more" to craft.

Then players can make their own equipment (maybe like RP gear), with minimal stats to show off.

14
General Discussions / Finding player peak periods
« on: August 29, 2014, 06:41:19 pm »
This is what I'm thinking of at the moment, to find player peak periods. However, I'm not sure whether this would be the most efficient way - so I'll post what I'd do - and see if anyone wants to tweak it.

1. Get the syslog of players
2. Look through all the new player created / player generated / player went netdead / player quit / player extracted when idle, etc etc, and simply give a label of "enter" for text signifying a player enters, and "quit" where it means a player quits.
3. Run through each player in the syslog, and map the hours when they are on the MUD - deleting each entry as you find them. When the "syslog" is empty, all players have been accounted for.

Potential questions: Does 2) account for alts? i.e. would we be double counting players?

15
Suggestions & Ideas / Helpfiles revamp ?
« on: August 22, 2014, 02:19:12 am »
Molly and I were talking about helpfiles recently, and I got this idea - though I'm not sure how feasible it would be given 4D's code.

Would it be possible to re-arrange the helpfiles into different categories, like on the main "help" page. Eg, typing help shows several categories - (movement, communication, objects, information, etc ...)

So if there's a folder called /help where the helpfiles are stored, there could be subfolders, such as:

/help/movement
/help/communication

etc, and the respective helpfiles placed there.

So typing "help" would list the categories (or rather, list the current folders in the /help folder) in a nice formatted list.

Typing "help <category>" would list help files in the respective folder, eg help movement will list all the help files in /help/movement.

Typing "help <topic>" will scan through all folders and find helpfiles with the same keyword.
Typing "help <category> <topic>" will scan through the respective category instead - so maybe "help spells fortify" will show entries on fortify body and fortify mind, and not show the entry on fortify.
Typing "help 'blahblah'" (in quotations) will try to search for the help file with the exact keyword.

The hedit command could be modified to include in the respective category (or create new ones), so new helpfiles can be created on the fly and put in the respective folder.

I'm not sure whether this is feasible, or whether a lot of effort will be needed in doing this - I would think the most amount of effort would be to rearrange the current helpfiles into the categories. I don't think the helpfiles would need to be edited to show what "category" they're in, since a check could be done by looking what folder the helpfile is in, and then saying <folder name> is the category....

What do coders think of this idea?


Pages: [1] 2 3