basic npc
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index cb3b9907ea337adc4e3ff9ee6c1c5901d3e2ae90..7c8c69f6ab104cfdcb821d902e9e199e62066dc0 100644 (file)
@@ -1,9 +1,10 @@
-import bpy, math, gpu, os
+import bpy, blf, math, gpu, os
 import cProfile
 from ctypes import *
 from mathutils import *
 from gpu_extras.batch import batch_for_shader
 from bpy_extras import mesh_utils
+from bpy_extras import view3d_utils
 
 bl_info = {
    "name":"Skaterift .mdl exporter",
@@ -33,9 +34,25 @@ sr_entity_list = [
    ('ent_skateshop',    'Skate Shop',     '', 12 ),
    ('ent_camera',       'Camera',         '', 13 ),
    ('ent_swspreview', 'Workshop Preview', '', 14 ),
-   ('ent_menuitem',     'Menu Item',      '', 15 )
+   ('ent_menuitem',     'Menu Item',      '', 15 ),
+   ('ent_worldinfo',    'World Info',     '', 16 ),
+   ('ent_ccmd',         'CCmd',           '', 17 ),
+   ('ent_objective',    'Objective',      '', 18 ),
+   ('ent_challenge',    'Challenge',      '', 19 ),
+   ('ent_relay',        'Relay',          '', 20 ),
+   ('ent_miniworld',    'Mini World',     '', 22 ),
+   ('ent_prop',         'Prop',           '', 23 ),
+   ('ent_list',         'Entity List',    '', 24 ),
+   ('ent_region',       'Region',         '', 25 ),
+   ('ent_glider',       'Glider',         '', 26 ),
+   ('ent_npc',          'npc',            '', 27 )
 ]
 
+MDL_VERSION_NR = 105
+SR_TRIGGERABLE = [ 'ent_audio', 'ent_ccmd', 'ent_gate', 'ent_challenge', \
+                   'ent_relay', 'ent_skateshop', 'ent_objective', 'ent_route',\
+                   'ent_miniworld', 'ent_region', 'ent_glider', 'ent_list' ]
+
 def get_entity_enum_id( alias ):
 #{
    for et in sr_entity_list:#{
@@ -44,6 +61,8 @@ def get_entity_enum_id( alias ):
       #}
    #}
 
+   if alias == 'ent_cubemap': return 21
+
    return 0
 #}
 
@@ -183,8 +202,9 @@ class version_refcount_union(Union):
 
 class ent_gate(Structure):
 #{
-   _fields_ = [("type",c_uint32),
+   _fields_ = [("flags",c_uint32),
                ("target", c_uint32),
+               ("key",c_uint32),
                ("dimensions", c_float*3),
                ("co", (c_float*3)*2),
                ("q", (c_float*4)*2),
@@ -193,7 +213,11 @@ class ent_gate(Structure):
                ("_anonymous_union",version_refcount_union),
                ("timing_time",c_double),
                ("routes",c_uint16*4),
-               ("route_count",c_uint8)]
+               ("route_count",c_uint8),
+               ("submesh_start",c_uint32), # v102+ 
+               ("submesh_count",c_uint32), # v102+ (can be 0)
+               ]
+   sr_functions = { 0: 'unlock' }
 #}
 
 class ent_route_node(Structure):
@@ -222,12 +246,26 @@ class union_file_audio_clip(Union):
                ("reserved",vg_audio_clip)]
 #}
 
+# NOTE: not really an entity. no reason for ent_ -- makes more sense as file_,
+#       but then again, too late to change because compat.
 class ent_audio_clip(Structure):
 #{
    _fields_ = [("_anon",union_file_audio_clip),
                ("probability",c_float)]
 #}
 
+class ent_list(Structure):
+#{
+   _fields_ = [("entity_ref_start",c_uint32),
+               ("entity_ref_count",c_uint32)]
+#}
+
+# used in ent_list
+class file_entity_ref(Structure):
+#{
+   _fields_ = [("index",c_uint32)]
+#}
+
 class ent_checkpoint(Structure):
 #{
    _fields_ = [("gate_index",c_uint16),
@@ -246,7 +284,29 @@ class ent_route(Structure):
                ("factive",c_float),
                ("board_transform",(c_float*3)*4),
                ("sm",mdl_submesh),
-               ("latest_pass",c_double)]
+               ("latest_pass",c_double),
+               ("id_camera",c_uint32), # v103+ 
+               ]
+
+   sr_functions = { 0: 'view' }
+#}
+
+class ent_list(Structure):#{
+   _fields_ = [("start",c_uint16),("count",c_uint16)]
+#}
+
+class ent_glider(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("flags",c_uint32),
+               ("cooldown",c_float)]
+   sr_functions = { 0: 'unlock',
+                    1: 'equip' }
+#}
+
+class ent_npc(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("id",c_uint32),
+               ("context",c_uint32)]
 #}
 
 class ent_water(Structure):
@@ -260,7 +320,7 @@ class ent_water(Structure):
 class volume_trigger(Structure):
 #{
    _fields_ = [("event",c_uint32),
-               ("blank",c_uint32)]
+               ("event_leave",c_uint32)]
 #}
 
 class volume_particles(Structure):
@@ -340,13 +400,42 @@ class ent_traffic(Structure):
                ("index",c_uint32)]
 #}
 
-class ent_skateshop(Structure):
+# Skateshop
+# ---------------------------------------------------------------
+class ent_skateshop_characters(Structure):
 #{
-   _fields_ = [("transform",mdl_transform),
-               ("id_display",c_uint32),
+   _fields_ = [("id_display",c_uint32),
+               ("id_info",c_uint32)]
+#}
+class ent_skateshop_boards(Structure):
+#{
+   _fields_ = [("id_display",c_uint32),
                ("id_info",c_uint32),
-               ("id_rack",c_uint32),
-               ("id_camera",c_uint32)]
+               ("id_rack",c_uint32)]
+#}  
+class ent_skateshop_worlds(Structure):
+#{
+   _fields_ = [("id_display",c_uint32),
+               ("id_info",c_uint32)]
+#}
+class ent_skateshop_server(Structure):
+#{
+   _fields_ = [("id_lever",c_uint32)]
+#}
+class ent_skateshop_anon_union(Union):
+#{
+   _fields_ = [("boards",ent_skateshop_boards),
+               ("character",ent_skateshop_characters),
+               ("worlds",ent_skateshop_worlds),
+               ("server",ent_skateshop_server)]
+#}
+class ent_skateshop(Structure):
+#{
+   _fields_ = [("transform",mdl_transform), ("type",c_uint32),
+               ("id_camera",c_uint32), 
+               ("_anonymous_union",ent_skateshop_anon_union)]
+
+   sr_functions = { 0: 'trigger' }
 #}
 
 class ent_swspreview(Structure):
@@ -356,6 +445,12 @@ class ent_swspreview(Structure):
                ("id_display1",c_uint32)]
 #}
 
+# Menu
+# -----------------------------------------------------------------
+class ent_menuitem_visual(Structure):
+#{
+   _fields_ = [("pstr_name",c_uint32)]
+#}
 class ent_menuitem_slider(Structure):
 #{
    _fields_ = [("id_min",c_uint32),
@@ -363,40 +458,46 @@ class ent_menuitem_slider(Structure):
                ("id_handle",c_uint32),
                ("pstr_data",c_uint32)]
 #}
-
 class ent_menuitem_button(Structure):
 #{
-   _fields_ = [("pstr",c_uint32)]
+   _fields_ = [("pstr",c_uint32),
+               ("stack_behaviour",c_uint32)]
 #}
-
 class ent_menuitem_checkmark(Structure):
 #{
    _fields_ = [("id_check",c_uint32),
-               ("pstr_data",c_uint32)]
+               ("pstr_data",c_uint32),
+               ("offset",c_float*3)]
 #}
-
 class ent_menuitem_page(Structure):
 #{
    _fields_ = [("pstr_name",c_uint32),
                ("id_entrypoint",c_uint32),
                ("id_viewpoint",c_uint32)]
 #}
-
+class ent_menuitem_binding(Structure):
+#{
+   _fields_ = [("pstr_bind",c_uint32),
+               ("font_variant",c_uint32)]
+#}
 class ent_menuitem_anon_union(Union):
 #{
    _fields_ = [("slider",ent_menuitem_slider),
                ("button",ent_menuitem_button),
                ("checkmark",ent_menuitem_checkmark),
-               ("page",ent_menuitem_page)]
+               ("page",ent_menuitem_page),
+               ("visual",ent_menuitem_visual),
+               ("binding",ent_menuitem_binding)]
 #}
-
 class ent_menuitem(Structure):
 #{
    _fields_ = [("type",c_uint32), ("groups",c_uint32),
-               ("id_links",c_uint32*4),
+               ("id_links",c_uint32*4), 
+               ("factive",c_float), ("fvisible",c_float),
                #-- TODO: Refactor this into a simple mesh structure
                ("transform",mdl_transform),
                ("submesh_start",c_uint32),("submesh_count",c_uint32),
+               ("_u64",c_uint64),
                #-- end
                ("_anonymous_union", ent_menuitem_anon_union)]
 #}
