a different way of doing non-local links
authorhgn <hgodden00@gmail.com>
Tue, 11 Feb 2025 16:45:01 +0000 (16:45 +0000)
committerhgn <hgodden00@gmail.com>
Tue, 11 Feb 2025 16:45:01 +0000 (16:45 +0000)
15 files changed:
content_skaterift/maps/dev_heaven/main.mdl
skaterift_blender/sr_main.py
skaterift_blender/sr_mdl.py
src/entity.h
src/player.c
src/player.h
src/player_skate.c
src/player_skate.h
src/player_walk.c
src/player_walk.h
src/world.h
src/world_gate.c
src/world_load.c
src/world_render.c
src/world_routes.c

index 6aadd05397506cb594eb2261b3ac9e26b02bc616..2adad981918fe04d61a849b16bdfa286b61e54c8 100644 (file)
Binary files a/content_skaterift/maps/dev_heaven/main.mdl and b/content_skaterift/maps/dev_heaven/main.mdl differ
index b76873d85d3355d2d0a0a3456574d072a3c70d3c..bea93ef9b52902431e524d49096d5076775f0ae4 100644 (file)
@@ -1171,7 +1171,8 @@ class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
    target: bpy.props.PointerProperty( \
                type=bpy.types.Object, name="destination", \
                poll=lambda self,obj: sr_filter_ent_type(obj,['ent_gate']))
-
+   
+   nonlocal_world: bpy.props.StringProperty()
    key: bpy.props.StringProperty()
    tipo: bpy.props.EnumProperty(items=(('default', 'Default', ""),
                                        ('nonlocal', 'Non-Local', "")))
@@ -1179,6 +1180,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 )
+   no_linkback: bpy.props.BoolProperty( name="No Linkback", default=False )
 
    @staticmethod
    def sr_inspector( layout, data ):
@@ -1186,13 +1188,23 @@ class SR_OBJECT_ENT_GATE(bpy.types.PropertyGroup):
       box = layout.box()
       box.prop( data[0], 'tipo', text="subtype" )
 
-      if   data[0].tipo == 'default':  box.prop( data[0], 'target' )
-      elif data[0].tipo == 'nonlocal': box.prop( data[0], 'key' )
+      if   data[0].tipo == 'default':
+      #{
+         box.prop( data[0], 'target' )
+      #}
+      elif data[0].tipo == 'nonlocal': 
+      #{
+         box.prop( data[0], 'nonlocal_world' )
+         box.prop( data[0], 'key' )
+      #}
 
       flags = box.box()
       flags.prop( data[0], 'flip' )
       flags.prop( data[0], 'custom' )
       flags.prop( data[0], 'locked' )
+
+      if data[0].tipo == 'nonlocal': 
+         flags.prop( data[0], 'no_linkback' )
    #}
 #}
 
index d67e59566a62b0d3246d3fba13f892fdd770b56f..c086bf1b9d6bdf9df800416d4b27e5f1b894e97d 100644 (file)
@@ -630,8 +630,9 @@ def _mdl_compiler_compile_entities():
                   flags |= 0x0001
                #}
             #}
-            elif obj_data.tipo == 'nonlocal':#{
-               gate.target = 0
+            elif obj_data.tipo == 'nonlocal':
+            #{
+               gate.target = _af_pack_string(obj_data.nonlocal_world)
                gate.key = _af_pack_string(obj_data.key)
                flags |= 0x0002
             #}
@@ -643,6 +644,7 @@ def _mdl_compiler_compile_entities():
                      mdl_compile_mesh_internal( obj )
             #}
             if obj_data.locked: flags |= 0x0010
+            if obj_data.no_linkback: flags |= 0x0040
             gate.flags = flags
             
             gate.dimensions[0] = mesh_data.dimensions[0]
