(50c0271)
[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 vg_async_call( world_link_gates_async, world, 0 );
186 vg_async_stall();
187
188 /* water */
189 for( u32 j=0; j<mdl_arrcount(&world->ent_water); j++ ){
190 ent_water *water = mdl_arritm( &world->ent_water, j );
191 if( world->water.enabled ){
192 vg_warn( "Multiple water surfaces in level!\n" );
193 break;
194 }
195
196 world->water.enabled = 1;
197 water_set_surface( world, water->transform.co[1] );
198 }
199
200 /* volumes */
201 for( u32 j=0; j<mdl_arrcount(&world->ent_volume); j++ ){
202 ent_volume *volume = mdl_arritm( &world->ent_volume, j );
203 mdl_transform_m4x3( &volume->transform, volume->to_world );
204 m4x3_invert_full( volume->to_world, volume->to_local );
205 }
206
207 /* audio packs */
208 for( u32 j=0; j<mdl_arrcount(&world->ent_audio); j++ ){
209 ent_audio *audio = mdl_arritm( &world->ent_audio, j );
210
211 for( u32 k=0; k<audio->clip_count; k++ ){
212 ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
213 audio->clip_start+k );
214
215 if( clip->_.file.pack_size ){
216 u32 size = clip->_.file.pack_size,
217 offset = clip->_.file.pack_offset;
218
219 /* embedded files are fine to clear the scratch buffer, only
220 * external audio uses it */
221
222 vg_linear_clear( vg_mem.scratch );
223 void *data = vg_linear_alloc( vg_mem.scratch,
224 clip->_.file.pack_size );
225
226 mdl_fread_pack_file( &world->meta, &clip->_.file, data );
227
228 clip->_.clip.path = NULL;
229 clip->_.clip.flags = audio->flags;
230 clip->_.clip.data = data;
231 clip->_.clip.size = size;
232 }
233 else{
234 clip->_.clip.path = mdl_pstr(&world->meta,clip->_.file.pstr_path);
235 clip->_.clip.flags = audio->flags;
236 clip->_.clip.data = NULL;
237 clip->_.clip.size = 0;
238 }
239
240 audio_clip_load( &clip->_.clip, world->heap );
241 }
242 }
243
244 /* create generic entity hierachy for those who need it */
245 u32 indexed_count = 0;
246 struct {
247 u32 type;
248 mdl_array_ptr *array;
249 }
250 indexables[] = {
251 { k_ent_gate, &world->ent_gate },
252 { k_ent_objective, &world->ent_objective },
253 { k_ent_volume, &world->ent_volume },
254 { k_ent_challenge, &world->ent_challenge },
255 { k_ent_glider, &world->ent_glider },
256 { k_ent_npc, &world->ent_npc }
257 };
258
259 for( u32 i=0; i<vg_list_size(indexables); i++ )
260 indexed_count += mdl_arrcount( indexables[i].array );
261 vg_info( "indexing %u entities\n", indexed_count );
262
263 world->entity_list = vg_linear_alloc( world->heap,
264 vg_align8(indexed_count*sizeof(u32)));
265
266 u32 index=0;
267 for( u32 i=0; i<vg_list_size(indexables); i++ ){
268 u32 type = indexables[i].type,
269 count = mdl_arrcount( indexables[i].array );
270
271 for( u32 j=0; j<count; j ++ )
272 world->entity_list[index ++] = mdl_entity_id( type, j );
273 }
274
275 world->entity_bh = bh_create( world->heap, &bh_system_entity_list, world,
276 indexed_count, 2 );
277
278 world->tar_min = world->entity_bh->nodes[0].bbx[0][1];
279 world->tar_max = world->entity_bh->nodes[0].bbx[1][1] + 20.0f;
280
281 for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i++ ){
282 ent_marker *marker = mdl_arritm( &world->ent_marker, i );
283
284 if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_min" ) )
285 world->tar_min = marker->transform.co[1];
286
287 if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_max" ) )
288 world->tar_max = marker->transform.co[1];
289 }
290 }
291
292 ent_spawn *world_find_closest_spawn( world_instance *world, v3f position )
293 {
294 ent_spawn *rp = NULL, *r;
295 float min_dist = INFINITY;
296
297 for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
298 r = mdl_arritm( &world->ent_spawn, i );
299 float d = v3_dist2( r->transform.co, position );
300
301 if( d < min_dist ){
302 min_dist = d;
303 rp = r;
304 }
305 }
306
307 if( !rp ){
308 if( mdl_arrcount(&world->ent_spawn) ){
309 vg_warn( "Invalid distances to spawns.. defaulting to first one.\n" );
310 return mdl_arritm( &world->ent_spawn, 0 );
311 }
312 else{
313 vg_error( "There are no spawns in the level!\n" );
314 }
315 }
316
317 return rp;
318 }
319
320 ent_spawn *world_find_spawn_by_name( world_instance *world, const char *name )
321 {
322 ent_spawn *rp = NULL, *r;
323 for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
324 r = mdl_arritm( &world->ent_spawn, i );
325 if( !strcmp( mdl_pstr(&world->meta, r->pstr_name), name ) ){
326 rp = r;
327 break;
328 }
329 }
330
331 if( !rp )
332 vg_warn( "No spawn named '%s'\n", name );
333
334 return rp;
335 }
336
337 void world_default_spawn_pos( world_instance *world, v3f pos )
338 {
339 ent_spawn *rp = world_find_spawn_by_name( world, "start" );
340 if( !rp ) rp = world_find_closest_spawn( world, (v3f){0,0,0} );
341 if( rp )
342 v3_copy( rp->transform.co, pos );
343 else
344 {
345 vg_error( "There are no valid spawns in the world\n" );
346 v3_zero( pos );
347 }
348 }
349
350 entity_call_result ent_volume_call( world_instance *world, ent_call *call )
351 {
352 u32 index = mdl_entity_id_id( call->id );
353 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
354
355 if( !volume->target )
356 return k_entity_call_result_OK;
357
358 if( call->function == k_ent_function_trigger )
359 {
360 call->id = volume->target;
361
362 if( volume->flags & k_ent_volume_flag_particles )
363 {
364 float *co = alloca( sizeof(float)*3 );
365 co[0] = vg_randf64(&vg.rand)*2.0f-1.0f;
366 co[1] = vg_randf64(&vg.rand)*2.0f-1.0f;
367 co[2] = vg_randf64(&vg.rand)*2.0f-1.0f;
368 m4x3_mulv( volume->to_world, co, co );
369
370 call->function = k_ent_function_particle_spawn;
371 call->data = co;
372 entity_call( world, call );
373 }
374 else
375 {
376 call->function = volume->trigger.event;
377 entity_call( world, call );
378 }
379
380 return k_entity_call_result_OK;
381 }
382 else if( call->function == k_ent_function_trigger_leave )
383 {
384 call->id = volume->target;
385
386 if( volume->flags & k_ent_volume_flag_particles )
387 {
388 vg_warn( "Invalid condition; calling leave on particle volume.\n" );
389 }
390 else
391 {
392 call->function = volume->trigger.event_leave;
393 entity_call( world, call );
394 }
395
396 return k_entity_call_result_OK;
397 }
398
399 return k_entity_call_result_unhandled;
400 }
401
402 entity_call_result ent_audio_call( world_instance *world, ent_call *call )
403 {
404 if( world->status == k_world_status_unloading )
405 {
406 vg_warn( "cannot modify audio while unloading world\n" );
407 return k_entity_call_result_invalid;
408 }
409
410 u8 world_id = (world - world_static.instances) + 1;
411 u32 index = mdl_entity_id_id( call->id );
412 ent_audio *audio = mdl_arritm( &world->ent_audio, index );
413
414 v3f sound_co;
415
416 if( call->function == k_ent_function_particle_spawn )
417 {
418 v3_copy( call->data, sound_co );
419 }
420 else if( call->function == k_ent_function_trigger )
421 {
422 v3_copy( audio->transform.co, sound_co );
423 }
424 else
425 return k_entity_call_result_unhandled;
426
427 float chance = vg_randf64(&vg.rand)*100.0f,
428 bar = 0.0f;
429
430 for( u32 i=0; i<audio->clip_count; i++ ){
431 ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
432 audio->clip_start+i );
433
434 float mod = world->probabilities[ audio->probability_curve ],
435 p = clip->probability * mod;
436
437 bar += p;
438 if( chance < bar )
439 {
440 audio_lock();
441
442 if( audio->behaviour == k_channel_behaviour_unlimited )
443 {
444 audio_oneshot_3d( &clip->_.clip, sound_co,
445 audio->transform.s[0],
446 audio->volume );
447 }
448 else if( audio->behaviour == k_channel_behaviour_discard_if_full )
449 {
450 audio_channel *ch =
451 audio_get_group_idle_channel( audio->group,
452 audio->max_channels );
453
454 if( ch )
455 {
456 audio_channel_init( ch, &clip->_.clip, audio->flags );
457 audio_channel_group( ch, audio->group );
458 audio_channel_world( ch, world_id );
459 audio_channel_set_spacial( ch, sound_co, audio->transform.s[0] );
460 audio_channel_edit_volume( ch, audio->volume, 1 );
461 ch = audio_relinquish_channel( ch );
462 }
463 }
464 else if( audio->behaviour == k_channel_behaviour_crossfade_if_full)
465 {
466 audio_channel *ch =
467 audio_get_group_idle_channel( audio->group,
468 audio->max_channels );
469
470 /* group is full */
471 if( !ch ){
472 audio_channel *existing =
473 audio_get_group_first_active_channel( audio->group );
474
475 if( existing ){
476 if( existing->source == &clip->_.clip ){
477 audio_unlock();
478 return k_entity_call_result_OK;
479 }
480
481 existing->group = 0;
482 existing = audio_channel_fadeout(existing, audio->crossfade);
483 }
484
485 ch = audio_get_first_idle_channel();
486 }
487
488 if( ch )
489 {
490 audio_channel_init( ch, &clip->_.clip, audio->flags );
491 audio_channel_group( ch, audio->group );
492 audio_channel_world( ch, world_id );
493 audio_channel_fadein( ch, audio->crossfade );
494 ch = audio_relinquish_channel( ch );
495 }
496 }
497
498 audio_unlock();
499 return k_entity_call_result_OK;
500 }
501 }
502 return k_entity_call_result_OK;
503 }
504
505
506 entity_call_result ent_ccmd_call( world_instance *world, ent_call *call )
507 {
508 if( call->function == k_ent_function_trigger )
509 {
510 u32 index = mdl_entity_id_id( call->id );
511 ent_ccmd *ccmd = mdl_arritm( &world->ent_ccmd, index );
512 vg_execute_console_input( mdl_pstr(&world->meta, ccmd->pstr_command), 0 );
513 return k_entity_call_result_OK;
514 }
515 else
516 return k_entity_call_result_unhandled;
517 }
518
519 /*
520 * BVH implementation
521 * ----------------------------------------------------------------------------
522 */
523
524 void entity_bh_expand_bound( void *user, boxf bound, u32 item_index )
525 {
526 world_instance *world = user;
527
528 u32 id = world->entity_list[ item_index ],
529 type = mdl_entity_id_type( id ),
530 index = mdl_entity_id_id( id );
531
532 if( type == k_ent_gate ){
533 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
534 boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
535 { gate->dimensions[0], gate->dimensions[1], 0.1f }};
536
537 m4x3_expand_aabb_aabb( gate->to_world, bound, box );
538 }
539 else if( type == k_ent_objective ){
540 ent_objective *objective = mdl_arritm( &world->ent_objective, index );
541
542 /* TODO: This might be more work than necessary. could maybe just get
543 * away with representing them as points */
544
545 boxf box;
546 box_init_inf( box );
547
548 for( u32 i=0; i<objective->submesh_count; i++ ){
549 mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
550 objective->submesh_start+i );
551 box_concat( box, sm->bbx );
552 }
553
554 m4x3f transform;
555 mdl_transform_m4x3( &objective->transform, transform );
556 m4x3_expand_aabb_aabb( transform, bound, box );
557 }
558 else if( type == k_ent_volume ){
559 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
560 m4x3_expand_aabb_aabb( volume->to_world, bound,
561 (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
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 m4x3_expand_aabb_aabb( transform, bound, box );
571 }
572 else if( type == k_ent_glider ){
573 ent_glider *glider = mdl_arritm( &world->ent_glider, index );
574 m4x3f transform;
575 mdl_transform_m4x3( &glider->transform, transform );
576 m4x3_expand_aabb_aabb( transform, bound,
577 (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
578 }
579 else if( type == k_ent_npc )
580 {
581 ent_npc *npc = mdl_arritm( &world->ent_npc, index );
582 box_addpt( bound, npc->transform.co );
583 }
584 else{
585 vg_fatal_error( "Programming error\n" );
586 }
587 }
588
589 float entity_bh_centroid( void *user, u32 item_index, int axis )
590 {
591 world_instance *world = user;
592
593 u32 id = world->entity_list[ item_index ],
594 type = mdl_entity_id_type( id ),
595 index = mdl_entity_id_id( id );
596
597 if( type == k_ent_gate ){
598 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
599 return gate->to_world[3][axis];
600 }
601 else if( type == k_ent_objective ){
602 ent_objective *objective = mdl_arritm( &world->ent_objective, index );
603 return objective->transform.co[axis];
604 }
605 else if( type == k_ent_volume ){
606 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
607 return volume->transform.co[axis];
608 }
609 else if( type == k_ent_challenge )
610 {
611 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
612 return challenge->transform.co[axis];
613 }
614 else if( type == k_ent_glider )
615 {
616 ent_glider *glider = mdl_arritm( &world->ent_glider, index );
617 return glider->transform.co[axis];
618 }
619 else if( type == k_ent_npc )
620 {
621 ent_npc *npc = mdl_arritm( &world->ent_npc, index );
622 return npc->transform.co[axis];
623 }
624 else
625 {
626 vg_fatal_error( "Programming error\n" );
627 return INFINITY;
628 }
629 }
630
631 void entity_bh_swap( void *user, u32 ia, u32 ib )
632 {
633 world_instance *world = user;
634
635 u32 a = world->entity_list[ ia ],
636 b = world->entity_list[ ib ];
637
638 world->entity_list[ ia ] = b;
639 world->entity_list[ ib ] = a;
640 }
641
642 void entity_bh_debug( void *user, u32 item_index ){
643 world_instance *world = user;
644
645 u32 id = world->entity_list[ item_index ],
646 type = mdl_entity_id_type( id ),
647 index = mdl_entity_id_id( id );
648
649 if( type == k_ent_gate ){
650 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
651 boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
652 { gate->dimensions[0], gate->dimensions[1], 0.1f }};
653 vg_line_boxf_transformed( gate->to_world, box, 0xf000ff00 );
654 }
655 else if( type == k_ent_objective ){
656 ent_objective *objective = mdl_arritm( &world->ent_objective, index );
657 boxf box;
658 box_init_inf( box );
659
660 for( u32 i=0; i<objective->submesh_count; i++ ){
661 mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
662 objective->submesh_start+i );
663 box_concat( box, sm->bbx );
664 }
665
666 m4x3f transform;
667 mdl_transform_m4x3( &objective->transform, transform );
668 vg_line_boxf_transformed( transform, box, 0xf000ff00 );
669 }
670 else if( type == k_ent_volume ){
671 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
672 vg_line_boxf_transformed( volume->to_world,
673 (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}},
674 0xf000ff00 );
675 }
676 else if( type == k_ent_challenge ){
677 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
678
679 boxf box = {{-1.2f*0.5f,-0.72f*0.5f,-0.01f*0.5f},
680 { 1.2f*0.5f, 0.72f*0.5f, 0.01f*0.5f}};
681 m4x3f transform;
682 mdl_transform_m4x3( &challenge->transform, transform );
683 vg_line_boxf_transformed( transform, box, 0xf0ff0000 );
684 }
685 else{
686 vg_fatal_error( "Programming error\n" );
687 }
688 }
689
690 void update_ach_models(void)
691 {
692 world_instance *hub = &world_static.instances[k_world_purpose_hub];
693 if( hub->status != k_world_status_loaded ) return;
694
695 for( u32 i=0; i<mdl_arrcount( &hub->ent_prop ); i ++ ){
696 ent_prop *prop = mdl_arritm( &hub->ent_prop, i );
697 if( prop->flags & 0x2 ){
698 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "MARC" ) )
699 if( skaterift.achievements & 0x1 )
700 prop->flags &= ~0x1;
701 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "ALBERT" ) )
702 if( skaterift.achievements & 0x2 )
703 prop->flags &= ~0x1;
704 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "JANET" ) )
705 if( skaterift.achievements & 0x4 )
706 prop->flags &= ~0x1;
707 if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "BERNADETTA" ) )
708 if( skaterift.achievements & 0x8 )
709 prop->flags &= ~0x1;
710 }
711 }
712 }
713
714 void entity_bh_closest( void *user, u32 item_index, v3f point, v3f closest )
715 {
716 world_instance *world = user;
717
718 u32 id = world->entity_list[ item_index ],
719 type = mdl_entity_id_type( id ),
720 index = mdl_entity_id_id( id );
721
722 if( type == k_ent_gate ){
723 ent_gate *gate = mdl_arritm( &world->ent_gate, index );
724 v3_copy( gate->to_world[3], closest );
725 }
726 else if( type == k_ent_objective ){
727 ent_objective *challenge = mdl_arritm( &world->ent_objective, index );
728 v3_copy( challenge->transform.co, closest );
729 }
730 else if( type == k_ent_volume ){
731 ent_volume *volume = mdl_arritm( &world->ent_volume, index );
732 v3_copy( volume->to_world[3], closest );
733 }
734 else if( type == k_ent_challenge ){
735 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
736 v3_copy( challenge->transform.co, closest );
737 }
738 else{
739 vg_fatal_error( "Programming error\n" );
740 }
741 }
742
743 void world_entity_start( world_instance *world, vg_msg *sav )
744 {
745 vg_info( "Start instance %p\n", world );
746
747 world->probabilities[ k_probability_curve_constant ] = 1.0f;
748 for( u32 i=0; i<mdl_arrcount(&world->ent_audio); i++ )
749 {
750 ent_audio *audio = mdl_arritm(&world->ent_audio,i);
751 if( audio->flags & AUDIO_FLAG_AUTO_START )
752 {
753 ent_call call;
754 call.data = NULL;
755 call.function = k_ent_function_trigger;
756 call.id = mdl_entity_id( k_ent_audio, i );
757 entity_call( world, &call );
758 }
759 }
760
761 /* read savedata
762 * ----------------------------------------------------------------------- */
763
764 for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
765 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
766 const char *alias = mdl_pstr( &world->meta, challenge->pstr_alias );
767
768 u32 result;
769 vg_msg_getkvintg( sav, alias, k_vg_msg_u32, &result );
770
771 if( result ){
772 ent_call call;
773 call.data = NULL;
774 call.function = 0;
775 call.id = mdl_entity_id( k_ent_challenge, i );
776 entity_call( world, &call );
777 }
778 }
779
780 vg_msg routes_block = *sav;
781 if( vg_msg_seekframe( &routes_block, "routes" ) ){
782 for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
783 ent_route *route = mdl_arritm( &world->ent_route, i );
784
785 vg_msg route_info = routes_block;
786 if( vg_msg_seekframe( &route_info,
787 mdl_pstr(&world->meta,route->pstr_name) ) ){
788
789 u32 flags;
790 vg_msg_getkvintg( &route_info, "flags", k_vg_msg_u32, &flags );
791 route->flags |= flags;
792
793 vg_msg_getkvintg( &route_info, "best_laptime", k_vg_msg_f64,
794 &route->best_laptime );
795
796 f32 sections[ route->checkpoints_count ];
797 vg_msg_cmd cmd;
798 if( vg_msg_getkvcmd( &route_info, "sections", &cmd ) ){
799 vg_msg_cast( cmd.value, cmd.code, sections,
800 k_vg_msg_f32 |
801 vg_msg_count_bits(route->checkpoints_count) );
802 }
803 else{
804 for( u32 j=0; j<route->checkpoints_count; j ++ )
805 sections[j] = 0.0f;
806 }
807
808 for( u32 j=0; j<route->checkpoints_count; j ++ ){
809 ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
810 route->checkpoints_start + j );
811
812 cp->best_time = sections[j];
813 }
814
815 /* LEGACY: check if steam achievements can give us a medal */
816 if( steam_ready && steam_stats_ready ){
817 for( u32 j=0; j<vg_list_size(track_infos); j ++ ){
818 struct track_info *inf = &track_infos[j];
819 if( !strcmp(inf->name,
820 mdl_pstr(&world->meta,route->pstr_name))){
821
822 steamapi_bool set = 0;
823 if( SteamAPI_ISteamUserStats_GetAchievement(
824 hSteamUserStats, inf->achievement_id, &set ) )
825 {
826 if( set ){
827 route->flags |= k_ent_route_flag_achieve_silver;
828 }
829 }
830 }
831 }
832 }
833 }
834 }
835 }
836
837 ent_region_re_eval( world );
838 }
839
840 void world_entity_serialize( world_instance *world, vg_msg *sav )
841 {
842 for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
843 ent_challenge *challenge = mdl_arritm(&world->ent_challenge,i);
844
845 const char *alias = mdl_pstr(&world->meta,challenge->pstr_alias);
846 vg_msg_wkvnum( sav, alias, k_vg_msg_u32, 1, &challenge->status );
847 }
848
849 if( mdl_arrcount(&world->ent_route) ){
850 vg_msg_frame( sav, "routes" );
851 for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
852 ent_route *route = mdl_arritm( &world->ent_route, i );
853
854 vg_msg_frame( sav, mdl_pstr( &world->meta, route->pstr_name ) );
855 {
856 vg_msg_wkvnum( sav, "flags", k_vg_msg_u32, 1, &route->flags );
857 vg_msg_wkvnum( sav, "best_laptime",
858 k_vg_msg_f64, 1, &route->best_laptime );
859
860 f32 sections[ route->checkpoints_count ];
861
862 for( u32 j=0; j<route->checkpoints_count; j ++ ){
863 ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
864 route->checkpoints_start + j );
865
866 sections[j] = cp->best_time;
867 }
868
869 vg_msg_wkvnum( sav, "sections", k_vg_msg_f32,
870 route->checkpoints_count, sections );
871 }
872 vg_msg_end_frame( sav );
873 }
874 vg_msg_end_frame( sav );
875 }
876 }