• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • It's time for the first HD Modeling Contest of 2025. Join the theme discussion for Hive's HD Modeling Contest #7! Click here to post your idea!

NPC Comments

Status
Not open for further replies.
Level 15
Joined
Nov 30, 2007
Messages
1,202
I would like some help to create the following system in vJass preferably.

You save a unit-type or unit as a key.
Then you save comments into certain keywords.

ID _ Hi _ "Hello!"
ID _ Bye _ "Good bye"
ID _ No _ "No"
ID _ No1 _ "Never"
ID _ No1 _ "Never. Ever."

If more then one keyword is stored such as No1 having two, it randoms between the options. Then you load comments through the ID and keyword.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
I checked out your System and its a good idea. But the concept currently only works for SP. But I'll see what I can gather from this. But to clear things up I only need to be able to load comments not create dialog between the player and computer (as of right now).

ID -> Keyword -> Random or 1, 2, 3...

So "Peasant", "Hi", 1 Would load the first sentence stored under the keyword "Hi", 2 would load the second and 0 would try to get a random sentence from the keyword label. So i'm trying to make a database of dialog to draw from.
 
Here you go:

JASS:
library npcComments

globals
    private hashtable hash = InitHashtable()
endglobals

//registers a string "s" to an identifier string "index"
function RegisterComment takes string index, string s returns nothing
    local integer ID = StringHash(index)
    local integer count = LoadInteger(hash, ID, 0)
    set count = count + 1
    call SaveInteger(hash, ID, 0, count)
    call SaveStr(hash, ID, count, s)
endfunction

