unlockables basics
authorhgn <hgodden00@gmail.com>
Fri, 21 Jul 2023 09:02:02 +0000 (10:02 +0100)
committerhgn <hgodden00@gmail.com>
Fri, 21 Jul 2023 09:02:02 +0000 (10:02 +0100)
13 files changed:
blender_export.py
ent_challenge.c
ent_relay.c [new file with mode: 0644]
ent_unlock.c [new file with mode: 0644]
entity.c
entity.h
maps_src/mp_spawn/main.mdl
player_common.c
world.h
world_gate.c
world_gate.h
world_load.c
world_render.c

index 8eb39a365dba70129c69776182175b39a30ea90f..54a02e81310a2300b73ab1a3e3038239a1a92229 100644 (file)
@@ -36,10 +36,14 @@ sr_entity_list = [
    ('ent_menuitem',     'Menu Item',      '', 15 ),
    ('ent_worldinfo',    'World Info',     '', 16 ),
    ('ent_ccmd',         'CCmd',           '', 17 ),
-   ('ent_challenge',    'Challenge',      '', 18 )
+   ('ent_challenge',    'Challenge',      '', 18 ),
+   ('ent_unlock',       'Unlockable',     '', 19 ),
+   ('ent_relay',        'Relay',          '', 20 )
 ]
 
 MDL_VERSION_NR = 102
+SR_TRIGGERABLE = [ 'ent_audio', 'ent_ccmd', 'ent_gate', 'ent_unlock', \
+                   'ent_relay', 'ent_skateshop', 'ent_challenge' ]
 
 def get_entity_enum_id( alias ):
 #{
@@ -203,6 +207,7 @@ class ent_gate(Structure):
                ("submesh_start",c_uint32), # v102+ 
                ("submesh_count",c_uint32), # v102+ (can be 0)
                ]
+   sr_functions = { 0: 'unlock' }
 #}
 
 class ent_route_node(Structure):
@@ -456,12 +461,28 @@ class ent_challenge(Structure):#{
                ("submesh_start",c_uint32), ("submesh_count",c_uint32),
                ("id_next",c_uint32),
                ("filter",c_uint32),
+               ("id_win",c_uint32),
+               ("win_event",c_uint32),
                ("time_limit",c_float)]
 
    sr_functions = { 0: 'trigger',
                     1: 'start_challenge' }
 #}
 
+class ent_unlock(Structure):#{
+   _fields_ = [("pstr_alias",c_uint32),
+               ("target",c_uint32),
+               ("target_event",c_uint32),
+               ("status",c_uint32)]
+   sr_functions = { 0: 'unlock' }
+#}
+
+class ent_relay(Structure):#{
+   _fields_ = [("targets",(c_uint32*2)*4),
+               ("targets_events",c_uint32*4)]
+   sr_functions = { 0: 'trigger' }
+#}
+
 def obj_ent_type( obj ):
 #{
    if obj.type == 'ARMATURE': return 'mdl_armature'
@@ -1650,6 +1671,7 @@ def sr_compile( collection ):
                gate.submesh_start, gate.submesh_count, _ = \
                      sr_compile_mesh_internal( obj )
             #}
+            if obj_data.locked: flags |= 0x0010
             gate.flags = flags
             
             gate.dimensions[0] = mesh_data.dimensions[0]
@@ -1747,7 +1769,7 @@ def sr_compile( collection ):
 
             if obj_data.target:#{
                volume.target = sr_entity_id( obj_data.target )
-               volume._anon.trigger.event = obj_data.event
+               volume._anon.trigger.event = obj_data.target_event
             #}
 
             sr_ent_push(volume)
@@ -1809,6 +1831,8 @@ def sr_compile( collection ):
             challenge = ent_challenge()
             obj_data = obj.SR_data.ent_challenge[0]
             challenge.id_next = sr_entity_id( obj_data.proxima )
+            challenge.id_win = sr_entity_id( obj_data.target )
+            challenge.win_event = obj_data.target_event
             challenge.filter = 0
             challenge.time_limit = obj_data.time_limit
 
