snake5

SGScript Game Framework

Dec 17th, 2013
387
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