SHARE
TWEET

SGScript Game Framework

snake5 Dec 17th, 2013 236 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
Want to get better at JavaScript?
Learn to code JavaScript in 2017
Pastebin PRO Summer Special!
Get 40% OFF on Pastebin PRO accounts!
Top