@@ -1818,6 +1842,28 @@ def sr_compile( collection ):
 
             sr_ent_push( challenge )
          #}
+         elif ent_type == 'ent_unlock':#{
+            unlock = ent_unlock()
+            obj_data = obj.SR_data.ent_unlock[0]
+            unlock.pstr_alias = sr_compile_string( obj_data.alias )
+            unlock.target = sr_entity_id( obj_data.target )
+            unlock.target_event = obj_data.target_event
+            unlock.status = 0
+            sr_ent_push( unlock )
+         #}
+         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 )
+         #}
       #}
    #}
 
@@ -2404,6 +2450,7 @@ class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
 
    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 ):
@@ -2417,6 +2464,7 @@ class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
       flags = box.box()
       flags.prop( data[0], 'flip' )
       flags.prop( data[0], 'custom' )
+      flags.prop( data[0], 'locked' )
    #}
 #}
 
@@ -2776,31 +2824,40 @@ class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):#{
            poll=lambda self,obj: sr_filter_ent_type(obj,\
                                     ['ent_audio','ent_skateshop','ent_ccmd',\
                                      'ent_challenge']))
-
-   event: bpy.props.IntProperty( name="Event/Method" )
+   target_event: bpy.props.IntProperty( name="Event/Method" )
 
    @staticmethod
-   def sr_inspector( layout, data ):#{
-      layout.prop( data[0], 'subtype' )
-      layout.prop( data[0], 'target' )
+   def inspect_target( layout, data, propname ):#{
+      box = layout.box()
+      box.prop( data[0], propname )
 
-      row = layout.row()
-      row.prop( data[0], 'event' )
+      row = box.row()
+      row.prop( data[0], propname + '_event')
 
-      if data[0].target:#{
-         tipo = data[0].target.SR_data.ent_type
+      target = getattr( data[0], propname )
+      if target:#{
+         tipo = target.SR_data.ent_type
          cls = globals()[ tipo ]
 
          table = getattr( cls, 'sr_functions', None )
          if table:#{
-            if data[0].event in table:#{
-               row.label( text=table[data[0].event] )
-            #}
-            else:#{
+            index = getattr( data[0], propname+'_event')
+            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' )
    #}
 #}
 
@@ -3087,11 +3144,59 @@ class SR_OBJECT_ENT_CHALLENGE(bpy.types.PropertyGroup):#{
             type=bpy.types.Object, name="Next", \
             poll=lambda self,obj: sr_filter_ent_type(obj,['ent_challenge']))
    target: bpy.props.PointerProperty( \
-           type=bpy.types.Object, name="Target", \
-           poll=lambda self,obj: sr_filter_ent_type(obj,\
-                                    ['ent_audio','ent_ccmd']))
-   event: bpy.props.IntProperty( name="Event/Method" )
+           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 )
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'proxima' )
+      layout.prop( data[0], 'time_limit' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target' )
+   #}
+#}
+
+class SR_OBJECT_ENT_UNLOCK(bpy.types.PropertyGroup):#{
+   alias: bpy.props.StringProperty( name="Alias" )
+   target: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Target", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target_event: bpy.props.IntProperty( name="Event/Method" )
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'alias' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target' )
+   #}
+#}
+
+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):
@@ -3112,6 +3217,8 @@ class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
    ent_worldinfo: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_WORLD_INFO)
    ent_ccmd: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_CCMD)
    ent_challenge: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_CHALLENGE)
+   ent_unlock: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_UNLOCK)
+   ent_relay: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_RELAY)
 
    ent_type: bpy.props.EnumProperty(
       name="Type",
@@ -3417,7 +3524,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 ):
 #{
    global cv_view_verts, cv_view_colours
 
@@ -4048,6 +4155,24 @@ def cv_draw():
             if data.proxima:#{
                cv_draw_arrow( obj.location, data.proxima.location, (0,0.2,1.0) )
             #}
