Kawari Tips

2004/02/01
Phase 8.2.0

Kawari Development Team :
NAKAUE.T (Meister), 偽Meister (夢乃), さとー, 酔狂, さくらのにえ

Index

← back
Dictionary
  pseudokawari.ini
  Story
  Nonoverlap for Entries
  Sequential Entries
Communicate
  Easy Communicate
Other
  Simple Character Discriminator
  SHIORI/3.0 NOTIFY Processing
  Debugging Assistance Script

Dictionary

pseudokawari.ini

If you don't want to declare the load command one by one to read the ghost definition dictionary file, you can write the dictionary file name in dict in the same way as the conventional kawari.ini by writing the following in 'kawarirc.kis'.

# kawarirc.kis sample
=kis
load kawari.ini;
foreach @f dict $( load ${@f} );
=end
# kawari.ini sample
dict : dict-word.txt
dict : dict-game.txt

Story

With the introduction of entry array calls, it is now possible to write continuous talks such as stories in spontaneous talks in an easy-to-understand manner. It is also easy to call consecutive talks as random talks.

# Write a story in the story1 entry
story1 : (
    "\0\s[0]Once upon a time, there lived an old man and an old woman."
    \1\s[10]...\e
  )
story1 : (
    "\0\s[0]The woman was washing her clothes in the river"、
    "when a large peach came from upstream."
    "\1\s[10]Well, that's a promise."\e
  )
story1 : (
    "\0\s[0]The woman, delighted, took the peach home"
    "and split it in two with her husband."
    "\1\s[10]But, is Momotaro safe?"\e
  )
story1 : (
    "\0\s[0]Then, from inside the peach, a plump Unyuu emerged..."
    "\1\s[10]Wait a sec.\e"
  )
story1 : (
    "\0\s[0]The woman hurridly sealed the peach..."
    "\1\s[10]Hey!"
    "\0\s[0]And sold it to "${badCorporation}" at a high price,"
    "\s[5]and Unyuu lived out the rest of his life in a very comical way. The end!"
    "\1\s[11]The end?!"\e
  )
story1 : (
    "\0\s[5]That was a good story. Wasn't it heartwarming, Unyuu?"
    "\1\s[11]Which part?! You and my fist need to talk at once."
    "\0\s[0So, you want a full-course of "${specialMove}"?\s[6]"
    \1...\w9...\w9...\w9\s[10]We have a "${treat}", tonight, Miss."
    "\0\s[5]Good to know."\e
  )
# A counter for how far you've read
story1.pos : 0
# Increment the counter by one, and reset it to zero when all the entries have been read
story1.inc : $(setstr story1.pos $[ (${story1.pos} + 1) % $(size story1) ])

# Registers the story in the talk entry. When called, increment the counter by one.
sentence : $story1[${story1.pos}]${story1.inc}

# Registers the story in the talk entry
sentence : ${story1}

Nonoverlap for Entries

A normal entry call (${hoge}) randomly chooses a word and returns it. However, if you use the same entry call multiple times in the same sentence, there's no gurantee it will be different. If this bothers you, first prepare a script like this:

# Call random words from entries without duplicates
# argument : entry name
# return : random entry definition
# note : Occupies the entry "entry name.buffer"
=kis
function GetWordRandom $(
    if $[ $(size @arg) != 2] $(return);
    if $[ $(size $@arg[1].buffer) == 0 ] $(copy $@arg[1] $@arg[1].buffer);

    setstr @pos $(rand $(size $@arg[1].buffer));
    get $@arg[1].buffer[${@pos}];
    clear $@arg[1].buffer[${@pos}];
);
=end

# Create an entry that calls the specified entry without overlapping words
# argument : entry name
# return : none
# note : Occupies "entry name.backup" and "entry name.backup.buffer"
=kis
function Nonoverlap $(
    if $[ $(size $@arg[1].backup) == 0 ] $(copy $@arg[1] $@arg[1].backup);
    set $@arg[1] "$(GetWordRandom "$@arg[1]".backup)";
    writeprotect $@arg[1];
    writeprotect $@arg[1].backup;
);
=end

Then, write as follows:

# Specifying the 'name' entry avoids duplication.
=kis
Nonoverlap name;
=end

Then just call ${name} and it won't repeat the same word until you've used all the words in the entry. The order in which the words are called is random as usual.

If you do this, you will not be able to add words to the specified entry. When specifying it, it is a good idea to execute Nonoverlap when all the dictionary reading is completed.

