snake5

SGScript Game Framework

Dec 17th, 2013
444
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  
  2. /*
  3.     Game Framework
  4. */
  5.  
  6. include "io", "string", "fmt", "math";
  7.  
  8. global BIGNUM = 1e+32;
  9.  
  10.  
  11. /* Utility functions */
  12. function sign( x ){ return if( x == 0, 0, if( x < 0, -1, 1 ) ); }
  13. function normalize_angle( x ){ x = x % ( M_PI * 2 ); return if( x < 0, x + M_PI*2, x ); }
  14. function saturate( x ){ return if( x < 0, 0, if( x > 1, 1, x ) ); }
  15. function smoothstep( x ){ return x * x * ( 3 - 2 * x ); }
  16. function smoothlerp_oneway( t, a, b ){ if( b == a ) return 1.0; return smoothstep( saturate( ( t - a ) / ( b - a ) ) ); }
  17. function smoothlerp_range( t, a, b, c, d ){ return smoothlerp_oneway( t, a, b ) * smoothlerp_oneway( t, d, c ); }
  18.  
  19.  
  20. global GameFramework =
  21. {
  22.     Debug = false,
  23. };
  24.  
  25.  
  26. /*
  27.     Bigger utility functions
  28. */
  29.  
  30. function Config_Load( file, defaults )
  31. {
  32.     out = null;
  33.     pcall(function() use(file, defaults, out)
  34.     {
  35.         data = io_file_read( file );
  36.         if( !data )
  37.             return "failed to read the file";
  38.        
  39.         data = string_replace( data, ["\r\n","\r"], "\n" );
  40.        
  41.         entries = string_explode( data, "\n" );
  42.         out = clone( defaults );
  43.         foreach( eid, entry : entries )
  44.         {
  45.             keyval = string_explode( entry, "=" );
  46.             if( keyval.size != 2 )
  47.                 return "error in format, entry #"$(eid+1);
  48.                
  49.             key = string_trim( keyval[0] );
  50.             value = string_trim( keyval[1] );
  51.            
  52.             out[ key ] = value;
  53.         }
  54.     });
  55.     return out;
  56. }
  57.  
  58. /*
  59.     GameLoop
  60.     - create( fn, mindt, maxdt )
  61.     + !call( func )
  62. */
  63.  
  64. global GameLoop = {};
  65.  
  66. function GameLoop.create( fn, mindt, maxdt )
  67. {
  68.     lasttime = ftime();
  69.     mindt ||= 0.0;
  70.     maxdt ||= 1.0 / 15.0;
  71.     stickydelta = 0.016;
  72.    
  73.     return function() use( fn, mindt, maxdt, lasttime, stickydelta )
  74.     {
  75.         curtime = ftime();
  76.         delta = curtime - lasttime;
  77.         lasttime = curtime;
  78.         if( delta > 1.0/15.0 )
  79.             delta = 1.0/15.0;
  80.        
  81.         if( GameFramework.Debug && abs( stickydelta - delta ) > 1.0 / 120.0 )
  82.             println( "GFW DEBUG INFO: major deviation in delta time: "${ stickydelta = stickydelta, delta = delta } );
  83.         stickydelta = stickydelta * 0.95 + delta * 0.05;
  84.        
  85.         fn( stickydelta, lasttime );
  86.        
  87.         if( mindt > 0 )
  88.         {
  89.             sleeptime = mindt - ( ftime() - lasttime );
  90.             if( sleeptime > 0 )
  91.         ;//     sleep( sleeptime * 1000 );
  92.         }
  93.     };
  94. }
  95.  
  96.  
  97. /*
  98.     InputKeys
  99.     - create()
  100.     > clear()
  101.     > add_range( from, count )
  102.     > add_list( list )
  103.     > advance()
  104.     > set( key, isdown )
  105.     > get( key )
  106.     > getprev( key )
  107.     > is_pressed( key )
  108.     > is_released( key )
  109. */
  110.  
  111. global InputKeys =
  112. {
  113.     keys = {},
  114.     prevkeys = {},
  115. };
  116.  
  117. function InputKeys.create()
  118. {
  119.     data =
  120.     {
  121.         keys = {},
  122.         prevkeys = {},
  123.     };
  124.     return class( data, InputKeys );
  125. }
  126.  
  127. function InputKeys.clear()
  128. {
  129.     this.keys = {};
  130.     this.prevkeys = {};
  131. }
  132.  
  133. function InputKeys.add_range( from, count )
  134. {
  135.     for( i = from; i < from + count; ++i )
  136.     {
  137.         this.keys[ i ] = 0.0;
  138.         this.prevkeys[ i ] = 0.0;
  139.     }
  140. }
  141.  
  142. function InputKeys.add_list( list )
  143. {
  144.     foreach( i : list )
  145.     {
  146.         this.keys[ i ] = 0.0;
  147.         this.prevkeys[ i ] = 0.0;
  148.     }
  149. }
  150.  
  151. function InputKeys.advance()
  152. {
  153.     foreach( i, v : this.keys )
  154.         this.prevkeys[ i ] = v;
  155. }
  156.  
  157. function InputKeys.set( key, amount ){ this.keys[ key ] = amount; }
  158. function InputKeys.get( key ){ return this.keys[ key ]; }
  159. function InputKeys.getprev( key ){ return this.keys[ key ]; }
  160. function InputKeys.is_pressed( key ){ return this.keys[ key ] && !this.prevkeys[ key ]; }
  161. function InputKeys.is_released( key ){ return !this.keys[ key ] && this.prevkeys[ key ]; }
  162.  
  163.  
  164. /*
  165.     ActionMap
  166.     - create()
  167.     + add_action( action )
  168.     + remove_action( action )
  169.     + advance()
  170.     + set( action, amount )
  171.     + get( action )
  172.     + getprev( action )
  173.     + is_pressed( action )
  174.     + is_released( action )
  175. */
  176.  
  177. global ActionMap = {};
  178.  
  179. function ActionMap.create()
  180. {
  181.     data =
  182.     {
  183.         actions = {},
  184.         prevactions = {},
  185.         keys_to_actions = {},
  186.     };
  187.     return class( data, ActionMap );
  188. }
  189.  
  190. function ActionMap.add_action( action )
  191. {
  192.     this.actions[ action ] = this.prevactions[ action ] = 0.0;
  193. }
  194.  
  195. function ActionMap.remove_action( action )
  196. {
  197.     unset( this.actions, action );
  198.     unset( this.prevactions, action );
  199. }
  200.  
  201. function ActionMap.has_binding( action ){ return get_values( this.keys_to_actions ).find( action ) !== null; }
  202. function ActionMap.add_binding( action, key ){ this.keys_to_actions[ key ] = action; }
  203. function ActionMap.add_default_binding( action, key ){ if( !this.has_binding( action ) ) this.add_binding( action, key ); }
  204. function ActionMap.remove_binding( action, key ){ if( this.keys_to_actions[ key ] == action ) unset( this.keys_to_actions, key ); }
  205. function ActionMap.remove_all_bindings( action )
  206. {
  207.     foreach( k, a : this.keys_to_actions )
  208.     {
  209.         if( a == action )
  210.         {
  211.             this.keys_to_actions[ k ] = null;
  212.             break;
  213.         }
  214.     }
  215.     this.keys_to_actions = dict_filter( this.keys_to_actions );
  216. }
  217.  
  218. function ActionMap.advance()
  219. {
  220.     foreach( i, v : this.actions )
  221.         this.prevactions[ i ] = v;
  222. }
  223.  
  224. function ActionMap.set( key, amount )
  225. {
  226.     if( isset( this.keys_to_actions, key ) )
  227.         this.actions[ this.keys_to_actions[ key ] ] = toreal( amount );
  228. }
  229.  
  230. function ActionMap.get( action ){ return this.actions[ action ]; }
  231. function ActionMap.getprev( action ){ return this.prevactions[ action ]; }
  232. function ActionMap.is_pressed( action ){ return this.actions[ action ] && !this.prevactions[ action ]; }
  233. function ActionMap.is_released( action ){ return !this.actions[ action ] && this.prevactions[ action ]; }
  234.  
  235.  
  236. /*
  237.     EntityBuilder
  238.     - clear()
  239.     - scan( folder )
  240.     - build( type, params )
  241. */
  242.  
  243. global EntityBuilder =
  244. {
  245.     entity_types = {},
  246. };
  247.  
  248. function EntityBuilder._load_error( file, type, error )
  249. {
  250.     ERROR( string_format( "ENTITY BUILDER: error while loading file {1}, type {2} - {3}", file, type, error ) );
  251. }
  252.  
  253. function EntityBuilder._build_error( type, error )
  254. {
  255.     ERROR( string_format( "ENTITY BUILDER: error while creating entity of type {1} - {2}", type, error ) );
  256. }
  257.  
  258. function EntityBuilder.clear()
  259. {
  260.     this.entity_types = {};
  261. }
  262.  
  263. function EntityBuilder._prepare( file, type, data )
  264. {
  265.     if( !isset( data, "create" ) ) this._load_error( file, type, "missing 'create' function" );
  266.     if( !is_callable( data.create ) ) this._load_error( file, type, "'create' not callable" );
  267.     if( !isset( data, "defaults" ) )
  268.         data.defaults = {};
  269. }
  270.  
  271. function EntityBuilder.scan( folder )
  272. {
  273.     foreach( is_real, item : io_dir( folder ) )
  274.     {
  275.         if( !is_real )
  276.             continue;
  277.         fullpath = folder $ "/" $ item;
  278.         new_ents = eval_file( fullpath );
  279.         foreach( new_ent_name, new_ent_data : new_ents )
  280.         {
  281.             this._prepare( fullpath, new_ent_name, new_ent_data );
  282.         }
  283.         this.entity_types = get_merged( this.entity_types, new_ents );
  284.     }
  285. }
  286.  
  287. function EntityBuilder.build( type, params )
  288. {
  289.     if( !isset( this.entity_types, type ) )
  290.         return this._build_error( type, "type is not loaded" );
  291.    
  292.     info = this.entity_types[ type ];
  293.     params = get_merged( info.defaults, params );
  294.     return info.create( params );
  295. }
  296.  
  297.  
  298. /*
  299.     GameWorld
  300.     - create()
  301.     + add_entity( entity )
  302.     + remove_entity( id )
  303.     + remove_all_entities()
  304.     + get_entity_by_id( id )
  305.     + tick( delta )
  306. */
  307.  
  308. global GameWorld = {};
  309.  
  310. function GameWorld.create()
  311. {
  312.     data =
  313.     {
  314.         // DATA
  315.         entities = {},
  316.         in_tick = false,
  317.        
  318.         // CACHE
  319.         z_order = [],
  320.         z_indices = [],
  321.         to_add = [],
  322.         to_remove = [],
  323.        
  324.         // INDEXING
  325.         auto_id = 1,
  326.        
  327.         // UTILITY
  328.         efn = function(){},
  329.     };
  330.     return class( data, GameWorld );
  331. }
  332.  
  333. function GameWorld._real_add_entity( entity )
  334. {
  335.     entity.id = this.auto_id++;
  336.     if( !isset( entity, "z_index" ) )
  337.         entity.z_index = 0;
  338.     if( !isset( entity, "tick" ) ) entity.tick = this.efn;
  339.     if( !isset( entity, "draw" ) ) entity.draw = this.efn;
  340.     if( !isset( entity, "draw_ui" ) ) entity.draw_ui = this.efn;
  341.    
  342.     this.entities[ entity.id ] = entity;
  343.     if( isset( entity, "added" ) ) entity.added( GameWorld );
  344. }
  345.  
  346. function GameWorld._real_remove_entity( eid )
  347. {
  348.     if( isset( this.entities, eid ) )
  349.     {
  350.         if( isset( this.entities[ eid ], "removed" ) )
  351.             this.entities[ eid ].removed( GameWorld );
  352.         unset( this.entities, eid );
  353.     }
  354. }
  355.  
  356. function GameWorld._process_queues()
  357. {
  358.     foreach( eid : this.to_remove )
  359.         this._real_remove_entity( eid );
  360.     this.to_remove.clear();
  361.    
  362.     foreach( entity : this.to_add )
  363.         this._real_add_entity( entity );
  364.     this.to_add.clear();
  365. }
  366.  
  367. function GameWorld.add_entity( entity )
  368. {
  369.     // expect to be used inside tick
  370.     if( this.in_tick )
  371.     {
  372.         this.to_add.push( entity );
  373.     }
  374.     else
  375.     {
  376.         this._real_add_entity( entity );
  377.     }
  378. }
  379.  
  380. function GameWorld.remove_entity( eid )
  381. {
  382.     // expect to be used inside tick
  383.     // cannot search to_add because there are no IDs assigned yet
  384.     if( this.in_tick )
  385.     {
  386.         this.to_remove.push( eid );
  387.     }
  388.     else
  389.     {
  390.         this._real_remove_entity( eid );
  391.     }
  392. }
  393.  
  394. function GameWorld.remove_all_entities()
  395. {
  396.     // clone to avoid modification on iteration
  397.     foreach( eid, : clone(this.entities) )
  398.         this.remove_entity( eid );
  399. }
  400.  
  401. function GameWorld.get_entity_by_id( eid )
  402. {
  403.     if( isset( this.entities, eid ) )
  404.         return this.entities[ eid ];
  405.     return null;
  406. }
  407.  
  408. function GameWorld.tick( delta )
  409. {
  410.     this._process_queues();
  411.    
  412.     this.in_tick = true;
  413.    
  414.     this.z_indices.clear();
  415.     this.z_order.clear();
  416.     foreach( entity : this.entities )
  417.     {
  418.         if( delta > 0 )
  419.             entity.tick( delta );
  420.         this.z_indices.push( entity.z_index );
  421.         this.z_order.push( entity );
  422.     }
  423.    
  424.     this.z_order.sort_mapped( this.z_indices );
  425.    
  426.     foreach( entity : this.z_order )
  427.         entity.draw();
  428.    
  429.     this.in_tick = false;
  430. }
  431.  
  432. function GameWorld.tick_nodraw( delta )
  433. {
  434.     this._process_queues();
  435.    
  436.     if( delta > 0 )
  437.     {
  438.         this.in_tick = true;
  439.        
  440.         foreach( entity : this.entities )
  441.         {
  442.                 entity.tick( delta );
  443.         }
  444.        
  445.         this.in_tick = false;
  446.     }
  447. }
  448.  
  449. function GameWorld.send_message( eid, message, param )
  450. {
  451.     if( !isset( this.entities, eid ) )
  452.         return false;
  453.     ent = this.entities[ eid ];
  454.     if( !isset( ent, message ) )
  455.         return false;
  456.     ent.(message)( param );
  457.     return true;
  458. }
  459.  
  460. function GameWorld.draw_ui( w, h )
  461. {
  462.     foreach( entity : this.z_order )
  463.         entity.draw_ui( w, h );
  464. }
  465.  
  466.  
  467. /*
  468.     AABBHash
  469.     [aabb => min_x, min_y, max_x, max_y]
  470.     - create( cell_width, cell_height )
  471.     + cleanup()
  472.     + add( aabb, item )
  473.     + remove( aabb, item )
  474.     + find_point( x, y )
  475.     + find_range( aabb )
  476.     + for_each( aabb, func, create = false )
  477.     + point_to_cell( x, y ) => (cx,cy)
  478.     + cell_id( x, y )
  479. */
  480.  
  481. global AABBHash = {};
  482.  
  483. function AABBHash.create( cell_width, cell_height )
  484. {
  485.     if( cell_width <= 0.0 || cell_height <= 0.0 )
  486.         return ERROR( "AABBHash: cell size must be positive" );
  487.    
  488.     data =
  489.     {
  490.         cell_width = toreal( cell_width ),
  491.         cell_height = toreal( cell_height ),
  492.         hash = {},
  493.     };
  494.    
  495.     return class( data, AABBHash );
  496. }
  497.  
  498. function AABBHash.cleanup()
  499. {
  500.     // remove empty arrays from hash table
  501.     this.hash = dict_filter( this.hash );
  502. }
  503.  
  504. function AABBHash.add( min_x, min_y, max_x, max_y, item )
  505. {
  506.     this.for_each( min_x, min_y, max_x, max_y, function( cell ) use( item )
  507.     {
  508.         if( cell.find( item ) === null )
  509.             cell.push( item );
  510.     }, true );
  511. }
  512.  
  513. function AABBHash.remove( min_x, min_y, max_x, max_y, item )
  514. {
  515.     this.for_each( min_x, min_y, max_x, max_y, function( cell ) use( item )
  516.     {
  517.         cell.remove( item );
  518.     });
  519. }
  520.  
  521. function AABBHash.find_point( x, y )
  522. {
  523.     (cx,cy) = this.point_to_cell( x, y );
  524.     id = this.cell_id( cx, cy );
  525.     if( isset( this.hash, id ) )
  526.         return this.hash[ id ];
  527.     return [];
  528. }
  529.  
  530. function AABBHash.find_range( min_x, min_y, max_x, max_y )
  531. {
  532.     out = [];
  533.     this.for_each( min_x, min_y, max_x, max_y, function( cell ) use( out )
  534.     {
  535.         out = get_concat( out, cell );
  536.     });
  537.     return out.unique();
  538. }
  539.  
  540. function AABBHash.intersects( min_x, min_y, max_x, max_y )
  541. {
  542.     return min_x < this.max_x && this.min_x <= max_x
  543.         && min_y < this.max_y && this.min_y <= max_y;
  544. }
  545.  
  546. function AABBHash.for_each( min_x, min_y, max_x, max_y, func, create )
  547. {
  548.     create ||= false;
  549.     (mincx,mincy) = this.point_to_cell( min_x, min_y );
  550.     (maxcx,maxcy) = this.point_to_cell( max_x, max_y );
  551.     for( y = mincy; y <= maxcy; ++y )
  552.     {
  553.         for( x = mincx; x <= maxcx; ++x )
  554.         {
  555.             id = this.cell_id( x, y );
  556.             if( isset( this.hash, id ) )
  557.                 func( this.hash[ id ] );
  558.             else if( create )
  559.             {
  560.                 arr = [];
  561.                 func( arr );
  562.                 this.hash[ id ] = arr;
  563.             }
  564.         }
  565.     }
  566. }
  567.  
  568. function AABBHash.point_to_cell( x, y )
  569. {
  570.     x /= this.cell_width;
  571.     y /= this.cell_height;
  572.     x = floor( x );
  573.     y = floor( y );
  574.     return x, y;
  575. }
  576.  
  577. function AABBHash.cell_id( x, y )
  578. {
  579. //  return x$'|'$y;
  580.     return fmt_pack( "ll", x, y );
  581. }
  582.  
  583.  
  584. /*
  585.     AStarPathfinder
  586.     - create( links, cost_function = <1>, data_set = null )
  587.     + find_path( from_id, to_id )
  588. */
  589.  
  590. global AStarPathfinder = {};
  591.  
  592. function AStarPathfinder.create( links, cost_function, data_set )
  593. {
  594.     cost_function ||= function(){ return 1; };
  595.     node_links = {};
  596.     node_pair_links = {};
  597.     foreach( link : links )
  598.     {
  599.         n0 = link[0];
  600.         n1 = link[1];
  601.         if( !isset( node_links, n0 ) )
  602.             node_links[ n0 ] = [ link ];
  603.         else
  604.             node_links[ n0 ].push( link );
  605.         if( !isset( node_links, n1 ) )
  606.             node_links[ n1 ] = [ link ];
  607.         else
  608.             node_links[ n1 ].push( link );
  609.         node_pair_links[ fmt_pack( "ll", n0, n1 ) ] = link;
  610.         node_pair_links[ fmt_pack( "ll", n1, n0 ) ] = link;
  611.     }
  612.    
  613.     data =
  614.     {
  615.         links = links,
  616.         cost_function = cost_function,
  617.         data_set = data_set,
  618.         node_links = node_links,
  619.         node_pair_links = node_pair_links,
  620.     };
  621.    
  622.     return class( data, AStarPathfinder );
  623. }
  624.  
  625. function AStarPathfinder.find_path( from_id, to_id, from_item, to_item )
  626. {
  627.     if( from_id == to_id )
  628.         return [];
  629.    
  630.     cost_function = this.cost_function;
  631.     data_set = this.data_set;
  632.    
  633.     closed_set = {};
  634.     open_set = {}; open_set[ from_id ] = true;
  635.     came_from = {};
  636.    
  637.     g_score = {};
  638.     f_score = {};
  639.    
  640.     g_score[ from_id ] = 0;
  641.     f_score[ from_id ] = cost_function( from_item, to_item );
  642.    
  643.     while( open_set )
  644.     {
  645.         // current = item from f_score with lowest cost
  646.         lowest = BIGNUM;
  647.         current = -1;
  648.         foreach( fitem ,: open_set )
  649.         {
  650.             score = f_score[ fitem ];
  651.             if( score < lowest )
  652.             {
  653.                 current = fitem;
  654.                 lowest = score;
  655.             }
  656.         }
  657.        
  658.         // reached destination
  659.         if( current == to_id )
  660.         {
  661.             prev = to_id;
  662.             out = [];
  663.             for(;;)
  664.             {
  665.                 next = came_from[ prev ];
  666.                 out.push( this.node_pair_links[ fmt_pack( "ll", prev, next ) ] );
  667.                 if( next == from_id )
  668.                     break;
  669.                 prev = next;
  670.             }
  671.             return out.reverse();
  672.         }
  673.        
  674.         // mark node as checked
  675.         unset( open_set, current );
  676.         closed_set[ current ] = true;
  677.        
  678.         // check the neighbors
  679.         if( isset( this.node_links, current ) )
  680.         {
  681.             foreach( neighbor_obj : this.node_links[ current ] )
  682.             {
  683.                 neighbor = neighbor_obj[ neighbor_obj[0] == current ];
  684.                
  685.                 pred_g_score = g_score[ current ] + cost_function( if( current == from_id, from_item, data_set[ current ] ), data_set[ neighbor ] );
  686.                 pred_f_score = pred_g_score + cost_function( data_set[ neighbor ], to_item );
  687.                
  688.                 if( isset( closed_set, neighbor ) && pred_f_score >= f_score[ neighbor ] )
  689.                     continue;
  690.                
  691.                 if( !isset( open_set, neighbor ) || pred_f_score < f_score[ neighbor ] )
  692.                 {
  693.                     came_from[ neighbor ] = current;
  694.                     g_score[ neighbor ] = pred_g_score;
  695.                     f_score[ neighbor ] = pred_f_score;
  696.                     open_set[ neighbor ] = true;
  697.                 }
  698.             }
  699.         }
  700.     }
  701.    
  702.     return null;
  703. }
  704.  
  705.  
  706. /*
  707.     NavMeshPathfinder
  708.     - create( polygons, links )
  709.     + find_path( from_x, from_y, to_x, to_y, char_width = 0 )
  710.     - poly_to_aabb( poly ) => (min_x,min_y,max_x,max_y)
  711.     + find_point_polygon( x, y )
  712.     - cost_function( p1, p2, data(centers) )
  713.    
  714.     Notes:
  715.     - link format: [ p1, p2, p1v1, p1v2, p2v1, p2v2, point1, point2 ]
  716.     - find_path returns a list of points, each between its link line segment endpoints ..
  717.     - .. proper corner handling can be done by looking ahead, forming a triangle and checking where the path will turn ..
  718.     - .. ending point (to_x,to_y) can be added manually if necessary - after all, it is passed to the function
  719. */
  720.  
  721. global NavMeshPathfinder = {};
  722.  
  723. function NavMeshPathfinder.create( polygons, links )
  724. {
  725.     centers = [].reserve( polygons.size );
  726.     foreach( poly : polygons )
  727.     {
  728.         center = vec2(0,0);
  729.         foreach( vertex : poly )
  730.             center += vertex;
  731.         center /= poly.size;
  732.         centers.push( center );
  733.     }
  734.    
  735.     bbhash = AABBHash.create( 32, 32 );
  736.     foreach( pid, poly : polygons )
  737.     {
  738.         (min_x,min_y,max_x,max_y) = NavMeshPathfinder.poly_to_aabb( poly );
  739.         bbhash.add( min_x, min_y, max_x, max_y, pid );
  740.     }
  741.    
  742.     astar = AStarPathfinder.create( links, NavMeshPathfinder.cost_function, centers );
  743.    
  744.     data =
  745.     {
  746.         bbhash = bbhash,
  747.         pathfinder = astar,
  748.         polygons = polygons,
  749.         centers = centers,
  750.         links = links,
  751.     };
  752.    
  753.     return class( data, NavMeshPathfinder );
  754. }
  755.  
  756. function NavMeshPathfinder.find_path( from_x, from_y, to_x, to_y, char_radius )
  757. {
  758.     from_id = this.find_point_polygon( from_x, from_y );
  759.     to_id = this.find_point_polygon( to_x, to_y );
  760.    
  761.     if( from_id === null || to_id === null )
  762.         return null;
  763.    
  764.     from_v2 = vec2(from_x,from_y);
  765.     to_v2 = vec2(to_x,to_y);
  766.     asp = this.pathfinder.find_path( from_id, to_id, from_v2, to_v2 );
  767.     if( asp === null )
  768.         return null;
  769.    
  770.     char_radius ||= 0;
  771.     out = [ from_v2 ];
  772.     foreach( pl : asp )
  773.     {
  774.         out.push( ( pl[ 6 ] + pl[ 7 ] ) / 2 );
  775.     }
  776.     out.push( to_v2 );
  777.    
  778.     iters = 3;
  779.     last = out.size - 2;
  780.     while( iters --> 0 )
  781.     {
  782.         for( i = 1; i <= last; ++i )
  783.         {
  784.             pi = i - 1;
  785.            
  786.             // get link limits
  787.             lep1 = asp[pi][6];
  788.             lep2 = asp[pi][7];
  789.            
  790.             t = ( lep2 - lep1 ).normalized;
  791.             n = t.perp;
  792.            
  793.             dpn = vec2_dot( lep1, n );
  794.             dp1 = vec2_dot( lep1, t );
  795.             dp2 = vec2_dot( lep2, t );
  796.             dcp = vec2_dot( out[i], t );
  797.             dcpPrev = vec2_dot( out[i-1], t );
  798.             dcpNext = vec2_dot( out[i+1], t );
  799.             ndpPrev = vec2_dot( out[i-1], n );
  800.             ndpCurr = dpn;
  801.             ndpNext = vec2_dot( out[i+1], n );
  802.            
  803.             // average and clamp
  804.             sum = abs( ndpPrev - ndpCurr ) + abs( ndpNext - ndpCurr );
  805.             factor = if( sum < 0.01, 0.5, abs( ndpPrev - ndpCurr ) / sum );
  806.             dcp = dcpPrev * (1-factor) + dcpNext * factor;
  807.             if( dcp < dp1 ) dcp = dp1;
  808.             if( dcp > dp2 ) dcp = dp2;
  809.            
  810.             // pull out of walls if necessary
  811.             fp = t * dcp + n * dpn;
  812.             if( dcp <= dp1 + asp[pi][8].length * char_radius ) fp = asp[pi][6] + asp[pi][8] * char_radius;
  813.             if( dcp >= dp2 - asp[pi][9].length * char_radius ) fp = asp[pi][7] + asp[pi][9] * char_radius;
  814.            
  815.             // push back
  816.             out[i] = fp;
  817.         }
  818.     }
  819.     out.pop();
  820.     out.shift();
  821.    
  822.     return out;
  823. }
  824.  
  825. function NavMeshPathfinder.poly_to_aabb( poly )
  826. {
  827.     min_x = BIGNUM;
  828.     max_x = -BIGNUM;
  829.     min_y = BIGNUM;
  830.     max_y = -BIGNUM;
  831.     foreach( v : poly )
  832.     {
  833.         x = v.x;
  834.         y = v.y;
  835.         if( x < min_x ) min_x = x;
  836.         if( x > max_x ) max_x = x;
  837.         if( y < min_y ) min_y = y;
  838.         if( y > max_y ) max_y = y;
  839.     }
  840.     return min_x, min_y, max_x, max_y;
  841. }
  842.  
  843. function NavMeshPathfinder.find_point_polygon( x, y )
  844. {
  845.     polys = this.bbhash.find_point( x, y );
  846.     if( polys )
  847.     {
  848.         // find overlap
  849.         foreach( pid : polys )
  850.         {
  851.             poly = this.polygons[ pid ];
  852.             if( this.point_in_polygon( poly, x, y ) )
  853.                 return pid;
  854.         }
  855.     }
  856.     return null;
  857. }
  858.  
  859. function NavMeshPathfinder.point_in_polygon( poly, x, y )
  860. {
  861.     if( poly.size < 3 )
  862.         return false;
  863.     tp = vec2(x,y);
  864.     pp = poly.last;
  865.     foreach( cp : poly )
  866.     {
  867.         enorm = ( cp - pp ).perp2.normalized;
  868.         if( vec2_dot( enorm, tp ) > vec2_dot( enorm, cp ) + 0.001 )
  869.             return false;
  870.         pp = cp;
  871.     }
  872.     return true;
  873. }
  874.  
  875. function NavMeshPathfinder.cost_function( p1, p2 )
  876. {
  877.     return ( p1 - p2 ).length;
  878. }
  879.  
  880.  
  881. /*
  882.     AIInfo
  883.     - create()
  884.     + put_entity( id, group, pos )
  885.     + remove_entity( id )
  886.     + emit_sound( type, timeout, radius, pos )
  887.     + gather_visible( from, func )
  888.     + gather_heard( from )
  889.    
  890.     SOUND [ pos, type, timeout, radius ]
  891.     ENTITY [ pos, group ]
  892. */
  893.  
  894. global AIInfo = {};
  895.  
  896. function AIInfo.create()
  897. {
  898.     data =
  899.     {
  900.         entities = {},
  901.         sounds = [],
  902.     };
  903.    
  904.     return class( data, AIInfo );
  905. }
  906.  
  907. function AIInfo.put_entity( id, group, pos )
  908. {
  909.     this.entities[ id ] = [ pos, group ];
  910. }
  911.  
  912. function AIInfo.remove_entity( id )
  913. {
  914.     unset( this.entities, id );
  915. }
  916.  
  917. function AIInfo.emit_sound( pos, type, timeout, radius, id )
  918. {
  919.     this.sounds.push([ pos, type, timeout, radius, id ]);
  920. }
  921.  
  922. function AIInfo.gather_visible( from, func )
  923. {
  924.     visible = {};
  925.     foreach( eid, entity : this.entities )
  926.     {
  927.         factor = func( eid, entity, from );
  928.         if( factor > 0 )
  929.             visible[ eid ] = get_concat( [ factor ], entity );
  930.     }
  931.     return visible;
  932. }
  933.  
  934. function AIInfo.gather_heard( from, mindist, ignore_ids )
  935. {
  936.     heard = [].reserve( sqrt( this.sounds.size ) );
  937.     foreach( sound : this.sounds )
  938.     {
  939.         dist = ( sound[0] - from ).length;
  940.         if( dist < mindist ) continue;
  941.         if( dist > sound[3] ) continue;
  942.         if( ignore_ids && ignore_ids.find( sound[4] ) !== null ){ continue; }
  943.         power = 1 - dist / sound[3];
  944.         heard.push([
  945.             power,
  946.             sound[0],
  947.             sound[1],
  948.             sound[2],
  949.             sound[3],
  950.             sound[4]
  951.         ]);
  952.     }
  953.     return heard;
  954. }
  955.  
  956. function AIInfo.tick( delta )
  957. {
  958.     foreach( sid, sound : this.sounds )
  959.     {
  960.         if( ( sound[1] -= delta ) < 0 )
  961.         {
  962.             this.sounds[ sid ] = null;
  963.             continue;
  964.         }
  965.     }
  966.     this.sounds = array_filter( this.sounds );
  967. }
RAW Paste Data

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×