//retrieves a string by identifier string "index" at position "which".
//if entered "0" for "which", retrieves a random string.
function GetComment takes string index, integer which returns string
    local integer ID = StringHash(index)
    if which > 0 then
        return LoadStr(hash, ID, which)
    else
        return LoadStr(hash, ID, GetRandomInt(1, LoadInteger(hash, ID, 0))
    endif
    return ""
endfunction

endlibrary

Note that this uses the StringHash native, which has a very low chance for collision (as it uses a 32 bit hash). However, the chance for that is 1 in 2147483648 ... so yeah, you might probably win the lottery before that happens. And if it does, you can just use a different identifier.
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
Yes exactly like this (almost). Only problem with it is
that it can only store a group of words to ID so it works fine as long as you keep within 1 category. But how to add a additional ID as a subcategory:

JASS:
call RegisterComment(BANK_TELLER, HI, "Greetings.")
call RegisterComment(BANK_TELLER, HI, "Come over to my desk and I shall help you with your banking needs!")
call RegisterComment(BANK_TELLER, TY, "Thank you for your deposit.")
call RegisterComment(BANK_TELLER, TY, "Thanks.")

HI = 1 and TY = 2 etc... And its HI and TY in this example that i want to index or get random as you've made in your code. I suppose one could put it in the first ID though but I find the first solution better because then you can attach stuff to unit types.

Making up dialog is fun:
JASS:
private function SetupBankTellerComments takes nothing returns nothing
        call RegisterComment("BANKTELLER_DEPFAIL", "Deposit what exactly?")
        call RegisterComment("BANKTELLER_DEPFAIL", "Is this your idea of some kind of joke?")
        call RegisterComment("BANKTELLER_DEPFAIL", "I'm sorry but this is a bank, we deal with currency not air.")
        call RegisterComment("BANKTELLER_DEPFAIL", "Let me explain it to you... First you show me the money then I deposit it.")
        call RegisterComment("BANKTELLER_DEPFAIL", "Another homeless person... Great!")
        call RegisterComment("BANKTELLER_DEPFAIL", "Well, this is awkward - Broke huh?")
        call RegisterComment("BANKTELLER_TY0", "Don't you need this money for something else?")
        call RegisterComment("BANKTELLER_TY1", "Thank you for your deposit.")
        call RegisterComment("BANKTELLER_TY2", "You wont regret saving this with us!")
    endfunction
 
Last edited:
Ah, so you actually don't want to identify these strings via strings, but actual integer IDs? That makes this a lot easier. Give me a moment, I'll rewrite the snippet.

Here you go:

JASS:
library npcComments

globals
     private constant integer MAX_STRINGS = 100 //defines the maximum number of registered strings per unit and ID

     private hashtable hash = InitHashtable()
endglobals

//registers a string "s" to an identifier integer "index" for unit "u".
function RegisterComment takes unit u, integer index, string s returns nothing
     local integer ID = GetHandleId(u)
     local integer count = LoadInteger(hash, ID, index*MAX_STRINGS)
     set count = count + 1
     if count >= MAX_STRINGS then
         debug call BJDebugMsg("NPC_COMMENTS_ERROR: Invalid Child Key.")
         return
     endif
     call SaveInteger(hash, ID, index*MAX_STRINGS, count)
     call SaveStr(hash, ID, index*MAX_STRINGS+count, s)
endfunction

//retrieves a string by identifier integer "index" at position "which" of unit "u".
//if entered "0" for "which", retrieves a random string.
function GetComment takes unit u, integer index, integer which returns string
     local integer ID = GetHandleId(u)
     if which >= MAX_STRINGS then
         debug call BJDebugMsg("NPC_COMMENTS_ERROR: Invalid Child Key.")
         return ""
     endif
     if which != 0 then
         return LoadStr(hash, ID, index*MAX_STRINGS+which)
     else
         return LoadStr(hash, ID, index*MAX_STRINGS+GetRandomInt(1, LoadInteger(hash, ID, index*MAX_STRINGS))
     endif
     return ""
endfunction

endlibrary

You can now do this:
JASS:
call RegisterComment(BANK_TELLER, HI, "Greetings.")
call RegisterComment(BANK_TELLER, HI, "Come over to my desk!")
call RegisterComment(BANK_TELLER, TY, "Thank you for your deposit.")
call RegisterComment(BANK_TELLER, TY, "Thanks.")
and this:
JASS:
call GetComment(BANK_TELLER, HI, 1) //always returns "Greetings"
call GetComment(BANK_TELLER, HI, 2) //always returns "Come over to my desk!"
call GetComment(BANK_TELLER, HI, 0) //returns random one of the above

You can also have a general purpose string set by entering a null unit.
 
Last edited by a moderator:
I made something where you can bind it to unit type:

JASS:
library test initializer Init

    globals
        private hashtable hash = InitHashtable()
    endglobals
    
    function AddComment takes integer unitType, string stringKey, string whichString returns nothing
        local integer stringId = StringHash(stringKey)
        local integer commentTypeAmount // How many categories/types of a comment does a unitType currently have
        local integer commentIdAmount = LoadInteger(hash, unitType, stringId) + 1 // How many same types does it have
        
        if(commentIdAmount == 1) then
        
            // That means a new category/type of comment has been added
        
            set commentTypeAmount = LoadInteger(hash, unitType, 0) + 1
            call SaveInteger(hash, stringId, unitType, commentTypeAmount)
            call SaveInteger(hash, unitType, 0, commentTypeAmount)
        else
            set commentTypeAmount = LoadInteger(hash, unitType, 0)
        endif
        
        call SaveStr(hash, unitType, stringId*commentTypeAmount + commentIdAmount, whichString)
        call SaveInteger(hash, unitType, stringId, commentIdAmount)
     endfunction
     
     function GetComment takes integer unitType, string stringKey, integer index returns string
         local integer stringId = StringHash(stringKey)
         local integer i
         if (index != 0) then
            return LoadStr(hash, unitType, stringId*LoadInteger(hash, stringId, unitType) + index)
        else
            set i = GetRandomInt(1, LoadInteger(hash, unitType, stringId))
            return LoadStr(hash, unitType, stringId*LoadInteger(hash, stringId, unitType) + i)
        endif
     endfunction
     
     private function Init takes nothing returns nothing
        local string s
        call AddComment('hfoo', "Hi", "Hello 1")
        call AddComment('hfoo', "Hi", "Hello 2")
        call AddComment('hfoo', "Fight", "Fight 1")
        call AddComment('hfoo', "Fight", "Fight 2")
        call AddComment('hfoo', "Else", "Just something else")
        
        set s = GetComment('hfoo', "Hi", 2)
        call BJDebugMsg(s) // Will print "Hello 2"
        set s = GetComment('hfoo', "Fight", 1)
        call BJDebugMsg(s)  // Will print "Fight 1"
        set s = GetComment('hfoo', "Else", 0)
        call BJDebugMsg(s)  // Will print "Just something else"

        set s = GetComment('hfoo', "Hello", 0)
        call BJDebugMsg(s)  // Will print "Hello 1" or "Hello 2"
        
     endfunction
endlibrary
Hope no mistake was done.^^
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Oh looking great! I thought it was enough already but now its even better. It's rare to find two masters writing code in the same thread for me^^. Gonna test yours later IcemanBo which seems to be the most similar to what i asked for.

Anyway it's solved.

And actually both your snippets could be used in the case that you have very unique NPCs of a ordinary unit type. First check for unique dialog then for regular dialog.
 
Yeah I checked stringhash, maybethe method is too dangerous.

Edit: But maybe one can just abstract the stringhash by your own index with help of a new system :p

Edit2: Here you go

JASS:
library Comment
    globals
        private hashtable hash = InitHashtable()
        private integer StringIndex = -1
    endglobals
    
    function AddComment takes integer unitType, string stringKey, string whichString returns nothing
        local integer stringId = StringHash(stringKey)
        local integer commentTypeAmount // How many categories/types of a comment does a unitType currently have
        local integer commentIdAmount = LoadInteger(hash, unitType, stringId) + 1 // How many same types does it have
        
        if(commentIdAmount == 1) then
        
            // That means a new category/type of comment has been added
            set StringIndex = StringIndex + 1
            call SaveInteger(hash, stringId, 0, StringIndex)
        
            set commentTypeAmount = LoadInteger(hash, unitType, 0) + 1
            call SaveInteger(hash, stringId, unitType, commentTypeAmount)
            call SaveInteger(hash, unitType, 0, commentTypeAmount)
            
        else
            set commentTypeAmount = LoadInteger(hash, unitType, 0)
        endif
        
        call SaveStr(hash, unitType, LoadInteger(hash, stringId, 0)*commentTypeAmount + commentIdAmount, whichString)
        call SaveInteger(hash, unitType, stringId, commentIdAmount)
     endfunction
     
     function GetComment takes integer unitType, string stringKey, integer index returns string
         local integer stringId = StringHash(stringKey)
         local integer i
         if (index != 0) then
            return LoadStr(hash, unitType, LoadInteger(hash, stringId, 0)*LoadInteger(hash, stringId, unitType) + index)
        else
            set i = GetRandomInt(1, LoadInteger(hash, unitType, stringId))
            return LoadStr(hash, unitType, LoadInteger(hash, stringId, 0)*LoadInteger(hash, stringId, unitType) + i)
        endif
     endfunction
endlibrary

Demo:

JASS:
scope CommentTest initializer Init
    private function Init takes nothing returns nothing
        local string s
        call AddComment('hfoo', "Hi", "Hello 1")
        call AddComment('hfoo', "Hi", "Hello 2")
        call AddComment('hfoo', "Fight", "Fight 1")
        call AddComment('hfoo', "Fight", "Fight 2")
        call AddComment('hfoo', "Else", "Just something else")
       
        set s = GetComment('hfoo', "Hi", 2)
        call BJDebugMsg(s) // Prints "Hello2"
        set s = GetComment('hfoo', "Fight", 1)
        call BJDebugMsg(s) // Prints "Fight1"
        set s = GetComment('hfoo', "Else", 0)
        call BJDebugMsg(s)  // Prints "Just something else"
        set s = GetComment('hfoo', "Hi", 0)
        call BJDebugMsg(s) // Prints "Hello1" or "Hello2"
    
     endfunction
endscope
 
Last edited:
Yeah I checked stringhash, maybethe method is too dangerous.

Edit: But maybe one can just abstract the stringhash by your own index with help of a new system :p

Edit2: Here you go
What's wrong with my approach I posted above? I know you dig stringhash, but I think the TO actually wants the custom indices to be integers, not strings, which makes this a whole lot simpler. ;)
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,264
You use the hashtable to map linked list root nodes. Each list contains all the entries for a certain phrase ID. You pick a random element between 1 and number of elements in linked list (saved on root) and traverse to it returning the phrase at that node. This makes the system completely dynamic.

For implementing specific unit type phrases you have 2 hashtables. One maps units and phrase IDs to their phrase lists and the other unit types and phrase IDs to their phrase lists. For a specific unit you then first query the specific unit hashtable if it has entries. If not then you fall back to the unit type hashtable.
 
Oh, hm. Me personaly would prefer to have a unitType-binded system.
Yours can also have good effetcs, but idk, I prefer the other approach.:)
If you wanted this, you could just replace GetHandleId(u) with the rawcode in my script and change the input parameter to take integer instead of units.

You use the hashtable to map linked list root nodes. Each list contains all the entries for a certain phrase ID. You pick a random element between 1 and number of elements in linked list (saved on root) and traverse to it returning the phrase at that node. This makes the system completely dynamic.

For implementing specific unit type phrases you have 2 hashtables. One maps units and phrase IDs to their phrase lists and the other unit types and phrase IDs to their phrase lists. For a specific unit you then first query the specific unit hashtable if it has entries. If not then you fall back to the unit type hashtable.
Yeah... because ... why make it simple when you can have it complicated as hell? ;)