Sequential Entries

When calling an entry, a call can be made sequentially for each definition. It will mainly be useful when writing stories, etc. Use a script like this:

# Call words from an entry in index order
# argument : entry name
# return : an entry definition
# note : Occupies the entry "entryname.pos"
=kis
function GetWordSequential $(
    if $[ $(size @arg) != 2] $(return);
    if $[ $(size $@arg[1].pos) == 0 ] $(setstr $@arg[1].pos 0);

    get $@arg[1][${$@arg[1].pos}];
    setstr $@arg[1].pos $[ (${$@arg[1].pos}+1) % $(size $@arg[1]) ];
);
=end

# Make an entry that calls the specified entry in subscript order
# argument : entry name
# return : none
# note : occupies "entryname.backup" and "entryname.backup.pos"
=kis
function Sequential $(
    if $[ $(size $@arg[1].backup) == 0 ] $(copy $@arg[1] $@arg[1].backup);
    set $@arg[1] "$(GetWordSequential "$@arg[1]".backup)";
    writeprotect $@arg[1];
    writeprotect $@arg[1].backup;
);
=end

Then, write as follows:

# Specify the card entry to be called in order
=kis
Sequential card;
=end

Then, simply calling ${card} will call the words in the entry in order. Calling the last word will return to the beginning.

If you do this, you will not be able to add words to the specified entry. When specifying it, it is a good idea to execute Sequential when all the dictionary reading is completed.

Communicate

Easy Communicate

Kawari Phase 8's communication commands are more primitive than Phase 7.3.1, and there are parts that are harder to use, too.

While it allows you to dynamically expand your keywords, it consumes a lot of entry names. Therefore, I devised a method to make the description easier by intentionally fixing the keywords as before.

=kis
# 1st argument: target entry to register
# 2nd argument: the entry that records the data to be registered
function regist_communicate $(
    # exit if argument is not 2
    if $[ $(size @arg) != 3 ] $(return);
    # exit if there is no data to register
    if $[ $(size $@arg[2]) == 0 ] $(return);
    # set the number of data registrations to be registered
    if $[ $(size $@arg[1]) == 0 ] $(setstr $@arg[1].size 0);

    setstr @pos 0;
    setstr @segment 0;
    # main routine
    while $[ ${@segment} < $(size $@arg[2]) ] $(
        # Identify keywords and response parts
        setstr @segment $(find $@arg[2] "//" ${@pos});
        if $[ ${@segment} == -1 ] $(return);
        setstr @keypos $(find $@arg[2] "-" ${@pos});
        if $[ ${@keypos} == -1 ] $(return);
        if $[ ${@keypos} > ${@segment} ] $(return);
        if $[ ${@keypos}+1 >= ${@segment} ] $(return);

        # register in the entry for communicate
        # keywords
        setstr @i ${@pos};
        while $[ ${@i} < ${@keypos} ] $(
            push $@arg[1].${$@arg[1].size}.keyword $(getcode $@arg[2][${@i}]);
            inc @i;
        );
        # response string
        setstr @i $[ ${@keypos}+1 ];
        while $[ ${@i} < ${@segment} ] $(
            push $@arg[1].${$@arg[1].size}.answer $(getcode $@arg[2][${@i}]);
            inc @i;
        );

        # register in communicate entry
        push $@arg[1] (
            "$(if $(xargs "
            $@arg[1]
            "."
            ${$@arg[1].size}
            ".keyword matchall ${System.Request.Reference1}) "
            $@arg[1]
            "."
            ${$@arg[1].size}".answer)"
        );

        # increment
        inc $@arg[1].size;
        setstr @pos $[ ${@segment}+1 ];
    );
);


