-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathNPC.py
More file actions
234 lines (226 loc) · 9.33 KB
/
Copy pathNPC.py
File metadata and controls
234 lines (226 loc) · 9.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
"""
Dialog engine support code.
Each npc's DialogTree is organized into a tree of DialogNodes. Most nodes display
some text; they also provide DialogOptions, which link to other nodes. (If a DialogNode
does not contain any text, then it's not actually displayed to the user, but is used
to organize the logic in jumping to other nodes).
The NPC text file can include code - hopefully not much, since it's a bit onerous
to debug complex snippets. The code helps the NPCs become a little more lifelike - e.g.
they can say "hello" on the first meeting and "welcome back" later. The whole
procedure is a little awkward, and perhaps wouldn't work well for a dialog-focused
game like Curse of Monkey Island or Starcon 2. But our prime focus is on (a) killing
monsters, and (b) taking their stuff.
INTRO CODE
~
Root|~
<directions to first node>
~
NodeName|Blah blah
blah blah
blah blah~
DialogString|NextNode|PreLogic|PostLogic|TriggerCode
DialogString2|NextNode|PreLogic|PostLogic|TriggerCode
~
"""
from Utils import *
from Constants import *
import Screen
import Tendrils
import Resources
import Global
class DialogOption:
def __init__(self, Text, NextNode, PreLogic = None, PostLogic = None, TriggerCode = None):
self.Text = Text
self.NextNode = NextNode
self.PreLogic = PreLogic
self.PostLogic = PostLogic
self.TriggerCode = TriggerCode
def __str__(self):
return "<DialogOption '%s'>"%self.Text
def GetNode(self):
#print "GetNode() called on '%s'"%self.Text
if self.NextNode:
#print "Returning NextNode %s"%self.NextNode
return self.NextNode
if self.PostLogic:
#print "Applying post logic:", self.PostLogic
NodeID = apply(self.PostLogic, (Global.Party, Global.Maze))
#print "Post logic gave node ID:", NodeID
return NodeID
print "We have neither NextNode nor PostLogic!!!"
return None
class DialogNode:
"""
A logic node. Its function gets Party and Maze as parameters, and should return the name or number
of the next node.
"""
def __init__(self, Name, Text):
self.Name = Name
self.Text = Text
self.Choices = []
def __str__(self):
return "<Node '%s' (%s)>"%(self.Name, str(self.Text)[:50])
def AddChoice(self, Text, NextNode, PreLogic = None, PostLogic = None, TriggerCode = None):
Choice = DialogOption(Text, NextNode, PreLogic, PostLogic, TriggerCode)
self.Choices.append(Choice)
def GetChoices(self):
List = []
for Choice in self.Choices:
if (not Choice.PreLogic) or (apply(Choice.PreLogic, (Global.Party, Global.Maze))):
List.append(Choice)
return List
def DebugPrint(self):
print "---Node '%s': %s"%(self.Name, self.Text)
for Choice in self.Choices:
print Choice.Text
print "-next:",Choice.NextNode
print "-pre:",Choice.PreLogic
print "-post:",Choice.PostLogic
print "-trig:",Choice.TriggerCode
class DialogTree:
"""
A DialogTree represents a conversation you can have with an NPC. Some are more talkative than others;
many just give some general information and send you on your way. A dialog tree is made of NODES. Each
node SAYS STUFF, and provides one or more OPTIONS. The options can have pre-logic (to see whether they're
shown), or post-logic (to see where they head to). If a node has no text, it isn't displayed at all.
"""
def __init__(self, Name):
self.Nodes = []
self.NodeDict = {}
self.RootNode = None
self.Name = Name
def AddNode(self, Node):
self.Nodes.append(Node)
self.NodeDict[Node.Name] = Node
def GetFirstNode(self):
print "Dialog:GetFirstNode()"
Node = self.Nodes[0]
if Node.Text:
print "Root node has text. Use it!"
return Node
# Branch immediately:
for Branch in Node.Choices:
if (not Branch.PreLogic) or (apply(Branch.PreLogic, (Global.Party, Global.Maze))):
return self.GetNextNode(Branch)
## print "Follow branch with prelogic:", Branch.PreLogic
## if Branch.TriggerCode:
## apply(Branch.TriggerCode, (Global.Party, Global.Maze))
## NodeID = Branch.GetNode()
## if type(NodeID) == types.IntType:
## return self.Nodes[NodeID]
## else:
## return self.NodeDict[NodeID]
def GetNode(self, Name):
return self.NodeDict.get(Name,None)
def GetNextNode(self, Choice):
print "Following choice:", Choice
NodeID = Choice.GetNode()
print "Node ID is:", NodeID
if Choice.TriggerCode:
apply(Choice.TriggerCode, (Global.Party, Global.Maze))
if NodeID == None:
return None
if type(NodeID) == types.IntType:
Node = self.Nodes[NodeID]
else:
Node = self.NodeDict.get(NodeID, None)
if not Node:
print "Node is None - conversation will stop now!"
print self.NodeDict.keys()
if Node and not Node.Text:
print "Branch immediately from %s"%Node.Name
# Branch immediately:
for Branch in Node.Choices:
if (not Branch.PreLogic) or (apply(Branch.PreLogic, (Global.Party, Global.Maze))):
return self.GetNextNode(Branch)
return None
return Node
def IntegrityCheck(self):
for Node in self.Nodes:
if Node.Text.find("|")!=-1:
print "WARNING! Node '%s' has a pipe in its text, probably broken"%Node.Name
print "Text: '%s'"%Node.Text
for Choice in Node.Choices:
if Choice.NextNode:
if type(Choice.NextNode) == types.IntType:
OtherNode = self.Nodes[Choice.NextNode]
else:
OtherNode = self.NodeDict.get(Choice.NextNode, None)
if not OtherNode:
print "** WARNING: Choice '%s' from node %s directs to nonexistent '%s'"%(Choice.Text, Node.Name, Choice.NextNode)
def GetFunction(self, Name):
if Name=="None" or not Name:
return None
try:
return self.Locals[Name]
except:
print "** UNKNOWN FUNCTION: '%s'"%Name
GlobKeys = self.Locals.keys()
GlobKeys.sort()
print GlobKeys
def DebugPrint(self):
print "\n\n\n"
print "---Dialog tree '%s'"%self.Name
for Node in self.Nodes:
Node.DebugPrint()
def Load(self):
File = open(os.path.join("NPC", "%s.txt"%self.Name))
FileText = File.read()
File.close()
# Let's ignore carriage returns, so that we work properly on Linux:
FileText = FileText.replace("\r", "")
FuncEnd = FileText.find("~")
print "FuncEnd:", FuncEnd
# Local execution context:
self.Globals = globals()
self.Locals = locals()
exec(FileText[:FuncEnd], self.Globals, self.Locals)
LastTilde = FuncEnd
NextTilde = FileText.find("~", FuncEnd + 1)
while NextTilde!=-1:
Bits = FileText[LastTilde+1:NextTilde].split("|")
if len(Bits)<2:
print "*** Error: Bad dialog node definition: '%s'"%FileText[LastTilde+1:NextTilde]
NodeBody = Bits[1].strip()
NodeBody = NodeBody.replace("\n"," ")
NodeBody = NodeBody.replace("\\n","\n")
NewNode = DialogNode(Bits[0].strip(), NodeBody)
self.AddNode(NewNode)
FinalTilde = FileText.find("~", NextTilde+1)
if FinalTilde==-1:
FinalTilde = len(FileText)
Lines = FileText[NextTilde+1:FinalTilde].split("\n")
for Line in Lines:
Line = Line.strip()
if len(Line)==0 or Line[0]=="#":
continue
Bits = Line.split("|")
Text = Bits[0]
if len(Bits)>1:
NextNode = Bits[1]
else:
NextNode = None
if NextNode=="" or NextNode=="None":
NextNode = None
if len(Bits)>2:
PreLogic = self.GetFunction(Bits[2])
else:
PreLogic = None
if len(Bits)>3:
PostLogic = self.GetFunction(Bits[3])
else:
PostLogic = None
if len(Bits)>4:
TriggerCode = self.GetFunction(Bits[4])
else:
TriggerCode = None
NewNode.AddChoice(Text, NextNode, PreLogic, PostLogic, TriggerCode)
LastTilde = FinalTilde
NextTilde = FileText.find("~", LastTilde + 1)
# For development:
#self.DebugPrint() #%%%
#self.IntegrityCheck() #%%%
if __name__ == "__main__":
Tree = DialogTree(sys.argv[1])
Tree.Load()
Tree.IntegrityCheck()