@@ -407,11 +508,104 @@ class ent_camera(Structure):
                ("fov",c_float)]
 #}
 
+class ent_worldinfo(Structure):
+#{
+   _fields_ = [("pstr_name",c_uint32),
+               ("pstr_author",c_uint32),    # unused
+               ("pstr_desc",c_uint32),      # unused
+               ("timezone",c_float),
+               ("pstr_skybox",c_uint32),
+               ("flags",c_uint32)]
+#}
+
+class ent_ccmd(Structure):
+#{
+   _fields_ = [("pstr_command",c_uint32)]
+#}
+
+class ent_objective(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("submesh_start",c_uint32), ("submesh_count",c_uint32),
+               ("flags",c_uint32),
+               ("id_next",c_uint32),
+               ("filter",c_uint32),("filter2",c_uint32),
+               ("id_win",c_uint32),
+               ("win_event",c_uint32),
+               ("time_limit",c_float)]
+
+   sr_functions = { 0: 'trigger',
+                    2: 'show',
+                    3: 'hide' }
+#}
+
+class ent_challenge(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("pstr_alias",c_uint32),
+               ("flags",c_uint32),
+               ("target",c_uint32),
+               ("target_event",c_uint32),
+               ("reset",c_uint32),
+               ("reset_event",c_uint32),
+               ("first",c_uint32),
+               ("camera",c_uint32),
+               ("status",c_uint32)] #runtime
+   sr_functions = { 0: 'unlock',
+                    1: 'view/reset' }
+#}
+
+class ent_region(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("submesh_start",c_uint32), ("submesh_count",c_uint32),
+               ("pstr_title",c_uint32),
+               ("flags",c_uint32),
+               ("zone_volume",c_uint32),
+               #105+
+               ("target0",c_uint32*2)]
+   sr_functions = { 0: 'enter', 1: 'leave' }
+#}
+
+class ent_relay(Structure):#{
+   _fields_ = [("targets",(c_uint32*2)*4),
+               ("targets_events",c_uint32*4)]
+   sr_functions = { 0: 'trigger' }
+#}
+
+class ent_cubemap(Structure):#{
+   _fields_ = [("co",c_float*3),
+               ("resolution",c_uint32), #placeholder
+               ("live",c_uint32),       #placeholder
+               ("texture_id",c_uint32), #engine
+               ("framebuffer_id",c_uint32),#engine
+               ("renderbuffer_id",c_uint32),#engine
+               ("placeholder",c_uint32*2)]
+#}
+
+print( sizeof(ent_cubemap) )
+
+class ent_miniworld(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("pstr_world",c_uint32),
+               ("camera",c_uint32),
+               ("proxy",c_uint32)]
+
+   sr_functions = { 0: 'zone', 1: 'leave' }
+#}
+
+class ent_prop(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("submesh_start",c_uint32),
+               ("submesh_count",c_uint32),
+               ("flags",c_uint32),
+               ("pstr_alias",c_uint32)]
+#}
+
 def obj_ent_type( obj ):
 #{
    if obj.type == 'ARMATURE': return 'mdl_armature'
    elif obj.type == 'LIGHT': return 'ent_light'
    elif obj.type == 'CAMERA': return 'ent_camera'
+   elif obj.type == 'LIGHT_PROBE' and obj.data.type == 'CUBEMAP':
+      return 'ent_cubemap'
    else: return obj.SR_data.ent_type
 #}
 
@@ -430,6 +624,31 @@ def sr_filter_ent_type( obj, ent_types ):
    return False
 #}
 
+def v4_dot( a, b ):#{
+   return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]
+#}
+
+def q_identity( q ):#{
+   q[0] = 0.0
+   q[1] = 0.0
+   q[2] = 0.0
+   q[3] = 1.0
+#}
+
+def q_normalize( q ):#{
+   l2 = v4_dot(q,q)
+   if( l2 < 0.00001 ):#{
+      q_identity( q )
+   #}
+   else:#{
+      s = 1.0/math.sqrt(l2)
+      q[0] *= s
+      q[1] *= s
+      q[2] *= s
+      q[3] *= s
+   #}
+#}
+
 def compile_obj_transform( obj, transform ):
 #{
    co = obj.matrix_world @ Vector((0,0,0))
@@ -437,6 +656,7 @@ def compile_obj_transform( obj, transform ):
    # This was changed from matrix_local on 09.05.23
    q = obj.matrix_world.to_quaternion() 
    s = obj.scale
+   q_normalize( q )
    
    # Setup transform
    #
@@ -549,14 +769,17 @@ def material_info(mat):
       if node == None:#{
          _graph_read.extracted = []
 
+         done = False
          for node_idname in node_def:#{
             for n in mat.node_tree.nodes:#{
                if n.name == node_idname:#{
                   node_def = node_def[node_idname]
                   node = n
+                  done = True
                   break
                #}
             #}
+            if done: break
          #}
       #}
 
@@ -650,8 +873,7 @@ def sr_compile_texture( img ):
    return texture_index
 #}
 
-def sr_compile_material( mat ):
-#{
+def sr_compile_material( mat ):#{
    if mat == None: 
       return 0
    if mat.name in sr_compile.material_cache: 
@@ -671,17 +893,21 @@ def sr_compile_material( mat ):
          if mat.SR_data.skate_surface: flags |= 0x1
          if mat.SR_data.grow_grass: flags |= 0x4
          if mat.SR_data.grind_surface: flags |= 0x8
+         if mat.SR_data.preview_visibile: flags |= 0x40
       #}
       if mat.SR_data.shader == 'invisible': flags |= 0x10
       if mat.SR_data.shader == 'boundary': flags |= (0x10|0x20)
+      if mat.SR_data.shader == 'walking': flags |= (0x10|0x80)
    #}
 
    m.flags = flags
 
    m.surface_prop = int(mat.SR_data.surface_prop)
+   inf = material_info( mat )
 
    if mat.SR_data.shader == 'standard': m.shader = 0
    if mat.SR_data.shader == 'standard_cutout': m.shader = 1
+   if mat.SR_data.shader == 'foliage': m.shader = 10
    if mat.SR_data.shader == 'terrain_blend':#{
       m.shader = 2
 
@@ -721,18 +947,36 @@ def sr_compile_material( mat ):
    if mat.SR_data.shader == 'boundary':#{
       m.shader = 6
    #}
-   
-   inf = material_info( mat )
 
-   if mat.SR_data.shader == 'standard' or \
-      mat.SR_data.shader == 'standard_cutout' or \
-      mat.SR_data.shader == 'terrain_blend' or \
-      mat.SR_data.shader == 'vertex_blend':
-   #{
+   if mat.SR_data.shader == 'fxglow':#{
+      m.shader = 7
+   #}
+
+   if mat.SR_data.shader == 'cubemap':#{
+      m.shader = 8
+      m.tex_none0 = sr_entity_id( mat.SR_data.cubemap )
+
+      m.colour[0]  = pow( mat.SR_data.tint[0], 1.0/2.2 )
+      m.colour[1]  = pow( mat.SR_data.tint[1], 1.0/2.2 )
+      m.colour[2]  = pow( mat.SR_data.tint[2], 1.0/2.2 )
+      m.colour[3]  = pow( mat.SR_data.tint[3], 1.0/2.2 )
+   #}
+
+   if mat.SR_data.shader == 'walking':#{
+      m.shader = 9
+   #}
+   
+   if mat.SR_data.shader in ['standard', 'standard_cutout', 'terrain_blend', \
+                             'vertex_blend', 'fxglow', 'cubemap', \
+                             'foliage' ]: #{
       if 'tex_diffuse' in inf: 
          m.tex_diffuse = sr_compile_texture(inf['tex_diffuse'])
    #}
 
+   if mat.SR_data.tex_diffuse_rt >= 0:#{
+      m.tex_diffuse = 0x80000000 | mat.SR_data.tex_diffuse_rt
+   #}
+
    sr_compile.material_data.extend( bytearray(m) )
    return index
 #}
@@ -750,8 +994,9 @@ def sr_armature_bones( armature ):
          yield from _recurse_bone( b )
 #}
 
-def sr_entity_id( obj ):
-#{
+def sr_entity_id( obj ):#{
+   if not obj: return 0
+   
    tipo = get_entity_enum_id( obj_ent_type(obj) )
    index = sr_compile.entity_ids[ obj.name ]
 
@@ -1149,7 +1394,7 @@ def sr_compile_fonts( collection ):
 
 def sr_compile_menus( collection ):
 #{
-   print( "[SR] Compiling menus" )
+   print( "[SR1] Compiling menus" )
    groups = []
 
    for obj in collection.all_objects:#{
@@ -1174,20 +1419,30 @@ def sr_compile_menus( collection ):
                sr_compile_mesh_internal( obj )
       #}
 
-      if item.type == 1 or item.type == 2:#{
+      if item.type == 1 or item.type == 2 or item.type == 7:#{
          item_button = item._anonymous_union.button
          item_button.pstr = sr_compile_string( obj_data.string )
