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