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