+         item_button.stack_behaviour = int( obj_data.stack_behaviour )
+      #}
+      elif item.type == 0:#{
+         item_visual = item._anonymous_union.visual
+         item_visual.pstr_name = sr_compile_string( obj_data.string )
       #}
       elif item.type == 3:#{
          item_checkmark = item._anonymous_union.checkmark
          item_checkmark.pstr_data = sr_compile_string( obj_data.string )
          item_checkmark.id_check = sr_entity_id( obj_data.checkmark )
+         delta = obj_data.checkmark.location - obj.location
+         item_checkmark.offset[0] =  delta[0]
+         item_checkmark.offset[1] =  delta[2]
+         item_checkmark.offset[2] = -delta[1]
       #}
       elif item.type == 4:#{
          item_slider = item._anonymous_union.slider
          item_slider.id_min = sr_entity_id( obj_data.slider_minloc )
          item_slider.id_max = sr_entity_id( obj_data.slider_maxloc )
          item_slider.id_handle = sr_entity_id( obj_data.slider_handle )
+         item_slider.pstr_data = sr_compile_string( obj_data.string )
       #}
       elif item.type == 5:#{
          item_page = item._anonymous_union.page
@@ -1195,6 +1450,11 @@ def sr_compile_menus( collection ):
          item_page.id_entrypoint = sr_entity_id( obj_data.newloc )
          item_page.id_viewpoint = sr_entity_id( obj_data.camera )
       #}
+      elif item.type == 6:#{
+         item_binding = item._anonymous_union.binding
+         item_binding.pstr_bind = sr_compile_string( obj_data.string )
+         item_binding.font_variant = obj_data.font_variant
+      #}
 
       if obj_data.link0:
          item.id_links[0] = sr_entity_id( obj_data.link0 )
@@ -1340,6 +1600,7 @@ def sr_compile_armature( obj ):
                      lc_m = smtx.inverted() @ lc_m
                   #}
                   rq = lc_m.to_quaternion()
+                  q_normalize( rq )
 
                   kf = mdl_transform()
                   kf.co[0] =  loc[0]
@@ -1480,17 +1741,27 @@ def sr_compile( collection ):
          # entity ignore mesh list
          #
          if ent_type == 'ent_traffic': continue
+         if ent_type == 'ent_prop': continue
          if ent_type == 'ent_font': continue
          if ent_type == 'ent_font_variant': continue
          if ent_type == 'ent_menuitem': continue
+         if ent_type == 'ent_objective': continue
+         if ent_type == 'ent_region': continue
+
+         #TODO: This is messy.
+         if ent_type == 'ent_gate':#{
+            obj_data = obj.SR_data.ent_gate[0]
+            if obj_data.custom: continue
+         #}
          #--------------------------
 
-         print( F'[SR] {i: 3}/{mesh_count} {obj.name:<40}', end='\r' )
+         print( F'[SR] {i: 3}/{mesh_count} {obj.name:<40}' )
          sr_compile_mesh( obj )
       #}
    #}
 
    audio_clip_count = 0
+   entity_file_ref_count = 0
 
    for ent_type, arr in sr_compile.entities.items():#{
       print(F"[SR] Compiling {len(arr)} {ent_type}{'s' if len(arr)>1 else ''}")
@@ -1530,17 +1801,28 @@ def sr_compile( collection ):
             obj_data = obj.SR_data.ent_gate[0]
             mesh_data = obj.data.SR_data.ent_gate[0]
 
+            flags = 0x0000
+
             if obj_data.tipo == 'default':#{
                if obj_data.target:#{
                   gate.target = sr_compile.entity_ids[obj_data.target.name]
-                  gate.type = 1
+                  flags |= 0x0001
                #}
             #}
             elif obj_data.tipo == 'nonlocal':#{
-               gate.target = sr_compile_string(obj_data.key)
-               gate.type = 2
+               gate.target = 0
+               gate.key = sr_compile_string(obj_data.key)
+               flags |= 0x0002
+            #}
+
+            if obj_data.flip:   flags |= 0x0004
+            if obj_data.custom:#{
+               flags |= 0x0008
+               gate.submesh_start, gate.submesh_count, _ = \
+                     sr_compile_mesh_internal( obj )
             #}
-            else: gate.type = 0
+            if obj_data.locked: flags |= 0x0010
+            gate.flags = flags
             
             gate.dimensions[0] = mesh_data.dimensions[0]
             gate.dimensions[1] = mesh_data.dimensions[1]
@@ -1637,6 +1919,11 @@ def sr_compile( collection ):
 
             if obj_data.target:#{
                volume.target = sr_entity_id( obj_data.target )
+               volume._anon.trigger.event = obj_data.target_event
+
+               ev = 0xffffffff if obj_data.target_event_leave < 0 else \
+                     obj_data.target_event_leave
+               volume._anon.trigger.event_leave = ev
             #}
 
             sr_ent_push(volume)
@@ -1650,9 +1937,27 @@ def sr_compile( collection ):
          elif ent_type == 'ent_skateshop':#{
             skateshop = ent_skateshop()
             obj_data = obj.SR_data.ent_skateshop[0]
-            skateshop.id_display = sr_entity_id( obj_data.mark_display )
-            skateshop.id_info = sr_entity_id( obj_data.mark_info )
-            skateshop.id_rack = sr_entity_id( obj_data.mark_rack )
+            skateshop.type = int(obj_data.tipo)
+            if skateshop.type == 0:#{
+               boardshop = skateshop._anonymous_union.boards
+               boardshop.id_display = sr_entity_id( obj_data.mark_display )
+               boardshop.id_info = sr_entity_id( obj_data.mark_info )
+               boardshop.id_rack = sr_entity_id( obj_data.mark_rack )
+            #}
+            elif skateshop.type == 1:#{
+               charshop = skateshop._anonymous_union.character
+               charshop.id_display = sr_entity_id( obj_data.mark_display )
+               charshop.id_info = sr_entity_id( obj_data.mark_info )
+            #}
+            elif skateshop.type == 2:#{
+               worldshop = skateshop._anonymous_union.worlds
+               worldshop.id_display = sr_entity_id( obj_data.mark_display )
+               worldshop.id_info = sr_entity_id( obj_data.mark_info )
+            #}
+            elif skateshop.type == 3:#{
+               server = skateshop._anonymous_union.server
+               server.id_lever = sr_entity_id( obj_data.mark_display )
+            #}
             skateshop.id_camera = sr_entity_id( obj_data.cam )
             compile_obj_transform( obj, skateshop.transform )
             sr_ent_push(skateshop)
@@ -1665,6 +1970,148 @@ def sr_compile( collection ):
             workshop_preview.id_camera = sr_entity_id( obj_data.cam )
             sr_ent_push( workshop_preview )
          #}
