1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """ main xml parser class for xml map loading """
24 from __future__ import print_function
25
26 from builtins import str
27 from builtins import object
28 import time
29
30 from fife import fife
31
32 from fife.extensions.serializers import ET
33 from fife.extensions.serializers import SerializerError, InvalidFormat
34 from fife.extensions.serializers import NameClash, NotFound, WrongFileType
35
36 from fife.extensions.serializers.xmlobject import XMLObjectLoader
37 from fife.extensions.serializers.xmlanimation import loadXMLAnimation
38 from fife.extensions.serializers.xml_loader_tools import loadImportFile, loadImportDir
39 from fife.extensions.serializers.xml_loader_tools import loadImportDirRec
40 from fife.extensions.serializers.xml_loader_tools import root_subfile, reverse_root_subfile
41
42
43 FORMAT = '1.0'
44
46 """ The B{XMLMapLoader} parses the xml map using several section.
47 Each section fires a callback (if given) which can e. g. be
48 used to show a progress bar.
49
50 The callback sends two values, a string and a float (which shows
51 the overall process): callback(string, float)
52 """
53 - def __init__(self, engine, callback, debug, extensions):
54 """
55 @type engine: object
56 @param engine: a pointer to fife.engine
57 @type callback: function
58 @param callback: a callback with two arguments, optional
59 @type debug: bool
60 @param debug: flag to activate / deactivate print statements
61 @type extensions: dict
62 @param extensions: information package which extension should be activated (lights, sounds)
63 """
64
65
66 self.callback = callback
67 self.debug = debug
68
69 self.engine = engine
70 self.vfs = self.engine.getVFS()
71 self.model = self.engine.getModel()
72 self.image_manager = self.engine.getImageManager()
73 self.anim_pool = None
74
75 self.obj_loader = XMLObjectLoader(engine)
76
77 self.map = None
78 self.source = None
79 self.time_to_load = 0
80
81 self.nspace = None
82
83 self.msg = {}
84 self.msg['map'] = 'created map'
85 self.msg['imports'] = 'loaded imports'
86 self.msg['layer'] = 'loaded layer: %s'
87 self.msg['camera'] = 'loaded camera: %s'
88
89 if 'sound' not in extensions:
90 extensions['sound'] = False
91 if 'lights' not in extensions:
92 extensions['lights'] = False
93
94 self.light_data = {}
95 self.extensions = extensions
96
97 - def _err(self, msg):
98 raise SyntaxError(''.join(['File: ', self.source, ' . ', msg]))
99
101 """ overwrite of B{fife.ResourceLoader}
102
103 @type location: object
104 @param location: path to a map file as a fife.ResourceLocation
105 @return FIFE map object
106 @rtype object
107 """
108 start_time = time.time()
109 self.source = location
110 f = self.vfs.open(self.source)
111 f.thisown = 1
112 tree = ET.parse(f)
113 root = tree.getroot()
114
115 map = self.parse_map(root)
116 self.time_to_load = time.time() - start_time
117 return map
118
120 """ start parsing the xml structure and
121 call submethods for turning found tags
122 into FIFE objects and create the map
123
124 @type mapelt: object
125 @param mapelt: ElementTree root
126 @return FIFE map object
127 @rtype object
128 """
129 if not mapelt:
130 self._err('No <map> element found at top level of map file definition.')
131 _id, format = mapelt.get('id'), mapelt.get('format')
132
133 if not format == FORMAT: self._err(''.join(['This file has format ', format, ' but this loader has format ', FORMAT]))
134 if not _id: self._err('Map declared without an identifier.')
135
136 map = None
137 try:
138 self.map = self.model.createMap(str(_id))
139 self.map.setFilename(self.source)
140 except fife.Exception as e:
141 print(e.getMessage())
142 print(''.join(['File: ', self.source, '. The map ', str(_id), ' already exists! Ignoring map definition.']))
143 return map
144
145
146 self.map.importDirs = []
147
148 if self.callback is not None:
149 self.callback(self.msg['map'], float(0.25) )
150
151 self.parse_imports(mapelt, self.map)
152 self.parse_layers(mapelt, self.map)
153 self.parse_cameras(mapelt, self.map)
154
155
156 if self.light_data:
157 self.create_light_nodes(self.map)
158
159 return self.map
160
162 """ load all objects defined as import into memory
163
164 @type mapelt: object
165 @param mapelt: ElementTree root
166 @return FIFE map object
167 @rtype object
168 """
169 parsedImports = {}
170
171 if self.callback:
172 tmplist = mapelt.findall('import')
173 i = float(0)
174
175 for item in mapelt.findall('import'):
176 _file = item.get('file')
177 if _file:
178 _file = reverse_root_subfile(self.source, _file)
179 _dir = item.get('dir')
180 if _dir:
181 _dir = reverse_root_subfile(self.source, _dir)
182
183
184 if (_dir,_file) in parsedImports:
185 if self.debug: print("Duplicate import:" ,(_dir, _file))
186 continue
187 parsedImports[(_dir,_file)] = 1
188
189 if _file and _dir:
190 loadImportFile(self.obj_loader, '/'.join(_dir, _file), self.engine, self.debug)
191 elif _file:
192 loadImportFile(self.obj_loader, _file, self.engine, self.debug)
193 elif _dir:
194 loadImportDirRec(self.obj_loader, _dir, self.engine, self.debug)
195 map.importDirs.append(_dir)
196 else:
197 if self.debug: print('Empty import statement?')
198
199 if self.callback:
200 i += 1
201 self.callback(self.msg['imports'], float( i / float(len(tmplist)) * 0.25 + 0.25 ) )
202
204 """ create all layers and their instances
205
206 @type mapelt: object
207 @param mapelt: ElementTree root
208 @type map: object
209 @param map: FIFE map object
210 """
211 if self.callback is not None:
212 tmplist = mapelt.findall('layer')
213 i = float(0)
214
215 for layer in mapelt.findall('layer'):
216 _id = layer.get('id')
217 grid_type = layer.get('grid_type')
218
219 if not _id: self._err('<layer> declared with no id attribute.')
220 if not grid_type: self._err(''.join(['Layer ', str(_id), ' has no grid_type attribute.']))
221
222 x_scale = layer.get('x_scale')
223 y_scale = layer.get('y_scale')
224 rotation = layer.get('rotation')
225 x_offset = layer.get('x_offset')
226 y_offset = layer.get('y_offset')
227 z_offset = layer.get('z_offset')
228 pathing = layer.get('pathing')
229 transparency = layer.get('transparency')
230
231 layer_type = layer.get('layer_type')
232 layer_type_id = layer.get('layer_type_id')
233
234 if not x_scale: x_scale = 1.0
235 if not y_scale: y_scale = 1.0
236 if not rotation: rotation = 0.0
237 if not x_offset: x_offset = 0.0
238 if not y_offset: y_offset = 0.0
239 if not z_offset: z_offset = 0.0
240 if not pathing: pathing = "cell_edges_only"
241 if not transparency:
242 transparency = 0
243 else:
244 transparency = int(transparency)
245
246 cellgrid = self.model.getCellGrid(grid_type)
247 if not cellgrid: self._err('<layer> declared with invalid cellgrid type. (%s)' % grid_type)
248
249 cellgrid.setRotation(float(rotation))
250 cellgrid.setXScale(float(x_scale))
251 cellgrid.setYScale(float(y_scale))
252 cellgrid.setXShift(float(x_offset))
253 cellgrid.setYShift(float(y_offset))
254 cellgrid.setZShift(float(z_offset))
255
256 layer_obj = None
257 try:
258 layer_obj = map.createLayer(str(_id), cellgrid)
259 except fife.Exception as e:
260 print(e.getMessage())
261 print('The layer ' + str(_id) + ' already exists! Ignoring this layer.')
262
263 continue
264
265 strgy = fife.CELL_EDGES_ONLY
266 if pathing == "cell_edges_and_diagonals":
267 strgy = fife.CELL_EDGES_AND_DIAGONALS
268
269 layer_obj.setPathingStrategy(strgy)
270 layer_obj.setLayerTransparency(transparency)
271
272 if layer_type:
273 if layer_type == 'walkable':
274 layer_obj.setWalkable(True)
275 elif layer_type == 'interact' and layer_type_id:
276 layer_obj.setInteract(True, layer_type_id)
277
278 self.parse_instances(layer, layer_obj)
279
280 if self.extensions['lights']:
281 self.parse_lights(layer, layer_obj)
282 if self.extensions['sound']:
283 self.parse_sounds(layer, layer_obj)
284
285 if self.callback is not None:
286 i += 1
287 self.callback(self.msg['layer'] % str(_id), float( i / float(len(tmplist)) * 0.25 + 0.5 ) )
288
289
290 layers = map.getLayers()
291 for l in layers:
292 if l.isInteract():
293 walk_layer = map.getLayer(l.getWalkableId())
294 if walk_layer:
295 walk_layer.addInteractLayer(l);
296
297 for l in layers:
298 if l.isWalkable():
299 l.createCellCache()
300
301
302 if self.callback is not None:
303 del tmplist
304 del i
305
307 """ create light nodes
308
309 @type layerelt: object
310 @param layerelt: ElementTree layer branch
311 @type layer: object
312 @param layer: FIFE layer object
313 """
314 _LIGHT_DEFAULT_BLENDING_SRC = -1
315 _LIGHT_DEFAULT_BLENDING_DST = -1
316 _LIGHT_DEFAULT_SUBDIVISIONS = 32
317 _LIGHT_DEFAULT_CAM_ID = 'default'
318 _LIGHT_DEFAULT_INTENSITY = 128
319 _LIGHT_DEFAULT_RADIUS = 10.0
320
321 print("Processing lights ... ")
322 lightelt = layerelt.find('lights')
323 if not lightelt:
324 print("\tno lights found on layer %s" % layer.getId())
325 return
326
327 lights = []
328 for attr in ('l', 'light', 'lgt'):
329 lights.extend(lightelt.findall(attr))
330
331 for light in lights:
332 group = light.get('group')
333 if not group:
334 print("Light has no group. Omitting...")
335 continue
336
337 blending_src = light.get('src')
338 if not blending_src:
339 blending_src = _LIGHT_DEFAULT_BLENDING_SRC
340 blending_dst = light.get('dst')
341 if not blending_dst:
342 blending_dst = _LIGHT_DEFAULT_BLENDING_DST
343
344 _x = light.get('x')
345 if not _x: _x = 0
346 _y = light.get('y')
347 if not _y: _y = 0
348 _z = light.get('y')
349 if not _z: _z = 0
350
351 node = {}
352 node['blending_src'] = int(blending_src)
353 node['blending_dst'] = int(blending_dst)
354 node['layer'] = layer.getId()
355 node['position'] = int(_x), int(_y), int(_z)
356
357
358 instance_id = light.get('instance')
359 node['instance'] = None
360 if instance_id and layer.getInstance(instance_id):
361 node['instance'] = instance_id
362
363 type = light.get('type')
364 if type:
365 s_ref = light.get('s_ref')
366 if not s_ref: s_ref = -1
367 node['s_ref'] = int(s_ref)
368 a_ref = light.get('a_ref')
369 if not a_ref: a_ref = 0.0
370 node['a_ref'] = float(a_ref)
371
372 if type == 'image':
373 image = light.get('image')
374 if not image:
375 print("Light has no image. Omitting...")
376 continue
377 node['type'] = 'image'
378 image = reverse_root_subfile(self.source, image)
379 img = self.image_manager.create(image)
380 node['image'] = img
381 elif type == 'animation':
382 animation = light.get('animation')
383 if not animation:
384 print("Light has no animation. Omitting...")
385 continue
386 node['type'] = 'animation'
387 animation = reverse_root_subfile(self.source, animation)
388 anim = loadXMLAnimation(self.engine, animation)
389 node['animation'] = anim
390 elif type == 'simple':
391 node['type'] = type
392 radius = light.get('radius')
393 if not radius: radius = _LIGHT_DEFAULT_RADIUS
394 node['radius'] = float(radius)
395
396 subdivisions = light.get('subdivisions')
397 if not subdivisions:
398 subdivisions = _LIGHT_DEFAULT_SUBDIVISIONS
399 node['subdivisions'] = int(subdivisions)
400
401 intensity = light.get('intensity')
402 if not intensity:
403 intensity = _LIGHT_DEFAULT_INTENSITY
404 node['intensity'] = int(intensity)
405
406 xstretch = light.get('xstretch')
407 if not xstretch: xstretch = 1.0
408 ystretch = light.get('ystretch')
409 if not ystretch: ystretch = 1.0
410 node['stretch'] = float(xstretch), float(ystretch)
411
412 color = light.get('color')
413 if not color: color = '%d,%d,%d' % (255, 255, 255)
414 node['color'] = ([int(c) for c in color.split(',')])
415
416 else:
417 continue
418
419 cam_id = light.get('camera_id')
420 if not cam_id: cam_id = _LIGHT_DEFAULT_CAM_ID
421
422 if not cam_id in self.light_data:
423 self.light_data[cam_id] = {}
424 if group not in self.light_data[cam_id]:
425 self.light_data[cam_id][group] = []
426
427 self.light_data[cam_id][group].append(node)
428
429 for camera, groups in self.light_data.items():
430 print("Lights for camera %s" % camera)
431 for group, lights in groups.items():
432 print(group, lights)
433
435 """ create sound emitter
436
437 FIXME:
438 - FIFE has a hard limit of sound emitters
439 how should we load emitters here?
440 - my first thought: collect a list of sound
441 files & data for emitter creation,
442 then let the client decide what to do with it
443
444 @type layerelt: object
445 @param layerelt: ElementTree layer branch
446 @type layer: object
447 @param layer: FIFE layer object
448 """
449
450 pass
451
453 """ create all layers and their instances
454
455 @type layerelt: object
456 @param layerelt: ElementTree layer branch
457 @type layer: object
458 @param layer: FIFE layer object
459 """
460 instelt = layerelt.find('instances')
461
462 instances = []
463 for attr in ('i', 'inst', 'instance'):
464 instances.extend(instelt.findall(attr))
465
466 for instance in instances:
467 _id = instance.get('id')
468 if not _id:
469 _id = ''
470
471 objectID = ''
472 for attr in ('o', 'object', 'obj'):
473 objectID = instance.get(attr)
474 if objectID: break
475 if not objectID: self._err('<instance> %s does not specify an object attribute.' % str(objectID))
476 objectID = str(objectID)
477
478 nspace = ''
479 for attr in ('namespace', 'ns'):
480 nspace = instance.get(attr)
481 if nspace: break
482
483 if not nspace and self.nspace:
484 nspace = self.nspace
485 if not nspace and not self.nspace: self._err('<instance> %s does not specify an object namespace, and no default is available.' % str(objectID))
486 nspace = str(nspace)
487 self.nspace = nspace
488
489
490 object = self.model.getObject(objectID, nspace)
491 if not object:
492 print("Object with id=%s, ns=%s could not be found. Omitting..." % (objectID, nspace))
493 continue
494
495 x = instance.get('x')
496 if x: self.x = x = float(x)
497 else: x = self.x
498
499 y = instance.get('y')
500 if y: self.y = y = float(y)
501 else: y = self.y
502
503 z = instance.get('z')
504 if z: z = float(z)
505 else: z = 0.0
506
507 inst = layer.createInstance(object, fife.ExactModelCoordinate(x,y,z), _id)
508
509 rotation = 0
510 for attr in ('r', 'rotation'):
511 rotation = instance.get(attr)
512 if rotation: break
513 if not rotation:
514 angles = object.get2dGfxVisual().getStaticImageAngles()
515 if angles:
516 rotation = angles[0]
517 else:
518 rotation = 0
519 else:
520 rotation = int(rotation)
521 inst.setRotation(rotation)
522
523 over_block = instance.get('override_blocking')
524 if over_block is not None:
525 inst.setOverrideBlocking(bool(over_block))
526 blocking = instance.get('blocking')
527 if blocking is not None:
528 inst.setBlocking(bool(int(blocking)))
529
530 fife.InstanceVisual.create(inst)
531
532 stackpos = instance.get('stackpos')
533 if stackpos:
534 inst.get2dGfxVisual().setStackPosition(int(stackpos))
535
536 if (object.getAction('default')):
537 target = fife.Location(layer)
538 inst.actRepeat('default', target)
539
541 """ create all cameras and activate them
542
543 FIXME:
544 - should the cameras really be enabled here?
545 IMO that's part of the setup within a client
546 (we just _load_ things here)
547
548 @type mapelt: object
549 @param mapelt: ElementTree root
550 @type map: object
551 @param map: FIFE map object
552 """
553 if self.callback:
554 tmplist = mapelt.findall('camera')
555 i = float(0)
556
557 for camera in mapelt.findall('camera'):
558 _id = camera.get('id')
559 zoom = camera.get('zoom')
560 tilt = camera.get('tilt')
561 rotation = camera.get('rotation')
562 ref_cell_width = camera.get('ref_cell_width')
563 ref_cell_height = camera.get('ref_cell_height')
564 viewport = camera.get('viewport')
565 light_color = camera.get('light_color')
566
567 if not zoom: zoom = 1
568 if not tilt: tilt = 0
569 if not rotation: rotation = 0
570
571 if not _id: self._err('Camera declared without an id.')
572 if not (ref_cell_width and ref_cell_height): self._err(''.join(['Camera ', str(_id), ' declared without reference cell dimensions.']))
573
574 try:
575 if viewport:
576 cam = map.addCamera(str(_id), fife.Rect(*[int(c) for c in viewport.split(',')]))
577
578 else:
579 screen = self.engine.getRenderBackend()
580 cam = map.addCamera(str(_id), fife.Rect(0,0,screen.getScreenWidth(),screen.getScreenHeight()))
581
582 renderer = fife.InstanceRenderer.getInstance(cam)
583 renderer.activateAllLayers(map)
584
585 except fife.Exception as e:
586 print(e.getMessage())
587
588 if light_color: cam.setLightingColor(*[float(c) for c in light_color.split(',')])
589 cam.setCellImageDimensions(int(ref_cell_width), int(ref_cell_height))
590 cam.setRotation(float(rotation))
591 cam.setTilt(float(tilt))
592 cam.setZoom(float(zoom))
593
594 renderer = fife.InstanceRenderer.getInstance(cam)
595 renderer.activateAllLayers(map)
596
597 if self.callback:
598 i += 1
599 self.callback(self.msg['camera'] % str(_id), float( i / len(tmplist) * 0.25 + 0.75 ) )
600
602 """ loop through all preloaded lights and create them
603 according to their data
604
605 @type map: object
606 @param map: FIFE map object
607 """
608 cameras = [i.getId() for i in map.getCameras()]
609 renderers = {}
610 default_cam = map.getCameras()[0].getId()
611
612 def add_simple_light(group, renderer, node, data):
613 """ add a node as simple light to the renderer
614
615 @type group: string
616 @param group: name of the light group
617 @type renderer: object
618 @param renderer: fife.LightRenderer instance
619 @type node: object
620 @param node: fife.RendererNode instance
621 @type data: dict
622 @param data: all data for the light type creation
623 """
624 if not node: return
625 if not group: return
626 renderer.addSimpleLight(
627 group,
628 node,
629 data['intensity'],
630 data['radius'],
631 data['subdivisions'],
632 data['stretch'][0],
633 data['stretch'][1],
634 data['color'][0],
635 data['color'][1],
636 data['color'][2],
637 data['blending_src'],
638 data['blending_dst'],
639 )
640 if data['s_ref'] != -1:
641 add_stencil_test(group, renderer, data)
642 def add_animated_lightmap(group, renderer, node, data):
643 """ add a node as animated lightmap to the renderer
644
645 @type group: string
646 @param group: name of the light group
647 @type renderer: object
648 @param renderer: fife.LightRenderer instance
649 @type node: object
650 @param node: fife.RendererNode instance
651 @type data: dict
652 @param data: all data for the light type creation
653 """
654 if not node: return
655 if not group: return
656 renderer.addAnimation(
657 group,
658 node,
659 data['animation'],
660 data['blending_src'],
661 data['blending_dst'],
662 )
663 if data['s_ref'] != -1:
664 add_stencil_test(group, renderer, data)
665 def add_lightmap(group, renderer, node, data):
666 """ add a node as lightmap to the renderer
667
668 @type group: string
669 @param group: name of the light group
670 @type renderer: object
671 @param renderer: fife.LightRenderer instance
672 @type node: object
673 @param node: fife.RendererNode instance
674 @type data: dict
675 @param data: all data for the light type creation
676 """
677 if not node: return
678 if not group: return
679 renderer.addImage(
680 group,
681 node,
682 data['image'],
683 data['blending_src'],
684 data['blending_dst'],
685 )
686 if data['s_ref'] != -1:
687 add_stencil_test(group, renderer, data)
688 def add_stencil_test(group, renderer, data):
689 """ add a stencil test to a group
690
691 @type group: string
692 @param group: name of the light group
693 @type renderer: object
694 @param renderer: fife.LightRenderer instance
695 @type data: dict
696 @param data: all data for the light type creation
697 """
698 if not group: return
699 renderer.addStencilTest(
700 group,
701 data['s_ref'],
702 data['a_ref'],
703 )
704
705 def create_node(instance=None, point=None, layer=None):
706 """ creates a node of one of these types:
707
708 - attached to an instance
709 - attached to an instance with offset
710 - attached to a point
711
712 FIXME:
713 - add location node
714
715 @type: instance: object
716 @param instance: fife instance object
717 @type point: tuple
718 @param point: x,y,z tuple
719 @type layer: object
720 @param layer: fife layer object
721 """
722 node = None
723 if not layer: return node
724
725
726 if point and not instance:
727 point = fife.Point(point[0], point[1])
728 node = fife.RendererNode(point);
729
730 if instance and point:
731 node = fife.RendererNode(instance, layer, fife.Point(point[0], point[1]))
732
733 if instance and not point:
734 node = fife.RendererNode(instance, layer)
735
736 if node:
737 node.thisown = 0
738
739 return node
740
741 def dump_data():
742 """ dump all loaded data """
743 for camera, groups in self.light_data.items():
744 print("Lights for camera %s" % camera)
745 for group, lights in groups.items():
746 print(group, lights)
747
748
749 for _id in cameras:
750 camera = map.getCamera(_id)
751 renderers[_id] = fife.LightRenderer.getInstance(camera)
752
753
754 for camera, groups in self.light_data.items():
755 for group, lights in groups.items():
756 for light in lights:
757 instance = None
758 layer = map.getLayer(light['layer'])
759 if light['instance']:
760 instance = layer.getInstance(light['instance'])
761 position = light['position']
762 node = create_node(instance, position, layer)
763
764
765 if not node: continue
766
767 if camera == 'default':
768 renderer = renderers[default_cam]
769 else:
770 renderer = renderers[camera]
771
772 if light['type'] == 'simple':
773 add_simple_light(group, renderer, node, light)
774 elif light['type'] == 'image':
775 add_lightmap(group, renderer, node, light)
776 elif light['type'] == 'animation':
777 add_animated_lightmap(group, renderer, node, light)
778
779
780