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