+         elif ent_type == 'ent_worldinfo':#{
+            worldinfo = ent_worldinfo()
+            obj_data = obj.SR_data.ent_worldinfo[0]
+            worldinfo.pstr_name = sr_compile_string( obj_data.name )
+            worldinfo.pstr_author = sr_compile_string( obj_data.author )
+            worldinfo.pstr_desc = sr_compile_string( obj_data.desc )
+
+            flags = 0x00
+
+            if obj_data.fix_time:#{
+               worldinfo.timezone = obj_data.fixed_time
+               flags |= 0x1
+            #}
+            else:
+               worldinfo.timezone = obj_data.timezone
+
+            worldinfo.flags = flags
+            worldinfo.pstr_skybox = sr_compile_string( obj_data.skybox )
+            sr_ent_push( worldinfo )
+         #}
+         elif ent_type == 'ent_ccmd':#{
+            ccmd = ent_ccmd()
+            obj_data = obj.SR_data.ent_ccmd[0]
+            ccmd.pstr_command = sr_compile_string( obj_data.command )
+            sr_ent_push( ccmd )
+         #}
+         elif ent_type == 'ent_objective':#{
+            objective = ent_objective()
+            obj_data = obj.SR_data.ent_objective[0]
+            objective.id_next = sr_entity_id( obj_data.proxima )
+            objective.id_win = sr_entity_id( obj_data.target )
+            objective.win_event = obj_data.target_event
+            objective.filter = int(obj_data.filtrar)
+            objective.filter2 = 0
+            objective.time_limit = obj_data.time_limit
+
+            compile_obj_transform( obj, objective.transform )
+            objective.submesh_start, objective.submesh_count, _ = \
+                  sr_compile_mesh_internal( obj )
+
+            sr_ent_push( objective )
+         #}
+         elif ent_type == 'ent_challenge':#{
+            challenge = ent_challenge()
+            obj_data = obj.SR_data.ent_challenge[0]
+            compile_obj_transform( obj, challenge.transform )
+            challenge.pstr_alias = sr_compile_string( obj_data.alias )
+            challenge.target = sr_entity_id( obj_data.target )
+            challenge.target_event = obj_data.target_event
+            challenge.reset = sr_entity_id( obj_data.reset )
+            challenge.reset_event = obj_data.reset_event
+            challenge.first = sr_entity_id( obj_data.first )
+            challenge.flags = 0x00
+            challenge.camera = sr_entity_id( obj_data.camera )
+            if obj_data.time_limit: challenge.flags |= 0x01
+            challenge.status = 0
+            sr_ent_push( challenge )
+         #}
+         elif ent_type == 'ent_region':#{
+            region = ent_region()
+            obj_data = obj.SR_data.ent_region[0]
+            compile_obj_transform( obj, region.transform )
+            region.submesh_start, region.submesh_count, _ = \
+                  sr_compile_mesh_internal( obj )
+            region.pstr_title = sr_compile_string( obj_data.title )
+            region.zone_volume = sr_entity_id( obj_data.zone_volume )
+            region.target0[0] = sr_entity_id( obj_data.target0 )
+            region.target0[1] = obj_data.target0_event
+            sr_ent_push( region )
+         #}
+         elif ent_type == 'ent_relay':#{
+            relay = ent_relay()
+            obj_data = obj.SR_data.ent_relay[0]
+            relay.targets[0][0] = sr_entity_id( obj_data.target0 )
+            relay.targets[1][0] = sr_entity_id( obj_data.target1 )
+            relay.targets[2][0] = sr_entity_id( obj_data.target2 )
+            relay.targets[3][0] = sr_entity_id( obj_data.target3 )
+            relay.targets[0][1] = obj_data.target0_event
+            relay.targets[1][1] = obj_data.target1_event
+            relay.targets[2][1] = obj_data.target2_event
+            relay.targets[3][1] = obj_data.target3_event
+            sr_ent_push( relay )
+         #}
+         # elif ent_type == 'ent_list':#{
+         #    lista = ent_list()
+         #    obj_data = obj.SR_data.ent_list[0]
+
+         #    lista.entity_ref_start = entity_file_ref_count
+         #    lista.entity_ref_count = len( obj_data.entities )
+         #    entity_file_ref_count += lista.entity_ref_count
+
+         #    for child in obj_data.entities:#{
+         #       reference_struct = file_entity_ref()
+         #       reference_struct.index = sr_entity_id( child.target )
+         #       sr_ent_push( reference_struct )
+         #    #}
+
+         #    sr_ent_push( lista )
+         # #}
+         elif ent_type == 'ent_glider':#{
+            glider = ent_glider()
+            compile_obj_transform( obj, glider.transform )
+            sr_ent_push( glider )
+         #}
+         elif ent_type == 'ent_npc':#{
+            obj_data = obj.SR_data.ent_npc[0]
+            npc = ent_npc()
+            compile_obj_transform( obj, npc.transform )
+            npc.id = obj_data.au
+            npc.context = obj_data.context
+            sr_ent_push( npc )
+         #}
+         elif ent_type == 'ent_cubemap':#{
+            cubemap = ent_cubemap()
+            co = obj.matrix_world @ Vector((0,0,0))
+            cubemap.co[0] =  co[0]
+            cubemap.co[1] =  co[2]
+            cubemap.co[2] = -co[1]
+            cubemap.resolution = 0
+            cubemap.live = 60
+            sr_ent_push( cubemap )
+         #}
+         elif ent_type == 'ent_miniworld':#{
+            miniworld = ent_miniworld()
+            obj_data = obj.SR_data.ent_miniworld[0]
+
+            compile_obj_transform( obj, miniworld.transform )
+            miniworld.pstr_world = sr_compile_string( obj_data.world )
+            miniworld.proxy = sr_entity_id( obj_data.proxy )
+            miniworld.camera = sr_entity_id( obj_data.camera )
+            sr_ent_push( miniworld )
+         #}
+         elif ent_type == 'ent_prop':#{
+            prop = ent_prop()
+            obj_data = obj.SR_data.ent_prop[0]
+            compile_obj_transform( obj, prop.transform )
+            prop.submesh_start, prop.submesh_count, _ = \
+                  sr_compile_mesh_internal( obj )
+            prop.flags = obj_data.flags
+            prop.pstr_alias = sr_compile_string( obj_data.alias )
+            sr_ent_push( prop )
+         #}
       #}
    #}
 
@@ -1716,6 +2163,7 @@ def sr_compile( collection ):
          route.pstr_name = sr_compile_string( obj_data.alias )
          route.checkpoints_start = checkpoint_count
          route.checkpoints_count = 0
+         route.id_camera = sr_entity_id( obj_data.cam )
 
          for ci in range(3):
             route.colour[ci] = obj_data.colour[ci]
@@ -1860,9 +2308,10 @@ def sr_compile( collection ):
    path = F"{folder}{collection.name}.mdl"
    print( path )
 
+   os.makedirs(os.path.dirname(path),exist_ok=True)
    fp = open( path, "wb" )
    header = mdl_header()
-   header.version = 101
+   header.version = MDL_VERSION_NR
    sr_array_title( header.arrays, \
                    'index', len(file_array_instructions), \
                    sizeof(mdl_array), header_size )
@@ -1896,7 +2345,7 @@ class SR_SCENE_SETTINGS(bpy.types.PropertyGroup):
 #{
    use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
    export_dir: bpy.props.StringProperty( name="Export Dir", subtype='DIR_PATH' )
-   gizmos: bpy.props.BoolProperty( name="Draw Gizmos", default=True )
+   gizmos: bpy.props.BoolProperty( name="Draw Gizmos", default=False )
 
    panel: bpy.props.EnumProperty(
         name='Panel',
@@ -2076,9 +2525,12 @@ class SR_INTERFACE(bpy.types.Panel):
          active_object = context.active_object
          if not active_object: return
 
-         _.layout.operator( 'skaterift.copy_entity_data', \
-               text=F'Copy entity data to {len(context.selected_objects)-1} '+\
-                    F'other objects' )
+         amount = max( 0, len(context.selected_objects)-1 )
+         
+         row = _.layout.row()
+         row.operator( 'skaterift.copy_entity_data', \
+               text=F'Copy entity data to {amount} other objects' )
+         if amount == 0: row.enabled=False
 
          box = _.layout.box()
          row = box.row()
@@ -2086,13 +2538,13 @@ class SR_INTERFACE(bpy.types.Panel):
          row.label( text=active_object.name )
          row.scale_y = 1.5
 
-         def _draw_prop_collection( data ): #{
+         def _draw_prop_collection( source, data ): #{
             nonlocal box
             row = box.row()
             row.alignment = 'CENTER'
             row.enabled = False
             row.scale_y = 1.5
-            row.label( text=F'{data[0]}' )
+            row.label( text=F'{source}' )
             
             if hasattr(type(data[0]),'sr_inspector'):#{
                type(data[0]).sr_inspector( box, data )
@@ -2112,7 +2564,9 @@ class SR_INTERFACE(bpy.types.Panel):
                                  text=F'Mirror attributes to {mb.name}' )
                #}
 
-               _draw_prop_collection( [bones.active.SR_data ] )
+               _draw_prop_collection( \
+                     F'bpy.types.Bone["{bones.active.name}"].SR_data',\
+                     [bones.active.SR_data ] )
             #}
             else: #{
                row = box.row()
@@ -2123,18 +2577,26 @@ class SR_INTERFACE(bpy.types.Panel):
             #}
          #}
          elif active_object.type == 'LIGHT': #{
-            _draw_prop_collection( [active_object.data.SR_data] )
+            _draw_prop_collection( \
+                  F'bpy.types.Light["{active_object.data.name}"].SR_data', \
+                  [active_object.data.SR_data] )
          #}
          elif active_object.type in ['EMPTY','CURVE','MESH']:#{
             box.prop( active_object.SR_data, "ent_type" )
             ent_type = active_object.SR_data.ent_type
             
             col = getattr( active_object.SR_data, ent_type, None )
-            if col != None and len(col)!=0: _draw_prop_collection( col )
+            if col != None and len(col)!=0: 
+               _draw_prop_collection( \
+         F'bpy.types.Object["{active_object.name}"].SR_data.{ent_type}[0]', \
+         col )
 
             if active_object.type == 'MESH':#{
                col = getattr( active_object.data.SR_data, ent_type, None )
-               if col != None and len(col)!=0: _draw_prop_collection( col )
+               if col != None and len(col)!=0: 
+                  _draw_prop_collection( \
+         F'bpy.types.Mesh["{active_object.data.name}"].SR_data.{ent_type}[0]', \
+         col )
             #}
          #}
       #}
@@ -2172,10 +2634,12 @@ class SR_MATERIAL_PANEL(bpy.types.Panel):
          row = box.row()
 
          if (active_mat.SR_data.shader != 'invisible') and \
-            (active_mat.SR_data.shader != 'boundary'):#{
+            (active_mat.SR_data.shader != 'boundary') and \
+            (active_mat.SR_data.shader != 'walking'):#{
             row.prop( active_mat.SR_data, "skate_surface" )
             row.prop( active_mat.SR_data, "grind_surface" )
             row.prop( active_mat.SR_data, "grow_grass" )
+            row.prop( active_mat.SR_data, "preview_visibile" )
          #}
       #}
 