index aec54204688297e3db7c4a3711fdd917cf2b04a9..1f2ada44185a1070ea5eb4e5a804ec0d3cf3c260 100644 (file)
@@ -168,7 +168,8 @@ enum gate_type{
 #endif
 
 /* v102+ */
-enum ent_gate_flag{
+enum ent_gate_flag
+{
    k_ent_gate_linked      = 0x1, /* this is a working portal */
    k_ent_gate_nonlocal    = 0x2, /* use the key string to link this portal.
                                        NOTE: if set, it adds the flip flag. */
@@ -177,6 +178,7 @@ enum ent_gate_flag{
    k_ent_gate_locked      = 0x10,/* has to be unlocked to be useful */
 
    k_ent_gate_clean_pass  = 0x20,/* player didn't rewind while getting here */
+   k_ent_gate_no_linkback = 0x40,/* NONLOCAL Recievers are not allowed to linkback through this gate */
 };
 
 struct ent_gate{
@@ -191,9 +193,10 @@ struct ent_gate{
 
    /* runtime */
    m4x3f to_world, transport;
-
-   union{
+   union
+   {
       u32 timing_version;
+      u32 addon_reg;
 
       struct{
          u8 ref_count;
index 55edf728ad52292db628e9393bf4ff026fa27001..7daea1151e873261a6b65ad7fcd08f084ced11cb 100644 (file)
@@ -166,46 +166,71 @@ void player__post_update(void)
    SDL_AtomicUnlock( &air_audio_data.sl );
 }
 
+void player__transport( m4x3f transport )
+{
+   struct player_cam_controller *cc = &localplayer.cam_control;
+   m4x3_mulv( transport, cc->tpv_lpf, cc->tpv_lpf );
+   m3x3_mulv( transport, cc->cam_velocity_smooth, cc->cam_velocity_smooth );
+   m4x3_mulv( transport, localplayer.cam.pos, localplayer.cam.pos );
+   
+   v3f v0;
+   v3_angles_vector( localplayer.angles, v0 );
+   m3x3_mulv( transport, v0, v0 );
+   v3_angles( v0, localplayer.angles );
+
+   if( player_subsystems[ localplayer.subsystem ]->transport )
+      player_subsystems[ localplayer.subsystem ]->transport( transport );
+   else
+      vg_warn( "Player passed gate, but subsystem(%d) doesn't have a transport function.\n", localplayer.subsystem );
+}
+
 /*
  * Applies gate transport to a player_interface
  */
 void player__pass_gate( u32 id )
 {
-   world_instance *world = &_world.main;
-   skaterift_record_frame( &player_replay.local, 1 );
+   audio_lock();
+   audio_oneshot( &audio_gate_pass, 1.0f, 0.0f );
+   audio_unlock();
 
-   /* update boundary hash (network animation) */
-   u16 index = mdl_entity_id_id(id) & ~NETMSG_BOUNDARY_MASK;
-   localplayer.boundary_hash ^= NETMSG_GATE_BOUNDARY_BIT;
-   localplayer.boundary_hash &= ~NETMSG_BOUNDARY_MASK;
-   localplayer.boundary_hash |= index;
-   
+   world_instance *world = &_world.main;
    ent_gate *gate = af_arritm( &world->ent_gate, mdl_entity_id_id(id) );
-   world_routes_fracture( world, gate, localplayer.rb.co, localplayer.rb.v );
 
-   localplayer.gate_waiting = gate;
-   localplayer.deferred_frame_record = 1;
+   if( gate->flags & k_ent_gate_nonlocal )
+   {
+      _world.travelled_through_nonlocal_gate = 1;
+      _world.copy_of_nonlocal_sender = *gate;
+      _world.copy_of_nonlocal_sender.target = 0;
+      _world.copy_of_nonlocal_sender.key = 0;
+      _world.copy_of_nonlocal_sender.submesh_start = 0;
+      _world.copy_of_nonlocal_sender.submesh_count = 0;
+
+      vg_strncpy( af_str( &world->meta.af, gate->key ), 
+                  _world.nonlocal_destination_key, 32, 
+                  k_strncpy_overflow_fatal );
+
+      addon_reg *reg = get_addon_from_index( k_addon_type_world, gate->addon_reg, 0 );
+      skaterift_switch_world_start( reg );
+      return;
+   }
+   else
+   {
+      player__transport( gate->transport );
 
-   struct player_cam_controller *cc = &localplayer.cam_control;
-   m4x3_mulv( gate->transport, cc->tpv_lpf, cc->tpv_lpf );
-   m3x3_mulv( gate->transport, cc->cam_velocity_smooth, 
-                               cc->cam_velocity_smooth );
+      skaterift_record_frame( &player_replay.local, 1 );
 
-   m4x3_mulv( gate->transport, localplayer.cam.pos, localplayer.cam.pos );
+      /* update boundary hash (network animation) */
+      u16 index = mdl_entity_id_id(id) & ~NETMSG_BOUNDARY_MASK;
+      localplayer.boundary_hash ^= NETMSG_GATE_BOUNDARY_BIT;
+      localplayer.boundary_hash &= ~NETMSG_BOUNDARY_MASK;
+      localplayer.boundary_hash |= index;
 
-   if( gate->flags & k_ent_gate_nonlocal )
-      vg_error( "Nonlocal gates are deprecated!\n" );
-   else 
-      world_routes_activate_entry_gate( world, gate );
-   
-   v3f v0;
-   v3_angles_vector( localplayer.angles, v0 );
-   m3x3_mulv( gate->transport, v0, v0 );
-   v3_angles( v0, localplayer.angles );
+      localplayer.gate_waiting = gate;
+      localplayer.deferred_frame_record = 1;
 
-   audio_lock();
-   audio_oneshot( &audio_gate_pass, 1.0f, 0.0f );
-   audio_unlock();
+      world_routes_fracture( world, gate, localplayer.rb.co, localplayer.rb.v );
+      world_routes_activate_entry_gate( world, gate );
+   }
 }
 
 void player_apply_transport_to_cam( m4x3f transport )
index c7ded0f69e5a40feed1c8d2f0d4d24ac494c60db..6b10f6586fc0c9398ec50f8d5fa69539fa3c1029 100644 (file)
@@ -47,6 +47,7 @@ struct player_subsystem_interface
 
    void(*sfx_comp)(void *animator);
    void(*sfx_kill)(void);
+   void(*transport)( m4x3f transport );
 
    void *animator_data;
    u32 animator_size;
@@ -179,6 +180,7 @@ void player__update(void);
 void player__post_update(void);
 
 void player__pass_gate( u32 id );
+void player__transport( m4x3f transport );
 void player__im_gui( ui_context *ctx );
 void player__setpos( v3f pos );
 void player__spawn( ent_spawn *rp );
index e408b5f98fa38c6a1b7f9495fc0095fb7e40fe65..c75b7cc16122189fba0112459bcb985a8198cc60 100644 (file)
@@ -23,6 +23,7 @@ struct player_subsystem_interface player_subsystem_skate =
    .bind = player__skate_bind,
    .pre_update = player__skate_pre_update,
    .update = player__skate_update,
+   .transport = player__skate_transport,
    .post_update = player__skate_post_update,
    .im_gui = player__skate_im_gui,
    .animate = player__skate_animate,
@@ -2222,7 +2223,8 @@ static enum skate_activity skate_availible_grind(void){
    return new_activity;
 }
 
-void player__skate_update(void){
+void player__skate_update(void)
+{
    struct player_skate_state *state = &player_skate.state;
    world_instance *world = &_world.main;
 
@@ -2709,26 +2711,9 @@ begin_collision:;
 
    u32 id = world_intersect_gates( world, localplayer.rb.co, state->prev_pos );
 
-   if( id ){
+   if( id )
+   {
       ent_gate *gate = af_arritm( &world->ent_gate, mdl_entity_id_id(id) );
-
-      m4x3_mulv( gate->transport, localplayer.rb.co, localplayer.rb.co );
-      m3x3_mulv( gate->transport, localplayer.rb.v,  localplayer.rb.v );
-      m4x3_mulv( gate->transport, state->cog,   state->cog );
-      m3x3_mulv( gate->transport, state->cog_v, state->cog_v );
-      m3x3_mulv( gate->transport, state->throw_v, state->throw_v );
-      m3x3_mulv( gate->transport, state->head_position,
-                                  state->head_position );
-      m3x3_mulv( gate->transport, state->up_dir, state->up_dir );
-
-      v4f transport_rotation;
-      m3x3_q( gate->transport, transport_rotation );
-      q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
-      q_mul( transport_rotation, state->smoothed_rotation,
-                                 state->smoothed_rotation );
-      q_normalize( localplayer.rb.q );
-      q_normalize( state->smoothed_rotation );
-      rb_update_matrices( &localplayer.rb );
       player__pass_gate( id );
    }
 
@@ -2791,6 +2776,26 @@ begin_collision:;
    }
 }
 
+void player__skate_transport( m4x3f transport )
+{
+   struct player_skate_state *state = &player_skate.state;
+   m4x3_mulv( transport, localplayer.rb.co, localplayer.rb.co );
+   m3x3_mulv( transport, localplayer.rb.v,  localplayer.rb.v );
+   m4x3_mulv( transport, state->cog,   state->cog );
+   m3x3_mulv( transport, state->cog_v, state->cog_v );
+   m3x3_mulv( transport, state->throw_v, state->throw_v );
+   m3x3_mulv( transport, state->head_position, state->head_position );
+   m3x3_mulv( transport, state->up_dir, state->up_dir );
+
+   v4f transport_rotation;
+   m3x3_q( transport, transport_rotation );
+   q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
+   q_mul( transport_rotation, state->smoothed_rotation, state->smoothed_rotation );
+   q_normalize( localplayer.rb.q );
+   q_normalize( state->smoothed_rotation );
+   rb_update_matrices( &localplayer.rb );
+}
+
 void player__skate_im_gui( ui_context *ctx )
 {
    struct player_skate_state *state = &player_skate.state;
index c234bdc369340fb2083ca570da6c9ff9bf652bca..338658ce8d1f6296410850eada168f2e49089889 100644 (file)
@@ -307,6 +307,7 @@ static void player__skate_register(void)
 void player__skate_bind         (void);
 void player__skate_pre_update   (void);
 void player__skate_update       (void);
+void player__skate_transport( m4x3f transport );
 void player__skate_post_update  (void);
 void player__skate_im_gui       ( ui_context *ctx );
 void player__skate_animate      (void);
index 2c78bb8e604c7632d3f2ddef6aa69b6631f03bd4..4a19079546add8b5c124d7797fa7b825e7361a27 100644 (file)
@@ -16,6 +16,7 @@ struct player_subsystem_interface player_subsystem_walk =
    .bind = player__walk_bind,
    .pre_update = player__walk_pre_update,
    .update = player__walk_update,
+   .transport = player__walk_transport,
    .post_update = player__walk_post_update,
    .im_gui = player__walk_im_gui,
    .animate = player__walk_animate,
@@ -671,16 +672,9 @@ static void player_walk_update_generic(void){
    }
 
    u32 id = world_intersect_gates(world, localplayer.rb.co, w->state.prev_pos);
-   if( id ){
+   if( id )
+   {
       ent_gate *gate = af_arritm( &world->ent_gate, mdl_entity_id_id(id) );
-      m4x3_mulv( gate->transport, localplayer.rb.co, localplayer.rb.co );
-      m3x3_mulv( gate->transport, localplayer.rb.v,  localplayer.rb.v );
-
-      v4f transport_rotation;
-      m3x3_q( gate->transport, transport_rotation );
-      q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
-      q_normalize( localplayer.rb.q );
-      rb_update_matrices( &localplayer.rb );
       player__pass_gate( id );
    }
    rb_update_matrices( &localplayer.rb );
@@ -696,6 +690,19 @@ static void player_walk_update_generic(void){
                             k_runspeed );
 }
 
+void player__walk_transport( m4x3f transport )
+{
+   m4x3_mulv( transport, localplayer.rb.co, localplayer.rb.co );
+   m3x3_mulv( transport, localplayer.rb.v,  localplayer.rb.v );
+
+   v4f transport_rotation;
+   m3x3_q( transport, transport_rotation );
+   q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
+   q_normalize( localplayer.rb.q );
+   rb_update_matrices( &localplayer.rb );
+}
+
+
 void player__walk_post_update(void){
    struct player_walk *w = &player_walk;
 
index 56b8914369725cd9d08c3023f83266d6bb6953bf..b740a8ca40cb3869a5f39bebd771f6db074adcb9 100644 (file)
@@ -100,6 +100,7 @@ static void player__walk_register(void)
 
 void player__walk_pre_update  (void);
 void player__walk_update      (void);
+void player__walk_transport   ( m4x3f transport );
 void player__walk_post_update (void);
 void player__walk_animate     (void);
 void player__walk_pose        (void *animator, player_pose *pose);
index 5576fb6ce429dc9d3498e8f54df5758e8fa39f42..662a3dd7da10bf4415fc07fe3174ecf10ccee204 100644 (file)
@@ -235,6 +235,11 @@ struct world_static
    world_instance main;
    addon_reg *default_hub_addon, *switch_to_addon;
 
+   addon_reg *previous_world_addon;
+   char nonlocal_destination_key[32];
+   bool travelled_through_nonlocal_gate;
+   ent_gate copy_of_nonlocal_sender;
+
    enum world_loader_state
    {
       k_world_loader_none,
index fb13999f50c5623bff1278347d5f655a97ed0389..6ab4c7b0b4dd8ab58877cf1a989dd09f491551a1 100644 (file)
@@ -325,10 +325,95 @@ void world_link_gates_async( void *payload, u32 size )
    VG_ASSERT( vg_thread_purpose() == k_thread_purpose_main );
 
    world_instance *world = payload;
+   bool found_nonlocal_reciever = 0;
+
    for( u32 j=0; j<af_arrcount(&world->ent_gate); j ++ )
    {
       ent_gate *gate = af_arritm( &world->ent_gate, j );
       gate_transform_update( gate );
+
+      if( gate->flags & k_ent_gate_nonlocal )
+      {
+         if( gate->target )
+         {
+            const char *dest_world = af_str( &world->meta.af, gate->target );
+            
+            addon_alias q;
+            addon_uid_to_alias( dest_world, &q );
+
+            gate->addon_reg = addon_match( &q );
+            if( gate->addon_reg != 0xffffffff )
+            {
+               gate->flags |= k_ent_gate_linked;
+               vg_info( "Linked non-local gate to addon #%u\n", gate->addon_reg );
+            }
+            else
+            {
+               vg_error( "Reference in non-local gate to other world '%s' was not found.\n", dest_world );
+            }
+         }
+         else
+         {
+            if( _world.travelled_through_nonlocal_gate )
+            {
+               const char *key = af_str( &world->meta.af, gate->key );
+               if( vg_str_eq( key, _world.nonlocal_destination_key ) )
+               {
+                  if( found_nonlocal_reciever )
+                     vg_warn( "There are multiple nonlocal gates that share the same key in this world (%s)\n", key );
+                  else
+                  {
+                     found_nonlocal_reciever = 1;
+
+                     ent_gate *gate2 = &_world.copy_of_nonlocal_sender;
+
+                     v3_copy( gate->co[0], gate2->co[1] );
+                     v3_copy( gate2->co[0], gate->co[1] );
+                     v4_copy( gate->q[0], gate2->q[1] );
+                     v4_copy( gate2->q[0], gate->q[1] );
+
+                     if( world->meta.version < 102 )
+                     {
+                        /* LEGACY BEHAVIOUR: v101
+                         *   this would flip both the client worlds portal's entrance and
+                         *   exit. effectively the clients portal would be the opposite
+                         *   to the hub worlds one. new behaviour is to just flip the
+                         *   destinations so the rules are consistent in each world.
+                         */
+                        v4f qflip;
+                        q_axis_angle( qflip, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
+                        q_mul( gate->q[0], qflip, gate->q[0] );
+                        q_mul( gate->q[1], qflip, gate->q[1] );
+                     }
+
+                     gate_transform_update( gate );
+                     gate_transform_update( gate2 );
+                     
+                     if( !(_world.copy_of_nonlocal_sender.flags & k_ent_gate_no_linkback) )
+                     {
+                        gate->addon_reg = get_index_from_addon( k_addon_type_world, _world.previous_world_addon );
+
+                        if( gate->addon_reg != 0xffffffff )
+                        {
+                           gate->flags |= k_ent_gate_linked;
+                           vg_info( "Linked non-local gate to addon #%u\n", gate->addon_reg );
+                        }
+                        else
+                        {
+                           vg_error( "Error while linking to previous world.\n" );
+                        }
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+
+   if( _world.travelled_through_nonlocal_gate && !found_nonlocal_reciever )
+   {
+      vg_error( "Player travelled through non-local gate, but no reciever gate was found. Player will default to start spawn or world save file.\n" );
+      _world.travelled_through_nonlocal_gate = 0;
    }
 }
 
index 9abcd14896bfc2c4853c0eb53e32fa8ece942a72..2cb84198ce18d8243e940c48e1c820ade32f78bf 100644 (file)
@@ -284,17 +284,32 @@ void async_start_player_from_worldsave( void *payload, u32 size )
 
    /* start entities in the world */
    world_entity_start( data->instance, &sav );
-   world_default_spawn_pos( data->instance, localplayer.rb.co );
 
    /* start player in the world */
-   vg_msg_init( &sav, data->save.buf, data->save.len );
-   vg_msg player_frame = sav;
-   if( vg_msg_seekframe( &player_frame, "player" ) )
+   if( _world.travelled_through_nonlocal_gate )
    {
-      vg_msg_getkvvecf( &player_frame, "position", k_vg_msg_v3f, 
-                        localplayer.rb.co, NULL );
+      player__transport( _world.copy_of_nonlocal_sender.transport );
+      _world.travelled_through_nonlocal_gate = 0;
+      vg_success( "Successfuly consumed linked non-local gates composite transport matrix.\n" );
+   }
+   else
+   {
+      bool restored_player_position = 0;
+
+      vg_msg_init( &sav, data->save.buf, data->save.len );
+      vg_msg player_frame = sav;
+      if( vg_msg_seekframe( &player_frame, "player" ) )
+      {
+         if( vg_msg_getkvvecf( &player_frame, "position", k_vg_msg_v3f, 
+                               localplayer.rb.co, NULL ) )
+            restored_player_position = 1;
+      }
+
+      if( !restored_player_position )
+         world_default_spawn_pos( data->instance, localplayer.rb.co );
+
+      player__reset();
    }
-   player__reset();
 }
 
 void load_player_from_world_savedata_thread( void *_args )
@@ -424,6 +439,9 @@ void skaterift_switch_world_start( addon_reg *reg )
          return;
       }
    }
+   
+   if( reg != _world.main.addon )
+      _world.previous_world_addon = _world.main.addon;
 
    g_client.unreadyness ++;
    _world.loader_state = k_world_loader_saving_current;
index f30e97a799a82624b7bc9b50b7c9653090965371..bf71a7c241b86cd728ac32d5b76169dfca782d7d 100644 (file)
@@ -900,6 +900,16 @@ void render_world_gates( world_instance *world, vg_camera *cam )
 
       if( gate->flags & k_ent_gate_nonlocal )
       {
+         world->rendering_gate = NULL; /* this is for jump prediction only. */
+
+         if( gate->flags & k_ent_gate_linked )
+         {
+
+         }
+         else
+         {
+            render_gate_unlinked( world, gate, cam );
+         }
       }
       else
          render_gate( world, world, gate, cam );
index d677a704c304158c6ef9831bb9cd29a5ac0e102a..68686b3ac3d808f66b15c8f50b0356b15096dd6b 100644 (file)
@@ -31,10 +31,15 @@ void world_routes_clear( world_instance *world )
       route->active_checkpoint = 0xffff;
    }
 
-   for( u32 i=0; i<af_arrcount( &world->ent_gate ); i++ ){
+   for( u32 i=0; i<af_arrcount( &world->ent_gate ); i++ )
+   {
       ent_gate *rg = af_arritm( &world->ent_gate, i );
-      rg->timing_version = 0;
-      rg->timing_time = 0.0;
+
+      if( !(rg->flags & k_ent_gate_nonlocal) )
+      {
+         rg->timing_version = 0;
+         rg->timing_time = 0.0;
+      }
    }
 
    _world.current_run_version += 4;
@@ -489,13 +494,19 @@ void world_gen_routes_generate( world_instance *world )
                                                   &world->mesh_route_lines,
                                                   200000, 300000 );
 
-   for( u32 i=0; i<af_arrcount(&world->ent_gate); i++ ){
+   for( u32 i=0; i<af_arrcount(&world->ent_gate); i++ )
+   {
       ent_gate *gate = af_arritm( &world->ent_gate, i );
-      gate->ref_count = 0;
-      gate->route_count = 0;
+
+      if( !(gate->flags & k_ent_gate_nonlocal) )
+      {
+         gate->ref_count = 0;
+         gate->route_count = 0;
+      }
    }
 
-   for( u32 i=0; i<af_arrcount(&world->ent_route_node); i++ ){
+   for( u32 i=0; i<af_arrcount(&world->ent_route_node); i++ )
+   {
       ent_route_node *rn = af_arritm( &world->ent_route_node, i );
       rn->ref_count = 0;
       rn->ref_total = 0;