Comment lib with unit-types instead of units:

JASS:
library npcComments

globals
      private constant integer MAX_STRINGS = 100 //defines the maximum number of registered strings per unit type and ID

      private hashtable hash = InitHashtable()
endglobals

//registers a string "s" to an identifier integer "index" for unit of rawcode "raw"
function RegisterComment takes integer raw, integer index, string s returns nothing
      local integer count = LoadInteger(hash, raw, index*MAX_STRINGS)
      set count = count + 1
      if count >= MAX_STRINGS then
          debug call BJDebugMsg("NPC_COMMENTS_ERROR: Invalid Child Key.")
          return
      endif
      call SaveInteger(hash, raw, index*MAX_STRINGS, count)
      call SaveStr(hash, raw, index*MAX_STRINGS+count, s)
endfunction

//retrieves a string by identifier integer "index" at position "which" of unit of type "raw"
//if entered "0" for "which", retrieves a random string.
function GetComment takes integer raw, integer index, integer which returns string
      if which >= MAX_STRINGS then
          debug call BJDebugMsg("NPC_COMMENTS_ERROR: Invalid Child Key.")
          return ""
      endif
      if which != 0 then
          return LoadStr(hash, raw, index*MAX_STRINGS+which)
      else
          return LoadStr(hash, raw, index*MAX_STRINGS+GetRandomInt(1, LoadInteger(hash, raw, index*MAX_STRINGS))
      endif
      return ""
endfunction

endlibrary
 
but I think the TO actually wants the custom indices to be integers, not strings, which makes this a whole lot simpler. ;)
^In opening post, so I don't know why you assume he wants custom index as parameter instead of a string keyword like in his example "Hi", "No", "Bye".
Then you load comments through the ID and keyword.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,264
Yeah... because ... why make it simple when you can have it complicated as hell? ;)
My solution is simple? In Java it would literally be A Map<Unit, Map<String, List<String>>>. The two outer maps in JASS are a single HashTable.

but I think the TO actually wants the custom indices to be integers, not strings, which makes this a whole lot simpler. ;)
This...
Then you save comments into certain keywords.
The term keyword generally refers to a unique string which should have a meaning associated with it. If he just wanted a unique integer with a meaning he would have used the term "key". And what he asks is reasonable since that is how JASS works under the hood (resolves all named elements at run time on each execution).
 
Status
Not open for further replies.
Top