@@ -2195,6 +2659,15 @@ class SR_MATERIAL_PANEL(bpy.types.Panel):
          box.prop( active_mat.SR_data, "shore_colour" )
          box.prop( active_mat.SR_data, "ocean_colour" )
       #}
+      elif active_mat.SR_data.shader == "cubemap":#{
+         box = _.layout.box()
+         box.prop( active_mat.SR_data, "cubemap" )
+         box.prop( active_mat.SR_data, "tint" )
+      #}
+
+      _.layout.label( text="" )
+      _.layout.label( text="advanced (you probably don't want to edit these)" )
+      _.layout.prop( active_mat.SR_data, "tex_diffuse_rt" )
    #}
 #}
 
@@ -2245,7 +2718,11 @@ class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
 
    key: bpy.props.StringProperty()
    tipo: bpy.props.EnumProperty(items=(('default', 'Default', ""),
-                                       ('nonlocal', 'Non-Local', ""),))
+                                       ('nonlocal', 'Non-Local', "")))
+
+   flip: bpy.props.BoolProperty( name="Flip exit", default=False )
+   custom: bpy.props.BoolProperty( name="Mesh is surface", default=False )
+   locked: bpy.props.BoolProperty( name="Start Locked", default=False )
 
    @staticmethod
    def sr_inspector( layout, data ):
@@ -2255,6 +2732,11 @@ class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
 
       if   data[0].tipo == 'default':  box.prop( data[0], 'target' )
       elif data[0].tipo == 'nonlocal': box.prop( data[0], 'key' )
+
+      flags = box.box()
+      flags.prop( data[0], 'flip' )
+      flags.prop( data[0], 'custom' )
+      flags.prop( data[0], 'locked' )
    #}
 #}
 
@@ -2270,6 +2752,17 @@ class SR_OBJECT_ENT_ROUTE_ENTRY(bpy.types.PropertyGroup):
                poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
 #}
 
+class SR_OBJECT_ENT_MINIWORLD(bpy.types.PropertyGroup):
+#{
+   world: bpy.props.StringProperty( name='world UID' )
+   proxy: bpy.props.PointerProperty( \
+               type=bpy.types.Object, name='proxy', \
+               poll=lambda self,obj: sr_filter_ent_type(obj,['ent_prop']))
+   camera: bpy.props.PointerProperty( \
+           type=bpy.types.Object, name="Camera", \
+           poll=lambda self,obj: sr_filter_ent_type(obj,['ent_camera']))
+#}
+
 class SR_UL_ROUTE_NODE_LIST(bpy.types.UIList):
 #{
    bl_idname = 'SR_UL_ROUTE_NODE_LIST'
@@ -2586,11 +3079,16 @@ class SR_OBJECT_ENT_ROUTE(bpy.types.PropertyGroup):
           name="Alias",\
           default="Untitled Course")
 
+   cam: bpy.props.PointerProperty( \
+           type=bpy.types.Object, name="Viewpoint", \
+           poll=lambda self,obj: sr_filter_ent_type(obj,['ent_camera']))
+
    @staticmethod
    def sr_inspector( layout, data ):
    #{
       layout.prop( data[0], 'alias' )
       layout.prop( data[0], 'colour' )
+      layout.prop( data[0], 'cam' )
 
       layout.label( text='Checkpoints' )
       layout.template_list('SR_UL_ROUTE_NODE_LIST', 'Checkpoints', \
@@ -2602,8 +3100,75 @@ class SR_OBJECT_ENT_ROUTE(bpy.types.PropertyGroup):
    #}
 #}
 
-class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):
+
+class SR_OT_ENT_LIST_NEW_ITEM(bpy.types.Operator):#{
+   bl_idname = "skaterift.ent_list_new_entry"
+   bl_label = "Add entity"
+   
+   def execute(self, context):#{
+      return internal_listadd_execute(self,context,'ent_list','entities')
+   #}
+#}
+
+class SR_OT_ENT_LIST_DEL_ITEM(bpy.types.Operator):#{
+   bl_idname = "skaterift.ent_list_del_entry"
+   bl_label = "Remove entity"
+
+   @classmethod 
+   def poll(cls, context):#{
+      active_object = context.active_object
+      if obj_ent_type(active_object) == 'ent_list':#{
+         return active_object.SR_data.ent_list[0].entities
+      #}
+      else: return False
+   #}
+   
+   def execute(self, context):#{
+      return internal_listdel_execute(self,context,'ent_list','entities')
+   #}
+#}
+
+class SR_OBJECT_ENT_LIST_ENTRY(bpy.types.PropertyGroup):
 #{
+   target: bpy.props.PointerProperty( \
+               type=bpy.types.Object, name='target' )
+#}
+
+class SR_UL_ENT_LIST(bpy.types.UIList):#{
+   bl_idname = 'SR_UL_ENT_LIST'
+
+   def draw_item(_,context,layout,data,item,icon,active_data,active_propname):#{
+      layout.prop( item, 'target', text='', emboss=False )
+   #}
+#}
+
+class SR_OBJECT_ENT_LIST(bpy.types.PropertyGroup):#{
+   entities: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_LIST_ENTRY)
+   entities_index: bpy.props.IntProperty()
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.label( text='Entities' )
+      layout.template_list('SR_UL_ENT_LIST', 'Entities', \
+                            data[0], 'entities', data[0], \
+                            'entities_index', rows=5)
+
+      row = layout.row()
+      row.operator( 'skaterift.ent_list_new_entry', text='Add' )
+      row.operator( 'skaterift.ent_list_del_entry', text='Remove' )
+   #}
+#}
+
+class SR_OBJECT_ENT_GLIDER(bpy.types.PropertyGroup):#{
+   nothing: bpy.props.StringProperty()
+#}
+
+class SR_OBJECT_ENT_NPC(bpy.types.PropertyGroup):#{
+   au: bpy.props.IntProperty()
+   context: bpy.props.IntProperty()
+#}
+
+class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):#{
    subtype: bpy.props.EnumProperty(
       name="Subtype",
       items=[('0','Trigger',''),
@@ -2612,15 +3177,45 @@ class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):
 
    target: bpy.props.PointerProperty( \
            type=bpy.types.Object, name="Target", \
-           poll=lambda self,obj: sr_filter_ent_type(obj,\
-                                    ['ent_audio','ent_skateshop']))
+           poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target_event: bpy.props.IntProperty( name="Enter Ev" )
+   target_event_leave: bpy.props.IntProperty( name="Leave Ev", default=-1 )
 
    @staticmethod
-   def sr_inspector( layout, data ):
-   #{
-      data = data[0]
-      layout.prop( data, 'subtype' )
-      layout.prop( data, 'target' )
+   def inspect_target( layout, data, propname, evs = ['_event'] ):#{
+      box = layout.box()
+      box.prop( data[0], propname )
+
+      for evname in evs:#{
+         row = box.row()
+         row.prop( data[0], propname + evname )
+
+         target = getattr( data[0], propname )
+         if target:#{
+            tipo = target.SR_data.ent_type
+            cls = globals()[ tipo ]
+
+            table = getattr( cls, 'sr_functions', None )
+            if table:#{
+               index = getattr( data[0], propname + evname )
+               if index in table:
+                  row.label( text=table[index] )
+               else:
+                  row.label( text="undefined function" )
+            #}
+         #}
+         else:#{
+            row.label( text="..." )
+            row.enabled=False
+         #}
+      #}
+   #}
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'subtype' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target', \
+            ['_event','_event_leave'] )
    #}
 #}
 
@@ -2684,16 +3279,15 @@ class SR_OBJECT_ENT_AUDIO(bpy.types.PropertyGroup):
       box.prop( data[0], 'flag_loop' )
       box.prop( data[0], 'flag_auto' )
 
+      layout.prop( data[0], 'probability_curve' )
+
       split = layout.split(factor=0.7)
       c = split.column()
       c.label( text='Filepath' )
       c = split.column()
-      c.label( text='Chance (0.1s)' )
-
-      layout.prop( data[0], 'probability_curve' )
-
+      c.label( text='Chance' )
       layout.template_list('SR_UL_AUDIO_LIST', 'Files', \
-                            data[0], 'files', data[0], 'file_index', rows=5)
+                            data[0], 'files', data[0], 'files_index', rows=5)
 
       row = layout.row()
       row.operator( 'skaterift.al_new_entry', text='Add' )
@@ -2704,6 +3298,7 @@ class SR_OBJECT_ENT_AUDIO(bpy.types.PropertyGroup):
 class SR_OBJECT_ENT_MARKER(bpy.types.PropertyGroup):
 #{
    alias: bpy.props.StringProperty()
+   flags: bpy.props.IntProperty()
 #}
 
 class SR_OBJECT_ENT_GLYPH(bpy.types.PropertyGroup):
@@ -2766,6 +3361,11 @@ class SR_OBJECT_ENT_TRAFFIC(bpy.types.PropertyGroup):
 
 class SR_OBJECT_ENT_SKATESHOP(bpy.types.PropertyGroup):
 #{