+            if data.target:
+               cv_draw_arrow( obj.location, data.target.location, (0,1.0,0.0) )
+         #}
+         elif ent_type == 'ent_relay':#{
+            data = obj.SR_data.ent_relay[0]
+            if data.target0:
+               cv_draw_arrow( obj.location, data.target0.location, (0,1,0) )
+            if data.target1:
+               cv_draw_arrow( obj.location, data.target1.location, (0,1,0) )
+            if data.target2:
+               cv_draw_arrow( obj.location, data.target2.location, (0,1,0) )
+            if data.target3:
+               cv_draw_arrow( obj.location, data.target3.location, (0,1,0) )
+         #}
+         elif ent_type == 'ent_unlock':#{
+            data = obj.SR_data.ent_unlock[0]
+            if data.target:
+               cv_draw_arrow( obj.location, data.target.location, (0,1.0,0.0) )
          #}
          elif ent_type == 'ent_audio':#{
             if obj.SR_data.ent_audio[0].flag_3d:
@@ -4240,7 +4365,7 @@ classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
             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_CHALLENGE,\
+            SR_OBJECT_ENT_CHALLENGE,SR_OBJECT_ENT_UNLOCK,SR_OBJECT_ENT_RELAY,\
             \
             SR_OBJECT_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES, 
             SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \
index 5f22eaaedf9f724b495a9069cc61896a807c4674..f01d335f52a50b16bffd98003e598f4ad21dc4c0 100644 (file)
@@ -20,6 +20,14 @@ VG_STATIC void ent_challenge_pass( world_instance *world,
       vg_success( "NYU Film school graduate SUCKAH\n" );
       world->challenge_target = NULL;
       world->challenge_timer = 0.0f;
+
+      if( challenge->id_win ){
+         ent_call call;
+         call.data = NULL;
+         call.function = challenge->win_event;
+         call.id = challenge->id_win;
+         entity_call( world, &call );
+      }
    }
 }
 
diff --git a/ent_relay.c b/ent_relay.c
new file mode 100644 (file)
index 0000000..60144ab
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef ENT_RELAY_C
+#define ENT_RELAY_C
+
+#include "entity.h"
+
+VG_STATIC void ent_relay_call( world_instance *world, ent_call *call ){
+   u32 index = mdl_entity_id_id( call->id );
+   ent_relay *relay = mdl_arritm( &world->ent_relay, index );
+
+   if( call->function == 0 ){
+      for( u32 i=0; i<vg_list_size(relay->targets); i++ ){
+         if( relay->targets[i][0] ){
+            ent_call call;
+            call.data = NULL;
+            call.function = relay->targets[i][1];
+            call.id = relay->targets[i][0];
+            entity_call( world, &call );
+         }
+      }
+   }
+   else {
+      vg_print_backtrace();
+      vg_error( "Unhandled function id: %u\n", call->function );
+   }
+}
+
+#endif /* ENT_RELAY_C */
diff --git a/ent_unlock.c b/ent_unlock.c
new file mode 100644 (file)
index 0000000..8a266ae
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef ENT_UNLOCK_C
+#define ENT_UNLOCK_C
+
+#include "entity.h"
+
+VG_STATIC void ent_unlock_call( world_instance *world, ent_call *call ){
+   u32 index = mdl_entity_id_id( call->id );
+   ent_unlock *unlock = mdl_arritm( &world->ent_unlock, index );
+
+   if( call->function == 0 ){ /* unlock() */
+      if( !unlock->status ){
+         vg_info( "unlock( '%s' )\n", 
+                  mdl_pstr( &world->meta, unlock->pstr_alias) );
+         ent_call call;
+         call.data = NULL;
+         call.function = unlock->target_event;
+         call.id = unlock->target;
+         entity_call( world, &call );
+      }
+      unlock->status = 1;
+   }
+   else {
+      vg_print_backtrace();
+      vg_error( "Unhandled function id: %u\n", call->function );
+   }
+}
+
+#endif /* ENT_UNLOCK_C */
index cfc710618e60af5a26d6d1064c95ced82b4631e4..33bed055a2b8d0df0eafa1b84ea6f6a32ff4c3ee 100644 (file)
--- a/entity.c
+++ b/entity.c
@@ -7,6 +7,8 @@
 
 #include "ent_skateshop.c"
 #include "ent_challenge.c"