# 1st argument: target entry to register
# 2nd argument: the entry that records the data to be registered
function regist_communicate_to $(
    # exit if argument is not 2
    if $[ $(size @arg) != 3 ] $(return);
    # exit if there is no data to register
    if $[ $(size $@arg[2]) == 0 ] $(return);
    # set the number of data registrations to be registered
    if $[ $(size $@arg[1]) == 0 ] $(setstr $@arg[1].size 0);

    setstr @pos 0;
    setstr @segment 0;
    # main routine
    while $[ ${@segment} < $(size $@arg[2]) ] $(
        # identify the ghost name part
        setstr @segment $(find $@arg[2] "//" ${@pos});
        if $[ ${@segment} == -1 ] $(return);
        setstr @ghostpos $(find $@arg[2] "-" ${@pos});
        if $[ ${@ghostpos} == -1 ] $(return);
        if $[ ${@ghostpos} > ${@segment} ] $(return);
        if $[ ${@ghostpos}+1 >= ${@segment} ] $(return);

        # register in the communicate entry
        # ghost name
        setstr @i ${@pos};
        while $[ ${@i} < ${@ghostpos} ] $(
            push $@arg[1].${$@arg[1].size}.ghost $(getcode $@arg[2][${@i}]);
            inc @i;
        );

        # Identify keywords and response parts
        setstr @pos $[ ${@ghostpos}+1 ];
        setstr @keypos $(find $@arg[2] "-" ${@pos});
        if $[ ${@keypos} == -1 ] $(return);
        if $[ ${@keypos} > ${@segment} ] $(return);
        if $[ ${@keypos}+1 >= ${@segment} ] $(return);

        # register in the communicate entry
        # keywords
        setstr @i ${@pos};
        while $[ ${@i} < ${@keypos} ] $(
            push $@arg[1].${$@arg[1].size}.keyword $(getcode $@arg[2][${@i}]);
            inc @i;
        );
        # response string
        setstr @i $[ ${@keypos}+1 ];
        while $[ ${@i} < ${@segment} ] $(
            push $@arg[1].${$@arg[1].size}.answer $(getcode $@arg[2][${@i}]);
            inc @i;
        );

        # register in the communicate entry
        push $@arg[1] (
            "$(if $[ $(find "
            $@arg[1]
            "."
            ${$@arg[1].size}
            ".ghost ${System.Request.Reference0}) >= 0 && $(xargs "
            $@arg[1]
            "."
            ${$@arg[1].size}
            ".keyword matchall ${System.Request.Reference1}) ] "
            $@arg[1]
            "."
            ${$@arg[1].size}".answer)"
        );

        # increment
        inc $@arg[1].size;
        setstr @pos $[ ${@segment}+1 ];
    );
);
=end

The regist_communicate defined above is used as follows; First, register the following words in an entry (in this case, data1).

data1 (
    today, weather, good,
    -,
    \0\s[5]That's right. I want to go out somewhere.\e,
    \0\s[0]On a day like this, I want to go out and play. \e,
    \0\s[4]I'm so busy on days like this...\e,
    //
  )

After that, if the entry actually registered in the communicate command, is com.

=kis
regist_communicate com data1;
=end

Describe it like this. Then, the contents of the data1 entry are expanded to the following group of entries under com.

com : $(if $(xargs com.0.keyword matchall ${System.Request.Reference1}) com.0.answer)
com.0.keyword : today , weather , good
com.0.answer : \0\s[5]That's right. I want to go out somewhere. \e
com.0.answer : \0\s[0]On a day like this, I want to go out and play. \e
com.0.answer : \0\s[4]I'm so busy on days like this...\e
com.size : 1

If you want to check the above expansion result, please execute the following script after executing regist_communicate. The expansion result is recorded in saved.txt.

=kis
clear temp;
listtree temp com;
xargs temp save saved.txt;
=end

This is the format of the entry read by regist_communicate, but the words up to the first "-" are "keywords". If all the above keywords exist in Reference1, one of the sentences after "-" will be selected as the response sentence. Please put "//" at the end of one response combination as a delimiter. After "//", you can write another response.

data2 (
    keyword1,
    -,
    response1,
    //,
    keyword2,
    -,
    response2,
    //
  )

These responses work by subscribing the com entry to the OnCommunicate event through the communicate command.

reply.OnCommunicate : $(communicate com)

The regist_communicate_to command, on the other hand, is an enhancement that allows you to specify a more reactive ghost name as well. The name of the ghost to which the word up to the first "-" responds, and responds when called by any of the ghosts listed here. From the first "-" to the next "-" is the keyword, and after the second "-" is the sentence to return. As with the regist_communicate, put "//" at the end of the keyword/response combination.

data3 (
    Busuko,Somberlain,
    -,
    metal,
    -,
    "\0\s[5]Next time, introduce me to a good album.\e",
    "\0\s[0]What's the difference between death and gore?\e",
    //
  )

=kis
regist_communicate_to com data3;
=end

You can judge ghosts by using two steps of communicate, or using only regist_communicate. Please choose and use the one that is convenient for you.