+   tipo: bpy.props.EnumProperty( name='Type',
+                                 items=[('0','boards',''),
+                                        ('1','character',''),
+                                        ('2','world',''),
+                                        ('4','server','')] )
    mark_rack: bpy.props.PointerProperty( \
            type=bpy.types.Object, name="Board Rack", \
            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_marker']))
@@ -2774,7 +3374,8 @@ class SR_OBJECT_ENT_SKATESHOP(bpy.types.PropertyGroup):
            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_marker']))
    mark_info: bpy.props.PointerProperty( \
            type=bpy.types.Object, name="Selected Board Info", \
-           poll=lambda self,obj: sr_filter_ent_type(obj,['ent_marker']))
+           poll=lambda self,obj: sr_filter_ent_type(obj,\
+           ['ent_marker','ent_prop']))
    cam: bpy.props.PointerProperty( \
            type=bpy.types.Object, name="Viewpoint", \
            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_camera']))
@@ -2811,6 +3412,10 @@ class SR_OBJECT_ENT_MENU_ITEM(bpy.types.PropertyGroup):
    newloc: bpy.props.PointerProperty( \
            type=bpy.types.Object, name="New location", \
            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_menuitem']))
+   stack_behaviour: bpy.props.EnumProperty( name='Stack Behaviour',
+                                 items=[('0','append',''),
+                                        ('1','replace','')])
+
    camera: bpy.props.PointerProperty( \
            type=bpy.types.Object, name="Camera", \
            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_camera']))
@@ -2829,6 +3434,8 @@ class SR_OBJECT_ENT_MENU_ITEM(bpy.types.PropertyGroup):
            type=bpy.types.Object, name="Checked", \
            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_menuitem']))
 
+   font_variant: bpy.props.IntProperty( name="Font Variant" )
+
    string: bpy.props.StringProperty( name="String" )
    tipo: bpy.props.EnumProperty( name='Type',
                                  items=[('0','visual',''),
@@ -2836,7 +3443,9 @@ class SR_OBJECT_ENT_MENU_ITEM(bpy.types.PropertyGroup):
                                         ('2','page button',''),
                                         ('3','toggle', ''),
                                         ('4','slider',''),
-                                        ('5','page','')])
+                                        ('5','page',''),
+                                        ('6','binding',''),
+                                        ('7','visual(no colourize)','')])
 
    @staticmethod
    def sr_inspector( layout, data ):
@@ -2845,7 +3454,8 @@ class SR_OBJECT_ENT_MENU_ITEM(bpy.types.PropertyGroup):
       box = layout.box()
       box.prop( data, 'tipo' )
 
-      if data.tipo == '0':#{
+      if data.tipo == '0' or data.tipo == '7':#{
+         box.prop( data, 'string', text='Name' )
          return
       #}
       elif data.tipo == '1':#{
@@ -2853,7 +3463,7 @@ class SR_OBJECT_ENT_MENU_ITEM(bpy.types.PropertyGroup):
       #}
       elif data.tipo == '2':#{
          box.prop( data, 'string', text='Page' )
-         box.prop( data, 'newloc' )
+         box.prop( data, 'stack_behaviour' )
       #}
       elif data.tipo == '3':#{
          box.prop( data, 'string', text='Data (i32)' )
@@ -2876,6 +3486,11 @@ class SR_OBJECT_ENT_MENU_ITEM(bpy.types.PropertyGroup):
          box.prop( data, 'camera', text='Viewpoint' )
          return
       #}
+      elif data.tipo == '6':#{
+         box.prop( data, 'string', text='ID' )
+         box.prop( data, 'font_variant' )
+         return
+      #}
       
       box = box.box()
       box.label( text="Links" )
@@ -2886,6 +3501,151 @@ class SR_OBJECT_ENT_MENU_ITEM(bpy.types.PropertyGroup):
    #}
 #}
 
+class SR_OBJECT_ENT_WORLD_INFO(bpy.types.PropertyGroup):
+#{
+   name: bpy.props.StringProperty(name="Name")
+   desc: bpy.props.StringProperty(name="Description")
+   author: bpy.props.StringProperty(name="Author")
+   skybox: bpy.props.StringProperty(name="Skybox")
+
+   fix_time: bpy.props.BoolProperty(name="Fix Time")
+   timezone: bpy.props.FloatProperty(name="Timezone(hrs) (UTC0 +hrs)")
+   fixed_time: bpy.props.FloatProperty(name="Fixed Time (0-1)")
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'name' )
+      layout.prop( data[0], 'desc' )
+      layout.prop( data[0], 'author' )
+
+      layout.prop( data[0], 'fix_time' )
+      if data[0].fix_time:
+         layout.prop( data[0], 'fixed_time' )
+      else:
+         layout.prop( data[0], 'timezone' )
+   #}
+#}
+
+class SR_OBJECT_ENT_CCMD(bpy.types.PropertyGroup):
+#{
+   command: bpy.props.StringProperty(name="Command Line")
+#}
+
+class SR_OBJECT_ENT_OBJECTIVE(bpy.types.PropertyGroup):#{
+   proxima: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Next", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_objective']))
+   target: bpy.props.PointerProperty( \
+           type=bpy.types.Object, name="Win", \
+           poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target_event: bpy.props.IntProperty( name="Event/Method" )
+   time_limit: bpy.props.FloatProperty( name="Time Limit", default=1.0 )
+   filtrar: bpy.props.EnumProperty( name='Filter',\
+      items=[('0','none',''),
+             (str(0x1),'trick_shuvit',''),
+             (str(0x2),'trick_kickflip',''),
+             (str(0x4),'trick_treflip',''),
+             (str(0x1|0x2|0x4),'trick_any',''),
+             (str(0x8),'flip_back',''),
+             (str(0x10),'flip_front',''),
+             (str(0x8|0x10),'flip_any',''),
+             (str(0x20),'grind_truck_any',''),
+             (str(0x40),'grind_board_any',''),
+             (str(0x20|0x40),'grind_any',''),
+             (str(0x80),'footplant',''),
+             (str(0x100),'passthrough',''),
+             ])
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'proxima' )
+      layout.prop( data[0], 'time_limit' )
+      layout.prop( data[0], 'filtrar' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target' )
+   #}
+#}
+
+class SR_OBJECT_ENT_CHALLENGE(bpy.types.PropertyGroup):#{
+   alias: bpy.props.StringProperty( name="Alias" )
+
+   target: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="On Complete", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target_event: bpy.props.IntProperty( name="Event/Method" )
+   reset: bpy.props.PointerProperty( \
+           type=bpy.types.Object, name="On Reset", \
+           poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   reset_event: bpy.props.IntProperty( name="Event/Method" )
+
+   time_limit: bpy.props.BoolProperty( name="Time Limit" )
+
+   first: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="First Objective", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_objective']))
+
+   camera: bpy.props.PointerProperty( \
+           type=bpy.types.Object, name="Camera", \
+           poll=lambda self,obj: sr_filter_ent_type(obj,['ent_camera']))
+
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'alias' )
+      layout.prop( data[0], 'camera' )
+      layout.prop( data[0], 'first' )
+      layout.prop( data[0], 'time_limit' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'reset' )
+   #}
+#}
+
+class SR_OBJECT_ENT_REGION(bpy.types.PropertyGroup):#{
+   title: bpy.props.StringProperty( name="Title" )
+   zone_volume: bpy.props.PointerProperty(
+            type=bpy.types.Object, name="Zone Volume", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,['ent_volume']))
+
+   target0: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Triger on unlock", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target0_event: bpy.props.IntProperty( name="Event/Method" )
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'title' )
+      layout.prop( data[0], 'zone_volume' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target0' )
+   #}
+#}
+
+class SR_OBJECT_ENT_RELAY(bpy.types.PropertyGroup):#{
+   target0: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Target 0", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target1: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Target 1", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target2: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Target 2", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target3: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Target 3", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+
+   target0_event: bpy.props.IntProperty( name="Event" )
+   target1_event: bpy.props.IntProperty( name="Event" )
+   target2_event: bpy.props.IntProperty( name="Event" )
+   target3_event: bpy.props.IntProperty( name="Event" )
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target0' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target1' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target2' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target3' )
+   #}
+#}
+
 class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
 #{
    ent_gate: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GATE)
@@ -2894,6 +3654,7 @@ class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
    ent_volume: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_VOLUME)
    ent_audio: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO)
    ent_marker: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MARKER)
+   ent_prop: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MARKER)
    ent_glyph: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH)
    ent_font: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT)
    ent_traffic: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_TRAFFIC)
@@ -2901,6 +3662,16 @@ class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
    ent_swspreview: \
          bpy.props.CollectionProperty(type=SR_OBJECT_ENT_WORKSHOP_PREVIEW)
    ent_menuitem: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MENU_ITEM)
