server loopback tool
[carveJwlIkooP6JGAAIwe30JlM.git] / world_entity.c
1 #include "vg/vg_steam.h"
2 #include "vg/vg_steam_user_stats.h"
3 #include "model.h"
4 #include "entity.h"
5 #include "world.h"
6 #include "world_load.h"
7 #include "save.h"
8 #include "vg/vg_msg.h"
9 #include "menu.h"
10 #include "ent_challenge.h"
11 #include "ent_skateshop.h"
12 #include "ent_route.h"
13 #include "ent_traffic.h"
14 #include "ent_glider.h"
15 #include "ent_region.h"
16 #include "ent_npc.h"
17 #include "input.h"
18 #include "player_walk.h"
19
20 bh_system bh_system_entity_list =
21 {
22 .expand_bound = entity_bh_expand_bound,
23 .item_centroid = entity_bh_centroid,
24 .item_closest = entity_bh_closest,
25 .item_swap = entity_bh_swap,
26 .item_debug = entity_bh_debug,
27 .cast_ray = NULL
28 };
29
30 void world_entity_set_focus( u32 entity_id )
31 {
32 if( world_static.focused_entity )
33 {
34 vg_warn( "Entity %u#%u tried to take focus from %u#%u\n",
35 mdl_entity_id_type( entity_id ),
36 mdl_entity_id_id( entity_id ),
37 mdl_entity_id_type( world_static.focused_entity ),
38 mdl_entity_id_id( world_static.focused_entity ) );
39 return;
40 }
41
42 world_static.focused_entity = entity_id;
43 }
44
45 void world_entity_focus_modal(void)
46 {
47 localplayer.immobile = 1;
48 menu.disable_open = 1;
49 srinput.state = k_input_state_resume;
50
51 v3_zero( localplayer.rb.v );
52 v3_zero( localplayer.rb.w );
53 player_walk.move_speed = 0.0f;
54 skaterift.activity = k_skaterift_ent_focus;
55 }
56
57 void world_entity_exit_modal(void)
58 {
59 if( skaterift.activity != k_skaterift_ent_focus )
60 {
61 vg_warn( "Entity %u#%u tried to exit modal when we weren't in one\n",
62 mdl_entity_id_type( world_static.focused_entity ),
63 mdl_entity_id_id( world_static.focused_entity ) );
64 return;
65 }
66
67 localplayer.immobile = 0;
68 menu.disable_open = 0;
69 srinput.state = k_input_state_resume;
70 skaterift.activity = k_skaterift_default;
71 }
72
73 void world_entity_clear_focus(void)
74 {
75 if( skaterift.activity == k_skaterift_ent_focus )
76 {
77 vg_warn( "Entity %u#%u tried to clear focus before exiting modal\n",
78 mdl_entity_id_type( world_static.focused_entity ),
79 mdl_entity_id_id( world_static.focused_entity ) );
80 return;
81 }
82
83 world_static.focused_entity = 0;
84 }
85
86 void world_entity_focus_camera( world_instance *world, u32 uid )
87 {
88 if( mdl_entity_id_type( uid ) == k_ent_camera ){
89 u32 index = mdl_entity_id_id( uid );
90 ent_camera *cam = mdl_arritm( &world->ent_camera, index );
91
92 v3f dir = {0.0f,-1.0f,0.0f};
93 mdl_transform_vector( &cam->transform, dir, dir );
94 v3_angles( dir, world_static.focus_cam.angles );
95 v3_copy( cam->transform.co, world_static.focus_cam.pos );
96 world_static.focus_cam.fov = cam->fov;
97 }
98 else {
99 vg_camera_copy( &localplayer.cam, &world_static.focus_cam );
100
101 /* TODO ? */
102 world_static.focus_cam.nearz = localplayer.cam.nearz;
103 world_static.focus_cam.farz = localplayer.cam.farz;
104 }
105 }
106
107 /* logic preupdate */
108 void world_entity_focus_preupdate(void)
109 {
110 f32 rate = vg_minf( 1.0f, vg.time_frame_delta * 2.0f );
111 int active = 0;
112 if( skaterift.activity == k_skaterift_ent_focus )
113 active = 1;
114
115 vg_slewf( &world_static.focus_strength, active,
116 vg.time_frame_delta * (1.0f/0.5f) );
117
118 if( world_static.focused_entity == 0 )
119 return;
120
121 u32 type = mdl_entity_id_type( world_static.focused_entity ),
122 index = mdl_entity_id_id( world_static.focused_entity );
123
124 world_instance *world = world_current_instance();
125
126 static void (*table[])( ent_focus_context *ctx ) =
127 {
128 [ k_ent_skateshop ] = ent_skateshop_preupdate,
129 [ k_ent_challenge ] = ent_challenge_preupdate,
130 [ k_ent_route ] = ent_route_preupdate,
131 [ k_ent_npc ] = ent_npc_preupdate,
132 };
133
134 if( (type > vg_list_size(table)) || (table[type] == NULL) )
135 {
136 vg_fatal_error( "No pre-update method set for entity (%u#%u)\n",
137 type, index );
138 }
139
140 table[type]( &(ent_focus_context){
141 .world = world,
142 .index = index,
143 .active = active } );
144 }
145
146 /* additional renderings like text etc.. */
147 void world_entity_focus_render(void)
148 {
149 world_instance *world = world_current_instance();
150 if( skaterift.activity != k_skaterift_ent_focus ){
151 skateshop_render_nonfocused( world, &skaterift.cam );
152 return;
153 }
154
155 u32 type = mdl_entity_id_type( world_static.focused_entity ),
156 index = mdl_entity_id_id( world_static.focused_entity );
157
158 if( type == k_ent_skateshop ){
159 ent_skateshop *skateshop = mdl_arritm( &world->ent_skateshop, index );
160 skateshop_render( skateshop );
161 }
162 else if( type == k_ent_challenge ){}
163 else if( type == k_ent_route ){}
164 else if( type == k_ent_miniworld ){}
165 else if( type == k_ent_npc ){}
166 else {
167 vg_fatal_error( "Programming error\n" );
168 }
169 }
170
171 void world_gen_entities_init( world_instance *world )
172 {
173 /* lights */
174 for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
175 ent_light *light = mdl_arritm( &world->ent_light, j );
176
177 m4x3f to_world;
178 q_m3x3( light->transform.q, to_world );
179 v3_copy( light->transform.co, to_world[3] );
180 m4x3_invert_affine( to_world, light->inverse_world );
181
182 light->angle_sin_cos[0] = sinf( light->angle * 0.5f );
183 light->angle_sin_cos[1] = cosf( light->angle * 0.5f );
184 }
185
186 vg_async_call( world_link_gates_async, world, 0 );
187 vg_async_stall();
188
189 /* water */
190 for( u32 j=0; j<mdl_arrcount(&world->ent_water); j++ ){
191 ent_water *water = mdl_arritm( &world->ent_water, j );
192 if( world->water.enabled ){
193 vg_warn( "Multiple water surfaces in level!\n" );
194 break;
195 }
196
197 world->water.enabled = 1;
198 water_set_surface( world, water->transform.co[1] );
199 }
200
201 /* volumes */
202 for( u32 j=0; j<mdl_arrcount(&world->ent_volume); j++ ){
203 ent_volume *volume = mdl_arritm( &world->ent_volume, j );
204 mdl_transform_m4x3( &volume->transform, volume->to_world );
205 m4x3_invert_full( volume->to_world, volume->to_local );
206 }
207
208 /* audio packs */
209 for( u32 j=0; j<mdl_arrcount(&world->ent_audio); j++ ){
210 ent_audio *audio = mdl_arritm( &world->ent_audio, j );
211
212 for( u32 k=0; k<audio->clip_count; k++ ){
213 ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
214 audio->clip_start+k );
215
216 if( clip->_.file.pack_size ){
217 u32 size = clip->_.file.pack_size,
218 offset = clip->_.file.pack_offset;
219
220 /* embedded files are fine to clear the scratch buffer, only
221 * external audio uses it */
222
223 vg_linear_clear( vg_mem.scratch );
224 void *data = vg_linear_alloc( vg_mem.scratch,
225 clip->_.file.pack_size );
226
227 mdl_fread_pack_file( &world->meta, &clip->_.file, data );
228
229 clip->_.clip.path = NULL;
230 clip->_.clip.flags = audio->flags;
231 clip->_.clip.data = data;
232 clip->_.clip.size = size;
233 }
234 else{
235 clip->_.clip.path = mdl_pstr(&world->meta,clip->_.file.pstr_path);
236 clip->_.clip.flags = audio->flags;
237 clip->_.clip.data = NULL;
238 clip->_.clip.size = 0;
239 }
240
241 audio_clip_load( &clip->_.clip, world->heap );
242 }
243 }
244
245 /* create generic entity hierachy for those who need it */
246 u32 indexed_count = 0;
247 struct {
248 u32 type;
249 mdl_array_ptr *array;
250 }
251 indexables[] = {
252 { k_ent_gate, &world->ent_gate },
253 { k_ent_objective, &world->ent_objective },
254 { k_ent_volume, &world->ent_volume },
255 { k_ent_challenge, &world->ent_challenge },
256 { k_ent_glider, &world->ent_glider },
257 { k_ent_npc, &world->ent_npc }
258 };
259
260 for( u32 i=0; i<vg_list_size(indexables); i++ )
261 indexed_count += mdl_arrcount( indexables[i].array );
262 vg_info( "indexing %u entities\n", indexed_count );
263
264 world->entity_list = vg_linear_alloc( world->heap,
265 vg_align8(indexed_count*sizeof(u32)));
266
267 u32 index=0;
268 for( u32 i=0; i<vg_list_size(indexables); i++ ){
269 u32 type = indexables[i].type,
270 count = mdl_arrcount( indexables[i].array );
271
272 for( u32 j=0; j<count; j ++ )
273 world->entity_list[index ++] = mdl_entity_id( type, j );
274 }
275
276 world->entity_bh = bh_create( world->heap, &bh_system_entity_list, world,
277 indexed_count, 2 );
278
279 world->tar_min = world->entity_bh->nodes[0].bbx[0][1];
280 world->tar_max = world->entity_bh->nodes[0].bbx[1][1] + 20.0f;
281
282 for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i++ ){
283 ent_marker *marker = mdl_arritm( &world->ent_marker, i );
284
285 if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_min" ) )
286 world->tar_min = marker->transform.co[1];
287
288 if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_max" ) )
289 world->tar_max = marker->transform.co[1];
290 }
291 }
292
293 ent_spawn *world_find_closest_spawn( world_instance *world, v3f position )
294 {
295 ent_spawn *rp = NULL, *r;
296 float min_dist = INFINITY;
297
298 for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
299 r = mdl_arritm( &world->ent_spawn, i );
300 float d = v3_dist2( r->transform.co, position );
301
302 if( d < min_dist ){
303 min_dist = d;
304 rp = r;
305 }
306 }
307
308 if( !rp ){
309 if( mdl_arrcount(&world->ent_spawn) ){
310 vg_warn( "Invalid distances to spawns.. defaulting to first one.\n" );
311 return mdl_arritm( &world->ent_spawn, 0 );
312 }
313 else{
314 vg_error( "There are no spawns in the level!\n" );
315 }
316 }
317
318 return rp;
319 }
320
321 ent_spawn *world_find_spawn_by_name( world_instance *world, const char *name )
322 {
323 ent_spawn *rp = NULL, *r;
324 for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
325 r = mdl_arritm( &world->ent_spawn, i );
326 if( !strcmp( mdl_pstr(&world->meta, r->pstr_name), name ) ){
327 rp = r;
328 break;
329 }
330 }
331
332 if( !rp )
333 vg_warn( "No spawn named '%s'\n", name );
334
335 return rp;
336 }
337
338 void world_default_spawn_pos( world_instance *world, v3f pos )
339 {
340 ent_spawn *rp = world_find_spawn_by_name( world, "start" );
341 if( !rp ) rp = world_find_closest_spawn( world, (v3f){0,0,0} );
342 if( rp )
343 v3_copy( rp->transform.co, pos );
344 else
345 {
346 vg_error( "There are no valid spawns in the world\n" );
347 v3_zero( pos );
348 }
349 }
350
351 entity_call_result ent_volume_call( world_instance *world, ent_call *call )
352 {
353 u32 index = mdl_entity_id_id( call->id );
354 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
355
356 if( !volume->target )
357 return k_entity_call_result_OK;
358
359 if( call->function == k_ent_function_trigger )
360 {
361 call->id = volume->target;
362
363 if( volume->flags & k_ent_volume_flag_particles )
364 {
365 float *co = alloca( sizeof(float)*3 );
366 co[0] = vg_randf64(&vg.rand)*2.0f-1.0f;
367 co[1] = vg_randf64(&vg.rand)*2.0f-1.0f;
368 co[2] = vg_randf64(&vg.rand)*2.0f-1.0f;
369 m4x3_mulv( volume->to_world, co, co );
370
371 call->function = k_ent_function_particle_spawn;
372 call->data = co;
373 entity_call( world, call );
374 }
375 else
376 {
377 call->function = volume->trigger.event;
378 entity_call( world, call );
379 }
380
381 return k_entity_call_result_OK;
382 }
383 else if( call->function == k_ent_function_trigger_leave )
384 {
385 call->id = volume->target;
386
387 if( volume->flags & k_ent_volume_flag_particles )
388 {
389 vg_warn( "Invalid condition; calling leave on particle volume.\n" );
390 }
391 else
392 {
393 call->function = volume->trigger.event_leave;
394 entity_call( world, call );
395 }
396
397 return k_entity_call_result_OK;
398 }
399
400 return k_entity_call_result_unhandled;
401 }
402
403 entity_call_result ent_audio_call( world_instance *world, ent_call *call )
404 {
405 if( world->status == k_world_status_unloading )
406 {
407 vg_warn( "cannot modify audio while unloading world\n" );
408 return k_entity_call_result_invalid;
409 }
410
411 u8 world_id = (world - world_static.instances) + 1;
412 u32 index = mdl_entity_id_id( call->id );
413 ent_audio *audio = mdl_arritm( &world->ent_audio, index );
414
415 v3f sound_co;
416
417 if( call->function == k_ent_function_particle_spawn )
418 {
419 v3_copy( call->data, sound_co );
420 }
421 else if( call->function == k_ent_function_trigger )
422 {
423 v3_copy( audio->transform.co, sound_co );
424 }
425 else
426 return k_entity_call_result_unhandled;
427
428 float chance = vg_randf64(&vg.rand)*100.0f,
429 bar = 0.0f;
430
431 for( u32 i=0; i<audio->clip_count; i++ ){
432 ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
433 audio->clip_start+i );
434
435 float mod = world->probabilities[ audio->probability_curve ],
436 p = clip->probability * mod;
437
438 bar += p;
439 if( chance < bar )
440 {
441 audio_lock();
442
443 if( audio->behaviour == k_channel_behaviour_unlimited )
444 {
445 audio_oneshot_3d( &clip->_.clip, sound_co,
446 audio->transform.s[0],
447 audio->volume );
448 }
449 else if( audio->behaviour == k_channel_behaviour_discard_if_full )
450 {
451 audio_channel *ch =
452 audio_get_group_idle_channel( audio->group,
453 audio->max_channels );
454
455 if( ch )
456 {
457 audio_channel_init( ch, &clip->_.clip, audio->flags );
458 audio_channel_group( ch, audio->group );
459 audio_channel_world( ch, world_id );
460 audio_channel_set_spacial( ch, sound_co, audio->transform.s[0] );
461 audio_channel_edit_volume( ch, audio->volume, 1 );
462 ch = audio_relinquish_channel( ch );
463 }
464 }
465 else if( audio->behaviour == k_channel_behaviour_crossfade_if_full)
466 {
467 audio_channel *ch =
468 audio_get_group_idle_channel( audio->group,
469 audio->max_channels );
470
471 /* group is full */
472 if( !ch ){
473 audio_channel *existing =
474 audio_get_group_first_active_channel( audio->group );
475
476 if( existing ){
477 if( existing->source == &clip->_.clip ){
478 audio_unlock();
479 return k_entity_call_result_OK;
480 }
481
482 existing->group = 0;
483 existing = audio_channel_fadeout(existing, audio->crossfade);
484 }
485
486 ch = audio_get_first_idle_channel();
487 }
488
489 if( ch )
490 {
491 audio_channel_init( ch, &clip->_.clip, audio->flags );
492 audio_channel_group( ch, audio->group );
493 audio_channel_world( ch, world_id );
494 audio_channel_fadein( ch, audio->crossfade );
495 ch = audio_relinquish_channel( ch );
496 }
497 }
498
499 audio_unlock();
500 return k_entity_call_result_OK;
501 }
502 }
503 return k_entity_call_result_OK;
504 }
505
506
507 entity_call_result ent_ccmd_call( world_instance *world, ent_call *call )
508 {
509 if( call->function == k_ent_function_trigger )
510 {
511 u32 index = mdl_entity_id_id( call->id );
512 ent_ccmd *ccmd = mdl_arritm( &world->ent_ccmd, index );
513 vg_execute_console_input( mdl_pstr(&world->meta, ccmd->pstr_command), 0 );
514 return k_entity_call_result_OK;
515 }
516 else
517 return k_entity_call_result_unhandled;
518 }
519
520 /*
521 * BVH implementation
522 * ----------------------------------------------------------------------------
523 */
524
525 void entity_bh_expand_bound( void *user, boxf bound, u32 item_index )
526 {
527 world_instance *world = user;
528
529 u32 id = world->entity_list[ item_index ],
530 type = mdl_entity_id_type( id ),
531 index = mdl_entity_id_id( id );
532
533 if( type == k_ent_gate ){
534 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
535 boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
536 { gate->dimensions[0], gate->dimensions[1], 0.1f }};
537
538 m4x3_expand_aabb_aabb( gate->to_world, bound, box );
539 }
540 else if( type == k_ent_objective ){
541 ent_objective *objective = mdl_arritm( &world->ent_objective, index );
542
543 /* TODO: This might be more work than necessary. could maybe just get
544 * away with representing them as points */
545
546 boxf box;
547 box_init_inf( box );
548
549 for( u32 i=0; i<objective->submesh_count; i++ ){
550 mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
551 objective->submesh_start+i );
552 box_concat( box, sm->bbx );
553 }
554
555 m4x3f transform;
556 mdl_transform_m4x3( &objective->transform, transform );
557 m4x3_expand_aabb_aabb( transform, bound, box );
558 }
559 else if( type == k_ent_volume ){
560 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
561 m4x3_expand_aabb_aabb( volume->to_world, bound,
562 (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
563 }
564 else if( type == k_ent_challenge ){
565 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
566
567 boxf box = {{-1.2f*0.5f,-0.72f*0.5f,-0.01f*0.5f},
568 { 1.2f*0.5f, 0.72f*0.5f, 0.01f*0.5f}};
569 m4x3f transform;
570 mdl_transform_m4x3( &challenge->transform, transform );
571 m4x3_expand_aabb_aabb( transform, bound, box );
572 }
573 else if( type == k_ent_glider ){
574 ent_glider *glider = mdl_arritm( &world->ent_glider, index );
575 m4x3f transform;
576 mdl_transform_m4x3( &glider->transform, transform );
577 m4x3_expand_aabb_aabb( transform, bound,
578 (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
579 }
580 else if( type == k_ent_npc )
581 {
582 ent_npc *npc = mdl_arritm( &world->ent_npc, index );
583 box_addpt( bound, npc->transform.co );
584 }
585 else{
586 vg_fatal_error( "Programming error\n" );
587 }
588 }
589
590 float entity_bh_centroid( void *user, u32 item_index, int axis )
591 {
592 world_instance *world = user;
593
594 u32 id = world->entity_list[ item_index ],
595 type = mdl_entity_id_type( id ),
596 index = mdl_entity_id_id( id );
597
598 if( type == k_ent_gate ){
599 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
600 return gate->to_world[3][axis];
601 }
602 else if( type == k_ent_objective ){
603 ent_objective *objective = mdl_arritm( &world->ent_objective, index );
604 return objective->transform.co[axis];
605 }
606 else if( type == k_ent_volume ){
607 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
608 return volume->transform.co[axis];
609 }
610 else if( type == k_ent_challenge )
611 {
612 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
613 return challenge->transform.co[axis];
614 }
615 else if( type == k_ent_glider )
616 {
617 ent_glider *glider = mdl_arritm( &world->ent_glider, index );
618 return glider->transform.co[axis];
619 }
620 else if( type == k_ent_npc )
621 {
622 ent_npc *npc = mdl_arritm( &world->ent_npc, index );
623 return npc->transform.co[axis];
624 }
625 else
626 {
627 vg_fatal_error( "Programming error\n" );
628 return INFINITY;
629 }
630 }
631
632 void entity_bh_swap( void *user, u32 ia, u32 ib )
633 {
634 world_instance *world = user;
635
636 u32 a = world->entity_list[ ia ],
637 b = world->entity_list[ ib ];
638
639 world->entity_list[ ia ] = b;
640 world->entity_list[ ib ] = a;
641 }
642
643 void entity_bh_debug( void *user, u32 item_index ){
644 world_instance *world = user;
645
646 u32 id = world->entity_list[ item_index ],
647 type = mdl_entity_id_type( id ),
648 index = mdl_entity_id_id( id );
649
650 if( type == k_ent_gate ){
651 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
652 boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
653 { gate->dimensions[0], gate->dimensions[1], 0.1f }};
654 vg_line_boxf_transformed( gate->to_world, box, 0xf000ff00 );
655 }
656 else if( type == k_ent_objective ){
657 ent_objective *objective = mdl_arritm( &world->ent_objective, index );
658 boxf box;
659 box_init_inf( box );
660
661 for( u32 i=0; i<objective->submesh_count; i++ ){
662 mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
663 objective->submesh_start+i );
664 box_concat( box, sm->bbx );
665 }
666
667 m4x3f transform;
668 mdl_transform_m4x3( &objective->transform, transform );
669 vg_line_boxf_transformed( transform, box, 0xf000ff00 );
670 }
671 else if( type == k_ent_volume ){
672 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
673 vg_line_boxf_transformed( volume->to_world,
674 (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}},
675 0xf000ff00 );
676 }
677 else if( type == k_ent_challenge ){
678 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
679
680 boxf box = {{-1.2f*0.5f,-0.72f*0.5f,-0.01f*0.5f},
681 { 1.2f*0.5f, 0.72f*0.5f, 0.01f*0.5f}};
682 m4x3f transform;
683 mdl_transform_m4x3( &challenge->transform, transform );
684 vg_line_boxf_transformed( transform, box, 0xf0ff0000 );
685 }
686 else{
687 vg_fatal_error( "Programming error\n" );
688 }
689 }
690
691 void update_ach_models(void)
692 {
693 world_instance *hub = &world_static.instances[k_world_purpose_hub];
694 if( hub->status != k_world_status_loaded ) return;
695
696 for( u32 i=0; i<mdl_arrcount( &hub->ent_prop ); i ++ ){
697 ent_prop *prop = mdl_arritm( &hub->ent_prop, i );
698 if( prop->flags & 0x2 ){
699 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "MARC" ) )
700 if( skaterift.achievements & 0x1 )
701 prop->flags &= ~0x1;
702 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "ALBERT" ) )
703 if( skaterift.achievements & 0x2 )
704 prop->flags &= ~0x1;
705 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "JANET" ) )
706 if( skaterift.achievements & 0x4 )
707 prop->flags &= ~0x1;
708 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "BERNADETTA" ) )
709 if( skaterift.achievements & 0x8 )
710 prop->flags &= ~0x1;
711 }
712 }
713 }
714
715 void entity_bh_closest( void *user, u32 item_index, v3f point, v3f closest )
716 {
717 world_instance *world = user;
718
719 u32 id = world->entity_list[ item_index ],
720 type = mdl_entity_id_type( id ),
721 index = mdl_entity_id_id( id );
722
723 if( type == k_ent_gate ){
724 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
725 v3_copy( gate->to_world[3], closest );
726 }
727 else if( type == k_ent_objective ){
728 ent_objective *challenge = mdl_arritm( &world->ent_objective, index );
729 v3_copy( challenge->transform.co, closest );
730 }
731 else if( type == k_ent_volume ){
732 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
733 v3_copy( volume->to_world[3], closest );
734 }
735 else if( type == k_ent_challenge ){
736 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
737 v3_copy( challenge->transform.co, closest );
738 }
739 else{
740 vg_fatal_error( "Programming error\n" );
741 }
742 }
743
744 void world_entity_start( world_instance *world, vg_msg *sav )
745 {
746 vg_info( "Start instance %p\n", world );
747
748 world->probabilities[ k_probability_curve_constant ] = 1.0f;
749 for( u32 i=0; i<mdl_arrcount(&world->ent_audio); i++ )
750 {
751 ent_audio *audio = mdl_arritm(&world->ent_audio,i);
752 if( audio->flags & AUDIO_FLAG_AUTO_START )
753 {
754 ent_call call;
755 call.data = NULL;
756 call.function = k_ent_function_trigger;
757 call.id = mdl_entity_id( k_ent_audio, i );
758 entity_call( world, &call );
759 }
760 }
761
762 /* read savedata
763 * ----------------------------------------------------------------------- */
764
765 for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
766 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
767 const char *alias = mdl_pstr( &world->meta, challenge->pstr_alias );
768
769 u32 result;
770 vg_msg_getkvintg( sav, alias, k_vg_msg_u32, &result, NULL );
771
772 if( result ){
773 ent_call call;
774 call.data = NULL;
775 call.function = 0;
776 call.id = mdl_entity_id( k_ent_challenge, i );
777 entity_call( world, &call );
778 }
779 }
780
781 vg_msg routes_block = *sav;
782 if( vg_msg_seekframe( &routes_block, "routes" ) ){
783 for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
784 ent_route *route = mdl_arritm( &world->ent_route, i );
785
786 vg_msg route_info = routes_block;
787 if( vg_msg_seekframe( &route_info,
788 mdl_pstr(&world->meta,route->pstr_name) ) ){
789
790 u32 flags;
791 vg_msg_getkvintg( &route_info, "flags", k_vg_msg_u32,
792 &flags, NULL );
793 route->flags |= flags;
794
795 vg_msg_getkvintg( &route_info, "best_laptime", k_vg_msg_f64,
796 &route->best_laptime, NULL );
797
798 f32 sections[ route->checkpoints_count ];
799 vg_msg_cmd cmd;
800 if( vg_msg_getkvcmd( &route_info, "sections", &cmd ) ){
801 vg_msg_cast( cmd.value, cmd.code, sections,
802 k_vg_msg_f32 |
803 vg_msg_count_bits(route->checkpoints_count) );
804 }
805 else{
806 for( u32 j=0; j<route->checkpoints_count; j ++ )
807 sections[j] = 0.0f;
808 }
809
810 for( u32 j=0; j<route->checkpoints_count; j ++ ){
811 ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
812 route->checkpoints_start + j );
813
814 cp->best_time = sections[j];
815 }
816
817 /* LEGACY: check if steam achievements can give us a medal */
818 if( steam_ready && steam_stats_ready ){
819 for( u32 j=0; j<vg_list_size(track_infos); j ++ ){
820 struct track_info *inf = &track_infos[j];
821 if( !strcmp(inf->name,
822 mdl_pstr(&world->meta,route->pstr_name))){
823
824 steamapi_bool set = 0;
825 if( SteamAPI_ISteamUserStats_GetAchievement(
826 hSteamUserStats, inf->achievement_id, &set ) )
827 {
828 if( set ){
829 route->flags |= k_ent_route_flag_achieve_silver;
830 }
831 }
832 }
833 }
834 }
835 }
836 }
837 }
838
839 ent_region_re_eval( world );
840 }
841
842 void world_entity_serialize( world_instance *world, vg_msg *sav )
843 {
844 for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
845 ent_challenge *challenge = mdl_arritm(&world->ent_challenge,i);
846
847 const char *alias = mdl_pstr(&world->meta,challenge->pstr_alias);
848 vg_msg_wkvnum( sav, alias, k_vg_msg_u32, 1, &challenge->status );
849 }
850
851 if( mdl_arrcount(&world->ent_route) ){
852 vg_msg_frame( sav, "routes" );
853 for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
854 ent_route *route = mdl_arritm( &world->ent_route, i );
855
856 vg_msg_frame( sav, mdl_pstr( &world->meta, route->pstr_name ) );
857 {
858 vg_msg_wkvnum( sav, "flags", k_vg_msg_u32, 1, &route->flags );
859 vg_msg_wkvnum( sav, "best_laptime",
860 k_vg_msg_f64, 1, &route->best_laptime );
861
862 f32 sections[ route->checkpoints_count ];
863
864 for( u32 j=0; j<route->checkpoints_count; j ++ ){
865 ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
866 route->checkpoints_start + j );
867
868 sections[j] = cp->best_time;
869 }
870
871 vg_msg_wkvnum( sav, "sections", k_vg_msg_f32,
872 route->checkpoints_count, sections );
873 }
874 vg_msg_end_frame( sav );
875 }
876 vg_msg_end_frame( sav );
877 }
878 }