# To the com.Busuko entry, register the anti-Busuko text with register_communicate
# For the com.Proton entry, register the text for Proton with register_communicate
com : $(
    if $[ ${System.Request.Reference0} == "Busuko" ]
        com.Busuko
    );
  )

com : $(
    if $[ ${System.Request.Reference0} == "Proton" ]
        com.Proton
    );
  )

reply.OnCommunicate : $(communicate com ${com.unknown})

In addition, this script can be used by embedding it in the ghost, or it can be used for conversion in advance using Kosui.

Other

Simple Character Discriminator

When creating functions, it is often necessary to distinguish between uppercase and lowercase letters, numbers, and other types of characters. The following is a relatively simple function that can be used to determine these types of characters, although its accuracy is not as high as it should be.

# Character table used for discrimination
asciichar : "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"
asciichar : "abcdefghijklmnopqrstuvwxyz_"
asciichar : "+-0123456789."
asciichar : "-^\!\"#$%&'()=|@[;:],./`{+*}<>?_"
=kis
writeprotect asciichar;
=end

# Function that serves as a template for character type identification
=kis
function ischaracters $(
    setstr @pos 0;

    while $[ ${@pos} < $(length $@arg[1]) ] $(
        setstr @char $(char_at $@arg[1] ${@pos});
        if $[ $@arg[2] !~ ${@char} ] $(
            return 0;
        );
        inc @pos;
    );
    return 1;
);
# True if the string contains only uppercase alphabetic characters
function isupper $(ischaracters $@arg[1] $asciichar[0]);
# True if the string contains only lower-case alphabetic characters
function islower $(ischaracters $@arg[1] $asciichar[1]);
# 数字だけなら真
function isnumerical $(ischaracters $@arg[1] $asciichar[2]);
# True if the string is alphabetic only
function isalpha $(ischaracters $@arg[1] $(get asciichar[0..1]));
# True if the string consists only of alphabetic and numeric characters
function isalnum $(ischaracters $@arg[1] $(get asciichar[0..2]));
=end

These functions are used as follows:

string1 : alphabet
string2 : P2P
string3 : 12345

=kis
isupper ${string1};
# return 0
islower ${string1};
# return 1
isalnum ${string2};
# return 1
isnumerilac ${string1}${string3};
# return 0
=end

SHIORI/3.0 NOTIFY Processing

The NOTIFY processing that Kawari has traditionally handled is probably common to all middleware. Therefore, a sample of basic NOTIFY processing is shown below. This sample is modeled after the NOTIFY processing in the Kawari Phase 7.3.1 era. In addition, the Reference command defined in the Applications section of the User's Manual is used to reference the Reference header.

# Set callback entry
System.Callback.OnNOTIFY: ${notify.${System.Request.ID}}

# HWnd Notification
# System.Hwnd.shell entry...shell side HWnd
# System.Hwnd.balloon entry...HWnd on balloon side
notify.hwnd : $(
    split System.Hwnd.shell $(Reference 0) $(chr 1);
    split System.Hwnd.balloon $(Reference 1) $(chr 1);
)

# Installed Ghost Notifications
# List of installed ghost names in System.InstalledGhost entry
notify.installedghostname : $(
    clear System.InstalledGhost;

    setstr @count 0;
    while $(Reference ${@count}) $(
        pushstr System.Installedghost $(Reference ${@count});
        inc @count;
    );
)

# Other ghost notifications during startup
# System.OtherGhost entry...list of running ghost names
# System.OtherGhostEx entries...List of ghost names + surface numbers during startup
notify.otherghostname : $(
    clear System.OtherGhost;
    clear System.OtherGhostEx;

    setstr @count 0;
    while $(Reference ${@count}) $(
        split @ghost $(Reference ${@count}) $(chr 1);
        pushstr System.OtherGhost $@ghost[0];
        pushstr System.OtherGhostEx $(Reference ${@count});
        inc @count;
    );
)

# unique ID notification
# System.UniqueId entry...Unique ID
notify.uniqueid : $(setstr System.UniqueId $(Reference 0))

Debugging Assistance Script

Trial and error debugging is inevitable when creating ghosts. However, it is not very efficient to launch the main body one by one for that purpose. In that respect, debugging using Kosui is efficient, but if you try to debug event processing or NOTIFY processing, a little ingenuity is required.。