+#include "ent_unlock.c"
+#include "ent_relay.c"
 
 typedef void (*fn_entity_call_handler)( world_instance *, ent_call *);
 
@@ -18,10 +20,13 @@ VG_STATIC void entity_call( world_instance *world, ent_call *call ){
       [k_ent_audio]     = ent_audio_call,
       [k_ent_skateshop] = ent_skateshop_call,
       [k_ent_challenge] = ent_challenge_call,
-      [k_ent_ccmd]      = ent_ccmd_call
+      [k_ent_ccmd]      = ent_ccmd_call,
+      [k_ent_gate]      = ent_gate_call,
+      [k_ent_relay]     = ent_relay_call,
+      [k_ent_unlock]    = ent_unlock_call
    };
 
-   if( type > vg_list_size(table) ){
+   if( type >= vg_list_size(table) ){
       vg_error( "call to entity type: %u is out of range\n", type );
       return;
    }
index da2341e5618149e1788acb63febdbb821607adf3..ae9d63bd45f7aa442b41779d7d1d6d919792c648 100644 (file)
--- a/entity.h
+++ b/entity.h
@@ -27,6 +27,8 @@ typedef struct ent_swspreview ent_swspreview;
 typedef struct ent_worldinfo ent_worldinfo;
 typedef struct ent_ccmd ent_ccmd;
 typedef struct ent_challenge ent_challenge;
+typedef struct ent_unlock ent_unlock;
+typedef struct ent_relay ent_relay;
 
 enum entity_alias{
    k_ent_none        = 0,
@@ -48,7 +50,8 @@ enum entity_alias{
    k_ent_worldinfo   = 16,
    k_ent_ccmd        = 17,
    k_ent_challenge   = 18,
-   k_ent_relay       = 19
+   k_ent_unlock      = 19,
+   k_ent_relay       = 20
 };
 
 static u32 mdl_entity_id_type( u32 entity_id ){
@@ -108,6 +111,7 @@ enum ent_gate_flag{
                                        NOTE: if set, it adds the flip flag. */
    k_ent_gate_flip        = 0x4, /* flip direction 180* for exiting portal */
    k_ent_gate_custom_mesh = 0x8, /* use a custom submesh instead of default */
+   k_ent_gate_locked      = 0x10,/* has to be unlocked to be useful */
 };
 
 struct ent_gate{
@@ -400,10 +404,23 @@ struct ent_challenge{
    u32 submesh_start,
        submesh_count,
        id_next,
-       filter;
+       filter,
+       id_win,
+       win_event;
    f32 time_limit;
 };
 
+struct ent_unlock{
+   u32 pstr_alias,
+       target,
+       target_event,
+       status;
+};
+
+struct ent_relay {
+   u32 targets[4][2];
+};
+
 typedef struct ent_call ent_call;
 struct ent_call{
    u32 id, function;
index 4467e7b18e3e9baa1dfc01a7350a1cafcce6296b..4d9629f05908e4b2aa0fd094470bae35618d4cf7 100644 (file)
Binary files a/maps_src/mp_spawn/main.mdl and b/maps_src/mp_spawn/main.mdl differ
index 270f40e5140174179b956f41a515e3cebc3d9ecd..3711405226dc0bc4fa88a144fb1f7e73ce758474 100644 (file)
@@ -40,8 +40,10 @@ VG_STATIC void player_camera_portal_correction( player_instance *player )
       q_mulv( player->gate_waiting->q[1], (v3f){0.0f,0.0f,1.0f}, plane );
       plane[3] = v3_dot( plane, player->gate_waiting->co[1] );
 
+      f32 pol = v3_dot( player->cam.pos, plane ) - plane[3];
+
       /* check camera polarity */
-      if( v3_dot( player->cam.pos, plane ) < plane[3] ) {
+      if( (pol < 0.0f) || (pol > 5.0f) ) {
          vg_success( "Plane cleared\n" );
          player_apply_transport_to_cam( player->gate_waiting->transport );
          player->gate_waiting = NULL;
diff --git a/world.h b/world.h
index 49c030ea37260f7945311ee69e4a9db447882f9d..e95f005b5bffb8042108ab82deed8e567b3d6a6b 100644 (file)
--- a/world.h
+++ b/world.h
@@ -156,7 +156,9 @@ struct world_instance {
                  ent_camera,
                  ent_swspreview,
                  ent_ccmd,
-                 ent_challenge;
+                 ent_challenge,
+                 ent_unlock,
+                 ent_relay;
 
    ent_gate *rendering_gate;
 
index a4cf8fdc703e8b21ae976918e924bbb5206fb671..0a645518770484ce6ace789cc59bbdb6cfe9d5f7 100644 (file)
@@ -265,6 +265,7 @@ VG_STATIC ent_gate *world_intersect_gates( world_instance *world,
       ent_gate *gate = mdl_arritm( &world->ent_gate, i );
 
       if( !(gate->flags & k_ent_gate_linked) ) continue;
+      if( gate->flags & k_ent_gate_locked ) continue;
 
       if( gate->flags & k_ent_gate_nonlocal ){
          if( world_loader.state != k_world_loader_none ){
@@ -367,4 +368,17 @@ matched:;
    }
 }
 
+VG_STATIC void ent_gate_call( world_instance *world, ent_call *call ){
+   u32 index = mdl_entity_id_id( call->id );
+   ent_gate *gate = mdl_arritm( &world->ent_gate, index );
+
+   if( call->function == 0 ){ /* unlock() */
+      gate->flags &= ~k_ent_gate_locked;
+   }
+   else {
+      vg_print_backtrace();
+      vg_error( "Unhandled function id: %u\n", call->function );
+   }
+}
+
 #endif /* WORLD_GATE_C */
index 4b3299aded23d2e47caf2b18388c361268e7e873..89040fd811583e7812fa6faaa54e133f862ed332 100644 (file)
@@ -27,5 +27,6 @@ VG_STATIC int gate_intersect( ent_gate *gate, v3f pos, v3f last );
 VG_STATIC ent_gate *world_intersect_gates( world_instance *world,
                                            v3f pos, v3f last );
 
+VG_STATIC void ent_gate_call( world_instance *world, ent_call *call );
 
 #endif /* WORLD_GATE_H */
index 01fa9cc6da2a6796b11f2d5a9bf4e048b69e3112..afd4986826e0a63f856e39950fad077c3ca36bf4 100644 (file)
@@ -60,6 +60,8 @@ VG_STATIC void world_load_mdl( const char *path )
    mdl_load_array( meta, &world->ent_swspreview,"ent_swspreview", heap );
    mdl_load_array( meta, &world->ent_ccmd,      "ent_ccmd",       heap );
    mdl_load_array( meta, &world->ent_challenge, "ent_challenge",  heap );
+   mdl_load_array( meta, &world->ent_unlock,    "ent_unlock",     heap );
+   mdl_load_array( meta, &world->ent_relay,     "ent_relay",      heap );
 
    mdl_array_ptr infos;
    mdl_load_array( meta, &infos, "ent_worldinfo", vg_mem.scratch );
index a7ff25ccf3639c1eff192e104e13064c30139937..d59d4e1b163eb1241d280061c958b669b386e384 100644 (file)
@@ -535,6 +535,8 @@ VG_STATIC void render_world_gates( world_instance *world, camera *cam,
    
    world->rendering_gate = gate;
    if( gate ){
+      if( gate->flags & k_ent_gate_locked ) return;
+
       if( gate->flags & k_ent_gate_nonlocal ){
          if( world_loader.state != k_world_loader_none ){
             world->rendering_gate = NULL;