1# -*-python-*-
2# GemRB - Infinity Engine Emulator
3# Copyright (C) 2003 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
20# Maze.py - script to generate the modron maze in PST
21
22###################################################
23
24import GemRB
25from maze_defs import *
26from GUIDefines import STR_AREANAME
27
28rooms = None
29max = 0
30dims = 0
31entries = None
32offx = (0,0,1,0,-1)
33offy = (0,-1,0,1,0)
34#wallbits = (0, WALL_WEST, WALL_NORTH, WALL_EAST, WALL_SOUTH)
35wallbits = (0, WALL_NORTH, WALL_EAST, WALL_SOUTH, WALL_WEST)
36entrances = ("", "Entry3", "Entry4", "Entry1", "Entry2")
37doors = ("", "northdoor", "eastdoor", "southdoor", "westdoor")
38anims = ("", "a13xxdn", "a13xxde", "a13xxds", "a13xxdw")
39aposx=(0,1012,1005,505,380)
40aposy=(0,524,958,996,562)
41cposx=(686,886,497)
42cposy=(498,722,726)
43
44def Possible(posx, posy):
45	global entries
46
47	pos = posx*MAZE_MAX_DIM+posy
48
49	if entries[pos]:
50		return -1
51	return pos
52
53def GetPossible (pos):
54	posx = pos/MAZE_MAX_DIM
55	posy = pos-posx*MAZE_MAX_DIM
56	possible = []
57
58	if posx>0:
59		newpos = Possible(posx-1, posy)
60		if newpos!=-1:
61			possible[:0] = [newpos]
62	if posy>0:
63		newpos = Possible(posx, posy-1)
64		if newpos!=-1:
65			possible[:0] = [newpos]
66	if posx<dims-1:
67		newpos = Possible(posx+1, posy)
68		if newpos!=-1:
69			possible[:0] = [newpos]
70	if posy<dims-1:
71		newpos = Possible(posx, posy+1)
72		if newpos!=-1:
73			possible[:0] = [newpos]
74
75	return possible
76
77#loads a 2da and sets it up as maze
78def LoadMazeFrom2da(tablename):
79	MazeTable = GemRB.LoadTable(tablename)
80	if MazeTable == None:
81		return
82	size = MazeTable.GetValue(-1,-1)
83	GemRB.SetupMaze(size, size)
84	traps = 0
85	for i in range(MazeTable.GetRowCount()):
86		Area = MazeTable.GetRowName(i)
87		OVERRIDE = MazeTable.GetValue(Area,"OVERRIDE")
88		TRAPTYPE = MazeTable.GetValue(Area,"TRAPTYPE")
89		WALLS = MazeTable.GetValue(Area,"WALLS")
90		VISITED = MazeTable.GetValue(Area,"VISITED")
91		pos = ConvertPos(int(Area[4:])-1)
92		GemRB.SetMazeEntry(pos, ME_OVERRIDE, OVERRIDE)
93		GemRB.SetMazeEntry(pos, ME_TRAP, TRAPTYPE)
94		GemRB.SetMazeEntry(pos, ME_WALLS, WALLS)
95		GemRB.SetMazeEntry(pos, ME_VISITED, VISITED)
96		if TRAPTYPE>=0:
97			traps = traps+1
98
99	#disabling special rooms
100	GemRB.SetMazeData(MH_POS1X, -1)
101	GemRB.SetMazeData(MH_POS1Y, -1)
102	GemRB.SetMazeData(MH_POS2X, -1)
103	GemRB.SetMazeData(MH_POS2Y, -1)
104	#adding foyer coordinates (middle of bottom)
105	GemRB.SetMazeData(MH_POS3X, size/2)
106	GemRB.SetMazeData(MH_POS3Y, size-1)
107	#adding engine room coordinates (bottom right)
108	GemRB.SetMazeData(MH_POS4X, size-1)
109	GemRB.SetMazeData(MH_POS4Y, size-1)
110	#adding trap
111	GemRB.SetMazeData(MH_TRAPCOUNT, traps)
112	#finish
113	GemRB.SetMazeData(MH_INITED, 1)
114	return
115
116def AddRoom (pos):
117	global rooms
118	global entries
119
120	rooms[len(rooms):]=[pos]
121	entries[pos] = 1
122	return
123
124def MainRoomFits (pos1x, pos1y, pos):
125	global entries
126
127	room = pos1x*MAZE_MAX_DIM+pos1y
128	if room==pos:
129		return False
130
131	south = pos1x*MAZE_MAX_DIM+pos1y+1
132	if south==pos:
133		return False
134
135	north = pos1x*MAZE_MAX_DIM+pos1y-1
136	if north==pos:
137		return False
138
139	GemRB.SetMazeEntry(room, ME_WALLS, WALL_SOUTH)
140	entries[room] = 1
141	entries[north] = 1
142	return True
143
144def zeros (size):
145	return size*[0]
146
147def PrintMaze():
148	header = GemRB.GetMazeHeader()
149	if header==None or header["Inited"]==0:
150		print("There is no maze or it is not initialized!")
151		return
152
153	MazeX = header["MazeX"]
154	MazeY = header["MazeY"]
155	MainX = header["Pos1X"]
156	MainY = header["Pos1Y"]
157	NordomX = header["Pos2X"]
158	NordomY = header["Pos2Y"]
159	FoyerX = header["Pos3X"]
160
161	print("Maze size is " + str(MazeX) + "X" + str(MazeY))
162	for y in range (MazeY):
163		line = ""
164		for x in range (MazeX):
165			pos = MAZE_MAX_DIM*x+y
166			entry = GemRB.GetMazeEntry(pos)
167			if entry["Walls"]&WALL_NORTH:
168				line = line + "+ "
169			else:
170				line = line + "+-"
171		print(line + "+")
172		line = ""
173		for x in range (MazeX):
174			pos = MAZE_MAX_DIM*x+y
175			entry = GemRB.GetMazeEntry(pos)
176			if entry["Walls"]&WALL_WEST:
177				line = line + " "
178			else:
179				line = line + "|"
180			if x == NordomX and y == NordomY:
181				line = line + "N"
182			elif x == MainX and y == MainY:
183				line = line + "W"
184			elif entry["Trapped"]>=0:
185				line = line + chr(entry["Trapped"]+65)
186			else:
187				line = line + " "
188		print(line + "|")
189	line = ""
190	for x in range (MazeX):
191		if FoyerX==x:
192			line = line + "+ "
193		else:
194			line = line + "+-"
195	print(line + "+")
196	return
197
198def ConvertPos (pos):
199	return ((pos&7)<<3)|(pos>>3)
200
201###################################################
202def CreateMaze ():
203	global max
204	global dims
205	global entries
206	global rooms
207
208	if GemRB.GetGameVar("EnginInMaze")>0:
209		LoadMazeFrom2da("easymaze")
210		return
211
212	mazedifficulty = GemRB.GetGameVar("MazeDifficulty")
213
214	#make sure there are no more traps than rooms
215	#make sure dimensions don't exceed maximum possible
216	if mazedifficulty==0:
217		dims = 4
218		traps = 5
219	elif mazedifficulty==1:
220		dims = 6
221		traps = 12
222	else:
223		dims = 8
224		traps = 20
225
226	entries = zeros(MAZE_ENTRY_COUNT)
227	rooms = []
228
229	GemRB.SetupMaze(dims, dims)
230	for x in range(dims, MAZE_MAX_DIM):
231		for y in range(dims, MAZE_MAX_DIM):
232			pos = x*MAZE_MAX_DIM+y
233			entries[pos] = 1
234
235	nordomx = GemRB.Roll(1, dims-1, -1)
236	nordomy = GemRB.Roll(1, dims, -1)
237	pos = nordomx*MAZE_MAX_DIM+nordomy
238	entries[pos] = 1
239	GemRB.SetMazeEntry(pos, ME_WALLS, WALL_EAST)
240	pos = nordomx*MAZE_MAX_DIM+nordomy+MAZE_MAX_DIM
241	AddRoom(pos)
242	if (mazedifficulty>1):
243		GemRB.SetMazeData(MH_POS2X, nordomx)
244		GemRB.SetMazeData(MH_POS2Y, nordomy)
245		pos1x = GemRB.Roll(1, dims, -1)
246		pos1y = GemRB.Roll(1, dims-2, 0)
247		while not MainRoomFits(pos1x, pos1y, pos):
248			pos1x = GemRB.Roll(1, dims, -1)
249			pos1y = GemRB.Roll(1, dims-2, 0)
250		GemRB.SetMazeData(MH_POS1X, pos1x)
251		GemRB.SetMazeData(MH_POS1Y, pos1y)
252	else:
253		GemRB.SetMazeData(MH_POS1X, -1)
254		GemRB.SetMazeData(MH_POS1Y, -1)
255		GemRB.SetMazeData(MH_POS2X, -1)
256		GemRB.SetMazeData(MH_POS2Y, -1)
257
258	oldentries = entries
259	for i in range(traps):
260		posx = GemRB.Roll(1, dims, -1)
261		posy = GemRB.Roll(1, dims, -1)
262		pos = posx*MAZE_MAX_DIM+posy
263		while entries[pos]:
264			pos = pos + 1
265			if pos>=MAZE_ENTRY_COUNT:
266				posx = 0
267				posy = 0
268				pos = 0
269			else:
270				posx = pos/MAZE_MAX_DIM
271				posy = pos-posx*MAZE_MAX_DIM
272		GemRB.SetMazeEntry(pos, ME_TRAP, GemRB.Roll(1, 3, -1) )
273
274	entries = oldentries
275	while len(rooms)>0:
276		pos = rooms.pop(0)
277		posx = pos/MAZE_MAX_DIM
278		posy = pos-posx*MAZE_MAX_DIM
279		possible = GetPossible(pos)
280		plen = len(possible)
281		if plen>0:
282			if plen==1:
283				newpos = possible[0]
284			else:
285				#adding item back if we got room to grow
286				AddRoom(pos)
287				newpos = possible[GemRB.Roll(1, plen, -1) ]
288			if entries[newpos]==0:
289				if newpos+1 == pos:
290					GemRB.SetMazeEntry(pos, ME_WALLS, WALL_NORTH)
291				elif pos+1 == newpos:
292					GemRB.SetMazeEntry(pos, ME_WALLS, WALL_SOUTH)
293				elif pos+MAZE_MAX_DIM == newpos:
294					GemRB.SetMazeEntry(pos, ME_WALLS, WALL_EAST)
295				elif newpos+MAZE_MAX_DIM == pos:
296					GemRB.SetMazeEntry(pos, ME_WALLS, WALL_WEST)
297				else:
298					print("Something went wrong at pos: ", pos, " newpos: ", newpos)
299				AddRoom(newpos)
300
301	#adding foyer coordinates
302	x = GemRB.Roll(1,dims,-1)
303	while x!=nordomx and dims-1!=nordomy:
304		x=GemRB.Roll(1,dims,-1)
305
306	GemRB.SetMazeData(MH_POS3X, GemRB.Roll(1,dims,-1) )
307	GemRB.SetMazeData(MH_POS3Y, dims-1)
308
309	#setting engine room coordinates to hidden (accessible from foyer)
310	GemRB.SetMazeData(MH_POS4X, -1)
311	GemRB.SetMazeData(MH_POS4Y, -1)
312	#adding traps
313	GemRB.SetMazeData(MH_TRAPCOUNT, traps)
314	#finish
315	GemRB.SetMazeData(MH_INITED, 1)
316	return
317
318def FormatAreaName(pos):
319	if pos<9:
320		return "AR130"+str(pos+1)
321	return "AR13"+str(pos+1)
322
323def CustomizeMaze(AreaName):
324
325	header = GemRB.GetMazeHeader()
326
327	mainX = header['Pos1X']
328	mainY = header['Pos1Y']
329	nordomX = header['Pos2X']
330	nordomY = header['Pos2Y']
331	foyerX = header['Pos3X']
332	foyerY = header['Pos3Y']
333	engineX = header['Pos4X']
334	engineY = header['Pos4Y']
335	#modron foyer
336	if AreaName == "fy":
337		#TODO modron foyer, only one entrance if EnginInMaze = 1
338		tmp = foyerX+foyerY*MAZE_MAX_DIM
339		GemRB.SetMapExit ("exit1", FormatAreaName(tmp), "Entry3" )
340
341		#disable engine room
342		if GemRB.GetGameVar("EnginInMaze")==1:
343			GemRB.SetMapExit ("exit3" )
344			GemRB.SetMapDoor (doors[3], 0)
345			GemRB.SetMapAnimation(aposx[3], aposy[3], anims[3])
346		else:
347			GemRB.SetMapExit ("exit3", "AR13EN")
348
349		GemRB.SetMapExit ("exit4" )
350		GemRB.SetMapAnimation(aposx[4], aposy[4], anims[4])
351		return
352
353	if AreaName == "en":
354		if GemRB.GetGameVar("EnginInMaze")==1:
355			tmp = engineX+(engineY-1)*MAZE_MAX_DIM
356			GemRB.SetMapExit ("exit1", FormatAreaName(tmp), "Entry3" )
357		else:
358			GemRB.SetMapExit ("exit1", "AR13FY" )
359		return
360
361	if AreaName == "wz":
362		#TODO wizard's lair
363		tmp = mainY+mainX*MAZE_MAX_DIM
364		entry = GemRB.GetMazeEntry(tmp)
365		tmp = mainX+mainY*MAZE_MAX_DIM
366		GemRB.SetMapExit ("exit3", FormatAreaName(tmp), "Entry1" )
367		GemRB.SetMapDoor (doors[3], 1)
368		return
369
370	if AreaName == "fd":
371		#TODO nordom
372		tmp = nordomY+nordomX*MAZE_MAX_DIM
373		entry = GemRB.GetMazeEntry(tmp)
374		tmp = nordomX+nordomY*MAZE_MAX_DIM
375		GemRB.SetMapExit ("exit2", FormatAreaName(tmp), "Entry4" )
376		GemRB.SetMapDoor (doors[2], 1)
377		return
378
379	tmp = int(AreaName)-1
380	if tmp<0 or tmp>63:
381		return
382
383	pos = ConvertPos(tmp)
384	entry = GemRB.GetMazeEntry(pos)
385	#TODO: customize maze area based on entry (walls, traps)
386
387	if entry['Visited']:
388		#already customized
389		return
390
391	difficulty = GemRB.GetGameVar("MazeDifficulty")
392	if difficulty == 0:
393		name = "CLOW"
394	elif difficulty == 1:
395		name = "CMOD"
396	else:
397		name = "CHIGH"
398
399	ccount = GemRB.Roll(1,3,0)
400
401	for i in range(ccount):
402		GemRB.CreateCreature(0, name, cposx[i], cposy[i])
403
404	trapped = entry['Trapped']
405	if trapped>=0:
406		roll = GemRB.Roll(1,4,0)
407		GemRB.SetMapRegion('Trap'+chr(trapped+65), '1300trp'+str(roll) )
408
409	GemRB.SetMazeEntry(pos, ME_VISITED, 1)
410	walls = entry['Walls']
411	y = tmp / MAZE_MAX_DIM
412	x = tmp - y*MAZE_MAX_DIM
413	for i in range(1,5):
414		if wallbits[i]&walls:
415			x2 = x+offx[i]
416			y2 = y+offy[i]
417			set = 0
418			if x2 == nordomX and y2 == nordomY:
419				NewArea = "AR13FD"
420			elif x2 == mainX and y2 == mainY:
421				NewArea = "AR13WZ"
422			elif x2 == foyerX and y2 == foyerY+1:
423				NewArea = "AR13FY"
424			elif x2 == engineX and y2 == engineY:
425				NewArea = "AR13EN"
426			else:
427				if x2>=0 and x2<MAZE_MAX_DIM and y2>=0 and y2<MAZE_MAX_DIM:
428					#reversed coordinates
429					NewArea = FormatAreaName (x2+y2*MAZE_MAX_DIM)
430				else:
431					#maximum dimensions
432					set = 1
433		else:
434			set = 1
435
436		if set:
437			#remove exit
438			GemRB.SetMapExit ("exit"+str(i) )
439			GemRB.SetMapDoor (doors[i], 0)
440			GemRB.SetMapAnimation(aposx[i], aposy[i], anims[i])
441		else:
442			#set exit
443			GemRB.SetMapExit ("exit"+str(i), NewArea, entrances[i] )
444			GemRB.SetMapDoor (doors[i], 1)
445			GemRB.SetMapAnimation(-1, -1, "", 0, 0)
446	return
447
448def CustomizeArea():
449	Area = GemRB.GetGameString (STR_AREANAME)
450	if Area[0:4] == "ar13":
451		CustomizeMaze(Area[4:])
452		return
453
454	#TODO insert non maze area customization here (set own area scripts for special areas)
455	return
456