So, I made a script that emulates the callback entry processing behavior and input/output headers with the body. You are free to emulate any event you want, NOTIFY, and log the results. Everything is written in KIS, so you can debug ghosts with Kosui alone.

=kis
# debugOnGET...debug GET
#
# 1st argument : event name, resource name
# 2nd argument : Reference[x].
function debugOnGET $(
    # Ends without ID
    if $[ $(size @arg) < 2 ] $(return);

    # Header cleaning
    cleartree System.Request;

    # header set
    setstr System.Request GET;
    setstr System.Request.ID $@arg[1];

    clear @arg[0];
    setstr @arg[0] OnGET;
    xargs @arg debugCallback;
);

# ddebugOnNOTIFY...debugging NOTIFY
#
# 1st argument : name of notified information
# 2nd argument : Reference[x]
function debugOnNOTIFY $(
    # Ends without ID
    if $[ $(size @arg) < 2 ] $(return);

    # Header cleaning
    cleartree System.Request;

    # header set
    setstr System.Request NOTIFY;
    setstr System.Request.ID $@arg[1];

    clear @arg[0];
    setstr @arg[0] OnNOTIFY;
    xargs @arg debugCallback;
);

# debugCallback...common function for debugging callback entries
#
# 1st argument : callback entry (OnGET, OnNOTIFY)
# 2nd argument : Reference[x]
# note : The request header must be set before calling this function
function debugCallback $(
    # Ends without ID
    if $[ $(size @arg) < 2 ] $(return);

    # Header cleaning
    cleartree System.Response;

    # Common Header Sets
    setstr System.Request.Sender Kosui;
    setstr System.Request.SecurityLevel local;
    setstr System.Request.Charset Shift_JIS;
    setstr @pos 2;
    loop $[ $(size @arg)-2 ] $(
        setstr System.Request.Reference$[${@pos}-2] $@arg[${@pos}];
        inc @pos;
    );

    # query start declaration
    logprint "[SHIORI/SAORI Emulator] Query sequence begin.";

    # View Requests
    printRequestHeaders;

    # callback
    if $(size System.Callback.$@arg[1]) $(
        setstr @Value $(get System.Callback.$@arg[1]);
        if $(length ${@Value}) $(setstr System.Response.Value ${@Value});
    );

    # Display Response
    printResponseHeaders;

    # Declaration of End of Query
    logprint "[SHIORI/SAORI Emulator] Query sequence end.";

    # Cleaning up after headers
    cleartree System.Request;
    cleartree System.Response;
);

# Displaying Request Headers
function printRequestHeaders $(
    logprint "---------------------- REQUEST";
    logprint ${System.Request} "SHIORI/3.0";

    listtree @Headers System.Request;
    if $(size @Headers) $(
        foreach @h @Headers $(
            if $[ ${@h} != "System.Request" ] $(
                logprint $(substr ${@h} 15)":" ${${@h}};
            );
        );
    );
    logprint;
);

# Display of response headers
function printResponseHeaders $(
    logprint "---------------------- RESPONSE";
    if $(size System.Response.Value) $(
        logprint "SHIORI/3.0 200 OK";
    ) else $(
        logprint "SHIORI/3.0 204 No Content";
    );

    listtree @Headers System.Response;
    if $(size @Headers) $(
        foreach @h @Headers $(
            if $[ ${@h} != "System.Response" ] $(
                logprint $(substr ${@h} 16)":" ${${@h}};
            );
        );
    );
    logprint;
);
=end

If you load this script from Kosui and enter it in command mode as follows, you can emulate "OnSecondChange event, 15th hour of continuous startup, no interruptions/overlapping, state where talk is output".

debugOnGET OnSecondChange 15 0 0 1

You can also emulate a NOTIFY for "Installed ghosts are Sakura, Mayura, Futaba, Tokoko" by typing:

debugOnNOTIFY installedghostname さくら まゆら 双葉 毒子

In fact, it would be convenient to create a script using this command and perform acceleration tests, etc. during long startup. The following example shows a script that sends OnSecondChange and OnMinuteChange for one hour to perform an acceleration endurance test. The script does not take into account the return to the surface when a choice is made in the middle of a talk, but I think this is usually sufficient.

=kis
# the Hour entry remembers how many hours of activation
loop 60 $(
    debugOnGET OnMinuteChange ${Hour} 0 0 1;
    loop 60 $(
        debugOnGET OnSecondChange ${Hour} 0 0 1;
    );
);
=end