1# -*-python-*-
2# GemRB - Infinity Engine Emulator
3# Copyright (C) 2003-2004 The GemRB Project
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 2
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18#
19# LUProfsSelection.py - Modular selection of proficiencies
20
21import GemRB
22from GUIDefines import *
23from ie_stats import *
24import GameCheck
25import GUICommon
26import CommonTables
27
28#the different types possible
29LUPROFS_TYPE_LEVELUP = 1
30LUPROFS_TYPE_DUALCLASS = 2
31LUPROFS_TYPE_CHARGEN = 4
32
33#refs to the script calling this
34ProfsWindow = 0
35ProfsCallback = 0
36
37#all our offsets for the various parts of the window
38ProfsOffsetSum = 0
39ProfsOffsetButton1 = 0
40ProfsOffsetLabel = 0
41ProfsOffsetStar = 0
42ProfsOffsetPress = 0
43Profs2ndOffsetButton1 = Profs2ndOffsetStar = Profs2ndOffsetLabel = -1
44ClassNameSave = 0
45OddIDs = 0
46
47#internal variables
48ProfsPointsLeft = 0
49ProfsNumButtons = 0
50ProfsTopIndex = 0
51ProfsTextArea = 0
52ProfsColumn = 0
53ProfsTable = 0
54ProfCount = 0
55
56# the table offset is used in bg2, since in the end they made it use different
57# profs than in bg1, but left the table entries intact
58ProfsTableOffset = 0
59ProfsScrollBar = None
60
61# iwd1/bg1 table that holds the allowed proficiency <-> class mapping
62ClassWeaponsTable = None
63
64ProfsType = None
65
66def SetupProfsWindow (pc, proftype, window, callback, level1=[0,0,0], level2=[1,1,1], classid=0, scroll=True, profTableOffset=8):
67	"""Opens the proficiency selection window.
68
69	type specifies the type of selection we are doing; choices are above.
70	window specifies the window to be updated.
71	callback specifies the function to call on changes.
72	classid is sent only during dualclassing to specify the new class."""
73
74	global ProfsOffsetSum, ProfsOffsetButton1, ProfsOffsetLabel, ProfsOffsetStar, OddIDs
75	global ProfsOffsetPress, ProfsPointsLeft, ProfsNumButtons, ProfsTopIndex
76	global ProfsScrollBar, ProfsTableOffset, ProfsType
77	global ProfsWindow, ProfsCallback, ProfsTextArea, ProfsColumn, ProfsTable, ProfCount
78	global Profs2ndOffsetButton1, Profs2ndOffsetStar, Profs2ndOffsetLabel, ClassNameSave, ClassWeaponsTable
79
80	# make sure we're within ranges
81	GemRB.SetVar ("ProfsPointsLeft", 0)
82	if not window or not callback or len(level1)!=len(level2):
83		return
84
85	# save the values we'll point to
86	ProfsWindow = window
87	ProfsCallback = callback
88	ProfsTableOffset = profTableOffset
89	ProfsType = type
90
91	if proftype == LUPROFS_TYPE_CHARGEN and (GameCheck.IsBG1() or GameCheck.IsBG2()): #chargen
92		ProfsOffsetSum = 9
93		ProfsOffsetButton1 = 11
94		ProfsOffsetStar = 27
95		ProfsOffsetLabel = 1
96		ProfsOffsetPress = 69
97		ProfsNumButtons = 8
98		ProfsTextArea = ProfsWindow.GetControl (68)
99		ProfsTextArea.SetText (9588)
100		if (scroll):
101			ProfsScrollBar = ProfsWindow.GetControl (78)
102
103		if GameCheck.IsBG2() or GameCheck.IsIWD2():
104			import CharGenCommon
105			CharGenCommon.PositionCharGenWin (ProfsWindow)
106	elif proftype == LUPROFS_TYPE_LEVELUP and GameCheck.IsBG2(): #levelup
107		ProfsOffsetSum = 36
108		ProfsOffsetButton1 = 1
109		ProfsOffsetStar = 48
110		ProfsOffsetLabel = 24
111		ProfsOffsetPress = 112
112		ProfsNumButtons = 7
113		ProfsTextArea = ProfsWindow.GetControl (110)
114		ProfsScrollBar = ProfsWindow.GetControl (108)
115	elif proftype == LUPROFS_TYPE_LEVELUP and GameCheck.IsBG1(): #levelup
116		ProfsOffsetSum = 36
117		ProfsOffsetButton1 = 1
118		ProfsOffsetStar = 48
119		ProfsOffsetLabel = 24
120		ProfsOffsetPress = 17
121		ProfsNumButtons = 8
122		ProfsTextArea = ProfsWindow.GetControl (42)
123		if (scroll):
124			ProfsScrollBar = ProfsWindow.GetControl (108)
125	elif proftype == LUPROFS_TYPE_LEVELUP and GameCheck.IsIWD1(): #levelup
126		ProfsOffsetSum = 36
127		ProfsOffsetButton1 = 1
128		ProfsOffsetStar = 48
129		ProfsOffsetLabel = 24
130		ProfsOffsetPress = -1
131		ProfsNumButtons = 15 # 8+7, the 7 are done with the following vars
132		Profs2ndOffsetButton1 = 150
133		Profs2ndOffsetStar = 115
134		Profs2ndOffsetLabel = 108
135		ProfsTextArea = ProfsWindow.GetControl (42)
136		if (scroll):
137			ProfsScrollBar = ProfsWindow.GetControl (108)
138		OddIDs = 0
139	elif proftype == LUPROFS_TYPE_DUALCLASS and GameCheck.IsIWD1(): #dualclass
140		ProfsOffsetSum = 40
141		ProfsOffsetButton1 = 50
142		ProfsOffsetStar = 0
143		ProfsOffsetLabel = 41
144		ProfsOffsetPress = -1 #66
145		ProfsNumButtons = 15
146		Profs2ndOffsetButton1 = 78
147		Profs2ndOffsetStar = 92
148		Profs2ndOffsetLabel = 126
149		ProfsTextArea = ProfsWindow.GetControl (74)
150		ProfsTextArea.SetText (9588)
151		if (scroll):
152			ProfsScrollBar = ProfsWindow.GetControl (None)
153		OddIDs = 1
154	elif proftype == LUPROFS_TYPE_DUALCLASS and GameCheck.IsBG1(): #dualclass
155		ProfsOffsetSum = 40
156		ProfsOffsetButton1 = 50
157		ProfsOffsetStar = 0
158		ProfsOffsetLabel = 41
159		ProfsOffsetPress = -1 #FIXME
160		ProfsNumButtons = 8
161		ProfsTextArea = ProfsWindow.GetControl (74)
162		ProfsTextArea.SetText (9588)
163		if (scroll):
164			ProfsScrollBar = ProfsWindow.GetControl (None)
165	elif proftype == LUPROFS_TYPE_DUALCLASS: #dualclass
166		ProfsOffsetSum = 40
167		ProfsOffsetButton1 = 50
168		ProfsOffsetStar = 0
169		ProfsOffsetLabel = 41
170		ProfsOffsetPress = 66
171		ProfsNumButtons = 8
172		ProfsTextArea = ProfsWindow.GetControl (74)
173		ProfsTextArea.SetText (9588)
174		if (scroll):
175			ProfsScrollBar = ProfsWindow.GetControl (78)
176	else: #unknown
177		return
178
179	#nullify internal variables
180	GemRB.SetVar ("ProfsTopIndex", 0)
181	ProfsPointsLeft = 0
182	ProfsTopIndex = 0
183
184	ProfsTable = GemRB.LoadTable ("profs")
185	if GameCheck.IsIWD1() or GameCheck.IsBG1():
186		ClassWeaponsTable = GemRB.LoadTable ("clasweap")
187	else:
188		ClassWeaponsTable = None
189
190	#get the class name
191	IsDual = GUICommon.IsDualClassed (pc, 1)
192	if classid: #for dual classes when we can't get the class dualing to
193		Class = classid
194	elif IsDual[0] == 3:
195		Class = CommonTables.KitList.GetValue (IsDual[2], 7)
196	elif IsDual[0]:
197		Class = GUICommon.GetClassRowName(IsDual[2], "index")
198		Class = CommonTables.Classes.GetValue (Class, "ID")
199	else:
200		Class = GemRB.GetPlayerStat (pc, IE_CLASS)
201	ClassName = GUICommon.GetClassRowName (Class, "class")
202
203	# profs.2da has entries for everyone, so no need to muck around
204	ProfsRate = ProfsTable.GetValue (ClassName, "RATE")
205
206	#figure out how many prof points we have
207	if sum (level1) == 0: #character is being generated (either chargen or dual)
208		ProfsPointsLeft = ProfsTable.GetValue (ClassName, "FIRST_LEVEL")
209
210	ProfIndex = 0
211	IsMulti = GUICommon.IsMultiClassed (pc, 1)
212	if IsMulti[0] > 1:
213		if sum (level1) == 0: #character is being generated (either chargen or dual)
214			# don't give too many points; exact formula unknown, but this works with f/m and f/m/t
215			ProfsPointsLeft += sum(level2) // IsMulti[0] // ProfsRate
216		else:
217			# just look at the fastest prof-gaining class in the bunch and consider their level
218			# eg. a m/t should get a point at 3/4 and 7/8 (rate of 4)
219			BestRate = 100
220			for cls in IsMulti[1:]:
221				if cls == 0:
222					break
223				ClsName = GUICommon.GetClassRowName (cls, "class")
224				Rate = ProfsTable.GetValue (ClsName, "RATE")
225				if Rate < BestRate:
226					BestRate = Rate
227					ProfIndex = IsMulti[1:].index(cls)
228
229			ProfsPointsLeft += level2[ProfIndex] // ProfsRate - level1[ProfIndex] // ProfsRate
230	else:
231		if GUICommon.IsDualSwap (pc):
232			ProfIndex = 1
233
234		#we need these 2 number to floor before subtracting
235		ProfsPointsLeft += level2[ProfIndex] // ProfsRate - level1[ProfIndex] // ProfsRate
236
237	#setup prof vars for passing between functions
238	ProfsTable = GemRB.LoadTable ("weapprof")
239
240	# weapprof has no sorcerer entry
241	if ClassName == "SORCERER":
242		ClassName = "MAGE"
243	ClassNameSave = ClassName
244
245	# if we have the classweapons table, use it
246	if ClassWeaponsTable:
247		ProfsColumn = ClassWeaponsTable.GetRowIndex (ClassName)
248	else:
249		Kit = GUICommon.GetKitIndex (pc)
250		if Kit and proftype != LUPROFS_TYPE_DUALCLASS and IsMulti[0]<2 and IsDual[0] in [0, 3]:
251			#if we do kit with dualclass, we'll get the old kit (usually)
252			#also don't want to worry about kitted multis
253			ProfsColumn = CommonTables.KitList.GetValue (Kit, 5)
254		else:
255			ProfsColumn = ProfsTable.GetColumnIndex (ClassName)
256
257	#setup some basic counts
258	RowCount = ProfsTable.GetRowCount () - ProfsTableOffset
259	ProfCount = RowCount-ProfsNumButtons #decrease it with the number of controls
260
261	ProfsAssignable = 0
262	TwoWeapIndex = ProfsTable.GetRowIndex ("2WEAPON")
263	for i in range(RowCount):
264		ProfName = ProfsTable.GetValue (i+ProfsTableOffset, 1)
265		#decrease it with the number of invalid proficiencies
266		if ProfName > 0x1000000 or ProfName <= 0:
267			ProfCount -= 1
268
269		#we only need the low 3 bits for proficiencies on levelup; otherwise
270		#we just set them all to 0
271		currentprof = 0
272		if proftype == LUPROFS_TYPE_LEVELUP:
273			stat = ProfsTable.GetValue (i+ProfsTableOffset, 0)
274			if GameCheck.IsBG1():
275				stat = stat + IE_PROFICIENCYBASTARDSWORD
276			currentprof = GemRB.GetPlayerStat (pc, stat)&0x07
277		else:
278			#rangers always get 2 points in 2 weapons style
279			if (i+ProfsTableOffset) == TwoWeapIndex and "RANGER" in ClassName.split("_"):
280				currentprof = 2
281		GemRB.SetVar ("Prof "+str(i), currentprof)
282		GemRB.SetVar ("ProfBase "+str(i), currentprof)
283
284		#see if we can assign to this prof
285		if ClassWeaponsTable:
286			# this table has profs as rows, so ignore the weird use of the column var
287			# it also has different ordering than ProfsTable
288			col = ClassWeaponsTable.GetColumnIndex (ProfsTable.GetRowName(i))
289			maxprof = ClassWeaponsTable.GetValue (ProfsColumn, col)
290		else:
291			maxprof = ProfsTable.GetValue(i+ProfsTableOffset, ProfsColumn)
292		if maxprof > currentprof:
293			ProfsAssignable += maxprof-currentprof
294
295	#correct the profs left if we can't assign that much
296	if ProfsPointsLeft > ProfsAssignable:
297		ProfsPointsLeft = ProfsAssignable
298	GemRB.SetVar ("ProfsPointsLeft", ProfsPointsLeft)
299
300	# setup the +/- and info controls
301	for i in range (ProfsNumButtons):
302		if ProfsOffsetPress != -1:
303			Button=ProfsWindow.GetControl(i+ProfsOffsetPress)
304			Button.SetVarAssoc("Prof", i)
305			Button.SetEvent(IE_GUI_BUTTON_ON_PRESS, ProfsJustPress)
306
307		cid = i*2+ProfsOffsetButton1
308		if Profs2ndOffsetButton1 != -1 and i > 7:
309			cid = (i-8)*2+Profs2ndOffsetButton1
310
311		Button=ProfsWindow.GetControl(cid)
312		Button.SetVarAssoc("Prof", i)
313		Button.SetEvent(IE_GUI_BUTTON_ON_PRESS, ProfsLeftPress)
314		Button.SetActionInterval (200)
315
316		Button=ProfsWindow.GetControl(cid+1)
317		Button.SetVarAssoc("Prof", i)
318		Button.SetEvent(IE_GUI_BUTTON_ON_PRESS, ProfsRightPress)
319		Button.SetActionInterval (200)
320
321	if(ProfsScrollBar):
322		# proficiencies scrollbar
323		ProfsScrollBar.SetEvent(IE_GUI_SCROLLBAR_ON_CHANGE, ProfsScrollBarPress)
324		ProfsWindow.SetEventProxy(ProfsScrollBar)
325		ProfsScrollBar.SetVarAssoc ("ProfsTopIndex", ProfCount)
326	ProfsRedraw (1)
327	return
328
329def ProfsRedraw (first=0):
330	"""Redraws the proficiencies part of the window.
331
332	If first is true, it skips ahead to the first assignable proficiency."""
333
334	global ProfsTopIndex, ProfsScrollBar
335
336	ProfSumLabel = ProfsWindow.GetControl(0x10000000+ProfsOffsetSum)
337	ProfSumLabel.SetText(str(ProfsPointsLeft))
338	SkipProfs = []
339
340	for i in range(ProfsNumButtons):
341		Pos=ProfsTopIndex+i
342		ProfName = ProfsTable.GetValue(Pos+ProfsTableOffset, 1) #we add the bg1 skill count offset
343		if ClassWeaponsTable: # iwd
344			MaxProf = ClassWeaponsTable.GetValue (ClassNameSave, ProfsTable.GetRowName(Pos))
345		else:
346			MaxProf = ProfsTable.GetValue(Pos+ProfsTableOffset, ProfsColumn)
347			ProfName = ProfsTable.GetValue(Pos+ProfsTableOffset, 1)
348			if ProfName > 0x1000000 or ProfName < 0:
349				MaxProf = 0
350
351		cid = i*2+ProfsOffsetButton1
352		if Profs2ndOffsetButton1 != -1 and i > 7:
353			cid = (i-8)*2+Profs2ndOffsetButton1
354		Button1=ProfsWindow.GetControl (cid)
355		Button2=ProfsWindow.GetControl (cid+1)
356		if MaxProf == 0:
357			Button1.SetState(IE_GUI_BUTTON_DISABLED)
358			Button2.SetState(IE_GUI_BUTTON_DISABLED)
359			Button1.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_OR)
360			Button2.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_OR)
361			if GameCheck.IsBG2() and (i==0 or ((i-1) in SkipProfs)):
362				SkipProfs.append (i)
363		else:
364			Button1.SetState(IE_GUI_BUTTON_ENABLED)
365			Button2.SetState(IE_GUI_BUTTON_ENABLED)
366			Button1.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_NAND)
367			Button2.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_NAND)
368
369		cid = 0x10000000 + ProfsOffsetLabel + i
370		if Profs2ndOffsetLabel != -1 and i > 7:
371			if OddIDs:
372				cid = 0x10000000 + Profs2ndOffsetLabel + 2*(i - 8)
373			else:
374				cid = 0x10000000 + Profs2ndOffsetLabel + i - 8
375
376		Label=ProfsWindow.GetControl (cid)
377		Label.SetText(ProfName)
378
379		ActPoint = GemRB.GetVar("Prof "+str(Pos) )
380		for j in range(5): #5 is maximum distributable
381			cid = i*5 + j + ProfsOffsetStar
382			if Profs2ndOffsetStar != -1 and i > 7:
383				cid = (i-8)*5 + j + Profs2ndOffsetStar
384			Star=ProfsWindow.GetControl (cid)
385			Star.SetSprites("GUIPFC", 0, 0, 0, 0, 0)
386			if ActPoint > j:
387				Star.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_NAND)
388			else:
389				Star.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_OR)
390
391	if first and len (SkipProfs):
392		ProfsTopIndex += SkipProfs[-1]+1
393		GemRB.SetVar ("ProfsTopIndex", ProfsTopIndex)
394		if (ProfsScrollBar):
395			ProfsScrollBar.SetVarAssoc ("ProfsTopIndex", ProfCount)
396		ProfsRedraw ()
397	return
398
399def ProfsScrollBarPress():
400	"""Scrolls the window by reassigning ProfsTopIndex."""
401
402	global ProfsTopIndex
403
404	ProfsTopIndex = GemRB.GetVar ("ProfsTopIndex")
405	ProfsRedraw ()
406	return
407
408def ProfsJustPress(btn, val):
409	"""Updates the text area with a description of the proficiency."""
410	Pos = val+ProfsTopIndex
411	ProfsTextArea.SetText (ProfsTable.GetValue(Pos+ProfsTableOffset, 2) )
412	return
413
414def ProfsRightPress(btn, val):
415	"""Decrease the current proficiency by one."""
416
417	global ProfsPointsLeft
418
419	Pos = val+ProfsTopIndex
420	ProfsTextArea.SetText(ProfsTable.GetValue(Pos+ProfsTableOffset, 2) )
421	ActPoint = GemRB.GetVar("Prof "+str(Pos) )
422	MinPoint = GemRB.GetVar ("ProfBase "+str(Pos) )
423	if ActPoint <= 0 or ActPoint <= MinPoint:
424		return
425	GemRB.SetVar("Prof "+str(Pos),ActPoint-1)
426	ProfsPointsLeft += 1
427	GemRB.SetVar ("ProfsPointsLeft", ProfsPointsLeft)
428	ProfsRedraw ()
429	ProfsCallback ()
430	return
431
432def ProfsLeftPress(btn, val):
433	"""Increases the current proficiency by one."""
434
435	global ProfsPointsLeft
436
437	Pos = val+ProfsTopIndex
438	ProfsTextArea.SetText(ProfsTable.GetValue(Pos+ProfsTableOffset, 2) )
439	if ProfsPointsLeft == 0:
440		return
441	if GameCheck.IsIWD1() or GameCheck.IsBG1():
442		ProfMaxTable = GemRB.LoadTable ("profsmax")
443		if ProfsType == LUPROFS_TYPE_CHARGEN:
444			MaxProf = ProfMaxTable.GetValue(ClassNameSave, "FIRST_LEVEL")
445		else:
446			MaxProf = ProfMaxTable.GetValue(ClassNameSave, "OTHER_LEVELS")
447	else:
448		MaxProf = ProfsTable.GetValue(Pos+ProfsTableOffset, ProfsColumn)
449	if MaxProf>5:
450		MaxProf = 5
451
452	ActPoint = GemRB.GetVar("Prof "+str(Pos) )
453	if ActPoint >= MaxProf:
454		return
455	GemRB.SetVar("Prof "+str(Pos),ActPoint+1)
456	ProfsPointsLeft -= 1
457	GemRB.SetVar ("ProfsPointsLeft", ProfsPointsLeft)
458	ProfsRedraw ()
459	ProfsCallback ()
460	return
461
462def ProfsSave (pc, proftype = LUPROFS_TYPE_LEVELUP):
463	"""Updates the actor with the new proficiencies."""
464
465	ProfCount = ProfsTable.GetRowCount () - ProfsTableOffset
466	for i in range(ProfCount): # skip bg1 weapprof.2da proficiencies
467		ProfID = ProfsTable.GetValue (i+ProfsTableOffset, 0)
468		if GameCheck.IsBG1():
469			ProfID = ProfID + IE_PROFICIENCYBASTARDSWORD
470		SaveProf = GemRB.GetVar ("Prof "+str(i))
471
472		if proftype == LUPROFS_TYPE_CHARGEN and GameCheck.IsBG2():
473			GemRB.DispelEffect (pc, "Proficiency", ProfID)
474		else:
475			if proftype != LUPROFS_TYPE_DUALCLASS:
476				OldProf = GemRB.GetPlayerStat (pc, ProfID) & 0x38
477				SaveProf = OldProf | SaveProf
478			else: # gotta move the old prof to the back for dual class
479				OldProf = GemRB.GetPlayerStat (pc, ProfID) & 0x07
480				SaveProf = (OldProf << 3) | SaveProf
481
482		GemRB.SetPlayerStat (pc, ProfID, SaveProf)
483		if GameCheck.IsBG2() and (proftype == LUPROFS_TYPE_LEVELUP or proftype == LUPROFS_TYPE_CHARGEN):
484			if SaveProf:
485				GemRB.ApplyEffect (pc, "Proficiency", SaveProf, ProfID)
486	return
487
488def ProfsNullify ():
489	"""Resets all of the internal variables to 0."""
490
491	global ProfsTable
492	if not ProfsTable:
493		ProfsTable = GemRB.LoadTable ("weapprof")
494	for i in range (ProfsTable.GetRowCount()-ProfsTableOffset+1): #skip bg1 profs
495		GemRB.SetVar ("Prof "+str(i), 0)
496		GemRB.SetVar ("ProfBase "+str(i), 0)
497	return
498