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