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