+   ent_worldinfo: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_WORLD_INFO)
+   ent_ccmd: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_CCMD)
+   ent_objective: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_OBJECTIVE)
+   ent_challenge: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_CHALLENGE)
+   ent_region: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_REGION)
+   ent_relay: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_RELAY)
+   ent_miniworld: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MINIWORLD)
+   ent_list: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_LIST)
+   ent_glider: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLIDER)
+   ent_npc: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_NPC)
 
    ent_type: bpy.props.EnumProperty(
       name="Type",
@@ -2975,7 +3746,11 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
       ('vertex_blend', "vertex_blend", ''),
       ('water',"water",''),
       ('invisible','Invisible',''),
-      ('boundary','Boundary','')
+      ('boundary','Boundary',''),
+      ('fxglow','FX Glow',''),
+      ('cubemap','Cubemap',''),
+      ('walking','Walking',''),
+      ('foliage','Foliage','')
       ])
 
    surface_prop: bpy.props.EnumProperty(
@@ -2985,7 +3760,9 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
       ('1','wood',''),
       ('2','grass',''),
       ('3','tiles',''),
-      ('4','metal','')
+      ('4','metal',''),
+      ('5','snow (low friction)',''),
+      ('6','sand (medium friction)','')
       ])
    
    collision: bpy.props.BoolProperty( \
@@ -3008,6 +3785,11 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
          default=False,\
          description = "Spawn grass sprites on this surface?" \
    )
+   preview_visibile: bpy.props.BoolProperty( \
+         name="Preview visibile", \
+         default=True,\
+         description = "Show this material in preview models?" \
+   )
    blend_offset: bpy.props.FloatVectorProperty( \
          name="Blend Offset", \
          size=2, \
@@ -3036,6 +3818,20 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
          default=Vector((0.0,0.006,0.03)),\
          description="Water colour in the deep bits"\
    )
+   tint: bpy.props.FloatVectorProperty( \
+         name="Tint",\
+         subtype='COLOR',\
+         min=0.0,max=1.0,\
+         size=4,\
+         default=Vector((1.0,1.0,1.0,1.0)),\
+         description="Reflection tint"\
+   )
+
+   cubemap: bpy.props.PointerProperty( \
+               type=bpy.types.Object, name="cubemap", \
+               poll=lambda self,obj: sr_filter_ent_type(obj,['ent_cubemap']))
+
+   tex_diffuse_rt: bpy.props.IntProperty( name="diffuse: RT index", default=-1 )
 #}
 
 # ---------------------------------------------------------------------------- #
@@ -3045,6 +3841,7 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
 # ---------------------------------------------------------------------------- #
 
 cv_view_draw_handler = None
+cv_view_pixel_handler = None
 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
 cv_view_verts = []
 cv_view_colours = []
@@ -3200,7 +3997,7 @@ def cv_tangent_basis( n, tx, ty ):
 
 # Draw coloured arrow
 #
-def cv_draw_arrow( p0, p1, c0, size=0.15 ):
+def cv_draw_arrow( p0, p1, c0, size=0.25, outline=True ):
 #{
    global cv_view_verts, cv_view_colours
 
@@ -3211,10 +4008,26 @@ def cv_draw_arrow( p0, p1, c0, size=0.15 ):
    tx = Vector((1,0,0))
    ty = Vector((1,0,0))
    cv_tangent_basis( n, tx, ty )
-   
+   tx *= 0.5
+   ty *= 0.5
+
+   if outline:#{
+      cv_draw_lines()
+      gpu.state.line_width_set(1.0)
+   #}
+
    cv_view_verts += [p0,p1, midpt+(tx-n)*size,midpt, midpt+(-tx-n)*size,midpt ]
    cv_view_colours += [c0,c0,c0,c0,c0,c0]
-   #cv_draw_lines()
+   cv_draw_lines()
+
+   if outline:#{
+      gpu.state.line_width_set(3.0)
+      cv_view_verts += [p0,p1,midpt+(tx-n)*size,midpt,midpt+(-tx-n)*size,midpt]
+      b0 = (0,0,0)
+      cv_view_colours += [b0,b0,b0,b0,b0,b0]
+      cv_draw_lines()
+      gpu.state.line_width_set(2.0)
+   #}
 #}
 
 def cv_draw_line_dotted( p0, p1, c0, dots=10 ):
@@ -3304,7 +4117,8 @@ def cv_draw_lines():
          cv_view_shader, 'LINES', \
          { "pos":cv_view_verts, "color":cv_view_colours })
 
-   lines.draw( cv_view_shader )
+   if bpy.context.scene.SR_data.gizmos:
+      lines.draw( cv_view_shader )
 
    cv_view_verts = []
    cv_view_colours = []
@@ -3381,8 +4195,8 @@ def draw_cone_twist( center, vx, vy, va ):
       p0 = center + (axis + vx*c0 + vy*s0).normalized() * size
       p1 = center + (axis + vx*c1 + vy*s1).normalized() * size
 
-      col0 = ( abs(c0), abs(s0), 0.0, 1.0 )
-      col1 = ( abs(c1), abs(s1), 0.0, 1.0 )
+      col0 = ( abs(c0), abs(s0), 0.0 )
+      col1 = ( abs(c1), abs(s1), 0.0 )
 
       cv_view_verts += [center, p0, p0, p1]
       cv_view_colours += [ (0,0,0), col0, col0, col1 ]
@@ -3482,6 +4296,14 @@ def draw_skeleton_helpers( obj ):
    #}
 #}
 
