mirror of
https://github.com/goonstation/goonstation-2020.git
synced 2026-07-12 23:52:20 +01:00
287 lines
11 KiB
Plaintext
287 lines
11 KiB
Plaintext
/*
|
|
Various Notes: OnActivate and OnLeave can return a value of DIALOGUE_CLOSE to close the dialogue window. (Without causing runtimes)
|
|
*/
|
|
|
|
var/global/list/globalDialogueFlags = list()
|
|
/client/var/list/dialogueNodePath = list()
|
|
|
|
//Same as the dialogue master based versions but global.vvv
|
|
proc/setGlobalFlag(var/client/C, var/flag="", var/value="")
|
|
if(!globalDialogueFlags.Find(C.ckey))
|
|
globalDialogueFlags.Add(C.ckey)
|
|
globalDialogueFlags[C.ckey] = list()
|
|
|
|
var/list/curr = globalDialogueFlags[C.ckey]
|
|
curr[flag] = value
|
|
globalDialogueFlags[C.ckey] = curr
|
|
return
|
|
|
|
proc/getGlobalFlag(var/client/C, var/flag="")
|
|
if(!globalDialogueFlags.Find(C.ckey)) return null
|
|
else
|
|
var/list/curr = globalDialogueFlags[C.ckey]
|
|
if(curr.Find(flag))
|
|
return curr[flag]
|
|
else
|
|
return null
|
|
return null
|
|
|
|
/datum/dialogueMaster
|
|
var/datum/dialogueNode/start = null //The node the dialogue starts at.
|
|
var/dialogueName = "" //Name that is shown in the dialogue and title.
|
|
var/atom/master = null //The atom that this dialogueMaster belongs to.
|
|
var/visibleDialogue = 1 //Is the dialogue visible to everyone?
|
|
var/windowSize = "400x700" //Size of the dialogue window.
|
|
var/maxDistance = -1 //If >=0 The user needs to be at least this close for the dialogue to work.
|
|
var/objectDialogueVerb = "says" //If the dialogueMaster belong to an object and showDialogue is one, what "verb" do we use for it's chat output.
|
|
var/list/dialogueFlags = list() //Holds simple string flags that can be used in dialogue. I.e. "Have we talked about this before"
|
|
var/list/allNodes = null //Complete list of nodes in the master.
|
|
|
|
New(var/datum/M)
|
|
master = M
|
|
if(ispath(start))
|
|
var/datum/dialogueNode/instance = new start(src)
|
|
allNodes = instance.initialize(recursive=1)
|
|
start = allNodes[1]
|
|
..()
|
|
|
|
Topic(href,href_list[])
|
|
if(href_list["close"])
|
|
var/datum/dialogueNode/D = getUserNode(usr.client)
|
|
D.onLeave(usr.client, null)
|
|
return
|
|
if(href_list["link"] && href_list["client"])
|
|
var/datum/dialogueNode/N = locate(href_list["link"])
|
|
var/client/C = locate(href_list["client"])
|
|
if(maxDistance >= 0)
|
|
if(get_dist(C.mob, master) > maxDistance)
|
|
boutput(C.mob, "You are too far away for that.")
|
|
return
|
|
if(N != null && N.canShow(C)) //The order of the stuff below is very important. You might break things if you change it.
|
|
var/datum/dialogueNode/D = getUserNode(C)
|
|
var/retVal = D.onLeave(C, N)
|
|
if(retVal & DIALOGUE_CLOSE)
|
|
C << browse(null, "window=dialogue")
|
|
return
|
|
if(!C.dialogueNodePath.Find("\ref[src]"))
|
|
C.dialogueNodePath.Add("\ref[src]")
|
|
C.dialogueNodePath["\ref[src]"] = list()
|
|
var/list/path = C.dialogueNodePath["\ref[src]"]
|
|
path.Add(N)
|
|
retVal = null
|
|
retVal = N.onActivate(C)
|
|
if(retVal & DIALOGUE_CLOSE)
|
|
C << browse(null, "window=dialogue")
|
|
return
|
|
var/html = getHtml(C, N)
|
|
showDialogue(C.mob, html)
|
|
if(visibleDialogue)
|
|
C.mob.say(N.getLinkText(C))
|
|
if(N != start)
|
|
if(ismob(master))
|
|
var/mob/M = master
|
|
M.say(N.getNodeText(C))
|
|
else if(isobj(master))
|
|
for(var/mob/O in all_hearers(5, master.loc))
|
|
O.show_message("<span class='name'>[master.name]</span> [objectDialogueVerb], <span class='message'>\"[N.getNodeText(C)]\"</span>",2)
|
|
return
|
|
|
|
proc/setFlag(var/client/C, var/flag="", var/value="") //Sets flag to value for this client in this dialogue master.
|
|
if(!dialogueFlags.Find(C.ckey))
|
|
dialogueFlags.Add(C.ckey)
|
|
dialogueFlags[C.ckey] = list()
|
|
|
|
var/list/curr = dialogueFlags[C.ckey]
|
|
curr[flag] = value
|
|
dialogueFlags[C.ckey] = curr //Not sure if reference or not. Just to be sure here.
|
|
return
|
|
|
|
proc/getFlag(var/client/C, var/flag="") //Returns value of dialogue flag, or null if no match.
|
|
if(!dialogueFlags.Find(C.ckey)) return null
|
|
else
|
|
var/list/curr = dialogueFlags[C.ckey]
|
|
if(curr.Find(flag))
|
|
return curr[flag]
|
|
else
|
|
return null
|
|
return null
|
|
|
|
proc/setFlagGlobal(var/client/C, var/flag="", var/value="")
|
|
return setGlobalFlag(C, flag, value)
|
|
|
|
proc/getFlagGlobal(var/client/C, var/flag="")
|
|
return getGlobalFlag(C, flag)
|
|
|
|
proc/getUserNode(var/client/C, var/steps=0) //Returns the last activated node of the given user on this dialogueMaster. Can be considered the currently "active" node.
|
|
if(C.dialogueNodePath) //Optionally goes [steps] steps back and gets that node instead of the last one.
|
|
if(C.dialogueNodePath["\ref[src]"])
|
|
if(C.dialogueNodePath["\ref[src]"].len)
|
|
var/length = C.dialogueNodePath["\ref[src]"].len
|
|
if(steps > 0)
|
|
if(length > steps)
|
|
return C.dialogueNodePath["\ref[src]"][length - steps]
|
|
else
|
|
return null
|
|
else
|
|
return C.dialogueNodePath["\ref[src]"][length] //AAAAAAAAH WHAT THE FUCK
|
|
return null
|
|
|
|
proc/showDialogue(var/mob/user, var/html = null)
|
|
if(!user || !user.client) return
|
|
var/client/C = user.client
|
|
if(html == null)
|
|
if(start != null)
|
|
if(!C.dialogueNodePath.Find("\ref[src]"))
|
|
C.dialogueNodePath.Add("\ref[src]")
|
|
C.dialogueNodePath["\ref[src]"] = list()
|
|
var/list/path = C.dialogueNodePath["\ref[src]"]
|
|
path.Add(start)
|
|
var/retVal = start.onActivate(C) //I don't know why you'd want to close the dialogue window right after you open it but there you go.
|
|
if(retVal & DIALOGUE_CLOSE)
|
|
C << browse(null, "window=dialogue")
|
|
return
|
|
html = getHtml(C, start)
|
|
C.Browse(html,"window=dialogue;title=[dialogueName];size=[windowSize]", 1)
|
|
onclose(user, "dialogue", src)
|
|
return
|
|
|
|
proc/getHtml(var/client/C, var/datum/dialogueNode/node = start)
|
|
var/html = {"<link rel="stylesheet" type="text/css" href="[resource("css/dialogue.css")]">"}
|
|
html += node.getNodeHtml(C)
|
|
html += "<br><br>"
|
|
html += node.getBodyHtml(C)
|
|
if(node != start && node.showBackToMain)
|
|
html += start.getLinkHtml(C)
|
|
return html
|
|
|
|
/*
|
|
Seeker nodes are a special type of node subclass that break the normal dialogue flow. Be careful with these.
|
|
targetNodeType is the type of the node we want to jump to. If you give your subclass of this node any
|
|
images or text, they will override the corresponding values on the target node. Useful if you want a
|
|
"go back one step" button that provides a different text when you get back to the last node, or something like that.
|
|
Links are ignored on this node. It uses the target node's links.
|
|
*/
|
|
/datum/dialogueNode/seeker
|
|
var/targetNodeType = null //Which node type to jump to. Must be present in the same dialogueMaster as this node.
|
|
var/respectCanShow = 1 //Only consider nodes that can normally be shown to the client.
|
|
|
|
proc/getTargetNode()
|
|
var/datum/dialogueNode/foundNode = null
|
|
for(var/datum/dialogueNode/N in master.allNodes)
|
|
if(N.type == targetNodeType)
|
|
foundNode = N
|
|
break
|
|
return foundNode
|
|
|
|
canShow(var/client/C)
|
|
var/datum/dialogueNode/foundNode = getTargetNode()
|
|
if(foundNode && (!respectCanShow || foundNode.canShow(C)))
|
|
return 1
|
|
else
|
|
return 0
|
|
|
|
getBodyHtml(var/client/C)
|
|
var/datum/dialogueNode/foundNode = getTargetNode()
|
|
if(foundNode != null)
|
|
src.showBackToMain = foundNode.showBackToMain
|
|
foundNode.onActivate(C)
|
|
var/html = ""
|
|
for(var/X in foundNode.links)
|
|
if(ispath(X))
|
|
message_coders("Uninitialized node in dialogue [X] in [foundNode.type] - via seeker node [src.type]")
|
|
continue
|
|
else if(istype(X, /datum/dialogueNode))
|
|
var/datum/dialogueNode/N = X
|
|
if(N.canShow(C))
|
|
html += N.getLinkHtml(C)
|
|
html += "<br>"
|
|
return html
|
|
else
|
|
alert(C, "Uh-oh, the dialogue broke! No valid link was found. Please alert a coder with this info: getBodyHtml [targetNodeType] [master.allNodes.len]", "Oh no", "Ok")
|
|
|
|
getNodeHtml(var/client/C)
|
|
var/datum/dialogueNode/foundNode = getTargetNode()
|
|
if(foundNode != null)
|
|
var/html = ""
|
|
|
|
if(nodeImage != null)
|
|
html += {"<img class="centerimg" src='[getNodeImage(C)]'><HR><B>[master.dialogueName]</B><HR>"}
|
|
else if(foundNode.nodeImage != null)
|
|
html += {"<img class="centerimg" src='[foundNode.getNodeImage(C)]'><HR><B>[master.dialogueName]</B><HR>"}
|
|
|
|
if(getNodeText(C) != "")
|
|
html += {"<span>[getNodeText(C)]</span>"}
|
|
else
|
|
html += {"<span>[foundNode.getNodeText(C)]</span>"}
|
|
return html
|
|
else
|
|
alert(C, "Uh-oh, the dialogue broke! No valid link was found. Please alert a coder with this info: getNodeHtml [targetNodeType] [master.allNodes.len]", "Oh no", "Ok")
|
|
|
|
/datum/dialogueNode
|
|
var/nodeImage = null //Portrait or whatever for this node.
|
|
var/nodeText = "" //What's displayed when you enter this node. What the npc says.
|
|
var/linkText = "" //The text that links TO this node. What you say to the NPC to get to this node.
|
|
var/links = list() //Which nodes are inside this node. What "buttons" this node contains.
|
|
var/showBackToMain = 1 //Show button leading back to first screen on this node? Careful: Without this people might get stuck. Plan your dialogue.
|
|
var/datum/dialogueMaster/master = null
|
|
|
|
New(var/datum/dialogueMaster/M)
|
|
master = M
|
|
..()
|
|
|
|
proc/initialize(var/recursive = 1, var/list/tree = null)
|
|
if(tree == null)
|
|
tree = list(src)
|
|
else
|
|
tree.Add(src)
|
|
for(var/X in links)
|
|
if(ispath(X))
|
|
var/datum/dialogueNode/N = new X(master)
|
|
links -= X
|
|
links += N
|
|
if(recursive)
|
|
N.initialize(recursive, tree)
|
|
return tree
|
|
|
|
proc/getNodeImage(var/client/C) //Returns nodeImage by default but can override for dynamic behaviour.
|
|
return resource("images/traders/[nodeImage]")
|
|
|
|
proc/getNodeHtml(var/client/C) //Returns portrait and getNodeText() by default, separated by line. Can override for dynamic behaviour.
|
|
var/html = ""
|
|
if(nodeImage != null)
|
|
html += {"<img class="centerimg" src='[getNodeImage(C)]'><HR><B>[master.dialogueName]</B><HR>"}
|
|
html += {"<span>[getNodeText(C)]</span>"}
|
|
return html
|
|
|
|
proc/getLinkHtml(var/client/C) //Returns getLinkText() by default. Can override for dynamic behaviour.
|
|
return {"<a class="button full" href='?src=\ref[master];link=\ref[src];client=\ref[C]'>[getLinkText(C)]</a>"}
|
|
|
|
proc/getNodeText(var/client/C) //Simply returns the node text by default - node text is what the npc says. Can override for dynamic behaviour.
|
|
return nodeText
|
|
|
|
proc/getLinkText(var/client/C) //Returns linkText by default - link text is what you say to the npc to get to this node. Can override for dynamic behaviour.
|
|
return linkText
|
|
|
|
proc/getBodyHtml(var/client/C) //Returns a list of buttons representing all nodes in this nodes links by default.
|
|
var/html = ""
|
|
for(var/X in links)
|
|
if(ispath(X))
|
|
message_coders("Uninitialized node in dialogue [X] in [src.type]")
|
|
continue
|
|
else if(istype(X, /datum/dialogueNode))
|
|
var/datum/dialogueNode/N = X
|
|
if(N.canShow(C))
|
|
html += N.getLinkHtml(C)
|
|
html += "<br>"
|
|
return html
|
|
|
|
proc/onActivate(var/client/C) //Called when selected. If you want to hook the actual world up somehow. This runs before any html is generates so it could be used to manipulate things before that.
|
|
return
|
|
|
|
// !!!!!!!!!!! WARNING : THIS WILL GET CALLED TWICE PER CHUI WINDOW CLOSE DUE TO A BUG I HAVENT BEEN ABLE TO RESOLVE - ACCOUNT FOR THAT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
proc/onLeave(var/client/C, var/datum/dialogueNode/nextNode) //Called when node is left for another node or when the window is closed with a node open. nextNode might be null if window was closed.
|
|
return
|
|
|
|
proc/canShow(var/client/C) //For conditional options. Link to this node only shows if result == true/1/whatever.
|
|
return 1
|