+def cv_draw_wireframe( mdl, points, colour ):#{
+   for i in range(len(points)//2):#{
+      p0 = mdl@points[i*2+0]
+      p1 = mdl@points[i*2+1]
+      cv_draw_line( p0, p1, colour )
+   #}
+#}
+
 def cv_ent_gate( obj ):
 #{
    global cv_view_verts, cv_view_colours
@@ -3536,17 +4358,17 @@ def cv_ent_volume( obj ):
    data = obj.SR_data.ent_volume[0]
 
    if data.subtype == '0':#{
-      cv_draw_ucube( obj.matrix_world, (0,1,0) )
+      cv_draw_ucube( obj.matrix_world, (0,1,0), Vector((0.99,0.99,0.99)) )
 
       if data.target:#{
-         cv_draw_line( obj.location, data.target.location, (0,1,0) )
+         cv_draw_arrow( obj.location, data.target.location, (1,1,1) )
       #}
    #}
    elif data.subtype == '1':#{
       cv_draw_ucube( obj.matrix_world, (1,1,0) )
 
       if data.target:#{
-         cv_draw_line( obj.location, data.target.location, (1,1,0) )
+         cv_draw_arrow( obj.location, data.target.location, (1,1,1) )
       #}
    #}
 #}
@@ -3765,14 +4587,14 @@ def cv_draw_route( route, dij ):
       path = solve_graph( dij, gi.name, gj.name )
 
       if path:#{
-         cv_draw_arrow(gi.location,dij.points[path[0]],cc,1.5)
-         cv_draw_arrow(dij.points[path[len(path)-1]],gj.location,cc,1.5)
+         cv_draw_arrow(gi.location,dij.points[path[0]],cc,1.5,False)
+         cv_draw_arrow(dij.points[path[len(path)-1]],gj.location,cc,1.5,False)
          for j in range(len(path)-1):#{
             i0 = path[j]
             i1 = path[j+1]
             o0 = dij.points[ i0 ]
             o1 = dij.points[ i1 ]
-            cv_draw_arrow(o0,o1,cc,1.5)
+            cv_draw_arrow(o0,o1,cc,1.5,False)
          #}
       #}
       else:#{
@@ -3781,8 +4603,7 @@ def cv_draw_route( route, dij ):
    #}
 #}
 
-def cv_draw():
-#{
+def cv_draw():#{
    global cv_view_shader
    global cv_view_verts
    global cv_view_colours
@@ -3793,7 +4614,7 @@ def cv_draw():
    cv_view_colours = []
 
    cv_view_shader.bind()
-   gpu.state.depth_mask_set(False)
+   gpu.state.depth_mask_set(True)
    gpu.state.line_width_set(2.0)
    gpu.state.face_culling_set('BACK')
    gpu.state.depth_test_set('LESS')
@@ -3825,6 +4646,51 @@ def cv_draw():
          elif ent_type == 'ent_volume':#{
             cv_ent_volume( obj )
          #}
+         elif ent_type == 'ent_objective':#{
+            data = obj.SR_data.ent_objective[0]
+            if data.proxima:#{
+               cv_draw_arrow( obj.location, data.proxima.location, (1,0.6,0.2) )
+            #}
+            if data.target:
+               cv_draw_arrow( obj.location, data.target.location, (1,1,1) )
+         #}
+         elif ent_type == 'ent_relay':#{
+            data = obj.SR_data.ent_relay[0]
+            if data.target0:
+               cv_draw_arrow( obj.location, data.target0.location, (1,1,1) )
+            if data.target1:
+               cv_draw_arrow( obj.location, data.target1.location, (1,1,1) )
+            if data.target2:
+               cv_draw_arrow( obj.location, data.target2.location, (1,1,1) )
+            if data.target3:
+               cv_draw_arrow( obj.location, data.target3.location, (1,1,1) )
+         #}
+         elif ent_type == 'ent_challenge':#{
+            data = obj.SR_data.ent_challenge[0]
+            if data.target:
+               cv_draw_arrow( obj.location, data.target.location, (1,1,1) )
+            if data.reset:
+               cv_draw_arrow( obj.location, data.reset.location, (0.9,0,0) )
+            if data.first:
+               cv_draw_arrow( obj.location, data.first.location, (1,0.6,0.2) )
+
+            cc1 = (0.4,0.3,0.2)
+            info_cu = Vector((1.2,0.01,0.72))*0.5
+            info_co = Vector((0.0,0.0,0.72))*0.5
+            cv_draw_ucube( obj.matrix_world, cc1, info_cu, info_co)
+            if data.camera:
+               cv_draw_line_dotted( obj.location, data.camera.location, (1,1,1))
+
+            vs = [Vector((-0.2,0.0,0.10)),Vector((-0.2,0.0,0.62)),\
+                  Vector(( 0.2,0.0,0.62)),Vector((-0.2,0.0,0.30)),\
+                  Vector(( 0.1,0.0,0.30))]
+            for v in range(len(vs)):#{
+               vs[v] = obj.matrix_world @ vs[v]
+            #}
+
+            cv_view_verts += [vs[0],vs[1],vs[1],vs[2],vs[3],vs[4]]
+            cv_view_colours += [cc1,cc1,cc1,cc1,cc1,cc1]
+         #}
          elif ent_type == 'ent_audio':#{
             if obj.SR_data.ent_audio[0].flag_3d:
                cv_draw_sphere( obj.location, obj.scale[0], (1,1,0) )
@@ -3852,22 +4718,67 @@ def cv_draw():
                #}
             #}
          #}
+         elif ent_type == 'ent_glider':#{
+            mesh = [Vector((-1.13982, 0.137084, -0.026358)), \
+                    Vector(( 1.13982, 0.137084, -0.026358)), \
+                    Vector(( 0.0, 1.6, 1.0)), \
+                    Vector(( 0.0, -3.0, 1.0)), \
+                    Vector(( -3.45, -1.78, 0.9)), \
+                    Vector(( 0.0, 1.6, 1.0)), \
+                    Vector((  3.45, -1.78, 0.9)), \
+                    Vector(( 0.0, 1.6, 1.0)), \
+                    Vector((  3.45, -1.78, 0.9)), \
+                    Vector(( -3.45, -1.78, 0.9))]
+
+            cv_draw_wireframe( obj.matrix_world, mesh, (1,1,1) )
+         #}
          elif ent_type == 'ent_skateshop':#{
-            cc = (0.0,0.9,0.6)
-            cc1 = (0.4,0.9,0.2)
-            cc2 = (0.9,0.6,0.1)
-
             data = obj.SR_data.ent_skateshop[0]
             display = data.mark_display
             info = data.mark_info
-            rack = data.mark_rack
 
-            rack_cu = Vector((3.15,2.0,0.1))*0.5
-            rack_co = Vector((0.0,0.0,0.0))
-            display_cu = Vector((0.3,1.2,0.1))*0.5
-            display_co = Vector((0.0,0.0,0.1))*0.5
-            info_cu = Vector((1.2,0.01,0.3))*0.5
-            info_co = Vector((0.0,0.0,0.0))*0.5
+            if data.tipo == '0':#{
+               cc = (0.0,0.9,0.6)
+               cc1 = (0.4,0.9,0.2)
+               cc2 = (0.9,0.6,0.1)
+
+               rack = data.mark_rack
+
+               rack_cu = Vector((3.15,2.0,0.1))*0.5
+               rack_co = Vector((0.0,0.0,0.0))
+               display_cu = Vector((0.3,1.2,0.1))*0.5
+               display_co = Vector((0.0,0.0,0.1))*0.5
+               info_cu = Vector((1.2,0.01,0.3))*0.5
+               info_co = Vector((0.0,0.0,0.0))*0.5
+            #}
+            elif data.tipo == '1':#{
+               rack = None
+               cc1 = (1.0,0.0,0.0)
+               cc2 = (1.0,0.5,0.0)
+               display_cu = Vector((0.4,0.4,2.0))*0.5
+               display_co = Vector((0.0,0.0,1.0))*0.5
+               info_cu = Vector((1.2,0.01,0.3))*0.5
+               info_co = Vector((0.0,0.0,0.0))*0.5
+            #}
+            elif data.tipo == '2':#{
+               rack = None
+               cc1 = (1.0,0.0,0.0)
+               cc2 = (1.0,0.5,0.0)
+               display_cu = Vector((1.0,1.0,0.5))*0.5
+               display_co = Vector((0.0,0.0,0.5))*0.5
+               info_cu = Vector((1.2,0.01,0.3))*0.5
+               info_co = Vector((0.0,0.0,0.0))*0.5
+            #}
+            elif data.tipo == '3':#{
+               rack = None
+               display = None
+               info = None
+            #}
+            elif data.tipo == '4':#{
+               rack = None
+               display = None
+               info = None
+            #}
 
             if rack:
                cv_draw_ucube( rack.matrix_world, cc, rack_cu, rack_co )
@@ -3888,6 +4799,22 @@ def cv_draw():
             if display1:
                cv_draw_ucube(display1.matrix_world, cc1, display_cu, display_co)
          #}
+         # elif ent_type == 'ent_list':#{
+         #    data = obj.SR_data.ent_list[0]
+         #    for child in data.entities:#{
+         #       if child.target:#{
+         #          cv_draw_arrow( obj.location, child.target.location, \
+         #                         (.5,.5,.5), 0.1 )
+         #       #}
+         #    #}
+         # #}
+         elif ent_type == 'ent_region':#{
+            data = obj.SR_data.ent_region[0]
+            if data.target0:#{
+               cv_draw_arrow( obj.location, data.target0.location, \
+                              (.5,.5,.5), 0.1 )
+            #}
+         #}
          elif ent_type == 'ent_menuitem':#{
             for i,col in enumerate(obj.users_collection):#{
                colour32 = hash_djb2( col.name )
@@ -3970,7 +4897,31 @@ def cv_draw():
    #}
 
    cv_draw_lines()
-   return
+#}
+
+def pos3d_to_2d( pos ):#{
+   return view3d_utils.location_3d_to_region_2d( \
+            bpy.context.region, \
+            bpy.context.space_data.region_3d, pos )
+#}
+
+def cv_draw_pixel():#{
+   if not bpy.context.scene.SR_data.gizmos: return
+   blf.size(0,10)
+   blf.color(0, 1.0,1.0,1.0,0.9)
+   blf.enable(0,blf.SHADOW)
+   blf.shadow(0,3,0.0,0.0,0.0,1.0)
+   for obj in bpy.context.collection.objects:#{
+      ent_type = obj_ent_type( obj )
+      
+      if ent_type != 'none':#{
+         co = pos3d_to_2d( obj.location )
+
+         if not co: continue
+         blf.position(0,co[0],co[1],0)
+         blf.draw(0,ent_type)
+      #}
+   #}
 #}
 
 classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
@@ -3994,6 +4945,13 @@ classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
             SR_UL_FONT_VARIANT_LIST,SR_UL_FONT_GLYPH_LIST,\
             SR_OBJECT_ENT_FONT,SR_OBJECT_ENT_TRAFFIC,SR_OBJECT_ENT_SKATESHOP,\
             SR_OBJECT_ENT_WORKSHOP_PREVIEW,SR_OBJECT_ENT_MENU_ITEM,\
+            SR_OBJECT_ENT_WORLD_INFO,SR_OBJECT_ENT_CCMD,\
+            SR_OBJECT_ENT_OBJECTIVE,SR_OBJECT_ENT_CHALLENGE,\
+            SR_OBJECT_ENT_REGION,\
+            SR_OBJECT_ENT_RELAY,SR_OBJECT_ENT_MINIWORLD,\
+            SR_OBJECT_ENT_LIST_ENTRY, SR_UL_ENT_LIST, SR_OBJECT_ENT_LIST, \
+            SR_OT_ENT_LIST_NEW_ITEM, SR_OT_ENT_LIST_DEL_ITEM,\
+            SR_OBJECT_ENT_GLIDER, SR_OBJECT_ENT_NPC, \
             \
             SR_OBJECT_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES, 
             SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \
@@ -4020,9 +4978,11 @@ def register():
    bpy.types.Material.SR_data = \
          bpy.props.PointerProperty(type=SR_MATERIAL_PROPERTIES)
 
-   global cv_view_draw_handler
+   global cv_view_draw_handler, cv_view_pixel_handler
    cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
       cv_draw,(),'WINDOW','POST_VIEW')
+   cv_view_pixel_handler = bpy.types.SpaceView3D.draw_handler_add(\
+      cv_draw_pixel,(),'WINDOW','POST_PIXEL')
 #}
 
 def unregister():
@@ -4030,8 +4990,9 @@ def unregister():
    for c in classes:
       bpy.utils.unregister_class(c)
 
-   global cv_view_draw_handler
+   global cv_view_draw_handler, cv_view_pixel_handler
    bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')
+   bpy.types.SpaceView3D.draw_handler_remove(cv_view_pixel_handler,'WINDOW')
 #}
 
 # ---------------------------------------------------------------------------- #