+++ /dev/null
-#include "vg/vg_engine.h"
-#include "vg/vg_io.h"
-#include "vg/vg_loader.h"
-#include "addon.h"
-#include "addon_types.h"
-#include "vg/vg_msg.h"
-#include "steam.h"
-#include "workshop.h"
-#include <string.h>
-
-struct addon_system addon_system;
-
-u32 addon_count( enum addon_type type, u32 ignoreflags )
-{
- if( ignoreflags ){
- u32 typecount = 0, count = 0;
- for( u32 i=0; typecount<addon_count( type, 0 ); i++ ){
- addon_reg *reg = &addon_system.registry[i];
- if( reg->alias.type == type ){
- typecount ++;
-
- if( reg->flags & ignoreflags )
- continue;
-
- count ++;
- }
- }
-
- return count;
- }
- else
- return addon_system.registry_type_counts[ type ];
-}
-
-
-/* these kind of suck, oh well. */
-addon_reg *get_addon_from_index( enum addon_type type, u32 index,
- u32 ignoreflags )
-{
- u32 typecount = 0, count = 0;
- for( u32 i=0; typecount<addon_count(type,0); i++ ){
- addon_reg *reg = &addon_system.registry[i];
- if( reg->alias.type == type ){
- typecount ++;
-
- if( reg->flags & ignoreflags )
- continue;
-
- if( index == count )
- return reg;
-
- count ++;
- }
- }
-
- return NULL;
-}
-
-u32 get_index_from_addon( enum addon_type type, addon_reg *a )
-{
- u32 count = 0;
- for( u32 i=0; count<addon_system.registry_type_counts[type]; i++ ){
- addon_reg *reg = &addon_system.registry[i];
- if( reg->alias.type == type ){
- if( reg == a )
- return count;
-
- count ++;
- }
- }
-
- return 0xffffffff;
-}
-
-u32 addon_match( addon_alias *alias )
-{
- if( alias->type == k_addon_type_none ) return 0xffffffff;
-
- u32 foldername_djb2 = 0;
- if( !alias->workshop_id )
- foldername_djb2 = vg_strdjb2( alias->foldername );
-
- u32 count = 0;
- for( u32 i=0; count<addon_system.registry_type_counts[alias->type]; i++ ){
- addon_reg *reg = &addon_system.registry[i];
- if( reg->alias.type == alias->type ){
-
- if( alias->workshop_id ){
- if( alias->workshop_id == reg->alias.workshop_id )
- return count;
- }
- else{
- if( reg->foldername_hash == foldername_djb2 ){
- if( !strcmp( reg->alias.foldername, alias->foldername ) ){
- return count;
- }
- }
- }
-
- count ++;
- }
- }
-
- return 0xffffffff;
-}
-
-/*
- * Create a string version of addon alias in buf
- */
-void addon_alias_uid( addon_alias *alias, char buf[ADDON_UID_MAX] )
-{
- if( alias->workshop_id ){
- snprintf( buf, 128, "sr%03d-steam-"PRINTF_U64,
- alias->type, alias->workshop_id );
- }
- else {
- snprintf( buf, 128, "sr%03d-local-%s",
- alias->type, alias->foldername );
- }
-}
-
-/*
- * equality check
- */
-int addon_alias_eq( addon_alias *a, addon_alias *b )
-{
- if( a->type == b->type ){
- if( a->workshop_id == b->workshop_id ){
- if( a->workshop_id )
- return 1;
- else
- return !strcmp( a->foldername, b->foldername );
- }
- else
- return 0;
- }
- else return 0;
-}
-
-/*
- * make alias represent NULL.
- */
-void invalidate_addon_alias( addon_alias *alias )
-{
- alias->type = k_addon_type_none;
- alias->workshop_id = 0;
- alias->foldername[0] = '\0';
-}
-
-/*
- * parse uid to alias. returns 1 if successful
- */
-int addon_uid_to_alias( const char *uid, addon_alias *alias )
-{
-/* 1
- * 01234567890123
- * sr&&&-@@@@@-#*
- * | | |
- * type | id
- * |
- * location
- */
- if( strlen(uid) < 13 ){
- invalidate_addon_alias( alias );
- return 0;
- }
- if( !((uid[0] == 's') && (uid[1] == 'r')) ){
- invalidate_addon_alias( alias );
- return 0;
- }
-
- char type[4];
- memcpy( type, uid+2, 3 );
- type[3] = '\0';
- alias->type = atoi(type);
-
- char location[6];
- memcpy( location, uid+6, 5 );
- location[5] = '\0';
-
- if( !strcmp(location,"steam") )
- alias->workshop_id = atoll( uid+12 );
- else if( !strcmp(location,"local") ){
- alias->workshop_id = 0;
- vg_strncpy( uid+12, alias->foldername, 64, k_strncpy_always_add_null );
- }
- else{
- invalidate_addon_alias( alias );
- return 0;
- }
-
- return 1;
-}
-
-void addon_system_init( void )
-{
- u32 reg_size = sizeof(addon_reg)*ADDON_MOUNTED_MAX;
- addon_system.registry = vg_linear_alloc( vg_mem.rtmemory, reg_size );
-
- for( u32 type=0; type<k_addon_type_max; type++ ){
- struct addon_type_info *inf = &addon_type_infos[type];
- struct addon_cache *cache = &addon_system.cache[type];
-
- if( inf->cache_count ){
- /* create the allocations pool */
- u32 alloc_size = sizeof(struct addon_cache_entry)*inf->cache_count;
- cache->allocs = vg_linear_alloc( vg_mem.rtmemory, alloc_size );
- memset( cache->allocs, 0, alloc_size );
-
- cache->pool.buffer = cache->allocs;
- cache->pool.count = inf->cache_count;
- cache->pool.stride = sizeof( struct addon_cache_entry );
- cache->pool.offset = offsetof( struct addon_cache_entry, poolnode );
- vg_pool_init( &cache->pool );
-
- /* create the real memory */
- u32 cache_size = inf->cache_stride*inf->cache_count;
- cache->items = vg_linear_alloc( vg_mem.rtmemory, cache_size );
- cache->stride = inf->cache_stride;
- memset( cache->items, 0, cache_size );
-
- for( i32 j=0; j<inf->cache_count; j++ ){
- struct addon_cache_entry *alloc = &cache->allocs[j];
- alloc->reg_ptr = NULL;
- alloc->reg_index = 0xffffffff;
- }
- }
- }
-}
-
-/*
- * Scanning routines
- * -----------------------------------------------------------------------------
- */
-
-/*
- * Reciever for scan completion. copies the registry counts back into main fred
- */
-void async_addon_reg_update( void *data, u32 size )
-{
- vg_info( "Registry update notify\n" );
-
- for( u32 i=0; i<k_addon_type_max; i++ ){
- addon_system.registry_type_counts[i] = 0;
- }
-
- for( u32 i=0; i<addon_system.registry_count; i++ ){
- enum addon_type type = addon_system.registry[i].alias.type;
- addon_system.registry_type_counts[ type ] ++;
- }
-}
-
-static void addon_set_foldername( addon_reg *reg, const char name[64] ){
- vg_strncpy( name, reg->alias.foldername, 64, k_strncpy_always_add_null );
- reg->foldername_hash = vg_strdjb2( reg->alias.foldername );
-}
-
-/*
- * Create a new registry
- */
-static addon_reg *addon_alloc_reg( PublishedFileId_t workshop_id,
- enum addon_type type ){
- if( addon_system.registry_count == ADDON_MOUNTED_MAX ){
- vg_error( "You have too many addons installed!\n" );
- return NULL;
- }
-
- addon_reg *reg = &addon_system.registry[ addon_system.registry_count ];
- reg->flags = 0;
- reg->metadata_len = 0;
- reg->cache_id = 0;
- reg->state = k_addon_state_indexed;
- reg->alias.workshop_id = workshop_id;
- reg->alias.foldername[0] = '\0';
- reg->alias.type = type;
-
- if( workshop_id ){
- char foldername[64];
- snprintf( foldername, 64, PRINTF_U64, workshop_id );
- addon_set_foldername( reg, foldername );
- }
- return reg;
-}
-
-/*
- * If the addon.inf exists int the folder, load into the reg
- */
-static int addon_try_load_metadata( addon_reg *reg, vg_str folder_path ){
- vg_str meta_path = folder_path;
- vg_strcat( &meta_path, "/addon.inf" );
- if( !vg_strgood( &meta_path ) ){
- vg_error( "The metadata path is too long\n" );
- return 0;
- }
-
- FILE *fp = fopen( meta_path.buffer, "rb" );
- if( !fp ){
- vg_error( "Could not open the '%s'\n", meta_path.buffer );
- return 0;
- }
-
- reg->metadata_len = fread( reg->metadata, 1, 512, fp );
- if( reg->metadata_len != 512 ){
- if( !feof(fp) ){
- fclose(fp);
- vg_error( "unknown error codition" );
- reg->metadata_len = 0;
- return 0;
- }
- }
- fclose(fp);
- return 1;
-}
-
-static void addon_print_info( addon_reg *reg ){
- vg_info( "addon_reg #%u{\n", addon_system.registry_count );
- vg_info( " type: %d\n", reg->alias.type );
- vg_info( " workshop_id: " PRINTF_U64 "\n", reg->alias.workshop_id );
- vg_info( " folder: [%u]%s\n", reg->foldername_hash, reg->alias.foldername );
- vg_info( " metadata_len: %u\n", reg->metadata_len );
- vg_info( " cache_id: %hu\n", reg->cache_id );
- vg_info( "}\n" );
-}
-
-static void addon_mount_finish( addon_reg *reg ){
-#if 0
- addon_print_info( reg );
-#endif
- addon_system.registry_count ++;
-}
-
-/*
- * Mount a fully packaged addon, one that certainly has a addon.inf
- */
-static addon_reg *addon_mount_workshop_folder( PublishedFileId_t workshop_id,
- vg_str folder_path )
-{
- addon_reg *reg = addon_alloc_reg( workshop_id, k_addon_type_none );
- if( !reg ) return NULL;
-
- if( !addon_try_load_metadata( reg, folder_path ) ){
- return NULL;
- }
-
- enum addon_type type = k_addon_type_none;
- vg_msg msg;
- vg_msg_init( &msg, reg->metadata, reg->metadata_len );
-
- if( vg_msg_seekframe( &msg, "workshop" ))
- {
- vg_msg_getkvintg( &msg, "type", k_vg_msg_u32, &type, NULL );
- }
-
- if( type == k_addon_type_none )
- {
- vg_error( "Cannot determine addon type\n" );
- return NULL;
- }
-
- reg->alias.type = type;
- addon_mount_finish( reg );
- return reg;
-}
-
-/*
- * Mount a local folder. may or may not have addon.inf
- */
-addon_reg *addon_mount_local_addon( const char *folder,
- enum addon_type type,
- const char *content_ext )
-{
- char folder_path_buf[4096];
- vg_str folder_path;
- vg_strnull( &folder_path, folder_path_buf, 4096 );
- vg_strcat( &folder_path, folder );
-
- const char *folder_name = vg_strch( &folder_path, '/' )+1;
- u32 folder_hash = vg_strdjb2(folder_name);
- for( u32 i=0; i<addon_system.registry_count; i++ ){
- addon_reg *reg = &addon_system.registry[i];
-
- if( (reg->alias.type == type) && (reg->foldername_hash == folder_hash) ){
- if( !strcmp( reg->alias.foldername, folder_name ) ){
- reg->state = k_addon_state_indexed;
- return reg;
- }
- }
- }
-
- addon_reg *reg = addon_alloc_reg( 0, type );
- if( !reg ) return NULL;
- addon_set_foldername( reg, folder_name );
- addon_try_load_metadata( reg, folder_path );
-
- if( reg->metadata_len == 0 ){
- /* create our own content commands */
- vg_msg msg;
- vg_msg_init( &msg, reg->metadata, sizeof(reg->metadata) );
-
- u32 content_count = 0;
-
- vg_strcat( &folder_path, "" );
- vg_warn( "Creating own metadata for: %s\n", folder_path.buffer );
-
- vg_dir subdir;
- if( !vg_dir_open(&subdir, folder_path.buffer) ){
- vg_error( "Failed to open '%s'\n", folder_path.buffer );
- return NULL;
- }
-
- while( vg_dir_next_entry(&subdir) ){
- if( vg_dir_entry_type(&subdir) == k_vg_entry_type_file ){
- const char *fname = vg_dir_entry_name(&subdir);
- vg_str file = folder_path;
- vg_strcat( &file, "/" );
- vg_strcat( &file, fname );
- if( !vg_strgood( &file ) ) continue;
-
- char *ext = vg_strch( &file, '.' );
- if( !ext ) continue;
- if( strcmp(ext,content_ext) ) continue;
-
- vg_msg_wkvstr( &msg, "content", fname );
- content_count ++;
- }
- }
- vg_dir_close(&subdir);
-
- if( !content_count ) return NULL;
- if( msg.error == k_vg_msg_error_OK )
- reg->metadata_len = msg.cur.co;
- else{
- vg_error( "Error creating metadata: %d\n", msg.error );
- return NULL;
- }
- }
-
- addon_mount_finish( reg );
- return reg;
-}
-
-/*
- * Check all subscribed items
- */
-void addon_mount_workshop_items(void)
-{
- if( skaterift.demo_mode ){
- vg_info( "Won't load workshop items in demo mode\n" );
- return;
- }
- if( !steam_ready ) return;
-
- /*
- * Steam workshop scan
- */
- vg_info( "Mounting steam workshop subscriptions\n" );
- PublishedFileId_t workshop_ids[ ADDON_MOUNTED_MAX ];
- u32 workshop_count = ADDON_MOUNTED_MAX;
-
- vg_async_item *call = vg_async_alloc(
- sizeof(struct async_workshop_installed_files_info));
- struct async_workshop_installed_files_info *info = call->payload;
- info->buffer = workshop_ids;
- info->len = &workshop_count;
- vg_async_dispatch( call, async_workshop_get_installed_files );
- vg_async_stall();
-
- for( u32 j=0; j<workshop_count; j++ ){
- /* check for existance in both our caches
- * ----------------------------------------------------------*/
- PublishedFileId_t id = workshop_ids[j];
- for( u32 i=0; i<addon_system.registry_count; i++ ){
- addon_reg *reg = &addon_system.registry[i];
-
- if( reg->alias.workshop_id == id ){
- reg->state = k_addon_state_indexed;
- goto next_file_workshop;
- }
- }
-
- vg_async_item *call1 =
- vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
-
- char path[ 4096 ];
-
- struct async_workshop_filepath_info *info = call1->payload;
- info->buf = path;
- info->id = id;
- info->len = VG_ARRAY_LEN(path);
- vg_async_dispatch( call1, async_workshop_get_filepath );
- vg_async_stall(); /* too bad! */
-
- vg_str folder = {.buffer = path, .i=strlen(path), .len=4096};
- addon_mount_workshop_folder( id, folder );
-next_file_workshop:;
- }
-}
-
-/*
- * Scan a local content folder for addons. It must find at least one file with
- * the specified content_ext to be considered.
- */
-void addon_mount_content_folder( enum addon_type type,
- const char *base_folder,
- const char *content_ext )
-{
- vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n",
- type, base_folder, content_ext );
-
- char path_buf[4096];
- vg_str path;
- vg_strnull( &path, path_buf, 4096 );
- vg_strcat( &path, base_folder );
-
- vg_dir dir;
- if( !vg_dir_open(&dir,path.buffer) ){
- vg_error( "vg_dir_open('%s') failed\n", path.buffer );
- return;
- }
-
- vg_strcat(&path,"/");
-
- while( vg_dir_next_entry(&dir) ){
- if( vg_dir_entry_type(&dir) == k_vg_entry_type_dir ){
- const char *d_name = vg_dir_entry_name(&dir);
-
- vg_str folder = path;
- if( strlen( d_name ) > ADDON_FOLDERNAME_MAX ){
- vg_warn( "folder too long: %s\n", d_name );
- continue;
- }
-
- vg_strcat( &folder, d_name );
- if( !vg_strgood( &folder ) ) continue;
-
- addon_mount_local_addon( folder.buffer, type, content_ext );
- }
- }
- vg_dir_close(&dir);
-}
-
-/*
- * write the full path of the addon's folder into the vg_str
- */
-int addon_get_content_folder( addon_reg *reg, vg_str *folder, int async)
-{
- if( reg->alias.workshop_id ){
- struct async_workshop_filepath_info *info = NULL;
- vg_async_item *call = NULL;
-
- if( async ){
- call = vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
- info = call->payload;
- }
- else
- info = alloca( sizeof(struct async_workshop_filepath_info) );
-
- info->buf = folder->buffer;
- info->id = reg->alias.workshop_id;
- info->len = folder->len;
-
- if( async ){
- vg_async_dispatch( call, async_workshop_get_filepath );
- vg_async_stall(); /* too bad! */
- }
- else {
- async_workshop_get_filepath( info, 0 );
- }
-
- if( info->buf[0] == '\0' ){
- vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64 ")\n",
- reg->alias.workshop_id );
- return 0;
- }
- folder->i = strlen( folder->buffer );
- return 1;
- }
- else{
- folder->i = 0;
-
- const char *local_folder =
- addon_type_infos[reg->alias.type].local_content_folder;
-
- if( !local_folder ) return 0;
- vg_strcat( folder, local_folder );
- vg_strcat( folder, reg->alias.foldername );
- return 1;
- }
-}
-
-/*
- * Return existing cache id if reg_index points to a registry with its cache
- * already set.
- */
-u16 addon_cache_fetch( enum addon_type type, u32 reg_index )
-{
- addon_reg *reg = NULL;
-
- if( reg_index < addon_count( type, 0 ) ){
- reg = get_addon_from_index( type, reg_index, 0 );
- if( reg->cache_id )
- return reg->cache_id;
- }
-
- return 0;
-}
-
-/*
- * Allocate a new cache item from the pool
- */
-u16 addon_cache_alloc( enum addon_type type, u32 reg_index )
-{
- struct addon_cache *cache = &addon_system.cache[ type ];
-
- u16 new_id = vg_pool_lru( &cache->pool );
- struct addon_cache_entry *new_entry = vg_pool_item( &cache->pool, new_id );
-
- addon_reg *reg = NULL;
- if( reg_index < addon_count( type, 0 ) )
- reg = get_addon_from_index( type, reg_index, 0 );
-
- if( new_entry ){
- if( new_entry->reg_ptr )
- new_entry->reg_ptr->cache_id = 0;
-
- if( reg )
- reg->cache_id = new_id;
-
- new_entry->reg_ptr = reg;
- new_entry->reg_index = reg_index;
- return new_id;
- }
- else{
- vg_error( "cache full (type: %u)!\n", type );
- return 0;
- }
-}
-
-/*
- * Get the real item data for cache id
- */
-void *addon_cache_item( enum addon_type type, u16 id )
-{
- if( !id ) return NULL;
-
- struct addon_cache *cache = &addon_system.cache[type];
- return cache->items + ((size_t)(id-1) * cache->stride);
-}
-
-/*
- * Get the real item data for cache id ONLY if the item is completely loaded.
- */
-void *addon_cache_item_if_loaded( enum addon_type type, u16 id )
-{
- if( !id ) return NULL;
-
- struct addon_cache *cache = &addon_system.cache[type];
- struct addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
-
- if( entry->state == k_addon_cache_state_loaded )
- return addon_cache_item( type, id );
- else return NULL;
-}
-
-/*
- * Updates the item state from the main thread
- */
-void async_addon_setstate( void *_entry, u32 _state )
-{
- addon_cache_entry *entry = _entry;
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- entry->state = _state;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- vg_success( " loaded (%s)\n", entry->reg_ptr->alias.foldername );
-}
-
-/*
- * Handles the loading of an individual item
- */
-static int addon_cache_load_request( enum addon_type type, u16 id,
- addon_reg *reg, vg_str folder ){
-
- /* load content files
- * --------------------------------- */
- vg_str content_path = folder;
-
- vg_msg msg;
- vg_msg_init( &msg, reg->metadata, reg->metadata_len );
-
- const char *kv_content = vg_msg_getkvstr( &msg, "content" );
- if( kv_content ){
- vg_strcat( &content_path, "/" );
- vg_strcat( &content_path, kv_content );
- }
- else{
- vg_error( " No content paths in metadata\n" );
- return 0;
- }
-
- if( !vg_strgood( &content_path ) ) {
- vg_error( " Metadata path too long\n" );
- return 0;
- }
-
- if( type == k_addon_type_board ){
- struct player_board *board = addon_cache_item( type, id );
- player_board_load( board, content_path.buffer );
- return 1;
- }
- else if( type == k_addon_type_player ){
- struct player_model *model = addon_cache_item( type, id );
- player_model_load( model, content_path.buffer );
- return 1;
- }
- else {
- return 0;
- }
-
- return 0;
-}
-
-static void addon_cache_free_item( enum addon_type type, u16 id ){
- if( type == k_addon_type_board ){
- struct player_board *board = addon_cache_item( type, id );
- player_board_unload( board );
- }
- else if( type == k_addon_type_player ){
- struct player_model *model = addon_cache_item( type, id );
- player_model_unload( model );
- }
-}
-
-/*
- * Goes over cache item load requests and calls the above ^
- */
-static void T1_addon_cache_load_loop(void *_)
-{
- vg_info( "Running load loop\n" );
- char path_buf[4096];
-
- for( u32 type=0; type<k_addon_type_max; type++ )
- {
- struct addon_cache *cache = &addon_system.cache[type];
-
- for( u32 id=1; id<=cache->pool.count; id++ )
- {
- addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- if( entry->state == k_addon_cache_state_load_request )
- {
- vg_info( "process cache load request (%u#%u, reg:%u)\n",
- type, id, entry->reg_index );
-
- if( entry->reg_index >= addon_count(type,0) )
- {
- /* should maybe have a different value for this case */
- entry->state = k_addon_cache_state_none;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- continue;
- }
-
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
-
- /* continue with the request */
- addon_reg *reg = get_addon_from_index( type, entry->reg_index, 0 );
- entry->reg_ptr = reg;
-
- vg_str folder;
- vg_strnull( &folder, path_buf, 4096 );
- if( addon_get_content_folder( reg, &folder, 1 ) )
- {
- if( addon_cache_load_request( type, id, reg, folder ) )
- {
- vg_async_call( async_addon_setstate,
- entry, k_addon_cache_state_loaded );
- continue;
- }
- }
-
- vg_warn( "cache item did not load (%u#%u)\n", type, id );
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- entry->state = k_addon_cache_state_none;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- }
- else
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- }
- }
-}
-
-void addon_system_pre_update(void)
-{
- if( !vg_loader_availible() ) return;
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- for( u32 type=0; type<k_addon_type_max; type++ )
- {
- struct addon_cache *cache = &addon_system.cache[type];
-
- for( u32 id=1; id<=cache->pool.count; id++ )
- {
- addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
- if( entry->state == k_addon_cache_state_load_request )
- {
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- vg_loader_start( T1_addon_cache_load_loop, NULL );
- return;
- }
- }
- }
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
-}
-
-/*
- * Perform the cache interactions required to create a viewslot which will
- * eventually be loaded by other parts of the system.
- */
-u16 addon_cache_create_viewer( enum addon_type type, u16 reg_id )
-{
- struct addon_cache *cache = &addon_system.cache[type];
- vg_pool *pool = &cache->pool;
-
- u16 cache_id = addon_cache_fetch( type, reg_id );
- if( !cache_id ){
- cache_id = addon_cache_alloc( type, reg_id );
-
- if( cache_id ){
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- addon_cache_entry *entry = vg_pool_item( pool, cache_id );
-
- if( entry->state == k_addon_cache_state_loaded ){
- addon_cache_free_item( type, cache_id );
- }
-
- entry->state = k_addon_cache_state_load_request;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- }
- }
-
- if( cache_id )
- vg_pool_watch( pool, cache_id );
-
- return cache_id;
-}
-
-u16 addon_cache_create_viewer_from_uid( enum addon_type type,
- char uid[ADDON_UID_MAX] )
-{
- addon_alias q;
- if( !addon_uid_to_alias( uid, &q ) ) return 0;
- if( q.type != type ) return 0;
-
- u32 reg_id = addon_match( &q );
-
- if( reg_id == 0xffffffff ){
- vg_warn( "We dont have the addon '%s' installed.\n", uid );
- return 0;
- }
- else {
- return addon_cache_create_viewer( type, reg_id );
- }
-}
-
-void addon_cache_watch( enum addon_type type, u16 cache_id )
-{
- if( !cache_id ) return;
-
- struct addon_cache *cache = &addon_system.cache[type];
- vg_pool *pool = &cache->pool;
- vg_pool_watch( pool, cache_id );
-}
-
-void addon_cache_unwatch( enum addon_type type, u16 cache_id )
-{
- if( !cache_id ) return;
-
- struct addon_cache *cache = &addon_system.cache[type];
- vg_pool *pool = &cache->pool;
- vg_pool_unwatch( pool, cache_id );
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_steam_ugc.h"
-#include "vg/vg_mem_pool.h"
-#include "vg/vg_string.h"
-#include "addon_types.h"
-
-typedef struct addon_reg addon_reg;
-typedef struct addon_cache_entry addon_cache_entry;
-typedef struct addon_alias addon_alias;
-
-struct addon_alias
-{
- enum addon_type type;
- PublishedFileId_t workshop_id;
- char foldername[ ADDON_FOLDERNAME_MAX ];
-};
-
-#define ADDON_REG_HIDDEN 0x1
-#define ADDON_REG_MTZERO 0x2
-#define ADDON_REG_CITY 0x4
-#define ADDON_REG_PREMIUM 0x8
-
-struct addon_system
-{
- struct addon_reg
- {
- addon_alias alias;
- u32 foldername_hash;
- u8 metadata[512]; /* vg_msg buffer */
- u32 metadata_len;
- u32 flags;
-
- u16 cache_id;
-
- enum addon_state{
- k_addon_state_none,
- k_addon_state_indexed,
- k_addon_state_indexed_absent /* gone but not forgotten */
- }
- state;
- }
- *registry;
- u32 registry_count;
-
- /* deffered: updates in main thread */
- u32 registry_type_counts[k_addon_type_max];
-
- struct addon_cache
- {
- struct addon_cache_entry
- {
- u32 reg_index;
- addon_reg *reg_ptr; /* TODO: only use reg_index? */
-
- vg_pool_node poolnode;
-
- enum addon_cache_state{
- k_addon_cache_state_none,
- k_addon_cache_state_loaded,
- k_addon_cache_state_load_request
- }
- state;
- }
- *allocs;
- vg_pool pool;
-
- void *items; /* the real data */
- size_t stride;
- }
- cache[k_addon_type_max];
- SDL_SpinLock sl_cache_using_resources;
-}
-extern addon_system;
-
-void addon_system_init( void );
-u32 addon_count( enum addon_type type, u32 ignoreflags );
-addon_reg *get_addon_from_index( enum addon_type type, u32 index,
- u32 ignoreflags );
-u32 get_index_from_addon( enum addon_type type, addon_reg *a );
-int addon_get_content_folder( addon_reg *reg, vg_str *folder, int async);
-
-/* scanning routines */
-u32 addon_match( addon_alias *alias );
-int addon_alias_eq( addon_alias *a, addon_alias *b );
-void addon_alias_uid( addon_alias *alias, char buf[ADDON_UID_MAX] );
-int addon_uid_to_alias( const char *uid, addon_alias *alias );
-void invalidate_addon_alias( addon_alias *alias );
-void addon_mount_content_folder( enum addon_type type,
- const char *base_folder,
- const char *content_ext );
-void addon_mount_workshop_items(void);
-void async_addon_reg_update( void *data, u32 size );
-addon_reg *addon_mount_local_addon( const char *folder,
- enum addon_type type,
- const char *content_ext );
-u16 addon_cache_fetch( enum addon_type type, u32 reg_index );
-u16 addon_cache_alloc( enum addon_type type, u32 reg_index );
-void *addon_cache_item( enum addon_type type, u16 id );
-void *addon_cache_item_if_loaded( enum addon_type type, u16 id );
-void async_addon_setstate( void *data, u32 size );
-
-void addon_system_pre_update(void);
-u16 addon_cache_create_viewer( enum addon_type type, u16 reg_id);
-
-void addon_cache_watch( enum addon_type type, u16 cache_id );
-void addon_cache_unwatch( enum addon_type type, u16 cache_id );
-u16 addon_cache_create_viewer_from_uid( enum addon_type type,
- char uid[ADDON_UID_MAX] );
+++ /dev/null
-#include "player.h"
-#include "player_render.h"
-#include "player_api.h"
-
-struct addon_type_info addon_type_infos[] =
-{
- [k_addon_type_board] = {
- .local_content_folder = "boards/",
- .cache_stride = sizeof(struct player_board),
- .cache_count = 20
- },
- [k_addon_type_player] = {
- .local_content_folder = "playermodels/",
- .cache_stride = sizeof(struct player_model),
- .cache_count = 20
- },
- [k_addon_type_world] = {
- .local_content_folder = "maps/"
- }
-};
+++ /dev/null
-#pragma once
-
-enum addon_type{
- k_addon_type_none = 0,
- k_addon_type_board = 1,
- k_addon_type_world = 2,
- k_addon_type_player = 3,
- k_addon_type_max
-};
-
-#define ADDON_FOLDERNAME_MAX 64
-#define ADDON_MOUNTED_MAX 128 /* total count that we have knowledge of */
-#define ADDON_UID_MAX 76
-
-#ifdef VG_ENGINE
-
-struct addon_type_info {
- size_t cache_stride;
- u16 cache_count;
- const char *local_content_folder;
-}
-extern addon_type_infos[];
-
-#endif
+++ /dev/null
-#include "world.h"
-#include "audio.h"
-#include "vg/vg_audio_dsp.h"
-
-audio_clip audio_board[] =
-{
- { .path="sound/skate_hpf.ogg" },
- { .path="sound/wheel.ogg" },
- { .path="sound/slide.ogg" },
- { .path="sound/grind_enter.ogg" },
- { .path="sound/grind_exit.ogg" },
- { .path="sound/grind_loop.ogg" },
- { .path="sound/woodslide.ogg" },
- { .path="sound/metalscrape.ogg" },
- { .path="sound/slidetap.ogg" }
-};
-
-audio_clip audio_taps[] =
-{
- { .path="sound/tap0.ogg" },
- { .path="sound/tap1.ogg" },
- { .path="sound/tap2.ogg" },
- { .path="sound/tap3.ogg" }
-};
-
-audio_clip audio_flips[] =
-{
- { .path="sound/lf0.ogg" },
- { .path="sound/lf1.ogg" },
- { .path="sound/lf2.ogg" },
- { .path="sound/lf3.ogg" },
-};
-
-audio_clip audio_hits[] =
-{
- { .path="sound/hit0.ogg" },
- { .path="sound/hit1.ogg" },
- { .path="sound/hit2.ogg" },
- { .path="sound/hit3.ogg" },
- { .path="sound/hit4.ogg" }
-};
-
-audio_clip audio_splash =
-{ .path = "sound/splash.ogg" };
-
-audio_clip audio_jumps[] = {
- { .path = "sound/jump0.ogg" },
- { .path = "sound/jump1.ogg" },
-};
-
-audio_clip audio_footsteps[] = {
- {.path = "sound/step_concrete0.ogg" },
- {.path = "sound/step_concrete1.ogg" },
- {.path = "sound/step_concrete2.ogg" },
- {.path = "sound/step_concrete3.ogg" }
-};
-
-audio_clip audio_footsteps_grass[] = {
- {.path = "sound/step_bush0.ogg" },
- {.path = "sound/step_bush1.ogg" },
- {.path = "sound/step_bush2.ogg" },
- {.path = "sound/step_bush3.ogg" },
- {.path = "sound/step_bush4.ogg" },
- {.path = "sound/step_bush5.ogg" }
-};
-
-audio_clip audio_footsteps_wood[] = {
- {.path = "sound/step_wood0.ogg" },
- {.path = "sound/step_wood1.ogg" },
- {.path = "sound/step_wood2.ogg" },
- {.path = "sound/step_wood3.ogg" },
- {.path = "sound/step_wood4.ogg" },
- {.path = "sound/step_wood5.ogg" }
-};
-
-audio_clip audio_lands[] = {
- { .path = "sound/land0.ogg" },
- { .path = "sound/land1.ogg" },
- { .path = "sound/land2.ogg" },
- { .path = "sound/landsk0.ogg" },
- { .path = "sound/landsk1.ogg" },
- { .path = "sound/onto.ogg" },
- { .path = "sound/outo.ogg" },
-};
-
-audio_clip audio_water[] = {
- { .path = "sound/wave0.ogg" },
- { .path = "sound/wave1.ogg" },
- { .path = "sound/wave2.ogg" },
- { .path = "sound/wave3.ogg" },
- { .path = "sound/wave4.ogg" },
- { .path = "sound/wave5.ogg" }
-};
-
-audio_clip audio_grass[] = {
- { .path = "sound/grass0.ogg" },
- { .path = "sound/grass1.ogg" },
- { .path = "sound/grass2.ogg" },
- { .path = "sound/grass3.ogg" },
-};
-
-audio_clip audio_ambience[] =
-{
- { .path="sound/town_generic.ogg" }
-};
-
-audio_clip audio_gate_pass = {
- .path = "sound/gate_pass.ogg"
-};
-
-audio_clip audio_gate_lap = {
- .path = "sound/gate_lap.ogg"
-};
-
-audio_clip audio_gate_ambient = {
-.path = "sound/gate_ambient.ogg"
-};
-
-audio_clip audio_rewind[] = {
-{ .path = "sound/rewind_start.ogg" },
-{ .path = "sound/rewind_end_1.5.ogg" },
-{ .path = "sound/rewind_end_2.5.ogg" },
-{ .path = "sound/rewind_end_6.5.ogg" },
-{ .path = "sound/rewind_clack.ogg" },
-};
-
-audio_clip audio_ui[] = {
- { .path = "sound/ui_click.ogg" },
- { .path = "sound/ui_ding.ogg" },
- { .path = "sound/teleport.ogg" },
- { .path = "sound/ui_move.ogg" }
-};
-
-audio_clip audio_challenge[] = {
- { .path = "sound/objective0.ogg" },
- { .path = "sound/objective1.ogg" },
- { .path = "sound/objective_win.ogg" },
- { .path = "sound/ui_good.ogg" },
- { .path = "sound/ui_inf.ogg" },
- { .path = "sound/ui_ok.ogg" },
- { .path = "sound/objective_fail.ogg" }
-};
-
-struct air_synth_data air_audio_data;
-
-static void audio_air_synth_get_samples( void *_data, f32 *buf, u32 count ){
- struct air_synth_data *data = _data;
-
- SDL_AtomicLock( &data->sl );
- f32 spd = data->speed;
- SDL_AtomicUnlock( &data->sl );
-
- f32 s0 = sinf(data->t*2.0f),
- s1 = sinf(data->t*0.43f),
- s2 = sinf(data->t*1.333f),
- sm = vg_clampf( data->speed / 45.0f, 0, 1 ),
- ft = (s0*s1*s2)*0.5f+0.5f,
- f = vg_lerpf( 200.0f, 1200.0f, sm*0.7f + ft*0.3f ),
- vol = 0.25f * sm;
-
- dsp_init_biquad_butterworth_lpf( &data->lpf, f );
-
- for( u32 i=0; i<count; i ++ ){
- f32 v = (vg_randf64(&vg_dsp.rand) * 2.0f - 1.0f) * vol;
- v = dsp_biquad_process( &data->lpf, v );
-
- buf[i*2+0] = v;
- buf[i*2+1] = v;
- }
-
- data->t += (f32)(count)/44100.0f;
-};
-
-static audio_clip air_synth = {
- .flags = k_audio_format_gen,
- .size = 0,
- .func = audio_air_synth_get_samples,
- .data = &air_audio_data
-};
-
-void audio_init(void)
-{
- audio_clip_loadn( audio_board, VG_ARRAY_LEN(audio_board), NULL );
- audio_clip_loadn( audio_taps, VG_ARRAY_LEN(audio_taps), NULL );
- audio_clip_loadn( audio_flips, VG_ARRAY_LEN(audio_flips), NULL );
- audio_clip_loadn( audio_hits, VG_ARRAY_LEN(audio_hits), NULL );
- audio_clip_loadn( audio_ambience, VG_ARRAY_LEN(audio_ambience), NULL );
- audio_clip_loadn( &audio_splash, 1, NULL );
- audio_clip_loadn( &audio_gate_pass, 1, NULL );
- audio_clip_loadn( &audio_gate_lap, 1, NULL );
- audio_clip_loadn( &audio_gate_ambient, 1, NULL );
-
- audio_clip_loadn( audio_jumps, VG_ARRAY_LEN(audio_jumps), NULL );
- audio_clip_loadn( audio_lands, VG_ARRAY_LEN(audio_lands), NULL );
- audio_clip_loadn( audio_water, VG_ARRAY_LEN(audio_water), NULL );
- audio_clip_loadn( audio_grass, VG_ARRAY_LEN(audio_grass), NULL );
- audio_clip_loadn( audio_footsteps, VG_ARRAY_LEN(audio_footsteps), NULL );
- audio_clip_loadn( audio_footsteps_grass,
- VG_ARRAY_LEN(audio_footsteps_grass), NULL );
- audio_clip_loadn( audio_footsteps_wood,
- VG_ARRAY_LEN(audio_footsteps_wood), NULL );
- audio_clip_loadn( audio_rewind, VG_ARRAY_LEN(audio_rewind), NULL );
- audio_clip_loadn( audio_ui, VG_ARRAY_LEN(audio_ui), NULL );
- audio_clip_loadn( audio_challenge, VG_ARRAY_LEN(audio_challenge), NULL );
-
- audio_lock();
- audio_set_lfo_wave( 0, k_lfo_polynomial_bipolar, 80.0f );
- audio_set_lfo_frequency( 0, 20.0f );
-
- air_audio_data.channel = audio_get_first_idle_channel();
- if( air_audio_data.channel )
- audio_channel_init( air_audio_data.channel, &air_synth, 0 );
-
- audio_unlock();
-}
-
-void audio_ambient_sprite_play( v3f co, audio_clip *clip )
-{
- audio_lock();
- u16 group_id = 0xfff0;
- audio_channel *ch = audio_get_group_idle_channel( group_id, 4 );
-
- if( ch ){
- audio_channel_init( ch, clip, AUDIO_FLAG_SPACIAL_3D );
- audio_channel_group( ch, group_id );
- audio_channel_set_spacial( ch, co, 80.0f );
- audio_channel_edit_volume( ch, 1.0f, 1 );
- ch = audio_relinquish_channel( ch );
- }
- audio_unlock();
-}
-
-enum audio_sprite_type world_audio_sample_sprite_random(v3f origin, v3f output);
-void audio_ambient_sprites_update( world_instance *world, v3f co )
-{
- static float accum = 0.0f;
- accum += vg.time_delta;
-
- if( accum > 0.1f )
- accum -= 0.1f;
- else return;
-
- v3f sprite_pos;
- enum audio_sprite_type sprite_type =
- world_audio_sample_sprite_random( co, sprite_pos );
-
- if( sprite_type != k_audio_sprite_type_none ){
- if( sprite_type == k_audio_sprite_type_grass ){
- audio_ambient_sprite_play( sprite_pos,
- &audio_grass[vg_randu32(&vg.rand)%4] );
- }
- else if( sprite_type == k_audio_sprite_type_water ){
- if( world->water.enabled ){
- audio_ambient_sprite_play( sprite_pos,
- &audio_water[vg_randu32(&vg.rand)%6] );
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-
-#include "vg/vg_engine.h"
-#include "vg/vg_audio.h"
-#include "vg/vg_audio_dsp.h"
-#include "world.h"
-
-struct air_synth_data {
- f32 speed;
-
- /* internal */
- f32 t;
- struct dsp_biquad lpf;
- SDL_SpinLock sl;
-
- /* not used in locking */
- audio_channel *channel;
-}
-extern air_audio_data;
-
-void audio_init(void);
-void audio_ambient_sprite_play( v3f co, audio_clip *clip );
-void audio_ambient_sprites_update( world_instance *world, v3f co );
-
-/* TODO(ASSETS):
- * Have these as asignable ID's and not a bunch of different arrays.
- */
-extern audio_clip audio_board[];
-extern audio_clip audio_taps[];
-extern audio_clip audio_flips[];
-extern audio_clip audio_hits[];
-extern audio_clip audio_splash;
-extern audio_clip audio_jumps[];
-extern audio_clip audio_footsteps[];
-extern audio_clip audio_footsteps_grass[];
-extern audio_clip audio_footsteps_wood[];
-extern audio_clip audio_lands[];
-extern audio_clip audio_water[];
-extern audio_clip audio_grass[];
-extern audio_clip audio_ambience[];
-extern audio_clip audio_gate_pass;
-extern audio_clip audio_gate_lap;
-extern audio_clip audio_gate_ambient;
-extern audio_clip audio_rewind[];
-extern audio_clip audio_ui[];
-extern audio_clip audio_challenge[];
-
-enum audio_sprite_type
-{
- k_audio_sprite_type_none,
- k_audio_sprite_type_grass,
- k_audio_sprite_type_water
-};
#include "vg/vg_build.h"
#include "vg/vg_build_utils_shader.h"
#include "vg/vg_msg.h"
-#include "addon_types.h"
+#include "src/addon_types.h"
#include "vg/vg_m.h"
-#include "model.h"
-#include "model.c"
+#include "src/model.h"
+#include "src/model.c"
/*
* Addon metadata utilities
vg_syscall( "cp bin/skaterift_blender.zip bin/%s/tools/", proj->uid.buffer );
}
-#include "build_control_overlay.c"
+#include "src/build_control_overlay.c"
void build_game_bin( struct vg_project *proj, struct vg_compiler_env *env )
{
vg_strcat( &conf.link, "-lws2_32 " );
}
- //vg_strcat( &conf.defines, "-DSKATERIFT " );
- vg_strcat( &conf.defines, "-DHGATE " );
-
vg_make_app( proj, &(struct vg_engine_config )
{
.fixed_update_hz = 60,
.custom_game_settings = 0,
.custom_shaders = 1
},
- env, &conf, "client.c", "skaterift" );
+ env, &conf, "src/client.c", "skaterift" );
vg_add_controller_database( proj );
}
vg_strcat( &sources, sqlite.path.buffer );
struct vg_compiler_conf conf = {0};
- vg_strcat( &conf.include, "-I. -I./dep " );
+ vg_strcat( &conf.include, "-Isrc -I./dep " );
vg_strcat( &conf.library, "-L./vg/dep/steam " );
- vg_strcat( &conf.link, "-ldl -lpthread -lm -lsdkencryptedappticket -lsteam_api " );
+ vg_strcat( &conf.link, "-ldl -lpthread -lm "
+ "-lsdkencryptedappticket -lsteam_api " );
vg_add_blob( proj, "vg/dep/steam/libsteam_api.so", "" );
vg_add_blob( proj, "vg/dep/steam/libsdkencryptedappticket.so", "" );
-clang -fsanitize=address -O0 -I./vg build.c vg/vg_tool.c -o /tmp/tmpsr && /tmp/tmpsr $@
+clang -fsanitize=address -O0 -I. -I./vg build.c vg/vg_tool.c -o /tmp/tmpsr && /tmp/tmpsr $@
+++ /dev/null
-/*
- * Script to load the overlay model and generate an enum referencing each
- * submesh by its name.
- */
-void build_control_overlay(void)
-{
- FILE *hdr = fopen( "control_overlay.h.generated", "w" );
- mdl_context ctx;
- mdl_open( &ctx, "content_skaterift/models/rs_overlay.mdl", NULL );
- mdl_load_metadata_block( &ctx, NULL );
- mdl_close( &ctx );
-
- for( u32 i=0; i<mdl_arrcount( &ctx.meshs ); i ++ )
- {
- mdl_mesh *mesh = mdl_arritm( &ctx.meshs, i );
- fprintf( hdr, " %s = %u,\n",
- mdl_pstr( &ctx, mesh->pstr_name ), mesh->submesh_start );
- }
-
- fclose( hdr );
-}
+++ /dev/null
-#include "vg/vg_opt.h"
-#include "vg/vg_loader.h"
-#include "vg/vg_io.h"
-#include "vg/vg_audio.h"
-#include "vg/vg_async.h"
-
-#include "client.h"
-#include "render.h"
-#include "network.h"
-#include "player_remote.h"
-#include "menu.h"
-
-const char* __asan_default_options() { return "detect_leaks=0"; }
-
-struct game_client g_client =
-{
- .demo_mode = 1
-};
-
-static void async_client_ready( void *payload, u32 size )
-{
- g_client.loaded = 1;
-
- if( network_client.auto_connect )
- network_client.user_intent = k_server_intent_online;
-
- menu_at_begin();
-}
-
-void vg_load(void)
-{
- vg_audio.always_keep_compressed = 1;
- vg_loader_step( render_init, NULL );
-
- game_load();
-
- vg_async_call( async_client_ready, NULL, 0 );
-}
-
-void vg_preload(void)
-{
-vg_info(" Copyright . . . -----, ,----- ,---. .---. \n" );
-vg_info(" 2021-2024 |\\ /| | / | | | | /| \n" );
-vg_info(" | \\ / | +-- / +----- +---' | / | \n" );
-vg_info(" | \\ / | | / | | \\ | / | \n" );
-vg_info(" | \\/ | | / | | \\ | / | \n" );
-vg_info(" ' ' '--' [] '----- '----- ' ' '---' "
- "SOFTWARE\n" );
-
- /* please forgive me! */
- u32 sz; char *drm;
- if( (drm = vg_file_read_text( vg_mem.scratch, "DRM", &sz )) )
- if( !strcmp(drm, "blibby!") )
- g_client.demo_mode = 0;
-
- vg_loader_step( remote_players_init, NULL );
-
- steam_init();
- vg_loader_step( NULL, steam_end );
- vg_loader_step( network_init, network_end );
-}
-
-void vg_launch_opt(void)
-{
- const char *arg;
-
- if( vg_long_opt( "noauth" ) )
- network_client.auth_mode = eServerModeNoAuthentication;
-
- if( (arg = vg_long_opt_arg( "server" )) )
- network_set_host( arg, NULL );
-
- if( vg_long_opt( "demo" ) )
- g_client.demo_mode = 1;
-
- game_launch_opt();
-}
-
-int main( int argc, char *argv[] )
-{
- network_set_host( "skaterift.com", NULL );
- vg_mem.use_libc_malloc = 1;
- vg_set_mem_quota( 160*1024*1024 );
- vg_enter( argc, argv, "Voyager Game Engine" );
- return 0;
-}
-
-#include "skaterift.c"
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-
-/*
- * client - entry point. window, common things like render init.. etc
- * vg - backend code
- * game - top layer: game content, state
- */
-
-struct game_client
-{
- bool loaded, demo_mode;
-}
-extern g_client;
-
-/* game defined */
-void game_launch_opt( void );
-void game_load( void );
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#ifndef COMMON_H
-#define COMMON_H
-
-#endif /* COMMON_H */
+++ /dev/null
-#include "control_overlay.h"
-#include "model.h"
-#include "input.h"
-#include "player.h"
-#include "player_skate.h"
-#include "player_walk.h"
-#include "shaders/model_menu.h"
-#include "vg/vg_engine.h"
-#include "vg/vg_mem.h"
-#include "vg/vg_m.h"
-
-struct control_overlay control_overlay = { .enabled = 1 };
-
-static void render_overlay_mesh( enum control_overlay_mesh index )
-{
- mdl_draw_submesh( mdl_arritm( &control_overlay.mdl.submeshs, index ) );
-}
-
-void control_overlay_init(void)
-{
- void *alloc = vg_mem.rtmemory;
- mdl_context *mdl = &control_overlay.mdl;
-
- mdl_open( mdl, "models/rs_overlay.mdl", alloc );
- mdl_load_metadata_block( mdl, alloc );
- mdl_async_full_load_std( mdl );
- mdl_close( mdl );
-
- vg_async_stall();
-
- if( mdl_arrcount( &mdl->textures ) )
- {
- mdl_texture *tex = mdl_arritm( &mdl->textures, 0 );
- control_overlay.tex = tex->glname;
- }
- else
- {
- control_overlay.tex = vg.tex_missing;
- vg_error( "No texture in control overlay\n" );
- }
-
- vg_console_reg_var( "control_overlay", &control_overlay.enabled,
- k_var_dtype_i32, VG_VAR_PERSISTENT );
-}
-
-static void draw_key( bool press, bool wide )
-{
- if( wide ) render_overlay_mesh( press? ov_shift_down: ov_shift );
- else render_overlay_mesh( press? ov_key_down: ov_key );
-}
-
-static void colorize( bool press, bool condition )
-{
- v4f cnorm = { 1,1,1,0.76f },
- cdis = { 1,1,1,0.35f },
- chit = { 1,0.5f,0.2f,0.8f };
-
- if( condition )
- if( press )
- shader_model_menu_uColour( chit );
- else
- shader_model_menu_uColour( cnorm );
- else
- shader_model_menu_uColour( cdis );
-}
-
-void control_overlay_render(void)
-{
- if( !control_overlay.enabled ) return;
- if( skaterift.activity != k_skaterift_default ) return;
-
- glEnable(GL_BLEND);
- glDisable(GL_DEPTH_TEST);
- glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
- glBlendEquation(GL_FUNC_ADD);
-
- m4x4f ortho;
- f32 r = (f32)vg.window_x / (f32)vg.window_y,
- fl = -r,
- fr = r,
- fb = 1.0f,
- ft = -1.0f,
- rl = 1.0f / (fr-fl),
- tb = 1.0f / (ft-fb);
-
- m4x4_zero( ortho );
- ortho[0][0] = 2.0f * rl;
- ortho[2][1] = 2.0f * tb;
- ortho[3][0] = (fr + fl) * -rl;
- ortho[3][1] = (ft + fb) * -tb;
- ortho[3][3] = 1.0f;
-
- v4f cnorm = { 1,1,1,0.76f },
- cdis = { 1,1,1,0.35f },
- chit = { 1,0.5f,0.2f,0.8f };
-
- shader_model_menu_use();
- shader_model_menu_uTexMain( 1 );
- shader_model_menu_uPv( ortho );
- shader_model_menu_uColour( cnorm );
-
- mdl_context *mdl = &control_overlay.mdl;
- mesh_bind( &mdl->mesh );
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, control_overlay.tex );
-
- enum player_subsystem subsytem = localplayer.subsystem;
-
- m4x3f mmdl;
- m4x3_identity( mmdl );
-
- bool in_air = 0, grinding = 0;
-
- if( subsytem == k_player_subsystem_walk )
- in_air = player_walk.state.activity == k_walk_activity_air;
- else if( subsytem == k_player_subsystem_skate )
- in_air = player_skate.state.activity < k_skate_activity_ground;
-
- grinding = (subsytem == k_player_subsystem_skate) &&
- (player_skate.state.activity >= k_skate_activity_grind_any);
-
- if( vg_input.display_input_method == k_input_method_controller )
- {
- bool press_jump = player_skate.state.jump_charge > 0.2f;
- u8 lb_down = 0, rb_down = 0;
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, vg_end
- }, &rb_down );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, vg_end
- }, &lb_down );
- f32 lt_amt = 0.0f, rt_amt = 0.0f;
- vg_exec_input_program( k_vg_input_type_axis_f32,
- (vg_input_op[]){ vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERLEFT, vg_end },
- <_amt );
- vg_exec_input_program( k_vg_input_type_axis_f32,
- (vg_input_op[]){ vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, vg_end },
- &rt_amt );
-
- /* joystick L */
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- mmdl[3][0] = -r + 0.375f;
- mmdl[3][2] = 1.0f - 0.375f;
- shader_model_menu_uMdl( mmdl );
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( 0, 1 );
- render_overlay_mesh( ov_ls_circ_skate );
-
- colorize( steer[1]>=0.5f, press_jump );
- render_overlay_mesh( ov_ls_circ_backflip );
- colorize( steer[1]<=-0.5f, press_jump );
- render_overlay_mesh( ov_ls_circ_frontflip );
-
- colorize( steer[1] > 0.7f, !press_jump && !in_air );
- render_overlay_mesh( ov_ls_circ_manual );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( 0, 1 );
- render_overlay_mesh( ov_ls_circ_walk );
- }
-
- mmdl[3][0] += steer[0]*0.125f*0.75f;
- mmdl[3][2] += steer[1]*0.125f*0.75f;
-
- colorize( 0, 1 );
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_ls );
-
- /* joystick R */
- mmdl[3][0] = r - 0.375f;
- mmdl[3][2] = 1.0f - 0.375f;
- shader_model_menu_uMdl( mmdl );
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( rt_amt > 0.5f, in_air );
- render_overlay_mesh( ov_rs_circ_grab );
- colorize( 0, in_air );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( 0, 1 );
- render_overlay_mesh( ov_rs_circ_look );
- }
-
- v2f jlook;
- joystick_state( k_srjoystick_look, jlook );
-
- mmdl[3][0] += jlook[0]*0.125f*0.75f;
- mmdl[3][2] += jlook[1]*0.125f*0.75f;
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_rs );
-
-
-
- /* LEFT UPPERS */
- mmdl[3][0] = -r;
- mmdl[3][2] = -1.0f;
- shader_model_menu_uMdl( mmdl );
-
- /* LB -------------------------------------------------------------- */
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( lb_down, !in_air );
- render_overlay_mesh( ov_carve_l );
- }
- else
- colorize( 0, 0 );
-
- render_overlay_mesh( lb_down? ov_lb_down: ov_lb );
-
- /* LT ---------------------------------------------------------------- */
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( 0, 0 );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( lt_amt>0.2f, 1 );
- render_overlay_mesh( ov_lt_run );
- }
-
- render_overlay_mesh( ov_lt );
-
- mmdl[3][2] += lt_amt*0.125f*0.5f;
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_lt_act );
-
- /* RIGHT UPPERS */
- mmdl[3][0] = r;
- mmdl[3][2] = -1.0f;
- shader_model_menu_uMdl( mmdl );
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( rb_down, !in_air );
- render_overlay_mesh( ov_carve_r );
- }
- else
- colorize( 0, 0 );
-
- render_overlay_mesh( rb_down? ov_rb_down: ov_rb );
-
- /* RT ---------------------------------------------------------------- */
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( rt_amt>0.2f, in_air );
- render_overlay_mesh( ov_rt_grab );
- colorize( rt_amt>0.2f, !in_air );
- render_overlay_mesh( ov_rt_crouch );
- colorize( rt_amt>0.2f, 1 );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( 0, 0 );
- }
-
- render_overlay_mesh( ov_rt );
-
- mmdl[3][2] += rt_amt*0.125f*0.5f;
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_rt_act );
-
- /* RIGHT SIDE BUTTONS */
- bool press_a = 0, press_b = 0, press_x = 0, press_y = 0,
- press_dpad_w = 0, press_dpad_e = 0, press_dpad_n = 0, press_dpad_s = 0,
- press_menu = 0, press_back = 0;
-
- bool is_ps = 0;
- if( (vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS3) ||
- (vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS4) ||
- (vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS5) )
- {
- is_ps = 1;
- }
-
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_A, vg_end }, &press_a );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_B, vg_end }, &press_b );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_X, vg_end }, &press_x );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_Y, vg_end }, &press_y );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_LEFT, vg_end }, &press_dpad_w );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, vg_end }, &press_dpad_e );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_UP, vg_end }, &press_dpad_n );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_DOWN, vg_end }, &press_dpad_s );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_BACK, vg_end }, &press_back );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_START, vg_end }, &press_menu );
-
- mmdl[3][0] = r;
- mmdl[3][2] = 0.0f;
- shader_model_menu_uMdl( mmdl );
-
- /* B / KICKFLIP / PUSH */
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( press_b, !in_air );
- render_overlay_mesh( ov_text_b_push );
- colorize( press_b, in_air );
- render_overlay_mesh( ov_text_b_kickflip );
- colorize( press_b, 1 );
- }
- else
- {
- colorize( 0, 0 );
- }
-
- if( is_ps ) render_overlay_mesh( press_b? ov_b_down_ps: ov_b_ps );
- else render_overlay_mesh( press_b? ov_b_down: ov_b );
-
- /* Y / SKATE / WALK / GLIDE */
-
- if( subsytem == k_player_subsystem_skate )
- {
- if( localplayer.have_glider )
- {
- colorize( press_y, !in_air );
- render_overlay_mesh( ov_text_y_walk_lwr );
- colorize( press_y, in_air );
- render_overlay_mesh( ov_text_y_glide );
- }
- else
- {
- colorize( press_y, 1 );
- render_overlay_mesh( ov_text_y_walk );
- }
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( press_y, player_walk.state.activity < k_walk_activity_inone );
- render_overlay_mesh( ov_text_y_skate );
- }
- else if( subsytem == k_player_subsystem_glide )
- {
- colorize( press_y, 1 );
- render_overlay_mesh( ov_text_y_skate );
- }
- else
- colorize( 0, 0 );
-
- if( is_ps ) render_overlay_mesh( press_y? ov_y_down_ps: ov_y_ps );
- else render_overlay_mesh( press_y? ov_y_down: ov_y );
-
- /* X / TREFLIP */
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( press_x, in_air );
- render_overlay_mesh( ov_text_x_treflip );
- }
- else
- colorize( press_x, 0 );
-
- if( is_ps ) render_overlay_mesh( press_x? ov_x_down_ps: ov_x_ps );
- else render_overlay_mesh( press_x? ov_x_down: ov_x );
-
- /* A / JUMP / SHUVIT */
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( press_a, !in_air );
- render_overlay_mesh( ov_text_a_jump );
- colorize( press_a, in_air );
- render_overlay_mesh( ov_text_a_shuvit );
- colorize( press_a, 1 );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( press_a, !in_air );
- render_overlay_mesh( ov_text_a_jump_mid );
- }
-
- if( is_ps ) render_overlay_mesh( press_a? ov_a_down_ps: ov_a_ps );
- else render_overlay_mesh( press_a? ov_a_down: ov_a );
-
- /* JUMP CHARGE */
- if( subsytem == k_player_subsystem_skate )
- {
- if( player_skate.state.jump_charge > 0.01f )
- {
- mmdl[0][0] = player_skate.state.jump_charge * 0.465193f;
- mmdl[3][0] += -0.4375f;
- mmdl[3][2] += 0.09375f;
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_jump_ind );
- mmdl[0][0] = 1.0f;
- }
- }
-
-
- /* DPAD --------------------------------------------------- */
-
- mmdl[3][0] = -r;
- mmdl[3][2] = 0.0f;
- shader_model_menu_uMdl( mmdl );
- colorize( 0, 1 );
- render_overlay_mesh( ov_dpad );
-
- colorize( press_dpad_e, 1 );
- render_overlay_mesh( ov_text_de_camera );
- if( press_dpad_e )
- render_overlay_mesh( ov_dpad_e );
-
- colorize( press_dpad_w, 1 );
- render_overlay_mesh( ov_text_dw_rewind );
- if( press_dpad_w )
- render_overlay_mesh( ov_dpad_w );
-
- if( subsytem == k_player_subsystem_dead )
- {
- colorize( press_dpad_n, 1 );
- render_overlay_mesh( ov_text_dn_respawn );
- }
- else colorize( press_dpad_n, 0 );
- if( press_dpad_n )
- render_overlay_mesh( ov_dpad_n );
-
- colorize( press_dpad_s, 0 );
- if( press_dpad_s )
- render_overlay_mesh( ov_dpad_s );
-
-
- /* WEIGHT */
- if( subsytem == k_player_subsystem_skate )
- {
- /* stored indicator text */
- mmdl[3][0] = r -0.842671f;
- mmdl[3][2] = -1.0f + 0.435484f;
- colorize( 0, !in_air );
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_text_stored );
-
- mmdl[0][0] = v3_length( player_skate.state.throw_v ) / k_mmthrow_scale;
- shader_model_menu_uMdl( mmdl );
- colorize( 0, !in_air );
- render_overlay_mesh( ov_stored_ind );
-
- static f32 collect = 0.0f;
- collect = vg_lerpf( collect, player_skate.collect_feedback,
- vg.time_frame_delta * 15.0f );
- collect = vg_clampf( collect, 0.0f, 1.0f );
-
- mmdl[0][0] = collect;
- mmdl[3][2] += 0.015625f;
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_stored_ind );
- }
-
- mmdl[0][0] = 1.0f;
- mmdl[3][0] = 0.0f;
- mmdl[3][2] = -1.0f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_menu, 1 );
- render_overlay_mesh( press_menu? ov_met_r_down: ov_met_r );
- render_overlay_mesh( ov_text_met_menu );
-
- colorize( press_back, 0 );
- render_overlay_mesh( press_back? ov_met_l_down: ov_met_l );
-
- colorize( 0, 0 );
- render_overlay_mesh( ov_met );
- }
- else
- {
- static v2f gd;
- v2_lerp( gd, player_skate.state.grab_mouse_delta, vg.time_frame_delta*20.0f, gd );
-
- /* CTRL || CARVE */
- if( subsytem == k_player_subsystem_skate )
- {
- bool press_ctrl = vg_getkey(SDLK_LCTRL);
- mmdl[3][0] = -r + 0.25f;
- mmdl[3][2] = 1.0f - 0.125f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_ctrl, !in_air && !grinding );
- draw_key( press_ctrl, 1 );
- render_overlay_mesh( ov_text_carve );
- }
-
- /* SHIFT || CROUCH / GRAB / RUN */
- bool press_shift = vg_getkey( SDLK_LSHIFT );
- if( subsytem == k_player_subsystem_skate ||
- subsytem == k_player_subsystem_walk )
- {
- mmdl[3][0] = -r + 0.25f;
- mmdl[3][2] = 1.0f - 0.125f - 0.25f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_shift, !grinding );
- draw_key( press_shift, 1 );
- render_overlay_mesh( ov_text_shift );
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( press_shift, !in_air && !grinding );
- render_overlay_mesh( ov_text_crouch );
- colorize( press_shift, in_air && !grinding );
- render_overlay_mesh( ov_text_grab );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- render_overlay_mesh( ov_text_run );
- }
- }
-
- if( subsytem == k_player_subsystem_skate )
- {
- /* stored indicator text */
- mmdl[3][0] = -r + 0.25f + 0.203125f + 0.007812f;
- colorize( 0, !in_air );
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_text_stored );
-
- mmdl[0][0] = v3_length( player_skate.state.throw_v ) / k_mmthrow_scale;
- shader_model_menu_uMdl( mmdl );
- colorize( 0, !in_air );
- render_overlay_mesh( ov_stored_ind );
-
- static f32 collect = 0.0f;
- collect = vg_lerpf( collect, player_skate.collect_feedback,
- vg.time_frame_delta * 15.0f );
- collect = vg_clampf( collect, 0.0f, 1.0f );
-
- mmdl[0][0] = collect;
- mmdl[3][2] += 0.015625f;
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_stored_ind );
- }
-
- /* -1 */
- if( subsytem != k_player_subsystem_dead )
- {
- bool press_c = vg_getkey(SDLK_c);
- mmdl[0][0] = 1.0f;
- mmdl[3][0] = -r + 0.125f + 1.0f;
- mmdl[3][2] = 1.0f - 0.125f - 0.25f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_c, 1 );
- draw_key( press_c, 0 );
- render_overlay_mesh( ov_text_camera );
- }
-
- /* +0 */
- mmdl[0][0] = 1.0f;
- mmdl[3][2] = 1.0f - 0.125f - 0.25f - 0.25f;
-
- /* A || LEFT */
- if( subsytem != k_player_subsystem_dead )
- {
- bool press_a = vg_getkey(SDLK_a);
- mmdl[3][0] = -r + 0.125f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_a, 1 );
- draw_key( press_a, 0 );
- render_overlay_mesh( ov_text_left );
- }
-
- bool press_jump = player_skate.state.jump_charge < 0.2f;
-
- /* S || MANUAL / BACKFLIP */
- bool press_s = vg_getkey(SDLK_s);
- mmdl[3][0] = -r + 0.125f + 0.25f;
- shader_model_menu_uMdl( mmdl );
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( press_s, !in_air );
- draw_key( press_s, 0 );
- render_overlay_mesh( ov_text_s );
- /* backflip/manual */
- colorize( press_s, !in_air && !press_jump );
- render_overlay_mesh( ov_text_back_flip );
- colorize( press_s, !in_air && press_jump );
- render_overlay_mesh( ov_text_manual );
- }
- else if( subsytem != k_player_subsystem_dead )
- {
- colorize( press_s, 1 );
- draw_key( press_s, 0 );
- render_overlay_mesh( ov_text_s );
- render_overlay_mesh( ov_text_back );
- }
-
- /* D || RIGHT */
- if( subsytem != k_player_subsystem_dead )
- {
- bool press_d = vg_getkey(SDLK_d);
- mmdl[3][0] = -r + 0.125f + 0.25f + 0.25f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_d, 1 );
- draw_key( press_d, 0 );
- render_overlay_mesh( ov_text_right );
- }
-
- /* +1 */
- mmdl[3][2] = 1.0f - 0.125f - 0.25f - 0.25f - 0.25f;
-
- /* Q */
- if( subsytem == k_player_subsystem_dead )
- {
- bool press_q = vg_getkey(SDLK_q);
- mmdl[3][0] = -r + 0.125f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_q, 1 );
- draw_key( press_q, 0 );
- render_overlay_mesh( ov_text_respawn );
- }
-
- /* W || PUSH / FRONTFLIP */
- bool press_w = vg_getkey(SDLK_w);
- mmdl[3][0] = -r + 0.125f + 0.25f;
- shader_model_menu_uMdl( mmdl );
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( press_w, !in_air );
- draw_key( press_w, 0 );
- render_overlay_mesh( ov_text_w );
- /* frontflip/push */
- colorize( press_w, !in_air && !press_jump );
- render_overlay_mesh( ov_text_front_flip );
- colorize( press_w, !in_air && press_jump );
- render_overlay_mesh( ov_text_push );
- }
- else if( subsytem != k_player_subsystem_dead )
- {
- colorize( press_w, 1 );
- draw_key( press_w, 0 );
- render_overlay_mesh( ov_text_w );
- render_overlay_mesh( ov_text_forward );
- }
-
- /* E */
- bool press_e = vg_getkey(SDLK_e);
- mmdl[3][0] = -r + 0.125f + 0.25f + 0.25f;
-
- shader_model_menu_uMdl( mmdl );
-
- if( subsytem == k_player_subsystem_skate )
- {
- if( localplayer.have_glider )
- {
- colorize( press_e, !in_air );
- render_overlay_mesh( ov_text_walk_lwr );
- colorize( press_e, in_air );
- render_overlay_mesh( ov_text_glide );
- }
- else
- {
- colorize( press_e, 1 );
- render_overlay_mesh( ov_text_walk );
- }
- }
- else if( subsytem == k_player_subsystem_glide )
- {
- colorize( press_e, 1 );
- render_overlay_mesh( ov_text_skate );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( press_e, player_walk.state.activity < k_walk_activity_inone );
- render_overlay_mesh( ov_text_skate );
- }
-
- if( subsytem != k_player_subsystem_dead )
- {
- draw_key( press_e, 0 );
- render_overlay_mesh( ov_text_e );
- }
-
- /* R */
- bool press_r = vg_getkey(SDLK_r);
- mmdl[3][0] = -r + 0.125f + 0.25f + 0.25f + 0.25f;
- shader_model_menu_uColour( cnorm );
- shader_model_menu_uMdl( mmdl );
-
- colorize( press_r, 1 );
- draw_key( press_r, 0 );
- render_overlay_mesh( ov_text_rewind );
-
- /* space */
- bool press_space = vg_getkey(SDLK_SPACE);
- mmdl[3][0] = 0.0f;
- mmdl[3][2] = 1.0f - 0.125f;
-
-
- if( subsytem == k_player_subsystem_skate ||
- subsytem == k_player_subsystem_walk )
- {
- shader_model_menu_uMdl( mmdl );
- colorize( press_space, !in_air );
-
- render_overlay_mesh( press_space?
- ov_space_down: ov_space );
- render_overlay_mesh( ov_text_jump );
- }
-
- if( subsytem == k_player_subsystem_skate )
- {
- if( player_skate.state.jump_charge > 0.01f )
- {
- mmdl[0][0] = player_skate.state.jump_charge;
- mmdl[3][0] = -0.4375f;
- shader_model_menu_uMdl( mmdl );
- render_overlay_mesh( ov_jump_ind );
- }
- }
-
- bool press_esc = vg_getkey(SDLK_ESCAPE);
- mmdl[0][0] = 1.0f;
- mmdl[3][0] = -r + 0.125f;;
- mmdl[3][2] = -1.0f + 0.125f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_esc, 1 );
- render_overlay_mesh( ov_text_menu );
- render_overlay_mesh( press_esc? ov_key_menu_down: ov_key_menu );
- mmdl[3][0] = r - 0.38f;
- mmdl[3][2] = 0.0f;
- shader_model_menu_uMdl( mmdl );
- colorize( press_shift, in_air );
-
- if( subsytem == k_player_subsystem_skate )
- {
- render_overlay_mesh( ov_mouse_grabs );
-
- if( in_air && press_shift )
- {
- mmdl[3][0] += gd[0]*0.125f;
- mmdl[3][2] += gd[1]*0.125f;
- }
- }
-
- shader_model_menu_uMdl( mmdl );
-
- bool lmb = button_press( k_srbind_trick0 ),
- rmb = button_press( k_srbind_trick1 );
-
- if( subsytem == k_player_subsystem_skate )
- {
- colorize( 0, press_space || in_air );
- render_overlay_mesh( ov_mouse );
-
- colorize( lmb&&!rmb, press_space || in_air );
- render_overlay_mesh( ov_text_shuvit );
-
- colorize( lmb, press_space || in_air );
- render_overlay_mesh( lmb? ov_lmb_down: ov_lmb );
-
- colorize( rmb&&!lmb, press_space || in_air );
- render_overlay_mesh( ov_text_kickflip );
-
- colorize( rmb, press_space || in_air );
- render_overlay_mesh( rmb? ov_rmb_down: ov_rmb );
-
- colorize( rmb&&lmb, press_space || in_air );
- render_overlay_mesh( ov_text_treflip );
- }
- else if( subsytem == k_player_subsystem_walk )
- {
- colorize( 0, 1 );
- render_overlay_mesh( ov_mouse );
- render_overlay_mesh( ov_text_look );
-
- render_overlay_mesh( lmb? ov_lmb_down: ov_lmb );
- render_overlay_mesh( rmb? ov_rmb_down: ov_rmb );
- }
- }
-}
+++ /dev/null
-#pragma once
-
-enum control_overlay_mesh
-{
- #include "control_overlay.h.generated"
-};
-
-struct control_overlay
-{
- mdl_context mdl;
- GLuint tex;
- i32 enabled;
-}
-extern control_overlay;
-
-void control_overlay_render(void);
-void control_overlay_init(void);
+++ /dev/null
-#pragma once
-#include "vg/vg_m.h"
-#include "vg/vg_framebuffer.h"
-#include "skaterift.h"
-#include "render.h"
-
-static inline void depth_compare_bind(
- void (*uTexSceneDepth)(int),
- void (*uInverseRatioDepth)(v3f),
- void (*uInverseRatioMain)(v3f),
- vg_camera *cam )
-{
- uTexSceneDepth( 5 );
- vg_framebuffer_bind_texture( g_render.fb_main, 2, 5 );
- v3f inverse;
- vg_framebuffer_inverse_ratio( g_render.fb_main, inverse );
- inverse[2] = g_render.cam.farz-g_render.cam.nearz;
-
- uInverseRatioDepth( inverse );
- vg_framebuffer_inverse_ratio( NULL, inverse );
- inverse[2] = cam->farz-cam->nearz;
- uInverseRatioMain( inverse );
-}
+++ /dev/null
-#include "entity.h"
-
-void ent_camera_unpack( ent_camera *ent, vg_camera *cam )
-{
- v3f dir = {0.0f,-1.0f,0.0f};
- mdl_transform_vector( &ent->transform, dir, dir );
- v3_angles( dir, cam->angles );
- v3_copy( ent->transform.co, cam->pos );
- cam->fov = ent->fov;
-}
+++ /dev/null
-#include "entity.h"
-
-void ent_camera_unpack( ent_camera *ent, vg_camera *cam );
+++ /dev/null
-#include "vg/vg_engine.h"
-#include "entity.h"
-#include "input.h"
-#include "gui.h"
-#include "audio.h"
-
-entity_call_result ent_challenge_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
-
- if( call->function == 0 ) /* unlock() */
- {
- if( !challenge->status )
- {
- vg_info( "challenge( '%s' )\n",
- mdl_pstr( &world->meta, challenge->pstr_alias) );
- ent_call call;
- call.data = NULL;
- call.function = challenge->target_event;
- call.id = challenge->target;
- entity_call( world, &call );
- }
- challenge->status = 1;
- return k_entity_call_result_OK;
- }
- else if( call->function == 1 ) /* view() */
- {
- if( (localplayer.subsystem == k_player_subsystem_walk) &&
- (world_static.challenge_target == NULL) )
- {
- world_static.challenge_target = NULL;
- world_entity_set_focus( call->id );
- world_entity_focus_modal();
-
- gui_helper_clear();
- vg_str text;
- if( gui_new_helper( input_button_list[k_srbind_maccept], &text ))
- vg_strcat( &text, "Start" );
- if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
- vg_strcat( &text, "Exit" );
- }
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
-
-void ent_challenge_preupdate( ent_focus_context *ctx )
-{
- world_instance *world = ctx->world;
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, ctx->index );
-
- /* maximum distance from active challenge */
- if( !ctx->active )
- {
- f32 min_dist2 = 999999.9f;
-
- if( mdl_entity_id_type( challenge->first ) == k_ent_objective )
- {
- u32 next = challenge->first;
- while( mdl_entity_id_type(next) == k_ent_objective ){
- u32 index = mdl_entity_id_id( next );
- ent_objective *objective = mdl_arritm(&world->ent_objective,index);
- next = objective->id_next;
-
- f32 d2 = v3_dist2( localplayer.rb.co, objective->transform.co );
- if( d2 < min_dist2 )
- min_dist2 = d2;
- }
- }
-
- f32 max_dist = 100.0f;
- if( min_dist2 > max_dist*max_dist ){
- world_static.challenge_target = NULL;
- world_static.challenge_timer = 0.0f;
- world_entity_clear_focus();
- audio_lock();
- audio_oneshot_3d( &audio_challenge[6], localplayer.rb.co,
- 30.0f, 1.0f );
- audio_unlock();
- }
- return;
- }
-
- world_entity_focus_camera( world, challenge->camera );
-
- if( mdl_entity_id_type( challenge->first ) == k_ent_objective ){
- if( button_down( k_srbind_maccept ) ){
- u32 index = mdl_entity_id_id( challenge->first );
- world_static.challenge_target = mdl_arritm( &world->ent_objective,
- index );
- world_static.challenge_timer = 0.0f;
- world_entity_exit_modal();
- gui_helper_clear();
-
- u32 next = challenge->first;
- while( mdl_entity_id_type(next) == k_ent_objective ){
- u32 index = mdl_entity_id_id( next );
- ent_objective *objective = mdl_arritm(&world->ent_objective,index);
- objective->flags &= ~k_ent_objective_passed;
- next = objective->id_next;
- v3_fill( objective->transform.s, 1.0f );
- }
- audio_lock();
- audio_oneshot( &audio_challenge[5], 1.0f, 0.0f );
- audio_unlock();
- return;
- }
- }
-
- if( button_down( k_srbind_mback ) )
- {
- world_static.challenge_target = NULL;
- world_entity_exit_modal();
- world_entity_clear_focus();
- gui_helper_clear();
- audio_lock();
- audio_oneshot( &audio_challenge[4], 1.0f, 0.0f );
- audio_unlock();
- return;
- }
-}
-
-static void ent_challenge_render( ent_challenge *challenge ){
-
-}
+++ /dev/null
-#pragma once
-#include "entity.h"
-
-void ent_challenge_preupdate( ent_focus_context *ctx );
-entity_call_result ent_challenge_call( world_instance *world, ent_call *call );
+++ /dev/null
-#pragma once
-#include "entity.h"
-#include "player_glide.h"
-
-entity_call_result ent_glider_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_glider *glider = mdl_arritm( &world->ent_glider, index );
-
- if( call->function == 0 )
- {
- glider->flags |= 0x1;
- return k_entity_call_result_OK;
- }
- else if( call->function == 1 )
- {
- if( glider->flags & 0x1 )
- {
- player_glide_equip_glider();
- }
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
+++ /dev/null
-#pragma once
-#include "entity.h"
-
-entity_call_result ent_glider_call( world_instance *world, ent_call *call );
+++ /dev/null
-#include "entity.h"
-#include "ent_miniworld.h"
-#include "world_render.h"
-#include "world_load.h"
-#include "input.h"
-#include "gui.h"
-#include "menu.h"
-#include "audio.h"
-
-struct global_miniworld global_miniworld;
-
-entity_call_result ent_miniworld_call( world_instance *world, ent_call *call )
-{
- ent_miniworld *miniworld = mdl_arritm( &world->ent_miniworld,
- mdl_entity_id_id(call->id) );
-
- int world_id = world - world_static.instances;
-
- if( call->function == 0 ) /* zone() */
- {
- const char *uid = mdl_pstr( &world->meta, miniworld->pstr_world );
- skaterift_load_world_command( 1, (const char *[]){ uid } );
-
- mdl_transform_m4x3( &miniworld->transform, global_miniworld.mmdl );
- global_miniworld.active = miniworld;
-
- gui_helper_clear();
- vg_str text;
-
- if( gui_new_helper( input_button_list[k_srbind_miniworld_resume], &text ))
- vg_strcat( &text, "Enter World" );
-
- return k_entity_call_result_OK;
- }
- else if( call->function == 1 )
- {
- global_miniworld.active = NULL;
- gui_helper_clear();
-
- if( miniworld->proxy )
- {
- ent_prop *prop = mdl_arritm( &world->ent_prop,
- mdl_entity_id_id(miniworld->proxy) );
- prop->flags &= ~0x1;
- }
-
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
-
-static void miniworld_icon( vg_camera *cam, enum gui_icon icon,
- v3f pos, f32 size)
-{
- m4x3f mmdl;
- v3_copy( cam->transform[2], mmdl[2] );
- mmdl[2][1] = 0.0f;
- v3_normalize( mmdl[2] );
- v3_copy( (v3f){0,1,0}, mmdl[1] );
- v3_cross( mmdl[1], mmdl[2], mmdl[0] );
- m4x3_mulv( global_miniworld.mmdl, pos, mmdl[3] );
-
- shader_model_font_uMdl( mmdl );
- shader_model_font_uOffset( (v4f){0,0,0,20.0f*size} );
-
- m4x4f m4mdl;
- m4x3_expand( mmdl, m4mdl );
- m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
- shader_model_font_uPvmPrev( m4mdl );
-
- mdl_submesh *sm = gui.icons[ icon ];
- if( sm )
- mdl_draw_submesh( sm );
-}
-
-void ent_miniworld_render( world_instance *host_world, vg_camera *cam )
-{
- if( host_world != &world_static.instances[k_world_purpose_hub] )
- return;
-
- ent_miniworld *miniworld = global_miniworld.active;
-
- if( !miniworld )
- return;
-
- world_instance *dest_world = &world_static.instances[k_world_purpose_client];
-
- int rendering = 1;
- if( dest_world->status != k_world_status_loaded )
- rendering = 0;
-
- if( miniworld->proxy ){
- ent_prop *prop = mdl_arritm( &host_world->ent_prop,
- mdl_entity_id_id(miniworld->proxy) );
- if( !rendering )
- prop->flags &= ~0x1;
- else
- prop->flags |= 0x1;
- }
-
-
- if( !rendering )
- return;
-
- render_world_override( dest_world, host_world, global_miniworld.mmdl, cam,
- NULL, (v4f){dest_world->tar_min,10000.0f,0.0f,0.0f} );
- render_world_routes( dest_world, host_world,
- global_miniworld.mmdl, cam, 0, 1 );
-
- /* icons
- * ---------------------*/
- font3d_bind( &gui.font, k_font_shader_default, 0, NULL, cam );
- mesh_bind( &gui.icons_mesh );
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, gui.icons_texture );
- shader_model_font_uTexMain( 0 );
- shader_model_font_uColour( (v4f){1,1,1,1} );
-
- miniworld_icon( cam, k_gui_icon_player, dest_world->player_co,
- 1.0f + sinf(vg.time)*0.2f );
-
- for( u32 i=0; i<mdl_arrcount(&dest_world->ent_challenge); i++ ){
- ent_challenge *challenge = mdl_arritm( &dest_world->ent_challenge, i );
-
- enum gui_icon icon = k_gui_icon_exclaim;
- if( challenge->status )
- icon = k_gui_icon_tick;
-
- miniworld_icon( cam, icon, challenge->transform.co, 1.0f );
- }
-
- for( u32 i=0; i<mdl_arrcount(&dest_world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &dest_world->ent_route, i );
-
- if( route->flags & k_ent_route_flag_achieve_gold ){
- miniworld_icon( cam, k_gui_icon_rift_run_gold,
- route->board_transform[3],1.0f);
- }
- else if( route->flags & k_ent_route_flag_achieve_silver ){
- miniworld_icon( cam, k_gui_icon_rift_run_silver,
- route->board_transform[3],1.0f);
- }
- }
-
- for( u32 i=0; i<mdl_arrcount(&dest_world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &dest_world->ent_route, i );
-
- v4f colour;
- v4_copy( route->colour, colour );
- v3_muls( colour, 1.6666f, colour );
- shader_model_font_uColour( colour );
- miniworld_icon( cam, k_gui_icon_rift_run, route->board_transform[3],1.0f);
- }
-}
-
-void ent_miniworld_preupdate(void)
-{
- world_instance *hub = world_current_instance(),
- *dest = &world_static.instances[k_world_purpose_client];
-
- ent_miniworld *miniworld = global_miniworld.active;
-
- if( (localplayer.subsystem != k_player_subsystem_walk) ||
- (global_miniworld.transition) ||
- (world_static.active_instance != k_world_purpose_hub) ||
- (!miniworld) ||
- (dest->status != k_world_status_loaded) ||
- (skaterift.activity != k_skaterift_default)) {
- return;
- }
-
- if( button_down( k_srbind_miniworld_resume ) )
- {
- if( skaterift.demo_mode )
- {
- if( world_static.instance_addons[1]->flags & ADDON_REG_PREMIUM )
- {
- menu_open( k_menu_page_premium );
- return;
- }
- }
-
- global_miniworld.transition = 1;
- global_miniworld.t = 0.0f;
- global_miniworld.cam = g_render.cam;
-
- world_switch_instance(1);
- srinput.state = k_input_state_resume;
- menu.disable_open = 0;
- gui_helper_clear();
- audio_lock();
- audio_oneshot( &audio_ui[2], 1.0f, 0.0f );
- audio_unlock();
- }
-}
-
-void ent_miniworld_goback(void)
-{
- audio_lock();
- audio_oneshot( &audio_ui[2], 1.0f, 0.0f );
- audio_unlock();
-
- global_miniworld.transition = -1;
- global_miniworld.t = 1.0f;
-
- global_miniworld.cam = g_render.cam;
- vg_m4x3_transform_camera( global_miniworld.mmdl, &global_miniworld.cam );
- world_switch_instance(0);
-}
+++ /dev/null
-#pragma once
-#include "entity.h"
-
-struct global_miniworld
-{
- ent_miniworld *active;
- int transition;
- f32 t;
-
- m4x3f mmdl;
- vg_camera cam;
-}
-extern global_miniworld;
-
-entity_call_result ent_miniworld_call( world_instance *world, ent_call *call );
-void ent_miniworld_render( world_instance *host_world, vg_camera *cam );
-void ent_miniworld_goback(void);
-void ent_miniworld_preupdate(void);
+++ /dev/null
-#include "vg/vg_mem.h"
-#include "ent_npc.h"
-#include "shaders/model_character_view.h"
-#include "input.h"
-#include "player.h"
-#include "gui.h"
-
-struct npc npc_gumpa, npc_slowmo, npc_volc_flight;
-static struct skeleton_anim *gumpa_idle;
-static struct skeleton_anim *slowmo_momentum, *slowmo_slide, *slowmo_rewind,
- *anim_tutorial_cam;
-static float slowmo_opacity = 0.0f;
-static f64 volc_start_preview = 0.0;
-
-void npc_load_model( struct npc *npc, const char *path )
-{
- vg_linear_clear( vg_mem.scratch );
-
- mdl_context *meta = &npc->meta;
- mdl_open( meta, path, vg_mem.rtmemory );
- mdl_load_metadata_block( meta, vg_mem.rtmemory );
- mdl_load_animation_block( meta, vg_mem.rtmemory );
-
- struct skeleton *sk = &npc->skeleton;
- skeleton_setup( sk, vg_mem.rtmemory, meta );
-
- u32 mtx_size = sizeof(m4x3f)*sk->bone_count;
- npc->final_mtx = vg_linear_alloc( vg_mem.rtmemory, mtx_size );
-
- if( mdl_arrcount( &meta->textures ) )
- {
- mdl_texture *tex0 = mdl_arritm( &meta->textures, 0 );
- void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
- mdl_fread_pack_file( meta, &tex0->file, data );
-
- vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
- VG_TEX2D_NEAREST|VG_TEX2D_CLAMP,
- &npc->texture );
- }
- else
- {
- npc->texture = vg.tex_missing;
- }
-
- mdl_async_load_glmesh( meta, &npc->mesh, NULL );
- mdl_close( meta );
-}
-
-void npc_init(void)
-{
- npc_load_model( &npc_gumpa, "models/gumpa.mdl" );
- gumpa_idle = skeleton_get_anim( &npc_gumpa.skeleton, "gumpa_idle" );
-
- npc_load_model( &npc_slowmo, "models/slowmos.mdl" );
- slowmo_momentum =
- skeleton_get_anim( &npc_slowmo.skeleton, "slowmo_momentum" );
- slowmo_slide = skeleton_get_anim( &npc_slowmo.skeleton, "slowmo_slide" );
- slowmo_rewind = skeleton_get_anim( &npc_slowmo.skeleton, "slowmo_rewind" );
-
- npc_load_model( &npc_volc_flight, "models/volc_flight.mdl" );
- anim_tutorial_cam =
- skeleton_get_anim( &npc_volc_flight.skeleton, "tutorial" );
-}
-
-static struct npc *npc_resolve( u32 id )
-{
- if( id == 1 ) return &npc_gumpa;
- else if( id == 2 ) return &npc_slowmo;
- else if( id == 3 ) return &npc_volc_flight;
- else return NULL;
-}
-
-static entity_call_result npc_slowmo_call( ent_npc *npc, ent_call *call )
-{
- if( call->function == 0 )
- {
- gui_helper_clear();
- vg_str text;
-
- if( npc->context == 2 )
- {
- if( gui_new_helper( input_axis_list[k_sraxis_grab], &text ))
- vg_strcat( &text, "Crouch (store energy)" );
- if( gui_new_helper( input_joy_list[k_srjoystick_steer], &text ))
- vg_strcat( &text, "Slide" );
- }
- else if( npc->context == 1 )
- {
- if( gui_new_helper( input_axis_list[k_sraxis_grab], &text ))
- vg_strcat( &text, "Crouch (store energy)" );
- }
- else if( npc->context == 3 )
- {
- if( gui_new_helper( input_button_list[k_srbind_reset], &text ))
- vg_strcat( &text, "Rewind time" );
- if( gui_new_helper( input_button_list[k_srbind_replay_resume], &text ))
- vg_strcat( &text, "Resume" );
- }
- return k_entity_call_result_OK;
- }
- else if( call->function == -1 )
- {
- world_entity_clear_focus();
- gui_helper_clear();
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
-
-entity_call_result ent_npc_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_npc *npc = mdl_arritm( &world->ent_npc, index );
-
- if( npc->id == 2 )
- {
- return npc_slowmo_call( npc, call );
- }
- else if( npc->id == 3 )
- {
- if( call->function == 0 )
- {
- world_entity_set_focus( call->id );
- gui_helper_clear();
- vg_str text;
- if( gui_new_helper( input_button_list[k_srbind_maccept], &text ))
- vg_strcat( &text, "Preview course" );
- return k_entity_call_result_OK;
- }
- else if( call->function == -1 )
- {
- world_entity_clear_focus();
- gui_helper_clear();
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
- }
- else if( npc->id == 4 )
- {
- if( call->function == 0 )
- {
- gui_helper_clear();
- vg_str text;
- if( gui_new_helper( input_button_list[k_srbind_camera], &text ))
- vg_strcat( &text, "First/Thirdperson" );
- return k_entity_call_result_OK;
- }
- else if( call->function == -1 )
- {
- gui_helper_clear();
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
- }
- else
- {
- if( call->function == 0 )
- {
- world_entity_set_focus( call->id );
- gui_helper_clear();
- vg_str text;
- if( gui_new_helper( input_button_list[k_srbind_maccept], &text ))
- vg_strcat( &text, "Talk to ???" );
- return k_entity_call_result_OK;
- }
- else if( call->function == -1 )
- {
- world_entity_clear_focus();
- gui_helper_clear();
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
- }
-}
-
-void ent_npc_preupdate( ent_focus_context *ctx )
-{
- world_instance *world = ctx->world;
- ent_npc *ent = mdl_arritm( &world->ent_npc, ctx->index );
-
- if( !ctx->active )
- {
- if( button_down(k_srbind_maccept) )
- {
- world_entity_focus_modal();
- gui_helper_clear();
- vg_str text;
- if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
- vg_strcat( &text, "leave" );
-
- volc_start_preview = vg.time;
- }
-
- return;
- }
-
- if( ent->id == 3 )
- {
- player_pose pose;
- struct skeleton *sk = &npc_volc_flight.skeleton;
-
- f64 t = (vg.time - volc_start_preview) * 0.5;
- skeleton_sample_anim_clamped( sk, anim_tutorial_cam, t, pose.keyframes );
-
- ent_camera *cam = mdl_arritm( &world->ent_camera,
- mdl_entity_id_id(ent->camera) );
- v3_copy( pose.keyframes[0].co, cam->transform.co );
-
- v4f qp;
- q_axis_angle( qp, (v3f){1,0,0}, VG_TAUf*0.25f );
- q_mul( pose.keyframes[0].q, qp, cam->transform.q );
- q_normalize( cam->transform.q );
-
- v3_add( ent->transform.co, cam->transform.co, cam->transform.co );
- }
-
- world_entity_focus_camera( world, ent->camera );
-
- if( button_down( k_srbind_mback ) )
- {
- world_entity_exit_modal();
- world_entity_clear_focus();
- gui_helper_clear();
- }
-}
-
-void npc_update( ent_npc *ent )
-{
- if( ent->id == 3 ) return;
- if( ent->id == 4 ) return;
-
- struct npc *npc_def = npc_resolve( ent->id );
- VG_ASSERT( npc_def );
-
- player_pose pose;
- struct skeleton *sk = &npc_def->skeleton;
- pose.type = k_player_pose_type_ik;
- pose.board.lean = 0.0f;
-
- if( ent->id == 1 )
- {
- skeleton_sample_anim( sk, gumpa_idle, vg.time, pose.keyframes );
- }
- else if( ent->id == 2 )
- {
- struct skeleton_anim *anim = NULL;
- if( ent->context == 1 ) anim = slowmo_momentum;
- else if( ent->context == 2 ) anim = slowmo_slide;
- else if( ent->context == 3 ) anim = slowmo_rewind;
-
- VG_ASSERT( anim );
-
- f32 t = vg.time*0.5f,
- animtime = fmodf( t*anim->rate, anim->length ),
- lt = animtime / (f32)anim->length;
- skeleton_sample_anim( sk, anim, t, pose.keyframes );
- slowmo_opacity = vg_clampf(fabsf(lt-0.5f)*9.0f-3.0f,0,1);
- }
-
- v3_copy( ent->transform.co, pose.root_co );
- v4_copy( ent->transform.q, pose.root_q );
- apply_full_skeleton_pose( &npc_def->skeleton, &pose, npc_def->final_mtx );
-}
-
-void npc_render( ent_npc *ent, world_instance *world, vg_camera *cam )
-{
- if( ent->id == 3 ) return;
- if( ent->id == 4 ) return;
-
- struct npc *npc_def = npc_resolve( ent->id );
- VG_ASSERT( npc_def );
-
- shader_model_character_view_use();
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, npc_def->texture );
- shader_model_character_view_uTexMain( 0 );
- shader_model_character_view_uCamera( cam->transform[3] );
- shader_model_character_view_uPv( cam->mtx.pv );
-
- if( ent->id == 2 )
- {
- shader_model_character_view_uDepthMode( 2 );
- shader_model_character_view_uDitherCutoff( slowmo_opacity );
- }
- else
- {
- shader_model_character_view_uDepthMode( 0 );
- }
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_character_view );
-
- glUniformMatrix4x3fv( _uniform_model_character_view_uTransforms,
- npc_def->skeleton.bone_count,
- 0,
- (const GLfloat *)npc_def->final_mtx );
-
- mesh_bind( &npc_def->mesh );
- mesh_draw( &npc_def->mesh );
-}
+++ /dev/null
-#pragma once
-#include "player_render.h"
-#include "entity.h"
-
-struct npc
-{
- glmesh mesh;
- GLuint texture;
-
- mdl_context meta;
- struct skeleton skeleton;
-
- m4x3f *final_mtx;
-}
-extern npc_gumpa;
-
-enum npc_id
-{
- k_npc_id_none = 0,
- k_npc_id_gumpa = 1
-};
-
-void npc_load_model( struct npc *npc, const char *path );
-void ent_npc_preupdate( ent_focus_context *context );
-entity_call_result ent_npc_call( world_instance *world, ent_call *call );
-void npc_update( ent_npc *ent );
-void npc_render( ent_npc *ent, world_instance *world, vg_camera *cam );
-void npc_init(void);
+++ /dev/null
-#include "world.h"
-#include "world_load.h"
-#include "entity.h"
-#include "audio.h"
-#include "steam.h"
-#include "ent_region.h"
-#include "player.h"
-#include "player_skate.h"
-
-static void ent_objective_pass( world_instance *world,
- ent_objective *objective ){
- if( objective->id_next ){
- world_static.challenge_timer += objective->filter;
-
- u32 index = mdl_entity_id_id( objective->id_next );
- ent_objective *next = mdl_arritm( &world->ent_objective, index );
- world_static.challenge_target = next;
- objective->flags |= k_ent_objective_passed;
-
- if( next->filter & k_ent_objective_filter_passthrough )
- ent_objective_pass( world, next );
- else{
- vg_info( "pass challenge point\n" );
- audio_lock();
- audio_oneshot_3d( &audio_challenge[0], localplayer.rb.co,
- 30.0f, 1.0f );
- audio_unlock();
- }
- }
- else {
- vg_success( "challenge win\n" );
- audio_lock();
- audio_oneshot( &audio_challenge[2], 1.0f, 0.0f );
- audio_unlock();
- world_static.challenge_target = NULL;
- world_static.challenge_timer = 0.0f;
- world_static.focused_entity = 0;
-
- if( objective->id_win ){
- ent_call call;
- call.data = NULL;
- call.function = objective->win_event;
- call.id = objective->id_win;
- entity_call( world, &call );
- }
-
- ent_region_re_eval( world );
- }
-}
-
-static int ent_objective_check_filter( ent_objective *objective ){
- if( objective->filter ){
- struct player_skate_state *s = &player_skate.state;
- enum trick_type trick = s->trick_type;
-
- u32 state = 0x00;
-
- if( trick == k_trick_type_shuvit )
- state |= k_ent_objective_filter_trick_shuvit;
- if( trick == k_trick_type_treflip )
- state |= k_ent_objective_filter_trick_treflip;
- if( trick == k_trick_type_kickflip )
- state |= k_ent_objective_filter_trick_kickflip;
-
- if( s->flip_rate < -0.0001f ) state |= k_ent_objective_filter_flip_back;
- if( s->flip_rate > 0.0001f ) state |= k_ent_objective_filter_flip_front;
-
- if( s->activity == k_skate_activity_grind_5050 ||
- s->activity == k_skate_activity_grind_back50 ||
- s->activity == k_skate_activity_grind_front50 )
- state |= k_ent_objective_filter_grind_truck_any;
-
- if( s->activity == k_skate_activity_grind_boardslide )
- state |= k_ent_objective_filter_grind_board_any;
-
- return ((objective->filter & state) || !objective->filter) &&
- ((objective->filter2 & state) || !objective->filter2);
- }
- else {
- return 1;
- }
-}
-
-entity_call_result ent_objective_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_objective *objective = mdl_arritm( &world->ent_objective, index );
-
- if( call->function == 0 )
- {
- if( objective->flags & (k_ent_objective_hidden|k_ent_objective_passed))
- {
- return k_entity_call_result_OK;
- }
-
- if( world_static.challenge_target )
- {
- if( (world_static.challenge_target == objective) &&
- ent_objective_check_filter( objective )){
- ent_objective_pass( world, objective );
- }
- else
- {
- audio_lock();
- audio_oneshot_3d( &audio_challenge[6], localplayer.rb.co,
- 30.0f, 1.0f );
- audio_unlock();
- vg_error( "challenge failed\n" );
- world_static.challenge_target = NULL;
- world_static.challenge_timer = 0.0f;
- world_static.focused_entity = 0;
- }
- }
-
- return k_entity_call_result_OK;
- }
- else if( call->function == 2 )
- {
- objective->flags &= ~k_ent_objective_hidden;
-
- if( mdl_entity_id_type( objective->id_next ) == k_ent_objective ){
- call->id = objective->id_next;
- entity_call( world, call );
- }
- return k_entity_call_result_OK;
- }
- else if( call->function == 3 )
- {
- objective->flags |= k_ent_objective_hidden;
-
- if( mdl_entity_id_type( objective->id_next ) == k_ent_objective ){
- call->id = objective->id_next;
- entity_call( world, call );
- }
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
+++ /dev/null
-#pragma once
-#include "entity.h"
-#include "world.h"
-entity_call_result ent_objective_call( world_instance *world, ent_call *call );
+++ /dev/null
-#include "ent_region.h"
-#include "gui.h"
-#include "network_common.h"
-#include "network.h"
-
-struct global_ent_region global_ent_region;
-
-u32 region_spark_colour( u32 flags )
-{
- if( flags & k_ent_route_flag_achieve_gold )
- return 0xff8ce0fa;
- else if( flags & k_ent_route_flag_achieve_silver )
- return 0xffc2c2c2;
- else
- return 0x00;
-}
-
-entity_call_result ent_region_call( world_instance *world, ent_call *call )
-{
- ent_region *region =
- mdl_arritm( &world->ent_region, mdl_entity_id_id(call->id) );
-
- if( !region->zone_volume )
- return k_entity_call_result_invalid;
-
- ent_volume *volume =
- mdl_arritm( &world->ent_volume, mdl_entity_id_id(region->zone_volume) );
-
- if( call->function == 0 ) /* enter */
- {
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i ++ )
- {
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- v3f local;
- m4x3_mulv( volume->to_local, route->board_transform[3], local );
- if( (fabsf(local[0]) <= 1.0f) &&
- (fabsf(local[1]) <= 1.0f) &&
- (fabsf(local[2]) <= 1.0f) )
- {
- route->flags &= ~k_ent_route_flag_out_of_zone;
- }
- else
- {
- route->flags |= k_ent_route_flag_out_of_zone;
- }
- }
-
- gui_location_print_ccmd( 1, (const char *[]){
- mdl_pstr(&world->meta,region->pstr_title)} );
-
- vg_strncpy( mdl_pstr(&world->meta,region->pstr_title),
- global_ent_region.location, NETWORK_REGION_MAX,
- k_strncpy_always_add_null );
- global_ent_region.flags = region->flags;
- network_send_region();
-
- localplayer.effect_data.spark.colour = region_spark_colour(region->flags);
- return k_entity_call_result_OK;
- }
- else if( call->function == 1 ) /* leave */
- {
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i ++ )
- {
- ent_route *route = mdl_arritm( &world->ent_route, i );
- route->flags |= k_ent_route_flag_out_of_zone;
- }
- localplayer.effect_data.spark.colour = 0x00;
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
-
-/*
- * reevaluate all achievements to calculate the compiled achievement
- */
-void ent_region_re_eval( world_instance *world )
-{
- u32 world_total = k_ent_route_flag_achieve_gold |
- k_ent_route_flag_achieve_silver;
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_region); i ++ ){
- ent_region *region = mdl_arritm(&world->ent_region, i);
-
- if( !region->zone_volume )
- continue;
-
- ent_volume *volume = mdl_arritm(&world->ent_volume,
- mdl_entity_id_id(region->zone_volume));
-
- u32 combined = k_ent_route_flag_achieve_gold |
- k_ent_route_flag_achieve_silver;
-
- for( u32 j=0; j<mdl_arrcount(&world->ent_route); j ++ ){
- ent_route *route = mdl_arritm(&world->ent_route, j );
-
- v3f local;
- m4x3_mulv( volume->to_local, route->board_transform[3], local );
- if( !((fabsf(local[0]) <= 1.0f) &&
- (fabsf(local[1]) <= 1.0f) &&
- (fabsf(local[2]) <= 1.0f)) ){
- continue;
- }
-
- combined &= route->flags;
- }
-
- for( u32 j=0; j<mdl_arrcount(&world->ent_challenge); j ++ ){
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, j );
-
- v3f local;
- m4x3_mulv( volume->to_local, challenge->transform.co, local );
- if( !((fabsf(local[0]) <= 1.0f) &&
- (fabsf(local[1]) <= 1.0f) &&
- (fabsf(local[2]) <= 1.0f)) ){
- continue;
- }
-
- u32 flags = 0x00;
- if( challenge->status ){
- flags |= k_ent_route_flag_achieve_gold;
- flags |= k_ent_route_flag_achieve_silver;
- }
-
- combined &= flags;
- }
-
- region->flags = combined;
- world_total &= combined;
-
- /* run unlock triggers. v105+ */
- if( world->meta.info.version >= 105 ){
- if( region->flags & (k_ent_route_flag_achieve_gold|
- k_ent_route_flag_achieve_silver) ){
- if( region->target0[0] ){
- ent_call call;
- call.data = NULL;
- call.id = region->target0[0];
- call.function = region->target0[1];
- entity_call( world, &call );
- }
- }
- }
- }
-
- u32 instance_id = world - world_static.instances;
-
- if( world_static.instance_addons[instance_id]->flags & ADDON_REG_MTZERO ){
- if( world_total & k_ent_route_flag_achieve_gold ){
- steam_set_achievement( "MTZERO_GOLD" );
- steam_store_achievements();
- }
-
- if( world_total & k_ent_route_flag_achieve_silver ){
- steam_set_achievement( "MTZERO_SILVER" );
- steam_store_achievements();
- }
- }
-
- if( world_static.instance_addons[instance_id]->flags & ADDON_REG_CITY ){
- steam_set_achievement( "CITY_COMPLETE" );
- steam_store_achievements();
- }
-}
+++ /dev/null
-#pragma once
-#include "world_entity.h"
-#include "network_common.h"
-
-struct global_ent_region
-{
- char location[ NETWORK_REGION_MAX ];
- u32 flags;
-}
-extern global_ent_region;
-
-u32 region_spark_colour( u32 flags );
-void ent_region_re_eval( world_instance *world );
-entity_call_result ent_region_call( world_instance *world, ent_call *call );
+++ /dev/null
-#include "ent_relay.h"
-
-entity_call_result ent_relay_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_relay *relay = mdl_arritm( &world->ent_relay, index );
-
- if( call->function == 0 )
- {
- for( u32 i=0; i<VG_ARRAY_LEN(relay->targets); i++ )
- {
- if( relay->targets[i][0] )
- {
- ent_call call;
- call.data = NULL;
- call.function = relay->targets[i][1];
- call.id = relay->targets[i][0];
- entity_call( world, &call );
- }
- }
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
+++ /dev/null
-#pragma once
-#include "entity.h"
-entity_call_result ent_relay_call( world_instance *world, ent_call *call );
+++ /dev/null
-#include "ent_route.h"
-#include "input.h"
-#include "gui.h"
-
-struct global_ent_route global_ent_route;
-
-entity_call_result ent_route_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_route *route = mdl_arritm( &world->ent_route, index );
-
- if( call->function == 0 )
- { /* view() */
- if( localplayer.subsystem == k_player_subsystem_walk )
- {
- world_entity_set_focus( call->id );
- world_entity_focus_modal();
-
- gui_helper_clear();
- vg_str text;
-
- if( (global_ent_route.helper_weekly =
- gui_new_helper( input_button_list[k_srbind_mleft], &text )))
- vg_strcat( &text, "Weekly" );
-
- if( (global_ent_route.helper_alltime =
- gui_new_helper( input_button_list[k_srbind_mright], &text )))
- vg_strcat( &text, "All time" );
-
- if( gui_new_helper( input_button_list[k_srbind_mback], &text ) )
- vg_strcat( &text, "Exit" );
- }
-
- return k_entity_call_result_OK;
- }
-
- return k_entity_call_result_unhandled;
-}
-
-void ent_route_preupdate( ent_focus_context *ctx )
-{
- if( !ctx->active )
- return;
-
- world_instance *world = ctx->world;
- ent_route *route = mdl_arritm( &world->ent_route, ctx->index );
-
- u32 cam_id = 0;
-
- if( __builtin_expect( world->meta.info.version >= 103, 1 ) )
- cam_id = route->id_camera;
-
- world_entity_focus_camera( world, cam_id );
-
- if( button_down( k_srbind_mleft ) ){
- world_sfd.view_weekly = 1;
- world_sfd_compile_active_scores();
- }
-
- if( button_down( k_srbind_mright ) ){
- world_sfd.view_weekly = 0;
- world_sfd_compile_active_scores();
- }
-
- global_ent_route.helper_alltime->greyed =!world_sfd.view_weekly;
- global_ent_route.helper_weekly->greyed = world_sfd.view_weekly;
-
- if( button_down( k_srbind_mback ) )
- {
- world_entity_exit_modal();
- world_entity_clear_focus();
- gui_helper_clear();
- return;
- }
-}
+++ /dev/null
-#pragma once
-#include "entity.h"
-
-struct global_ent_route
-{
- struct gui_helper *helper_weekly, *helper_alltime;
-}
-extern global_ent_route;
-
-entity_call_result ent_route_call( world_instance *world, ent_call *call );
-void ent_route_preupdate( ent_focus_context *ctx );
+++ /dev/null
-#include "vg/vg_steam_ugc.h"
-#include "vg/vg_msg.h"
-#include "vg/vg_tex.h"
-#include "vg/vg_image.h"
-#include "vg/vg_loader.h"
-#include "ent_skateshop.h"
-#include "world.h"
-#include "player.h"
-#include "gui.h"
-#include "menu.h"
-#include "steam.h"
-#include "addon.h"
-#include "save.h"
-#include "network.h"
-
-struct global_skateshop global_skateshop =
-{
- .render={.reg_id=0xffffffff,.world_reg=0xffffffff}
-};
-
-/*
- * Checks string equality but does a hash check first
- */
-static inline int const_str_eq( u32 hash, const char *str, const char *cmp )
-{
- if( hash == vg_strdjb2(cmp) )
- if( !strcmp( str, cmp ) )
- return 1;
- return 0;
-}
-
-static void skateshop_update_viewpage(void){
- u32 page = global_skateshop.selected_board_id/SKATESHOP_VIEW_SLOT_MAX;
-
- for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
- u32 j = SKATESHOP_VIEW_SLOT_MAX-1-i;
- struct shop_view_slot *slot = &global_skateshop.shop_view_slots[j];
- addon_cache_unwatch( k_addon_type_board, slot->cache_id );
- }
-
- for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
- struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
- u32 request_id = page*SKATESHOP_VIEW_SLOT_MAX + i;
- slot->cache_id = addon_cache_create_viewer( k_addon_type_board,
- request_id );
- }
-}
-
-struct async_preview_load_thread_data{
- void *data;
- addon_reg *reg;
-};
-
-static void skateshop_async_preview_imageload( void *data, u32 len ){
- struct async_preview_load_thread_data *inf = data;
-
- if( inf->data ){
- glBindTexture( GL_TEXTURE_2D, global_skateshop.tex_preview );
- glTexSubImage2D( GL_TEXTURE_2D, 0,0,0,
- WORKSHOP_PREVIEW_WIDTH, WORKSHOP_PREVIEW_HEIGHT,
- GL_RGB, GL_UNSIGNED_BYTE, inf->data );
- glGenerateMipmap( GL_TEXTURE_2D );
- stbi_image_free( inf->data );
-
- skaterift.rt_textures[k_skaterift_rt_workshop_preview] =
- global_skateshop.tex_preview;
- }
- else {
- skaterift.rt_textures[k_skaterift_rt_workshop_preview] = vg.tex_missing;
- }
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- global_skateshop.reg_loaded_preview = inf->reg;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
-}
-
-static void skateshop_update_preview_image_thread(void *_args)
-{
- char path_buf[4096];
- vg_str folder;
- vg_strnull( &folder, path_buf, sizeof(path_buf) );
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- addon_reg *reg_preview = global_skateshop.reg_preview;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
-
- if( !addon_get_content_folder( reg_preview, &folder, 1 ) )
- {
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- global_skateshop.reg_loaded_preview = reg_preview;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- return;
- }
-
- vg_strcat( &folder, "/preview.jpg" );
- vg_async_item *call =
- vg_async_alloc( sizeof(struct async_preview_load_thread_data) );
- struct async_preview_load_thread_data *inf = call->payload;
-
- inf->reg = reg_preview;
-
- if( vg_strgood( &folder ) )
- {
- stbi_set_flip_vertically_on_load(1);
- int x, y, nc;
- inf->data = stbi_load( folder.buffer, &x, &y, &nc, 3 );
-
- if( inf->data )
- {
- if( (x != WORKSHOP_PREVIEW_WIDTH) || (y != WORKSHOP_PREVIEW_HEIGHT) )
- {
- vg_error( "Resolution does not match framebuffer, so we can't"
- " show it\n" );
- stbi_image_free( inf->data );
- inf->data = NULL;
- }
- }
-
- vg_async_dispatch( call, skateshop_async_preview_imageload );
- }
- else
- {
- vg_error( "Path too long to workshop preview image.\n" );
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- global_skateshop.reg_loaded_preview = reg_preview;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- }
-}
-
-void skateshop_world_preview_preupdate(void)
-{
- /* try to load preview image if we availible to do. */
- if( vg_loader_availible() )
- {
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- if( global_skateshop.reg_preview != global_skateshop.reg_loaded_preview )
- {
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- vg_loader_start( skateshop_update_preview_image_thread, NULL );
- }
- else SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- }
-}
-
-/*
- * op/subroutine: k_workshop_op_item_load
- * -----------------------------------------------------------------------------
- */
-
-/*
- * Regular stuff
- * -----------------------------------------------------------------------------
- */
-
-static void skateshop_init_async(void *_data,u32 size){
- glGenTextures( 1, &global_skateshop.tex_preview );
- glBindTexture( GL_TEXTURE_2D, global_skateshop.tex_preview );
- glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB,
- WORKSHOP_PREVIEW_WIDTH, WORKSHOP_PREVIEW_HEIGHT,
- 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
-
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
- GL_LINEAR_MIPMAP_LINEAR );
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
-
- skaterift.rt_textures[ k_skaterift_rt_workshop_preview ] = vg.tex_missing;
- skaterift.rt_textures[ k_skaterift_rt_server_status ] = vg.tex_missing;
- render_server_status_gui();
-}
-
-/*
- * VG event init
- */
-void skateshop_init(void)
-{
- vg_async_call( skateshop_init_async, NULL, 0 );
-}
-
-static u16 skateshop_selected_cache_id(void){
- if( addon_count(k_addon_type_board, ADDON_REG_HIDDEN) ){
- addon_reg *reg = get_addon_from_index(
- k_addon_type_board, global_skateshop.selected_board_id,
- ADDON_REG_HIDDEN );
- return reg->cache_id;
- }
- else return 0;
-}
-
-static void skateshop_server_helper_update(void){
- vg_str text;
- vg_strnull( &text, global_skateshop.helper_toggle->text,
- sizeof(global_skateshop.helper_toggle->text) );
-
- if( skaterift.demo_mode ){
- vg_strcat( &text, "Not availible in demo" );
- }
- else {
- if( network_client.user_intent == k_server_intent_online )
- vg_strcat( &text, "Disconnect" );
- else
- vg_strcat( &text, "Go Online" );
- }
-}
-
-/*
- * VG event preupdate
- */
-void temp_update_playermodel(void);
-void ent_skateshop_preupdate( ent_focus_context *ctx )
-{
- if( !ctx->active )
- return;
-
- world_instance *world = ctx->world;
- ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, ctx->index );
-
- /* camera positioning */
- ent_camera *ref = mdl_arritm( &world->ent_camera,
- mdl_entity_id_id(shop->id_camera) );
-
- v3f dir = {0.0f,-1.0f,0.0f};
- mdl_transform_vector( &ref->transform, dir, dir );
- v3_angles( dir, world_static.focus_cam.angles );
-
- v3f lookat;
- if( shop->type == k_skateshop_type_boardshop ||
- shop->type == k_skateshop_type_worldshop ){
- ent_marker *display = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(shop->boards.id_display) );
- v3_sub( display->transform.co, localplayer.rb.co, lookat );
- }
- else if( shop->type == k_skateshop_type_charshop ){
- v3_sub( ref->transform.co, localplayer.rb.co, lookat );
- }
- else if( shop->type == k_skateshop_type_server ){
- ent_prop *prop = mdl_arritm( &world->ent_prop,
- mdl_entity_id_id(shop->server.id_lever) );
- v3_sub( prop->transform.co, localplayer.rb.co, lookat );
- }
- else
- vg_fatal_error( "Unknown store (%u)\n", shop->type );
-
- q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
- atan2f(lookat[0],lookat[2]) );
-
- v3_copy( ref->transform.co, world_static.focus_cam.pos );
- world_static.focus_cam.fov = ref->fov;
-
- /* input */
- if( shop->type == k_skateshop_type_boardshop ){
- if( !vg_loader_availible() ) return;
-
- u16 cache_id = skateshop_selected_cache_id();
- global_skateshop.helper_pick->greyed = !cache_id;
-
- /*
- * Controls
- * ----------------------
- */
- u32 opage = global_skateshop.selected_board_id/SKATESHOP_VIEW_SLOT_MAX;
-
- if( button_down( k_srbind_mleft ) ){
- if( global_skateshop.selected_board_id > 0 ){
- global_skateshop.selected_board_id --;
- }
- }
-
- u32 valid_count = addon_count( k_addon_type_board, 0 );
- if( button_down( k_srbind_mright ) ){
- if( global_skateshop.selected_board_id+1 < valid_count ){
- global_skateshop.selected_board_id ++;
- }
- }
-
- u32 npage = global_skateshop.selected_board_id/SKATESHOP_VIEW_SLOT_MAX;
-
- if( opage != npage ){
- skateshop_update_viewpage();
- }
- else if( cache_id && button_down( k_srbind_maccept )){
- vg_info( "chose board from skateshop (%u)\n",
- global_skateshop.selected_board_id );
-
- addon_cache_unwatch( k_addon_type_board, localplayer.board_view_slot );
- addon_cache_watch( k_addon_type_board, cache_id );
- localplayer.board_view_slot = cache_id;
- network_send_item( k_netmsg_playeritem_board );
-
- world_entity_exit_modal();
- world_entity_clear_focus();
- gui_helper_clear();
- skaterift_autosave(1);
- return;
- }
- }
- else if( shop->type == k_skateshop_type_charshop ){
- if( !vg_loader_availible() ) return;
-
- int changed = 0;
- u32 valid_count = addon_count( k_addon_type_player, ADDON_REG_HIDDEN );
-
- if( button_down( k_srbind_mleft ) ){
- if( global_skateshop.selected_player_id > 0 ){
- global_skateshop.selected_player_id --;
- }
- else{
- global_skateshop.selected_player_id = valid_count-1;
- }
-
- changed = 1;
- }
-
- if( button_down( k_srbind_mright ) ){
- if( global_skateshop.selected_player_id+1 < valid_count ){
- global_skateshop.selected_player_id ++;
- }
- else{
- global_skateshop.selected_player_id = 0;
- }
-
- changed = 1;
- }
-
- if( changed ){
- addon_reg *addon = get_addon_from_index(
- k_addon_type_player, global_skateshop.selected_player_id,
- ADDON_REG_HIDDEN );
-
- u32 real_id = get_index_from_addon(
- k_addon_type_player, addon );
-
- player__use_model( real_id );
- }
-
- if( button_down( k_srbind_maccept ) ){
- network_send_item( k_netmsg_playeritem_player );
- world_entity_exit_modal();
- world_entity_clear_focus();
- gui_helper_clear();
- }
- }
- else if( shop->type == k_skateshop_type_worldshop ){
- int browseable = 0,
- loadable = 0;
-
- u32 valid_count = addon_count( k_addon_type_world, ADDON_REG_HIDDEN );
-
- if( valid_count && vg_loader_availible() )
- browseable = 1;
-
- if( valid_count && vg_loader_availible() )
- loadable = 1;
-
- global_skateshop.helper_browse->greyed = !browseable;
- global_skateshop.helper_pick->greyed = !loadable;
-
- addon_reg *selected_world = NULL;
-
- int change = 0;
- if( browseable ){
- if( button_down( k_srbind_mleft ) ){
- if( global_skateshop.selected_world_id > 0 ){
- global_skateshop.selected_world_id --;
- change = 1;
- }
- }
-
- if( button_down( k_srbind_mright ) ){
- if( global_skateshop.selected_world_id+1 < valid_count ){
- global_skateshop.selected_world_id ++;
- change = 1;
- }
- }
-
- selected_world = get_addon_from_index( k_addon_type_world,
- global_skateshop.selected_world_id, ADDON_REG_HIDDEN );
-
- if( change || (global_skateshop.reg_preview == NULL) ){
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- global_skateshop.reg_preview = selected_world;
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- }
- }
-
- if( loadable ){
- if( button_down( k_srbind_maccept ) ){
- skaterift_change_world_start( selected_world );
- }
- }
- }
- else if( shop->type == k_skateshop_type_server ){
- f64 delta = vg.time_real - network_client.last_intent_change;
-
- if( (delta > 5.0) && (!skaterift.demo_mode) ){
- global_skateshop.helper_pick->greyed = 0;
- if( button_down( k_srbind_maccept ) ){
- network_client.user_intent = !network_client.user_intent;
- network_client.last_intent_change = vg.time_real;
- skateshop_server_helper_update();
- render_server_status_gui();
- }
- }
- else {
- global_skateshop.helper_pick->greyed = 1;
- }
- }
- else{
- vg_fatal_error( "Unknown store (%u)\n", shop->type );
- }
-
- if( button_down( k_srbind_mback ) )
- {
- if( shop->type == k_skateshop_type_charshop )
- network_send_item( k_netmsg_playeritem_player );
-
- world_entity_exit_modal();
- world_entity_clear_focus();
- gui_helper_clear();
- return;
- }
-}
-
-void skateshop_world_preupdate( world_instance *world )
-{
- for( u32 i=0; i<mdl_arrcount(&world->ent_skateshop); i++ ){
- ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, i );
-
- if( shop->type == k_skateshop_type_server ){
- f32 a = network_client.user_intent;
-
- vg_slewf( &network_client.fintent, a, vg.time_frame_delta );
- a = (vg_smoothstepf( network_client.fintent ) - 0.5f) * (VG_PIf/2.0f);
-
- ent_prop *lever = mdl_arritm( &world->ent_prop,
- mdl_entity_id_id(shop->server.id_lever) );
-
- /* we need parent transforms now? */
- q_axis_angle( lever->transform.q, (v3f){0,0,1}, a );
- }
- }
-}
-
-static void skateshop_render_boardshop( ent_skateshop *shop ){
- world_instance *world = world_current_instance();
- u32 slot_count = VG_ARRAY_LEN(global_skateshop.shop_view_slots);
-
- ent_marker *mark_rack = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(shop->boards.id_rack)),
- *mark_display = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(shop->boards.id_display));
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- struct addon_cache *cache = &addon_system.cache[k_addon_type_board];
-
- /* Render loaded boards in the view slots */
- for( u32 i=0; i<slot_count; i++ ){
- struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
- float selected = 0.0f;
-
- if( !slot->cache_id )
- goto fade_out;
-
- addon_cache_entry *entry = vg_pool_item( &cache->pool, slot->cache_id );
-
- if( entry->state != k_addon_cache_state_loaded )
- goto fade_out;
-
- struct player_board *board =
- addon_cache_item( k_addon_type_board, slot->cache_id );
-
- mdl_transform xform;
- transform_identity( &xform );
-
- xform.co[0] = -((float)i - ((float)slot_count)*0.5f)*0.45f;
- mdl_transform_mul( &mark_rack->transform, &xform, &xform );
-
-
- if( entry->reg_index == global_skateshop.selected_board_id ){
- selected = 1.0f;
- }
-
- float t = slot->view_blend;
- v3_lerp( xform.co, mark_display->transform.co, t, xform.co );
- q_nlerp( xform.q, mark_display->transform.q, t, xform.q );
- v3_lerp( xform.s, mark_display->transform.s, t, xform.s );
-
- struct player_board_pose pose = {0};
- m4x3f mmdl;
- mdl_transform_m4x3( &xform, mmdl );
- render_board( &g_render.cam, world, board, mmdl,
- &pose, k_board_shader_entity );
-
-fade_out:;
- float rate = 5.0f*vg.time_delta;
- slot->view_blend = vg_lerpf( slot->view_blend, selected, rate );
- }
-
- ent_marker *mark_info = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(shop->boards.id_info));
- m4x3f mtext, mrack;
- mdl_transform_m4x3( &mark_info->transform, mtext );
- mdl_transform_m4x3( &mark_rack->transform, mrack );
-
- m4x3f mlocal, mmdl;
- m4x3_identity( mlocal );
-
- float scale = 0.2f,
- thickness = 0.03f;
-
- font3d_bind( &gui.font, k_font_shader_default, 0, world, &g_render.cam );
- shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
-
- /* Selection counter
- * ------------------------------------------------------------------ */
- m3x3_zero( mlocal );
- v3_zero( mlocal[3] );
- mlocal[0][0] = -scale*2.0f;
- mlocal[1][2] = -scale*2.0f;
- mlocal[2][1] = -thickness;
- mlocal[3][2] = -0.7f;
- m4x3_mul( mrack, mlocal, mmdl );
-
- u32 valid_count = addon_count(k_addon_type_board,0);
- if( valid_count ){
- char buf[16];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
- vg_strcati32( &str, global_skateshop.selected_board_id+1 );
- vg_strcatch( &str, '/' );
- vg_strcati32( &str, valid_count );
- font3d_simple_draw( 0, buf, &g_render.cam, mmdl );
- }
- else{
- font3d_simple_draw( 0, "Nothing installed", &g_render.cam, mmdl );
- }
-
- u16 cache_id = skateshop_selected_cache_id();
- struct addon_cache_entry *entry = vg_pool_item( &cache->pool, cache_id );
- addon_reg *reg = NULL;
-
- if( entry ) reg = entry->reg_ptr;
-
- if( !reg ){
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- global_skateshop.render.item_title = "";
- global_skateshop.render.item_desc = "";
- return;
- }
-
- if( global_skateshop.render.reg_id != global_skateshop.selected_board_id ){
- global_skateshop.render.item_title = "";
- global_skateshop.render.item_desc = "";
- vg_msg msg;
- vg_msg_init( &msg, reg->metadata, reg->metadata_len );
-
- if( vg_msg_seekframe( &msg, "workshop" ) ){
- const char *title = vg_msg_getkvstr( &msg, "title" );
- if( title ) global_skateshop.render.item_title = title;
-
- const char *dsc = vg_msg_getkvstr( &msg, "author" );
- if( dsc ) global_skateshop.render.item_desc = dsc;
- vg_msg_skip_frame( &msg );
- }
-
- global_skateshop.render.reg_id = global_skateshop.selected_board_id;
- }
-
- /* Skin title
- * ----------------------------------------------------------------- */
- m3x3_zero( mlocal );
- m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
- mlocal[3][0] = -font3d_string_width( 0, global_skateshop.render.item_title );
- mlocal[3][0] *= scale*0.5f;
- mlocal[3][1] = 0.1f;
- mlocal[3][2] = 0.0f;
- m4x3_mul( mtext, mlocal, mmdl );
- font3d_simple_draw( 0, global_skateshop.render.item_title,
- &g_render.cam, mmdl );
-
- /* Author name
- * ----------------------------------------------------------------- */
- scale *= 0.4f;
- m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
- mlocal[3][0] = -font3d_string_width( 0, global_skateshop.render.item_desc );
- mlocal[3][0] *= scale*0.5f;
- mlocal[3][1] = 0.0f;
- mlocal[3][2] = 0.0f;
- m4x3_mul( mtext, mlocal, mmdl );
- font3d_simple_draw( 0, global_skateshop.render.item_desc,
- &g_render.cam, mmdl );
-
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
-}
-
-static void skateshop_render_charshop( ent_skateshop *shop ){
-}
-
-static void skateshop_render_worldshop( ent_skateshop *shop ){
- world_instance *world = world_current_instance();
-
- ent_marker *mark_display = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(shop->worlds.id_display)),
- *mark_info = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(shop->boards.id_info));
-
- if( global_skateshop.render.world_reg != global_skateshop.selected_world_id){
- global_skateshop.render.world_title = "missing: workshop.title";
-
- addon_reg *reg = get_addon_from_index( k_addon_type_world,
- global_skateshop.selected_world_id, ADDON_REG_HIDDEN );
-
- if( !reg )
- goto none;
-
- if( reg->alias.workshop_id )
- {
- vg_msg msg;
- vg_msg_init( &msg, reg->metadata, reg->metadata_len );
-
- global_skateshop.render.world_loc = vg_msg_getkvstr(&msg,"location");
- global_skateshop.render.world_reg = global_skateshop.selected_world_id;
-
- if( vg_msg_seekframe( &msg, "workshop" ) )
- {
- global_skateshop.render.world_title = vg_msg_getkvstr(&msg,"title");
- vg_msg_skip_frame( &msg );
- }
- else {
- vg_warn( "No workshop body\n" );
- }
- }
- else {
- global_skateshop.render.world_title = reg->alias.foldername;
- }
- }
-
-none:;
-
- /* Text */
- char buftext[128], bufsubtext[128];
- vg_str info, subtext;
- vg_strnull( &info, buftext, 128 );
- vg_strnull( &subtext, bufsubtext, 128 );
-
- u32 valid_count = addon_count(k_addon_type_world,ADDON_REG_HIDDEN);
- if( valid_count )
- {
- vg_strcati32( &info, global_skateshop.selected_world_id+1 );
- vg_strcatch( &info, '/' );
- vg_strcati32( &info, valid_count );
- vg_strcatch( &info, ' ' );
- vg_strcat( &info, global_skateshop.render.world_title );
-
- if( !vg_loader_availible() )
- {
- vg_strcat( &subtext, "Loading..." );
- }
- else
- {
- addon_reg *reg = get_addon_from_index( k_addon_type_world,
- global_skateshop.selected_world_id, ADDON_REG_HIDDEN );
-
- if( reg->alias.workshop_id )
- vg_strcat( &subtext, "(Workshop) " );
-
- vg_strcat( &subtext, global_skateshop.render.world_loc );
- }
- }
- else
- {
- vg_strcat( &info, "No workshop worlds installed" );
- }
-
- m4x3f mtext,mlocal,mtextmdl;
- mdl_transform_m4x3( &mark_info->transform, mtext );
-
- font3d_bind( &gui.font, k_font_shader_default, 0, NULL, &g_render.cam );
- shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
-
- float scale = 0.2f, thickness = 0.015f, scale1 = 0.08f;
- m3x3_zero( mlocal );
- m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
- mlocal[3][0] = -font3d_string_width( 0, buftext );
- mlocal[3][0] *= scale*0.5f;
- mlocal[3][1] = 0.1f;
- mlocal[3][2] = 0.0f;
- m4x3_mul( mtext, mlocal, mtextmdl );
- font3d_simple_draw( 0, buftext, &g_render.cam, mtextmdl );
-
- m3x3_setdiagonalv3( mlocal, (v3f){ scale1, scale1, thickness } );
- mlocal[3][0] = -font3d_string_width( 0, bufsubtext );
- mlocal[3][0] *= scale1*0.5f;
- mlocal[3][1] = -scale1*0.3f;
- m4x3_mul( mtext, mlocal, mtextmdl );
- font3d_simple_draw( 0, bufsubtext, &g_render.cam, mtextmdl );
-}
-
-/*
- * World: render event
- */
-void skateshop_render( ent_skateshop *shop )
-{
- if( shop->type == k_skateshop_type_boardshop )
- skateshop_render_boardshop( shop );
- else if( shop->type == k_skateshop_type_charshop )
- skateshop_render_charshop( shop );
- else if( shop->type == k_skateshop_type_worldshop )
- skateshop_render_worldshop( shop );
- else if( shop->type == k_skateshop_type_server ){
- }
- else
- vg_fatal_error( "Unknown store (%u)\n", shop->type );
-}
-
-void skateshop_render_nonfocused( world_instance *world, vg_camera *cam )
-{
- for( u32 j=0; j<mdl_arrcount( &world->ent_skateshop ); j ++ )
- {
- ent_skateshop *shop = mdl_arritm(&world->ent_skateshop, j );
-
- if( shop->type != k_skateshop_type_boardshop ) continue;
-
- f32 dist2 = v3_dist2( cam->pos, shop->transform.co ),
- maxdist = 50.0f;
-
- if( dist2 > maxdist*maxdist ) continue;
- ent_marker *mark_rack = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(shop->boards.id_rack));
-
- if( !mark_rack )
- continue;
-
- u32 slot_count = VG_ARRAY_LEN(global_skateshop.shop_view_slots);
- for( u32 i=0; i<slot_count; i++ )
- {
- struct player_board *board = &localplayer.fallback_board;
-
- mdl_transform xform;
- transform_identity( &xform );
-
- xform.co[0] = -((float)i - ((float)slot_count)*0.5f)*0.45f;
- mdl_transform_mul( &mark_rack->transform, &xform, &xform );
-
- struct player_board_pose pose = {0};
- m4x3f mmdl;
- mdl_transform_m4x3( &xform, mmdl );
- render_board( cam, world, board, mmdl, &pose, k_board_shader_entity );
- }
- }
-}
-
-static void ent_skateshop_helpers_pickable( const char *acceptance )
-{
- vg_str text;
-
- if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
- vg_strcat( &text, "Exit" );
-
- if( (global_skateshop.helper_pick = gui_new_helper(
- input_button_list[k_srbind_maccept], &text))){
- vg_strcat( &text, acceptance );
- }
-
- if( (global_skateshop.helper_browse = gui_new_helper(
- input_axis_list[k_sraxis_mbrowse_h], &text ))){
- vg_strcat( &text, "Browse" );
- }
-}
-
-static void board_scan_thread( void *_args )
-{
- addon_mount_content_folder( k_addon_type_board, "boards", ".mdl" );
- addon_mount_workshop_items();
- vg_async_call( async_addon_reg_update, NULL, 0 );
- vg_async_stall();
-
- /* 04.03.24
- * REVIEW: This is removed as it *should* be done on the preupdate of the
- * addon system.
- *
- * Verify that it works the same.
- */
-#if 0
- board_processview_thread(NULL);
-#endif
-}
-
-static void world_scan_thread( void *_args )
-{
- addon_mount_content_folder( k_addon_type_world, "maps", ".mdl" );
- addon_mount_workshop_items();
- vg_async_call( async_addon_reg_update, NULL, 0 );
-}
-
-/*
- * Entity logic: entrance event
- */
-entity_call_result ent_skateshop_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, index );
- vg_info( "skateshop_call\n" );
-
- if( (skaterift.activity != k_skaterift_default) ||
- !vg_loader_availible() )
- return k_entity_call_result_invalid;
-
- if( call->function == k_ent_function_trigger )
- {
- if( localplayer.subsystem != k_player_subsystem_walk )
- return k_entity_call_result_OK;
-
- vg_info( "Entering skateshop\n" );
-
- world_entity_set_focus( call->id );
- world_entity_focus_modal();
- gui_helper_clear();
-
- if( shop->type == k_skateshop_type_boardshop )
- {
- skateshop_update_viewpage();
- vg_loader_start( board_scan_thread, NULL );
- ent_skateshop_helpers_pickable( "Pick" );
- }
- else if( shop->type == k_skateshop_type_charshop )
- {
- ent_skateshop_helpers_pickable( "Pick" );
- }
- else if( shop->type == k_skateshop_type_worldshop )
- {
- ent_skateshop_helpers_pickable( "Open rift" );
- vg_loader_start( world_scan_thread, NULL );
- }
- else if( shop->type == k_skateshop_type_server )
- {
- vg_str text;
- global_skateshop.helper_pick = gui_new_helper(
- input_button_list[k_srbind_maccept], &text);
- if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
- vg_strcat( &text, "exit" );
- skateshop_server_helper_update();
- }
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
+++ /dev/null
-#pragma once
-#include "world.h"
-#include "world_load.h"
-#include "player.h"
-#include "vg/vg_steam_remote_storage.h"
-#include "workshop.h"
-#include "addon.h"
-
-#define SKATESHOP_VIEW_SLOT_MAX 5
-
-struct global_skateshop
-{
- v3f look_target;
-
- struct shop_view_slot{
- u16 cache_id;
- float view_blend;
- }
- shop_view_slots[ SKATESHOP_VIEW_SLOT_MAX ];
-
- u32 selected_world_id,
- selected_board_id,
- selected_player_id,
- pointcloud_world_id;
-
- struct {
- const char *item_title, *item_desc;
- u32 reg_id;
-
- const char *world_title, *world_loc;
- u32 world_reg;
- }
- render;
-
- union {
- struct gui_helper *helper_pick, *helper_toggle;
- };
-
- struct gui_helper *helper_browse;
-
-
- addon_reg *reg_preview, *reg_loaded_preview;
- GLuint tex_preview;
-}
-extern global_skateshop;
-
-void skateshop_init(void);
-void ent_skateshop_preupdate( ent_focus_context *ctx );
-void skateshop_render( ent_skateshop *shop );
-void skateshop_render_nonfocused( world_instance *world, vg_camera *cam );
-void skateshop_autostart_loading(void);
-void skateshop_world_preupdate( world_instance *world );
-entity_call_result ent_skateshop_call( world_instance *world, ent_call *call );
-void skateshop_world_preview_preupdate(void);
+++ /dev/null
-#include "world.h"
-#include "particle.h"
-
-static f32 k_tornado_strength = 0.0f,
- k_tornado_ratio = 0.5f,
- k_tornado_range = 10.f;
-
-void ent_tornado_init(void)
-{
- vg_console_reg_var( "k_tonado_strength", &k_tornado_strength,
- k_var_dtype_f32, VG_VAR_PERSISTENT|VG_VAR_CHEAT );
- vg_console_reg_var( "k_tonado_ratio", &k_tornado_ratio,
- k_var_dtype_f32, VG_VAR_PERSISTENT|VG_VAR_CHEAT );
- vg_console_reg_var( "k_tonado_range", &k_tornado_range,
- k_var_dtype_f32, VG_VAR_PERSISTENT|VG_VAR_CHEAT );
-}
-
-void ent_tornado_debug(void)
-{
- world_instance *world = world_current_instance();
- for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i ++ ){
- ent_marker *marker = mdl_arritm( &world->ent_marker, i );
-
- if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tornado" ) ){
- v3f p1;
- v3_add( marker->transform.co, (v3f){0,20,0}, p1 );
- vg_line( marker->transform.co, p1, VG__RED );
-
- m4x3f mmdl;
- m4x3_identity( mmdl );
- v3_copy( marker->transform.co, mmdl[3] );
- vg_line_sphere( mmdl, k_tornado_range, 0 );
- }
- }
-}
-
-void ent_tornado_forces( v3f co, v3f cv, v3f out_a )
-{
- world_instance *world = world_current_instance();
- v3_zero( out_a );
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i ++ ){
- ent_marker *marker = mdl_arritm( &world->ent_marker, i );
-
- if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tornado" ) ){
- v3f d, dir;
- v3_sub( co, marker->transform.co, d );
- d[1] = 0.0f;
-
- f32 dist = v3_length( d );
- v3_normalize( d );
-
- v3_cross( d, (v3f){0,1,0}, dir );
- if( v3_dot( dir, cv ) < 0.0f )
- v3_negate( dir, dir );
-
- f32 s = vg_maxf(0.0f, 1.0f-dist/k_tornado_range),
- F0 = s*k_tornado_strength,
- F1 = s*s*k_tornado_strength;
-
- v3_muladds( out_a, dir, F0 * k_tornado_ratio, out_a );
- v3_muladds( out_a, d, F1 * -(1.0f-k_tornado_ratio), out_a );
- }
- }
-}
-
-void ent_tornado_pre_update(void)
-{
- world_instance *world = world_current_instance();
- for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i ++ ){
- ent_marker *marker = mdl_arritm( &world->ent_marker, i );
-
- if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tornado" ) ){
- v3f co;
- vg_rand_sphere( &vg.rand, co );
-
- v3f tangent = { co[2], 0, co[0] };
-
- f32 s = vg_signf( co[1] );
- v3_muls( tangent, s*10.0f, tangent );
- co[1] *= s;
-
- v3_muladds( marker->transform.co, co, k_tornado_range, co );
- particle_spawn( &particles_env, co, tangent, 2.0f, 0xffffffff );
- }
- }
-}
+++ /dev/null
-#pragma once
-
-void ent_tornado_init(void);
-void ent_tornado_debug(void);
-void ent_tornado_forces( v3f co, v3f cv, v3f out_a );
-void ent_tornado_pre_update(void);
+++ /dev/null
-#include "world.h"
-
-void ent_traffic_update( world_instance *world, v3f pos )
-{
- for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
- ent_traffic *traffic = mdl_arritm( &world->ent_traffic, i );
-
- u32 i1 = traffic->index,
- i0,
- i2 = i1+1;
-
- if( i1 == 0 ) i0 = traffic->node_count-1;
- else i0 = i1-1;
-
- if( i2 >= traffic->node_count ) i2 = 0;
-
- i0 += traffic->start_node;
- i1 += traffic->start_node;
- i2 += traffic->start_node;
-
- v3f h[3];
-
- ent_route_node *rn0 = mdl_arritm( &world->ent_route_node, i0 ),
- *rn1 = mdl_arritm( &world->ent_route_node, i1 ),
- *rn2 = mdl_arritm( &world->ent_route_node, i2 );
-
- v3_copy( rn1->co, h[1] );
- v3_lerp( rn0->co, rn1->co, 0.5f, h[0] );
- v3_lerp( rn1->co, rn2->co, 0.5f, h[2] );
-
- float const k_sample_dist = 0.0025f;
- v3f pc, pd;
- eval_bezier3( h[0], h[1], h[2], traffic->t, pc );
- eval_bezier3( h[0], h[1], h[2], traffic->t+k_sample_dist, pd );
-
- v3f v0;
- v3_sub( pd, pc, v0 );
- float length = vg_maxf( 0.0001f, v3_length( v0 ) );
- v3_muls( v0, 1.0f/length, v0 );
-
- float mod = k_sample_dist / length;
-
- traffic->t += traffic->speed * vg.time_delta * mod;
-
- if( traffic->t > 1.0f ){
- traffic->t -= 1.0f;
-
- if( traffic->t > 1.0f ) traffic->t = 0.0f;
-
- traffic->index ++;
-
- if( traffic->index >= traffic->node_count )
- traffic->index = 0;
- }
-
- v3_copy( pc, traffic->transform.co );
-
- float a = atan2f( -v0[0], v0[2] );
- q_axis_angle( traffic->transform.q, (v3f){0.0f,1.0f,0.0f}, -a );
-
- vg_line_point( traffic->transform.co, 0.3f, VG__BLUE );
- }
-}
+++ /dev/null
-#pragma once
-#include "world.h"
-void ent_traffic_update( world_instance *world, v3f pos );
+++ /dev/null
-#include "world.h"
-#include "entity.h"
-#include "world_entity.h"
-
-#include "ent_objective.h"
-#include "ent_skateshop.h"
-#include "ent_relay.h"
-#include "ent_challenge.h"
-#include "ent_route.h"
-#include "ent_miniworld.h"
-#include "ent_region.h"
-#include "ent_glider.h"
-#include "ent_npc.h"
-#include "world_water.h"
-
-#include <string.h>
-
-void entity_call( world_instance *world, ent_call *call )
-{
- u32 type = mdl_entity_id_type( call->id ),
- index = mdl_entity_id_id( call->id );
-
- fn_entity_call_handler table[] = {
- [k_ent_volume] = ent_volume_call,
- [k_ent_audio] = ent_audio_call,
- [k_ent_skateshop] = ent_skateshop_call,
- [k_ent_objective] = ent_objective_call,
- [k_ent_ccmd] = ent_ccmd_call,
- [k_ent_gate] = ent_gate_call,
- [k_ent_relay] = ent_relay_call,
- [k_ent_challenge] = ent_challenge_call,
- [k_ent_route] = ent_route_call,
- [k_ent_miniworld] = ent_miniworld_call,
- [k_ent_region] = ent_region_call,
- [k_ent_glider] = ent_glider_call,
- [k_ent_npc] = ent_npc_call,
- [k_ent_water] = ent_water_call,
- };
-
- if( type >= VG_ARRAY_LEN(table) ){
- vg_error( "call to entity type: %u is out of range\n", type );
- return;
- }
-
- fn_entity_call_handler fn = table[ type ];
-
- if( !fn )
- {
- vg_error( "Entity type %u does not have a call handler, "
- "but was called anyway\n", type );
- return;
- }
-
- enum entity_call_result res = fn( world, call );
-
- if( res == k_entity_call_result_unhandled )
- {
- vg_warn( "Call to entity %u#%u was unhandled.\n", type, index );
- }
-}
-
-ent_marker *ent_find_marker( mdl_context *mdl, mdl_array_ptr *arr,
- const char *alias )
-{
- for( u32 i=0; i<mdl_arrcount(arr); i++ )
- {
- ent_marker *marker = mdl_arritm( arr, i );
-
- if( !strcmp( mdl_pstr( mdl, marker->pstr_alias ), alias ) )
- {
- return marker;
- }
- }
-
- return NULL;
-}
-
+++ /dev/null
-#pragma once
-
-#include "vg/vg_audio.h"
-#include "vg/vg_ui/imgui.h"
-#include "model.h"
-
-typedef struct ent_spawn ent_spawn;
-typedef struct ent_light ent_light;
-typedef struct ent_gate ent_gate;
-typedef struct ent_route_node ent_route_node;
-typedef struct ent_path_index ent_path_index;
-typedef struct ent_checkpoint ent_checkpoint;
-typedef struct ent_route ent_route;
-typedef struct ent_water ent_water;
-typedef struct ent_audio_clip ent_audio_clip;
-typedef struct volume_particles volume_particles;
-typedef struct volume_trigger volume_trigger;
-typedef struct ent_volume ent_volume;
-typedef struct ent_audio ent_audio;
-typedef struct ent_marker ent_marker;
-typedef struct ent_traffic ent_traffic;
-typedef struct ent_font ent_font;
-typedef struct ent_font_variant ent_font_variant;
-typedef struct ent_glyph ent_glyph;
-typedef struct ent_skateshop ent_skateshop;
-typedef struct ent_camera ent_camera;
-typedef struct ent_swspreview ent_swspreview;
-typedef struct ent_worldinfo ent_worldinfo;
-typedef struct ent_ccmd ent_ccmd;
-typedef struct ent_objective ent_objective;
-typedef struct ent_challenge ent_challenge;
-typedef struct ent_relay ent_relay;
-typedef struct ent_cubemap ent_cubemap;
-typedef struct ent_miniworld ent_miniworld;
-typedef struct ent_prop ent_prop;
-typedef struct ent_region ent_region;
-typedef struct ent_list ent_list;
-typedef struct ent_glider ent_glider;
-typedef struct ent_npc ent_npc;
-
-enum entity_alias{
- k_ent_none = 0,
- k_ent_gate = 1,
- k_ent_spawn = 2,
- k_ent_route_node = 3,
- k_ent_route = 4,
- k_ent_water = 5,
- k_ent_volume = 6,
- k_ent_audio = 7,
- k_ent_marker = 8,
- k_ent_font = 9,
- k_ent_font_variant= 10,
- k_ent_traffic = 11,
- k_ent_skateshop = 12,
- k_ent_camera = 13,
- k_ent_swspreview = 14,
- k_ent_menuitem = 15,
- k_ent_worldinfo = 16,
- k_ent_ccmd = 17,
- k_ent_objective = 18,
- k_ent_challenge = 19,
- k_ent_relay = 20,
- k_ent_cubemap = 21,
- k_ent_miniworld = 22,
- k_ent_prop = 23,
- k_ent_list = 24,
- k_ent_region = 25,
- k_ent_glider = 26,
- k_ent_npc = 27
-};
-
-typedef struct ent_call ent_call;
-typedef enum entity_call_result entity_call_result;
-enum entity_call_result
-{
- k_entity_call_result_OK,
- k_entity_call_result_unhandled,
- k_entity_call_result_invalid
-};
-
-static inline u32 mdl_entity_id_type( u32 entity_id )
-{
- return (entity_id & 0x0fff0000) >> 16;
-}
-
-static inline u32 mdl_entity_id_id( u32 entity_id )
-{
- return entity_id & 0x0000ffff;
-}
-
-static inline u32 mdl_entity_id( u32 type, u32 index )
-{
- return (type & 0xfffff)<<16 | (index & 0xfffff);
-}
-
-enum entity_function
-{
- k_ent_function_trigger,
- k_ent_function_particle_spawn,
- k_ent_function_trigger_leave
-};
-
-struct ent_spawn{
- mdl_transform transform;
- u32 pstr_name;
-};
-
-enum light_type{
- k_light_type_point = 0,
- k_light_type_spot = 1
-};
-
-struct ent_light{
- mdl_transform transform;
- u32 daytime,
- type;
-
- v4f colour;
- float angle,
- range;
-
- m4x3f inverse_world;
- v2f angle_sin_cos;
-};
-
-/* v101 */
-#if 0
-enum gate_type{
- k_gate_type_unlinked = 0,
- k_gate_type_teleport = 1,
- k_gate_type_nonlocal_unlinked = 2,
- k_gate_type_nonlocel = 3
-};
-#endif
-
-/* v102+ */
-enum ent_gate_flag{
- k_ent_gate_linked = 0x1, /* this is a working portal */
- k_ent_gate_nonlocal = 0x2, /* use the key string to link this portal.
- NOTE: if set, it adds the flip flag. */
- k_ent_gate_flip = 0x4, /* flip direction 180* for exiting portal */
- k_ent_gate_custom_mesh = 0x8, /* use a custom submesh instead of default */
- k_ent_gate_locked = 0x10,/* has to be unlocked to be useful */
-
- k_ent_gate_clean_pass = 0x20,/* player didn't rewind while getting here */
-};
-
-struct ent_gate{
- u32 flags,
- target,
- key;
-
- v3f dimensions,
- co[2];
-
- v4f q[2];
-
- /* runtime */
- m4x3f to_world, transport;
-
- union{
- u32 timing_version;
-
- struct{
- u8 ref_count;
- };
- };
-
- double timing_time;
- u16 routes[4]; /* routes that pass through this gate */
- u8 route_count;
-
- /* v102+ */
- u32 submesh_start, submesh_count;
-};
-
-struct ent_route_node{
- v3f co;
- u8 ref_count, ref_total;
-};
-
-struct ent_path_index{
- u16 index;
-};
-
-struct ent_checkpoint{
- u16 gate_index,
- path_start,
- path_count;
-
- /* EXTENSION */
- f32 best_time;
-};
-
-enum ent_route_flag {
- k_ent_route_flag_achieve_silver = 0x1,
- k_ent_route_flag_achieve_gold = 0x2,
-
- k_ent_route_flag_out_of_zone = 0x10,
- k_ent_region_flag_hasname = 0x20
-};
-
-struct ent_route{
- union{
- mdl_transform transform;
- u32 official_track_id; /* TODO: remove this */
- }
- anon;
-
- u32 pstr_name;
- u16 checkpoints_start,
- checkpoints_count;
-
- v4f colour;
-
- /* runtime */
- u16 active_checkpoint,
- valid_checkpoints;
-
- f32 factive;
- m4x3f board_transform;
- mdl_submesh sm;
- f64 timing_base;
-
- u32 id_camera; /* v103+ */
-
- /* v104+, but always accessible */
- u32 flags;
- f64 best_laptime;
- f32 ui_stopper, ui_residual;
-
- ui_px ui_first_block_width, ui_residual_block_w;
-};
-
-struct ent_water{
- mdl_transform transform;
- float max_dist;
- u32 reserved0, reserved1;
-};
-
-struct ent_audio_clip{
- union{
- mdl_file file;
- audio_clip clip;
- }_;
-
- float probability;
-};
-
-struct volume_particles{
- u32 blank, blank2;
-};
-
-struct volume_trigger{
- i32 event, event_leave;
-};
-
-enum ent_volume_flag {
- k_ent_volume_flag_particles = 0x1,
- k_ent_volume_flag_disabled = 0x2
-};
-
-struct ent_volume{
- mdl_transform transform;
- m4x3f to_world, to_local;
- u32 flags;
-
- u32 target;
- union{
- volume_trigger trigger;
- volume_particles particles;
- };
-};
-
-struct ent_audio{
- mdl_transform transform;
- u32 flags,
- clip_start,
- clip_count;
- float volume, crossfade;
- u32 behaviour,
- group,
- probability_curve,
- max_channels;
-};
-
-struct ent_marker{
- mdl_transform transform;
- u32 pstr_alias;
-};
-
-enum skateshop_type{
- k_skateshop_type_boardshop = 0,
- k_skateshop_type_charshop = 1,
- k_skateshop_type_worldshop = 2,
- k_skateshop_type_DELETED = 3,
- k_skateshop_type_server = 4
-};
-
-struct ent_skateshop{
- mdl_transform transform;
- u32 type, id_camera;
-
- union{
- struct{
- u32 id_display,
- id_info,
- id_rack;
- }
- boards;
-
- struct{
- u32 id_display,
- id_info;
- }
- character;
-
- struct{
- u32 id_display,
- id_info;
- }
- worlds;
-
- struct{
- u32 id_lever;
- }
- server;
- };
-};
-
-struct ent_swspreview{
- u32 id_camera, id_display, id_display1;
-};
-
-struct ent_traffic{
- mdl_transform transform;
- u32 submesh_start,
- submesh_count,
- start_node,
- node_count;
- float speed,
- t;
- u32 index; /* into the path */
-};
-
-struct ent_camera{
- mdl_transform transform;
- float fov;
-};
-
-enum ent_menuitem_type{
- k_ent_menuitem_type_visual = 0,
- k_ent_menuitem_type_event_button = 1,
- k_ent_menuitem_type_page_button = 2,
- k_ent_menuitem_type_toggle = 3,
- k_ent_menuitem_type_slider = 4,
- k_ent_menuitem_type_page = 5,
- k_ent_menuitem_type_binding = 6,
- k_ent_menuitem_type_visual_nocol = 7,
- k_ent_menuitem_type_disabled = 90
-};
-
-enum ent_menuitem_stack_behaviour{
- k_ent_menuitem_stack_append = 0,
- k_ent_menuitem_stack_replace = 1
-};
-
-typedef struct ent_menuitem ent_menuitem;
-struct ent_menuitem{
- u32 type, groups,
- id_links[4]; /* ent_menuitem */
- f32 factive, fvisible;
-
- mdl_transform transform;
- u32 submesh_start, submesh_count;
-
- union{ u64 _u64; /* force storage for 64bit pointers */
- i32 *pi32;
- f32 *pf32;
- void *pvoid;
- };
-
- union{
- struct{
- u32 pstr_name;
- }
- visual;
-
- struct{
- u32 id_min, /* ent_marker */
- id_max, /* . */
- id_handle, /* ent_menuitem */
- pstr_data;
- }
- slider;
-
- struct{
- u32 pstr,
- stack_behaviour;
- }
- button;
-
- struct{
- u32 id_check, /* ent_menuitem */
- pstr_data;
- v3f offset; /* relative to parent */
- }
- checkmark;
-
- struct{
- u32 pstr_name,
- id_entrypoint, /* ent_menuitem */
- id_viewpoint; /* ent_camera */
- }
- page;
-
- struct{
- u32 pstr_bind,
- font_variant;
- }
- binding;
- };
-};
-
-struct ent_worldinfo{
- u32 pstr_name, pstr_author, pstr_desc;
- f32 timezone;
- u32 pstr_skybox;
- u32 flags;
-};
-
-ent_marker *ent_find_marker( mdl_context *mdl, mdl_array_ptr *arr,
- const char *alias );
-
-enum channel_behaviour{
- k_channel_behaviour_unlimited = 0,
- k_channel_behaviour_discard_if_full = 1,
- k_channel_behaviour_crossfade_if_full = 2
-};
-
-enum probability_curve{
- k_probability_curve_constant = 0,
- k_probability_curve_wildlife_day = 1,
- k_probability_curve_wildlife_night = 2
-};
-
-struct ent_font{
- u32 alias,
- variant_start,
- variant_count,
- glyph_start,
- glyph_count,
- glyph_utf32_base;
-};
-
-struct ent_font_variant{
- u32 name,
- material_id;
-};
-
-struct ent_glyph{
- v2f size;
- u32 indice_start,
- indice_count;
-};
-
-struct ent_ccmd{
- u32 pstr_command;
-};
-
-enum ent_objective_filter{
- k_ent_objective_filter_none = 0x00000000,
- k_ent_objective_filter_trick_shuvit = 0x00000001,
- k_ent_objective_filter_trick_kickflip = 0x00000002,
- k_ent_objective_filter_trick_treflip = 0x00000004,
- k_ent_objective_filter_trick_any =
- k_ent_objective_filter_trick_shuvit|
- k_ent_objective_filter_trick_treflip|
- k_ent_objective_filter_trick_kickflip,
- k_ent_objective_filter_flip_back = 0x00000008,
- k_ent_objective_filter_flip_front = 0x00000010,
- k_ent_objective_filter_flip_any =
- k_ent_objective_filter_flip_back|
- k_ent_objective_filter_flip_front,
- k_ent_objective_filter_grind_truck_any = 0x00000020,
- k_ent_objective_filter_grind_board_any = 0x00000040,
- k_ent_objective_filter_grind_any =
- k_ent_objective_filter_grind_truck_any|
- k_ent_objective_filter_grind_board_any,
- k_ent_objective_filter_footplant = 0x00000080,
- k_ent_objective_filter_passthrough = 0x00000100
-};
-
-enum ent_objective_flag {
- k_ent_objective_hidden = 0x1,
- k_ent_objective_passed = 0x2
-};
-
-struct ent_objective{
- mdl_transform transform;
- u32 submesh_start,
- submesh_count,
- flags,
- id_next,
- filter,filter2,
- id_win;
- i32 win_event;
- f32 time_limit;
-};
-
-enum ent_challenge_flag {
- k_ent_challenge_timelimit = 0x1
-};
-
-struct ent_challenge{
- mdl_transform transform;
- u32 pstr_alias,
- flags,
- target;
- i32 target_event;
- u32 reset;
- i32 reset_event;
- u32 first,
- camera,
- status;
-};
-
-struct ent_relay {
- u32 targets[4][2];
- i32 targets_events[4];
-};
-
-struct ent_cubemap {
- v3f co;
- u32 resolution, live, texture_id,
- framebuffer_id, renderbuffer_id, placeholder[2];
-};
-
-struct ent_miniworld {
- mdl_transform transform;
- u32 pstr_world;
- u32 camera;
- u32 proxy;
-};
-
-struct ent_prop {
- mdl_transform transform;
- u32 submesh_start, submesh_count, flags, pstr_alias;
-};
-
-struct ent_region {
- mdl_transform transform;
- u32 submesh_start, submesh_count, pstr_title, flags, zone_volume,
-
- /* 105+ */
- target0[2];
-};
-
-struct ent_glider {
- mdl_transform transform;
- u32 flags;
- f32 cooldown;
-};
-
-struct ent_npc
-{
- mdl_transform transform;
- u32 id, context, camera;
-};
-
-#include "world.h"
-
-struct ent_call{
- u32 id;
- i32 function;
- void *data;
-};
-
-typedef enum entity_call_result
- (*fn_entity_call_handler)( world_instance *, ent_call *);
-
-void entity_call( world_instance *world, ent_call *call );
+++ /dev/null
-#pragma once
-#include "model.h"
-#include "entity.h"
-#include "vg/vg_camera.h"
-#include "shaders/model_font.h"
-#include "shaders/scene_font.h"
-#include "world_render.h"
-#include "depth_compare.h"
-#include "vg/vg_tex.h"
-#include <string.h>
-
-enum efont_SRglyph{
- k_SRglyph_end = 0x00, /* control characters */
- k_SRglyph_ctrl_variant = 0x01,
- k_SRglyph_ctrl_size = 0x02, /* normalized 0-1 */
- k_SRglyph_ctrl_center = 0x03, /* useful when text is scaled down */
- k_SRglyph_ctrl_baseline = 0x04, /* . */
- k_SRglyph_ctrl_top = 0x05, /* . */
- k_SRglyph_mod_circle = 0x1e, /* surround and center next charater */
- k_SRglyph_mod_square = 0x1f, /* surround and center next character */
- k_SRglyph_ascii_min = 0x20, /* standard ascii */
- k_SRglyph_ascii_max = 0x7e,
- k_SRglyph_ps4_square = 0x7f,/* playstation buttons */
- k_SRglyph_ps4_triangle = 0x80,
- k_SRglyph_ps4_circle = 0x81,
- k_SRglyph_ps4_cross = 0x82,
- k_SRglyph_xb1_x = 0x83,/* xbox buttons */
- k_SRglyph_xb1_y = 0x84,
- k_SRglyph_xb1_a = 0x85,
- k_SRglyph_xb1_b = 0x86,
- k_SRglyph_gen_ls = 0x87,/* generic gamepad */
- k_SRglyph_gen_lsh = 0x88,
- k_SRglyph_gen_lsv = 0x89,
- k_SRglyph_gen_lshv = 0x8a,
- k_SRglyph_gen_rs = 0x8b,
- k_SRglyph_gen_rsh = 0x8c,
- k_SRglyph_gen_rsv = 0x8d,
- k_SRglyph_gen_rshv = 0x8e,
- k_SRglyph_gen_lt = 0x8f,
- k_SRglyph_gen_rt = 0x90,
- k_SRglyph_gen_lb = 0x91,
- k_SRglyph_gen_rb = 0x92,
- k_SRglyph_gen_left = 0x93,
- k_SRglyph_gen_up = 0x94,
- k_SRglyph_gen_right = 0x95,
- k_SRglyph_gen_down = 0x96,
- k_SRglyph_gen_options = 0x97,
- k_SRglyph_gen_shareview = 0x98,
- k_SRglyph_kbm_m0 = 0x99,/* mouse */
- k_SRglyph_kbm_m1 = 0x9a,
- k_SRglyph_kbm_m01 = 0x9b,
- k_SRglyph_kbm_m2 = 0x9c,
- k_SRglyph_kbm_m2s = 0x9d,
- k_SRglyph_kbm_shift = 0x9e,/* modifiers */
- k_SRglyph_kbm_ctrl = 0x9f,
- k_SRglyph_kbm_alt = 0xa0,
- k_SRglyph_kbm_space = 0xa1,
- k_SRglyph_kbm_return = 0xa2,
- k_SRglyph_kbm_escape = 0xa3,
- k_SRglyph_kbm_mousemove = 0xa4,
-
-#if 0
- k_SRglyph_vg_ret = 0xa5,
- k_SRglyph_vg_link = 0xa6,
- k_SRglyph_vg_square = 0xa7,
- k_SRglyph_vg_triangle = 0xa8,
- k_SRglyph_vg_circle = 0xa9
-#endif
-};
-
-typedef struct font3d font3d;
-struct font3d{
- mdl_context mdl;
- GLuint texture;
- glmesh mesh;
-
- ent_font info;
- mdl_array_ptr font_variants,
- glyphs;
-};
-
-static void font3d_load( font3d *font, const char *mdl_path, void *alloc ){
- mdl_open( &font->mdl, mdl_path, alloc );
- mdl_load_metadata_block( &font->mdl, alloc );
-
- vg_linear_clear( vg_mem.scratch );
- mdl_array_ptr fonts;
- MDL_LOAD_ARRAY( &font->mdl, &fonts, ent_font, vg_mem.scratch );
- font->info = *((ent_font *)mdl_arritm(&fonts,0));
-
- MDL_LOAD_ARRAY( &font->mdl, &font->font_variants, ent_font_variant, alloc);
- MDL_LOAD_ARRAY( &font->mdl, &font->glyphs, ent_glyph, alloc );
-
- vg_linear_clear( vg_mem.scratch );
-
- if( !mdl_arrcount( &font->mdl.textures ) )
- vg_fatal_error( "No texture in font file" );
-
- mdl_texture *tex0 = mdl_arritm( &font->mdl.textures, 0 );
- void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
- mdl_fread_pack_file( &font->mdl, &tex0->file, data );
-
- mdl_async_load_glmesh( &font->mdl, &font->mesh, NULL );
- vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
- VG_TEX2D_LINEAR|VG_TEX2D_CLAMP,
- &font->texture );
-
- mdl_close( &font->mdl );
-}
-
-static u32 font3d_find_variant( font3d *font, const char *name ){
- for( u32 i=0; i<mdl_arrcount( &font->font_variants ); i ++ ){
- ent_font_variant *variant = mdl_arritm( &font->font_variants, i );
-
- if( !strcmp( mdl_pstr( &font->mdl, variant->name ), name ) ){
- return i;
- }
- }
-
- return 0;
-}
-
-struct _font3d_render{
- v4f offset;
- font3d *font;
- u32 variant_id;
-
- enum font_shader {
- k_font_shader_default,
- k_font_shader_world
- }
- shader;
-}
-static gui_font3d;
-
-/*
- * world can be null if not using world shader
- */
-static void font3d_bind( font3d *font, enum font_shader shader,
- int depth_compare, world_instance *world,
- vg_camera *cam ){
- gui_font3d.shader = shader;
- gui_font3d.font = font;
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, font->texture );
-
- if( shader == k_font_shader_default )
- {
- shader_model_font_use();
- shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
- shader_model_font_uTexMain( 1 );
- shader_model_font_uDepthMode( depth_compare );
-
- if( depth_compare ){
- depth_compare_bind(
- shader_model_font_uTexSceneDepth,
- shader_model_font_uInverseRatioDepth,
- shader_model_font_uInverseRatioMain, cam );
- }
-
- shader_model_font_uPv( cam->mtx.pv );
- }
- else if( shader == k_font_shader_world )
- {
- shader_scene_font_use();
- shader_scene_font_uTexGarbage(0);
- shader_scene_font_uTexMain(1);
-
- shader_scene_font_uPv( g_render.cam.mtx.pv );
- shader_scene_font_uTime( vg.time );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_font );
-
- bind_terrain_noise();
- shader_scene_font_uCamera( g_render.cam.transform[3] );
- }
- mesh_bind( &font->mesh );
-}
-
-static ent_glyph *font3d_glyph( font3d *font, u32 variant_id, u32 utf32 ){
- if( utf32 < font->info.glyph_utf32_base ) return NULL;
- if( utf32 >= font->info.glyph_utf32_base+font->info.glyph_count) return NULL;
-
- u32 index = utf32 - font->info.glyph_utf32_base;
- index += font->info.glyph_start;
- index += font->info.glyph_count * variant_id;
- return mdl_arritm( &font->glyphs, index );
-}
-
-static void font3d_set_transform( const char *text,
- vg_camera *cam, m4x3f transform ){
- v4_copy( (v4f){0.0f,0.0f,0.0f,1.0f}, gui_font3d.offset );
-
- m4x4f prev_mtx;
- m4x3_expand( transform, prev_mtx );
- m4x4_mul( cam->mtx_prev.pv, prev_mtx, prev_mtx );
-
- if( gui_font3d.shader == k_font_shader_default ){
- shader_model_font_uPvmPrev( prev_mtx );
- shader_model_font_uMdl( transform );
- }
- else if( gui_font3d.shader == k_font_shader_world ){
- shader_scene_font_uPvmPrev( prev_mtx );
- shader_scene_font_uMdl( transform );
- }
-}
-
-static void font3d_setoffset( v4f offset ){
- if( gui_font3d.shader == k_font_shader_default )
- shader_model_font_uOffset( offset );
- else if( gui_font3d.shader == k_font_shader_world )
- shader_scene_font_uOffset( offset );
-}
-
-static void font3d_setcolour( v4f colour ){
- if( gui_font3d.shader == k_font_shader_default )
- shader_model_font_uColour( colour );
-#if 0
- else if( gui_font3d.shader == k_font_shader_world )
- shader_scene_font_uColour( colour );
-#endif
-}
-
-static void font3d_draw( const char *text ){
- u8 *u8pch = (u8*)text;
-
- u32 max_chars = 512;
- while( u8pch && max_chars ){
- max_chars --;
-
- u32 c0 = *u8pch, c1;
- u8pch ++;
-
- if( !c0 ) break;
-
- ent_glyph *glyph0 = font3d_glyph( gui_font3d.font,
- gui_font3d.variant_id, c0 ),
- *glyph1 = NULL;
-
- /* multibyte characters */
- if( c0 >= 1 && c0 < k_SRglyph_ascii_min ){
- c1 = *u8pch;
- if( !c1 ) break;
- glyph1 = font3d_glyph( gui_font3d.font, gui_font3d.variant_id, c1 );
- }
-
- if( c0 == k_SRglyph_ctrl_variant ){
- gui_font3d.variant_id = c1;
- u8pch ++;
- continue;
- }
- else if( c0 == k_SRglyph_ctrl_size ){
- gui_font3d.offset[3] = (float)c1 * (1.0f/255.0f);
- u8pch ++;
- continue;
- }
- else if( c0 == k_SRglyph_ctrl_baseline ){
- gui_font3d.offset[1] = 0.0f;
- continue;
- }
- else if( c0 == k_SRglyph_ctrl_center ){
- if( glyph1 ){
- float diff = glyph1->size[1] - glyph1->size[1]*gui_font3d.offset[3];
- gui_font3d.offset[1] = diff * 0.5f;
- }
- continue;
- }
- else if( c0 == k_SRglyph_ctrl_top ){
- if( glyph1 ){
- float diff = glyph1->size[1] - glyph1->size[1]*gui_font3d.offset[3];
- gui_font3d.offset[1] = diff;
- }
- continue;
- }
-
- if( !glyph0 ) continue;
-
- if( glyph1 && (c0 == k_SRglyph_mod_square || c0 == k_SRglyph_mod_circle)){
- v4f v0;
- v2_sub( glyph0->size, glyph1->size, v0 );
- v2_muladds( gui_font3d.offset, v0, -0.5f, v0 );
- v0[2] = gui_font3d.offset[2];
- v0[3] = gui_font3d.offset[3];
-
- font3d_setoffset( v0 );
- mesh_drawn( glyph0->indice_start, glyph0->indice_count );
- continue;
- }
- else{
- font3d_setoffset( gui_font3d.offset );
- mesh_drawn( glyph0->indice_start, glyph0->indice_count );
- }
-
- gui_font3d.offset[0] += glyph0->size[0]*gui_font3d.offset[3];
- }
-}
-
-static f32 font3d_simple_draw( u32 variant_id, const char *text,
- vg_camera *cam, m4x3f transform ){
- if( !text ) return 0.0f;
-
- gui_font3d.variant_id = variant_id;
- font3d_set_transform( text, cam, transform );
- font3d_draw( text );
- return gui_font3d.offset[0];
-}
-
-static f32 font3d_string_width( u32 variant_id, const char *text ){
- if( !text ) return 0.0f;
- float width = 0.0f;
-
- const u8 *buf = (const u8 *)text;
- for( int i=0;; i++ ){
- u32 c = buf[i];
- if(!c) break;
-
- ent_glyph *glyph = font3d_glyph( gui_font3d.font, variant_id, c );
- if( !glyph ) continue;
-
- width += glyph->size[0];
- }
-
- return width;
-}
+++ /dev/null
-#include "skaterift.h"
-#include "player.h"
-#include "player_render.h"
-#include "player_replay.h"
-#include "input.h"
-
-void freecam_preupdate(void)
-{
- vg_camera *cam = &player_replay.replay_freecam;
- v3f angles;
- v3_copy( cam->angles, angles );
- player_look( angles, 1.0f );
-
- f32 decay = vg_maxf(0.0f,1.0f-vg.time_frame_delta*10.0f);
-
- v3f d;
- v3_sub( angles, cam->angles, d );
- v3_muladds( player_replay.freecam_w, d, 20.0f, player_replay.freecam_w );
- v3_muls( player_replay.freecam_w, decay, player_replay.freecam_w );
- v3_muladds( cam->angles, player_replay.freecam_w, vg.time_frame_delta,
- cam->angles );
- cam->angles[1] = vg_clampf( cam->angles[1], -VG_PIf*0.5f,VG_PIf*0.5f);
-
- vg_camera_update_transform( cam );
-
- v3f lookdir = { 0.0f, 0.0f, -1.0f },
- sidedir = { 1.0f, 0.0f, 0.0f };
-
- m3x3_mulv( cam->transform, lookdir, lookdir );
- m3x3_mulv( cam->transform, sidedir, sidedir );
-
- v2f input;
- joystick_state( k_srjoystick_steer, input );
- v2_muls( input, vg.time_frame_delta*6.0f*20.0f, input );
-
- v3_muladds( player_replay.freecam_v, lookdir, -input[1],
- player_replay.freecam_v );
- v3_muladds( player_replay.freecam_v, sidedir, input[0],
- player_replay.freecam_v );
-
- v3_muls( player_replay.freecam_v, decay, player_replay.freecam_v );
- v3_muladds( cam->pos,
- player_replay.freecam_v, vg.time_frame_delta, cam->pos );
-}
+++ /dev/null
-#pragma once
-void freecam_preupdate(void);
-int freecam_cmd( int argc, const char *argv[] );
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#define _DEFAULT_SOURCE
-#include <signal.h>
-#include <unistd.h>
-#include <time.h>
-#include <string.h>
-
-volatile sig_atomic_t sig_stop;
-
-#include "gameserver.h"
-#include "vg/vg_opt.h"
-#include "network_common.h"
-#include "gameserver_db.h"
-#include "vg/vg_m.h"
-#include "vg/vg_msg.h"
-
-static u64 const k_steamid_max = 0xffffffffffffffff;
-
-static void inthandler( int signum ) {
- sig_stop = 1;
-}
-
-static void release_message( SteamNetworkingMessage_t *msg )
-{
- msg->m_nUserData --;
-
- if( msg->m_nUserData == 0 )
- SteamAPI_SteamNetworkingMessage_t_Release( msg );
-}
-
-/*
- * Send message to single client, with authentication checking
- */
-static void gameserver_send_to_client( i32 client_id,
- const void *pData, u32 cbData,
- int nSendFlags )
-{
- struct gameserver_client *client = &gameserver.clients[ client_id ];
-
- if( gameserver.loopback_test && !client->connection )
- return;
-
- if( !client->steamid )
- return;
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, client->connection,
- pData, cbData, nSendFlags, NULL );
-}
-
-/*
- * Send message to all clients if they are authenticated
- */
-static void gameserver_send_to_all( int ignore,
- const void *pData, u32 cbData,
- int nSendFlags )
-{
- for( int i=0; i<vg_list_size(gameserver.clients); i++ )
- {
- struct gameserver_client *client = &gameserver.clients[i];
-
- if( i != ignore )
- gameserver_send_to_client( i, pData, cbData, nSendFlags );
- }
-}
-
-static void gameserver_send_version_to_client( int index )
-{
- struct gameserver_client *client = &gameserver.clients[index];
-
- if( gameserver.loopback_test && !client->connection )
- return;
-
- netmsg_version version;
- version.inetmsg_id = k_inetmsg_version;
- version.version = NETWORK_SKATERIFT_VERSION;
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, client->connection,
- &version, sizeof(netmsg_version),
- k_nSteamNetworkingSend_Reliable, NULL );
-}
-
-/*
- * handle server update that client #'index' has joined
- */
-static void gameserver_player_join( int index )
-{
- struct gameserver_client *joiner = &gameserver.clients[index];
-
- netmsg_playerjoin join = { .inetmsg_id = k_inetmsg_playerjoin,
- .index = index,
- .steamid = joiner->steamid };
-
- gameserver_send_to_all( index, &join, sizeof(join),
- k_nSteamNetworkingSend_Reliable );
-
- /*
- * update the joining user about current connections and our version
- */
- gameserver_send_version_to_client( index );
-
- netmsg_playerusername *username =
- alloca( sizeof(netmsg_playerusername) + NETWORK_USERNAME_MAX );
- username->inetmsg_id = k_inetmsg_playerusername;
-
- netmsg_playeritem *item =
- alloca( sizeof(netmsg_playeritem) + ADDON_UID_MAX );
- item->inetmsg_id = k_inetmsg_playeritem;
-
- netmsg_region *region = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
- region->inetmsg_id = k_inetmsg_region;
-
- for( int i=0; i<vg_list_size(gameserver.clients); i++ )
- {
- struct gameserver_client *client = &gameserver.clients[i];
-
- if( (i == index) || !client->steamid )
- continue;
-
- /* join */
- netmsg_playerjoin init = { .inetmsg_id = k_inetmsg_playerjoin,
- .index = i,
- .steamid = client->steamid };
- gameserver_send_to_client( index, &init, sizeof(init),
- k_nSteamNetworkingSend_Reliable );
-
- /* username */
- username->index = i;
- u32 chs = vg_strncpy( client->username, username->name,
- NETWORK_USERNAME_MAX,
- k_strncpy_always_add_null );
- u32 size = sizeof(netmsg_playerusername) + chs + 1;
- gameserver_send_to_client( index, username, size,
- k_nSteamNetworkingSend_Reliable );
-
- /* items */
- for( int j=0; j<k_netmsg_playeritem_max; j++ )
- {
- chs = vg_strncpy( client->items[j].uid, item->uid, ADDON_UID_MAX,
- k_strncpy_always_add_null );
- item->type_index = j;
- item->client = i;
- size = sizeof(netmsg_playeritem) + chs + 1;
- gameserver_send_to_client( index, item, size,
- k_nSteamNetworkingSend_Reliable );
- }
-
- /* region */
-
- region->client = i;
- region->flags = client->region_flags;
- u32 l = vg_strncpy( client->region, region->loc, NETWORK_REGION_MAX,
- k_strncpy_always_add_null );
- size = sizeof(netmsg_region) + l + 1;
-
- gameserver_send_to_client( index, region, size,
- k_nSteamNetworkingSend_Reliable );
- }
-}
-
-/*
- * Handle server update that player has left
- */
-static void gameserver_player_leave( int index ){
- if( gameserver.auth_mode == eServerModeAuthentication ){
- if( !gameserver.clients[ index ].steamid )
- return;
- }
-
- netmsg_playerleave leave;
- leave.inetmsg_id = k_inetmsg_playerleave;
- leave.index = index;
-
- vg_info( "Player leave (%d)\n", index );
- gameserver_send_to_all( index, &leave, sizeof(leave),
- k_nSteamNetworkingSend_Reliable );
-}
-
-static void gameserver_update_all_knowledge( int client, int clear );
-
-/*
- * Deletes client at index and disconnects the connection handle if it was
- * set.
- */
-static void remove_client( int index ){
- struct gameserver_client *client = &gameserver.clients[index];
- if( client->connection ){
- SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
- hSteamNetworkingSockets, client->connection, -1 );
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, client->connection,
- k_ESteamNetConnectionEnd_Misc_InternalError,
- NULL, 1 );
- }
- memset( client, 0, sizeof(struct gameserver_client) );
- gameserver_update_all_knowledge( index, 1 );
-}
-
-/*
- * Handle incoming new connection and init flags on the steam handle. if the
- * server is full the userdata (client_id) will be set to -1 on the handle.
- */
-static void handle_new_connection( HSteamNetConnection conn )
-{
- SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
- hSteamNetworkingSockets, conn, -1 );
-
- int index = -1;
-
- for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
- if( !gameserver.clients[i].active ){
- index = i;
- break;
- }
- }
-
- if( index == -1 ){
- vg_error( "Server full\n" );
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, conn,
- 4500,
- NULL, 1 );
- return;
- }
-
- struct gameserver_client *client = &gameserver.clients[index];
- EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
- hSteamNetworkingSockets, conn );
-
- if( accept_status == k_EResultOK )
- {
- vg_success( "Accepted client (id: %u, index: %d)\n", conn, index );
-
- client->active = 1;
- client->connection = conn;
-
- SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
- hSteamNetworkingSockets, conn, gameserver.client_group );
-
- SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
- hSteamNetworkingSockets, conn, index );
-
- if( gameserver.loopback_test )
- {
- vg_warn( "[DEV] Creating loopback client\n" );
- struct gameserver_client *loopback = &gameserver.clients[1];
- loopback->active = 1;
- loopback->connection = 0;
- }
- }
- else
- {
- vg_warn( "Error accepting connection (id: %u)\n", conn );
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, conn,
- k_ESteamNetConnectionEnd_Misc_InternalError,
- NULL, 1 );
- }
-}
-
-static void on_auth_status( CallbackMsg_t *msg ){
- SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam;
- vg_info( " Authentication availibility: %s\n",
- string_ESteamNetworkingAvailability(info->m_eAvail) );
- vg_info( " %s\n", info->m_debugMsg );
-}
-
-/*
- * Get client id of connection handle. Will be -1 if unkown to us either because
- * the server is full or we already disconnected them
- */
-static i32 gameserver_conid( HSteamNetConnection hconn )
-{
- i64 id;
-
- if( hconn == 0 )
- {
- if( gameserver.loopback_test )
- return 1;
- else
- return -1;
- }
- else
- id = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
- hSteamNetworkingSockets, hconn );
-
- if( (id < 0) || (id >= NETWORK_MAX_PLAYERS) )
- return -1;
-
- return id;
-}
-
-/*
- * Callback for steam connection state change
- */
-static void on_connect_status( CallbackMsg_t *msg )
-{
- SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
- vg_info( " Connection status changed for %lu\n", info->m_hConn );
-
- vg_info( " %s -> %s\n",
- string_ESteamNetworkingConnectionState(info->m_eOldState),
- string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
-
- if( info->m_info.m_eState==k_ESteamNetworkingConnectionState_Connecting )
- {
- handle_new_connection( info->m_hConn );
- }
-
- if( (info->m_info.m_eState ==
- k_ESteamNetworkingConnectionState_ClosedByPeer ) ||
- (info->m_info.m_eState ==
- k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) ||
- (info->m_info.m_eState ==
- k_ESteamNetworkingConnectionState_Dead) ||
- (info->m_info.m_eState ==
- k_ESteamNetworkingConnectionState_None) )
- {
- vg_info( "End reason: %d\n", info->m_info.m_eEndReason );
-
- int client_id = gameserver_conid( info->m_hConn );
- if( client_id != -1 )
- {
- gameserver_player_leave( client_id );
- remove_client( client_id );
-
- if( gameserver.loopback_test )
- {
- gameserver_player_leave( 1 );
- remove_client( 1 );
- }
- }
- else
- {
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, info->m_hConn, 0, NULL, 0 );
- }
- }
-}
-
-static void gameserver_rx_version( SteamNetworkingMessage_t *msg )
-{
- netmsg_version *version = msg->m_pData;
-
- int client_id = gameserver_conid( msg->m_conn );
- if( client_id == -1 )
- {
- vg_warn( "Recieved version from unkown connection (%u)\n", msg->m_conn );
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, msg->m_conn,
- k_ESteamNetConnectionEnd_Misc_InternalError,
- NULL, 1 );
- return;
- }
-
- struct gameserver_client *client = &gameserver.clients[ client_id ];
-
- if( client->version )
- {
- vg_warn( "Already have version for this client (%d conn: %u)",
- client_id, msg->m_conn );
- return;
- }
-
- client->version = version->version;
-
- if( client->version != NETWORK_SKATERIFT_VERSION )
- {
- gameserver_send_version_to_client( client_id );
- remove_client( client_id );
- return;
- }
-
- /* this is the sign on point for non-auth servers,
- * for auth servers it comes at the end of rx_auth
- */
- if( gameserver.auth_mode != eServerModeAuthentication )
- {
- client->steamid = k_steamid_max;
- gameserver_player_join( client_id );
-
- if( gameserver.loopback_test )
- {
- struct gameserver_client *loopback = &gameserver.clients[1];
- loopback->steamid = k_steamid_max;
- gameserver_player_join( 1 );
- }
- }
-}
-
-/*
- * recieve auth ticket from connection. will only accept it if we've added them
- * to the client list first.
- */
-static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){
- if( gameserver.auth_mode != eServerModeAuthentication ){
- vg_warn( "Running server without authentication. "
- "Connection %u tried to authenticate.\n", msg->m_conn );
- return;
- }
-
- int client_id = gameserver_conid( msg->m_conn );
- if( client_id == -1 ) {
- vg_warn( "Recieved auth ticket from unkown connection (%u)\n",
- msg->m_conn );
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, msg->m_conn,
- k_ESteamNetConnectionEnd_Misc_InternalError, NULL, 1 );
- return;
- }
-
- struct gameserver_client *client = &gameserver.clients[ client_id ];
- if( client->steamid ){
- vg_warn( "Already authorized this user but another app ticket was sent"
- " again (%d conn: %u)\n", client_id, msg->m_conn );
- return;
- }
-
- if( client->version == 0 ){
- vg_error( "Client has not sent their version yet (%u)\n", msg->m_conn );
- remove_client( client_id );
- return;
- }
-
- vg_low( "Attempting to verify user\n" );
-
- if( msg->m_cbSize < sizeof(netmsg_auth) ){
- vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn );
- remove_client( client_id );
- return;
- }
-
- netmsg_auth *auth = msg->m_pData;
-
- if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length ||
- auth->ticket_length > 1024 ){
- vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
- auth->ticket_length );
- remove_client( client_id );
- return;
- }
-
- u8 decrypted[1024];
- u32 ticket_len = 1024;
-
- int success = SteamEncryptedAppTicket_BDecryptTicket(
- auth->ticket, auth->ticket_length, decrypted,
- &ticket_len, gameserver.app_symmetric_key,
- k_nSteamEncryptedAppTicketSymmetricKeyLen );
-
- if( !success ){
- vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn );
- vg_error( " ticket length: %u\n", auth->ticket_length );
- remove_client( client_id );
- return;
- }
-
- if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len )){
- RTime32 ctime = time(NULL),
- tickettime = SteamEncryptedAppTicket_GetTicketIssueTime(
- decrypted, ticket_len ),
- expiretime = tickettime + 24*3*60*60;
-
- if( ctime > expiretime ){
- vg_error( "Ticket expired (client %u)\n", msg->m_conn );
- remove_client( client_id );
- return;
- }
- }
-
- CSteamID steamid;
- SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid );
- vg_success( "User is authenticated! steamid %lu (%u)\n",
- steamid.m_unAll64Bits, msg->m_conn );
-
- client->steamid = steamid.m_unAll64Bits;
- gameserver_player_join( client_id );
-}
-
-/*
- * Player updates sent to us
- * -----------------------------------------------------------------------------
- */
-
-static int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){
- if( msg->m_cbSize < size ) {
- vg_error( "Invalid packet size (must be at least %u)\n", size );
- return 0;
- }
- else{
- return 1;
- }
-}
-
-struct db_set_username_thread_data {
- u64 steamid;
- char username[ NETWORK_USERNAME_MAX ];
-};
-
-static void gameserver_update_db_username( db_request *db_req ){
- struct db_set_username_thread_data *inf = (void *)db_req->data;
-
- if( inf->steamid == k_steamid_max )
- return;
-
- int admin = 0;
- if( inf->steamid == 76561198072130043 )
- admin = 2;
-
- db_updateuser( inf->steamid, inf->username, admin );
-}
-
-static int gameserver_item_eq( struct gameserver_item *ia,
- struct gameserver_item *ib ){
- if( ia->hash == ib->hash )
- if( !strcmp(ia->uid,ib->uid) )
- return 1;
-
- return 0;
-}
-
-/*
- * Match addons between two player IDs. if clear is set, then the flags between
- * those two IDs will all be set to 0.
- */
-static void gameserver_update_knowledge_table( int client0, int client1,
- int clear ){
- u32 idx = network_pair_index( client0, client1 );
-
- struct gameserver_client *c0 = &gameserver.clients[client0],
- *c1 = &gameserver.clients[client1];
-
- u8 flags = 0x00;
-
- if( !clear ){
- if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world0],
- &c1->items[k_netmsg_playeritem_world0]))
- flags |= CLIENT_KNOWLEDGE_SAME_WORLD0;
-
- if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world1],
- &c1->items[k_netmsg_playeritem_world1]))
- flags |= CLIENT_KNOWLEDGE_SAME_WORLD1;
- }
-
- gameserver.client_knowledge_mask[idx] = flags;
-}
-
-/*
- * If a change has been made on this client, then it will adjust the entire
- * table of other players. if clear is set, all references to client will be set
- * to 0.
- */
-static void gameserver_update_all_knowledge( int client, int clear ){
- for( int i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
- if( i == client )
- continue;
-
- struct gameserver_client *ci = &gameserver.clients[i];
-
- if( ci->steamid )
- gameserver_update_knowledge_table( client, i, clear );
- }
-}
-
-static void gameserver_propogate_player_frame( int client_id,
- netmsg_playerframe *frame,
- u32 size ){
- u32 basic_size = sizeof(netmsg_playerframe) + ((24*3)/8);
- netmsg_playerframe *full = alloca(size),
- *basic= alloca(basic_size);
-
- memcpy( full, frame, size );
- memcpy( basic, frame, basic_size );
-
- full->client = client_id;
- basic->client = client_id;
- basic->subsystem = 4; /* (.._basic_info: 24f*3 animator ) */
- basic->sound_effects = 0;
-
- struct gameserver_client *c0 = &gameserver.clients[client_id];
- c0->instance = frame->flags & NETMSG_PLAYERFRAME_INSTANCE_ID;
-
- for( int i=0; i<vg_list_size(gameserver.clients); i++ )
- {
- if( i == client_id )
- continue;
-
- struct gameserver_client *ci = &gameserver.clients[i];
-
- int send_full = 0;
-
- if( c0->instance == ci->instance )
- {
- u32 k_index = network_pair_index( client_id, i );
- u8 k_mask = gameserver.client_knowledge_mask[ k_index ];
-
- if( (k_mask & (CLIENT_KNOWLEDGE_SAME_WORLD0<<c0->instance)) )
- send_full = 1;
- }
-
- if( send_full )
- {
- gameserver_send_to_client( i, full, size,
- k_nSteamNetworkingSend_Unreliable );
- }
- else
- {
- gameserver_send_to_client( i, basic, basic_size,
- k_nSteamNetworkingSend_Unreliable );
- }
- }
-}
-
-static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg )
-{
- netmsg_blank *tmp = msg->m_pData;
-
- int client_id = gameserver_conid( msg->m_conn );
- if( client_id == -1 ) return;
-
- struct gameserver_client *client = &gameserver.clients[ client_id ];
-
- if( tmp->inetmsg_id == k_inetmsg_playerusername )
- {
- if( !packet_minsize( msg, sizeof(netmsg_playerusername)+1 ))
- return;
-
- netmsg_playerusername *src = msg->m_pData;
-
- u32 name_len = network_msgstring( src->name, msg->m_cbSize,
- sizeof(netmsg_playerusername),
- client->username,
- NETWORK_USERNAME_MAX );
-
- /* update other users about this change */
- netmsg_playerusername *prop = alloca(sizeof(netmsg_playerusername)+
- NETWORK_USERNAME_MAX );
-
- prop->inetmsg_id = k_inetmsg_playerusername;
- prop->index = client_id;
- u32 chs = vg_strncpy( client->username, prop->name, NETWORK_USERNAME_MAX,
- k_strncpy_always_add_null );
-
- vg_info( "client #%d changed name to: %s\n", client_id, prop->name );
-
- u32 propsize = sizeof(netmsg_playerusername) + chs + 1;
- gameserver_send_to_all( client_id, prop, propsize,
- k_nSteamNetworkingSend_Reliable );
-
- /* update database about this */
- db_request *call = db_alloc_request(
- sizeof(struct db_set_username_thread_data) );
- struct db_set_username_thread_data *inf = (void *)call->data;
- inf->steamid = client->steamid;
- vg_strncpy( client->username, inf->username,
- sizeof(inf->username), k_strncpy_always_add_null );
- call->handler = gameserver_update_db_username;
- db_send_request( call );
- }
- else if( tmp->inetmsg_id == k_inetmsg_playerframe )
- {
- gameserver_propogate_player_frame( client_id,
- msg->m_pData, msg->m_cbSize );
- }
- else if( tmp->inetmsg_id == k_inetmsg_playeritem )
- {
- netmsg_playeritem *item = msg->m_pData;
-
- /* record */
- if( item->type_index >= k_netmsg_playeritem_max )
- {
- vg_warn( "Client #%d invalid equip type %u\n",
- client_id, (u32)item->type_index );
- return;
- }
-
- char *dest = client->items[ item->type_index ].uid;
-
- network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem),
- dest, ADDON_UID_MAX );
-
- vg_info( "Client #%d equiped: [%s] %s\n",
- client_id,
- (const char *[]){[k_netmsg_playeritem_board]="board",
- [k_netmsg_playeritem_player]="player",
- [k_netmsg_playeritem_world0]="world0",
- [k_netmsg_playeritem_world1]="world1"
- }[item->type_index], item->uid );
-
- gameserver_update_all_knowledge( client_id, 0 );
-
- /* propogate */
- netmsg_playeritem *prop = alloca(msg->m_cbSize);
- memcpy( prop, msg->m_pData, msg->m_cbSize );
- prop->client = client_id;
- gameserver_send_to_all( client_id, prop, msg->m_cbSize,
- k_nSteamNetworkingSend_Reliable );
- }
- else if( tmp->inetmsg_id == k_inetmsg_chat )
- {
- netmsg_chat *chat = msg->m_pData,
- *prop = alloca( sizeof(netmsg_chat) + NETWORK_MAX_CHAT );
- prop->inetmsg_id = k_inetmsg_chat;
- prop->client = client_id;
-
- u32 l = network_msgstring( chat->msg, msg->m_cbSize, sizeof(netmsg_chat),
- prop->msg, NETWORK_MAX_CHAT );
- vg_info( "[%d]: %s\n", client_id, prop->msg );
-
- gameserver_send_to_all( client_id, prop, sizeof(netmsg_chat)+l+1,
- k_nSteamNetworkingSend_Reliable );
- }
- else if( tmp->inetmsg_id == k_inetmsg_region )
- {
- netmsg_region *region = msg->m_pData,
- *prop = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
-
- prop->inetmsg_id = k_inetmsg_region;
- prop->client = client_id;
- prop->flags = region->flags;
-
- u32 l = network_msgstring(
- region->loc, msg->m_cbSize, sizeof(netmsg_region),
- client->region, NETWORK_REGION_MAX );
- client->region_flags = region->flags;
-
- l = vg_strncpy( client->region, prop->loc, NETWORK_REGION_MAX,
- k_strncpy_always_add_null );
-
- gameserver_send_to_all( client_id, prop, sizeof(netmsg_region)+l+1,
- k_nSteamNetworkingSend_Reliable );
- vg_info( "client %d moved to region: %s\n", client_id, client->region );
- }
- else
- {
- vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
- tmp->inetmsg_id );
- }
-}
-
-static void gameserver_request_respond( enum request_status status,
- netmsg_request *res, vg_msg *body,
- SteamNetworkingMessage_t *msg ){
- int client_id = gameserver_conid( msg->m_conn );
- u32 len = 0;
- if( body ){
- len = body->cur.co;
- vg_low( "[%d#%d] Response: %d\n", client_id, (i32)res->id, status );
- vg_msg_print( body, len );
- }
-
- res->status = status;
-
- if( gameserver.loopback_test && !msg->m_conn )
- {
- release_message( msg );
- return;
- }
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, msg->m_conn,
- res, sizeof(netmsg_request) + len,
- k_nSteamNetworkingSend_Reliable, NULL );
-
- release_message( msg );
-}
-
-struct user_request_thread_data {
- SteamNetworkingMessage_t *msg;
-};
-
-static u32 gameserver_get_current_week(void){
- return time(NULL) / (7*24*60*60);
-}
-
-static enum request_status gameserver_cat_table(
- vg_msg *msg,
- const char *mod, const char *route, u32 week, const char *alias )
-{
- char table_name[ DB_TABLE_UID_MAX ];
- if( !db_get_highscore_table_name( mod, route, week, table_name ) )
- return k_request_status_out_of_memory;
-
- char buf[512];
- vg_str q;
- vg_strnull( &q, buf, 512 );
- vg_strcat( &q, "SELECT * FROM \"" );
- vg_strcat( &q, table_name );
- vg_strcat( &q, "\" ORDER BY time ASC LIMIT 10;" );
- if( !vg_strgood(&q) )
- return k_request_status_out_of_memory;
-
- sqlite3_stmt *stmt = db_stmt( q.buffer );
- if( !stmt )
- return k_request_status_database_error;
-
- vg_msg_frame( msg, alias );
- for( u32 i=0; i<10; i ++ ){
- int fc = sqlite3_step( stmt );
-
- if( fc == SQLITE_ROW ){
- i32 time = sqlite3_column_int( stmt, 1 );
- i64 steamid_i64 = sqlite3_column_int64( stmt, 0 );
- u64 steamid = *((u64 *)&steamid_i64);
-
- if( steamid == k_steamid_max )
- continue;
-
- vg_msg_frame( msg, "" );
- vg_msg_wkvnum( msg, "time", k_vg_msg_u32, 1, &time );
- vg_msg_wkvnum( msg, "steamid", k_vg_msg_u64, 1, &steamid );
-
- char username[32];
- if( db_getuserinfo( steamid, username, sizeof(username), NULL ) )
- vg_msg_wkvstr( msg, "username", username );
- vg_msg_end_frame( msg );
- }
- else if( fc == SQLITE_DONE ){
- break;
- }
- else {
- log_sqlite3( fc );
- break;
- }
- }
-
- sqlite3_finalize( stmt );
- vg_msg_end_frame( msg );
- return k_request_status_ok;
-}
-
-static void gameserver_process_user_request( db_request *db_req )
-{
- struct user_request_thread_data *inf = (void *)db_req->data;
- SteamNetworkingMessage_t *msg = inf->msg;
-
- int client_id = gameserver_conid( msg->m_conn );
- if( client_id == -1 )
- {
- release_message( msg );
- return;
- }
-
- struct gameserver_client *client = &gameserver.clients[ client_id ];
-
- netmsg_request *req = (netmsg_request *)msg->m_pData;
- vg_msg data;
- vg_msg_init( &data, req->q, msg->m_cbSize - sizeof(netmsg_request) );
-
- /* create response packet */
- netmsg_request *res = alloca( sizeof(netmsg_request) + NETWORK_REQUEST_MAX );
- res->inetmsg_id = k_inetmsg_response;
- res->id = req->id;
- vg_msg body;
- vg_msg_init( &body, res->q, NETWORK_REQUEST_MAX );
-
- const char *endpoint = vg_msg_getkvstr( &data, "endpoint" );
-
- if( !endpoint ){
- gameserver_request_respond( k_request_status_invalid_endpoint,
- res, NULL, msg );
- return;
- }
-
- if( !strcmp( endpoint, "scoreboard" ) ){
- const char *mod = vg_msg_getkvstr( &data, "mod" );
- const char *route = vg_msg_getkvstr( &data, "route" );
- u32 week;
- vg_msg_getkvintg( &data, "week", k_vg_msg_u32, &week, NULL );
-
- if( week == NETWORK_LEADERBOARD_CURRENT_WEEK ){
- gameserver_cat_table( &body, mod, route,
- gameserver_get_current_week(), "rows_weekly" );
- }
- else if( week == NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK ){
- gameserver_cat_table( &body, mod, route, 0, "rows" );
- gameserver_cat_table( &body, mod, route,
- gameserver_get_current_week(), "rows_weekly" );
- }
- else
- gameserver_cat_table( &body, mod, route, week, "rows" );
-
- if( body.error != k_vg_msg_error_OK ){
- gameserver_request_respond( k_request_status_out_of_memory,
- res, NULL, msg );
- return;
- }
-
- gameserver_request_respond( k_request_status_ok, res, &body, msg );
- }
- else if( !strcmp( endpoint, "setlap" ) ){
- if( client->steamid == k_steamid_max ){
- gameserver_request_respond( k_request_status_unauthorized,
- res, NULL, msg );
- return;
- }
-
- const char *mod = vg_msg_getkvstr( &data, "mod" );
- const char *route = vg_msg_getkvstr( &data, "route" );
-
- char weekly_table[ DB_TABLE_UID_MAX ],
- alltime_table[ DB_TABLE_UID_MAX ];
-
- u32 week = gameserver_get_current_week();
-
- if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) ||
- !db_get_highscore_table_name( mod, route, week, weekly_table ) ){
- gameserver_request_respond( k_request_status_out_of_memory,
- res, NULL, msg );
- return;
- }
-
- i32 centiseconds;
- vg_msg_getkvintg( &data, "time", k_vg_msg_i32, ¢iseconds, NULL );
- if( centiseconds < 5*100 ){
- gameserver_request_respond( k_request_status_client_error,
- res, NULL, msg );
- return;
- }
-
- db_writeusertime( alltime_table, client->steamid, centiseconds, 1 );
- db_writeusertime( weekly_table, client->steamid, centiseconds, 1 );
- gameserver_request_respond( k_request_status_ok, res, NULL, msg );
- }
- else{
- gameserver_request_respond( k_request_status_invalid_endpoint,
- res, NULL, msg );
- }
-}
-
-static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg )
-{
- netmsg_blank *tmp = msg->m_pData;
-
- int client_id = gameserver_conid( msg->m_conn );
- if( client_id == -1 )
- {
- release_message( msg );
- return;
- }
-
- if( tmp->inetmsg_id == k_inetmsg_request )
- {
- if( gameserver.loopback_test && (client_id == 1) )
- {
- release_message( msg );
- return;
- }
-
- if( !packet_minsize( msg, sizeof(netmsg_request)+1 ))
- {
- release_message( msg );
- return;
- }
-
- db_request *call = db_alloc_request(
- sizeof(struct user_request_thread_data) );
- struct user_request_thread_data *inf = (void *)call->data;
- inf->msg = msg;
- call->handler = gameserver_process_user_request;
- db_send_request( call );
- }
- else
- {
- vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
- tmp->inetmsg_id );
- release_message( msg );
- }
-}
-
-static void process_network_message( SteamNetworkingMessage_t *msg )
-{
- if( msg->m_cbSize < sizeof(netmsg_blank) ){
- vg_warn( "Discarding message (too small: %d)\n",
- msg->m_cbSize );
- return;
- }
-
- netmsg_blank *tmp = msg->m_pData;
-
- if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) )
- {
- gameserver_rx_200_300( msg );
- release_message( msg );
- }
- else if( (tmp->inetmsg_id >= 300) && (tmp->inetmsg_id < 400) )
- {
- gameserver_rx_300_400( msg );
- }
- else{
- if( tmp->inetmsg_id == k_inetmsg_auth )
- gameserver_rx_auth( msg );
- else if( tmp->inetmsg_id == k_inetmsg_version ){
- gameserver_rx_version( msg );
- }
- else {
- vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
- tmp->inetmsg_id );
- }
- release_message( msg );
- }
-}
-
-static void poll_connections(void)
-{
- SteamNetworkingMessage_t *messages[32];
- int len;
-
- while(1)
- {
- len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
- hSteamNetworkingSockets,
- gameserver.client_group, messages, vg_list_size(messages) );
-
- if( len <= 0 )
- return;
-
- for( int i=0; i<len; i++ )
- {
- SteamNetworkingMessage_t *msg = messages[i];
- msg->m_nUserData = 1;
-
- if( gameserver.loopback_test )
- {
- HSteamNetConnection conid = msg->m_conn;
- msg->m_conn = 0;
- msg->m_nUserData ++;
- process_network_message( msg );
- msg->m_conn = conid;
- }
-
- process_network_message( msg );
- }
- }
-}
-
-static u64 seconds_to_server_ticks( double s ){
- return s / 0.01;
-}
-
-int main( int argc, char *argv[] ){
- signal( SIGINT, inthandler );
- signal( SIGQUIT, inthandler );
- signal( SIGPIPE, SIG_IGN );
-
- char *arg;
- while( vg_argp( argc, argv ) )
- {
- if( vg_long_opt( "noauth" ) )
- gameserver.auth_mode = eServerModeNoAuthentication;
-
- if( vg_long_opt( "loopback" ) )
- gameserver.loopback_test = 1;
- }
-
- vg_set_mem_quota( 80*1024*1024 );
- vg_alloc_quota();
- db_init();
-
- /* steamworks init
- * --------------------------------------------------------------- */
- steamworks_ensure_txt( "2103940" );
- if( gameserver.auth_mode == eServerModeAuthentication ){
- if( !vg_load_steam_symetric_key( "application_key",
- gameserver.app_symmetric_key )){
- return 0;
- }
- }
- else{
- vg_warn( "Running without user authentication.\n" );
- }
-
- if( !SteamGameServer_Init( 0, NETWORK_PORT, NETWORK_PORT+1,
- gameserver.auth_mode, "1.0.0.0" ) ){
- vg_error( "SteamGameServer_Init failed\n" );
- return 0;
- }
-
- void *hSteamGameServer = SteamAPI_SteamGameServer();
- SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
-
- SteamAPI_ManualDispatch_Init();
- HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
- hSteamNetworkingSockets =
- SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
-
- steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status );
- steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
- on_connect_status );
-
- vg_success( "Steamworks API running\n" );
- steamworks_event_loop( hsteampipe );
-
- /*
- * Create a listener
- */
- HSteamListenSocket listener;
- SteamNetworkingIPAddr localAddr;
- SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
- localAddr.m_port = NETWORK_PORT;
-
- listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
- hSteamNetworkingSockets, &localAddr, 0, NULL );
- gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
- hSteamNetworkingSockets );
-
- u64 server_ticks = 8000,
- last_record_save = 8000,
- last_scoreboard_gen = 0;
-
- while( !sig_stop ){
- steamworks_event_loop( hsteampipe );
- poll_connections();
-
- usleep(10000);
- server_ticks ++;
-
- if( db_killed() )
- break;
- }
-
- SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
- gameserver.client_group );
- SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
- hSteamNetworkingSockets, listener );
-
- vg_info( "Shutting down\n..." );
- SteamGameServer_Shutdown();
- db_kill();
- db_free();
-
- return 0;
-}
+++ /dev/null
-#pragma once
-#define VG_SERVER
-
-#include "vg/vg_platform.h"
-#include "vg/vg_steam.h"
-#include "vg/vg_steam_networking.h"
-#include "vg/vg_steam_http.h"
-#include "vg/vg_steam_auth.h"
-#include "network_msg.h"
-#include "network_common.h"
-#include <sys/socket.h>
-
-#define CLIENT_KNOWLEDGE_SAME_WORLD0 0x1
-#define CLIENT_KNOWLEDGE_SAME_WORLD1 0x2
-#define CLIENT_KNOWLEDGE_FRIENDS 0x4 /* unused */
-
-struct {
- HSteamNetPollGroup client_group;
- EServerMode auth_mode;
-
- struct gameserver_client {
- int active;
- u32 version;
- int authenticated;
- HSteamNetConnection connection;
- char username[ NETWORK_USERNAME_MAX ];
-
- u8 instance;
-
- struct gameserver_item {
- char uid[ADDON_UID_MAX];
- u32 hash;
- }
- items[k_netmsg_playeritem_max];
-
- char region[ NETWORK_REGION_MAX ];
- u32 region_flags;
-
- u64 steamid;
- }
- clients[ NETWORK_MAX_PLAYERS ];
-
- u8 client_knowledge_mask[ (NETWORK_MAX_PLAYERS*(NETWORK_MAX_PLAYERS-1))/2 ];
- u8 app_symmetric_key[ k_nSteamEncryptedAppTicketSymmetricKeyLen ];
-
- bool loopback_test;
-}
-static gameserver = {
- .auth_mode = eServerModeAuthentication
-};
-
-static ISteamNetworkingSockets *hSteamNetworkingSockets = NULL;
+++ /dev/null
-#ifndef GAMESERVER_DB_H
-#define GAMESERVER_DB_H
-
-#include "vg/vg_log.h"
-#include "vg/vg_mem_queue.h"
-#include "network_common.h"
-#include "dep/sqlite3/sqlite3.h"
-#include <pthread.h>
-#include <unistd.h>
-
-#define DB_COURSE_UID_MAX 32
-#define DB_TABLE_UID_MAX (ADDON_UID_MAX+DB_COURSE_UID_MAX+32)
-//#define DB_CRASH_ON_SQLITE_ERROR
-#define DB_LOG_SQL_STATEMENTS
-#define DB_REQUEST_BUFFER_SIZE (1024*2)
-
-typedef struct db_request db_request;
-struct db_request {
- void (*handler)( db_request *req );
- u32 size,_;
- u8 data[];
-};
-
-struct {
- sqlite3 *db;
- pthread_t thread;
- pthread_mutex_t mux;
-
- vg_queue queue;
- int kill;
-}
-static database;
-
-/*
- * Log the error code (or carry on if its OK).
- */
-static void log_sqlite3( int code ){
- if( code == SQLITE_OK ) return;
- vg_print_backtrace();
- vg_error( "sqlite3(%d): %s\n", code, sqlite3_errstr(code) );
-
-#ifdef DB_CRASH_ON_SQLITE_ERROR
- int crash = *((int*)2);
-#endif
-}
-
-/*
- * Perpare statement and auto throw away if fails. Returns NULL on failure.
- */
-static sqlite3_stmt *db_stmt( const char *code ){
-#ifdef DB_LOG_SQL_STATEMENTS
- vg_low( code );
-#endif
-
- sqlite3_stmt *stmt;
- int fc = sqlite3_prepare_v2( database.db, code, -1, &stmt, NULL );
-
- if( fc != SQLITE_OK ){
- log_sqlite3( fc );
- sqlite3_finalize( stmt );
- return NULL;
- }
-
- return stmt;
-}
-
-/*
- * bind zero terminated string
- */
-static int db_sqlite3_bind_sz( sqlite3_stmt *stmt, int pos, const char *sz ){
- return sqlite3_bind_text( stmt, pos, sz, -1, SQLITE_STATIC );
-}
-
-/*
- * Allowed characters in sqlite table names. We use "" as delimiters.
- */
-static int db_verify_charset( const char *str, int mincount ){
- for( int i=0; ; i++ ){
- char c = str[i];
- if( c == '\0' ){
- if( i < mincount ) return 0;
- else return 1;
- }
-
- if( !((c==' ')||(c=='!')||(c>='#'&&c<='~')) ) return 0;
- }
-
- return 0;
-}
-
-/*
- * Find table name from mod UID and course UID, plus the week number
- */
-static int db_get_highscore_table_name( const char *mod_uid,
- const char *run_uid,
- u32 week,
- char table_name[DB_TABLE_UID_MAX] ){
- if( !db_verify_charset( mod_uid, 13 ) ||
- !db_verify_charset( run_uid, 1 ) ) return 0;
-
- vg_str a;
- vg_strnull( &a, table_name, DB_TABLE_UID_MAX );
- vg_strcat( &a, mod_uid );
- vg_strcat( &a, ":" );
- vg_strcat( &a, run_uid );
-
- if( week ){
- vg_strcat( &a, "#" );
- vg_strcati32( &a, week );
- }
-
- return vg_strgood( &a );
-}
-
-/*
- * Read value from highscore table. If not found or error, returns 0
- */
-static i32 db_readusertime( char table[DB_TABLE_UID_MAX], u64 steamid ){
- char buf[ 512 ];
- vg_str q;
- vg_strnull( &q, buf, 512 );
- vg_strcat( &q, "SELECT time FROM \"" );
- vg_strcat( &q, table );
- vg_strcat( &q, "\" WHERE steamid = ?;" );
- if( !vg_strgood(&q) ) return 0;
-
- sqlite3_stmt *stmt = db_stmt( q.buffer );
- if( stmt ){
- sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
- int fc = sqlite3_step( stmt );
-
- i32 result = 0;
-
- if( fc == SQLITE_ROW )
- result = sqlite3_column_int( stmt, 0 );
- else if( fc != SQLITE_DONE )
- log_sqlite3(fc);
-
- sqlite3_finalize( stmt );
- return result;
- }
- else return 0;
-}
-
-/*
- * Write to highscore table
- */
-static int db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid,
- i32 score, int only_if_faster ){
- /* auto create table
- * ------------------------------------------*/
- char buf[ 512 ];
- vg_str q;
- vg_strnull( &q, buf, 512 );
- vg_strcat( &q, "CREATE TABLE IF NOT EXISTS \n \"" );
- vg_strcat( &q, table );
- vg_strcat( &q, "\"\n (steamid BIGINT UNIQUE, time INT);" );
- if( !vg_strgood(&q) ) return 0;
-
- vg_str str;
- sqlite3_stmt *create_table = db_stmt( q.buffer );
-
- if( create_table ){
- db_sqlite3_bind_sz( create_table, 1, table );
-
- int fc = sqlite3_step( create_table );
- sqlite3_finalize( create_table );
- if( fc != SQLITE_DONE )
- return 0;
- }
- else return 0;
-
- if( only_if_faster ){
- i32 current = db_readusertime( table, steamid );
- if( (current != 0) && (score > current) )
- return 1;
- }
-
- /* insert score
- * -------------------------------------------------*/
- vg_strnull( &q, buf, 512 );
- vg_strcat( &q, "REPLACE INTO \"" );
- vg_strcat( &q, table );
- vg_strcat( &q, "\"(steamid,time)\n VALUES (?,?);" );
- if( !vg_strgood(&q) ) return 0;
-
- sqlite3_stmt *stmt = db_stmt( q.buffer );
-
- if( stmt ){
- sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
- sqlite3_bind_int( stmt, 2, score );
-
- int fc = sqlite3_step( stmt );
- sqlite3_finalize( stmt );
- if( fc != SQLITE_DONE )
- return 0;
- else
- return 1;
- }
- else return 0;
-}
-
-/*
- * Set username and type
- */
-static int db_updateuser( u64 steamid, const char *username, int admin ){
- sqlite3_stmt *stmt = db_stmt(
- "INSERT OR REPLACE INTO users (steamid, name, type) "
- "VALUES (?,?,?);" );
-
- if( stmt ){
- sqlite3_bind_int64( stmt, 1, *((i64*)(&steamid)) );
- db_sqlite3_bind_sz( stmt, 2, username );
- sqlite3_bind_int( stmt, 3, admin );
-
- int fc = sqlite3_step( stmt );
- sqlite3_finalize(stmt);
-
- if( fc == SQLITE_DONE ){
- vg_success( "Inserted %lu (%s), type: %d\n",
- steamid, username, admin );
- return 1;
- }
- else{
- log_sqlite3( fc );
- return 0;
- }
- }
- else return 0;
-}
-
-/*
- * Get user info
- */
-static int db_getuserinfo( u64 steamid, char *out_username, u32 username_max,
- i32 *out_type ){
- sqlite3_stmt *stmt = db_stmt( "SELECT * FROM users WHERE steamid = ?;" );
- if( !stmt ) return 0;
-
- sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
- int fc = sqlite3_step( stmt );
-
- if( fc != SQLITE_ROW ){
- log_sqlite3( fc );
- sqlite3_finalize( stmt );
- return 0;
- }
-
- if( out_username ){
- const char *name = (const char *)sqlite3_column_text( stmt, 1 );
- vg_strncpy( name, out_username, username_max, k_strncpy_allow_cutoff );
- }
-
- if( out_type )
- *out_type = sqlite3_column_int( stmt, 2 );
-
- sqlite3_finalize( stmt );
- return 1;
-}
-
-static void _db_thread_end(void){
- pthread_mutex_lock( &database.mux );
- database.kill = 1;
- pthread_mutex_unlock( &database.mux );
- sqlite3_close( database.db );
-}
-
-static void *db_loop(void *_){
- int rc = sqlite3_open( "highscores.db", &database.db );
-
- if( rc ){
- vg_error( "database failure: %s\n", sqlite3_errmsg(database.db) );
- _db_thread_end();
- return NULL;
- }
-
- sqlite3_stmt *stmt = db_stmt(
- "CREATE TABLE IF NOT EXISTS \n"
- " users(steamid BIGINT UNIQUE, name VARCHAR(128), type INT);" );
-
- if( stmt ){
- int fc = sqlite3_step( stmt );
- sqlite3_finalize(stmt);
-
- if( fc == SQLITE_DONE ){
- vg_success( "Created users table\n" );
- db_updateuser( 76561198072130043, "harry", 2 );
- }
- else{
- log_sqlite3( fc );
- _db_thread_end();
- return NULL;
- }
- }
- else {
- _db_thread_end();
- return NULL;
- }
-
- /*
- * Request processing loop
- */
- while(1){
- pthread_mutex_lock( &database.mux );
-
- if( database.kill ){
- pthread_mutex_unlock( &database.mux );
- _db_thread_end();
- break;
- }
-
- u32 processed = 0;
-
- for( u32 i=0; i<16; i ++ ){
- db_request *req = NULL;
- if( database.queue.tail ){
- req = (db_request *)database.queue.tail->data;
- pthread_mutex_unlock( &database.mux );
- }
- else{
- pthread_mutex_unlock( &database.mux );
- break;
- }
-
- req->handler( req );
- processed ++;
-
- pthread_mutex_lock( &database.mux );
- vg_queue_pop( &database.queue );
- }
-
- if( processed )
- vg_low( "Processed %u database requests.\n", processed );
-
- usleep(50000);
- }
-
- vg_low( "Database thread terminates.\n" );
- return NULL;
-}
-
-/*
- * Create database connection and users table
- */
-static int db_init(void){
- database.queue.buffer =
- (u8 *)vg_linear_alloc( vg_mem.rtmemory, DB_REQUEST_BUFFER_SIZE ),
- database.queue.size = DB_REQUEST_BUFFER_SIZE;
-
- if( pthread_mutex_init( &database.mux, NULL ) )
- return 0;
-
- if( pthread_create( &database.thread, NULL, db_loop, NULL ) )
- return 0;
-
- return 1;
-}
-
-static int db_killed(void){
- pthread_mutex_lock( &database.mux );
- int result = database.kill;
- pthread_mutex_unlock( &database.mux );
- return result;
-}
-
-static void db_kill(void){
- pthread_mutex_lock( &database.mux );
- database.kill = 1;
- pthread_mutex_unlock( &database.mux );
- pthread_join( database.thread, NULL );
-}
-
-static void db_free(void){
- pthread_mutex_destroy( &database.mux );
-}
-
-static db_request *db_alloc_request( u32 size ){
- u32 total = sizeof(db_request) + size;
-
- pthread_mutex_lock( &database.mux );
- vg_queue_frame *frame = vg_queue_alloc( &database.queue, total );
-
- if( frame ){
- db_request *req = (db_request *)frame->data;
- req->size = size;
- return req;
- }
- else {
- pthread_mutex_unlock( &database.mux );
- return NULL;
- }
-}
-
-static void db_send_request( db_request *request ){
- pthread_mutex_unlock( &database.mux );
-}
-
-#endif /* GAMESERVER_DB_H */
+++ /dev/null
-#pragma once
-#include "font.h"
-#include "input.h"
-#include "player.h"
-#include "vg/vg_engine.h"
-#include "vg/vg_ui/imgui.h"
-
-#define GUI_COL_DARK ui_opacity( 0x00000000, 0.7f )
-#define GUI_COL_NORM ui_opacity( 0x00101010, 0.7f )
-#define GUI_COL_ACTIVE ui_opacity( 0x00444444, 0.7f )
-#define GUI_COL_CLICK ui_opacity( 0x00858585, 0.7f )
-#define GUI_COL_HI ui_opacity( 0x00ffffff, 0.8f )
-
-enum gui_icon {
- k_gui_icon_tick = 0,
- k_gui_icon_tick_2d,
- k_gui_icon_exclaim,
- k_gui_icon_exclaim_2d,
- k_gui_icon_board,
- k_gui_icon_world,
- k_gui_icon_rift,
- k_gui_icon_rift_run,
- k_gui_icon_rift_run_2d,
- k_gui_icon_friend,
- k_gui_icon_player,
- k_gui_icon_rift_run_gold,
- k_gui_icon_rift_run_silver,
- k_gui_icon_glider,
- k_gui_icon_spawn,
- k_gui_icon_spawn_select,
-
- k_gui_icon_count,
-};
-
-#define GUI_HELPER_TEXT_LENGTH 32
-
-struct{
- struct gui_helper{
- vg_input_op *binding;
- char text[GUI_HELPER_TEXT_LENGTH];
- int greyed;
- }
- helpers[4];
- u32 helper_count;
-
- int active_positional_helper;
-
- struct icon_call {
- enum gui_icon icon;
- v4f location;
- v4f colour;
- int colour_changed;
- }
- icon_draw_buffer[64];
- u32 icon_draw_count;
- v4f cur_icon_colour;
- int colour_changed;
-
- char location[64];
- f64 location_time;
-
- f32 factive;
- font3d font;
-
- v3f trick_co;
-
- mdl_context model_icons;
- GLuint icons_texture;
- glmesh icons_mesh;
-
- mdl_submesh *icons[ k_gui_icon_count ];
-}
-static gui = {.cur_icon_colour = {1.0f,1.0f,1.0f,1.0f},.colour_changed=1};
-
-static void gui_helper_clear(void){
- gui.helper_count = 0;
- gui.active_positional_helper = 0;
-}
-
-static struct gui_helper *gui_new_helper( vg_input_op *bind, vg_str *out_text ){
- if( gui.helper_count >= VG_ARRAY_LEN(gui.helpers) ){
- vg_error( "Too many helpers\n" );
- return NULL;
- }
-
- struct gui_helper *helper = &gui.helpers[ gui.helper_count ++ ];
- helper->greyed = 0;
- helper->binding = bind;
- vg_strnull( out_text, helper->text, sizeof(helper->text) );
- return helper;
-}
-
-static void gui_render_icons(void)
-{
- vg_camera ortho;
-
- float fl = 0.0f,
- fr = vg.window_x,
- fb = 0.0f,
- ft = vg.window_y,
- rl = 1.0f / (fr-fl),
- tb = 1.0f / (ft-fb);
-
- m4x4_zero( ortho.mtx.p );
- ortho.mtx.p[0][0] = 2.0f * rl;
- ortho.mtx.p[1][1] = 2.0f * tb;
- ortho.mtx.p[3][0] = (fr + fl) * -rl;
- ortho.mtx.p[3][1] = (ft + fb) * -tb;
- ortho.mtx.p[3][3] = 1.0f;
- m4x3_identity( ortho.transform );
- vg_camera_update_view( &ortho );
- m4x4_mul( ortho.mtx.p, ortho.mtx.v, ortho.mtx.pv ); /* HACK */
- vg_camera_finalize( &ortho );
-
- /* icons */
- font3d_bind( &gui.font, k_font_shader_default, 0, NULL, &ortho );
- mesh_bind( &gui.icons_mesh );
-
- m4x3f mmdl;
- m4x3_identity( mmdl );
- shader_model_font_uMdl( mmdl );
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, gui.icons_texture );
- shader_model_font_uTexMain( 0 );
-
- for( u32 i=0; i<gui.icon_draw_count; i++ ){
- struct icon_call *call = &gui.icon_draw_buffer[i];
-
- if( call->colour_changed )
- shader_model_font_uColour( call->colour );
-
- shader_model_font_uOffset( call->location );
-
- mdl_submesh *sm = gui.icons[ call->icon ];
- if( sm )
- mdl_draw_submesh( sm );
- }
-
- gui.icon_draw_count = 0;
-}
-
-static void gui_draw( ui_context *ctx )
-{
- if( gui.active_positional_helper &&
- (v3_dist2(localplayer.rb.co,gui.trick_co) > 2.0f) )
- gui_helper_clear();
-
- /* helpers
- * ----------------------------------------------------------------- */
-
- gui.factive = vg_lerpf( gui.factive, gui.helper_count?1.0f:0.0f,
- vg.time_frame_delta*2.0f );
-
- ctx->font = &vgf_default_title;
- ui_px height = ctx->font->ch + 16;
- ui_rect lwr = { 0, vg.window_y - height, vg.window_x, height };
-
- ui_px x = 0;
- for( u32 i=0; i<gui.helper_count; i++ )
- {
- struct gui_helper *helper = &gui.helpers[i];
-
- char buf[128];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
- vg_input_string( &str, helper->binding, 1 );
-
- ui_rect box = { x, lwr[1], 1000, lwr[3] };
-
- u32 fg = 0;
- f32 opacity = 0.4f;
- if( helper->greyed )
- {
- fg = ui_colour(ctx, k_ui_fg+2);
- opacity = 0.1f;
- }
-
- struct ui_vert *bg = ui_fill( ctx, box,
- ui_opacity( GUI_COL_DARK, opacity ) );
-
- u32 w;
- box[0] += 16;
- w = ui_text( ctx, box, buf, 1, k_ui_align_middle_left, fg );
- w *= ctx->font->sx;
- bg[1].co[0] = x + w + 32;
- bg[2].co[0] = x + w + 32;
- x += w + 32;
-
- box[0] = x;
- bg = ui_fill( ctx, box, ui_opacity( GUI_COL_NORM, opacity*0.7f ) );
- box[0] += 8;
- w = ui_text( ctx, box, helper->text, 1, k_ui_align_middle_left, fg );
- w *= ctx->font->sx;
- bg[1].co[0] = box[0] + w + 16;
- bg[2].co[0] = box[0] + w + 16;
- x += w + 32;
- }
-
- vg_ui.frosting = gui.factive*0.015f;
- ui_flush( ctx, k_ui_shader_colour, NULL );
- vg_ui.frosting = 0.0f;
-
-
- f64 loc_t = (vg.time_real - gui.location_time) / 5.0;
- if( (loc_t < 1.0) && (gui.location_time != 0.0) )
- {
- f32 t = 1.0f-vg_minf(1.0f,vg_minf(loc_t*20.0f,2.0f-loc_t*2.0f)),
- o = 1.0f-t*t*(2.0f-t);
-
- ui_rect box = { 0, (vg.window_y*2)/3 - height/2, vg.window_x, height };
- ui_fill( ctx, box, ui_opacity( GUI_COL_NORM, 0.5f ) );
- ui_text( ctx, box, gui.location, 1, k_ui_align_middle_center, 0 );
-
- vg_ui.colour[3] = o;
- ui_flush( ctx, k_ui_shader_colour, NULL );
- }
-
- vg_ui.colour[3] = 1.0f;
- ctx->font = &vgf_default_small;
-}
-
-static int gui_location_print_ccmd( int argc, const char *argv[] )
-{
- if( argc > 0 )
- {
- char new_loc[64];
- vg_str str;
- vg_strnull( &str, new_loc, 64 );
- for( int i=0; i<argc; i++ )
- {
- vg_strcat( &str, argv[i] );
- vg_strcat( &str, " " );
- }
- if( !strcmp(gui.location,new_loc) ) return 0;
- vg_strncpy( new_loc, gui.location, 64, k_strncpy_always_add_null );
- gui.location_time = vg.time_real;
- }
- return 0;
-}
-
-static int gui_showtrick_ccmd( int argc, const char *argv[] )
-{
- if( argc == 1 )
- {
- gui_helper_clear();
- vg_str text;
-
- if( !strcmp( argv[0], "pump" ) ){
- if( gui_new_helper( input_axis_list[k_sraxis_grab], &text ) )
- vg_strcat( &text, "Pump" );
- }
- else if( !strcmp( argv[0], "flip" ) ){
- if( gui_new_helper( input_joy_list[k_srjoystick_steer], &text ) )
- vg_strcat( &text, "Flip" );
- }
- else if( !strcmp( argv[0], "ollie" ) ){
- if( gui_new_helper( input_button_list[k_srbind_jump], &text ) )
- vg_strcat( &text, "Ollie" );
- }
- else if( !strcmp( argv[0], "trick" ) ){
- if( gui_new_helper( input_button_list[k_srbind_trick0], &text ) )
- vg_strcat( &text, "Shuvit" );
- if( gui_new_helper( input_button_list[k_srbind_trick1], &text ) )
- vg_strcat( &text, "Kickflip" );
- if( gui_new_helper( input_button_list[k_srbind_trick2], &text ) )
- vg_strcat( &text, "Tre-Flip" );
- }
- else if( !strcmp( argv[0], "misc" ) ){
- if( gui_new_helper( input_button_list[k_srbind_camera], &text ) )
- vg_strcat( &text, "Camera" );
- if( gui_new_helper( input_button_list[k_srbind_use], &text ) )
- vg_strcat( &text, "Skate/Walk" );
- }
- else return 1;
-
- v3_copy( localplayer.rb.co, gui.trick_co );
- gui.active_positional_helper = 1;
- return 0;
- }
- return 1;
-}
-
-static void gui_draw_icon( enum gui_icon icon, v2f co, f32 size )
-{
- if( gui.icon_draw_count == VG_ARRAY_LEN(gui.icon_draw_buffer) )
- return;
-
- struct icon_call *call = &gui.icon_draw_buffer[ gui.icon_draw_count ++ ];
-
- call->icon = icon;
- call->location[0] = co[0] * (f32)vg.window_x;
- call->location[1] = co[1] * (f32)vg.window_y;
- call->location[2] = 0.0f;
- call->location[3] = size * (f32)vg.window_x;
-
- v4_copy( gui.cur_icon_colour, call->colour );
- call->colour_changed = gui.colour_changed;
- gui.colour_changed = 0;
-}
-
-static void gui_icon_setcolour( v4f colour ){
- gui.colour_changed = 1;
- v4_copy( colour, gui.cur_icon_colour );
-}
-
-static mdl_submesh *gui_find_icon( const char *name ){
- mdl_mesh *mesh = mdl_find_mesh( &gui.model_icons, name );
- if( mesh ){
- if( mesh->submesh_count ){
- return mdl_arritm( &gui.model_icons.submeshs, mesh->submesh_start );
- }
- }
-
- return NULL;
-}
-
-static void gui_init(void)
-{
- font3d_load( &gui.font, "models/rs_font.mdl", vg_mem.rtmemory );
- vg_console_reg_cmd( "gui_location", gui_location_print_ccmd, NULL );
- vg_console_reg_cmd( "showtrick", gui_showtrick_ccmd, NULL );
-
- /* load icons */
- void *alloc = vg_mem.rtmemory;
- mdl_open( &gui.model_icons, "models/rs_icons.mdl", alloc );
- mdl_load_metadata_block( &gui.model_icons, alloc );
-
- gui.icons[ k_gui_icon_tick ] = gui_find_icon( "icon_tick" );
- gui.icons[ k_gui_icon_tick_2d ] = gui_find_icon( "icon_tick2d" );
- gui.icons[ k_gui_icon_exclaim ] = gui_find_icon( "icon_exclaim" );
- gui.icons[ k_gui_icon_exclaim_2d ] = gui_find_icon( "icon_exclaim2d" );
- gui.icons[ k_gui_icon_board ] = gui_find_icon( "icon_board" );
- gui.icons[ k_gui_icon_world ] = gui_find_icon( "icon_world" );
- gui.icons[ k_gui_icon_rift ] = gui_find_icon( "icon_rift" );
- gui.icons[ k_gui_icon_rift_run ] = gui_find_icon( "icon_rift_run" );
- gui.icons[ k_gui_icon_rift_run_2d ] = gui_find_icon( "icon_rift_run2d" );
- gui.icons[ k_gui_icon_friend ] = gui_find_icon( "icon_friend" );
- gui.icons[ k_gui_icon_player ] = gui_find_icon( "icon_player" );
- gui.icons[ k_gui_icon_glider ] = gui_find_icon( "icon_glider" );
- gui.icons[ k_gui_icon_spawn ] = gui_find_icon( "icon_spawn" );
- gui.icons[ k_gui_icon_spawn_select ] = gui_find_icon( "icon_spawn_select" );
- gui.icons[ k_gui_icon_rift_run_gold ] =
- gui_find_icon("icon_rift_run_medal_gold");
- gui.icons[ k_gui_icon_rift_run_silver]=
- gui_find_icon("icon_rift_run_medal_silver");
-
- vg_linear_clear( vg_mem.scratch );
- if( !mdl_arrcount( &gui.model_icons.textures ) )
- vg_fatal_error( "No texture in menu file" );
- mdl_texture *tex0 = mdl_arritm( &gui.model_icons.textures, 0 );
- void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
- mdl_fread_pack_file( &gui.model_icons, &tex0->file, data );
- vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
- VG_TEX2D_LINEAR|VG_TEX2D_CLAMP,
- &gui.icons_texture );
-
- mdl_async_load_glmesh( &gui.model_icons, &gui.icons_mesh, NULL );
- mdl_close( &gui.model_icons );
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-#include "vg/vg_console.h"
-#include "vg/vg_input.h"
-#include "vg/vg_m.h"
-#include "font.h"
-
-enum sr_bind
-{
- k_srbind_jump = 0,
- k_srbind_push,
- k_srbind_skid,
- k_srbind_trick0,
- k_srbind_trick1,
- k_srbind_trick2,
- k_srbind_sit,
- k_srbind_use,
- k_srbind_reset,
- k_srbind_dead_respawn,
- k_srbind_camera,
- k_srbind_mleft,
- k_srbind_mright,
- k_srbind_mup,
- k_srbind_mdown,
- k_srbind_mback,
- k_srbind_maccept,
- k_srbind_mopen,
- k_srbind_mhub,
- k_srbind_replay_play,
- k_srbind_replay_freecam,
- k_srbind_replay_resume,
- k_srbind_world_left,
- k_srbind_world_right,
- k_srbind_home,
- k_srbind_lobby,
- k_srbind_chat,
- k_srbind_run,
-
- k_srbind_miniworld_teleport,
- k_srbind_miniworld_resume,
- k_srbind_devbutton,
- k_srbind_max,
-};
-
-enum sr_joystick{
- k_srjoystick_steer = 0,
- k_srjoystick_grab,
- k_srjoystick_look,
- k_srjoystick_max
-};
-
-enum sr_axis{
- k_sraxis_grab = 0,
- k_sraxis_mbrowse_h,
- k_sraxis_mbrowse_v,
- k_sraxis_replay_h,
- k_sraxis_skid,
- k_sraxis_max
-};
-
-
-#define INPUT_BASIC( KB, JS ) \
- (vg_input_op[]){vg_keyboard, KB, vg_joy_button, JS, vg_end}
-
-static vg_input_op *input_button_list[] = {
-[k_srbind_jump] = INPUT_BASIC( SDLK_SPACE, SDL_CONTROLLER_BUTTON_A ),
-[k_srbind_push] = INPUT_BASIC( SDLK_w, SDL_CONTROLLER_BUTTON_B ),
-[k_srbind_trick0] = (vg_input_op[]){
- vg_mouse, SDL_BUTTON_LEFT,
- vg_joy_button, SDL_CONTROLLER_BUTTON_A, vg_end
-},
-[k_srbind_trick1] = (vg_input_op[]){
- vg_mouse, SDL_BUTTON_RIGHT,
- vg_joy_button, SDL_CONTROLLER_BUTTON_B, vg_end
-},
-[k_srbind_trick2] = (vg_input_op[]){
- vg_mouse, SDL_BUTTON_LEFT, vg_mode_mul, vg_mouse, SDL_BUTTON_RIGHT,
- vg_mode_absmax, vg_joy_button, SDL_CONTROLLER_BUTTON_X, vg_end
-},
-[k_srbind_use] = INPUT_BASIC( SDLK_e, SDL_CONTROLLER_BUTTON_Y ),
-[k_srbind_reset] = INPUT_BASIC( SDLK_r, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
-[k_srbind_dead_respawn] =
- INPUT_BASIC( SDLK_q, SDL_CONTROLLER_BUTTON_DPAD_UP ),
-[k_srbind_camera]= INPUT_BASIC( SDLK_c, SDL_CONTROLLER_BUTTON_DPAD_RIGHT ),
-[k_srbind_mleft] = INPUT_BASIC( SDLK_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
-[k_srbind_mright]= INPUT_BASIC( SDLK_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT ),
-[k_srbind_world_left] =
- INPUT_BASIC( SDLK_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
-[k_srbind_world_right] =
- INPUT_BASIC( SDLK_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT ),
-[k_srbind_mup] = INPUT_BASIC( SDLK_UP, SDL_CONTROLLER_BUTTON_DPAD_UP ),
-[k_srbind_mdown] = INPUT_BASIC( SDLK_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN ),
-[k_srbind_mback] = INPUT_BASIC( SDLK_ESCAPE, SDL_CONTROLLER_BUTTON_B ),
-[k_srbind_mopen] = INPUT_BASIC( SDLK_ESCAPE, SDL_CONTROLLER_BUTTON_START ),
-[k_srbind_mhub] = INPUT_BASIC( SDLK_h, SDL_CONTROLLER_BUTTON_Y ),
-[k_srbind_maccept] = (vg_input_op[]){
- vg_keyboard, SDLK_e, vg_gui_visible, 0,
- vg_keyboard, SDLK_RETURN, vg_keyboard, SDLK_RETURN2,
- vg_gui_visible, 1,
- vg_joy_button, SDL_CONTROLLER_BUTTON_A, vg_end
-},
-[k_srbind_replay_play] = INPUT_BASIC( SDLK_g, SDL_CONTROLLER_BUTTON_X ),
-[k_srbind_replay_resume] = INPUT_BASIC( SDLK_SPACE, SDL_CONTROLLER_BUTTON_A ),
-[k_srbind_replay_freecam] = INPUT_BASIC( SDLK_f, SDL_CONTROLLER_BUTTON_Y ),
-[k_srbind_sit] = INPUT_BASIC( SDLK_z, SDL_CONTROLLER_BUTTON_B ),
-[k_srbind_lobby] = INPUT_BASIC( SDLK_TAB, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
-[k_srbind_chat ] = (vg_input_op[]){ vg_keyboard, SDLK_y, vg_end },
-[k_srbind_run ] = (vg_input_op[]){ vg_keyboard, SDLK_LSHIFT,
- vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERLEFT, vg_end },
-
-[k_srbind_miniworld_resume] = (vg_input_op[]){
- vg_keyboard, SDLK_RETURN, vg_gui_visible, 0,
- vg_keyboard, SDLK_RETURN2,
- vg_gui_visible, 1,
- vg_joy_button, SDL_CONTROLLER_BUTTON_X, vg_end
-},
-[k_srbind_miniworld_teleport]= INPUT_BASIC( SDLK_q,
- SDL_CONTROLLER_BUTTON_LEFTSHOULDER ),
-[k_srbind_skid] = (vg_input_op[]){ vg_keyboard, SDLK_LCTRL, vg_end },
-[k_srbind_devbutton] = (vg_input_op[]){ vg_keyboard, SDLK_3, vg_end },
-[k_srbind_max]=NULL
-};
-
-static vg_input_op *input_axis_list[] = {
-[k_sraxis_grab] = (vg_input_op[]){
- vg_keyboard, SDLK_LSHIFT,
- vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, vg_end
-},
-[k_sraxis_mbrowse_h] = (vg_input_op[]){
- vg_mode_sub, vg_keyboard, SDLK_LEFT,
- vg_mode_add, vg_keyboard, SDLK_RIGHT,
- vg_mode_add, vg_joy_axis, SDL_CONTROLLER_AXIS_LEFTX,
- vg_end
-},
-[k_sraxis_mbrowse_v] = (vg_input_op[]){
- vg_mode_sub, vg_keyboard, SDLK_DOWN,
- vg_mode_add, vg_keyboard, SDLK_UP,
- vg_mode_sub, vg_joy_axis, SDL_CONTROLLER_AXIS_LEFTY,
- vg_end
-},
-[k_sraxis_replay_h] = (vg_input_op[]){
- vg_mode_sub, vg_keyboard, SDLK_q,
- vg_mode_add, vg_keyboard, SDLK_e,
- vg_mode_sub, vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERLEFT,
- vg_mode_add, vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
- vg_end
-},
-[k_sraxis_skid] = (vg_input_op[]){
- vg_mode_sub, vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
- vg_mode_add, vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
- vg_end
-},
-[k_sraxis_max]=NULL
-};
-
-static vg_input_op *input_joy_list[] = {
-[k_srjoystick_steer] = (vg_input_op[]){
- vg_index, 0, vg_mode_sub, vg_keyboard, SDLK_a,
- vg_mode_add, vg_keyboard, SDLK_d,
- vg_index, 1, vg_mode_sub, vg_keyboard, SDLK_w,
- vg_mode_add, vg_keyboard, SDLK_s,
- vg_mode_absmax, vg_joy_ls,
- vg_end
-},
-[k_srjoystick_grab] = (vg_input_op[]){
- vg_joy_rs, vg_end
-},
-[k_srjoystick_look] = (vg_input_op[]){
- vg_joy_rs, vg_end
-},
-[k_srjoystick_max]=NULL
-};
-
-struct {
- float axis_states[ k_sraxis_max ][2];
- v2f joystick_states[ k_srjoystick_max ][2];
- u8 button_states[ k_srbind_max ][2];
-
- enum input_state {
- k_input_state_enabled,
- k_input_state_resume,
- k_input_state_resuming,
- k_input_state_pause
- }
- state;
-}
-static srinput;
-
-static int input_filter_generic(void){
- if( (srinput.state != k_input_state_enabled) || vg_console.enabled ||
- (workshop_form.page != k_workshop_form_hidden) )
- return 1;
- else
- return 0;
-}
-
-static int buttons_filter_fixed(void){
- if( input_filter_generic() )
- return 1;
-
- if( vg.engine_stage == k_engine_stage_update_fixed )
- if( vg.fixed_iterations > 0 )
- return 1;
-
- return 0;
-}
-
-/* Rising edge of button */
-static int button_down( enum sr_bind button ){
- if( buttons_filter_fixed() ) return 0;
-
- if( srinput.button_states[ button ][0] &&
- !srinput.button_states[ button ][1] )
- return 1;
- else
- return 0;
-}
-
-/* Falling edge of button */
-static int button_up( enum sr_bind button ){
- if( buttons_filter_fixed() ) return 0;
-
- if( !srinput.button_states[ button ][0] &&
- srinput.button_states[ button ][1] )
- return 1;
- else
- return 0;
-}
-
-/* State of button */
-static int button_press( enum sr_bind button ){
- if( input_filter_generic() )
- return 0;
- return
- srinput.button_states[ button ][0];
-}
-
-static void joystick_state( enum sr_joystick joystick, v2f state ){
- if( input_filter_generic() )
- v2_zero( state );
- else
- v2_copy( srinput.joystick_states[ joystick ][0], state );
-}
-
-static float axis_state( enum sr_axis axis ){
- if( input_filter_generic() )
- return 0.0f;
- else
- return srinput.axis_states[axis][0];
-}
-
-static void skaterift_preupdate_inputs(void){
- if( srinput.state == k_input_state_resuming )
- srinput.state = k_input_state_enabled;
-
- if( srinput.state == k_input_state_resume )
- srinput.state = k_input_state_resuming;
-
- for( u32 i=0; i<k_srbind_max; i++ ){
- srinput.button_states[i][1] = srinput.button_states[i][0];
- srinput.button_states[i][0] = 0;
- }
-
- for( u32 i=0; i<k_srjoystick_max; i++ ){
- v2_copy( srinput.joystick_states[i][0], srinput.joystick_states[i][1] );
- v2_zero( srinput.joystick_states[i][0] );
- }
-
- for( u32 i=0; i<k_sraxis_max; i++ ){
- srinput.axis_states[i][1] = srinput.axis_states[i][0];
- srinput.axis_states[i][0] = 0.0f;
- }
-
- for( int i=0; i<k_srbind_max; i++ ){
- vg_input_op *prog = input_button_list[i];
- if( prog ){
- vg_exec_input_program( k_vg_input_type_button_u8, prog,
- &srinput.button_states[i][0] );
- }
- }
-
- for( int i=0; i<k_sraxis_max; i++ ){
- vg_input_op *prog = input_axis_list[i];
- if( prog ){
- vg_exec_input_program( k_vg_input_type_axis_f32, prog,
- &srinput.axis_states[i][0] );
- }
- }
-
- for( int i=0; i<k_srjoystick_max; i++ ){
- vg_input_op *prog = input_joy_list[i];
- if( prog ){
- vg_exec_input_program( k_vg_input_type_joy_v2f, prog,
- srinput.joystick_states[i][0] );
- }
- }
-
- f32 x = srinput.axis_states[k_sraxis_mbrowse_h][0],
- y = srinput.axis_states[k_sraxis_mbrowse_v][0],
- sensitivity = 0.35f;
-
- if( fabsf(x) > sensitivity ){
- if( x > 0.0f ) srinput.button_states[k_srbind_mright][0] = 1;
- else srinput.button_states[k_srbind_mleft][0] = 1;
- }
-
- if( fabsf(y) > sensitivity ){
- if( y > 0.0f ) srinput.button_states[k_srbind_mup][0] = 1;
- else srinput.button_states[k_srbind_mdown][0] = 1;
- }
-}
+++ /dev/null
-#pragma once
-#include "skaterift.h"
-#include "menu.h"
-#include "model.h"
-#include "entity.h"
-#include "input.h"
-#include "world_map.h"
-#include "ent_miniworld.h"
-#include "audio.h"
-#include "workshop.h"
-#include "gui.h"
-#include "control_overlay.h"
-#include "network.h"
-#include "shaders/model_menu.h"
-
-struct global_menu menu = { .skip_starter = 0 };
-
-void menu_at_begin(void)
-{
- if( menu.skip_starter ) return;
-
- skaterift.activity = k_skaterift_menu;
- menu.page = k_menu_page_starter;
-}
-
-void menu_init(void)
-{
- vg_console_reg_var( "skip_starter_menu", &menu.skip_starter,
- k_var_dtype_i32, VG_VAR_PERSISTENT );
- vg_tex2d_load_qoi_async_file( "textures/prem.qoi",
- VG_TEX2D_CLAMP|VG_TEX2D_NOMIP|VG_TEX2D_NEAREST,
- &menu.prem_tex );
-}
-
-void menu_open( enum menu_page page )
-{
- skaterift.activity = k_skaterift_menu;
-
- if( page != k_menu_page_any )
- {
- menu.page = page;
- }
-}
-
-bool menu_viewing_map(void)
-{
- return (skaterift.activity == k_skaterift_menu) &&
- (menu.page == k_menu_page_main) &&
- (menu.main_index == k_menu_main_map);
-}
-
-static void menu_decor_select( ui_context *ctx, ui_rect rect )
-{
- ui_px b = ctx->font->sx, hb = b/2;
- ui_rect a0 = { rect[0] - 20 - hb, rect[1] + rect[3]/2 - hb, b,b },
- a1 = { rect[0] + rect[2] + 20 + hb, rect[1] + rect[3]/2 - hb, b,b };
-
- ui_text( ctx, a0, "\x95", 1, k_ui_align_middle_center, 0 );
- ui_text( ctx, a1, "\x93", 1, k_ui_align_middle_center, 0 );
-}
-
-static void menu_standard_widget( ui_context *ctx,
- ui_rect inout_panel, ui_rect rect, ui_px s )
-{
- ui_split( inout_panel, k_ui_axis_h, ctx->font->sy*s*2,
- 8, rect, inout_panel );
-}
-
-static bool menu_slider( ui_context *ctx,
- ui_rect inout_panel, bool select, const char *label,
- const f32 disp_min, const f32 disp_max, f32 *value,
- const char *format )
-{
- ui_rect rect, box;
- menu_standard_widget( ctx, inout_panel, rect, 1 );
- ui_label( ctx, rect, label, 1, 8, box );
-
- f32 t;
- enum ui_button_state state = ui_slider_base( ctx, box, 0, 1, value, &t ),
- mask_using =
- k_ui_button_holding_inside |
- k_ui_button_holding_outside |
- k_ui_button_click,
- mask_brighter = mask_using | k_ui_button_hover;
-
- if( vg_input.display_input_method == k_input_method_controller )
- {
- if( select )
- {
- f32 m = axis_state( k_sraxis_mbrowse_h );
- if( fabsf(m) > 0.5f )
- {
- *value += m * vg.time_frame_delta * (1.0f/2.0f);
- *value = vg_clampf( *value, 0, 1 );
- }
-
- menu_decor_select( ctx, rect );
- state |= k_ui_button_hover;
- }
- else
- state = k_ui_button_none;
- }
-
- ui_rect line = { box[0], box[1], t * (f32)box[2], box[3] };
- ui_fill( ctx, line, state&mask_brighter? GUI_COL_ACTIVE: GUI_COL_NORM );
- ui_fill( ctx, (ui_rect){ box[0]+line[2], box[1], box[2]-line[2], box[3] },
- GUI_COL_DARK );
-
- ui_outline( ctx, box, 1, state? GUI_COL_HI: GUI_COL_ACTIVE, 0 );
- ui_slider_text( ctx, box,
- format, disp_min + (*value)*( disp_max-disp_min ) );
-
- return (state & mask_using) && 1;
-}
-
-static bool menu_button( ui_context *ctx,
- ui_rect inout_panel, bool select, const char *text )
-{
- ui_rect rect;
- menu_standard_widget( ctx, inout_panel, rect, 1 );
-
- enum ui_button_state state = k_ui_button_none;
-
- if( vg_input.display_input_method == k_input_method_controller )
- {
- if( select )
- {
- menu_decor_select( ctx, rect );
-
- if( button_down( k_srbind_maccept ) )
- state = k_ui_button_click;
- }
- }
- else
- {
- state = ui_button_base( ctx, rect );
- select = 0;
- }
-
- if( state == k_ui_button_click )
- {
- ui_fill( ctx, rect, GUI_COL_DARK );
- }
- else if( state == k_ui_button_holding_inside )
- {
- ui_fill( ctx, rect, GUI_COL_DARK );
- }
- else if( state == k_ui_button_holding_outside )
- {
- ui_fill( ctx, rect, GUI_COL_DARK );
- ui_outline( ctx, rect, 1, GUI_COL_CLICK, 0 );
- }
- else if( state == k_ui_button_hover )
- {
- ui_fill( ctx, rect, GUI_COL_ACTIVE );
- ui_outline( ctx, rect, 1, GUI_COL_CLICK, 0 );
- }
- else
- {
- ui_fill( ctx, rect, select? GUI_COL_ACTIVE: GUI_COL_NORM );
- if( select )
- ui_outline( ctx, rect, 1, GUI_COL_HI, 0 );
- }
-
- ui_text( ctx, rect, text, 1, k_ui_align_middle_center, 0 );
-
- if( state == k_ui_button_click )
- {
- audio_lock();
- audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
- audio_unlock();
- return 1;
- }
- else return 0;
-}
-
-static bool menu_checkbox( ui_context *ctx, ui_rect inout_panel, bool select,
- const char *str_label, i32 *data )
-{
- ui_rect rect, label, box;
- menu_standard_widget( ctx, inout_panel, rect, 1 );
-
- ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box );
- ui_text( ctx, label, str_label, ctx->scale, k_ui_align_middle_left, 0 );
-
- enum ui_button_state state = k_ui_button_none;
-
- if( vg_input.display_input_method == k_input_method_controller )
- {
- if( select )
- {
- menu_decor_select( ctx, rect );
-
- if( button_down( k_srbind_maccept ) )
- {
- *data = (*data) ^ 0x1;
- state = k_ui_button_click;
- }
- }
- }
- else
- {
- state = ui_checkbox_base( ctx, box, data );
- select = 0;
- }
-
- if( state == k_ui_button_holding_inside )
- {
- ui_fill( ctx, box, GUI_COL_ACTIVE );
- ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
- }
- else if( state == k_ui_button_holding_outside )
- {
- ui_fill( ctx, box, GUI_COL_DARK );
- ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
- }
- else if( state == k_ui_button_hover )
- {
- ui_fill( ctx, box, GUI_COL_ACTIVE );
- ui_outline( ctx, box, 1, GUI_COL_HI, 0 );
- }
- else
- {
- ui_fill( ctx, box, select? GUI_COL_ACTIVE: GUI_COL_DARK );
- ui_outline( ctx, box, 1, select? GUI_COL_HI: GUI_COL_NORM, 0 );
- }
-
- if( *data )
- {
- ui_rect_pad( box, (ui_px[2]){8,8} );
- ui_fill( ctx, box, GUI_COL_HI );
- }
-
- if( state == k_ui_button_click )
- {
- audio_lock();
- audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
- audio_unlock();
- return 1;
- }
- else return 0;
-}
-
-static void menu_heading( ui_context *ctx,
- ui_rect inout_panel, const char *label, u32 colour )
-{
- ui_rect rect;
- menu_standard_widget( ctx, inout_panel, rect, 1 );
-
- rect[0] -= 8;
- rect[2] += 16;
-
- u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f ),
- c1 = ui_opacity( GUI_COL_DARK, 0.5f );
-
- struct ui_vert *vs = ui_fill( ctx, rect, c0 );
-
- vs[0].colour = c1;
- vs[1].colour = c1;
-
- rect[1] += 4;
- ui_text( ctx, rect, label, 1, k_ui_align_middle_center, 1 );
- rect[0] += 1;
- rect[1] -= 1;
- ui_text( ctx, rect, label, 1, k_ui_align_middle_center, colour? colour:
- ui_colour(ctx, k_ui_blue+k_ui_brighter) );
-}
-
-static u32 medal_colour( ui_context *ctx, u32 flags )
-{
- if( flags & k_ent_route_flag_achieve_gold )
- return ui_colour( ctx, k_ui_yellow );
- else if( flags & k_ent_route_flag_achieve_silver )
- return ui_colour( ctx, k_ui_fg );
- else return 0;
-}
-
-static i32 menu_nav( i32 *p_row, int mv, i32 max )
-{
- i32 row_prev = *p_row;
-
- if( mv < 0 ) *p_row = vg_min( max, (*p_row) +1 );
- if( mv > 0 ) *p_row = vg_max( 0, (*p_row) -1 );
-
- if( *p_row != row_prev )
- {
- audio_lock();
- audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
- audio_unlock();
- }
-
- return *p_row;
-}
-
-static void menu_try_find_cam( i32 id )
-{
- world_instance *world = &world_static.instances[0];
- for( u32 i=0; i<mdl_arrcount(&world->ent_npc); i ++ )
- {
- ent_npc *fnpc = mdl_arritm( &world->ent_npc, i );
- if( (fnpc->id == 50) && (fnpc->context == id) )
- {
- if( mdl_entity_id_type(fnpc->camera) == k_ent_camera )
- {
- u32 index = mdl_entity_id_id( fnpc->camera );
- menu.bg_cam = mdl_arritm( &world->ent_camera, index );
- menu.bg_blur = 0;
- }
- }
- }
-}
-
-static void menu_link_modal( const char *url )
-{
- menu.web_link = url;
-
- /* Only reset the choice of browser if 'No' was selected. */
- if( menu.web_choice == 2 )
- menu.web_choice = 0;
-}
-
-void menu_gui( ui_context *ctx )
-{
- if( button_down( k_srbind_mopen ) )
- {
- if( skaterift.activity == k_skaterift_default )
- {
- menu_open( k_menu_page_main );
- return;
- }
- }
-
- if( skaterift.activity != k_skaterift_menu )
- return;
-
- menu.bg_blur = 1;
- menu.bg_cam = NULL;
-
- /* get buttons inputs
- * -------------------------------------------------------------------*/
- int ml = button_press( k_srbind_mleft ),
- mr = button_press( k_srbind_mright ),
- mu = button_press( k_srbind_mup ),
- md = button_press( k_srbind_mdown ),
- mh = ml-mr,
- mv = mu-md,
- enter = button_down( k_srbind_maccept );
-
- /* TAB CONTROL */
- u8 lb_down = 0, rb_down = 0;
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, vg_end
- }, &rb_down );
- vg_exec_input_program( k_vg_input_type_button_u8,
- (vg_input_op[]){
- vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, vg_end
- }, &lb_down );
-
- int mtab = (i32)rb_down - (i32)lb_down;
-
- if( menu.repeater > 0.0f )
- {
- menu.repeater -= vg_minf( vg.time_frame_delta, 0.5f );
- mv = 0;
- mh = 0;
- mtab = 0;
- }
- else
- {
- if( mv || mh || mtab )
- menu.repeater += 0.2f;
- }
-
- if( vg_input.display_input_method == k_input_method_kbm )
- {
- ui_capture_mouse(ctx, 1);
- }
-
- if( skaterift.activity != k_skaterift_menu ) return;
-
-
- if( menu.web_link )
- {
- menu_try_find_cam( 3 );
-
- ui_rect panel = { 0,0, 800, 200 },
- screen = { 0,0, vg.window_x,vg.window_y };
- ui_rect_center( screen, panel );
- ui_fill( ctx, panel, GUI_COL_DARK );
- ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
- ui_rect_pad( panel, (ui_px[]){8,8} );
-
- ui_rect title;
- ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
- ctx->font = &vgf_default_title;
- ui_text( ctx, title, "Open Link?", 1, k_ui_align_middle_center, 0 );
-
- ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
- ctx->font = &vgf_default_large;
- ui_text( ctx, title, menu.web_link, 1, k_ui_align_middle_center, 0 );
-
- ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 };
-
- ui_rect a,b,c;
- ui_split_ratio( end, k_ui_axis_v, 2.0/3.0, 2, a, c );
- ui_split_ratio( a, k_ui_axis_v, 1.0/2.0, 2, a, b );
-
- i32 R = menu_nav( &menu.web_choice, mh, 2 );
-
- if( menu_button( ctx, a, R==0, "Steam Overlay" ) )
- {
- if( steam_ready )
- {
- ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
- SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends,
- menu.web_link,
- k_EActivateGameOverlayToWebPageMode_Default );
- menu.web_link = NULL;
- }
- }
-
- if( menu_button( ctx, b, R==1, "Web Browser" ) )
- {
- char buf[512];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
-#ifdef _WIN32
- vg_strcat( &str, "start " );
-#else
- vg_strcat( &str, "xdg-open " );
-#endif
- vg_strcat( &str, menu.web_link );
-
- if( vg_strgood(&str) )
- system( buf );
-
- menu.web_link = NULL;
- }
-
- if( menu_button( ctx, c, R==2, "No" ) || button_down( k_srbind_mback ) )
- {
- audio_lock();
- audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
- audio_unlock();
- menu.web_link = NULL;
- }
-
- goto menu_draw;
- }
-
-
- if( vg.settings_open )
- {
- if( button_down( k_srbind_mback ) )
- {
- audio_lock();
- audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
- audio_unlock();
- vg_settings_close();
- srinput.state = k_input_state_resume;
- }
-
- return;
- }
-
- if( menu.page == k_menu_page_credits )
- {
- ui_rect panel = { 0,0, 600, 400 },
- screen = { 0,0, vg.window_x,vg.window_y };
- ui_rect_center( screen, panel );
- ui_fill( ctx, panel, GUI_COL_DARK );
- ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
- ui_rect_pad( panel, (ui_px[]){8,8} );
-
- ui_rect title;
- ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
- ctx->font = &vgf_default_title;
- ui_text( ctx, title, "Skate Rift - Credits",
- 1, k_ui_align_middle_center, 0 );
-
- ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
- ctx->font = &vgf_default_large;
- ui_text( ctx, title,
- "Mt.Zero Software", 1, k_ui_align_middle_center, 0 );
-
- ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
- ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
- ctx->font = &vgf_default_title;
- ui_text( ctx, title, "Free Software", 1, k_ui_align_middle_center, 0 );
-
- ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
- ctx->font = &vgf_default_large;
- ui_text( ctx, panel,
- "Sam Lantinga - SDL2 - libsdl.org\n"
- "Hunter WB - Anyascii\n"
- "David Herberth - GLAD\n"
- "Dominic Szablewski - QOI - qoiformat.org\n"
- "Sean Barrett - stb_image, stb_vorbis,\n"
- " stb_include\n"
- "Khronos Group - OpenGL\n"
- , 1, k_ui_align_left, 0 );
-
- ui_rect end = { panel[0], panel[1] + panel[3] - 64, panel[2], 64 };
-
- if( menu_button( ctx, end, 1, "Back" ) || button_down( k_srbind_mback ) )
- {
- menu.page = k_menu_page_main;
- }
-
- goto menu_draw;
- }
- else if( menu.page == k_menu_page_starter )
- {
- i32 R = menu_nav( &menu.intro_row, mv, 3 );
- ui_rect panel = { 0,0, 600, 400 },
- screen = { 0,0, vg.window_x,vg.window_y };
- ui_rect_center( screen, panel );
- ui_fill( ctx, panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
- ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
- ui_rect_pad( panel, (ui_px[]){8,8} );
-
- ui_rect title;
- ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
- ctx->font = &vgf_default_title;
- ui_text( ctx, title,
- "Welcome to Skate Rift", 1, k_ui_align_middle_center, 0 );
-
- ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
- ctx->font = &vgf_default_large;
-
- menu_checkbox( ctx, panel, R == 0,
- "Show controls overlay (good for new players)",
- &control_overlay.enabled );
- menu_checkbox( ctx, panel, R == 1, "Auto connect to global server",
- &network_client.auto_connect );
-
- ui_rect end = { panel[0], panel[1] + panel[3] - 100, panel[2], 100 };
- menu_checkbox( ctx, end, R == 2,
- "Don't show this again", &menu.skip_starter );
- if( menu_button( ctx, end, R == 3, "OK" ) )
- {
- menu.page = k_menu_page_main;
- skaterift.activity = k_skaterift_default;
- }
-
- menu_try_find_cam( 3 );
- goto menu_draw;
- }
- else if( menu.page == k_menu_page_premium )
- {
- i32 R = menu_nav( &menu.prem_row, mh, 1 );
- ui_rect panel = { 0,0, 600, 400+240 },
- screen = { 0,0, vg.window_x,vg.window_y };
- ui_rect_center( screen, panel );
- ui_fill( ctx, panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
- ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
- ui_rect_pad( panel, (ui_px[]){8,8} );
-
- ui_rect title;
- ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
- ctx->font = &vgf_default_title;
- ui_text( ctx, title, "Content is in the full game.",
- 1, k_ui_align_middle_center, 0 );
-
- ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
- ctx->font = &vgf_default_large;
-
- ui_rect img;
- ui_split( panel, k_ui_axis_h, 456, 0, img, panel );
- ui_image( ctx, img, &menu.prem_tex );
-
- ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 }, a,b;
- ui_split_ratio( end, k_ui_axis_v, 0.5f, 2, a, b );
-
- if( menu_button( ctx, a, R == 0, "Store Page" ) )
- {
- if( steam_ready )
- SteamAPI_ISteamFriends_ActivateGameOverlayToStore(
- SteamAPI_SteamFriends(), 2103940, k_EOverlayToStoreFlag_None);
- }
-
- if( menu_button( ctx, b, R == 1, "Nah" ) || button_down( k_srbind_mback ) )
- {
- audio_lock();
- audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
- audio_unlock();
- skaterift.activity = k_skaterift_default;
- return;
- }
-
- goto menu_draw;
- }
-
- /* TOP BAR
- * -------------------------------------------------------------------*/
-
- ctx->font = &vgf_default_title;
- ui_px height = ctx->font->ch + 16;
- ui_rect topbar = { 0, 0, vg.window_x, height };
-
- const char *opts[] = {
- [k_menu_main_main] = "Menu",
- [k_menu_main_map] = "Map",
- [k_menu_main_settings ] = "Settings",
- [k_menu_main_guide ] = "Guides"
- };
-
- if( mtab )
- {
- menu.main_index += mtab;
-
- if( menu.main_index == -1 )
- menu.main_index ++;
-
- if( menu.main_index == VG_ARRAY_LEN(opts) )
- menu.main_index --;
-
- audio_lock();
- audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
- audio_unlock();
- }
-
- ui_px x = 0, spacer = 8;
- for( u32 draw=0; draw<2; draw ++ )
- {
- if( vg_input.display_input_method == k_input_method_controller )
- {
- if( draw )
- {
- ui_rect inf_lb = { x, 0, 32, height };
- ui_fill( ctx, inf_lb, lb_down? GUI_COL_NORM: GUI_COL_NORM );
- ui_text( ctx, inf_lb, "\x91", 1, k_ui_align_middle_center, 0 );
- }
- x += 32 + spacer;
- }
-
- for( i32 i=0; i<VG_ARRAY_LEN(opts); i ++ )
- {
- ui_rect box = { x, 0, ui_text_line_width(ctx, opts[i]) + 32, height };
-
- if( draw )
- {
- enum ui_button_state state = ui_button_base( ctx, box );
- if( state == k_ui_button_click )
- {
- ui_fill( ctx, box, GUI_COL_DARK );
- menu.main_index = i;
- }
- else if( state == k_ui_button_holding_inside )
- {
- ui_fill( ctx, box, GUI_COL_DARK );
- }
- else if( state == k_ui_button_holding_outside )
- {
- ui_fill( ctx, box, GUI_COL_DARK );
- ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
- }
- else if( state == k_ui_button_hover )
- {
- ui_fill( ctx, box, GUI_COL_NORM );
- ui_outline( ctx, box, 1, GUI_COL_ACTIVE, 0 );
- }
- else
- ui_fill( ctx, box,
- i==menu.main_index? GUI_COL_ACTIVE: GUI_COL_NORM );
-
- ui_text( ctx, box, opts[i], 1, k_ui_align_middle_center, 0 );
- }
-
- x += box[2] + spacer;
- }
-
- if( vg_input.display_input_method == k_input_method_controller )
- {
- if( draw )
- {
- ui_rect inf_rb = { x, 0, 32, height };
- ui_fill( ctx, inf_rb, rb_down? GUI_COL_NORM: GUI_COL_NORM );
- ui_text( ctx, inf_rb, "\x92", 1, k_ui_align_middle_center, 0 );
- }
- x += 32;
- }
-
- if( draw )
- ui_fill( ctx,
- (ui_rect){ x+8,0, vg.window_x-(x+8),height }, GUI_COL_NORM );
- else
- {
- x = vg.window_x/2 - x/2;
- ui_fill( ctx,
- (ui_rect){ 0, 0, x-8, height }, GUI_COL_NORM );
- }
- }
-
- if( menu.main_index == k_menu_main_map )
- {
- menu.bg_blur = 0;
- ctx->font = &vgf_default_large;
- ui_rect title = { vg.window_x/2- 512/2, height+8, 512, 64 };
-
- ui_px x = 8,
- y = height+8;
-
- struct ui_vert *vs =
- ui_fill( ctx, (ui_rect){ x,y, 0,height },
- world_static.active_instance? GUI_COL_DARK: GUI_COL_ACTIVE );
-
- char buf[64];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
- vg_strcat( &str, "Hub World" );
-
- if( world_static.active_instance )
- {
- vg_strcat( &str, " (" );
- vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
- vg_strcatch( &str, '\x06' );
- vg_strcatch( &str, '\x00' );
- vg_strcat( &str, "\x1B[0m)" );
- }
-
- ui_px w = ui_text( ctx, (ui_rect){ x+8, y, 1000, height }, buf, 1,
- k_ui_align_middle_left, 0 );
- w *= ctx->font->sx;
- x += w + 16;
-
- vs[1].co[0] = x + 8;
- vs[2].co[0] = x;
-
- x += 2;
-
- world_instance *world = &world_static.instances[1];
- if( world->status == k_world_status_loaded )
- {
- const char *world_name =
- mdl_pstr( &world->meta, world->info.pstr_name );
-
- vg_strnull( &str, buf, sizeof(buf) );
- vg_strcat( &str, world_name );
-
- if( !world_static.active_instance &&
- (vg_input.display_input_method == k_input_method_controller) )
- {
- vg_strcat( &str, " (" );
- vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
- vg_strcatch( &str, '\x06' );
- vg_strcatch( &str, '\x00' );
- vg_strcat( &str, "\x1B[0m)" );
- }
-
- vs = ui_fill( ctx, (ui_rect){ x,y, 1000,height },
- world_static.active_instance? GUI_COL_ACTIVE: GUI_COL_DARK );
- w = ui_text( ctx, (ui_rect){ x+16,y, 1000,height }, buf,
- 1, k_ui_align_middle_left, 0 );
-
- w = w*ctx->font->sx + 8*3;
- x += w;
-
- if( button_down( k_srbind_mhub ) ||
- ui_button_base( ctx, (ui_rect){0,y,x,height} ) == k_ui_button_click )
- {
- world_switch_instance( world_static.active_instance ^ 0x1 );
- skaterift.activity = k_skaterift_default;
- world_map.view_ready = 0;
- }
-
- vs[0].co[0] += 8;
- vs[1].co[0] = x + 8;
- vs[2].co[0] = x;
- }
-
- x = 8;
- y += 8 + height;
-
- if( world_static.active_instance )
- {
- ui_rect stat_panel = { x,y, 256,vg.window_y-y };
- u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f );
- struct ui_vert *vs = ui_fill( ctx, stat_panel, c0 );
-
- ui_rect_pad( stat_panel, (ui_px[2]){8,0} );
-
- for( u32 i=0; i<mdl_arrcount( &world->ent_region ); i ++ )
- {
- ent_region *region = mdl_arritm( &world->ent_region, i );
-
- if( !region->zone_volume )
- continue;
-
- const char *title = mdl_pstr( &world->meta, region->pstr_title );
- ctx->font = &vgf_default_large;
-
- ui_rect title_box;
- menu_standard_widget( ctx, stat_panel, title_box, 1 );
-
- stat_panel[0] += 16;
- stat_panel[2] -= 16;
- ctx->font = &vgf_default_small;
-
- ent_volume *volume = mdl_arritm(&world->ent_volume,
- mdl_entity_id_id(region->zone_volume));
-
- u32 combined = k_ent_route_flag_achieve_gold |
- k_ent_route_flag_achieve_silver;
-
- char buf[128];
- vg_str str;
-
- for( u32 j=0; j<mdl_arrcount(&world->ent_route); j ++ )
- {
- ent_route *route = mdl_arritm(&world->ent_route, j );
-
- v3f local;
- m4x3_mulv( volume->to_local, route->board_transform[3], local );
- if( !((fabsf(local[0]) <= 1.0f) &&
- (fabsf(local[1]) <= 1.0f) &&
- (fabsf(local[2]) <= 1.0f)) )
- {
- continue;
- }
-
- combined &= route->flags;
-
- vg_strnull( &str, buf, sizeof(buf) );
- vg_strcat( &str, "(Race) " );
- vg_strcat( &str, mdl_pstr(&world->meta, route->pstr_name));
-
- if( route->flags & k_ent_route_flag_achieve_silver )
- vg_strcat( &str, " \xb3");
- if( route->flags & k_ent_route_flag_achieve_gold )
- vg_strcat( &str, "\xb3");
-
- ui_rect r;
- ui_standard_widget( ctx, stat_panel, r, 1 );
- ui_text( ctx, r, buf, 1, k_ui_align_middle_left,
- medal_colour( ctx, route->flags ) );
- }
-
- for( u32 j=0; j<mdl_arrcount(&world->ent_challenge); j ++ )
- {
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, j );
-
- v3f local;
- m4x3_mulv( volume->to_local, challenge->transform.co, local );
- if( !((fabsf(local[0]) <= 1.0f) &&
- (fabsf(local[1]) <= 1.0f) &&
- (fabsf(local[2]) <= 1.0f)) )
- {
- continue;
- }
-
- vg_strnull( &str, buf, sizeof(buf) );
- vg_strcat( &str, mdl_pstr(&world->meta, challenge->pstr_alias));
-
- u32 flags = 0x00;
- if( challenge->status )
- {
- flags |= k_ent_route_flag_achieve_gold;
- flags |= k_ent_route_flag_achieve_silver;
- vg_strcat( &str, " \xb3\xb3" );
- }
-
- combined &= flags;
-
- ui_rect r;
- ui_standard_widget( ctx, stat_panel, r, 1 );
- ui_text( ctx, r, buf, 1,
- k_ui_align_middle_left, medal_colour( ctx, flags ) );
- }
-
- stat_panel[0] -= 16;
- stat_panel[2] += 16;
-
- u32 title_col = 0;
- if( combined & k_ent_route_flag_achieve_gold )
- title_col = ui_colour( ctx, k_ui_yellow );
- else if( combined & k_ent_route_flag_achieve_silver )
- title_col = ui_colour( ctx, k_ui_fg );
-
- menu_heading( ctx, title_box, title, title_col );
- }
-
- vs[2].co[1] = stat_panel[1];
- vs[3].co[1] = stat_panel[1];
- }
- }
- else
- {
- if( button_down( k_srbind_mback ) )
- {
- audio_lock();
- audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
- audio_unlock();
- skaterift.activity = k_skaterift_default;
- return;
- }
-
- ui_rect list0 = { vg.window_x/2 - 512/2, height+32,
- 512, vg.window_y-height-64 }, list;
- rect_copy( list0, list );
- ui_rect_pad( list, (ui_px[2]){8,8} );
-
- /* MAIN / MAIN
- * -------------------------------------------------------------------*/
-
- if( menu.main_index == k_menu_main_main )
- {
- i32 R = menu_nav( &menu.main_row, mv, 2 );
-
- if( menu_button( ctx, list, R == 0, "Resume" ) )
- {
- skaterift.activity = k_skaterift_default;
- return;
- }
-
- if( menu_button( ctx, list, R == 1, "Credits" ) )
- {
- menu.page = k_menu_page_credits;
- }
-
- ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
- if( menu_button( ctx, end, R == 2, "Quit Game" ) )
- {
- vg.window_should_close = 1;
- }
- }
- else if( menu.main_index == k_menu_main_settings )
- {
- ui_fill( ctx, list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
- ui_outline( ctx, list0, 1, GUI_COL_NORM, 0 );
- i32 R = menu_nav( &menu.settings_row, mv, 8 );
-
- ctx->font = &vgf_default_large;
- list[1] -= 8;
- menu_heading( ctx, list, "Game", 0 );
- menu_checkbox( ctx, list, R == 0, "Show controls overlay",
- &control_overlay.enabled );
- menu_checkbox( ctx, list, R == 1, "Auto connect to global server",
- &network_client.auto_connect );
-
- menu_heading( ctx, list, "Audio/Video", 0 );
- menu_slider( ctx, list, R == 2, "Volume", 0, 100,
- &vg_audio.external_global_volume, "%.f%%" );
- menu_slider( ctx, list, R == 3, "Resolution", 0, 100,
- &k_render_scale, "%.f%%" );
- menu_checkbox( ctx, list, R == 4, "Motion Blur", &k_blur_effect );
-
- menu_heading( ctx, list, "Camera", 0 );
- menu_slider( ctx, list, R == 5, "Fov", 97, 135,
- &k_fov, "%.1f\xb0" );
- menu_slider( ctx, list, R == 6, "Cam Height", -0.4f, +1.4f,
- &k_cam_height, vg_lerpf(-0.4f,1.4f,k_cam_height)>=0.0f?
- "+%.2fm": "%.2fm" );
- menu_checkbox( ctx, list, R == 7, "Invert Y Axis", &k_invert_y );
-
-
- ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
- ctx->font = &vgf_default_small;
- menu_heading( ctx, end, "Advanced", 0 );
- if( menu_button( ctx, end, R == 8, "Open Engine Settings" ) )
- {
- vg_settings_open();
- }
- }
- else if( menu.main_index == k_menu_main_guide )
- {
- list0[0] = 8;
- list[0] = 16;
-
- ui_px w = 700,
- marg = list0[0]+list0[2],
- pw = vg.window_x - marg,
- infx = pw/2 - w/2 + marg;
- ui_rect inf = { infx, height +32, w, 800 };
-
- struct ui_vert *vs = NULL;
-
- if( menu.guide_sel && (menu.guide_sel <= 3) )
- {
- vs = ui_fill( ctx, inf, ui_opacity( GUI_COL_DARK, 0.20f ) );
- ui_rect_pad( inf, (ui_px[]){8,8} );
- }
-
- ui_fill( ctx, list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
- ui_outline( ctx, list0, 1, GUI_COL_NORM, 0 );
- i32 R = menu_nav( &menu.guides_row, mv, 6 );
-
- ctx->font = &vgf_default_large;
- list[1] -= 8;
- menu_heading( ctx, list, "Info", 0 );
- if( menu.guide_sel == 1 )
- {
- menu_try_find_cam( 1 );
-
- ui_rect title;
- ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
- ctx->font = &vgf_default_title;
- ui_text( ctx,
- title, "Where to go", 1, k_ui_align_middle_center, 0 );
-
- ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
- ctx->font = &vgf_default_large;
- ui_text( ctx, inf,
- "Visit the sandcastles built by John Cockroach to be\n"
- "transported into the real location. Use the map in the\n"
- "menu to return back this hub island.\n"
- "\n"
- "You can begin training at the Volcano Island, where you\n"
- "can learn movement skills. Then travel to Mt.Zero Island\n"
- "or Bort Downtown to test them out. Finally the most\n"
- "challenging course can be taken on at the Valley.\n"
- "\n"
- "Also visit the Steam Workshop for community made\n"
- "locations to skate!"
- , 1, k_ui_align_left, 0 );
-
- vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*11 + 16;
- vs[3].co[1] = vs[2].co[1];
- }
- if( menu_button( ctx, list, R == 0, "Where to go" ) )
- menu.guide_sel = 1;
-
- if( menu.guide_sel == 3 )
- {
- menu_try_find_cam( 2 );
-
- ui_rect title;
- ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
- ctx->font = &vgf_default_title;
- ui_text( ctx, title, "Online", 1, k_ui_align_middle_center, 0 );
-
- ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
- ctx->font = &vgf_default_large;
- ui_text( ctx, inf,
- "Connection to the global server is managed by this radar\n"
- "dish in the hub island. Come back here to turn the\n"
- "connection on or off.\n"
- "\n"
- "You'll then see other players or your friends if you go\n"
- "to the same location, and your highscores will be saved\n"
- "onto the scoreboards."
- , 1, k_ui_align_left, 0 );
-
- vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*7 + 16;
- vs[3].co[1] = vs[2].co[1];
- }
- if( menu_button( ctx, list, R == 1, "Playing Online" ) )
- menu.guide_sel = 3;
-
- menu_heading( ctx, list, "Controls", 0 );
- if( menu_button( ctx, list, R == 2, "Skating \xb2" ) )
- {
- menu.guide_sel = 0;
- menu_link_modal(
- "https://skaterift.com/index.php?page=movement" );
- }
- if( menu.guide_sel == 0 || menu.guide_sel > 3 ) menu_try_find_cam( 3 );
-
- if( menu_button( ctx, list, R == 3, "Tricks \xb2" ) )
- {
- menu.guide_sel = 0;
- menu_link_modal(
- "https://skaterift.com/index.php?page=tricks" );
- }
-
- menu_heading( ctx, list, "Workshop", 0 );
- if( menu_button( ctx, list, R == 4, "Create a Board \xb2" ) )
- {
- menu.guide_sel = 0;
- menu_link_modal(
- "https://skaterift.com/index.php?page=workshop_board" );
- }
- if( menu_button( ctx, list, R == 5, "Create a World \xb2" ) )
- {
- menu.guide_sel = 0;
- menu_link_modal(
- "https://skaterift.com/index.php?page=workshop_world" );
- }
- if( menu_button( ctx, list, R == 6, "Create a Playermodel \xb2" ) )
- {
- menu.guide_sel = 0;
- menu_link_modal(
- "https://skaterift.com/index.php?page=workshop_player" );
- }
- }
- }
-
-menu_draw:
-
- vg_ui.frosting = 0.015f;
- ui_flush( ctx, k_ui_shader_colour, NULL );
- vg_ui.frosting = 0.0f;
- ctx->font = &vgf_default_small;
-}
+++ /dev/null
-#pragma once
-
-#define MENU_STACK_SIZE 8
-
-#include "vg/vg_engine.h"
-#include "entity.h"
-
-enum menu_page
-{
- k_menu_page_any,
- k_menu_page_starter,
- k_menu_page_premium,
- k_menu_page_main,
- k_menu_page_credits,
- k_menu_page_help,
-};
-
-enum menu_main_subpage
-{
- k_menu_main_main = 0,
- k_menu_main_map = 1,
- k_menu_main_settings = 2,
- k_menu_main_guide = 3
-};
-
-struct global_menu
-{
- int disable_open;
- i32 skip_starter;
- enum menu_page page;
- i32 main_index,
- main_row,
- settings_row,
- guides_row,
- intro_row,
- guide_sel,
- prem_row;
- f32 mouse_dist; /* used for waking up mouse */
-
- f32 repeater;
-
- bool bg_blur;
- ent_camera *bg_cam;
-
- const char *web_link; /* if set; modal */
- i32 web_choice;
-
- GLuint prem_tex;
-}
-extern menu;
-
-void menu_init(void);
-void menu_at_begin(void);
-void menu_gui( ui_context *ctx );
-void menu_open( enum menu_page page );
-bool menu_viewing_map(void);
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-
-#include "vg/vg_io.h"
-
-#ifdef VG_3D
-#include "vg/vg_async.h"
-#include "vg/vg_tex.h"
-#endif
-
-#include "vg/vg_msg.h"
-#include "vg/vg_string.h"
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-#include "model.h"
-#include "shader_props.h"
-
-static void mdl_load_fatal_corrupt( mdl_context *mdl )
-{
- vg_fatal_condition();
- vg_file_error_info( mdl->file );
- fclose( mdl->file );
- vg_fatal_exit();
-}
-
-/*
- * Model implementation
- */
-
-void mdl_fread_pack_file( mdl_context *mdl, mdl_file *info, void *dst )
-{
- if( !info->pack_size )
- {
- vg_fatal_condition();
- vg_info( "Packed file is only a header; it is not packed" );
- vg_info( "path: %s\n", mdl_pstr( mdl, info->pstr_path ) );
- vg_fatal_exit();
- }
-
- fseek( mdl->file, mdl->pack_base_offset+info->pack_offset, SEEK_SET );
- u64 l = fread( dst, info->pack_size, 1, mdl->file );
-
- if( l != 1 ) mdl_load_fatal_corrupt( mdl );
-}
-
-/* TODO: Rename these */
-static void mdl_load_array_file_buffer( mdl_context *mdl, mdl_array *arr,
- void *buffer, u32 stride )
-{
- if( arr->item_count )
- {
- fseek( mdl->file, arr->file_offset, SEEK_SET );
-
- if( stride == arr->item_size )
- {
- u64 l = fread( buffer, arr->item_size*arr->item_count, 1, mdl->file );
- if( l != 1 ) mdl_load_fatal_corrupt( mdl );
- }
- else
- {
- vg_warn( "Applying alignment fixup to array @%p [%u -> %u] x %u\n",
- buffer, arr->item_size, stride, arr->item_count );
-
- if( stride > arr->item_size )
- memset( buffer, 0, stride*arr->item_count );
-
- u32 read_size = VG_MIN( stride, arr->item_size );
-
- for( u32 i=0; i<arr->item_count; i++ )
- {
- u64 l = fread( buffer+i*stride, read_size, 1, mdl->file );
- if( stride < arr->item_size )
- fseek( mdl->file, arr->item_size-stride, SEEK_CUR );
-
- if( l != 1 ) mdl_load_fatal_corrupt( mdl );
- }
- }
- }
-}
-
-static void mdl_load_array_file( mdl_context *mdl, mdl_array_ptr *ptr,
- mdl_array *arr, void *lin_alloc, u32 stride )
-{
- if( arr->item_count )
- {
- u32 size = stride*arr->item_count;
- ptr->data = lin_alloc? vg_linear_alloc( lin_alloc, vg_align8(size) ):
- malloc( size );
- mdl_load_array_file_buffer( mdl, arr, ptr->data, stride );
- }
- else
- {
- ptr->data = NULL;
- }
-
- ptr->stride = stride;
- ptr->count = arr->item_count;
-}
-
-void *mdl_arritm( mdl_array_ptr *arr, u32 index )
-{
- return ((u8 *)arr->data) + index*arr->stride;
-}
-
-u32 mdl_arrcount( mdl_array_ptr *arr )
-{
- return arr->count;
-}
-
-static mdl_array *mdl_find_array( mdl_context *mdl, const char *name )
-{
- for( u32 i=0; i<mdl_arrcount(&mdl->index); i++ )
- {
- mdl_array *arr = mdl_arritm( &mdl->index, i );
-
- if( !strncmp(arr->name,name,16) )
- return arr;
- }
-
- return NULL;
-}
-
-int _mdl_load_array( mdl_context *mdl, mdl_array_ptr *ptr,
- const char *name, void *lin_alloc, u32 stride )
-{
- mdl_array *arr = mdl_find_array( mdl, name );
-
- if( arr )
- {
- mdl_load_array_file( mdl, ptr, arr, lin_alloc, stride );
- return 1;
- }
- else
- {
- ptr->data = NULL;
- ptr->count = 0;
- ptr->stride = 0;
- return 0;
- }
-}
-
-int mdl_load_mesh_block( mdl_context *mdl, void *lin_alloc )
-{
- int success = 1;
-
- success &= MDL_LOAD_ARRAY( mdl, &mdl->verts, mdl_vert, lin_alloc );
- success &= MDL_LOAD_ARRAY( mdl, &mdl->indices, mdl_indice, lin_alloc );
-
- return success;
-}
-
-int mdl_load_metadata_block( mdl_context *mdl, void *lin_alloc )
-{
- int success = 1;
-
- success &= _mdl_load_array( mdl, &mdl->strings, "strings", lin_alloc, 1 );
- success &= MDL_LOAD_ARRAY( mdl, &mdl->meshs, mdl_mesh, lin_alloc );
- success &= MDL_LOAD_ARRAY( mdl, &mdl->submeshs, mdl_submesh, lin_alloc );
- success &= MDL_LOAD_ARRAY( mdl, &mdl->textures, mdl_texture, lin_alloc );
- success &= MDL_LOAD_ARRAY( mdl, &mdl->armatures, mdl_armature, lin_alloc );
- success &= MDL_LOAD_ARRAY( mdl, &mdl->bones, mdl_bone, lin_alloc );
- success &= MDL_LOAD_ARRAY( mdl, &mdl->animations,mdl_animation,lin_alloc );
-
- success &= mdl_load_materials( mdl, lin_alloc );
-
- return success;
-}
-
-int mdl_load_animation_block( mdl_context *mdl, void *lin_alloc )
-{
- return MDL_LOAD_ARRAY( mdl, &mdl->keyframes, mdl_keyframe, lin_alloc );
-}
-
-void *mdl_shader_standard( vg_msg *msg, void *alloc )
-{
- struct shader_props_standard *props =
- vg_linear_alloc( alloc, sizeof(struct shader_props_standard) );
-
- vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
- NULL );
-
- return props;
-}
-
-void *mdl_shader_terrain( vg_msg *msg, void *alloc )
-{
- struct shader_props_terrain *props =
- vg_linear_alloc( alloc, sizeof(struct shader_props_terrain) );
-
- vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
- NULL );
- vg_msg_getkvvecf( msg, "sand_colour", k_vg_msg_v4f,
- props->sand_colour, (v4f){ 0.79, 0.63, 0.48, 1.0 } );
- vg_msg_getkvvecf( msg, "blend_offset", k_vg_msg_v2f,
- props->blend_offset, (v2f){ 0.5, 0.0 } );
-
- return props;
-}
-
-void *mdl_shader_vertex_blend( vg_msg *msg, void *alloc )
-{
- struct shader_props_vertex_blend *props =
- vg_linear_alloc( alloc, sizeof(struct shader_props_vertex_blend) );
-
- vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
- NULL );
- vg_msg_getkvvecf( msg, "blend_offset", k_vg_msg_v2f,
- props->blend_offset, (v2f){ 0.5, 0.0 } );
- return props;
-}
-
-void *mdl_shader_water( vg_msg *msg, void *alloc )
-{
- struct shader_props_water *props =
- vg_linear_alloc( alloc, sizeof(struct shader_props_water) );
-
- vg_msg_getkvvecf( msg, "shore_colour", k_vg_msg_v4f,
- props->shore_colour, (v4f){0.03,0.32,0.61,1.0} );
- vg_msg_getkvvecf( msg, "deep_colour", k_vg_msg_v4f,
- props->deep_colour, (v4f){0.0,0.006,0.03,1.0} );
- vg_msg_getkvintg( msg, "fog_scale", k_vg_msg_f32, &props->fog_scale,
- (f32[]){0.04} );
- vg_msg_getkvintg( msg, "fresnel", k_vg_msg_f32, &props->fresnel,
- (f32[]){5.0} );
- vg_msg_getkvintg( msg, "water_scale", k_vg_msg_f32, &props->water_sale,
- (f32[]){ 0.008 } );
- vg_msg_getkvvecf( msg, "wave_speed", k_vg_msg_v4f,
- props->wave_speed, (v4f){0.008,0.006,0.003,0.03} );
- return props;
-}
-
-void *mdl_shader_cubemapped( vg_msg *msg, void *alloc )
-{
- struct shader_props_cubemapped *props =
- vg_linear_alloc( alloc, sizeof(struct shader_props_cubemapped) );
-
- vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
- NULL );
- vg_msg_getkvintg( msg, "cubemap_entity", k_vg_msg_u32,
- &props->cubemap_entity, NULL );
- vg_msg_getkvvecf( msg, "tint", k_vg_msg_v4f,
- props->tint, (v4f){1.0,1.0,1.0,1.0} );
- return props;
-}
-
-bool _mdl_legacy_v105_properties( struct mdl_material_v105 *mat, vg_msg *dst )
-{
- vg_msg_wkvnum( dst, "tex_diffuse", k_vg_msg_u32, 1, &mat->tex_diffuse );
-
- if( mat->shader == k_shader_cubemap )
- {
- vg_msg_wkvnum( dst, "cubemap", k_vg_msg_u32, 1, &mat->tex_none0 );
- vg_msg_wkvnum( dst, "tint", k_vg_msg_f32, 4, mat->colour );
- }
- else if( mat->shader == k_shader_terrain_blend )
- {
- vg_msg_wkvnum( dst, "sand_colour", k_vg_msg_f32, 4, mat->colour );
- vg_msg_wkvnum( dst, "blend_offset", k_vg_msg_f32, 2, mat->colour1 );
- }
- else if( mat->shader == k_shader_standard_vertex_blend )
- {
- vg_msg_wkvnum( dst, "blend_offset", k_vg_msg_f32, 2, mat->colour1 );
- }
- else if( mat->shader == k_shader_water )
- {
- vg_msg_wkvnum( dst, "shore_colour", k_vg_msg_f32, 4, mat->colour );
- vg_msg_wkvnum( dst, "deep_colour", k_vg_msg_f32, 4, mat->colour1 );
- }
-
- return 1;
-}
-
-int mdl_load_materials( mdl_context *mdl, void *lin_alloc )
-{
- MDL_LOAD_ARRAY( mdl, &mdl->materials, mdl_material, lin_alloc );
-
-#if (MDL_VERSION_MIN <= 105)
- /* load legacy material data into scratch */
- mdl_array_ptr legacy_materials;
- if( mdl->info.version <= 105 )
- {
- _mdl_load_array( mdl, &legacy_materials, "mdl_material", vg_mem.scratch,
- sizeof(struct mdl_material_v105) );
- }
-#endif
-
- mdl_array_ptr data;
- _mdl_load_array( mdl, &data, "shader_data", vg_mem.scratch, 1 );
-
- if( !lin_alloc )
- return 1;
-
- for( u32 i=0; i<mdl_arrcount(&mdl->materials); i ++ )
- {
- mdl_material *mat = mdl_arritm( &mdl->materials, i );
- vg_msg msg;
-
-#if (MDL_VERSION_MIN <= 105)
- u8 legacy_buf[512];
- if( mdl->info.version <= 105 )
- {
- vg_msg_init( &msg, legacy_buf, sizeof(legacy_buf) );
- _mdl_legacy_v105_properties( mdl_arritm( &legacy_materials,i ), &msg );
- vg_msg_init( &msg, legacy_buf, msg.cur.co );
- }
- else
-#endif
- {
- vg_msg_init( &msg, data.data + mat->props.kvs.offset,
- mat->props.kvs.size );
- }
-
- if( mat->shader == k_shader_standard ||
- mat->shader == k_shader_standard_cutout ||
- mat->shader == k_shader_foliage ||
- mat->shader == k_shader_fxglow )
- {
- mat->props.compiled = mdl_shader_standard( &msg, lin_alloc );
- }
- else if( mat->shader == k_shader_standard_vertex_blend )
- {
- mat->props.compiled = mdl_shader_vertex_blend( &msg, lin_alloc );
- }
- else if( mat->shader == k_shader_cubemap )
- {
- mat->props.compiled = mdl_shader_cubemapped( &msg, lin_alloc );
- }
- else if( mat->shader == k_shader_terrain_blend )
- {
- mat->props.compiled = mdl_shader_terrain( &msg, lin_alloc );
- }
- else if( mat->shader == k_shader_water )
- {
- mat->props.compiled = mdl_shader_water( &msg, lin_alloc );
- }
- else
- mat->props.compiled = NULL;
- }
-
- return 1;
-}
-
-/*
- * if calling mdl_open, and the file does not exist, the game will fatal quit
- */
-void mdl_open( mdl_context *mdl, const char *path, void *lin_alloc )
-{
- memset( mdl, 0, sizeof( mdl_context ) );
- mdl->file = fopen( path, "rb" );
-
- if( !mdl->file )
- {
- vg_fatal_condition();
- vg_info( "mdl_open('%s'): %s\n", path, strerror(errno) );
- vg_fatal_exit();
- }
-
- u64 l = fread( &mdl->info, sizeof(mdl_header), 1, mdl->file );
- if( l != 1 )
- mdl_load_fatal_corrupt( mdl );
-
- if( mdl->info.version < MDL_VERSION_MIN )
- {
- vg_fatal_condition();
- vg_info( "Legacy model version incompatable" );
- vg_info( "For model: %s\n", path );
- vg_info( " version: %u (min: %u, current: %u)\n",
- mdl->info.version, MDL_VERSION_MIN, MDL_VERSION_NR );
- vg_fatal_exit();
- }
-
- mdl_load_array_file( mdl, &mdl->index, &mdl->info.index, lin_alloc,
- sizeof(mdl_array) );
-
- mdl_array *pack = mdl_find_array( mdl, "pack" );
- if( pack ) mdl->pack_base_offset = pack->file_offset;
- else mdl->pack_base_offset = 0;
-}
-
-/*
- * close file handle
- */
-void mdl_close( mdl_context *mdl )
-{
- fclose( mdl->file );
- mdl->file = NULL;
-}
-
-/* useful things you can do with the model */
-
-void mdl_transform_m4x3( mdl_transform *transform, m4x3f mtx )
-{
- q_m3x3( transform->q, mtx );
- v3_muls( mtx[0], transform->s[0], mtx[0] );
- v3_muls( mtx[1], transform->s[1], mtx[1] );
- v3_muls( mtx[2], transform->s[2], mtx[2] );
- v3_copy( transform->co, mtx[3] );
-}
-
-const char *mdl_pstr( mdl_context *mdl, u32 pstr )
-{
- return ((char *)mdl_arritm( &mdl->strings, pstr )) + 4;
-}
-
-
-int mdl_pstreq( mdl_context *mdl, u32 pstr, const char *str, u32 djb2 )
-{
- u32 hash = *((u32 *)mdl_arritm( &mdl->strings, pstr ));
- if( hash == djb2 ){
- if( !strcmp( str, mdl_pstr( mdl, pstr ))) return 1;
- else return 0;
- }
- else return 0;
-}
-
-/*
- * Simple mesh interface for OpenGL
- * ----------------------------------------------------------------------------
- */
-
-#ifdef VG_3D
-static void mesh_upload( glmesh *mesh,
- mdl_vert *verts, u32 vert_count,
- u32 *indices, u32 indice_count )
-{
- glGenVertexArrays( 1, &mesh->vao );
- glGenBuffers( 1, &mesh->vbo );
- glGenBuffers( 1, &mesh->ebo );
- glBindVertexArray( mesh->vao );
-
- size_t stride = sizeof(mdl_vert);
-
- glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo );
- glBufferData( GL_ARRAY_BUFFER, vert_count*stride, verts, GL_STATIC_DRAW );
-
- glBindVertexArray( mesh->vao );
- glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo );
- glBufferData( GL_ELEMENT_ARRAY_BUFFER, indice_count*sizeof(u32),
- indices, GL_STATIC_DRAW );
-
- /* 0: coordinates */
- glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
- glEnableVertexAttribArray( 0 );
-
- /* 1: normal */
- glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE,
- stride, (void *)offsetof(mdl_vert, norm) );
- glEnableVertexAttribArray( 1 );
-
- /* 2: uv */
- glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE,
- stride, (void *)offsetof(mdl_vert, uv) );
- glEnableVertexAttribArray( 2 );
-
- /* 3: colour */
- glVertexAttribPointer( 3, 4, GL_UNSIGNED_BYTE, GL_TRUE,
- stride, (void *)offsetof(mdl_vert, colour) );
- glEnableVertexAttribArray( 3 );
-
- /* 4: weights */
- glVertexAttribPointer( 4, 4, GL_UNSIGNED_SHORT, GL_TRUE,
- stride, (void *)offsetof(mdl_vert, weights) );
- glEnableVertexAttribArray( 4 );
-
- /* 5: groups */
- glVertexAttribIPointer( 5, 4, GL_UNSIGNED_BYTE,
- stride, (void *)offsetof(mdl_vert, groups) );
- glEnableVertexAttribArray( 5 );
-
- mesh->indice_count = indice_count;
- mesh->loaded = 1;
-}
-
-void mesh_bind( glmesh *mesh )
-{
- glBindVertexArray( mesh->vao );
-}
-
-void mesh_drawn( u32 start, u32 count )
-{
- glDrawElements( GL_TRIANGLES, count, GL_UNSIGNED_INT,
- (void *)(start*sizeof(u32)) );
-}
-
-void mesh_draw( glmesh *mesh )
-{
- mesh_drawn( 0, mesh->indice_count );
-}
-
-void mesh_free( glmesh *mesh )
-{
- if( mesh->loaded )
- {
- glDeleteVertexArrays( 1, &mesh->vao );
- glDeleteBuffers( 1, &mesh->ebo );
- glDeleteBuffers( 1, &mesh->vbo );
- mesh->loaded = 0;
- }
-}
-
-void mdl_draw_submesh( mdl_submesh *sm )
-{
- mesh_drawn( sm->indice_start, sm->indice_count );
-}
-#endif
-
-mdl_mesh *mdl_find_mesh( mdl_context *mdl, const char *name )
-{
- for( u32 i=0; i<mdl_arrcount( &mdl->meshs ); i++ )
- {
- mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
- if( !strcmp( name, mdl_pstr( mdl, mesh->pstr_name )))
- {
- return mesh;
- }
- }
- return NULL;
-}
-
-mdl_submesh *mdl_find_submesh( mdl_context *mdl, const char *mesh_name )
-{
- mdl_mesh *mesh = mdl_find_mesh( mdl, mesh_name );
-
- if( !mesh ) return NULL;
- if( !mesh->submesh_count ) return NULL;
-
- return mdl_arritm( &mdl->submeshs, mesh->submesh_start );
-}
-
-#ifdef VG_3D
-struct payload_glmesh_load
-{
- mdl_vert *verts;
- u32 *indices;
-
- u32 vertex_count,
- indice_count;
-
- glmesh *mesh;
-};
-
-static void _sync_mdl_load_glmesh( void *payload, u32 size )
-{
- struct payload_glmesh_load *job = payload;
- mesh_upload( job->mesh, job->verts, job->vertex_count,
- job->indices, job->indice_count );
-}
-
-void mdl_async_load_glmesh( mdl_context *mdl, glmesh *mesh, u32 *fixup_table )
-{
- mdl_array *arr_vertices = mdl_find_array( mdl, "mdl_vert" );
- mdl_array *arr_indices = mdl_find_array( mdl, "mdl_indice" );
-
- if( arr_vertices && arr_indices )
- {
- u32 size_verts = vg_align8(sizeof(mdl_vert)*arr_vertices->item_count),
- size_indices = vg_align8(sizeof(mdl_indice)*arr_indices->item_count),
- size_hdr = vg_align8(sizeof(struct payload_glmesh_load)),
- total = size_hdr + size_verts + size_indices;
-
- vg_async_item *call = vg_async_alloc( total );
- struct payload_glmesh_load *job = call->payload;
-
- u8 *payload = call->payload;
-
- job->mesh = mesh;
- job->verts = (void*)(payload + size_hdr);
- job->indices = (void*)(payload + size_hdr + size_verts);
- job->vertex_count = arr_vertices->item_count;
- job->indice_count = arr_indices->item_count;
-
- mdl_load_array_file_buffer( mdl, arr_vertices,
- job->verts, sizeof(mdl_vert) );
- mdl_load_array_file_buffer( mdl, arr_indices, job->indices,
- sizeof(mdl_indice) );
-
- if( fixup_table )
- {
- for( u32 i=0; i<job->vertex_count; i ++ )
- {
- mdl_vert *vert = &job->verts[i];
-
- for( u32 j=0; j<4; j++ )
- {
- vert->groups[j] = fixup_table[vert->groups[j]];
- }
- }
- }
-
- /*
- * Unpack the indices (if there are meshes)
- * ---------------------------------------------------------
- */
-
- if( mdl_arrcount( &mdl->submeshs ) )
- {
- mdl_submesh *sm = mdl_arritm( &mdl->submeshs, 0 );
- u32 offset = sm->vertex_count;
-
- for( u32 i=1; i<mdl_arrcount( &mdl->submeshs ); i++ )
- {
- mdl_submesh *sm = mdl_arritm( &mdl->submeshs, i );
- u32 *indices = job->indices + sm->indice_start;
-
- for( u32 j=0; j<sm->indice_count; j++ )
- indices[j] += offset;
-
- offset += sm->vertex_count;
- }
- }
-
- /*
- * Dispatch
- * -------------------------
- */
-
- vg_async_dispatch( call, _sync_mdl_load_glmesh );
- }
- else
- {
- vg_fatal_condition();
- vg_info( "No vertex/indice data in model file\n" );
- vg_fatal_exit();
- }
-}
-
-/* uploads the glmesh, and textures. everything is saved into the mdl_context */
-void mdl_async_full_load_std( mdl_context *mdl )
-{
- mdl_async_load_glmesh( mdl, &mdl->mesh, NULL );
-
- for( u32 i=0; i<mdl_arrcount( &mdl->textures ); i ++ )
- {
- vg_linear_clear( vg_mem.scratch );
- mdl_texture *tex = mdl_arritm( &mdl->textures, i );
-
- void *data = vg_linear_alloc( vg_mem.scratch, tex->file.pack_size );
- mdl_fread_pack_file( mdl, &tex->file, data );
-
- vg_tex2d_load_qoi_async( data, tex->file.pack_size,
- VG_TEX2D_CLAMP|VG_TEX2D_NEAREST, &tex->glname );
- }
-}
-#endif
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-
-#define MDL_VERSION_MIN 101
-#define MDL_VERSION_NR 106
-
-enum mdl_shader{
- k_shader_standard = 0,
- k_shader_standard_cutout = 1,
- k_shader_terrain_blend = 2,
- k_shader_standard_vertex_blend = 3,
- k_shader_water = 4,
- k_shader_invisible = 5,
- k_shader_boundary = 6,
- k_shader_fxglow = 7,
- k_shader_cubemap = 8,
- k_shader_walking = 9,
- k_shader_foliage = 10,
- k_shader_override = 30000
-};
-
-enum mdl_surface_prop{
- k_surface_prop_concrete = 0,
- k_surface_prop_wood = 1,
- k_surface_prop_grass = 2,
- k_surface_prop_tiles = 3,
- k_surface_prop_metal = 4,
- k_surface_prop_snow = 5,
- k_surface_prop_sand = 6
-};
-
-enum material_flag{
- k_material_flag_skate_target = 0x0001,
- k_material_flag_collision = 0x0002,
- k_material_flag_grow_grass = 0x0004,
- k_material_flag_grindable = 0x0008,
- k_material_flag_invisible = 0x0010,
- k_material_flag_boundary = 0x0020,
- k_material_flag_preview_visibile = 0x0040,
- k_material_flag_walking = 0x0080,
-
- k_material_flag_ghosts =
- k_material_flag_boundary|
- k_material_flag_invisible|
- k_material_flag_walking
-};
-
-#pragma pack(push,1)
-
-/* 48 byte */
-struct mdl_vert
-{
- v3f co, /* 3*32 */
- norm; /* 3*32 */
- v2f uv; /* 2*32 */
-
- u8 colour[4]; /* 4*8 */
- u16 weights[4];/* 4*16 */
- u8 groups[4]; /* 4*8 */
-};
-
-#pragma pack(pop)
-
-typedef u32 mdl_indice;
-
-typedef struct mdl_context mdl_context;
-typedef struct mdl_array_ptr mdl_array_ptr;
-typedef struct mdl_vert mdl_vert;
-typedef struct mdl_transform mdl_transform;
-typedef struct mdl_submesh mdl_submesh;
-typedef struct mdl_material mdl_material;
-typedef struct mdl_bone mdl_bone;
-typedef struct mdl_armature mdl_armature;
-typedef struct mdl_animation mdl_animation;
-typedef struct mdl_transform mdl_keyframe;
-typedef struct mdl_mesh mdl_mesh;
-typedef struct mdl_file mdl_file;
-typedef struct mdl_texture mdl_texture;
-typedef struct mdl_array mdl_array;
-typedef struct mdl_header mdl_header;
-
-typedef struct glmesh glmesh;
-struct glmesh
-{
- u32 vao, vbo, ebo;
- u32 indice_count;
- u32 loaded;
-};
-
-struct mdl_transform
-{
- v3f co, s;
- v4f q;
-};
-
-static void transform_identity( mdl_transform *transform )
-{
- v3_zero( transform->co );
- q_identity( transform->q );
- v3_fill( transform->s, 1.0f );
-}
-
-static void mdl_transform_vector( mdl_transform *transform, v3f vec, v3f dest )
-{
- v3_mul( transform->s, vec, dest );
- q_mulv( transform->q, dest, dest );
-}
-
-static void mdl_transform_point( mdl_transform *transform, v3f co, v3f dest )
-{
- mdl_transform_vector( transform, co, dest );
- v3_add( transform->co, dest, dest );
-}
-
-static void mdl_transform_mul( mdl_transform *a, mdl_transform *b,
- mdl_transform *d )
-{
- mdl_transform_point( a, b->co, d->co );
- q_mul( a->q, b->q, d->q );
- q_normalize( d->q );
- v3_mul( a->s, b->s, d->s );
-}
-
-struct mdl_file
-{
- u32 pstr_path,
- pack_offset,
- pack_size;
-};
-
-#if (MDL_VERSION_MIN <= 105)
-struct mdl_material_v105
-{
- u32 pstr_name,
- shader,
- flags,
- surface_prop;
-
- v4f colour,
- colour1;
-
- u32 tex_diffuse, /* Indexes start from 1. 0 if missing. */
- tex_none0,
- tex_none1;
-};
-#endif
-
-struct mdl_material
-{
- u32 pstr_name,
- shader,
- flags,
- surface_prop;
-
- union
- {
- struct
- {
- u32 offset, size;
- /* -> vg_msg containing KV properties */
- }
- kvs;
- void *compiled; /* -> shader specific structure for render */
- }
- props;
-};
-
-struct mdl_bone
-{
- v3f co, end;
- u32 parent,
- collider,
- ik_target,
- ik_pole,
- flags,
- pstr_name;
-
- boxf hitbox;
- v3f conevx, conevy, coneva;
- float conet;
-};
-
-enum bone_flag
-{
- k_bone_flag_deform = 0x00000001,
- k_bone_flag_ik = 0x00000002,
- k_bone_flag_cone_constraint = 0x00000004
-};
-
-enum bone_collider
-{
- k_bone_collider_none = 0,
- k_bone_collider_box = 1,
- k_bone_collider_capsule = 2
-};
-
-struct mdl_armature
-{
- mdl_transform transform;
- u32 bone_start,
- bone_count,
- anim_start,
- anim_count;
-};
-
-struct mdl_animation
-{
- u32 pstr_name,
- length;
- float rate;
- u32 offset;
-};
-
-struct mdl_submesh
-{
- u32 indice_start,
- indice_count,
- vertex_start,
- vertex_count;
-
- boxf bbx;
- u16 material_id, flags;
-};
-
-enum esubmesh_flags
-{
- k_submesh_flag_none = 0x0000,
- k_submesh_flag_consumed = 0x0001
-};
-
-struct mdl_mesh
-{
- mdl_transform transform;
- u32 submesh_start,
- submesh_count,
- pstr_name,
- entity_id, /* upper 16 bits: type, lower 16 bits: index */
- armature_id;
-};
-
-struct mdl_texture
-{
- mdl_file file;
- u32 glname;
-};
-
-struct mdl_array
-{
- u32 file_offset,
- item_count,
- item_size;
-
- char name[16];
-};
-
-struct mdl_header
-{
- u32 version;
- mdl_array index;
-};
-
-struct mdl_context
-{
- FILE *file;
- mdl_header info;
-
- struct mdl_array_ptr
- {
- void *data;
- u32 count, stride;
- }
- index,
-
- /* metadata */
- strings,
- meshs,
- submeshs,
- materials,
- textures,
- armatures,
- bones,
- animations,
-
- /* animation buffers */
- keyframes,
-
- /* mesh buffers */
- verts,
- indices;
- u32 pack_base_offset;
-
- /* runtime */
- glmesh mesh;
-};
-
-void mesh_bind( glmesh *mesh );
-void mesh_drawn( u32 start, u32 count );
-void mesh_draw( glmesh *mesh );
-void mesh_free( glmesh *mesh );
-
-/* file context management */
-void mdl_open( mdl_context *mdl, const char *path, void *lin_alloc );
-void mdl_close( mdl_context *mdl );
-
-/* array loading */
-int _mdl_load_array( mdl_context *mdl, mdl_array_ptr *ptr,
- const char *name, void *lin_alloc, u32 stride );
-#define MDL_LOAD_ARRAY( MDL, PTR, STRUCT, ALLOCATOR ) \
- _mdl_load_array( MDL, PTR, #STRUCT, ALLOCATOR, sizeof(STRUCT) )
-
-/* array access */
-void *mdl_arritm( mdl_array_ptr *arr, u32 index );
-u32 mdl_arrcount( mdl_array_ptr *arr );
-
-/* pack access */
-void mdl_fread_pack_file( mdl_context *mdl, mdl_file *info, void *dst );
-
-/* standard array groups */
-int mdl_load_animation_block( mdl_context *mdl, void *lin_alloc );
-int mdl_load_metadata_block( mdl_context *mdl, void *lin_alloc );
-int mdl_load_mesh_block( mdl_context *mdl, void *lin_alloc );
-int mdl_load_materials( mdl_context *mdl, void *lin_alloc );
-
-/* load mesh */
-void mdl_async_load_glmesh( mdl_context *mdl, glmesh *mesh, u32 *fixup_table );
-
-/* load textures and mesh */
-void mdl_async_full_load_std( mdl_context *mdl );
-
-/* rendering */
-void mdl_draw_submesh( mdl_submesh *sm );
-mdl_mesh *mdl_find_mesh( mdl_context *mdl, const char *name );
-mdl_submesh *mdl_find_submesh( mdl_context *mdl, const char *mesh_name );
-
-/* pstrs */
-const char *mdl_pstr( mdl_context *mdl, u32 pstr );
-int mdl_pstreq( mdl_context *mdl, u32 pstr, const char *str, u32 djb2 );
-#define MDL_CONST_PSTREQ( MDL, Q, CONSTSTR )\
- mdl_pstreq( MDL, Q, CONSTSTR, vg_strdjb2( CONSTSTR ) )
-
-void mdl_transform_m4x3( mdl_transform *transform, m4x3f mtx );
-
+++ /dev/null
-#include "skaterift.h"
-#include "vg/vg_steam.h"
-#include "vg/vg_steam_networking.h"
-#include "vg/vg_steam_auth.h"
-#include "vg/vg_steam_friends.h"
-#include "player.h"
-#include "network.h"
-#include "network_msg.h"
-#include "network_common.h"
-#include "player_remote.h"
-#include "world.h"
-#include "world_sfd.h"
-#include "world_routes.h"
-#include "vg/vg_ui/imgui.h"
-#include "gui.h"
-#include "ent_region.h"
-#include "vg/vg_loader.h"
-
-#ifdef _WIN32
- #include <winsock2.h>
- #include <ws2tcpip.h>
-#else
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <netdb.h>
-#endif
-
-struct network_client network_client =
-{
- .auth_mode = eServerModeAuthentication,
- .state = k_ESteamNetworkingConnectionState_None,
- .last_intent_change = -99999.9
-};
-
-static void scores_update(void);
-
-int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){
- if( msg->m_cbSize < size ) {
- vg_error( "Invalid packet size (must be at least %u)\n", size );
- return 0;
- }
- else{
- return 1;
- }
-}
-
-static void on_auth_ticket_recieved( void *result, void *context ){
- EncryptedAppTicketResponse_t *response = result;
-
- if( response->m_eResult == k_EResultOK ){
- vg_info( " New app ticket ready\n" );
- }
- else{
- vg_warn( " Could not request new encrypted app ticket (%u)\n",
- response->m_eResult );
- }
-
- if( SteamAPI_ISteamUser_GetEncryptedAppTicket( hSteamUser,
- network_client.app_symmetric_key,
- VG_ARRAY_LEN(network_client.app_symmetric_key),
- &network_client.app_key_length )){
- vg_success( " Loaded app ticket\n" );
- }
- else{
- vg_error( " No ticket availible\n" );
- network_client.app_key_length = 0;
- }
-}
-
-static void request_auth_ticket(void){
- /*
- * TODO Check for one thats cached on the disk and load it.
- * This might be OK though because steam seems to cache the result
- */
-
- vg_info( "Requesting new authorization ticket\n" );
-
- vg_steam_async_call *call = vg_alloc_async_steam_api_call();
- call->userdata = NULL;
- call->p_handler = on_auth_ticket_recieved;
- call->id =
- SteamAPI_ISteamUser_RequestEncryptedAppTicket( hSteamUser, NULL, 0 );
-}
-
-static void network_send_username(void){
- if( !network_connected() )
- return;
-
- netmsg_playerusername *update = alloca( sizeof(netmsg_playerusername)+
- NETWORK_USERNAME_MAX );
- update->inetmsg_id = k_inetmsg_playerusername;
- update->index = 0xff;
-
- ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
- const char *username = SteamAPI_ISteamFriends_GetPersonaName(hSteamFriends);
- u32 chs = str_utf8_collapse( username, update->name, NETWORK_USERNAME_MAX );
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote,
- update, sizeof(netmsg_playerusername)+chs+1,
- k_nSteamNetworkingSend_Reliable, NULL );
-}
-
-void network_send_region(void)
-{
- if( !network_connected() )
- return;
-
- netmsg_region *region = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
-
- region->inetmsg_id = k_inetmsg_region;
- region->client = 0;
- region->flags = global_ent_region.flags;
-
- u32 l = vg_strncpy( global_ent_region.location, region->loc,
- NETWORK_REGION_MAX, k_strncpy_always_add_null );
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote,
- region, sizeof(netmsg_region)+l+1,
- k_nSteamNetworkingSend_Reliable, NULL );
-}
-
-static void network_send_request( netmsg_request *req, vg_msg *body,
- void (*callback)(
- netmsg_request *res, vg_msg *body,
- u64 userdata),
- u64 userdata ){
- u32 len = 0;
- if( body ){
- len = body->cur.co;
- vg_info( "Request scoreboard. Info (%u):\n", body->cur.co );
- vg_msg_print( body, len );
-
- if( body->error != k_vg_msg_error_OK ){
- vg_error( "Body not OK\n" );
- return;
- }
- }
-
- if( callback ){
- req->id = vg_pool_lru( &network_client.request_pool );
- if( req->id ){
- vg_pool_watch( &network_client.request_pool, req->id );
- struct network_request *pn =
- vg_pool_item( &network_client.request_pool, req->id );
- pn->callback = callback;
- pn->sendtime = vg.time_real;
- pn->userdata = userdata;
- }
- else{
- vg_error( "Unable to send request. Pool is full.\n" );
- return;
- }
- }
- else
- req->id = 0;
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote,
- req, sizeof(netmsg_request)+len,
- k_nSteamNetworkingSend_Reliable, NULL );
-}
-
-static void network_scoreboard_callback( netmsg_request *res, vg_msg *body,
- u64 userdata ){
- world_instance *world = world_current_instance();
-
- world_routes_recv_scoreboard( world, body, userdata, res->status );
- if( userdata == world_sfd.active_route_board )
- world_sfd_compile_active_scores();
-}
-
-
-
-/* mod_uid: world mod uid,
- * route_uid: run name (just a string)
- * week:
- * 0 ALL TIME
- * 1 CURRENT WEEK
- * 2 ALL TIME + CURRENT WEEK
- * .
- * 10+ specific week index
- */
-void network_request_scoreboard( const char *mod_uid,
- const char *route_uid,
- u32 week, u64 userdata ){
- if( !network_connected() )
- return;
-
- netmsg_request *req = alloca( sizeof(netmsg_request) + 512 );
- req->inetmsg_id = k_inetmsg_request;
-
- vg_msg data;
- vg_msg_init( &data, req->q, 512 );
- vg_msg_wkvstr( &data, "endpoint", "scoreboard" );
- vg_msg_wkvstr( &data, "mod", mod_uid );
- vg_msg_wkvstr( &data, "route", route_uid );
- vg_msg_wkvnum( &data, "week", k_vg_msg_u32, 1, &week );
- network_send_request( req, &data, network_scoreboard_callback, userdata );
-}
-
-static void network_publish_callback( netmsg_request *res, vg_msg *body,
- u64 userdata ){
- if( res->status != k_request_status_ok ){
- vg_error( "Publish laptime, server error #%d\n", (i32)res->status );
- }
-}
-
-void network_publish_laptime( const char *mod_uid,
- const char *route_uid, f64 lap_time ){
- if( !network_connected() )
- return;
-
- i32 time_centiseconds = lap_time * 100.0;
-
- netmsg_request *req = alloca( sizeof(netmsg_request) + 512 );
- req->inetmsg_id = k_inetmsg_request;
-
- vg_msg data;
- vg_msg_init( &data, req->q, 512 );
- vg_msg_wkvstr( &data, "endpoint", "setlap" );
- vg_msg_wkvstr( &data, "mod", mod_uid );
- vg_msg_wkvstr( &data, "route", route_uid );
- vg_msg_wkvnum( &data, "time", k_vg_msg_i32, 1, &time_centiseconds );
- network_send_request( req, &data, network_publish_callback, 0 );
-}
-
-static void network_request_rx_300_400( SteamNetworkingMessage_t *msg ){
- netmsg_blank *tmp = msg->m_pData;
-
- if( tmp->inetmsg_id == k_inetmsg_request ){
-
- }
- else if( tmp->inetmsg_id == k_inetmsg_response ){
- netmsg_request *res = (netmsg_request *)msg->m_pData;
-
- vg_msg *body = NULL;
-
- vg_msg data;
- if( res->status == k_request_status_ok ){
- vg_msg_init( &data, res->q, msg->m_cbSize - sizeof(netmsg_request) );
- vg_success( "Response to #%d:\n", (i32)res->id );
- vg_msg_print( &data, data.max );
- body = &data;
- }
- else {
- vg_warn( "Server response to #%d: %d\n", (i32)res->id, res->status );
- }
-
- if( res->id ){
- struct network_request *pn =
- vg_pool_item( &network_client.request_pool, res->id );
- pn->callback( res, body, pn->userdata );
- vg_pool_unwatch( &network_client.request_pool, res->id );
- }
- }
-}
-
-void network_send_item( enum netmsg_playeritem_type type )
-{
- if( !network_connected() )
- return;
-
- netmsg_playeritem *item =
- alloca( sizeof(netmsg_playeritem) + ADDON_UID_MAX );
- item->inetmsg_id = k_inetmsg_playeritem;
- item->type_index = type;
- item->client = 0;
-
- if( (type == k_netmsg_playeritem_world0) ||
- (type == k_netmsg_playeritem_world1) ){
-
- enum world_purpose purpose = type - k_netmsg_playeritem_world0;
- addon_reg *reg = world_static.instance_addons[ purpose ];
-
- if( reg )
- addon_alias_uid( ®->alias, item->uid );
- else
- item->uid[0] = '\0';
- }
- else{
- u16 view_id = 0;
- enum addon_type addon_type = k_addon_type_none;
- if( type == k_netmsg_playeritem_board ){
- view_id = localplayer.board_view_slot;
- addon_type = k_addon_type_board;
- }
- else if( type == k_netmsg_playeritem_player ){
- view_id = localplayer.playermodel_view_slot;
- addon_type = k_addon_type_player;
- }
-
- struct addon_cache *cache = &addon_system.cache[addon_type];
- vg_pool *pool = &cache->pool;
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
- addon_cache_entry *entry = vg_pool_item( pool, view_id );
- addon_alias_uid( &entry->reg_ptr->alias, item->uid );
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
- }
-
- vg_info( "send equip: [%u] %s\n",
- item->type_index, item->uid );
- u32 chs = strlen(item->uid);
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote,
- item, sizeof(netmsg_playeritem)+chs+1,
- k_nSteamNetworkingSend_Reliable, NULL );
-}
-
-static void network_disconnect(void){
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, network_client.remote, 0, NULL, 0 );
- network_client.remote = 0;
- network_client.state = k_ESteamNetworkingConnectionState_None;
-
- for( int i=0; i<VG_ARRAY_LEN(netplayers.list); i++ ){
- netplayers.list[i].active = 0;
- }
-}
-
-void network_status_string( vg_str *str, u32 *colour )
-{
- if( skaterift.demo_mode ){
- vg_strcat( str, "Offline" );
- return;
- }
-
- if( steam_ready ){
- if( network_client.user_intent == k_server_intent_offline ){
- vg_strcat( str, "Offline" );
- }
- else {
- ESteamNetworkingConnectionState state = network_client.state;
-
- if( state == k_ESteamNetworkingConnectionState_None )
- vg_strcat( str, "No Connection" );
- else if( state == k_ESteamNetworkingConnectionState_Connecting )
- {
- vg_strcatf( str, "Connecting...\n%s", network_client.host_adress );
-
- if( network_client.retries ){
- vg_strcat( str, "\n(" );
- vg_strcati32( str, network_client.retries );
- vg_strcat( str, " retries)" );
- }
- }
- else if( state == k_ESteamNetworkingConnectionState_Connected ){
- vg_strcatf( str, "Connected to:\n%s", network_client.host_adress );
- *colour = 0xff00a020;
- }
- else if( state == k_ESteamNetworkingConnectionState_ClosedByPeer )
- vg_strcat( str, "Connection Closed" );
- else if( state == k_ESteamNetworkingConnectionState_FindingRoute )
- vg_strcat( str, "Finding Route" );
- else if( state ==
- k_ESteamNetworkingConnectionState_ProblemDetectedLocally){
- vg_strcat( str, "Problem Detected\nLocally" );
- *colour = 0xff0000a0;
- }
- else
- vg_strcat( str, "???" );
- }
- }
- else {
- vg_strcat( str, "Steam Offline" );
- *colour = 0xff0000a0;
- }
-}
-
-void render_server_status_gui(void)
-{
- vg_framebuffer_bind( g_render.fb_network_status, 1.0f );
-
- vg_ui_set_screen( 128, 48 );
- ui_context *ctx = &vg_ui.ctx;
-
- /* HACK */
- ctx->cur_vert = 0;
- ctx->cur_indice = 0;
- ctx->vert_start = 0;
- ctx->indice_start = 0;
-
- ui_rect r = { 0, 0, 128, 48 };
-
- char buf[128];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
-
- u32 bg = 0xff000000;
- network_status_string( &str, &bg );
-
- ui_fill( ctx, r, bg );
- ui_text( ctx, r, buf, 1, k_ui_align_center, 0 );
- ui_flush( ctx, k_ui_shader_colour, NULL );
-
- skaterift.rt_textures[ k_skaterift_rt_server_status ] =
- g_render.fb_network_status->attachments[0].id;
-}
-
-static void on_server_connect_status( CallbackMsg_t *msg ){
- SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
- vg_info( " Connection status changed for %lu\n", info->m_hConn );
- vg_info( " %s -> %s\n",
- string_ESteamNetworkingConnectionState(info->m_eOldState),
- string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
-
- if( info->m_hConn == network_client.remote ){
- network_client.state = info->m_info.m_eState;
-
- if( info->m_info.m_eState ==
- k_ESteamNetworkingConnectionState_Connected ){
- vg_success(" Connected to remote server.. authenticating\n");
-
- /* send version info to server */
- netmsg_version version;
- version.inetmsg_id = k_inetmsg_version;
- version.version = NETWORK_SKATERIFT_VERSION;
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote, &version,
- sizeof(netmsg_version), k_nSteamNetworkingSend_Reliable, NULL );
-
- /* TODO: We should really wait to see if the server is in auth mode
- * first... */
- u32 size = sizeof(netmsg_auth) + network_client.app_key_length;
- netmsg_auth *auth = alloca(size);
- auth->inetmsg_id = k_inetmsg_auth;
- auth->ticket_length = network_client.app_key_length;
- for( int i=0; i<network_client.app_key_length; i++ )
- auth->ticket[i] = network_client.app_symmetric_key[i];
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote, auth, size,
- k_nSteamNetworkingSend_Reliable, NULL );
- }
- else if( info->m_info.m_eState ==
- k_ESteamNetworkingConnectionState_ClosedByPeer ){
-
- if( info->m_info.m_eEndReason ==
- k_ESteamNetConnectionEnd_Misc_InternalError ){
- network_client.retries = 40;
- }
- network_disconnect();
- }
- else if( info->m_info.m_eState ==
- k_ESteamNetworkingConnectionState_ProblemDetectedLocally ){
- network_disconnect();
- }
- }
- else{
- //vg_warn( " Recieved signal from unknown connection\n" );
- }
-
- render_server_status_gui();
-}
-
-static void on_persona_state_change( CallbackMsg_t *msg ){
- if( !network_connected() )
- return;
-
- PersonaStateChange_t *info = (void *)msg->m_pubParam;
- ISteamUser *hSteamUser = SteamAPI_SteamUser();
-
- vg_info( "User: %llu, change: %u\n", info->m_ulSteamID,
- info->m_nChangeFlags );
-
- if( info->m_ulSteamID == SteamAPI_ISteamUser_GetSteamID(hSteamUser) ){
- if( info->m_nChangeFlags & k_EPersonaChangeName ){
- network_send_username();
- }
- }
-
- if( info->m_nChangeFlags & k_EPersonaChangeRelationshipChanged ){
- for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
- struct network_player *rp = &netplayers.list[i];
- if( rp->steamid == info->m_ulSteamID ){
- player_remote_update_friendflags( rp );
- }
- }
- }
-}
-
-void network_set_host( const char *host_str, const char *port_str )
-{
- vg_strncpy( host_str, network_client.host_adress,
- sizeof(network_client.host_adress), k_strncpy_overflow_fatal );
-
- memset( &network_client.ip, 0, sizeof(network_client.ip) );
- network_client.ip_resolved = 0;
-
- if( port_str )
- {
- vg_strncpy( port_str, network_client.host_port,
- sizeof(network_client.host_port), k_strncpy_overflow_fatal );
- }
- else
- {
- vg_str str;
- vg_strnull( &str, network_client.host_port,
- sizeof(network_client.host_port) );
- vg_strcati32( &str, NETWORK_PORT );
- }
-
- network_client.ip.m_port = atoi( network_client.host_port );
-}
-
-static void network_connect(void)
-{
- VG_ASSERT( network_client.ip_resolved );
-
- vg_info( "connecting...\n" );
- network_client.remote = SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(
- hSteamNetworkingSockets, &network_client.ip, 0, NULL );
-}
-
-static void network_sign_on_complete(void){
- vg_success( "Sign on completed\n" );
-
- /* send our init info */
- network_send_username();
- for( u32 i=0; i<k_netmsg_playeritem_max; i ++ ){
- network_send_item(i);
- }
- network_send_region();
-}
-
-static void poll_remote_connection(void){
- SteamNetworkingMessage_t *messages[32];
- int len;
-
- for( int i=0; i<10; i++ ){
- len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(
- hSteamNetworkingSockets, network_client.remote,
- messages, VG_ARRAY_LEN(messages));
-
- if( len <= 0 )
- return;
-
- for( int i=0; i<len; i++ ){
- SteamNetworkingMessage_t *msg = messages[i];
-
- if( msg->m_cbSize < sizeof(netmsg_blank) ){
- vg_warn( "Discarding message (too small: %d)\n", msg->m_cbSize );
- continue;
- }
-
- netmsg_blank *tmp = msg->m_pData;
-
- if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) ){
- player_remote_rx_200_300( msg );
- }
- else if( (tmp->inetmsg_id >= 300) && (tmp->inetmsg_id < 400) ){
- network_request_rx_300_400( msg );
- }
- else {
- if( tmp->inetmsg_id == k_inetmsg_version ){
- netmsg_version *version = msg->m_pData;
- if( version->version != NETWORK_SKATERIFT_VERSION ){
- network_disconnect();
- /* we dont want to connect to this server ever */
- network_client.retries = 999;
- network_client.last_attempt = 999999999.9;
- vg_error( "version mismatch with server\n" );
- }
- else {
- network_client.remote_version = version->version;
- network_sign_on_complete();
- }
- }
- }
-
- SteamAPI_SteamNetworkingMessage_t_Release( msg );
- }
- }
-}
-
-static void network_resolve_host_async( void *payload, u32 size )
-{
- u32 *status = payload;
- network_client.ip_resolved = *status;
-
- char buf[256];
- SteamAPI_SteamNetworkingIPAddr_ToString( &network_client.ip, buf, 256, 1 );
- vg_info( "Resolved host address to: %s\n", buf );
-}
-
-static void network_resolve_host_thread( void *_ )
-{
- vg_async_item *call = vg_async_alloc(8);
- u32 *status = call->payload;
- *status = 0;
-
- if( (network_client.host_adress[0] >= '0') &&
- (network_client.host_adress[0] <= '9') )
- {
- SteamAPI_SteamNetworkingIPAddr_ParseString(
- &network_client.ip,
- network_client.host_adress );
- network_client.ip.m_port = atoi( network_client.host_port );
- *status = 1;
- goto end;
- }
-
- vg_info( "Resolving host.. %s (:%s)\n",
- network_client.host_adress, network_client.host_port );
-
- struct addrinfo hints;
- struct addrinfo *result;
-
- /* Obtain address(es) matching host/port. */
-
- memset( &hints, 0, sizeof(hints) );
- hints.ai_family = AF_INET6;
- hints.ai_socktype = SOCK_DGRAM;
- hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
- hints.ai_protocol = 0;
-
- int s = getaddrinfo( network_client.host_adress, network_client.host_port,
- &hints, &result);
- if( s != 0 )
- {
-#ifndef _WIN32
- vg_error( "getaddrinfo: %s\n", gai_strerror(s) );
-#endif
-
- if( !strcmp( network_client.host_adress, "skaterift.com" ) )
- {
- vg_warn( "getaddrinfo failed for skaterift.com;\n "
- "falling back to a hardcoded IPv4\n" );
- strcpy( network_client.host_adress, "46.101.34.155" );
- SteamAPI_SteamNetworkingIPAddr_ParseString(
- &network_client.ip,
- network_client.host_adress );
- network_client.ip.m_port = NETWORK_PORT;
- *status = 1;
- }
-
- goto end;
- }
-
- struct sockaddr_in6 *inaddr = (struct sockaddr_in6 *)result->ai_addr;
- memcpy( network_client.ip.m_ipv6, &inaddr->sin6_addr, 16 );
- freeaddrinfo( result );
-
- *status = 1;
-
-end: vg_async_dispatch( call, network_resolve_host_async );
-}
-
-void network_update(void)
-{
- if( !steam_ready )
- return;
-
- ESteamNetworkingConnectionState state = network_client.state;
-
- if( network_client.user_intent == k_server_intent_offline )
- {
- if( state != k_ESteamNetworkingConnectionState_None )
- network_disconnect();
-
- return;
- }
-
- if( state == k_ESteamNetworkingConnectionState_Connected )
- {
- poll_remote_connection();
- f64 frame_delta = vg.time_real - network_client.last_frame;
-
- if( frame_delta > NETWORK_FRAMERATE )
- {
- network_client.last_frame = vg.time_real;
- remote_player_send_playerframe();
- localplayer.sfx_buffer_count = 0;
- }
-
- remote_player_debug_update();
- }
- else
- {
- if( (state == k_ESteamNetworkingConnectionState_Connecting) ||
- (state == k_ESteamNetworkingConnectionState_FindingRoute) )
- {
- return;
- }
- else
- {
- f64 waited = vg.time_real - network_client.last_attempt,
- min_wait = 1.0;
-
- if( network_client.retries > 5 )
- min_wait = 60.0;
-
- if( waited < min_wait )
- return;
-
- if( !network_client.ip_resolved )
- {
- if( vg_loader_availible() )
- {
- vg_loader_start( network_resolve_host_thread, NULL );
- }
- else return;
- }
- else
- network_connect();
-
- network_client.retries ++;
- network_client.last_attempt = vg.time_real;
- }
- }
-}
-
-void chat_send_message( const char *message )
-{
- if( !network_connected() ){
- return;
- }
-
- netmsg_chat *chat = alloca( sizeof(netmsg_chat) + NETWORK_MAX_CHAT );
- chat->inetmsg_id = k_inetmsg_chat;
- chat->client = 0;
-
- u32 l = vg_strncpy( message, chat->msg, NETWORK_MAX_CHAT,
- k_strncpy_always_add_null );
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote,
- chat, sizeof(netmsg_chat)+l+1,
- k_nSteamNetworkingSend_Reliable, NULL );
-}
-
-static int cmd_network_send_message( int argc, const char *argv[] ){
- char buf[ NETWORK_MAX_CHAT ];
- vg_str str;
- vg_strnull( &str, buf, NETWORK_MAX_CHAT );
-
- for( int i=0; i<argc; i ++ ){
- vg_strcat( &str, argv[i] );
-
- if( i < argc-1 )
- vg_strcatch( &str, ' ' );
- }
-
- chat_send_message( buf );
- return 0;
-}
-
-void network_init(void)
-{
- vg_console_reg_var( "network_info", &network_client.network_info,
- k_var_dtype_i32, VG_VAR_PERSISTENT );
- vg_console_reg_var( "auto_connect", &network_client.auto_connect,
- k_var_dtype_i32, VG_VAR_PERSISTENT );
- if( steam_ready ){
- u32 alloc_size = sizeof(struct network_request)*NETWORK_MAX_REQUESTS;
- network_client.request_buffer =
- vg_linear_alloc( vg_mem.rtmemory, alloc_size );
- memset( network_client.request_buffer, 0, alloc_size );
-
- vg_pool *pool = &network_client.request_pool;
- pool->buffer = network_client.request_buffer;
- pool->count = NETWORK_MAX_REQUESTS;
- pool->stride = sizeof( struct network_request );
- pool->offset = offsetof( struct network_request, poolnode );
- vg_pool_init( pool );
-
- steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
- on_server_connect_status );
- steam_register_callback( k_iPersonaStateChange,
- on_persona_state_change );
- request_auth_ticket();
-
- vg_console_reg_cmd( "say", cmd_network_send_message, NULL );
- }
-}
-
-void network_end(void)
-{
- /* TODO: Send buffered highscores that were not already */
- if( (network_client.state == k_ESteamNetworkingConnectionState_Connected) ||
- (network_client.state == k_ESteamNetworkingConnectionState_Connecting) )
- {
- SteamAPI_ISteamNetworkingSockets_CloseConnection(
- hSteamNetworkingSockets, network_client.remote, 0, NULL, 1 );
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- * All trademarks are property of their respective owners
- */
-
-#pragma once
-#include "vg/vg_platform.h"
-#include "vg/vg_steam_networking.h"
-#include "vg/vg_mem_pool.h"
-#include "vg/vg_msg.h"
-#include "steam.h"
-#include "network_common.h"
-#include "network_msg.h"
-#include "addon_types.h"
-
-#define NETWORK_MAX_REQUESTS 8
-
-/*
- * Interface
- */
-
-/* Call it at start; Connects us to the gameserver */
-void network_init(void);
-
-/* Run this from main loop */
-void network_update(void);
-
-/* Call it at shutdown */
-void network_end(void);
-
-/*
- * Can buffer up a bunch of these by calling many times, they will be
- * sent at the next connection
- */
-void network_submit_highscore( u32 trackid, u16 points, u16 time );
-
-/*
- * Game endpoints are provided with the same names to allow running without a
- * network connection.
- */
-
-struct network_client
-{
- u8 app_symmetric_key[ 1024 ];
- u32 app_key_length;
- EServerMode auth_mode;
-
- HSteamNetConnection remote;
- ESteamNetworkingConnectionState state;
- u32 remote_version;
-
- f64 last_attempt, last_frame;
- u32 retries;
-
- i32 network_info;
- i32 auto_connect;
-
- struct network_request {
- vg_pool_node poolnode;
- void (*callback)( netmsg_request *res, vg_msg *body, u64 userdata );
- f64 sendtime;
- u64 userdata;
- }
- *request_buffer;
- vg_pool request_pool;
-
- SteamNetworkingIPAddr ip;
- char host_port[8], host_adress[256];
- bool ip_resolved;
-
- enum server_intent {
- k_server_intent_offline,
- k_server_intent_online
- }
- user_intent;
- f64 last_intent_change;
- f32 fintent; /* yeah this shit really shouldnt be here but oh well */
-}
-extern network_client;
-
-int packet_minsize( SteamNetworkingMessage_t *msg, u32 size );
-void network_send_item( enum netmsg_playeritem_type type );
-void network_request_scoreboard( const char *mod_uid,
- const char *route_uid,
- u32 week, u64 userdata );
-void network_publish_laptime( const char *mod_uid,
- const char *route_uid, f64 lap_time );
-void chat_send_message( const char *message );
-void render_server_status_gui(void);
-void network_status_string( vg_str *str, u32 *colour );
-void network_send_region(void);
-void network_set_host( const char *host_str, const char *port_str );
-
-static inline int network_connected(void)
-{
- if( network_client.remote_version != NETWORK_SKATERIFT_VERSION ) return 0;
- return network_client.state == k_ESteamNetworkingConnectionState_Connected;
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-#include "vg/vg_string.h"
-
-#define NETWORK_USERNAME_MAX 32
-#define NETWORK_MAX_PLAYERS 20
-#define NETWORK_FRAMERATE 0.1
-#define NETWORK_BUFFERFRAMES 6
-#define NETWORK_MAX_CHAT 128
-#define NETWORK_REGION_MAX 32
-#define NETWORK_SKATERIFT_VERSION 10
-#define NETWORK_REQUEST_MAX 2048
-
-#define NETWORK_LEADERBOARD_ALLTIME 0
-#define NETWORK_LEADERBOARD_CURRENT_WEEK 1
-#define NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK 2
-#define NETWORK_PORT 27403
-#define NETWORK_PORT_STR(STR, X) STR #X
-
-#include "addon_types.h"
-
-static u32 network_msgstring( const char *src,
- u32 m_cbSize, u32 base_size,
- char *buf, u32 buf_size ){
-
- u32 string_len = VG_MIN( m_cbSize - base_size, buf_size );
- return vg_strncpy( src, buf, string_len, k_strncpy_always_add_null );
-}
-
-static u32 network_pair_index( u32 _a, u32 _b ){
- const u32 N = NETWORK_MAX_PLAYERS;
-
- if( !((_a != _b) && (_a<N) && (_b<N) ) )
- {
- vg_fatal_error( "Programming error\n" );
- }
-
- u32 a = VG_MIN( _a, _b ),
- b = VG_MAX( _a, _b );
-
- return ((N-a)*((N-a)-1))/2 - b + a;
-}
+++ /dev/null
-#ifndef NETWORK_COMPRESSION_H
-#define NETWORK_COMPRESSION_H
-
-#include "vg/vg_platform.h"
-#include "vg/vg_m.h"
-
-typedef struct bitpack_ctx bitpack_ctx;
-struct bitpack_ctx {
- enum bitpack_mode {
- k_bitpack_compress,
- k_bitpack_decompress
- }
- mode;
-
- u8 *buffer;
- u32 bytes, buffer_len;
-};
-
-static void bitpack_bytes( bitpack_ctx *ctx, u32 bytes, void *data ){
- u8 *ext = data;
- for( u32 i=0; i<bytes; i++ ){
- u32 index = ctx->bytes+i;
- if( ctx->mode == k_bitpack_compress ){
- if( index < ctx->buffer_len )
- ctx->buffer[index] = ext[i];
- }
- else{
- if( index < ctx->buffer_len )
- ext[i] = ctx->buffer[index];
- else
- ext[i] = 0x00;
- }
- }
- ctx->bytes += bytes;
-}
-
-static u32 bitpack_qf32( bitpack_ctx *ctx, u32 bits,
- f32 min, f32 max, f32 *v ){
- u32 mask = (0x1 << bits) - 1;
-
- if( ctx->mode == k_bitpack_compress ){
- u32 a = vg_quantf( *v, bits, min, max );
- bitpack_bytes( ctx, bits/8, &a );
- return a;
- }
- else {
- u32 a = 0;
- bitpack_bytes( ctx, bits/8, &a );
- *v = vg_dequantf( a, bits, min, max );
- return a;
- }
-}
-
-static void bitpack_qv2f( bitpack_ctx *ctx, u32 bits,
- f32 min, f32 max, v2f v ){
- for( u32 i=0; i<2; i ++ )
- bitpack_qf32( ctx, bits, min, max, v+i );
-}
-
-static void bitpack_qv3f( bitpack_ctx *ctx, u32 bits,
- f32 min, f32 max, v3f v ){
- for( u32 i=0; i<3; i ++ )
- bitpack_qf32( ctx, bits, min, max, v+i );
-}
-
-static void bitpack_qv4f( bitpack_ctx *ctx, u32 bits,
- f32 min, f32 max, v4f v ){
- for( u32 i=0; i<4; i ++ )
- bitpack_qf32( ctx, bits, min, max, v+i );
-}
-
-static void bitpack_qquat( bitpack_ctx *ctx, v4f quat ){
- const f32 k_domain = 0.70710678118f;
-
- if( ctx->mode == k_bitpack_compress ){
- v4f qabs;
- for( u32 i=0; i<4; i++ )
- qabs[i] = fabsf(quat[i]);
-
- u32 lxy = qabs[1]>qabs[0],
- lzw = (qabs[3]>qabs[2])+2,
- l = qabs[lzw]>qabs[lxy]? lzw: lxy;
-
- f32 sign = vg_signf(quat[l]);
-
- u32 smallest[3];
- for( u32 i=0, j=0; i<4; i ++ )
- if( i != l )
- smallest[j ++] = vg_quantf( quat[i]*sign, 10, -k_domain, k_domain );
-
- u32 comp = (smallest[0]<<2) | (smallest[1]<<12) | (smallest[2]<<22) | l;
- bitpack_bytes( ctx, 4, &comp );
- }
- else {
- u32 comp;
- bitpack_bytes( ctx, 4, &comp );
-
- u32 smallest[3] = {(comp>>2 )&0x3ff,
- (comp>>12)&0x3ff,
- (comp>>22)&0x3ff},
- l = comp & 0x3;
-
- f32 m = 1.0f;
-
- for( u32 i=0, j=0; i<4; i ++ ){
- if( i != l ){
- quat[i] = vg_dequantf( smallest[j ++], 10, -k_domain, k_domain );
- m -= quat[i]*quat[i];
- }
- }
-
- quat[l] = sqrtf(m);
- q_normalize( quat );
- }
-}
-
-#endif /* NETWORK_COMPRESSION_H */
+++ /dev/null
-/*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#ifndef NETWORK_MSG_H
-#define NETWORK_MSG_H
-
-#include "world_info.h"
-#include "vg/vg_platform.h"
-;
-
-#pragma pack(push,1)
-
-typedef struct netmsg_blank netmsg_blank;
-enum{ k_inetmsg_blank = 0 };
-struct netmsg_blank{
- u16 inetmsg_id;
-};
-
-/* send after version */
-typedef struct netmsg_auth netmsg_auth;
-enum{ k_inetmsg_auth = 1 };
-struct netmsg_auth
-{
- u16 inetmsg_id;
-
- u32 ticket_length;
- u8 ticket[];
-};
-
-/* version should be sent before auth */
-typedef struct netmsg_version netmsg_version;
-enum{ k_inetmsg_version = 2 };
-struct netmsg_version{
- u16 inetmsg_id;
- u32 version;
-};
-
-/* server control 100 */
-
-/* player updates 200 */
-
-#define NETMSG_BOUNDARY_BIT 0x8000
-#define NETMSG_GATE_BOUNDARY_BIT 0x4000
-#define NETMSG_BOUNDARY_MASK (NETMSG_BOUNDARY_BIT|NETMSG_GATE_BOUNDARY_BIT)
-#define NETMSG_PLAYERFRAME_INSTANCE_ID 0x3
-#define NETMSG_PLAYERFRAME_HAVE_GLIDER 0x4
-#define NETMSG_PLAYERFRAME_GLIDER_ORPHAN 0x8
-
-typedef struct netmsg_playerframe netmsg_playerframe;
-enum{ k_inetmsg_playerframe = 200 };
-struct netmsg_playerframe{
- u16 inetmsg_id;
- f64 timestamp;
-
- u8 client, subsystem,
- flags, sound_effects;
- u16 boundary_hash; /* used for animating correctly through gates, teleport..
- msb is a flip flop for teleporting
- second msb is flip flop for gate */
-
- u8 animdata[];
-};
-
-typedef struct netmsg_playerjoin netmsg_playerjoin;
-enum{ k_inetmsg_playerjoin = 201 };
-struct netmsg_playerjoin{
- u16 inetmsg_id;
- u8 index;
- u64 steamid;
-};
-
-typedef struct netmsg_playerleave netmsg_playerleave;
-enum{ k_inetmsg_playerleave = 202 };
-struct netmsg_playerleave{
- u16 inetmsg_id;
- u8 index;
-};
-
-typedef struct netmsg_playerusername netmsg_playerusername;
-enum{ k_inetmsg_playerusername = 203 };
-struct netmsg_playerusername{
- u16 inetmsg_id;
- u8 index;
- char name[];
-};
-
-typedef struct netmsg_playeritem netmsg_playeritem;
-enum{ k_inetmsg_playeritem = 204 };
-struct netmsg_playeritem{
- u16 inetmsg_id;
- u8 client;
- u8 type_index;
- char uid[];
-};
-enum netmsg_playeritem_type {
- k_netmsg_playeritem_board = 0,
- k_netmsg_playeritem_player,
- k_netmsg_playeritem_world0,
- k_netmsg_playeritem_world1,
- k_netmsg_playeritem_max
-};
-
-typedef struct netmsg_chat netmsg_chat;
-enum{ k_inetmsg_chat = 205 };
-struct netmsg_chat {
- u16 inetmsg_id;
- u8 client;
- char msg[];
-};
-
-typedef struct netmsg_region netmsg_region;
-enum{ k_inetmsg_region = 206 };
-struct netmsg_region {
- u16 inetmsg_id;
- u8 client;
- u32 flags;
- char loc[];
-};
-
-/* requests 300 */
-typedef struct netmsg_request netmsg_request;
-enum{ k_inetmsg_request = 300, k_inetmsg_response = 301 };
-struct netmsg_request {
- u16 inetmsg_id;
- u8 id, status;
- u8 q[];
-};
-
-enum request_status {
- k_request_status_client_error = 0,
- k_request_status_invalid_endpoint = 1,
- k_request_status_unauthorized = 2,
-
- k_request_status_server_error = 100,
- k_request_status_out_of_memory = 101,
- k_request_status_database_error = 102,
-
- k_request_status_ok = 200,
- k_request_status_not_found = 201
-};
-
-#pragma pack(pop)
-#endif /* NETWORK_MSG_H */
+++ /dev/null
-#include "vg/vg_lines.h"
-#include "vg/vg_async.h"
-#include "particle.h"
-#include "shaders/particle.h"
-
-struct particle_system particles_grind = {
- .scale = 0.02f,
- .velocity_scale = 0.001f,
- .width = 0.0125f
-},
-particles_env = {
- .scale = 0.04f,
- .velocity_scale = 0.001f,
- .width = 0.25f
-};
-
-void particle_spawn( particle_system *sys, v3f co, v3f v,
- f32 lifetime, u32 colour )
-{
- if( sys->alive == sys->max ) return;
-
- particle *p = &sys->array[ sys->alive ++ ];
- v3_copy( co, p->co );
- v3_copy( v, p->v );
- p->life = lifetime;
- p->colour = colour;
-}
-
-void particle_spawn_cone( particle_system *sys,
- v3f co, v3f dir, f32 angle, f32 speed,
- f32 lifetime, u32 colour )
-{
- if( sys->alive == sys->max ) return;
-
- particle *p = &sys->array[ sys->alive ++ ];
-
- v3f tx, ty;
- v3_tangent_basis( dir, tx, ty );
-
- v3f rand;
- vg_rand_cone( &vg.rand, rand, angle );
- v3_muls( tx, rand[0]*speed, p->v );
- v3_muladds( p->v, ty, rand[1]*speed, p->v );
- v3_muladds( p->v, dir, rand[2]*speed, p->v );
-
- p->life = lifetime;
- p->colour = colour;
- v3_copy( co, p->co );
-}
-
-void particle_system_update( particle_system *sys, f32 dt )
-{
- u32 i = 0;
-iter: if( i == sys->alive ) return;
-
- particle *p = &sys->array[i];
- p->life -= dt;
-
- if( p->life < 0.0f ){
- *p = sys->array[ -- sys->alive ];
- goto iter;
- }
-
- v3_muladds( p->co, p->v, dt, p->co );
- p->v[1] += -9.8f * dt;
-
- i ++;
- goto iter;
-}
-
-void particle_system_debug( particle_system *sys )
-{
- for( u32 i=0; i<sys->alive; i ++ ){
- particle *p = &sys->array[i];
- v3f p1;
- v3_muladds( p->co, p->v, 0.2f, p1 );
- vg_line( p->co, p1, p->colour );
- }
-}
-
-struct particle_init_args {
- particle_system *sys;
- u16 indices[];
-};
-
-static void async_particle_init( void *payload, u32 size ){
- struct particle_init_args *args = payload;
- particle_system *sys = args->sys;
-
- glGenVertexArrays( 1, &sys->vao );
- glGenBuffers( 1, &sys->vbo );
- glGenBuffers( 1, &sys->ebo );
- glBindVertexArray( sys->vao );
-
- size_t stride = sizeof(particle_vert);
-
- glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
- glBufferData( GL_ARRAY_BUFFER, sys->max*stride*4, NULL, GL_DYNAMIC_DRAW );
- glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, sys->ebo );
- glBufferData( GL_ELEMENT_ARRAY_BUFFER,
- sys->max*sizeof(u16)*6, args->indices, GL_STATIC_DRAW );
-
- /* 0: coordinates */
- glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
- glEnableVertexAttribArray( 0 );
-
- /* 3: colour */
- glVertexAttribPointer( 1, 4, GL_UNSIGNED_BYTE, GL_TRUE,
- stride, (void *)offsetof(particle_vert, colour) );
- glEnableVertexAttribArray( 1 );
-}
-
-void particle_alloc( particle_system *sys, u32 max )
-{
- size_t stride = sizeof(particle_vert);
-
- sys->max = max;
- sys->array = vg_linear_alloc( vg_mem.rtmemory, max*sizeof(particle) );
- sys->vertices = vg_linear_alloc( vg_mem.rtmemory, max*stride*4 );
-
- vg_async_item *call =
- vg_async_alloc( sizeof(particle_system *) + max*sizeof(u16)*6 );
- struct particle_init_args *init = call->payload;
- init->sys = sys;
-
- for( u32 i=0; i<max; i ++ ){
- init->indices[i*6+0] = i*4;
- init->indices[i*6+1] = i*4+1;
- init->indices[i*6+2] = i*4+2;
- init->indices[i*6+3] = i*4;
- init->indices[i*6+4] = i*4+2;
- init->indices[i*6+5] = i*4+3;
- }
-
- vg_async_dispatch( call, async_particle_init );
-}
-
-void particle_system_prerender( particle_system *sys )
-{
- for( u32 i=0; i<sys->alive; i ++ ){
- particle *p = &sys->array[i];
- particle_vert *vs = &sys->vertices[i*4];
-
- v3f v, right;
- v3_copy( p->v, v );
-
- f32 vm = v3_length( p->v );
- v3_muls( v, 1.0f/vm, v );
- v3_cross( v, (v3f){0,1,0}, right );
-
- f32 l = (sys->scale+sys->velocity_scale*vm),
- w = sys->width;
-
- v3f p0, p1;
- v3_muladds( p->co, p->v, l, p0 );
- v3_muladds( p->co, p->v, -l, p1 );
-
- v3_muladds( p0, right, w, vs[0].co );
- v3_muladds( p1, right, w, vs[1].co );
- v3_muladds( p1, right, -w, vs[2].co );
- v3_muladds( p0, right, -w, vs[3].co );
-
- vs[0].colour = p->colour;
- vs[1].colour = p->colour;
- vs[2].colour = p->colour;
- vs[3].colour = p->colour;
- }
-
- glBindVertexArray( sys->vao );
-
- size_t stride = sizeof(particle_vert);
- glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
- glBufferSubData( GL_ARRAY_BUFFER, 0, sys->alive*stride*4, sys->vertices );
-}
-
-void particle_system_render( particle_system *sys, vg_camera *cam )
-{
- glDisable( GL_CULL_FACE );
- glEnable( GL_DEPTH_TEST );
-
- shader_particle_use();
- shader_particle_uPv( cam->mtx.pv );
- shader_particle_uPvPrev( cam->mtx_prev.pv );
-
- glBindVertexArray( sys->vao );
- glDrawElements( GL_TRIANGLES, sys->alive*6, GL_UNSIGNED_SHORT, NULL );
-}
+++ /dev/null
-#pragma once
-#include "skaterift.h"
-
-typedef struct particle_system particle_system;
-typedef struct particle particle;
-typedef struct particle_vert particle_vert;
-
-struct particle_system {
- struct particle {
- v3f co, v;
- f32 life;
- u32 colour;
- }
- *array;
-
-#pragma pack(push,1)
- struct particle_vert {
- v3f co;
- u32 colour;
- }
- *vertices;
-#pragma pack(pop)
-
- u32 alive, max;
- GLuint vao, vbo, ebo;
-
- /* render settings */
- f32 scale, velocity_scale, width;
-}
-extern particles_grind, particles_env;
-
-void particle_alloc( particle_system *sys, u32 max );
-void particle_system_update( particle_system *sys, f32 dt );
-void particle_system_debug( particle_system *sys );
-void particle_system_prerender( particle_system *sys );
-void particle_system_render( particle_system *sys, vg_camera *cam );
-
-void particle_spawn( particle_system *sys,
- v3f co, v3f v, f32 lifetime, u32 colour );
-void particle_spawn_cone( particle_system *sys,
- v3f co, v3f dir, f32 angle, f32 speed,
- f32 lifetime, u32 colour );
+++ /dev/null
-/*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#ifndef PHYSICS_TEST_H
-#define PHYSICS_TEST_H
-
-#include "rigidbody.h"
-#include "player.h"
-
-rigidbody ground = { .type = k_rb_shape_box,
- .bbx = {{-100.0f,-1.0f,-100.0f},{100.0f,0.0f,100.0f}},
- .co = {0.0f, 0.0f, 0.0f},
- .q = {0.0f,0.0f,0.0f,1.0f},
- .is_world = 1 };
-
-rigidbody blocky =
- {
- .type = k_rb_shape_box,
- .bbx = {{-2.0f,-1.0f,-3.0f},{2.0f,1.0f,2.0f}},
- .co = {30.0f,2.0f,30.0f},
- .q = {0.0f,0.0f,0.0f,1.0f},
- .is_world = 1
- };
-
-rigidbody marko =
-{
- .type = k_rb_shape_box,
- .bbx = {{-0.5f,-0.5f,-0.5f},{0.5f,0.5f,0.5f}},
- .co = {-36.0f,8.0f,-36.0f},
- .q = {0.0f,0.0f,0.0f,1.0f},
- .is_world = 0
-};
-
-scene epic_scene;
-
-rigidbody epic_scene_rb =
-{
- .type = k_rb_shape_scene,
- .co = {0.0f,0.0f,0.0f},
- .q = {0.0f,0.0f,0.0f,1.0f},
- .is_world = 1,
- .inf.scene = { .pscene = &epic_scene }
-};
-
-rigidbody funnel[4] = {
- {
- .type = k_rb_shape_box,
- .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
- .co = {-10.0f,5.0f,0.0f},
- .is_world = 1
- },
- {
- .type = k_rb_shape_box,
- .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
- .co = { 10.0f,5.0f,0.0f},
- .is_world = 1
- },
- {
- .type = k_rb_shape_box,
- .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
- .co = { 0.0f,5.0f,10.0f},
- .is_world = 1
- },
- {
- .type = k_rb_shape_box,
- .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
- .co = {0.0f,5.0f,-10.0f},
- .is_world = 1
- }
-};
-
-rigidbody jeff1 = { .type = k_rb_shape_capsule,
- .inf.capsule = { .radius = 0.75f, .height = 3.0f },
- .co = {30.0f, 4.0f, 30.0f },
- .q = {1.0f,0.0f,0.0f,0.0f}
-};
-
-rigidbody ball = { .type = k_rb_shape_sphere,
- .inf.sphere = { .radius = 2.0f },
- .co = {0.0f,20.0f,2.0f},
- .q = {0.0f,0.0f,0.0f,1.0f}},
-
- ball1= { .type = k_rb_shape_sphere,
- .inf.sphere = { .radius = 2.0f },
- .co = {0.1f,25.0f,0.2f},
- .q = {0.0f,0.0f,0.0f,1.0f}};
-
-rigidbody jeffs[16];
-
-static void reorg_jeffs(void)
-{
- for( int i=0; i<vg_list_size(jeffs); i++ )
- {
- v3_copy( (v3f){ (vg_randf()-0.5f) * 10.0f,
- (vg_randf()-0.5f) * 10.0f + 17.0f,
- (vg_randf()-0.5f) * 10.0f }, jeffs[i].co );
- v4_copy( (v4f){ vg_randf(), vg_randf(), vg_randf(), vg_randf() },
- jeffs[i].q );
- q_normalize( jeffs[i].q );
-
- jeffs[i].type = k_rb_shape_capsule;
- jeffs[i].inf.capsule.radius = 0.75f;
- jeffs[i].inf.capsule.height = 3.0f;
-
- rb_init( &jeffs[i] );
- }
-}
-
-static void physics_test_start(void)
-{
- q_axis_angle( funnel[0].q, (v3f){1.0f,0.0f,0.0f}, 0.6f );
- q_axis_angle( funnel[1].q, (v3f){1.0f,0.0f,0.0f}, -0.6f );
- q_axis_angle( funnel[2].q, (v3f){0.0f,0.0f,1.0f}, 0.6f );
- q_axis_angle( funnel[3].q, (v3f){0.0f,0.0f,1.0f}, -0.6f );
-
- for( int i=0; i<4; i++ )
- rb_init( &funnel[i] );
-
- reorg_jeffs();
-
- rb_init( &ground );
- rb_init( &ball );
- rb_init( &ball1 );
- rb_init( &jeff1 );
- rb_init( &blocky );
-
- scene_init( &epic_scene );
-
- mdl_header *mdl = mdl_load( "models/epic_scene.mdl" );
-
- m4x3f transform;
- m4x3_identity( transform );
-
- for( int i=0; i<mdl->node_count; i++ )
- {
- mdl_node *pnode = mdl_node_from_id( mdl, i );
-
- for( int j=0; j<pnode->submesh_count; j++ )
- {
- mdl_submesh *sm = mdl_node_submesh( mdl, pnode, j );
- scene_add_submesh( &epic_scene, mdl, sm, transform );
- }
- }
-
- vg_free( mdl );
- scene_bh_create( &epic_scene );
-
- rb_init( &epic_scene_rb );
- rb_init( &marko );
-}
-
-static void physics_test_update(void)
-{
- player_freecam();
- player_camera_update();
-
- for( int i=0; i<4; i++ )
- rb_debug( &funnel[i], 0xff0060e0 );
- rb_debug( &ground, 0xff00ff00 );
- rb_debug( &ball, 0xffe00040 );
- rb_debug( &ball1, 0xff00e050 );
-
- rb_debug( &blocky, 0xffcccccc );
- rb_debug( &jeff1, 0xff00ffff );
-
- rb_debug( &epic_scene_rb, 0xffcccccc );
- rb_debug( &marko, 0xffffcc00 );
-
- {
-
- rb_solver_reset();
-
- for( int i=0; i<4; i++ )
- {
- rigidbody *fn = &funnel[i];
- rb_collide( &ball, fn );
- rb_collide( &ball1, fn );
- rb_collide( &jeff1, fn );
-
- for( int i=0; i<vg_list_size(jeffs); i++ )
- rb_collide( jeffs+i, fn );
- }
-
- for( int i=0; i<vg_list_size(jeffs)-1; i++ )
- {
- for( int j=i+1; j<vg_list_size(jeffs); j++ )
- {
- rb_collide( jeffs+i, jeffs+j );
- }
- }
-
- for( int i=0; i<vg_list_size(jeffs); i++ )
- {
- rb_collide( jeffs+i, &ground );
- rb_collide( jeffs+i, &ball );
- rb_collide( jeffs+i, &ball1 );
- rb_collide( jeffs+i, &jeff1 );
- }
-
- rb_collide( &jeff1, &ground );
- rb_collide( &jeff1, &blocky );
- rb_collide( &jeff1, &ball );
- rb_collide( &jeff1, &ball1 );
-
- rb_collide( &ball, &ground );
- rb_collide( &ball1, &ground );
- rb_collide( &ball1, &ball );
- rb_collide( &marko, &epic_scene_rb );
-
- rb_presolve_contacts( rb_contact_buffer, rb_contact_count );
- for( int i=0; i<8; i++ )
- rb_solve_contacts( rb_contact_buffer, rb_contact_count );
-
-
- /* ITERATE */
- {
- for( int i=0; i<vg_list_size(jeffs); i++ )
- {
- rb_debug( &jeffs[i], (u32[]){ 0xff0000ff, 0xff00ff00, 0xff00ffff,
- 0xffff0000, 0xffff00ff, 0xffffff00,
- }[i%6] );
- rb_iter( jeffs+i );
- }
-
- rb_iter( &ball );
- rb_iter( &ball1 );
- rb_iter( &jeff1 );
- rb_iter( &marko );
- }
-
- /* POSITION OVERRIDE */
- {
- if(glfwGetKey( vg.window, GLFW_KEY_L ))
- {
- m4x3_mulv( player.camera, (v3f){0.0f,0.0f,-5.0f}, marko.co );
- v3_zero( marko.v );
- v3_zero( marko.w );
- }
- if(glfwGetKey( vg.window, GLFW_KEY_K ))
- {
- m4x3_mulv( player.camera, (v3f){0.0f,0.0f,-5.0f}, ball.co );
- v3_zero( ball.v );
- v3_zero( ball.w );
- }
- if(glfwGetKey( vg.window, GLFW_KEY_J ))
- {
- m4x3_mulv( player.camera, (v3f){0.0f,0.0f,-5.0f}, ball1.co );
- v3_zero( ball1.v );
- v3_zero( ball1.w );
- }
-
- if(glfwGetKey( vg.window, GLFW_KEY_H ))
- {
- reorg_jeffs();
- }
- }
-
- /* UPDATE TRANSFORMS */
- for( int i=0; i<vg_list_size(jeffs); i++ )
- {
- rb_update_transform(jeffs+i);
- }
-
- rb_update_transform( &ball );
- rb_update_transform( &ball1 );
- rb_update_transform( &jeff1 );
- rb_update_transform( &marko );
-
- }
-}
-
-static void physics_test_render(void)
-{
- m4x4f world_4x4;
- m4x3_expand( player.camera_inverse, world_4x4 );
-
- gpipeline.fov = 60.0f;
- m4x4_projection( vg_pv, gpipeline.fov,
- (float)vg_window_x / (float)vg_window_y,
- 0.1f, 2100.0f );
-
- m4x4_mul( vg_pv, world_4x4, vg_pv );
- glEnable( GL_DEPTH_TEST );
-
- glDisable( GL_DEPTH_TEST );
- vg_lines_drawall( (float *)vg_pv );
-}
-
-#endif /* PHYSICS_TEST_H */
+++ /dev/null
-#include "player.h"
-#include "addon.h"
-#include "player_model.h"
-#include "input.h"
-#include "world.h"
-#include "audio.h"
-#include "player_replay.h"
-#include "network.h"
-#include "network_common.h"
-#include "world_routes.h"
-#include "ent_miniworld.h"
-#include "gui.h"
-
-#include "shaders/model_entity.h"
-#include "shaders/model_character_view.h"
-#include "shaders/model_board_view.h"
-
-#include "player_walk.h"
-#include "player_dead.h"
-#include "player_drive.h"
-#include "player_skate.h"
-#include "player_basic_info.h"
-#include "player_glide.h"
-#include <string.h>
-
-i32 k_invert_y = 0;
-struct localplayer localplayer =
-{
- .rb =
- {
- .co = { 0,0,0 },
- .w = { 0,0,0 },
- .v = { 0,0,0 },
- .q = { 0,0,0,1 },
- .to_world = M4X3_IDENTITY,
- .to_local = M4X3_IDENTITY
- }
-};
-
-struct player_subsystem_interface *player_subsystems[] =
-{
- [k_player_subsystem_walk] = &player_subsystem_walk,
- [k_player_subsystem_dead] = &player_subsystem_dead,
- [k_player_subsystem_drive] = &player_subsystem_drive,
- [k_player_subsystem_skate] = &player_subsystem_skate,
- [k_player_subsystem_basic_info]=&player_subsystem_basic_info,
- [k_player_subsystem_glide] = &player_subsystem_glide,
-};
-
-int localplayer_cmd_respawn( int argc, const char *argv[] )
-{
- ent_spawn *rp = NULL, *r;
- world_instance *world = world_current_instance();
-
- if( argc == 1 ){
- rp = world_find_spawn_by_name( world, argv[0] );
- }
- else if( argc == 0 ){
- rp = world_find_closest_spawn( world, localplayer.rb.co );
- }
-
- if( !rp )
- return 0;
-
- player__spawn( rp );
- return 1;
-}
-
-void player_init(void)
-{
- for( u32 i=0; i<k_player_subsystem_max; i++ )
- {
- struct player_subsystem_interface *sys = player_subsystems[i];
- if( sys->system_register ) sys->system_register();
- }
-
- vg_console_reg_cmd( "respawn", localplayer_cmd_respawn, NULL );
- VG_VAR_F32( k_cam_damp );
- VG_VAR_F32( k_cam_spring );
- VG_VAR_F32( k_cam_punch );
- VG_VAR_F32( k_cam_shake_strength );
- VG_VAR_F32( k_cam_shake_trackspeed );
- VG_VAR_I32( k_player_debug_info, flags=VG_VAR_PERSISTENT );
-
-#if 0
- vg_console_reg_var( "cinema", &k_cinema, k_var_dtype_f32, 0 );
- vg_console_reg_var( "cinema_fixed", &k_cinema_fixed, k_var_dtype_i32, 0 );
-#endif
- vg_console_reg_var( "invert_y", &k_invert_y,
- k_var_dtype_i32, VG_VAR_PERSISTENT );
-}
-
-void player__debugtext( ui_context *ctx, int size, const char *fmt, ... )
-{
- char buffer[ 1024 ];
-
- va_list args;
- va_start( args, fmt );
- vsnprintf( buffer, 1024, fmt, args );
- va_end( args );
-
- ui_text( ctx, g_player_debugger, buffer, size, k_ui_align_left, 0 );
- g_player_debugger[1] += size*16;
-}
-
-/*
- * Appearence
- */
-
-void player__use_model( u16 reg_id )
-{
- addon_cache_unwatch( k_addon_type_player,
- localplayer.playermodel_view_slot );
- localplayer.playermodel_view_slot =
- addon_cache_create_viewer( k_addon_type_player, reg_id );
-}
-
-void player__bind(void)
-{
- for( u32 i=0; i<k_player_subsystem_max; i++ )
- {
- struct player_subsystem_interface *sys = player_subsystems[i];
-
- if( sys->bind ) sys->bind();
- }
-}
-
-/*
- * Gameloop events
- * ----------------------------------------------------------------------------
- */
-
-void player__pre_update(void)
-{
- if( button_down( k_srbind_camera ) && !localplayer.immobile &&
- (localplayer.subsystem != k_player_subsystem_dead) ){
- if( localplayer.cam_control.camera_mode == k_cam_firstperson )
- localplayer.cam_control.camera_mode = k_cam_thirdperson;
- else
- localplayer.cam_control.camera_mode = k_cam_firstperson;
- }
-
- if( player_subsystems[ localplayer.subsystem ]->pre_update )
- player_subsystems[ localplayer.subsystem ]->pre_update();
-}
-
-void player__update(void)
-{
- if( player_subsystems[ localplayer.subsystem ]->update )
- player_subsystems[ localplayer.subsystem ]->update();
-
- if( localplayer.glider_orphan &&
- (skaterift.activity != k_skaterift_replay) )
- glider_physics( (v2f){0,0} );
-}
-
-void player__post_update(void)
-{
- struct player_subsystem_interface *sys =
- player_subsystems[ localplayer.subsystem ];
-
- if( sys->post_update ) sys->post_update();
-
- SDL_AtomicLock( &air_audio_data.sl );
- air_audio_data.speed = v3_length( localplayer.rb.v ) * vg.time_rate;
- SDL_AtomicUnlock( &air_audio_data.sl );
-}
-
-/*
- * Applies gate transport to a player_interface
- */
-void player__pass_gate( u32 id )
-{
- world_instance *world = world_current_instance();
- skaterift_record_frame( &player_replay.local, 1 );
-
- /* update boundary hash (network animation) */
- u16 index = mdl_entity_id_id(id) & ~NETMSG_BOUNDARY_MASK;
- localplayer.boundary_hash ^= NETMSG_GATE_BOUNDARY_BIT;
- localplayer.boundary_hash &= ~NETMSG_BOUNDARY_MASK;
- localplayer.boundary_hash |= index;
-
- ent_gate *gate = mdl_arritm( &world->ent_gate, mdl_entity_id_id(id) );
- world_routes_fracture( world, gate, localplayer.rb.co, localplayer.rb.v );
-
- localplayer.gate_waiting = gate;
- localplayer.deferred_frame_record = 1;
-
- struct player_cam_controller *cc = &localplayer.cam_control;
- m4x3_mulv( gate->transport, cc->tpv_lpf, cc->tpv_lpf );
- m3x3_mulv( gate->transport, cc->cam_velocity_smooth,
- cc->cam_velocity_smooth );
-
- m4x3_mulv( gate->transport, localplayer.cam.pos, localplayer.cam.pos );
-
- if( gate->flags & k_ent_gate_nonlocal )
- {
- world_default_spawn_pos( world, world->player_co );
- world_static.active_instance = gate->target;
- player__clean_refs();
-
- replay_clear( &player_replay.local );
- }
- else
- {
- world_routes_activate_entry_gate( world, gate );
- }
-
- v3f v0;
- v3_angles_vector( localplayer.angles, v0 );
- m3x3_mulv( gate->transport, v0, v0 );
- v3_angles( v0, localplayer.angles );
-
- audio_lock();
- audio_oneshot( &audio_gate_pass, 1.0f, 0.0f );
- audio_unlock();
-}
-
-void player_apply_transport_to_cam( m4x3f transport )
-{
- /* Pre-emptively edit the camera matrices so that the motion vectors
- * are correct */
- m4x3f transport_i;
- m4x4f transport_4;
- m4x3_invert_affine( transport, transport_i );
- m4x3_expand( transport_i, transport_4 );
- m4x4_mul( g_render.cam.mtx.pv, transport_4, g_render.cam.mtx.pv );
- m4x4_mul( g_render.cam.mtx.v, transport_4, g_render.cam.mtx.v );
-
- /* we want the regular transform here no the inversion */
- m4x3_expand( transport, transport_4 );
- m4x4_mul( world_gates.cam.mtx.pv, transport_4, world_gates.cam.mtx.pv );
- m4x4_mul( world_gates.cam.mtx.v, transport_4, world_gates.cam.mtx.v );
-}
-
-void player__im_gui( ui_context *ctx )
-{
- if( !k_player_debug_info ) return;
-
- ui_rect box = {
- vg.window_x - 300,
- 0,
- 300,
- vg.window_y
- };
-
- ui_fill( ctx, box, (ui_colour(ctx, k_ui_bg)&0x00ffffff)|0x50000000 );
-
- g_player_debugger[0] = box[0];
- g_player_debugger[1] = 0;
- g_player_debugger[2] = 300;
- g_player_debugger[3] = 32;
-
- player__debugtext( ctx, 2, "instance #%u", world_static.active_instance );
-
- char buf[96];
- for( u32 i=0; i<k_world_max; i++ )
- {
- if( world_static.instance_addons[ i ] )
- addon_alias_uid( &world_static.instance_addons[ i ]->alias, buf );
- else
- strcpy( buf, "none" );
-
- player__debugtext( ctx, 1, "world #%u: %s", i, buf );
- }
-
- player__debugtext( ctx, 2, "director" );
- player__debugtext( ctx, 1, "activity: %s",
- (const char *[]){ [k_skaterift_menu] = "menu",
- [k_skaterift_replay] = "replay",
- [k_skaterift_ent_focus] = "ent_focus",
- [k_skaterift_default] = "default",
- } [skaterift.activity] );
- player__debugtext( ctx, 1, "time_rate: %.4f", skaterift.time_rate );
-
- player__debugtext( ctx, 2, "player" );
- player__debugtext( ctx, 1, "angles: " PRINTF_v3f( localplayer.cam.angles ) );
-
- if( player_subsystems[ localplayer.subsystem ]->im_gui )
- player_subsystems[ localplayer.subsystem ]->im_gui( ctx );
-
- skaterift_replay_debug_info( ctx );
-}
-
-void player__setpos( v3f pos )
-{
- v3_copy( pos, localplayer.rb.co );
- v3_zero( localplayer.rb.v );
- rb_update_matrices( &localplayer.rb );
-}
-
-void player__clean_refs(void)
-{
- replay_clear( &player_replay.local );
- gui_helper_clear();
-
- world_static.challenge_target = NULL;
- world_static.challenge_timer = 0.0f;
- world_static.active_trigger_volume_count = 0;
- world_static.last_use = 0.0;
- world_entity_exit_modal();
- world_entity_clear_focus();
-
- localplayer.boundary_hash ^= NETMSG_BOUNDARY_BIT;
-
- for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ ){
- world_instance *instance = &world_static.instances[i];
- if( instance->status == k_world_status_loaded ){
- world_routes_clear( instance );
- }
- }
-}
-
-void player__reset(void)
-{
- v3_zero( localplayer.rb.v );
- v3_zero( localplayer.rb.w );
-
- f32 l = v4_length( localplayer.rb.q );
- if( (l < 0.9f) || (l > 1.1f) )
- q_identity( localplayer.rb.q );
-
- rb_update_matrices( &localplayer.rb );
-
- localplayer.subsystem = k_player_subsystem_walk;
- player__walk_reset();
-
- localplayer.immobile = 0;
- localplayer.gate_waiting = NULL;
- localplayer.have_glider = 0;
- localplayer.glider_orphan = 0;
- localplayer.drowned = 0;
-
- v3_copy( localplayer.rb.co, localplayer.cam_control.tpv_lpf );
- player__clean_refs();
-}
-
-void player__spawn( ent_spawn *rp )
-{
- player__setpos( rp->transform.co );
- player__reset();
-}
-
-
-void player__kill(void)
-{
-}
-
-void player__begin_holdout( v3f offset )
-{
- memcpy( &localplayer.holdout_pose, &localplayer.pose,
- sizeof(localplayer.pose) );
- v3_copy( offset, localplayer.holdout_pose.root_co );
- localplayer.holdout_time = 1.0f;
-}
-
-void net_sfx_exchange( bitpack_ctx *ctx, struct net_sfx *sfx )
-{
- bitpack_bytes( ctx, 1, &sfx->system );
- bitpack_bytes( ctx, 1, &sfx->priority );
- bitpack_bytes( ctx, 1, &sfx->id );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &sfx->subframe );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &sfx->volume );
- bitpack_qv3f( ctx, 16, -1024.0f, 1024.0f, sfx->location );
-}
-
-void net_sfx_play( struct net_sfx *sfx )
-{
- if( sfx->system < k_player_subsystem_max ){
- struct player_subsystem_interface *sys = player_subsystems[sfx->system];
- if( sys->sfx_oneshot ){
- sys->sfx_oneshot( sfx->id, sfx->location, sfx->volume );
- }
- }
-};
-
-static struct net_sfx *find_lower_priority_sfx( struct net_sfx *buffer, u32 len,
- u32 *count, u8 priority ){
- struct net_sfx *p_sfx = NULL;
- if( *count < len ){
- p_sfx = &buffer[ *count ];
- *count = *count+1;
- }
- else {
- for( u32 i=0; i<len; i++ ){
- struct net_sfx *a = &buffer[i];
- if( a->priority < priority ){
- p_sfx = a;
- break;
- }
- }
- }
-
- return p_sfx;
-}
-
-void player__networked_sfx( u8 system, u8 priority, u8 id,
- v3f pos, f32 volume )
-{
- struct net_sfx sfx,
- *p_net = find_lower_priority_sfx(
- localplayer.sfx_buffer, 4,
- &localplayer.sfx_buffer_count, priority ),
- *p_replay = find_lower_priority_sfx(
- localplayer.local_sfx_buffer, 2,
- &localplayer.local_sfx_buffer_count, priority );
-
- sfx.id = id;
- sfx.priority = priority;
- sfx.volume = volume;
- v3_copy( pos, sfx.location );
- sfx.system = system;
-
- /* we only care about subframe in networked sfx. local replays run at a
- * high enough framerate. */
- f32 t = (vg.time_real - network_client.last_frame) / NETWORK_FRAMERATE;
- sfx.subframe = vg_clampf( t, 0.0f, 1.0f );
-
- if( p_net ) *p_net = sfx;
- if( p_replay ) *p_replay = sfx;
-
- net_sfx_play( &sfx );
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-
-struct player_cam_controller {
- enum camera_mode{
- k_cam_firstperson = 1,
- k_cam_thirdperson = 0
- }
- camera_mode;
- f32 camera_type_blend;
-
- v3f fpv_offset, /* expressed relative to rigidbody */
- tpv_offset,
- tpv_offset_extra,
- fpv_viewpoint, /* expressed relative to neck bone inverse final*/
- fpv_offset_smooth,
- fpv_viewpoint_smooth,
- tpv_offset_smooth,
- tpv_lpf,
- cam_velocity_smooth;
-};
-
-#include "player_common.h"
-#include "network_compression.h"
-#include "player_effects.h"
-#include "player_api.h"
-#include "player_ragdoll.h"
-#include "player_model.h"
-#include "player_render.h"
-
-struct player_subsystem_interface
-{
- void(*system_register)(void);
- void(*bind)(void);
- void(*pre_update)(void);
- void(*update)(void);
- void(*post_update)(void);
- void(*im_gui)( ui_context *ctx );
- void(*animate)(void);
- void(*pose)( void *animator, player_pose *pose );
- void(*effects)( void *animator, m4x3f *final_mtx, struct player_board *board,
- struct player_effects_data *effect_data );
- void(*post_animate)(void);
-
- void(*network_animator_exchange)( bitpack_ctx *ctx, void *data );
- void(*sfx_oneshot)( u8 id, v3f pos, f32 volume );
-
- void(*sfx_comp)(void *animator);
- void(*sfx_kill)(void);
-
- void *animator_data;
- u32 animator_size;
-
- const char *name;
-};
-
-#define PLAYER_REWIND_FRAMES 60*4
-#define RESET_MAX_TIME 45.0
-
-extern i32 k_invert_y;
-struct localplayer
-{
- /* transform definition */
- rigidbody rb;
- v3f angles;
-
- bool have_glider, glider_orphan, drowned;
-
- /*
- * Camera management
- * ---------------------------
- */
- vg_camera cam;
- struct player_cam_controller cam_control;
- f32 cam_trackshake;
-
- float cam_velocity_influence,
- cam_velocity_coefficient,
- cam_velocity_constant,
- cam_velocity_coefficient_smooth,
- cam_velocity_constant_smooth,
- cam_velocity_influence_smooth,
- cam_dist,
- cam_dist_smooth;
-
- v3f cam_land_punch, cam_land_punch_v;
- ent_gate *gate_waiting;
- int deferred_frame_record;
-
- int immobile;
-
- int rewinded_since_last_gate;
-
- /*
- * Network
- * --------------------------------------------------
- */
- u16 boundary_hash;
- struct net_sfx {
- u8 system, priority, id;
- f32 subframe, volume;
- v3f location;
- }
- sfx_buffer[4], /* large timeframe 1/10s; for networking */
- local_sfx_buffer[2]; /* per framerate 1/30s; for replay */
- u32 sfx_buffer_count,
- local_sfx_buffer_count;
-
- /*
- * Animation
- * --------------------------------------------------
- */
-
- struct player_ragdoll ragdoll;
- struct player_model fallback_model;
- struct player_board fallback_board;
-
- u16 board_view_slot, playermodel_view_slot;
-
- player_pose pose;
- player_pose holdout_pose;
- float holdout_time;
-
- m4x3f *final_mtx;
-
- /*
- * Subsystems
- * -------------------------------------------------
- */
-
- enum player_subsystem subsystem,
- observing_system;
-
- /*
- * Rendering
- */
- mdl_context skeleton_meta;
- struct skeleton skeleton;
-
- u8 id_hip,
- id_chest,
- id_ik_hand_l,
- id_ik_hand_r,
- id_ik_elbow_l,
- id_ik_elbow_r,
- id_head,
- id_foot_l,
- id_foot_r,
- id_ik_foot_l,
- id_ik_foot_r,
- id_ik_knee_l,
- id_ik_knee_r,
- id_wheel_l,
- id_wheel_r,
- id_board,
- id_eyes,
- id_world;
-
- u8 skeleton_mirror[32];
-
- struct player_effects_data effect_data;
-}
-extern localplayer;
-extern struct player_subsystem_interface *player_subsystems[];
-
-/*
- * Gameloop tables
- * ---------------------------------------------------------
- */
-
-void player_init(void);
-void player__debugtext( ui_context *ctx, int size, const char *fmt, ... );
-void player__use_mesh( glmesh *mesh );
-void player__use_model( u16 reg_id );
-
-void player__bind(void);
-void player__pre_update(void);
-void player__update(void);
-void player__post_update(void);
-
-void player__pass_gate( u32 id );
-void player__im_gui( ui_context *ctx );
-void player__setpos( v3f pos );
-void player__spawn( ent_spawn *rp );
-void player__clean_refs(void);
-void player__reset(void);
-void player__kill(void);
-void player__begin_holdout( v3f offset );
-
-int localplayer_cmd_respawn( int argc, const char *argv[] );
-void player_apply_transport_to_cam( m4x3f transport );
-
-void player__clear_sfx_buffer(void);
-void player__networked_sfx( u8 system, u8 priority, u8 id,
- v3f pos, f32 volume );
-void net_sfx_exchange( bitpack_ctx *ctx, struct net_sfx *sfx );
-void net_sfx_play( struct net_sfx *sfx );
+++ /dev/null
-#pragma once
-#include "model.h"
-
-typedef struct player_instance player_instance;
-typedef struct player_pose player_pose;
-
-struct player_pose{
- enum player_pose_type {
- k_player_pose_type_ik, /* regular IK animation */
- k_player_pose_type_fk_2,
- }
- type;
-
- v3f root_co;
- v4f root_q;
-
- mdl_keyframe keyframes[32];
-
- struct player_board_pose {
- f32 lean;
- }
- board;
-};
-
-enum player_subsystem{
- k_player_subsystem_walk = 0,
- k_player_subsystem_skate = 1,
- k_player_subsystem_dead = 2,
- k_player_subsystem_drive = 3,
- k_player_subsystem_basic_info = 4,
- k_player_subsystem_glide = 5,
- k_player_subsystem_max,
- k_player_subsystem_invalid = 255
-};
+++ /dev/null
-#include "player_basic_info.h"
-#include "network_compression.h"
-
-struct player_basic_info player_basic_info;
-struct player_subsystem_interface player_subsystem_basic_info =
-{
- .pose = player__basic_info_pose,
- .network_animator_exchange = player__basic_info_animator_exchange,
- .animator_data = &player_basic_info.animator,
- .animator_size = sizeof(player_basic_info.animator),
- .name = "Basic Info"
-};
-
-void player__basic_info_animator_exchange(bitpack_ctx *ctx, void *data)
-{
- struct player_basic_info_animator *animator = data;
- /* TODO: This range needs to be standardized in a common header */
- bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
-}
-
-void player__basic_info_pose( void *_animator, player_pose *pose )
-{
- struct player_basic_info_animator *animator = _animator;
- v3_copy( animator->root_co, pose->root_co );
- q_identity( pose->root_q );
- pose->type = k_player_pose_type_fk_2;
- pose->board.lean = 0.0f;
-
- for( int i=0; i<localplayer.skeleton.bone_count; i ++ ){
- v3_zero(pose->keyframes[i].co);
- q_identity(pose->keyframes[i].q);
- v3_fill(pose->keyframes[i].s,1.0f);
- }
-}
+++ /dev/null
-#pragma once
-#include "player.h"
-#include "player_api.h"
-
-struct player_basic_info
-{
- struct player_basic_info_animator
- {
- v3f root_co;
- }
- animator;
-}
-extern player_basic_info;
-extern struct player_subsystem_interface player_subsystem_basic_info;
-
-void player__basic_info_animator_exchange(bitpack_ctx *ctx, void *data);
-void player__basic_info_pose( void *_animator, player_pose *pose );
-
+++ /dev/null
-#include "ent_skateshop.h"
-#include "player.h"
-#include "input.h"
-#include "menu.h"
-#include "vg/vg_perlin.h"
-
-float player_get_heading_yaw(void)
-{
- v3f xz;
- q_mulv( localplayer.rb.q, (v3f){ 0.0f,0.0f,1.0f }, xz );
- return atan2f( xz[0], xz[2] );
-}
-
-static void player_camera_portal_correction(void)
-{
- if( localplayer.gate_waiting ){
- /* construct plane equation for reciever gate */
- v4f plane;
- q_mulv( localplayer.gate_waiting->q[1], (v3f){0.0f,0.0f,1.0f}, plane );
- plane[3] = v3_dot( plane, localplayer.gate_waiting->co[1] );
-
- f32 pol = v3_dot( localplayer.cam.pos, plane ) - plane[3];
-
- int cleared = (pol < 0.0f) || (pol > 5.0f);
-
- if( cleared ){
- vg_success( "Plane cleared\n" );
- }
-
- m4x3f inverse;
- m4x3_invert_affine( localplayer.gate_waiting->transport, inverse );
-
- /* de-transform camera and player back */
- v3f v0;
- m4x3_mulv( inverse, localplayer.cam.pos, localplayer.cam.pos );
- v3_angles_vector( localplayer.cam.angles, v0 );
- m3x3_mulv( inverse, v0, v0 );
- v3_angles( v0, localplayer.cam.angles );
-
- skeleton_apply_transform( &localplayer.skeleton, inverse,
- localplayer.final_mtx );
-
- /* record and re-put things again */
- if( cleared )
- {
- skaterift_record_frame( &player_replay.local, 1 );
- localplayer.deferred_frame_record = 1;
-
- skeleton_apply_transform( &localplayer.skeleton,
- localplayer.gate_waiting->transport,
- localplayer.final_mtx );
-
- m4x3_mulv( localplayer.gate_waiting->transport,
- localplayer.cam.pos, localplayer.cam.pos );
- v3_angles_vector( localplayer.cam.angles, v0 );
- m3x3_mulv( localplayer.gate_waiting->transport, v0, v0 );
- v3_angles( v0, localplayer.cam.angles );
- player_apply_transport_to_cam( localplayer.gate_waiting->transport );
- localplayer.gate_waiting = NULL;
- }
- }
-}
-
-void player__cam_iterate(void)
-{
- struct player_cam_controller *cc = &localplayer.cam_control;
-
- if( localplayer.subsystem == k_player_subsystem_walk ){
- v3_copy( (v3f){-0.1f,1.8f,0.0f}, cc->fpv_viewpoint );
- v3_copy( (v3f){0.0f,0.0f,0.0f}, cc->fpv_offset );
- v3_copy( (v3f){0.0f,1.8f,0.0f}, cc->tpv_offset );
- }
- else if( localplayer.subsystem == k_player_subsystem_glide ){
- v3_copy( (v3f){-0.15f,1.75f,0.0f}, cc->fpv_viewpoint );
- v3_copy( (v3f){0.0f,0.0f,0.0f}, cc->fpv_offset );
- v3_copy( (v3f){0.0f,-1.0f,0.0f}, cc->tpv_offset );
- v3_add( cc->tpv_offset_extra, cc->tpv_offset, cc->tpv_offset );
- }
- else{
- v3_copy( (v3f){-0.15f,1.75f,0.0f}, cc->fpv_viewpoint );
- v3_copy( (v3f){0.0f,0.0f,0.0f}, cc->fpv_offset );
-
- f32 h = vg_lerpf( 0.4f, 1.4f, k_cam_height );
- v3_copy( (v3f){0.0f,h,0.0f}, cc->tpv_offset );
- v3_add( cc->tpv_offset_extra, cc->tpv_offset, cc->tpv_offset );
- }
-
- localplayer.cam_velocity_constant = 0.25f;
- localplayer.cam_velocity_coefficient = 0.7f;
-
- /* lerping */
-
- if( localplayer.cam_dist_smooth == 0.0f ){
- localplayer.cam_dist_smooth = localplayer.cam_dist;
- }
- else {
- localplayer.cam_dist_smooth = vg_lerpf(
- localplayer.cam_dist_smooth,
- localplayer.cam_dist,
- vg.time_frame_delta * 8.0f );
- }
-
- localplayer.cam_velocity_influence_smooth = vg_lerpf(
- localplayer.cam_velocity_influence_smooth,
- localplayer.cam_velocity_influence,
- vg.time_frame_delta * 8.0f );
-
- localplayer.cam_velocity_coefficient_smooth = vg_lerpf(
- localplayer.cam_velocity_coefficient_smooth,
- localplayer.cam_velocity_coefficient,
- vg.time_frame_delta * 8.0f );
-
- localplayer.cam_velocity_constant_smooth = vg_lerpf(
- localplayer.cam_velocity_constant_smooth,
- localplayer.cam_velocity_constant,
- vg.time_frame_delta * 8.0f );
-
- enum camera_mode target_mode = cc->camera_mode;
-
- if( localplayer.subsystem == k_player_subsystem_dead )
- target_mode = k_cam_thirdperson;
-
- cc->camera_type_blend =
- vg_lerpf( cc->camera_type_blend,
- (target_mode == k_cam_firstperson)? 1.0f: 0.0f,
- 5.0f * vg.time_frame_delta );
-
- v3_lerp( cc->fpv_viewpoint_smooth, cc->fpv_viewpoint,
- vg.time_frame_delta * 8.0f, cc->fpv_viewpoint_smooth );
-
- v3_lerp( cc->fpv_offset_smooth, cc->fpv_offset,
- vg.time_frame_delta * 8.0f, cc->fpv_offset_smooth );
-
- v3_lerp( cc->tpv_offset_smooth, cc->tpv_offset,
- vg.time_frame_delta * 8.0f, cc->tpv_offset_smooth );
-
- /* fov -- simple blend */
- float fov_skate = vg_lerpf( 97.0f, 135.0f, k_fov ),
- fov_walk = vg_lerpf( 90.0f, 110.0f, k_fov );
-
- localplayer.cam.fov = vg_lerpf( fov_walk, fov_skate, cc->camera_type_blend );
-
- /*
- * first person camera
- */
-
- /* position */
- v3f fpv_pos, fpv_offset;
- m4x3_mulv( localplayer.final_mtx[ localplayer.id_head-1 ],
- cc->fpv_viewpoint_smooth, fpv_pos );
- m3x3_mulv( localplayer.rb.to_world, cc->fpv_offset_smooth, fpv_offset );
- v3_add( fpv_offset, fpv_pos, fpv_pos );
-
- /* angles */
- v3f velocity_angles;
- v3_lerp( cc->cam_velocity_smooth, localplayer.rb.v, 4.0f*vg.time_frame_delta,
- cc->cam_velocity_smooth );
-
- v3_angles( cc->cam_velocity_smooth, velocity_angles );
- velocity_angles[1] *= localplayer.cam_velocity_coefficient_smooth;
- velocity_angles[1] += localplayer.cam_velocity_constant_smooth;
-
- float inf_fpv = localplayer.cam_velocity_influence_smooth *
- cc->camera_type_blend,
- inf_tpv = localplayer.cam_velocity_influence_smooth *
- (1.0f-cc->camera_type_blend);
-
- vg_camera_lerp_angles( localplayer.angles, velocity_angles,
- inf_fpv,
- localplayer.angles );
-
- /*
- * Third person camera
- */
-
- /* no idea what this technique is called, it acts like clamped position based
- * on some derivative of where the final camera would end up ....
- *
- * it is done in the local basis then transformed back */
-
- v3f future;
- v3_muls( localplayer.rb.v, 0.4f*vg.time_frame_delta, future );
-
- v3f camera_follow_dir =
- { -sinf( localplayer.angles[0] ) * cosf( localplayer.angles[1] ),
- sinf( localplayer.angles[1] ),
- cosf( localplayer.angles[0] ) * cosf( localplayer.angles[1] ) };
-
- v3f v0;
- v3_sub( camera_follow_dir, future, v0 );
-
- v3f follow_angles;
- v3_copy( localplayer.angles, follow_angles );
- follow_angles[0] = atan2f( -v0[0], v0[2] );
- follow_angles[1] = 0.3f + velocity_angles[1] * 0.2f;
-
- float ya = atan2f( -cc->cam_velocity_smooth[1], 30.0f );
-
- follow_angles[1] = 0.3f + ya;
- vg_camera_lerp_angles( localplayer.angles, follow_angles,
- inf_tpv,
- localplayer.angles );
-
- v3f pco;
- v4f pq;
- rb_extrapolate( &localplayer.rb, pco, pq );
- v3_muladds( pco, localplayer.holdout_pose.root_co,
- localplayer.holdout_time, pco );
- v3_lerp( cc->tpv_lpf, pco, 20.0f*vg.time_frame_delta, cc->tpv_lpf );
-
- /* now move into world */
- v3f tpv_pos, tpv_offset, tpv_origin;
-
- /* TODO: whats up with CC and not CC but both sets of variables are doing
- * the same ideas just saved in different places?
- */
- /* origin */
- q_mulv( pq, cc->tpv_offset_smooth, tpv_origin );
- v3_add( tpv_origin, cc->tpv_lpf, tpv_origin );
-
- /* offset */
- v3_muls( camera_follow_dir, localplayer.cam_dist_smooth, tpv_offset );
- v3_muladds( tpv_offset, cc->cam_velocity_smooth, -0.025f, tpv_offset );
-
- v3_add( tpv_origin, tpv_offset, tpv_pos );
-
-#if 0
- if( localplayer.subsystem == k_player_subsystem_walk )
- {
- v3f fwd, right;
- v3_angles_vector( localplayer.angles, fwd );
- v3_cross( fwd, (v3f){0,1.001f,0}, right );
- right[1] = 0.0f;
- v3_normalize( right );
- v3_muladds( tpv_pos, right, 0.5f, tpv_pos );
- }
-#endif
-
- /*
- * Blend cameras
- */
- v3_lerp( tpv_pos, fpv_pos, cc->camera_type_blend, localplayer.cam.pos );
- v3_copy( localplayer.angles, localplayer.cam.angles );
-
- /* Camera shake */
- f32 speed = v3_length(localplayer.rb.v),
- strength = k_cam_shake_strength * speed;
- localplayer.cam_trackshake +=
- speed*k_cam_shake_trackspeed*vg.time_frame_delta;
-
- v2f rnd = {vg_perlin_fract_1d( localplayer.cam_trackshake, 1.0f, 4, 20 ),
- vg_perlin_fract_1d( localplayer.cam_trackshake, 1.0f, 4, 63 ) };
- v2_muladds( localplayer.cam.angles, rnd, strength, localplayer.cam.angles );
-
- v3f Fd, Fs, F;
- v3_muls( localplayer.cam_land_punch_v, -k_cam_damp, Fd );
- v3_muls( localplayer.cam_land_punch, -k_cam_spring, Fs );
- v3_muladds( localplayer.cam_land_punch, localplayer.cam_land_punch_v,
- vg.time_frame_delta, localplayer.cam_land_punch );
- v3_add( Fd, Fs, F );
- v3_muladds( localplayer.cam_land_punch_v, F, vg.time_frame_delta,
- localplayer.cam_land_punch_v );
- v3_add( localplayer.cam_land_punch, localplayer.cam.pos,
- localplayer.cam.pos );
-
- /* portal transitions */
- player_camera_portal_correction();
-}
-
-void player_look( v3f angles, float speed )
-{
- if( vg_ui.ctx.wants_mouse ) return;
-
- angles[2] = 0.0f;
-
- v2f mouse_input;
- v2_copy( vg.mouse_delta, mouse_input );
- if( k_invert_y ) mouse_input[1] *= -1.0f;
- v2_muladds( angles, mouse_input, 0.0025f * speed, angles );
-
- v2f jlook;
- joystick_state( k_srjoystick_look, jlook );
-
- angles[0] += jlook[0] * vg.time_frame_delta * 4.0f * speed;
- float input_y = jlook[1] * vg.time_frame_delta * 4.0f;
- if( k_invert_y ) input_y *= -1.0f;
-
- angles[1] += input_y * speed;
- angles[1] = vg_clampf( angles[1], -VG_PIf*0.5f, VG_PIf*0.5f );
-}
+++ /dev/null
-#pragma once
-#include "player_api.h"
-
-static float
- k_cam_spring = 20.0f,
- k_cam_damp = 6.7f,
- k_cam_punch = -1.0f,
- k_cam_shake_strength = 0.0001f,
- k_cam_shake_trackspeed = 0.2f;
-
-static i32 k_player_debug_info = 0;
-static ui_rect g_player_debugger;
-
-void player_look( v3f angles, float speed );
-void player__cam_iterate(void);
-f32 player_get_heading_yaw(void);
+++ /dev/null
-#include "skaterift.h"
-#include "player_dead.h"
-#include "gui.h"
-
-struct player_dead player_dead;
-struct player_subsystem_interface player_subsystem_dead = {
- .update = player__dead_update,
- .post_update = player__dead_post_update,
- .animate = player__dead_animate,
- .pose = player__dead_pose,
- .post_animate = player__dead_post_animate,
- .im_gui = player__dead_im_gui,
- .bind = player__dead_bind,
-
- .animator_data = &player_dead.animator,
- .animator_size = sizeof(player_dead.animator),
- .network_animator_exchange = player__dead_animator_exchange,
- .name = "Dead"
-};
-
-void player__dead_update(void)
-{
- player_ragdoll_iter( &localplayer.ragdoll );
-
- world_instance *world = world_current_instance();
- world_water_player_safe( world, 0.2f );
-}
-
-void player__dead_post_update(void){
- struct ragdoll_part *part =
- &localplayer.ragdoll.parts[ localplayer.id_hip-1 ];
- struct player_dead *d = &player_dead;
-
- v3f ext_co;
- v4f ext_q;
- rb_extrapolate( &part->rb, ext_co, ext_q );
-
- v3_lerp( d->co_lpf, ext_co, vg.time_frame_delta*4.0f, d->co_lpf );
- v3_lerp( d->v_lpf, part->rb.v, vg.time_frame_delta*4.0f, d->v_lpf );
- v3_lerp( d->w_lpf, part->rb.w, vg.time_frame_delta*4.0f, d->w_lpf );
-
- v3_copy( d->co_lpf, localplayer.rb.co );
- v3_zero( localplayer.rb.v );
- v3_zero( localplayer.rb.w );
-
- if( (skaterift.activity == k_skaterift_default) &&
- button_down(k_srbind_dead_respawn) ){
- ent_spawn *spawn = world_find_closest_spawn(
- world_current_instance(), localplayer.rb.co );
-
- if( spawn ){
- v3_copy( spawn->transform.co, localplayer.rb.co );
- player__reset();
- srinput.state = k_input_state_resume;
- }
- else {
- vg_error( "No spawns!\n" );
- }
- }
-}
-
-void player__dead_animate(void){
- struct player_dead *d = &player_dead;
- struct player_dead_animator *animator = &d->animator;
- struct player_ragdoll *rd = &localplayer.ragdoll;
- struct skeleton *sk = &localplayer.skeleton;
-
- m4x3f transforms[ 32 ];
-
- /* root transform */
- q_m3x3( localplayer.rb.q, transforms[0] );
- v3_copy( localplayer.rb.co, transforms[0][3] );
-
- v4_copy( localplayer.rb.q, animator->transforms[0].q );
- v3_copy( localplayer.rb.co, animator->transforms[0].co );
-
- /* colliders with bones transforms */
- for( int i=0; i<rd->part_count; i++ ){
- struct ragdoll_part *part = &rd->parts[i];
-
- m4x3f mtx;
-
- v4f q_int;
- v3f co_int;
-
- float substep = vg.time_fixed_extrapolate;
- v3_lerp( part->prev_co, part->rb.co, substep, co_int );
- q_nlerp( part->prev_q, part->rb.q, substep, q_int );
- v4_copy( part->rb.q, q_int );
-
- q_m3x3( q_int, mtx );
- v3_copy( co_int, mtx[3] );
-
- m4x3_mul( mtx, part->inv_collider_mtx, transforms[part->bone_id] );
- }
-
- /* bones without colliders transforms */
- for( u32 i=1; i<sk->bone_count; i++ ){
- struct skeleton_bone *sb = &sk->bones[i];
-
- if( sb->parent && !sb->collider ){
- v3f delta;
- v3_sub( sk->bones[i].co, sk->bones[sb->parent].co, delta );
-
- m4x3f posemtx;
- m3x3_identity( posemtx );
- v3_copy( delta, posemtx[3] );
-
- /* final matrix */
- m4x3_mul( transforms[sb->parent], posemtx, transforms[i] );
- }
- }
-
- /* measurements */
- for( u32 i=1; i<sk->bone_count; i++ ){
- struct skeleton_bone *sb = &sk->bones[i];
-
- v3_zero( animator->transforms[i].co );
- q_identity( animator->transforms[i].q );
-
- m4x3f parent, inverse, local;
- m3x3_identity( parent );
- v3_sub( sk->bones[i].co, sk->bones[sb->parent].co, parent[3] );
- m4x3_mul( transforms[ sb->parent ], parent, parent );
- m4x3_invert_affine( parent, inverse );
-
- v3f _s;
- m4x3_mul( inverse, transforms[i], local );
- m4x3_decompose( local, animator->transforms[i].co,
- animator->transforms[i].q, _s );
- }
-}
-
-void player__dead_pose( void *_animator, player_pose *pose )
-{
- struct player_dead_animator *animator = _animator;
- struct player_ragdoll *rd = &localplayer.ragdoll;
- struct skeleton *sk = &localplayer.skeleton;
-
- pose->type = k_player_pose_type_fk_2;
- pose->board.lean = 0.0f;
-
- v3_copy( animator->transforms[0].co, pose->root_co );
- v4_copy( animator->transforms[0].q, pose->root_q );
-
- for( u32 i=1; i<sk->bone_count; i++ ){
- v3_copy( animator->transforms[i].co, pose->keyframes[i-1].co );
- v4_copy( animator->transforms[i].q, pose->keyframes[i-1].q );
- v3_fill( pose->keyframes[i-1].s, 1.0f );
- }
-}
-
-void player__dead_post_animate(void)
-{
- localplayer.cam_velocity_influence = 1.0f;
-}
-
-void player__dead_im_gui( ui_context *ctx )
-{
-}
-
-void player__dead_transition( enum player_die_type type )
-{
- if( localplayer.subsystem == k_player_subsystem_dead )
- return;
-
- localplayer.subsystem = k_player_subsystem_dead;
- copy_localplayer_to_ragdoll( &localplayer.ragdoll, type );
-
- struct ragdoll_part *part =
- &localplayer.ragdoll.parts[ localplayer.id_hip-1 ];
- v3_copy( part->rb.co, player_dead.co_lpf );
- v3_copy( part->rb.v, player_dead.v_lpf );
- v3_copy( part->rb.w, player_dead.w_lpf );
-
- gui_helper_clear();
- vg_str str;
-
- struct gui_helper *h;
- if( (h = gui_new_helper(input_button_list[k_srbind_reset], &str) )){
- vg_strcat( &str, "Rewind" );
-
- if( world_static.active_instance == k_world_purpose_hub )
- h->greyed = 1;
- }
-
- if( gui_new_helper(input_button_list[k_srbind_dead_respawn], &str ))
- vg_strcat( &str, "Spawn" );
-}
-
-void player__dead_animator_exchange( bitpack_ctx *ctx, void *data )
-{
- struct player_dead_animator *animator = data;
-
- for( u32 i=0; i<localplayer.skeleton.bone_count; i ++ ){
- bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->transforms[i].co );
- bitpack_qquat( ctx, animator->transforms[i].q );
- }
-}
-
-void player__dead_bind(void)
-{
- struct skeleton *sk = &localplayer.skeleton;
- player_dead.anim_bail = skeleton_get_anim( sk, "pose_bail_ball" );
-}
+++ /dev/null
-#pragma once
-#include "player.h"
-#include "player_api.h"
-
-struct player_dead
-{
- v3f co_lpf, v_lpf, w_lpf;
-
- struct player_dead_animator{
- struct {
- v3f co;
- v4f q;
- }
- transforms[ 32 ];
- }
- animator;
-
- struct skeleton_anim *anim_bail;
-}
-extern player_dead;
-extern struct player_subsystem_interface player_subsystem_dead;
-
-void player__dead_update (void);
-void player__dead_post_update (void);
-void player__dead_animate (void);
-void player__dead_pose (void *animator, player_pose *pose);
-void player__dead_post_animate(void);
-void player__dead_im_gui ( ui_context *ctx );
-void player__dead_bind (void);
-void player__dead_transition ( enum player_die_type type );
-void player__dead_animator_exchange( bitpack_ctx *ctx, void *data );
-
+++ /dev/null
-#include "player_drive.h"
-#include "input.h"
-
-struct player_drive player_drive;
-struct player_subsystem_interface player_subsystem_drive =
-{
- .pre_update = player__drive_pre_update,
- .update = player__drive_update,
- .post_update = player__drive_post_update,
- .animate = player__drive_animate,
- .pose = player__drive_pose,
- .post_animate = player__drive_post_animate,
- .im_gui = player__drive_im_gui,
- .bind = player__drive_bind,
-
- .animator_data = NULL,
- .animator_size = 0,
- .name = "Drive"
-};
-
-void player__drive_pre_update(void)
-{
- drivable_vehicle *vehc = player_drive.vehicle;
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- vehc->steer = vg_lerpf( vehc->steer, steer[0] * 0.4f,
- vg.time_fixed_delta * 8.0f );
- vehc->drive = steer[1];
-}
-
-void player__drive_update(void){}
-
-void player__drive_post_update(void)
-{
- v3_copy( player_drive.vehicle->rb.co,localplayer.rb.co );
- v3_copy( player_drive.vehicle->rb.v, localplayer.rb.v );
- v4_copy( player_drive.vehicle->rb.q, localplayer.rb.q );
- v3_copy( player_drive.vehicle->rb.w, localplayer.rb.w );
-}
-
-void player__drive_animate(void){}
-
-void player__drive_pose( void *animator, player_pose *pose )
-{
- struct skeleton *sk = &localplayer.skeleton;
-
- skeleton_sample_anim( sk, player_drive.anim_drive, 0.0f, pose->keyframes );
- v3_copy( localplayer.rb.co, pose->root_co );
- v4_copy( localplayer.rb.q, pose->root_q );
-}
-
-void player__drive_post_animate(void)
-{
- if( localplayer.cam_control.camera_mode == k_cam_firstperson )
- localplayer.cam_velocity_influence = 0.0f;
- else
- localplayer.cam_velocity_influence = 1.0f;
-
- rigidbody *rb = &gzoomer.rb;
- float yaw = atan2f( -rb->to_world[2][0], rb->to_world[2][2] ),
- pitch = atan2f
- (
- -rb->to_world[2][1],
- sqrtf
- (
- rb->to_world[2][0]*rb->to_world[2][0] +
- rb->to_world[2][2]*rb->to_world[2][2]
- )
- );
-
- localplayer.angles[0] = yaw;
- localplayer.angles[1] = pitch;
-}
-
-void player__drive_im_gui( ui_context *ctx )
-{
- player__debugtext( ctx, 1, "Nothing here" );
-}
-
-void player__drive_bind(void)
-{
- struct skeleton *sk = &localplayer.skeleton;
- player_drive.vehicle = &gzoomer;
- player_drive.anim_drive = skeleton_get_anim( sk, "idle_cycle+y" );
-}
+++ /dev/null
-#pragma once
-#include "player.h"
-#include "vehicle.h"
-
-struct player_drive
-{
- drivable_vehicle *vehicle;
- struct skeleton_anim *anim_drive;
-}
-extern player_drive;
-extern struct player_subsystem_interface player_subsystem_drive;
-
-void player__drive_pre_update(void);
-void player__drive_update(void);
-void player__drive_post_update(void);
-void player__drive_animate(void);
-void player__drive_pose( void *animator, player_pose *pose );
-
-void player__drive_post_animate(void);
-void player__drive_im_gui( ui_context *ctx );
-void player__drive_bind(void);
+++ /dev/null
-#include "player.h"
-#include "player_effects.h"
-#include "player_render.h"
-#include "particle.h"
-
-void effect_blink_apply( effect_blink *ef, player_pose *pose, f32 dt )
-{
- if( ef->t < 0.0f ){
- ef->t = (1.0f-powf(vg_randf64(&vg.rand),4.0f))*4.0f;
- ef->l = 0.08f;
- }
-
- pose->keyframes[ localplayer.id_eyes-1 ].s[1] = ef->l > 0.0f? 0.2f: 1.0f;
-
- ef->t -= dt;
- ef->l -= dt;
-}
-
-void effect_spark_apply( effect_spark *ef, v3f co, v3f v, f32 dt )
-{
- if( !ef->colour ) return;
-
- if( ef->t < 0.0f ){
- ef->t += 0.05f+vg_randf64(&vg.rand)*0.1f;
-
- v3f dir;
- v3_copy( v, dir );
- dir[1] += 1.0f;
- f32 l = v3_length(dir);
- v3_muls( dir, 1.0f/l, dir );
-
- particle_spawn_cone( &particles_grind, co, dir, VG_PIf/2.0f, l,
- 4.0f, ef->colour );
- }
- else
- ef->t -= dt;
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-#include "player_render.h"
-
-typedef struct effect_blink effect_blink;
-typedef struct effect_spark effect_spark;
-
-struct effect_blink
-{
- f32 t, l;
-};
-
-struct effect_spark
-{
- u32 colour;
- f32 t;
-};
-
-void effect_blink_apply( effect_blink *ef, player_pose *pose, f32 dt );
-void effect_spark_apply( effect_spark *ef, v3f co, v3f v, f32 dt );
-
-struct player_effects_data
-{
- effect_blink blink;
- effect_spark spark, sand;
-};
+++ /dev/null
-#include "player_glide.h"
-#include "vg/vg_rigidbody.h"
-#include "scene_rigidbody.h"
-#include "shaders/model_board_view.h"
-#include "shaders/model_entity.h"
-#include "input.h"
-#include "skaterift.h"
-
-#include "player_dead.h"
-#include "player_skate.h"
-
-trail_system trails_glider[] = {
- {
- .width = 0.035f,
- .lifetime = 5.0f,
- .min_dist = 0.5f
- },
- {
- .width = 0.035f,
- .lifetime = 5.0f,
- .min_dist = 0.5f
- },
-};
-
-struct player_glide player_glide =
-{
- .parts = {
- {
- .co = { 1.0f, 0.5f, -1.0f },
- .euler = { VG_TAUf*0.25f, VG_TAUf*0.125f, 0.0f },
- .shape = k_rb_shape_capsule,
- .inf = { .h = 2.82842712475f, .r = 0.25f },
- },
- {
- .co = { -1.0f, 0.5f, -1.0f },
- .euler = { VG_TAUf*0.25f, -VG_TAUf*0.125f, 0.0f },
- .shape = k_rb_shape_capsule,
- .inf = { .h = 2.82842712475f, .r = 0.25f },
- },
- {
- .co = { 0.0f, 0.5f, 1.0f },
- .euler = { VG_TAUf*0.25f, VG_TAUf*0.25f, 0.0f },
- .shape = k_rb_shape_capsule,
- .inf = { .h = 6.0f, .r = 0.25f },
- },
- {
- .co = { 0.0f, -0.5f, 0.0f },
- .euler = { VG_TAUf*0.25f, VG_TAUf*0.25f, 0.0f },
- .shape = k_rb_shape_capsule,
- .inf = { .h = 2.0f, .r = 0.25f },
- .is_damage = 1,
- },
- }
-};
-
-struct player_subsystem_interface player_subsystem_glide =
-{
- .pre_update = player_glide_pre_update,
- .update = player_glide_update,
- .post_update = player_glide_post_update,
- .animate = player_glide_animate,
- .pose = player_glide_pose,
- .post_animate = player_glide_post_animate,
- .network_animator_exchange = player_glide_animator_exchange,
- .im_gui = player_glide_im_gui,
- .bind = player_glide_bind,
-
- .animator_data = &player_glide.animator,
- .animator_size = sizeof(player_glide.animator),
- .name = "Glide"
-};
-
-static f32 k_glide_steer = 2.0f,
- k_glide_cl = 0.04f,
- k_glide_cs = 0.02f,
- k_glide_drag = 0.0001f,
- k_glide_slip_yaw = 0.1f,
- k_glide_lift_pitch = 0.0f,
- k_glide_wing_orient = -0.1f,
- k_glide_balance = 1.0f;
-
-static i32 k_glide_pause = 0;
-
-void player_glide_pre_update(void)
-{
- if( button_down(k_srbind_use) ){
- localplayer.subsystem = k_player_subsystem_skate;
- localplayer.glider_orphan = 1;
-
- player_skate.state.activity = k_skate_activity_air;
- player_skate.state.activity_prev = k_skate_activity_air;
-
- q_mulv( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f}, player_skate.state.cog );
- v3_add( player_skate.state.cog, localplayer.rb.co,
- player_skate.state.cog );
- v3_copy( localplayer.rb.v, player_skate.state.cog_v );
-
- player__begin_holdout( (v3f){0.0f,0.0f,0.0f} );
- player__skate_reset_animator();
- player__skate_clear_mechanics();
- v3_copy( (v3f){0.0f,0.0f,0.0f}, player_skate.state.trick_euler );
-
- player__approximate_best_trajectory();
- }
-}
-
-static void massless_accel( rigidbody *rb, v3f delta, v3f impulse ){
- /* linear */
- v3_muladds( rb->v, impulse, vg.time_fixed_delta, rb->v );
-
- /* Angular velocity */
- v3f wa;
- v3_cross( delta, impulse, wa );
- v3_muladds( rb->w, wa, vg.time_fixed_delta, rb->w );
-}
-
-static void calculate_lift( v3f vl, f32 aoa_bias,
- v3f axis, v3f back, f32 power,
- v3f out_force ){
- v3f up;
- v3_cross( back, axis, up );
-
- v3f wind;
- v3_muladds( vl, axis, -v3_dot(axis,vl), wind );
-
- f32 windv2 = v3_length2(wind),
- aoa = atan2f( v3_dot( up, wind ), v3_dot( back, wind ) ) + aoa_bias,
- cl = aoa / VG_PIf,
- L = windv2 * cl * power;
-
- v3f lift_dir;
- v3_normalize( wind );
- v3_cross( wind, axis, lift_dir );
-
- /* this is where induced drag (from the flappy things) would go */
-
- v3_muls( lift_dir, L, out_force );
-}
-
-static void calculate_drag( v3f vl, f32 cd, v3f out_force ){
- f32 v2 = v3_length2( vl );
- v3f dir;
- v3_copy( vl, dir );
- v3_normalize( dir );
- v3_muls( vl, -cd*v2, out_force );
-}
-
-/*
- * Returns true if the bottom sphere is hit
- */
-bool glider_physics( v2f steer )
-{
- rigidbody *rb = &player_glide.rb;
-
- /* lift */
- v3f vl, wl;
- m3x3_mulv( rb->to_local, rb->v, vl );
- m3x3_mulv( rb->to_local, rb->w, wl );
-
- v3f F, Flift, Fslip, Fdrag, FslipW, FliftW;
-
- calculate_lift( vl, steer[1]*k_glide_steer,
- (v3f){1,0,0},
- (v3f){0,sinf(k_glide_wing_orient),cosf(k_glide_wing_orient)},
- k_glide_cl, Flift );
- v3_copy( Flift, player_glide.info_lift );
- v3_cross( (v3f){0,0,0}, Flift, FliftW );
-
- calculate_lift( vl, 0.0f,
- (v3f){0,1,0},(v3f){0,0,1},
- k_glide_cs, Fslip );
- v3_copy( Fslip, player_glide.info_slip );
- v3_cross( (v3f){0,k_glide_lift_pitch,k_glide_slip_yaw}, Fslip, FslipW );
-
- calculate_drag( vl, k_glide_drag, Fdrag );
- v3_copy( Fdrag, player_glide.info_drag );
-
- v3f balance = {0.0f,-k_glide_balance,0.0f};
- m3x3_mulv( rb->to_local, balance, balance );
-
- v3f Fw = {
- steer[1]*k_glide_steer - balance[2],
- 0.0f,
- -steer[0]*k_glide_steer + balance[0],
- };
-
- if( player_glide.ticker ){
- player_glide.ticker --;
- return 0;
- }
- player_glide.ticker += k_glide_pause;
-
- /* apply forces */
- v3_add( Flift, Fslip, F );
- v3_add( F, Fdrag, F );
-
- m3x3_mulv( rb->to_world, F, F );
- v3_muladds( rb->v, F, vg.time_fixed_delta, rb->v );
-
- v3_add( Fw, FslipW, Fw );
- v3_add( Fw, FliftW, Fw );
- m3x3_mulv( rb->to_world, Fw, Fw );
- v3_muladds( rb->w, Fw, vg.time_fixed_delta, rb->w );
-
-
- /*
- * collisions & constraints
- */
- world_instance *world = world_current_instance();
- rb_solver_reset();
-
- bool bottom_hit = 0;
-
- rigidbody _null = {0};
- _null.inv_mass = 0.0f;
- m3x3_zero( _null.iI );
- for( u32 i=0; i < VG_ARRAY_LEN(player_glide.parts); i ++ ){
- m4x3f mmdl;
- m4x3_mul( rb->to_world, player_glide.parts[i].mdl, mmdl );
-
- if( player_glide.parts[i].shape == k_rb_shape_capsule ){
- vg_line_capsule( mmdl,
- player_glide.parts[i].inf.r,
- player_glide.parts[i].inf.h,
- VG__BLACK );
- }
- else if( player_glide.parts[i].shape == k_rb_shape_sphere ){
- vg_line_sphere( mmdl, player_glide.parts[i].r, 0 );
- }
-
- if( rb_global_has_space() ){
- rb_ct *buf = rb_global_buffer();
-
- u32 l = 0;
-
- if( player_glide.parts[i].shape == k_rb_shape_capsule ){
- l = rb_capsule__scene( mmdl, &player_glide.parts[i].inf,
- NULL, world->geo_bh, buf,
- k_material_flag_ghosts );
- }
- else if( player_glide.parts[i].shape == k_rb_shape_sphere ){
- l = rb_sphere__scene( mmdl, player_glide.parts[i].r,
- NULL, world->geo_bh, buf,
- k_material_flag_ghosts );
- }
-
- if( player_glide.parts[i].is_damage && l ){
- bottom_hit = 1;
- }
-
- for( u32 j=0; j<l; j ++ ){
- buf[j].rba = rb;
- buf[j].rbb = &_null;
- }
-
- rb_contact_count += l;
- }
- }
-
- rb_presolve_contacts( rb_contact_buffer,
- vg.time_fixed_delta, rb_contact_count );
- for( u32 i=0; i<10; i ++ )
- rb_solve_contacts( rb_contact_buffer, rb_contact_count );
-
- rb_iter( rb );
- rb_update_matrices( rb );
-
- return bottom_hit;
-}
-
-void player_glide_update(void)
-{
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- if( glider_physics( steer ) )
- {
- vg_info( "player fell off due to glider hitting ground\n" );
- player__dead_transition( k_player_die_type_generic );
- localplayer.glider_orphan = 1;
- }
-
- if( !world_water_player_safe( world_current_instance(), 1.0f ) )
- return;
-}
-
-void player_glide_post_update(void)
-{
- v3_copy( player_glide.rb.co, localplayer.rb.co );
- v4_copy( player_glide.rb.q, localplayer.rb.q );
- v3_copy( player_glide.rb.v, localplayer.rb.v );
- v3_copy( player_glide.rb.w, localplayer.rb.w );
- rb_update_matrices( &localplayer.rb );
-}
-
-void player_glide_animate(void)
-{
- struct player_glide *g = &player_glide;
- struct player_glide_animator *animator = &g->animator;
- rb_extrapolate( &localplayer.rb, animator->root_co, animator->root_q );
-}
-
-void player_glide_pose( void *_animator, player_pose *pose )
-{
- struct skeleton *sk = &localplayer.skeleton;
- struct player_glide_animator *animator = _animator;
- pose->type = k_player_pose_type_ik;
- pose->board.lean = 0.0f;
-
- skeleton_sample_anim( sk, player_glide.anim_glide, 0.0f, pose->keyframes );
-
- v3f temp;
- q_mulv( animator->root_q, (v3f){0,-0.5f,0}, temp );
- v3_add( animator->root_co, temp, pose->root_co );
-
- v4_copy( animator->root_q, pose->root_q );
-}
-
-void player_glide_post_animate(void)
-{
- if( localplayer.cam_control.camera_mode == k_cam_firstperson )
- localplayer.cam_velocity_influence = 0.0f;
- else
- localplayer.cam_velocity_influence = 0.0f;
-
- v3f fwd;
- v3_muls( localplayer.rb.to_world[2], -1.0f, fwd );
- v3_angles( fwd, localplayer.angles );
-
- localplayer.cam_dist = 2.0f + v3_length( localplayer.rb.v )*0.2f;
-}
-
-void player_glide_animator_exchange( bitpack_ctx *ctx, void *data )
-{
- struct player_glide_animator *animator = data;
-
- bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
- bitpack_qquat( ctx, animator->root_q );
-}
-
-void player_glide_remote_animator_exchange( bitpack_ctx *ctx, void *data )
-{
- struct remote_glider_animator *animator = data;
-
- bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->s );
- bitpack_qquat( ctx, animator->root_q );
-}
-
-void player_glide_im_gui( ui_context *ctx )
-{
- player__debugtext( ctx, 1, " lift: %.2f %.2f %.2f",
- player_glide.info_lift[0],
- player_glide.info_lift[1],
- player_glide.info_lift[2] );
- player__debugtext( ctx, 1, " slip: %.2f %.2f %.2f",
- player_glide.info_slip[0],
- player_glide.info_slip[1],
- player_glide.info_slip[2] );
- player__debugtext( ctx, 1, " drag: %.2f %.2f %.2f",
- player_glide.info_drag[0],
- player_glide.info_drag[1],
- player_glide.info_drag[2] );
-}
-
-void player_glide_equip_glider(void)
-{
- if( !localplayer.have_glider ){
- localplayer.have_glider = 1;
- localplayer.glider_orphan = 0;
- player_glide.t = -1.0f;
- }
-}
-
-static int ccmd_player_glider_spawn( int argc, const char *argv[] ){
- if( vg_console.cheats ){
- player_glide_equip_glider();
- }
- else {
- vg_error( "Can't spawn without cheats enabled.\n" );
- }
- return 0;
-}
-
-void player_glide_bind(void)
-{
- u32 mask = VG_VAR_CHEAT|VG_VAR_PERSISTENT;
- VG_VAR_F32( k_glide_steer, flags=mask );
- VG_VAR_F32( k_glide_cl, flags=mask );
- VG_VAR_F32( k_glide_cs, flags=mask );
- VG_VAR_F32( k_glide_drag, flags=mask );
- VG_VAR_F32( k_glide_slip_yaw, flags=mask );
- VG_VAR_F32( k_glide_lift_pitch, flags=mask );
- VG_VAR_I32( k_glide_pause, flags=mask );
- VG_VAR_F32( k_glide_balance, flags=mask );
- VG_VAR_F32( k_glide_wing_orient, flags=mask );
-
- vg_console_reg_cmd( "spawn_glider", ccmd_player_glider_spawn, NULL );
-
- f32 mass = 0.0f,k_density = 8.0f;
- m3x3f I;
- m3x3_zero( I );
-
- for( u32 i=0; i<VG_ARRAY_LEN(player_glide.parts); i ++ ){
- /* create part transform matrix */
- v4f qp, qy, qr, q;
- q_axis_angle( qp, (v3f){1,0,0}, player_glide.parts[i].euler[0] );
- q_axis_angle( qy, (v3f){0,1,0}, player_glide.parts[i].euler[1] );
- q_axis_angle( qr, (v3f){0,0,1}, player_glide.parts[i].euler[2] );
-
- q_mul( qr, qy, q );
- q_mul( q, qp, q );
-
- q_m3x3( q, player_glide.parts[i].mdl );
- v3_copy( player_glide.parts[i].co, player_glide.parts[i].mdl[3] );
-
- /* add it to inertia model */
-
- if( player_glide.parts[i].shape == k_rb_shape_capsule ){
- f32 r = player_glide.parts[i].inf.r,
- h = player_glide.parts[i].inf.h,
- pv = vg_capsule_volume( r, h ),
- pm = pv * k_density;
-
- mass += pm;
-
- m3x3f pI;
- vg_capsule_inertia( r, h, pm, pI );
- vg_rotate_inertia( pI, player_glide.parts[i].mdl );
- vg_translate_inertia( pI, pm, player_glide.parts[i].co );
- m3x3_add( I, pI, I );
- }
- else if( player_glide.parts[i].shape == k_rb_shape_sphere ){
- f32 r = player_glide.parts[i].r,
- pv = vg_sphere_volume( r ),
- pm = pv * k_density;
-
- mass += pm;
- m3x3f pI;
- vg_sphere_inertia( r, pm, pI );
- vg_translate_inertia( pI, pm, player_glide.parts[i].co );
- m3x3_add( I, pI, I );
- }
- }
-
- /* set inverses */
- m3x3_inv( I, player_glide.rb.iI );
- player_glide.rb.inv_mass = 1.0f / mass;
-
- /* resources */
- struct skeleton *sk = &localplayer.skeleton;
- player_glide.anim_glide = skeleton_get_anim( sk, "glide_pose" );
-
- void *alloc = vg_mem.rtmemory;
- mdl_context *mdl = &player_glide.glider;
-
- mdl_open( mdl, "models/glider.mdl", alloc );
- mdl_load_metadata_block( mdl, alloc );
- mdl_async_full_load_std( mdl );
-
- /* load trail positions */
- mdl_array_ptr markers;
- MDL_LOAD_ARRAY( mdl, &markers, ent_marker, vg_mem.scratch );
- mdl_close( mdl );
-
- for( u32 i=0; i<mdl_arrcount( &markers ); i ++ )
- {
- ent_marker *marker = mdl_arritm( &markers, i );
- v3_copy( marker->transform.co,
- player_glide.trail_positions[ player_glide.trail_count ++ ] );
-
- if( player_glide.trail_count == VG_ARRAY_LEN(trails_glider) )
- break;
- }
-
- /* allocate effects */
- for( u32 i=0; i<VG_ARRAY_LEN(trails_glider); i ++ )
- {
- trail_alloc( &trails_glider[i], 200 );
- }
-}
-
-void player_glide_transition(void)
-{
- localplayer.subsystem = k_player_subsystem_glide;
- localplayer.have_glider = 0;
- world_static.challenge_target = NULL;
- world_static.challenge_timer = 0.0f;
- world_static.focused_entity = 0;
- world_static.last_use = 0.0;
- for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ ){
- world_instance *instance = &world_static.instances[i];
- if( instance->status == k_world_status_loaded ){
- world_routes_clear( instance );
- }
- }
-
- v3_copy( localplayer.rb.co, player_glide.rb.co );
-
- f32 dir = v3_dot( localplayer.rb.v, localplayer.rb.to_world[2] );
-
- if( dir > 0.0f ){
- v4f qyaw;
- q_axis_angle( qyaw, (v3f){0,1,0}, VG_TAUf*0.5f );
- q_mul( qyaw, localplayer.rb.q, player_glide.rb.q );
- q_normalize( player_glide.rb.q );
- }
- else
- v4_copy( localplayer.rb.q, player_glide.rb.q );
-
- v3_copy( localplayer.rb.v, player_glide.rb.v );
- v3_copy( localplayer.rb.w, player_glide.rb.w );
- rb_update_matrices( &player_glide.rb );
-
- player__begin_holdout( (v3f){0,0,0} );
-}
-
-void render_glider_model( vg_camera *cam, world_instance *world,
- m4x3f mmdl, enum board_shader shader )
-{
- u32 current_mat = 0xffffffff;
- glActiveTexture( GL_TEXTURE0 );
-
- mdl_context *mdl = &player_glide.glider;
- mesh_bind( &player_glide.glider.mesh );
-
- for( u32 i=0; i<mdl_arrcount(&mdl->meshs); i ++ )
- {
- mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
-
- m4x3f mmmdl;
- mdl_transform_m4x3( &mesh->transform, mmmdl );
- m4x3_mul( mmdl, mmmdl, mmmdl );
-
- if( shader == k_board_shader_player )
- shader_model_board_view_uMdl( mmmdl );
- else if( shader == k_board_shader_entity )
- {
- m4x4f m4mmmdl;
- m4x3_expand( mmmdl, m4mmmdl );
- m4x4_mul( cam->mtx_prev.pv, m4mmmdl, m4mmmdl );
-
- shader_model_entity_uMdl( mmmdl );
- shader_model_entity_uPvmPrev( m4mmmdl );
- }
-
- for( u32 j=0; j<mesh->submesh_count; j ++ )
- {
- mdl_submesh *sm = mdl_arritm( &mdl->submeshs, mesh->submesh_start+j );
- if( !sm->material_id )
- {
- vg_error( "Invalid material ID 0\n" );
- continue;
- }
-
- if( sm->material_id != current_mat )
- {
- mdl_material *mat = mdl_arritm( &mdl->materials,sm->material_id-1 );
- GLuint tex = vg.tex_missing;
-
- if( mat->shader == k_shader_standard )
- {
- struct shader_props_standard *props = mat->props.compiled;
-
- u32 index = props->tex_diffuse-1;
- mdl_texture *ptex = mdl_arritm( &mdl->textures, index );
- tex = ptex->glname;
- }
-
- glBindTexture( GL_TEXTURE_2D, tex );
- current_mat = sm->material_id;
- }
-
- mdl_draw_submesh( sm );
- }
- }
-}
-
-/*
- * TODO: more flexible way to call
- * - this depends on the current state, but we need to pass a struct in
- * that can hold that information instead so we can save it into
- * the replay
- */
-void player_glide_render( vg_camera *cam, world_instance *world,
- player_pose *pose )
-{
- if( !((localplayer.subsystem == k_player_subsystem_glide) ||
- (localplayer.observing_system == k_player_subsystem_glide) ||
- localplayer.have_glider ||
- localplayer.glider_orphan) )
- return;
-
- shader_model_board_view_use();
- shader_model_board_view_uTexMain( 0 );
- shader_model_board_view_uCamera( cam->transform[3] );
- shader_model_board_view_uPv( cam->mtx.pv );
- shader_model_board_view_uDepthMode(1);
- depth_compare_bind(
- shader_model_board_view_uTexSceneDepth,
- shader_model_board_view_uInverseRatioDepth,
- shader_model_board_view_uInverseRatioMain,
- cam );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_board_view );
-
- mdl_keyframe kf_res;
- if( localplayer.glider_orphan ){
- rb_extrapolate( &player_glide.rb, kf_res.co, kf_res.q );
- v3_fill( kf_res.s, 1.0f );
-
- v3f temp;
- q_mulv( kf_res.q, (v3f){0,-0.5f,0}, temp );
- v3_add( temp, kf_res.co, kf_res.co );
- }
- else {
- f32 target;
- if( localplayer.subsystem == k_player_subsystem_glide ) target = 1.0f;
- else target = 0.0f;
-
- /* TODO: TEMP */
- if( skaterift.activity != k_skaterift_replay )
- vg_slewf( &player_glide.t, target, vg.time_frame_delta * 4.0f );
-
- mdl_keyframe kf_backpack;
-
- struct skeleton *sk = &localplayer.skeleton;
- m4x3_mulv( localplayer.final_mtx[localplayer.id_chest ],
- sk->bones[localplayer.id_chest].co,
- kf_backpack.co );
-
- v4f qyaw, qpitch, qchest, q;
- q_axis_angle( qyaw, (v3f){0,1,0}, VG_TAUf*0.25f );
- q_axis_angle( qpitch, (v3f){1,0,0}, VG_TAUf*0.25f );
- m3x3_q( localplayer.final_mtx[ localplayer.id_chest ], qchest );
-
- q_mul( qyaw, qpitch, q );
- q_mul( qchest, q, kf_backpack.q );
- q_normalize( kf_backpack.q );
-
- f32 scale;
- if( player_glide.t <= 0.0f ){
- f32 st = player_glide.t + 1.0f,
- sst = vg_smoothstepf(st),
- isst= 1.0f - sst;
- scale = vg_lerpf( 0.0f, 0.2f, sst );
-
- v4f qspin;
- q_axis_angle( qspin, (v3f){0,0,1}, VG_TAUf * isst * 0.5f );
- q_mul( kf_backpack.q, qspin, kf_backpack.q );
- kf_backpack.co[1] += isst * 1.0f;
- v3_muladds( kf_backpack.co,
- localplayer.final_mtx[ localplayer.id_chest ][0],
- isst * 0.25f,
- kf_backpack.co );
- }
- else{
- scale = vg_lerpf( 0.2f, 1.0f, vg_smoothstepf(player_glide.t) );
- }
-
-
- v3_fill( kf_backpack.s, scale );
-
- v3_copy( pose->root_co, kf_res.co );
- v4_copy( pose->root_q, kf_res.q );
- v3_fill( kf_res.s, scale );
-
- f32 blend = vg_smoothstepf( vg_maxf( 0, player_glide.t ) );
- keyframe_lerp( &kf_backpack, &kf_res, blend, &kf_res );
- }
-
- m4x3f mmdl;
- q_m3x3( kf_res.q, mmdl );
- m3x3_scale( mmdl, kf_res.s );
- v3_copy( kf_res.co, mmdl[3] );
-
- render_glider_model( cam, world, mmdl, k_board_shader_player );
-
- /* totally FUCKED */
- v4_copy( kf_res.q, player_glide.remote_animator.root_q );
- v3_copy( kf_res.co, player_glide.remote_animator.root_co );
- player_glide.remote_animator.s = kf_res.s[0];
-}
-
-void player_glide_render_effects( vg_camera *cam )
-{
- v3f co, temp;
- v4f q;
- rb_extrapolate( &player_glide.rb, co, q );
- q_mulv( q, (v3f){0,-0.5f,0}, temp );
- v3_add( temp, co, co );
-
- f32 alpha = vg_maxf( (fabsf(player_glide.info_lift[2])-1.0f), 0.0f ) /18.0f;
-
- for( u32 i=0; i<player_glide.trail_count; i ++ ){
- v3f vvert;
- q_mulv( q, player_glide.trail_positions[i], vvert );
- v3_add( co, vvert, vvert );
-
- trail_system_update( &trails_glider[i], vg.time_delta, vvert,
- localplayer.rb.to_world[1], alpha );
-
- trail_system_prerender( &trails_glider[i] );
- trail_system_render( &trails_glider[i], &g_render.cam );
- }
-}
+++ /dev/null
-#pragma once
-#include "player.h"
-#include "player_render.h"
-#include "trail.h"
-
-struct player_glide
-{
- struct skeleton_anim *anim_glide;
-
- struct player_glide_animator
- {
- v3f root_co;
- v4f root_q;
- }
- animator;
-
- /* this sucks */
- struct remote_glider_animator
- {
- v3f root_co;
- v4f root_q;
- f32 s;
- }
- remote_animator;
-
- v3f info_lift,
- info_slip,
- info_drag;
-
- u32 ticker;
-
- rigidbody rb;
-
- f32 t;
-
- struct {
- v3f co, euler;
- m4x3f mdl;
-
- union {
- rb_capsule inf;
- f32 r;
- };
-
- enum rb_shape shape;
- bool is_damage;
- }
- parts[4];
-
- u32 trail_count;
- v3f trail_positions[2];
-
- mdl_context glider;
-}
-extern player_glide;
-extern struct player_subsystem_interface player_subsystem_glide;
-
-void player_glide_pre_update(void);
-void player_glide_update(void);
-void player_glide_post_update(void);
-void player_glide_animate(void);
-void player_glide_pose( void *animator, player_pose *pose );
-
-void player_glide_post_animate(void);
-void player_glide_im_gui( ui_context *ctx );
-void player_glide_bind(void);
-void player_glide_transition(void);
-bool glider_physics( v2f steer );
-void player_glide_animator_exchange( bitpack_ctx *ctx, void *data );
-void player_glide_render( vg_camera *cam, world_instance *world,
- player_pose *pose );
-void render_glider_model( vg_camera *cam, world_instance *world,
- m4x3f mmdl, enum board_shader shader );
-void player_glide_remote_animator_exchange( bitpack_ctx *ctx, void *data );
-void player_glide_equip_glider(void);
-void player_glide_render_effects( vg_camera *cam );
-
+++ /dev/null
-/*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-#include "model.h"
-#include "skeleton.h"
-#include "player_ragdoll.h"
-
-#include "shaders/model_character_view.h"
+++ /dev/null
-#pragma once
-#include "vg/vg_rigidbody.h"
-#include "vg/vg_rigidbody_collision.h"
-#include "vg/vg_rigidbody_constraints.h"
-#include "scene_rigidbody.h"
-
-#include "player.h"
-#include "player_dead.h"
-#include "audio.h"
-
-static float k_ragdoll_floatyiness = 20.0f,
- k_ragdoll_floatydrag = 1.0f,
- k_ragdoll_limit_scale = 1.0f,
- k_ragdoll_spring = 127.0f,
- k_ragdoll_dampening = 15.0f,
- k_ragdoll_correction = 0.5f,
- k_ragdoll_angular_drag = 0.08f,
- k_ragdoll_active_threshold = 5.0f;
-
-static int k_ragdoll_div = 1,
- ragdoll_frame = 0,
- k_ragdoll_debug_collider = 1,
- k_ragdoll_debug_constraints = 0;
-
-static int dev_ragdoll_saveload(int argc, const char *argv[]){
- if( argc != 2 ){
- vg_info( "Usage: ragdoll load/save filepath\n" );
- return 1;
- }
-
- if( !strcmp(argv[0],"save") ){
- FILE *fp = fopen( argv[1], "wb" );
- if( !fp ){
- vg_error( "Failed to open file\n" );
- return 1;
- }
- fwrite( &localplayer.ragdoll.parts,
- sizeof(localplayer.ragdoll.parts), 1, fp );
- fclose( fp );
- }
- else if( !strcmp(argv[0],"load") ){
- FILE *fp = fopen( argv[1], "rb" );
- if( !fp ){
- vg_error( "Failed to open file\n" );
- return 1;
- }
-
- fread( &localplayer.ragdoll.parts,
- sizeof(localplayer.ragdoll.parts), 1, fp );
- fclose( fp );
- }
- else {
- vg_error( "Unknown command: %s (options are: save,load)\n", argv[0] );
- return 1;
- }
-
- return 0;
-}
-
-void player_ragdoll_init(void)
-{
- VG_VAR_F32( k_ragdoll_active_threshold );
- VG_VAR_F32( k_ragdoll_angular_drag );
- VG_VAR_F32( k_ragdoll_correction );
- VG_VAR_F32( k_ragdoll_limit_scale );
- VG_VAR_F32( k_ragdoll_spring );
- VG_VAR_F32( k_ragdoll_dampening );
- VG_VAR_I32( k_ragdoll_div );
- VG_VAR_I32( k_ragdoll_debug_collider );
- VG_VAR_I32( k_ragdoll_debug_constraints );
- vg_console_reg_cmd( "ragdoll", dev_ragdoll_saveload, NULL );
-}
-
-void player_init_ragdoll_bone_collider( struct skeleton_bone *bone,
- struct ragdoll_part *rp )
-{
- f32 k_density = 8.0f,
- k_inertia_scale = 2.0f;
-
- m4x3_identity( rp->collider_mtx );
-
- rp->type = bone->collider;
- if( bone->collider == k_bone_collider_box ){
- v3f delta;
- v3_sub( bone->hitbox[1], bone->hitbox[0], delta );
- v3_muls( delta, 0.5f, delta );
- v3_add( bone->hitbox[0], delta, rp->collider_mtx[3] );
-
- v3_muls( delta, -1.0f, rp->inf.box[0] );
- v3_copy( delta, rp->inf.box[1] );
-
- rp->colour = 0xffcccccc;
-
- rb_setbody_box( &rp->rb, rp->inf.box, k_density, k_inertia_scale );
- }
- else if( bone->collider == k_bone_collider_capsule ){
- v3f v0, v1, tx, ty;
- v3_sub( bone->hitbox[1], bone->hitbox[0], v0 );
-
- int major_axis = 0;
- float largest = -1.0f;
-
- for( int i=0; i<3; i ++ ){
- if( fabsf( v0[i] ) > largest ){
- largest = fabsf( v0[i] );
- major_axis = i;
- }
- }
-
- v3_zero( v1 );
- v1[ major_axis ] = 1.0f;
- v3_tangent_basis( v1, tx, ty );
-
- rp->inf.capsule.r = (fabsf(v3_dot(tx,v0)) + fabsf(v3_dot(ty,v0))) * 0.25f;
- rp->inf.capsule.h = fabsf(v0[ major_axis ]);
-
- /* orientation */
- v3_muls( tx, -1.0f, rp->collider_mtx[0] );
- v3_muls( v1, -1.0f, rp->collider_mtx[1] );
- v3_muls( ty, -1.0f, rp->collider_mtx[2] );
- v3_add( bone->hitbox[0], bone->hitbox[1], rp->collider_mtx[3] );
- v3_muls( rp->collider_mtx[3], 0.5f, rp->collider_mtx[3] );
-
- rp->colour = 0xff000000 | (0xff << (major_axis*8));
-
- rb_setbody_capsule( &rp->rb, rp->inf.capsule.r, rp->inf.capsule.h,
- k_density, k_inertia_scale );
- }
- else{
- vg_warn( "type: %u\n", bone->collider );
- vg_fatal_error( "Invalid bone collider type" );
- }
-
- m4x3_invert_affine( rp->collider_mtx, rp->inv_collider_mtx );
-
- /* Position collider into rest */
- m3x3_q( rp->collider_mtx, rp->rb.q );
- v3_add( rp->collider_mtx[3], bone->co, rp->rb.co );
- v3_zero( rp->rb.v );
- v3_zero( rp->rb.w );
- rb_update_matrices( &rp->rb );
-}
-
-/*
- * Get parent index in the ragdoll
- */
-u32 ragdoll_bone_parent( struct player_ragdoll *rd, u32 bone_id )
-{
- for( u32 j=0; j<rd->part_count; j++ )
- if( rd->parts[ j ].bone_id == bone_id )
- return j;
-
- vg_fatal_error( "Referenced parent bone does not have a rigidbody" );
- return 0;
-}
-
-/*
- * Setup ragdoll colliders from skeleton
- */
-void setup_ragdoll_from_skeleton( struct skeleton *sk,
- struct player_ragdoll *rd )
-{
- rd->part_count = 0;
-
- if( !sk->collider_count )
- return;
-
- rd->position_constraints_count = 0;
- rd->cone_constraints_count = 0;
-
- for( u32 i=1; i<sk->bone_count; i ++ ){
- struct skeleton_bone *bone = &sk->bones[i];
-
- /*
- * Bones with colliders
- */
- if( !(bone->collider) )
- continue;
-
- if( rd->part_count > VG_ARRAY_LEN(rd->parts) )
- vg_fatal_error( "Playermodel has too many colliders" );
-
- u32 part_id = rd->part_count;
- rd->part_count ++;
-
- struct ragdoll_part *rp = &rd->parts[ part_id ];
- rp->bone_id = i;
- rp->parent = 0xffffffff;
-
- player_init_ragdoll_bone_collider( bone, rp );
-
- /*
- * Bones with collider and parent
- */
- if( !bone->parent )
- continue;
-
- rp->parent = ragdoll_bone_parent( rd, bone->parent );
-
- if( bone->orig_bone->flags & k_bone_flag_cone_constraint ){
- u32 conid = rd->position_constraints_count;
- rd->position_constraints_count ++;
-
- struct rb_constr_pos *c = &rd->position_constraints[ conid ];
-
- struct skeleton_bone *bj = &sk->bones[rp->bone_id];
- struct ragdoll_part *pp = &rd->parts[rp->parent];
- struct skeleton_bone *bp = &sk->bones[pp->bone_id];
-
- rd->constraint_associations[conid][0] = rp->parent;
- rd->constraint_associations[conid][1] = part_id;
-
- /* Convention: rba -- parent, rbb -- child */
- c->rba = &pp->rb;
- c->rbb = &rp->rb;
-
- v3f delta;
- v3_sub( bj->co, bp->co, delta );
- m4x3_mulv( rp->inv_collider_mtx, (v3f){0.0f,0.0f,0.0f}, c->lcb );
- m4x3_mulv( pp->inv_collider_mtx, delta, c->lca );
-
-
- mdl_bone *inf = bone->orig_bone;
-
- struct rb_constr_swingtwist *a =
- &rd->cone_constraints[ rd->cone_constraints_count ++ ];
-
- a->rba = &pp->rb;
- a->rbb = &rp->rb;
- a->conet = cosf( inf->conet )-0.0001f;
-
- /* Store constraint in local space vectors */
- m3x3_mulv( c->rba->to_local, inf->conevx, a->conevx );
- m3x3_mulv( c->rba->to_local, inf->conevy, a->conevy );
- m3x3_mulv( c->rbb->to_local, inf->coneva, a->coneva );
- v3_copy( c->lca, a->view_offset );
-
- v3_cross( inf->coneva, inf->conevy, a->conevxb );
- m3x3_mulv( c->rbb->to_local, a->conevxb, a->conevxb );
-
- v3_normalize( a->conevxb );
- v3_normalize( a->conevx );
- v3_normalize( a->conevy );
- v3_normalize( a->coneva );
-
- a->conevx[3] = v3_length( inf->conevx );
- a->conevy[3] = v3_length( inf->conevy );
-
- rp->use_limits = 1;
- }
- }
-}
-
-/*
- * Make avatar copy the ragdoll
- */
-void copy_ragdoll_pose_to_localplayer( struct player_ragdoll *rd )
-{
- for( int i=0; i<rd->part_count; i++ ){
- struct ragdoll_part *part = &rd->parts[i];
-
- m4x3f mtx;
-
- v4f q_int;
- v3f co_int;
-
- float substep = vg.time_fixed_extrapolate;
- v3_lerp( part->prev_co, part->rb.co, substep, co_int );
- q_nlerp( part->prev_q, part->rb.q, substep, q_int );
-
- q_m3x3( q_int, mtx );
- v3_copy( co_int, mtx[3] );
-
- m4x3_mul( mtx, part->inv_collider_mtx,
- localplayer.final_mtx[part->bone_id] );
- }
-
- for( u32 i=1; i<localplayer.skeleton.bone_count; i++ ){
- struct skeleton_bone *sb = &localplayer.skeleton.bones[i];
-
- if( sb->parent && !sb->collider ){
- v3f delta;
- v3_sub( localplayer.skeleton.bones[i].co,
- localplayer.skeleton.bones[sb->parent].co, delta );
-
- m4x3f posemtx;
- m3x3_identity( posemtx );
- v3_copy( delta, posemtx[3] );
-
- /* final matrix */
- m4x3_mul( localplayer.final_mtx[sb->parent], posemtx,
- localplayer.final_mtx[i] );
- }
- }
-
- skeleton_apply_inverses( &localplayer.skeleton, localplayer.final_mtx );
-}
-
-/*
- * Make the ragdoll copy the player model
- */
-void copy_localplayer_to_ragdoll( struct player_ragdoll *rd,
- enum player_die_type type )
-{
- v3f centroid;
-
- v3f *bone_mtx = localplayer.final_mtx[localplayer.id_hip];
- m4x3_mulv( bone_mtx,
- localplayer.skeleton.bones[localplayer.id_hip].co, centroid );
-
- for( int i=0; i<rd->part_count; i++ ){
- struct ragdoll_part *part = &rd->parts[i];
-
- v3f pos, offset;
- u32 bone = part->bone_id;
-
- v3f *bone_mtx = localplayer.final_mtx[bone];
-
- m4x3_mulv( bone_mtx, localplayer.skeleton.bones[bone].co, pos );
- m3x3_mulv( bone_mtx, part->collider_mtx[3], offset );
- v3_add( pos, offset, part->rb.co );
-
- m3x3f r;
- m3x3_mul( bone_mtx, part->collider_mtx, r );
- m3x3_q( r, part->rb.q );
-
- v3f ra, v;
- v3_sub( part->rb.co, centroid, ra );
- v3_cross( localplayer.rb.w, ra, v );
- v3_add( localplayer.rb.v, v, part->rb.v );
-
- if( type == k_player_die_type_feet ){
- if( (bone == localplayer.id_foot_l) ||
- (bone == localplayer.id_foot_r) ){
- v3_zero( part->rb.v );
- }
- }
-
- v3_copy( localplayer.rb.w, part->rb.w );
-
- v3_copy( part->rb.co, part->prev_co );
- v4_copy( part->rb.q, part->prev_q );
-
- rb_update_matrices( &part->rb );
- }
-}
-
-/*
- * Ragdoll physics step
- */
-void player_ragdoll_iter( struct player_ragdoll *rd )
-{
- world_instance *world = world_current_instance();
-
- int run_sim = 0;
- ragdoll_frame ++;
-
- if( ragdoll_frame >= k_ragdoll_div ){
- ragdoll_frame = 0;
- run_sim = 1;
- }
-
- rb_solver_reset();
-
- float contact_velocities[256];
-
- rigidbody _null = {0};
- _null.inv_mass = 0.0f;
- m3x3_zero( _null.iI );
-
- for( int i=0; i<rd->part_count; i ++ ){
- v4_copy( rd->parts[i].rb.q, rd->parts[i].prev_q );
- v3_copy( rd->parts[i].rb.co, rd->parts[i].prev_co );
-
- if( rb_global_has_space() ){
- rb_ct *buf = rb_global_buffer();
-
- int l;
-
- if( rd->parts[i].type == k_bone_collider_capsule ){
- l = rb_capsule__scene( rd->parts[i].rb.to_world,
- &rd->parts[i].inf.capsule,
- NULL, world->geo_bh, buf,
- k_material_flag_ghosts );
- }
- else if( rd->parts[i].type == k_bone_collider_box ){
- l = rb_box__scene( rd->parts[i].rb.to_world,
- rd->parts[i].inf.box,
- NULL, world->geo_bh, buf,
- k_material_flag_ghosts );
- }
- else continue;
-
- for( int j=0; j<l; j++ ){
- buf[j].rba = &rd->parts[i].rb;
- buf[j].rbb = &_null;
- }
-
- rb_contact_count += l;
- }
- }
-
- /*
- * self-collision
- */
- for( int i=0; i<rd->part_count-1; i ++ ){
- for( int j=i+1; j<rd->part_count; j ++ ){
- if( rd->parts[j].parent != i ){
- if( !rb_global_has_space() )
- break;
-
- if( rd->parts[j].type != k_bone_collider_capsule )
- continue;
-
- if( rd->parts[i].type != k_bone_collider_capsule )
- continue;
-
- rb_ct *buf = rb_global_buffer();
-
- int l = rb_capsule__capsule( rd->parts[i].rb.to_world,
- &rd->parts[i].inf.capsule,
- rd->parts[j].rb.to_world,
- &rd->parts[j].inf.capsule,
- buf );
-
- for( int k=0; k<l; k++ ){
- buf[k].rba = &rd->parts[i].rb;
- buf[k].rbb = &rd->parts[j].rb;
- }
-
- rb_contact_count += l;
- }
- }
- }
-
- if( localplayer.drowned )
- {
- for( int j=0; j<rd->part_count; j++ )
- {
- struct ragdoll_part *pj = &rd->parts[j];
-
- if( run_sim )
- {
- rb_effect_simple_bouyency( &pj->rb, world->water.plane,
- k_ragdoll_floatyiness,
- k_ragdoll_floatydrag );
- }
- }
- }
-
- /*
- * PRESOLVE
- */
- for( u32 i=0; i<rb_contact_count; i++ ){
- rb_ct *ct = &rb_contact_buffer[i];
-
- v3f rv, ra, rb;
- v3_sub( ct->co, ct->rba->co, ra );
- v3_sub( ct->co, ct->rbb->co, rb );
- rb_rcv( ct->rba, ct->rbb, ra, rb, rv );
- float vn = v3_dot( rv, ct->n );
-
- contact_velocities[i] = vn;
- }
-
- rb_presolve_contacts( rb_contact_buffer, vg.time_fixed_delta,
- rb_contact_count );
- rb_presolve_swingtwist_constraints( rd->cone_constraints,
- rd->cone_constraints_count );
-
- /*
- * DEBUG
- */
- if( k_ragdoll_debug_collider ){
- for( u32 i=0; i<rd->part_count; i ++ ){
- struct ragdoll_part *rp = &rd->parts[i];
-
- if( rp->type == k_bone_collider_capsule ){
- vg_line_capsule( rp->rb.to_world,
- rp->inf.capsule.r, rp->inf.capsule.h, rp->colour );
- }
- else if( rp->type == k_bone_collider_box ){
- vg_line_boxf_transformed( rp->rb.to_world,
- rp->inf.box, rp->colour );
- }
- }
- }
-
- if( k_ragdoll_debug_constraints ){
- rb_debug_position_constraints( rd->position_constraints,
- rd->position_constraints_count );
-
- rb_debug_swingtwist_constraints( rd->cone_constraints,
- rd->cone_constraints_count );
- }
-
- /*
- * SOLVE CONSTRAINTS & Integrate
- */
- if( run_sim ){
- /* the solver is not very quickly converging so... */
- for( int i=0; i<40; i++ ){
- if( i<20 ){
- rb_solve_contacts( rb_contact_buffer, rb_contact_count );
- rb_solve_swingtwist_constraints( rd->cone_constraints,
- rd->cone_constraints_count );
- rb_postsolve_swingtwist_constraints( rd->cone_constraints,
- rd->cone_constraints_count );
- }
- rb_solve_position_constraints( rd->position_constraints,
- rd->position_constraints_count );
- }
-
- rb_correct_position_constraints( rd->position_constraints,
- rd->position_constraints_count,
- k_ragdoll_correction * 0.5f );
- rb_correct_swingtwist_constraints( rd->cone_constraints,
- rd->cone_constraints_count,
- k_ragdoll_correction * 0.25f );
-
- for( int i=0; i<rd->part_count; i++ ){
- rb_iter( &rd->parts[i].rb );
-
- v3f w;
- v3_copy( rd->parts[i].rb.w, w );
- if( v3_length2( w ) > 0.00001f ){
- v3_normalize( w );
- v3_muladds( rd->parts[i].rb.w, w, -k_ragdoll_angular_drag,
- rd->parts[i].rb.w );
- }
- }
-
- for( int i=0; i<rd->part_count; i++ )
- rb_update_matrices( &rd->parts[i].rb );
- }
-
- rb_ct *stress = NULL;
- float max_stress = 1.0f;
-
- for( u32 i=0; i<rb_contact_count; i++ ){
- rb_ct *ct = &rb_contact_buffer[i];
-
- v3f rv, ra, rb;
- v3_sub( ct->co, ct->rba->co, ra );
- v3_sub( ct->co, ct->rbb->co, rb );
- rb_rcv( ct->rba, ct->rbb, ra, rb, rv );
- float vn = v3_dot( rv, ct->n );
-
- float s = fabsf(vn - contact_velocities[i]);
- if( s > max_stress ){
- stress = ct;
- max_stress = s;
- }
- }
-
- static u32 temp_filter = 0;
-
- /*
- * motorized joints
- */
- if( run_sim &&
- (v3_length2(player_dead.v_lpf)>(k_ragdoll_active_threshold*
- k_ragdoll_active_threshold)) ){
- mdl_keyframe anim[32];
- skeleton_sample_anim( &localplayer.skeleton, player_dead.anim_bail,
- 0.0f, anim );
-
- for( u32 i=0; i<rd->cone_constraints_count; i ++ ){
- rb_constr_swingtwist *st = &rd->cone_constraints[i];
- rb_constr_pos *pc = &rd->position_constraints[i];
-
- v3f va, vap;
-
- m3x3_mulv( st->rbb->to_world, st->coneva, va );
-
- /* calculate va as seen in rest position, from the perspective of the
- * parent object, mapped to pose world space using the parents
- * transform. thats our target */
-
- u32 id_p = rd->constraint_associations[i][0],
- id_a = rd->constraint_associations[i][1];
-
- struct ragdoll_part *pa = &rd->parts[ id_a ],
- *pp = &rd->parts[ id_p ];
-
- mdl_keyframe *kf = &anim[ pa->bone_id-1 ];
- m3x3_mulv( pa->collider_mtx, st->coneva, vap );
- q_mulv( kf->q, vap, vap );
-
- /* This could be a transfer function */
- m3x3_mulv( pp->inv_collider_mtx, vap, vap );
- m3x3_mulv( st->rba->to_world, vap, vap );
-
- f32 d = v3_dot( vap, va ),
- a = acosf( vg_clampf( d, -1.0f, 1.0f ) );
-
- v3f axis;
- v3_cross( vap, va, axis );
-
- f32 Fs = -a * k_ragdoll_spring,
- Fd = -v3_dot( st->rbb->w, axis ) * k_ragdoll_dampening,
- F = Fs+Fd;
-
- v3f torque;
- v3_muls( axis, F, torque );
- v3_muladds( st->rbb->w, torque, vg.time_fixed_delta, st->rbb->w );
-
- /* apply a adjustment to keep velocity at joint 0 */
-#if 0
- v3f wcb, vcb;
- m3x3_mulv( st->rbb->to_world, pc->lcb, wcb );
- v3_cross( torque, wcb, vcb );
- v3_muladds( st->rbb->v, vcb, vg.time_fixed_delta, st->rbb->v );
-#endif
- }
- }
-
- if( temp_filter ){
- temp_filter --;
- return;
- }
-
- if( stress ){
- temp_filter = 20;
- audio_lock();
- audio_oneshot_3d( &audio_hits[vg_randu32(&vg.rand)%5],
- stress->co, 20.0f, 1.0f );
- audio_unlock();
- }
-}
+++ /dev/null
-#pragma once
-
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved
- *
- * Ragdoll system
- */
-
-#include "player_api.h"
-#include "skeleton.h"
-#include "vg/vg_rigidbody.h"
-#include "vg/vg_rigidbody_constraints.h"
-
-struct player_ragdoll{
- struct ragdoll_part{
- u32 bone_id;
-
- /* Collider transform relative to bone */
- m4x3f collider_mtx,
- inv_collider_mtx;
-
- v4f prev_q;
- v3f prev_co;
-
- u32 use_limits;
- v3f limits[2];
-
- u32 parent;
- u32 colour;
-
- rigidbody rb;
- enum bone_collider type;
-
- union {
- rb_capsule capsule;
- boxf box;
- }
- inf;
- }
- parts[32];
- u32 part_count;
-
- rb_constr_pos position_constraints[32];
- u32 position_constraints_count;
-
- rb_constr_swingtwist cone_constraints[32];
- u32 cone_constraints_count;
-
- /* TODO: Fix duplicated data */
- u32 constraint_associations[32][2];
- int shoes[2];
-};
-
-enum player_die_type {
- k_player_die_type_generic,
- k_player_die_type_head,
- k_player_die_type_feet
-};
-
-void player_ragdoll_init(void);
-void player_init_ragdoll_bone_collider( struct skeleton_bone *bone,
- struct ragdoll_part *rp );
-u32 ragdoll_bone_parent( struct player_ragdoll *rd, u32 bone_id );
-void setup_ragdoll_from_skeleton( struct skeleton *sk,
- struct player_ragdoll *rd );
-void copy_ragdoll_pose_to_localplayer( struct player_ragdoll *rd );
-void copy_localplayer_to_ragdoll( struct player_ragdoll *rd,
- enum player_die_type type );
-
-void player_debug_ragdoll(void);
-void player_ragdoll_iter( struct player_ragdoll *rd );
+++ /dev/null
-#include "player_remote.h"
-#include "skeleton.h"
-#include "player_render.h"
-#include "player_api.h"
-#include "network_common.h"
-#include "addon.h"
-#include "font.h"
-#include "gui.h"
-#include "ent_miniworld.h"
-#include "ent_region.h"
-#include "shaders/model_entity.h"
-#include "vg/vg_steam_friends.h"
-
-struct global_netplayers netplayers;
-
-static i32 k_show_own_name = 0;
-
-static void player_remote_clear( struct network_player *player )
-{
- addon_cache_unwatch( k_addon_type_player, player->playermodel_view_slot );
- addon_cache_unwatch( k_addon_type_board, player->board_view_slot );
-
- memset( player, 0, sizeof(*player) );
- strcpy( player->username, "unknown" );
- player->subsystem = k_player_subsystem_invalid;
-}
-
-/*
- * re-attatches addon_reg pointers on the remote client if we have the same
- * world loaded.
- */
-static void relink_remote_player_worlds( u32 client_id ){
- struct network_player *player = &netplayers.list[client_id];
-
- addon_alias q[2];
- addon_uid_to_alias( player->items[k_netmsg_playeritem_world0], &q[0] );
- addon_uid_to_alias( player->items[k_netmsg_playeritem_world1], &q[1] );
-
- /*
- * currently in 10.23, the hub world will always be the same.
- * this might but probably wont change in the future
- */
- for( u32 i=0; i<k_world_max; i++ ){
- addon_reg *reg = world_static.instance_addons[ i ];
-
- player->world_match[i] = 0;
-
- if( reg ){
- if( addon_alias_eq( &q[i], &world_static.instance_addons[i]->alias ) )
- player->world_match[i] = 1;
- }
- }
-}
-
-/*
- * re-attatches addon_reg pointers on the remote client if we have the mod
- * installed locally.
- *
- * Run if local worlds change
- */
-void relink_all_remote_player_worlds(void)
-{
- for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ ){
- struct network_player *player = &netplayers.list[i];
- if( player->active )
- relink_remote_player_worlds(i);
- }
-}
-
-void player_remote_update_friendflags( struct network_player *remote )
-{
- ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
- remote->isfriend = SteamAPI_ISteamFriends_HasFriend( hSteamFriends,
- remote->steamid, k_EFriendFlagImmediate );
- remote->isblocked = SteamAPI_ISteamFriends_HasFriend( hSteamFriends,
- remote->steamid, k_EFriendFlagBlocked );
-}
-
-void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
-{
- netmsg_blank *tmp = msg->m_pData;
-
- if( tmp->inetmsg_id == k_inetmsg_playerjoin ){
- netmsg_playerjoin *playerjoin = msg->m_pData;
- if( !packet_minsize( msg, sizeof(*playerjoin) )) return;
-
- if( playerjoin->index < VG_ARRAY_LEN(netplayers.list) ){
- struct network_player *player = &netplayers.list[ playerjoin->index ];
- player_remote_clear( player );
- player->active = 1;
- player->steamid = playerjoin->steamid;
- player_remote_update_friendflags( player );
-
- /* TODO: interpret the uids */
- player->board_view_slot = 0;
- player->playermodel_view_slot = 0;
-
- struct interp_buffer *buf = &netplayers.interp_data[playerjoin->index];
- buf->t = -99999999.9;
- for( u32 i=0; i<VG_ARRAY_LEN(buf->frames); i ++ ){
- buf->frames[i].active = 0;
- }
-
- vg_info( "#%u joined friend: %d, blocked: %d\n",
- playerjoin->index, player->isfriend, player->isblocked );
- }
- else {
- vg_error( "inetmsg_playerjoin: player index out of range\n" );
- }
- }
- else if( tmp->inetmsg_id == k_inetmsg_playerleave ){
- netmsg_playerleave *playerleave = msg->m_pData;
- if( !packet_minsize( msg, sizeof(*playerleave) )) return;
-
- if( playerleave->index < VG_ARRAY_LEN(netplayers.list) ){
- struct network_player *player = &netplayers.list[ playerleave->index ];
- player_remote_clear( player );
- player->active = 0;
- vg_info( "player leave (%d)\n", playerleave->index );
- }
- else {
- vg_error( "inetmsg_playerleave: player index out of range\n" );
- }
- }
- else if( tmp->inetmsg_id == k_inetmsg_playerusername ){
- netmsg_playerusername *update = msg->m_pData;
- if( !packet_minsize( msg, sizeof(*update) )) return;
-
- if( update->index < VG_ARRAY_LEN(netplayers.list) ){
- struct network_player *player = &netplayers.list[ update->index ];
-
- network_msgstring( update->name, msg->m_cbSize, sizeof(*update),
- player->username, sizeof(player->username) );
-
- vg_info( "#%u changed username to: %s\n",
- update->index, player->username );
- }
- else {
- vg_error( "inetmsg_playerleave: player index out of range\n" );
- }
- }
- else if( tmp->inetmsg_id == k_inetmsg_playerframe ){
- u32 datasize = msg->m_cbSize - sizeof(netmsg_playerframe);
-
- if( datasize > sizeof(union interp_animdata) ){
- vg_error( "Player frame data exceeds animdata size\n" );
- return;
- }
-
- netmsg_playerframe *frame = msg->m_pData;
-
- if( frame->client >= VG_ARRAY_LEN(netplayers.list) ){
- vg_error( "inetmsg_playerframe: player index out of range\n" );
- return;
- }
-
- if( frame->subsystem >= k_player_subsystem_max ){
- vg_error( "inetmsg_playerframe: subsystem out of range\n" );
- return;
- }
-
- struct interp_buffer *ib = &netplayers.interp_data[ frame->client ];
- struct interp_frame *dest = NULL;
-
- f64 min_time = INFINITY;
- for( u32 i=0; i<VG_ARRAY_LEN(ib->frames); i++ ){
- struct interp_frame *ifr = &ib->frames[i];
-
- if( !ifr->active ){
- dest = ifr;
- break;
- }
-
- if( ifr->timestamp < min_time ){
- min_time = ifr->timestamp;
- dest = ifr;
- }
- }
-
- dest->active = 1;
- dest->subsystem = frame->subsystem;
- dest->flags = frame->flags;
-
- bitpack_ctx ctx = {
- .mode = k_bitpack_decompress,
- .buffer = frame->animdata,
- .buffer_len = datasize,
- .bytes = 0,
- };
-
- /* animation
- * -------------------------------------------------------------*/
-
- dest->timestamp = frame->timestamp;
- dest->boundary_hash = frame->boundary_hash;
-
- struct network_player *player = &netplayers.list[ frame->client ];
- struct player_subsystem_interface *sys =
- player_subsystems[ frame->subsystem ];
-
- memset( &dest->data, 0, sys->animator_size );
- if( sys->network_animator_exchange )
- sys->network_animator_exchange( &ctx, &dest->data );
- else
- bitpack_bytes( &ctx, sys->animator_size, sys->animator_data );
-
- /* sfx
- * -------------------------------------------------------------*/
-
- for( u32 i=0; i<frame->sound_effects; i ++ ){
- struct net_sfx sfx;
- net_sfx_exchange( &ctx, &sfx );
-
- f64 t = (frame->timestamp - NETWORK_FRAMERATE) +
- (sfx.subframe*NETWORK_FRAMERATE);
-
- f32 remaining = t - ib->t;
-
- if( remaining <= 0.0f )
- net_sfx_play( &sfx );
- else{
- struct net_sfx *dst = NULL;
-
- for( u32 j=0; j<NETWORK_SFX_QUEUE_LENGTH; j ++ ){
- struct net_sfx *sj = &netplayers.sfx_queue[j];
- if( sj->system == k_player_subsystem_invalid ){
- dst = sj;
- break;
- }
-
- if( sj->priority < sfx.priority )
- dst = sj;
- }
-
- *dst = sfx;
- dst->subframe = remaining;
- }
- }
-
- /* glider
- * -------------------------------------------------------------*/
-
- memset( &dest->data_glider, 0, sizeof(struct remote_glider_animator) );
- if( dest->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|
- NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){
- player_glide_remote_animator_exchange( &ctx, &dest->data_glider );
- }
-
- player->subsystem = frame->subsystem;
- player->down_bytes += msg->m_cbSize;
- }
- else if( tmp->inetmsg_id == k_inetmsg_playeritem ){
- netmsg_playeritem *item = msg->m_pData;
- if( !packet_minsize( msg, sizeof(*item)+1 )) return;
-
- if( item->client >= VG_ARRAY_LEN(netplayers.list) ){
- vg_error( "inetmsg_playerframe: player index out of range\n" );
- return;
- }
-
- if( item->type_index >= k_netmsg_playeritem_max ){
- vg_warn( "Client #%d invalid equip type %u\n",
- (i32)item->client, (u32)item->type_index );
- return;
- }
-
- vg_info( "Client #%d equiped: [%s] %s\n",
- item->client,
- (const char *[]){[k_netmsg_playeritem_board]="board",
- [k_netmsg_playeritem_player]="player",
- [k_netmsg_playeritem_world0]="world0",
- [k_netmsg_playeritem_world1]="world1"
- }[item->type_index], item->uid );
-
- struct network_player *player = &netplayers.list[ item->client ];
- char *uid = player->items[ item->type_index ];
-
- network_msgstring( item->uid, msg->m_cbSize, sizeof(*item),
- uid, ADDON_UID_MAX );
-
- if( item->type_index == k_netmsg_playeritem_board ){
- addon_cache_unwatch( k_addon_type_board, player->board_view_slot );
- player->board_view_slot =
- addon_cache_create_viewer_from_uid( k_addon_type_board, uid );
- }
- else if( item->type_index == k_netmsg_playeritem_player ){
- addon_cache_unwatch( k_addon_type_player,
- player->playermodel_view_slot );
- player->playermodel_view_slot =
- addon_cache_create_viewer_from_uid( k_addon_type_player, uid );
- }
- else if( (item->type_index == k_netmsg_playeritem_world0) ||
- (item->type_index == k_netmsg_playeritem_world1) ){
- relink_remote_player_worlds( item->client );
- }
- }
- else if( tmp->inetmsg_id == k_inetmsg_chat ){
- netmsg_chat *chat = msg->m_pData;
-
- struct network_player *player = &netplayers.list[ chat->client ];
- network_msgstring( chat->msg, msg->m_cbSize, sizeof(netmsg_chat),
- player->chat, NETWORK_MAX_CHAT );
- player->chat_time = vg.time_real;
- vg_info( "[%d]: %s\n", chat->client, player->chat );
- }
- else if( tmp->inetmsg_id == k_inetmsg_region ){
- netmsg_region *region = msg->m_pData;
- struct network_player *player = &netplayers.list[ region->client ];
-
- u32 l = network_msgstring(
- region->loc, msg->m_cbSize, sizeof(netmsg_region),
- player->region, NETWORK_REGION_MAX );
- player->region_flags = region->flags;
-
- if( l )
- player->region_flags |= k_ent_region_flag_hasname;
-
- player->effect_data.spark.colour = region_spark_colour(region->flags);
- }
-}
-
-/*
- * Write localplayer pose to network
- */
-void remote_player_send_playerframe(void)
-{
- u8 sysid = localplayer.subsystem;
- if( sysid >= k_player_subsystem_max ) return;
-
- struct player_subsystem_interface *sys = player_subsystems[sysid];
-
- if( sys->animator_size ){
- u32 max_buf_size = sys->animator_size + sizeof(localplayer.sfx_buffer),
- base_size = sizeof(struct netmsg_playerframe),
- max_packet = base_size + max_buf_size;
-
- netmsg_playerframe *frame = alloca( max_packet );
- frame->inetmsg_id = k_inetmsg_playerframe;
- frame->client = 0xff;
- frame->subsystem = localplayer.subsystem;
- frame->flags = world_static.active_instance;
-
- bitpack_ctx ctx = {
- .mode = k_bitpack_compress,
- .buffer = frame->animdata,
- .buffer_len = max_buf_size,
- .bytes = 0
- };
-
- /* animation
- * -----------------------------------------------*/
-
- frame->timestamp = vg.time_real;
- frame->boundary_hash = localplayer.boundary_hash;
- if( sys->network_animator_exchange )
- sys->network_animator_exchange( &ctx, sys->animator_data );
- else
- bitpack_bytes( &ctx, sys->animator_size, sys->animator_data );
-
- /* sfx
- * ---------------------------------------------*/
-
- frame->sound_effects = localplayer.sfx_buffer_count;
- for( u32 i=0; i<localplayer.sfx_buffer_count; i ++ )
- net_sfx_exchange( &ctx, &localplayer.sfx_buffer[i] );
-
- /* glider
- * -------------------------------------------------------------*/
-
- if( localplayer.have_glider ||
- (localplayer.subsystem == k_player_subsystem_glide) ) {
- frame->flags |= NETMSG_PLAYERFRAME_HAVE_GLIDER;
- }
-
- if( localplayer.glider_orphan )
- frame->flags |= NETMSG_PLAYERFRAME_GLIDER_ORPHAN;
-
- if( frame->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|
- NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){
- player_glide_remote_animator_exchange( &ctx,
- &player_glide.remote_animator );
- }
-
- /* ------- */
-
- u32 wire_size = base_size + ctx.bytes;
- netplayers.up_bytes += wire_size;
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, network_client.remote,
- frame, wire_size,
- k_nSteamNetworkingSend_Unreliable, NULL );
- }
-}
-
-/*
- * Updates network traffic stats
- */
-void remote_player_debug_update(void)
-{
- if( (vg.time_real - netplayers.last_data_measurement) > 1.0 ){
- netplayers.last_data_measurement = vg.time_real;
- u32 total_down = 0;
-
- for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ ){
- struct network_player *player = &netplayers.list[i];
- if( player->active ){
- total_down += player->down_bytes;
- player->down_kbs = ((f32)player->down_bytes)/1024.0f;
- player->down_bytes = 0;
- }
- }
-
- netplayers.down_kbs = ((f32)total_down)/1024.0f;
- netplayers.up_kbs = ((f32)netplayers.up_bytes)/1024.0f;
- netplayers.up_bytes = 0;
- }
-}
-
-/*
- * Debugging information
- */
-void remote_player_network_imgui( ui_context *ctx, m4x4f pv )
-{
- if( network_client.user_intent == k_server_intent_online )
- {
- if( !(steam_ready &&
- (network_client.state == k_ESteamNetworkingConnectionState_Connected)))
- {
- char buf[128];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
- u32 fg = 0;
- network_status_string( &str, &fg );
- ui_text( ctx, (ui_rect){ vg.window_x - 200, 0, 200, 48 }, buf, 1,
- k_ui_align_middle_center, fg );
- }
- }
-
- if( !network_client.network_info )
- return;
-
- ui_rect panel = { (vg.window_x / 2) - 200, 0, 400, 600 };
- ui_fill( ctx, panel, (ui_colour(ctx, k_ui_bg)&0x00ffffff)|0x50000000 );
-
- ctx->font = &vgf_default_title;
- ui_info( ctx, panel, "Network" );
- ctx->font = &vgf_default_large;
- ui_info( ctx, panel, "Status" );
- ctx->font = &vgf_default_small;
-
- char buf[512];
- const char *netstatus = "PROGRAMMING ERROR";
-
- struct { enum ESteamNetworkingConnectionState state; const char *str; }
- states[] = {
- { k_ESteamNetworkingConnectionState_None, "None" },
- { k_ESteamNetworkingConnectionState_Connecting,
- (const char *[]){"Connecting -",
- "Connecting /",
- "Connecting |",
- "Connecting \\",
- }[(u32)(vg.time_real/0.25) & 0x3 ] },
- { k_ESteamNetworkingConnectionState_FindingRoute, "Finding Route" },
- { k_ESteamNetworkingConnectionState_Connected, "Connected" },
- { k_ESteamNetworkingConnectionState_ClosedByPeer, "Closed by peer" },
- { k_ESteamNetworkingConnectionState_ProblemDetectedLocally,
- "Problem Detected Locally" },
- { k_ESteamNetworkingConnectionState_FinWait, "Fin Wait" },
- { k_ESteamNetworkingConnectionState_Linger, "Linger" },
- { k_ESteamNetworkingConnectionState_Dead, "Dead" }
- };
- for( u32 i=0; i<VG_ARRAY_LEN(states); i ++ )
- {
- if( states[i].state == network_client.state )
- {
- netstatus = states[i].str;
- break;
- }
- }
- snprintf( buf, 512, "Network: %s", netstatus );
- ui_info( ctx, panel, buf );
- ui_info( ctx, panel, "---------------------" );
-
- if( network_client.state == k_ESteamNetworkingConnectionState_Connected )
- {
- ui_info( ctx, panel, "#-1: localplayer" );
-
- snprintf( buf, 512, "U%.3f/D%.3fkbs",
- netplayers.up_kbs, netplayers.down_kbs );
- ui_info( ctx, panel, buf );
-
- for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ )
- {
- struct network_player *player = &netplayers.list[i];
- if( player->active )
- {
- const char *sysname = "invalid";
-
- if( player->subsystem < k_player_subsystem_max )
- {
- sysname = player_subsystems[ player->subsystem ]->name;
- }
- snprintf( buf, 512, "#%u: %s [%s] D%.1fkbs",
- i, player->username, sysname, player->down_kbs );
- ui_info( ctx, panel, buf );
- }
- }
- }
- else
- {
- ui_info( ctx, panel, "offline" );
- }
-}
-
-static void remote_player_effect( struct network_player *player,
- player_pose *final_pose ){
- /* effects */
-}
-
-/*
- * write the remote players final_mtx
- */
-static void pose_remote_player( u32 index,
- struct interp_frame *f0,
- struct interp_frame *f1 ){
-
- struct network_player *player = &netplayers.list[ index ];
-
- struct interp_buffer *buf = &netplayers.interp_data[ index ];
- struct skeleton *sk = &localplayer.skeleton;
- m4x3f *final_mtx = &netplayers.final_mtx[ sk->bone_count*index ];
- struct player_board_pose *board_pose = &netplayers.board_poses[index];
-
- struct player_subsystem_interface *sys0 = player_subsystems[f0->subsystem],
- *sys1 = NULL;
-
- struct player_board *board =
- addon_cache_item_if_loaded( k_addon_type_board, player->board_view_slot );
-
- player_pose pose0, pose1, posed;
- sys0->pose( &f0->data, &pose0 );
-
- u8 instance_id = 0;
-
- f32 t = 0.0f;
-
- if( f1 ){
- t = (buf->t - f0->timestamp) / (f1->timestamp - f0->timestamp);
- t = vg_clampf( t, 0.0f, 1.0f );
-
- sys1 = player_subsystems[f1->subsystem];
- sys1->pose( &f1->data, &pose1 );
-
- u16 bounds = f0->boundary_hash^f1->boundary_hash;
-
- if( bounds & NETMSG_BOUNDARY_BIT )
- t = 1.0f;
-
- if( bounds & NETMSG_GATE_BOUNDARY_BIT ){
- /* TODO: Extra work retransforming the root_co, instance_id.. etc */
- t = 1.0f;
- }
-
- instance_id = f1->flags & NETMSG_PLAYERFRAME_INSTANCE_ID;
- lerp_player_pose( &pose0, &pose1, t, &posed );
- effect_blink_apply( &player->effect_data.blink, &posed, vg.time_delta );
-
- apply_full_skeleton_pose( sk, &posed, final_mtx );
-
- if( t < 0.5f ){
- if( sys0->effects )
- sys0->effects( &f0->data, final_mtx, board, &player->effect_data );
- }
- else{
- if( sys1->effects )
- sys1->effects( &f1->data, final_mtx, board, &player->effect_data );
- }
-
- memcpy( board_pose, &posed.board, sizeof(*board_pose) );
- }
- else {
- instance_id = f0->flags & NETMSG_PLAYERFRAME_INSTANCE_ID;
- effect_blink_apply( &player->effect_data.blink, &pose0, vg.time_delta );
- apply_full_skeleton_pose( sk, &pose0, final_mtx );
- if( sys0->effects )
- sys0->effects( &f0->data, final_mtx, board, &player->effect_data );
- memcpy( board_pose, &pose0.board, sizeof(*board_pose) );
- }
-
- if( f0->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|
- NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){
- player->render_glider = 1;
-
- v3f co;
- v4f q;
- f32 s;
-
- if( f1 ){
- v3_lerp( f0->data_glider.root_co, f1->data_glider.root_co, t, co );
- q_nlerp( f0->data_glider.root_q, f1->data_glider.root_q, t, q );
- s = vg_lerpf( f0->data_glider.s, f1->data_glider.s, t );
- }
- else {
- v3_copy( f0->data_glider.root_co, co );
- v4_copy( f0->data_glider.root_q, q );
- s = f0->data_glider.s;
- }
-
- v3f *mtx = netplayers.glider_mtx[ index ];
- q_m3x3( q, mtx );
- m3x3_scalef( mtx, s );
- v3_copy( co, mtx[3] );
- }
- else
- player->render_glider = 0;
-
- if( player->world_match[ instance_id ] )
- player->active_world = &world_static.instances[ instance_id ];
-}
-
-/*
- * animate remote player and store in final_mtx
- */
-void animate_remote_player( u32 index )
-{
- /*
- * Trys to keep the cursor inside the buffer
- */
- f64 min_time = -999999999.9,
- max_time = 999999999.9,
- abs_max_time = -999999999.9;
-
- struct interp_frame *minframe = NULL,
- *maxframe = NULL,
- *abs_max_frame = NULL;
-
- struct interp_buffer *buf = &netplayers.interp_data[index];
- for( u32 i=0; i<VG_ARRAY_LEN(buf->frames); i ++ ){
- struct interp_frame *ifr = &buf->frames[i];
-
- if( ifr->active ){
- if( (ifr->timestamp > min_time) && (ifr->timestamp < buf->t) ){
- min_time = ifr->timestamp;
- minframe = ifr;
- }
-
- if( (ifr->timestamp < max_time) && (ifr->timestamp > buf->t) ){
- max_time = ifr->timestamp;
- maxframe = ifr;
- }
-
- if( ifr->timestamp > abs_max_time ){
- abs_max_time = ifr->timestamp;
- abs_max_frame = ifr;
- }
- }
- }
-
- struct network_player *player = &netplayers.list[ index ];
- if( player->active != 2 )
- player->active_world = NULL;
-
- if( minframe && maxframe ){
- pose_remote_player( index, minframe, maxframe );
- buf->t += vg.time_frame_delta;
- }
- else {
- buf->t = abs_max_time - 0.25;
-
- if( abs_max_frame )
- pose_remote_player( index, abs_max_frame, NULL );
- else
- return;
- }
-}
-
-/*
- * Update full final_mtx for all remote players
- */
-void animate_remote_players(void)
-{
- for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i ++ ){
- struct network_player *player = &netplayers.list[i];
- if( !player->active ) continue;
-
- animate_remote_player( i );
- }
-}
-
-/*
- * Draw remote players
- */
-void render_remote_players( world_instance *world, vg_camera *cam )
-{
- u32 draw_list[ NETWORK_MAX_PLAYERS ],
- draw_list_count = 0,
- gliders = 0;
-
- for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
- struct network_player *player = &netplayers.list[i];
- if( !player->active || player->isblocked ) continue;
- if( player->active_world != world ) continue;
-
-#if 0
- if( !player->isfriend &&
- (world-world_static.instances == k_world_purpose_hub)) continue;
-#endif
-
- draw_list[draw_list_count ++] = i;
-
- if( player->render_glider )
- gliders ++;
- }
-
- struct skeleton *sk = &localplayer.skeleton;
-
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
-
- for( u32 j=0; j<draw_list_count; j ++ ){
- u32 index = draw_list[j];
-
- struct network_player *player = &netplayers.list[index];
- m4x3f *final_mtx = &netplayers.final_mtx[ sk->bone_count*index ];
-
- struct player_model *model =
- addon_cache_item_if_loaded( k_addon_type_player,
- player->playermodel_view_slot );
-
- if( !model ) model = &localplayer.fallback_model;
- render_playermodel( cam, world, 0, model, sk, final_mtx );
-
- struct player_board *board =
- addon_cache_item_if_loaded( k_addon_type_board,
- player->board_view_slot );
- render_board( cam, world, board, final_mtx[localplayer.id_board],
- &netplayers.board_poses[ index ], k_board_shader_player );
- }
-
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
-
- if( !gliders )
- return;
-
- /* TODO: we really, really only need to do all this once. at some point
- * PLEASE figure out a good place to do this once per frame!
- */
-
- shader_model_entity_use();
- shader_model_entity_uTexMain( 0 );
- shader_model_entity_uCamera( cam->transform[3] );
- shader_model_entity_uPv( cam->mtx.pv );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_entity );
-
- for( u32 j=0; j<draw_list_count; j ++ ){
- u32 index = draw_list[j];
-
- struct network_player *player = &netplayers.list[index];
- if( !player->render_glider ) continue;
-
- if( player->render_glider ){
- v3f *glider_mtx = netplayers.glider_mtx[ index ];
- render_glider_model( cam, world, glider_mtx, k_board_shader_entity );
- }
- }
-}
-
-static int remote_players_randomize( int argc, const char *argv[] ){
- for( int i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
- struct network_player *player = &netplayers.list[i];
-
- player->active = (vg_randu32(&vg.rand) & 0x1)? 2: 0;
- player->isfriend = vg_randu32(&vg.rand) & vg_randu32(&vg.rand) & 0x1;
- player->isblocked = vg_randu32(&vg.rand) &
- vg_randu32(&vg.rand) &
- vg_randu32(&vg.rand) & 0x1;
- player->world_match[ 0 ] = vg_randu32(&vg.rand) & 0x1;
- player->world_match[ 1 ] = 0;
-
- if( player->world_match[0] )
- player->active_world = &world_static.instances[0];
- else
- player->active_world = NULL;
-
- for( int i=0; i<sizeof(player->username)-1; i ++ ){
- player->username[i] = 'a' + (vg_randu32(&vg.rand) % 30);
- player->username[i+1] = '\0';
-
- if( (vg_randu32(&vg.rand) % 8) == 3 )
- break;
- }
-
- for( int i=0; i<3; i ++ ){
- player->medals[i] = vg_randu32(&vg.rand) % 3;
- }
-
- v3f pos;
-
- vg_rand_sphere( &vg.rand, pos );
- v3_muladds( localplayer.rb.co, pos, 100.0f,
- netplayers.final_mtx[ i*localplayer.skeleton.bone_count][3] );
- }
-
- return 0;
-}
-
-void remote_players_init(void)
-{
- vg_console_reg_cmd( "add_test_players", remote_players_randomize, NULL );
- vg_console_reg_var( "k_show_own_name", &k_show_own_name,
- k_var_dtype_i32, 0 );
- for( u32 i=0; i<NETWORK_SFX_QUEUE_LENGTH; i ++ ){
- netplayers.sfx_queue[i].system = k_player_subsystem_invalid;
- }
-}
-
-void remote_sfx_pre_update(void)
-{
- for( u32 i=0; i<NETWORK_SFX_QUEUE_LENGTH; i ++ ){
- struct net_sfx *si = &netplayers.sfx_queue[i];
-
- if( si->system != k_player_subsystem_invalid ){
- si->subframe -= vg.time_frame_delta;
- if( si->subframe <= 0.0f ){
- net_sfx_play( si );
- si->system = k_player_subsystem_invalid;
- }
- }
- }
-}
-
-/*
- * animator->root_co of remote player
- */
-static void remote_player_position( int id, v3f out_co ){
- struct skeleton *sk = &localplayer.skeleton;
- m4x3f *final_mtx = &netplayers.final_mtx[ sk->bone_count*id ];
- v3_copy( final_mtx[0][3], out_co );
-}
-
-enum remote_player_gui_type {
- k_remote_player_gui_type_stranger,
- k_remote_player_gui_type_friend,
- k_remote_player_gui_type_you,
-};
-
-/*
- * Given players' root_co, get the screen point where we should draw tag info.
- */
-static int player_tag_position( m4x4f pv, v3f root_co, ui_point out_point ){
- v4f wpos;
- v3_copy( root_co, wpos );
- wpos[1] += 2.0f;
- wpos[3] = 1.0f;
-
- m4x4_mulv( pv, wpos, wpos );
-
- if( wpos[3] > 0.0f ){
- v2_muls( wpos, (1.0f/wpos[3]) * 0.5f, wpos );
- v2_add( wpos, (v2f){ 0.5f, 0.5f }, wpos );
-
- float k_max = 32000.0f;
-
- out_point[0] = vg_clampf(wpos[0] * vg.window_x, -k_max, k_max );
- out_point[1] = vg_clampf((1.0f-wpos[1]) * vg.window_y, -k_max, k_max );
- return 1;
- }
- else
- return 0;
-}
-
-/*
- * Draw chat box relative to the root tag position on the screen
- */
-static void chat_box( ui_context *ctx,
- ui_point tag_root, f64 time, const char *message )
-{
- if( (vg.time_real - time) > 15.0 )
- return;
-
- ui_rect wr;
- wr[2] = ui_text_line_width( ctx, message ) + 8;
- wr[3] = ctx->font->ch + 2;
- wr[0] = tag_root[0]-(wr[2]/2);
- wr[1] = tag_root[1] - wr[3] - 8;
-
- ui_fill( ctx, wr, ui_opacity( ui_colour(ctx, k_ui_bg), 0.23f ) );
- ui_text( ctx, wr, message, 1, k_ui_align_middle_center, 0 );
-}
-
-/*
- * Draw full imgui for remote player
- */
-static void remote_player_nametag( ui_context *ctx, ui_point tag_root,
- struct network_player *player )
-{
- ctx->font = &vgf_default_large;
-
- ui_rect wr;
- wr[2] = VG_MAX( ui_text_line_width( ctx, player->username ), 140 ) + 8;
- wr[3] = 32;
- wr[0] = tag_root[0]-(wr[2]/2);
- wr[1] = tag_root[1]-(wr[3]/2);
-
- ui_fill( ctx, wr, ui_opacity( ui_colour(ctx, k_ui_bg), 0.23f ) );
- ui_text( ctx, wr, player->username, 1, k_ui_align_middle_center, 0 );
- ctx->font = &vgf_default_small;
-
- /* medals */
- int cols = 0;
- for( int i=0; i<3; i ++ )
- if( player->medals[i] )
- cols ++;
-
- char buf[32];
- vg_str str;
-
- if( cols )
- {
- f32 w = (f32)wr[2] / (f32)cols;
- cols = 0;
-
- for( int i=0; i<3; i ++ )
- {
- if( player->medals[i] )
- {
- ui_rect col = { wr[0] + (f32)cols*w, wr[1] + wr[3],
- w, ctx->font->ch };
-
- vg_strnull( &str, buf, 32 );
-#if 0
- vg_strcatch( &str, (char)k_SRglyph_vg_circle );
-#endif
- vg_strcati32( &str, player->medals[i] );
-
- ui_text( ctx, col, buf, 1, k_ui_align_middle_center,
- ui_colour( ctx, (enum ui_scheme_colour[]){
- k_ui_yellow, k_ui_gray, k_ui_orange }[i] ) );
-
- cols ++;
- }
- }
- }
-}
-
-static void remote_player_world_gui( ui_context *ctx, m4x4f pv, v3f root_co,
- struct network_player *player ){
- ui_point tag_root;
- if( !player_tag_position( pv, root_co, tag_root ) )
- return;
-
- if( player )
- {
- remote_player_nametag( ctx, tag_root, player );
- chat_box( ctx, tag_root, player->chat_time, player->chat );
- }
- else
- {
- if( netplayers.chatting )
- chat_box( ctx, tag_root, vg.time_real, netplayers.chat_buffer );
- else
- {
- chat_box( ctx, tag_root,
- netplayers.chat_time, netplayers.chat_message );
- }
- }
-}
-
-static void remote_player_gui_info( ui_context *ctx, ui_rect box,
- const char *username,
- const char *activity,
- enum remote_player_gui_type type,
- int in_world ){
-
- f32 opacity = in_world? 0.6f: 0.3f;
-
- if( type == k_remote_player_gui_type_you )
- ui_fill( ctx, box, ui_opacity( 0xff555555, opacity ) );
- else
- ui_fill( ctx, box, ui_opacity( 0xff000000, opacity ) );
-
- if( type == k_remote_player_gui_type_friend )
- ui_outline( ctx, box, -1, ui_opacity( 0xff00c4f0, opacity ), 0 );
-
- ui_rect top, bottom;
- ui_split_ratio( box, k_ui_axis_h, 0.6666f, 1, top, bottom );
-
- u32 fg;
-
- if( type == k_remote_player_gui_type_friend )
- fg = ui_colour( ctx, k_ui_yellow + (in_world? k_ui_brighter: 0) );
- else
- fg = ui_colour( ctx, in_world? k_ui_fg: k_ui_fg+4 );
-
- ctx->font = &vgf_default_large;
- ui_text( ctx, top, username, 1, k_ui_align_middle_center, fg );
- ctx->font = &vgf_default_small;
- ui_text( ctx, bottom, activity, 1, k_ui_align_middle_center, fg );
-}
-
-void remote_players_imgui_lobby( ui_context *ctx )
-{
- if( network_client.user_intent == k_server_intent_online ){
- if( !(steam_ready &&
- (network_client.state == k_ESteamNetworkingConnectionState_Connected)))
- {
- return;
- }
- }
-
- ui_px y = 50, width = 200, height = 42, gap = 2,
- x = vg.window_x - width;
-
- ctx->font = &vgf_default_large;
- ui_text( ctx, (ui_rect){ x, 0, width, height },
- "In World", 1, k_ui_align_middle_center, 0 );
- ctx->font = &vgf_default_small;
-
-
- ui_rect us = { x, y, width, height };
- /* FIXME: your location */
- remote_player_gui_info( ctx, us, steam_username_at_startup, "you",
- k_remote_player_gui_type_you, 1 );
- y += height + gap;
-
- for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ )
- {
- struct network_player *player = &netplayers.list[i];
- if( !player->active || player->isblocked ) continue;
-
- int in_same_world = player->active_world == world_current_instance();
- if( !player->isfriend && !in_same_world )
- continue;
-
- const char *location = in_same_world? "": "another world";
- if( player->region_flags & k_ent_region_flag_hasname ){
- location = player->region;
- }
-
- ui_rect box = { x, y, width, height };
- remote_player_gui_info( ctx, box, player->username, location,
- player->isfriend, in_same_world );
- y += height + gap;
- }
-}
-
-void remote_players_imgui_world( ui_context *ctx,
- world_instance *world, m4x4f pv,
- f32 max_dist, int geo_cull )
-{
- ui_flush( ctx, k_ui_shader_colour, NULL );
-
- for( u32 i=0; i<NETWORK_MAX_PLAYERS; i++ )
- {
- struct network_player *player = &netplayers.list[i];
- if( player->active )
- {
- v3f co;
- remote_player_position( i, co );
-
- if( !player->active_world )
- continue;
- if( !player->isfriend &&
- (world-world_static.instances == k_world_purpose_hub)) continue;
-
- /* their in our active subworld */
- if( player->active_world != world )
- {
- m4x3_mulv( global_miniworld.mmdl, co, co );
- co[1] -= 2.0f; /* HACK lol */
- }
-
- f32 d2 = v3_dist2( co, localplayer.rb.co );
-
- if( d2 > (max_dist*max_dist) )
- continue;
-
- f32 dist = sqrtf(d2);
- f32 opacity = 0.95f * sqrtf(((max_dist-dist)/max_dist));
-
- if( geo_cull ){
- ray_hit hit;
- hit.dist = dist;
-
- v3f dir;
- v3_sub( co, g_render.cam.pos, dir );
- v3_normalize( dir );
-
- if( ray_world( world, g_render.cam.pos, dir, &hit,
- k_material_flag_ghosts ) ){
- opacity *= 0.5f;
- }
- }
-
- player->opacity = vg_lerpf( player->opacity, opacity,
- vg.time_frame_delta * 2.0f );
-
- remote_player_world_gui( ctx, pv, co, player );
-
- vg_ui.colour[3] = player->opacity;
- ui_flush( ctx, k_ui_shader_colour, NULL );
- }
- }
-
- vg_ui.colour[3] = 1.0f;
- remote_player_world_gui( ctx, pv, localplayer.rb.co, NULL );
- ui_flush( ctx, k_ui_shader_colour, NULL );
-}
-
-static void chat_escape( ui_context *ctx )
-{
- netplayers.chatting = -1;
-}
-
-static void chat_enter( ui_context *ctx, char *buf, u32 len ){
- vg_strncpy( buf, netplayers.chat_message, NETWORK_MAX_CHAT,
- k_strncpy_always_add_null );
- netplayers.chatting = -1;
- netplayers.chat_time = vg.time_real;
- chat_send_message( buf );
-}
-
-void remote_players_chat_imgui( ui_context *ctx )
-{
- if( netplayers.chatting == 1 )
- {
- ui_rect box = { 0, 0, 400, 40 },
- window = { 0, 0, vg.window_x, vg.window_y };
- ui_rect_center( window, box );
-
- struct ui_textbox_callbacks callbacks =
- {
- .enter = chat_enter,
- .escape = chat_escape
- };
-
- ui_textbox( ctx, box, NULL,
- netplayers.chat_buffer, NETWORK_MAX_CHAT, 1,
- UI_TEXTBOX_AUTOFOCUS, &callbacks );
- }
- else
- {
- if( netplayers.chatting == -1 )
- {
- netplayers.chatting = 0;
- srinput.state = k_input_state_resume;
- }
- else
- {
- if( (skaterift.activity == k_skaterift_default) &&
- button_down( k_srbind_chat ) ){
- netplayers.chatting = 1;
- netplayers.chat_buffer[0] = '\0';
- srinput.state = k_input_state_pause;
- }
- }
- }
-}
+++ /dev/null
-#pragma once
-#include "player.h"
-#include "network.h"
-#include "network_common.h"
-#include "player_render.h"
-#include "player_effects.h"
-#include "player_api.h"
-
-#include "player_skate.h"
-#include "player_walk.h"
-#include "player_dead.h"
-#include "player_basic_info.h"
-#include "player_glide.h"
-
-#define NETWORK_SFX_QUEUE_LENGTH 12
-
-struct global_netplayers
-{
- struct network_player {
- int active, isfriend, isblocked;
- u64 steamid;
- u16 board_view_slot, playermodel_view_slot;
- enum player_subsystem subsystem;
-
- /* this is set IF they exist in a world that we have loaded */
- world_instance *active_world;
- int world_match[ k_world_max ];
- u32 location_pstr; /* TODO: valid if active_world set. */
-
- char username[ NETWORK_USERNAME_MAX ];
- char items[k_netmsg_playeritem_max][ADDON_UID_MAX];
- char chat[ NETWORK_MAX_CHAT ];
- char region[ NETWORK_REGION_MAX ];
- u32 region_flags;
- f64 chat_time;
-
- /* ui */
- u32 medals[3];
- f32 opacity;
-
- u32 down_bytes;
- f32 down_kbs;
-
- struct player_effects_data effect_data;
- bool render_glider;
- }
- list[ NETWORK_MAX_PLAYERS ];
-
- struct interp_buffer {
- /* collect the most recent 6 frames of animation data */
- struct interp_frame {
- int active;
- f64 timestamp;
- enum player_subsystem subsystem;
-
- u8 flags;
- u16 boundary_hash;
-
- union interp_animdata {
- /* these aren't accessed directly, just used to take the
- * max(sizeof) all systems */
- struct player_skate_animator __skate;
- struct player_walk_animator __walk;
- struct player_dead_animator __dead;
- struct player_basic_info_animator __basic;
- }
- data;
-
- struct remote_glider_animator data_glider;
- }
- frames[ NETWORK_BUFFERFRAMES ];
-
- f64 t;
- }
- interp_data[ NETWORK_MAX_PLAYERS ];
-
- struct net_sfx sfx_queue[ NETWORK_SFX_QUEUE_LENGTH ];
-
- m4x3f *final_mtx,
- *glider_mtx;
- struct player_board_pose board_poses[ NETWORK_MAX_PLAYERS ];
-
- u32 up_bytes;
- f32 up_kbs, down_kbs;
- f64 last_data_measurement;
-
- int chatting;
- char chat_buffer[ NETWORK_MAX_CHAT ], chat_message[ NETWORK_MAX_CHAT ];
- f64 chat_time;
-}
-extern netplayers;
-
-void player_remote_rx_200_300( SteamNetworkingMessage_t *msg );
-void remote_player_debug_update(void);
-void remote_player_send_playerframe(void);
-void animate_remote_player( u32 index );
-void animate_remote_players(void);
-void render_remote_players( world_instance *world, vg_camera *cam );
-void relink_all_remote_player_worlds(void);
-void player_remote_update_friendflags( struct network_player *remote );
-void remote_players_init(void);
-void remote_sfx_pre_update(void);
-void remote_player_network_imgui( ui_context *ctx, m4x4f pv );
-void remote_players_imgui_world( ui_context *ctx, world_instance *world,
- m4x4f pv, f32 max_dist, int geo_cull );
-void remote_players_imgui_lobby( ui_context *ctx );
-void remote_players_chat_imgui( ui_context *ctx );
+++ /dev/null
-#include "player.h"
-#include "player_render.h"
-#include "vg/vg_camera.h"
-#include "player_model.h"
-#include "ent_skateshop.h"
-#include "audio.h"
-#include "input.h"
-
-#include "shaders/model_character_view.h"
-#include "shaders/model_board_view.h"
-#include "shaders/model_entity.h"
-#include "shaders/model_board_view.h"
-#include "depth_compare.h"
-
-#include "network.h"
-#include "player_remote.h"
-#include "player_glide.h"
-
-void player_load_animation_reference( const char *path )
-{
- mdl_context *meta = &localplayer.skeleton_meta;
- mdl_open( meta, path, vg_mem.rtmemory );
- mdl_load_metadata_block( meta, vg_mem.rtmemory );
- mdl_load_animation_block( meta, vg_mem.rtmemory );
- mdl_close( meta );
-
- struct skeleton *sk = &localplayer.skeleton;
- skeleton_setup( sk, vg_mem.rtmemory, meta );
-
- localplayer.id_world = skeleton_bone_id( sk, "world" );
- localplayer.id_hip = skeleton_bone_id( sk, "hips" );
- localplayer.id_chest = skeleton_bone_id( sk, "chest" );
- localplayer.id_ik_hand_l = skeleton_bone_id( sk, "hand.IK.L" );
- localplayer.id_ik_hand_r = skeleton_bone_id( sk, "hand.IK.R" );
- localplayer.id_ik_elbow_l = skeleton_bone_id( sk, "elbow.L" );
- localplayer.id_ik_elbow_r = skeleton_bone_id( sk, "elbow.R" );
- localplayer.id_head = skeleton_bone_id( sk, "head" );
- localplayer.id_foot_l = skeleton_bone_id( sk, "foot.L" );
- localplayer.id_foot_r = skeleton_bone_id( sk, "foot.R" );
- localplayer.id_ik_foot_l = skeleton_bone_id( sk, "foot.IK.L" );
- localplayer.id_ik_foot_r = skeleton_bone_id( sk, "foot.IK.R" );
- localplayer.id_board = skeleton_bone_id( sk, "board" );
- localplayer.id_wheel_l = skeleton_bone_id( sk, "wheel.L" );
- localplayer.id_wheel_r = skeleton_bone_id( sk, "wheel.R" );
- localplayer.id_ik_knee_l = skeleton_bone_id( sk, "knee.L" );
- localplayer.id_ik_knee_r = skeleton_bone_id( sk, "knee.R" );
- localplayer.id_eyes = skeleton_bone_id( sk, "eyes" );
-
- for( i32 i=0; i<sk->bone_count; i ++ ){
- localplayer.skeleton_mirror[i] = 0;
- }
-
- for( i32 i=1; i<sk->bone_count-1; i ++ ){
- struct skeleton_bone *si = &sk->bones[i];
-
- char tmp[64];
- vg_str str;
- vg_strnull( &str, tmp, 64 );
- vg_strcat( &str, si->name );
-
- char *L = vg_strch( &str, 'L' );
- if( !L ) continue;
- u32 len = L-tmp;
-
- for( i32 j=i+1; j<sk->bone_count; j ++ ){
- struct skeleton_bone *sj = &sk->bones[j];
-
- if( !strncmp( si->name, sj->name, len ) ){
- if( sj->name[len] == 'R' ){
- localplayer.skeleton_mirror[i] = j;
- localplayer.skeleton_mirror[j] = i;
- break;
- }
- }
- }
- }
-
- setup_ragdoll_from_skeleton( sk, &localplayer.ragdoll );
-
- /* allocate matrix buffers for localplayer and remote players */
- u32 mtx_size = sizeof(m4x3f)*sk->bone_count;
- localplayer.final_mtx = vg_linear_alloc( vg_mem.rtmemory, mtx_size );
- netplayers.final_mtx = vg_linear_alloc( vg_mem.rtmemory,
- mtx_size*NETWORK_MAX_PLAYERS );
- netplayers.glider_mtx = vg_linear_alloc( vg_mem.rtmemory,
- sizeof(m4x3f)*NETWORK_MAX_PLAYERS );
-}
-
-/* TODO: Standard model load */
-
-void dynamic_model_load( mdl_context *ctx,
- struct dynamic_model_1texture *mdl,
- const char *path, u32 *fixup_table )
-{
- if( !mdl_arrcount( &ctx->textures ) )
- vg_fatal_error( "No texture in model" );
-
- mdl_texture *tex0 = mdl_arritm( &ctx->textures, 0 );
- void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
- mdl_fread_pack_file( ctx, &tex0->file, data );
-
- vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
- VG_TEX2D_NEAREST|VG_TEX2D_CLAMP,
- &mdl->texture );
-
- mdl_async_load_glmesh( ctx, &mdl->mesh, fixup_table );
-}
-
-void dynamic_model_unload( struct dynamic_model_1texture *mdl )
-{
- mesh_free( &mdl->mesh );
- glDeleteTextures( 1, &mdl->texture );
-}
-
-/* TODO: allow error handling */
-void player_board_load( struct player_board *board, const char *path )
-{
- vg_linear_clear( vg_mem.scratch );
-
- mdl_context ctx;
- mdl_open( &ctx, path, vg_mem.scratch );
- mdl_load_metadata_block( &ctx, vg_mem.scratch );
-
- dynamic_model_load( &ctx, &board->mdl, path, NULL );
-
- mdl_array_ptr markers;
- MDL_LOAD_ARRAY( &ctx, &markers, ent_marker, vg_mem.scratch );
-
- /* TODO: you get put into a new section, the above is standard mdl loads. */
- for( int i=0; i<4; i++ )
- board->wheels[i].indice_count = 0;
- for( int i=0; i<2; i++ )
- board->trucks[i].indice_count = 0;
- board->board.indice_count = 0;
-
- for( u32 i=0; i<mdl_arrcount(&ctx.meshs); i++ ){
- mdl_mesh *mesh = mdl_arritm( &ctx.meshs, i );
-
- if( mdl_entity_id_type( mesh->entity_id ) != k_ent_marker )
- continue;
-
- u32 index = mdl_entity_id_id( mesh->entity_id );
- ent_marker *marker = mdl_arritm( &markers, index );
-
- mdl_submesh *sm0 = mdl_arritm( &ctx.submeshs, mesh->submesh_start );
-
- const char *alias = mdl_pstr( &ctx, marker->pstr_alias );
- u32 lr = marker->transform.co[0] > 0.0f? 1: 0,
- fb = marker->transform.co[2] > 0.0f? 0: 1;
-
- if( !strcmp( alias, "wheel" ) ){
- u32 id = fb<<1 | lr;
- board->wheels[ id ] = *sm0;
- v3_copy( marker->transform.co, board->wheel_positions[ id ] );
- }
- else if( !strcmp( alias, "board" ) ){
- board->board = *sm0;
- v3_copy( marker->transform.co, board->board_position );
- }
- else if( !strcmp( alias, "truck" ) ){
- board->trucks[ fb ] = *sm0;
- v3_copy( marker->transform.co, board->truck_positions[ fb ] );
- }
- }
-
- mdl_close( &ctx );
-}
-
-void player_board_unload( struct player_board *board )
-{
- dynamic_model_unload( &board->mdl );
-}
-
-void player_model_load( struct player_model *board, const char *path)
-{
- vg_linear_clear( vg_mem.scratch );
-
- mdl_context ctx;
- mdl_open( &ctx, path, vg_mem.scratch );
- mdl_load_metadata_block( &ctx, vg_mem.scratch );
-
- if( !ctx.armatures.count )
- vg_fatal_error( "No armature in playermodel\n" );
-
- mdl_armature *armature = mdl_arritm( &ctx.armatures, 0 );
-
- u32 fixup_table[ armature->bone_count+1 ];
- for( u32 i=0; i<armature->bone_count+1; i ++ )
- fixup_table[i] = 0;
-
- for( u32 i=1; i<localplayer.skeleton.bone_count; i ++ ){
- struct skeleton_bone *sb = &localplayer.skeleton.bones[i];
- u32 hash = vg_strdjb2( sb->name );
-
- for( u32 j=1; j<armature->bone_count; j ++ ){
- mdl_bone *bone = mdl_arritm( &ctx.bones, armature->bone_start+j );
-
- if( mdl_pstreq( &ctx, bone->pstr_name, sb->name, hash ) ){
- fixup_table[j+1] = i;
- break;
- }
- }
- }
-
- dynamic_model_load( &ctx, &board->mdl, path, fixup_table );
- mdl_close( &ctx );
-}
-
-void player_model_unload( struct player_model *board )
-{
- dynamic_model_unload( &board->mdl );
-}
-
-void apply_full_skeleton_pose( struct skeleton *sk, player_pose *pose,
- m4x3f *final_mtx ){
- m4x3f transform;
- q_m3x3( pose->root_q, transform );
- v3_copy( pose->root_co, transform[3] );
-
- if( pose->type == k_player_pose_type_ik ){
- skeleton_apply_pose( sk, pose->keyframes,
- k_anim_apply_defer_ik, final_mtx );
- skeleton_apply_ik_pass( sk, final_mtx );
- skeleton_apply_pose( sk, pose->keyframes,
- k_anim_apply_deffered_only, final_mtx );
- skeleton_apply_inverses( sk, final_mtx );
- skeleton_apply_transform( sk, transform, final_mtx );
- }
- else if( pose->type == k_player_pose_type_fk_2 ){
- skeleton_apply_pose( sk, pose->keyframes,
- k_anim_apply_always, final_mtx );
- skeleton_apply_inverses( sk, final_mtx );
- skeleton_apply_transform( sk, transform, final_mtx );
- }
-}
-
-void player__animate(void)
-{
- struct player_subsystem_interface *sys =
- player_subsystems[localplayer.subsystem];
-
- struct player_board *board =
- addon_cache_item_if_loaded( k_addon_type_board,
- localplayer.board_view_slot );
-
- sys->animate();
-
- player_pose *pose = &localplayer.pose;
- sys->pose( sys->animator_data, pose );
-
- struct skeleton *sk = &localplayer.skeleton;
-
- if( localplayer.holdout_time > 0.0f ){
- skeleton_lerp_pose( sk,
- pose->keyframes,localplayer.holdout_pose.keyframes,
- localplayer.holdout_time, pose->keyframes );
-
- v3_muladds( pose->root_co, localplayer.holdout_pose.root_co,
- localplayer.holdout_time, pose->root_co );
- q_nlerp( pose->root_q, localplayer.holdout_pose.root_q,
- localplayer.holdout_time, pose->root_q );
-
- localplayer.holdout_time -= vg.time_frame_delta / 0.25f;
- }
-
- effect_blink_apply( &localplayer.effect_data.blink,
- &localplayer.pose, vg.time_delta );
- apply_full_skeleton_pose( sk, &localplayer.pose, localplayer.final_mtx );
-
- if( sys->effects ){
- sys->effects( sys->animator_data, localplayer.final_mtx, board,
- &localplayer.effect_data );
- }
-
- skeleton_debug( sk, localplayer.final_mtx );
-
- if( sys->post_animate )
- sys->post_animate();
-
- player__observe_system( localplayer.subsystem );
- if( sys->sfx_comp )
- sys->sfx_comp( sys->animator_data );
-
- player__cam_iterate();
-}
-
-static void player_copy_frame_animator( replay_frame *frame ){
- struct player_subsystem_interface *sys =
- player_subsystems[localplayer.subsystem];
-
- if( sys->animator_size ){
- void *src = replay_frame_data( frame, k_replay_framedata_animator );
- memcpy( sys->animator_data, src, sys->animator_size );
- }
-}
-
-void lerp_player_pose( player_pose *pose0, player_pose *pose1, f32 t,
- player_pose *posed ){
- struct skeleton *sk = &localplayer.skeleton;
-
- v3_lerp( pose0->root_co, pose1->root_co, t, posed->root_co );
- q_nlerp( pose0->root_q, pose1->root_q, t, posed->root_q );
- posed->type = pose0->type;
- posed->board.lean = vg_lerpf( pose0->board.lean, pose1->board.lean, t );
-
- if( pose0->type != pose1->type ){
- /* it would be nice to apply IK pass in-keyframes. TOO BAD! */
- skeleton_copy_pose( sk, pose0->keyframes, posed->keyframes );
- }
- else {
- skeleton_lerp_pose( sk, pose0->keyframes, pose1->keyframes, t,
- posed->keyframes );
- }
-}
-
-void player__observe_system( enum player_subsystem id )
-{
- if( id != localplayer.observing_system ){
- struct player_subsystem_interface *sysm1 =
- player_subsystems[ localplayer.observing_system ];
-
- if( sysm1->sfx_kill ) sysm1->sfx_kill();
- localplayer.observing_system = id;
- }
-}
-
-void player__animate_from_replay( replay_buffer *replay )
-{
- replay_frame *frame = replay->cursor_frame,
- *next = NULL;
- if( frame ){
- next = frame->r;
-
- struct player_subsystem_interface
- *sys0 = player_subsystems[frame->system];
- void *a0 = replay_frame_data( frame, k_replay_framedata_animator );
-
- struct replay_glider_data
- *g0 = replay_frame_data( frame, k_replay_framedata_glider ),
- *g1;
-
- f32 t = 0.0f;
-
- if( next ){
- t = replay_subframe_time( replay );
-
- player_pose pose0, pose1;
-
- struct player_subsystem_interface
- *sys1 = player_subsystems[next->system];
- void *a1 = replay_frame_data( next, k_replay_framedata_animator );
-
- sys0->pose( a0, &pose0 );
- sys1->pose( a1, &pose1 );
-
- lerp_player_pose( &pose0, &pose1, t, &localplayer.pose );
- g1 = replay_frame_data( next, k_replay_framedata_glider );
- }
- else{
- sys0->pose( a0, &localplayer.pose );
- g1 = NULL;
- }
-
- player__observe_system( frame->system );
- if( sys0->sfx_comp )
- sys0->sfx_comp( a0 );
-
- if( g0 ){
- if( g0->glider_orphan ){
- if( g1 ){
- v3_lerp( g0->co, g1->co, t, player_glide.rb.co );
- q_nlerp( g0->q, g1->q, t, player_glide.rb.q );
- }
- else {
- v3_copy( g0->co, player_glide.rb.co );
- v4_copy( g0->q, player_glide.rb.q );
- }
-
- rb_update_matrices( &player_glide.rb );
- }
-
- if( g1 )
- player_glide.t = vg_lerpf( g0->t, g1->t, t );
- else
- player_glide.t = g0->t;
-
- localplayer.have_glider = g0->have_glider;
- localplayer.glider_orphan = g0->glider_orphan;
- }
- else /* no glider data in g1, or edge case we dont care about */ {
- localplayer.have_glider = 0;
- localplayer.glider_orphan = 0;
- player_glide.t = 0.0f;
- }
- }
- else return;
-
- apply_full_skeleton_pose( &localplayer.skeleton, &localplayer.pose,
- localplayer.final_mtx );
-}
-
-void player__pre_render(void)
-{
- /* shadowing/ao info */
- struct player_board *board =
- addon_cache_item_if_loaded( k_addon_type_board,
- localplayer.board_view_slot );
- v3f vp0, vp1;
- if( board ){
- v3_copy((v3f){0.0f,0.1f, board->truck_positions[0][2]}, vp0 );
- v3_copy((v3f){0.0f,0.1f, board->truck_positions[1][2]}, vp1 );
- }
- else{
- v3_zero( vp0 );
- v3_zero( vp1 );
- }
-
- struct ub_world_lighting *ubo = &world_current_instance()->ub_lighting;
- v3f *board_mtx = localplayer.final_mtx[ localplayer.id_board ];
- m4x3_mulv( board_mtx, vp0, ubo->g_board_0 );
- m4x3_mulv( board_mtx, vp1, ubo->g_board_1 );
-}
-
-void render_board( vg_camera *cam, world_instance *world,
- struct player_board *board, m4x3f root,
- struct player_board_pose *pose,
- enum board_shader shader )
-{
- if( !board )
- board = &localplayer.fallback_board;
-
- /* TODO:
- * adding depth compare to this shader
- */
-
- v3f inverse;
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, board->mdl.texture );
-
- if( shader == k_board_shader_player )
- {
- shader_model_board_view_use();
- shader_model_board_view_uTexMain( 0 );
- shader_model_board_view_uCamera( cam->transform[3] );
- shader_model_board_view_uPv( cam->mtx.pv );
-
- shader_model_board_view_uDepthMode(1);
- depth_compare_bind(
- shader_model_board_view_uTexSceneDepth,
- shader_model_board_view_uInverseRatioDepth,
- shader_model_board_view_uInverseRatioMain,
- cam );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_board_view );
- }
- else if( shader == k_board_shader_entity )
- {
- shader_model_entity_use();
- shader_model_entity_uTexMain( 0 );
- shader_model_entity_uCamera( cam->transform[3] );
- shader_model_entity_uPv( cam->mtx.pv );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_entity );
- }
-
- mesh_bind( &board->mdl.mesh );
-
- m4x4f m4mdl;
-
- if( board->board.indice_count ){
- m4x3f mlocal;
- m3x3_identity( mlocal );
-
- mdl_keyframe kf;
- v3_zero( kf.co );
- q_identity( kf.q );
- v3_zero( kf.s );
-
- v4f qroll;
- q_axis_angle( qroll, (v3f){0.0f,0.0f,1.0f}, pose->lean * 0.6f );
- keyframe_rotate_around( &kf, (v3f){0.0f,0.11f,0.0f},
- (v3f){0.0f,0.0f,0.0f}, qroll );
-
- v3_add( board->board_position, kf.co, mlocal[3] );
- q_m3x3( kf.q, mlocal );
-
- m4x3_mul( root, mlocal, mlocal );
-
- if( shader == k_board_shader_entity ){
- /* TODO: provide a way to supply previous mdl mtx? */
- m4x3_expand( mlocal, m4mdl );
- m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
- shader_model_entity_uPvmPrev( m4mdl );
- shader_model_entity_uMdl( mlocal );
- }
- else
- shader_model_board_view_uMdl( mlocal );
-
- mdl_draw_submesh( &board->board );
- }
-
- for( int i=0; i<2; i++ ){
- if( !board->trucks[i].indice_count )
- continue;
-
- m4x3f mlocal;
- m3x3_identity( mlocal );
- v3_copy( board->truck_positions[i], mlocal[3] );
- m4x3_mul( root, mlocal, mlocal );
-
- if( shader == k_board_shader_entity ){
- m4x3_expand( mlocal, m4mdl );
- m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
- shader_model_entity_uPvmPrev( m4mdl );
- shader_model_entity_uMdl( mlocal );
- }
- else
- shader_model_board_view_uMdl( mlocal );
-
- mdl_draw_submesh( &board->trucks[i] );
- }
-
- for( int i=0; i<4; i++ ){
- if( !board->wheels[i].indice_count )
- continue;
-
- m4x3f mlocal;
- m3x3_identity( mlocal );
- v3_copy( board->wheel_positions[i], mlocal[3] );
- m4x3_mul( root, mlocal, mlocal );
-
- if( shader == k_board_shader_entity ){
- m4x3_expand( mlocal, m4mdl );
- m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
- shader_model_entity_uPvmPrev( m4mdl );
- shader_model_entity_uMdl( mlocal );
- }
- else
- shader_model_board_view_uMdl( mlocal );
-
- mdl_draw_submesh( &board->wheels[i] );
- }
-}
-
-void render_playermodel( vg_camera *cam, world_instance *world,
- int depth_compare,
- struct player_model *model,
- struct skeleton *skeleton,
- m4x3f *final_mtx )
-{
- if( !model ) return;
-
- shader_model_character_view_use();
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, model->mdl.texture );
- shader_model_character_view_uTexMain( 0 );
- shader_model_character_view_uCamera( cam->transform[3] );
- shader_model_character_view_uPv( cam->mtx.pv );
- shader_model_character_view_uDepthMode( depth_compare );
- if( depth_compare )
- {
- depth_compare_bind(
- shader_model_character_view_uTexSceneDepth,
- shader_model_character_view_uInverseRatioDepth,
- shader_model_character_view_uInverseRatioMain,
- cam );
- }
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_character_view );
-
- glUniformMatrix4x3fv( _uniform_model_character_view_uTransforms,
- skeleton->bone_count,
- 0,
- (const GLfloat *)final_mtx );
-
- mesh_bind( &model->mdl.mesh );
- mesh_draw( &model->mdl.mesh );
-}
-
-void player__render( vg_camera *cam )
-{
- world_instance *world = world_current_instance();
- SDL_AtomicLock( &addon_system.sl_cache_using_resources );
-
- struct player_model *model =
- addon_cache_item_if_loaded( k_addon_type_player,
- localplayer.playermodel_view_slot );
-
- if( !model ) model = &localplayer.fallback_model;
- render_playermodel( cam, world, 1, model, &localplayer.skeleton,
- localplayer.final_mtx );
-
- struct player_board *board =
- addon_cache_item_if_loaded( k_addon_type_board,
- localplayer.board_view_slot );
-
- render_board( cam, world, board, localplayer.final_mtx[localplayer.id_board],
- &localplayer.pose.board, k_board_shader_player );
-
- SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
-
- glEnable( GL_CULL_FACE );
- player_glide_render( cam, world, &localplayer.pose );
- glDisable( GL_CULL_FACE );
-}
-
-void player_mirror_pose( mdl_keyframe pose[32], mdl_keyframe mirrored[32] )
-{
- mdl_keyframe temp[32];
-
- struct skeleton *sk = &localplayer.skeleton;
- for( u32 i=1; i<sk->bone_count; i ++ ){
- mdl_keyframe *dest = &temp[i-1];
- u8 mapping = localplayer.skeleton_mirror[i];
-
- if( mapping ) *dest = pose[mapping-1]; /* R */
- else *dest = pose[i-1]; /* L */
-
- dest->co[2] *= -1.0f;
- dest->q[0] *= -1.0f;
- dest->q[1] *= -1.0f;
- }
-
- for( u32 i=0; i<sk->bone_count-1; i ++ ){
- mirrored[i] = temp[i];
- }
-}
+++ /dev/null
-#pragma once
-#include "model.h"
-#include "skeleton.h"
-#include "vg/vg_camera.h"
-#include "world.h"
-#include "player_render.h"
-#include "player_api.h"
-#include "player_replay.h"
-
-enum eboard_truck{
- k_board_truck_back = 0,
- k_board_truck_front = 1
-};
-
-enum eboard_wheel{
- k_board_wheel_fl = 0,
- k_board_wheel_fr = 1,
- k_board_wheel_bl = 2,
- k_board_wheel_br = 3,
-};
-
-/* TODO: Fully featured dynamic models
- * This is FAR from the final system we want at all, but it will do for now */
-struct dynamic_model_1texture{
- glmesh mesh;
- GLuint texture;
-};
-
-struct player_board{
- struct dynamic_model_1texture mdl;
-
- v4f wheel_positions[4],
- truck_positions[2],
- board_position;
-
- mdl_submesh wheels[4],
- trucks[2],
- board;
-};
-
-struct player_model{
- struct dynamic_model_1texture mdl;
-};
-
-enum board_shader{
- k_board_shader_player,
- k_board_shader_entity
-};
-
-void dynamic_model_load( mdl_context *ctx,
- struct dynamic_model_1texture *mdl,
- const char *path, u32 *fixup_table );
-void dynamic_model_unload( struct dynamic_model_1texture *mdl );
-
-void player_board_load( struct player_board *mdl, const char *path );
-void player_board_unload( struct player_board *mdl );
-
-void player_model_load( struct player_model *board, const char *path);
-void player_model_unload( struct player_model *board );
-
-void render_board( vg_camera *cam, world_instance *world,
- struct player_board *board, m4x3f root,
- struct player_board_pose *pose,
- enum board_shader shader );
-
-void render_playermodel( vg_camera *cam, world_instance *world,
- int depth_compare,
- struct player_model *model,
- struct skeleton *skeleton,
- m4x3f *final_mtx );
-void apply_full_skeleton_pose( struct skeleton *sk, player_pose *pose,
- m4x3f *final_mtx );
-void lerp_player_pose( player_pose *pose0, player_pose *pose1, f32 t,
- player_pose *posed );
-void player_mirror_pose( mdl_keyframe pose[32],
- mdl_keyframe mirrored[32] );
-void player__observe_system( enum player_subsystem id );
-void player_load_animation_reference( const char *path );
-void player__render( vg_camera *cam );
-void player__animate_from_replay( replay_buffer *replay );
-void player__animate(void);
-void player__pre_render(void);
+++ /dev/null
-#include "skaterift.h"
-#include "player.h"
-#include "player_replay.h"
-#include "input.h"
-#include "gui.h"
-#include "freecam.h"
-
-#include "player_walk.h"
-#include "player_skate.h"
-#include "player_dead.h"
-#include "player_glide.h"
-
-struct replay_globals player_replay =
-{
- .active_keyframe = -1,
- .show_ui = 1,
- .editor_mode = 0
-};
-
-void replay_clear( replay_buffer *replay )
-{
- replay->head = NULL;
- replay->tail = NULL;
- replay->cursor_frame = NULL;
- replay->statehead = NULL;
- replay->cursor = -99999.9;
-}
-
-void *replay_frame_data( replay_frame *frame, enum replay_framedata type )
-{
- if( frame->data_table[type][1] == 0 )
- return NULL;
-
- void *baseptr = frame;
- return baseptr + frame->data_table[type][0];
-}
-
-static u16 replay_frame_calculate_data_offsets(
- u16 data_table[k_replay_framedata_rows][2] ){
-
- u32 total = vg_align8( sizeof(replay_frame) );
- for( u32 i=0; i<k_replay_framedata_rows; i++ ){
- data_table[i][0] = total;
- total += vg_align8(data_table[i][1]);
-
- if( total > 0xffff )
- vg_fatal_error( "Exceeded frame storage capacity\n" );
- }
- return total;
-}
-
-static void replay_tailpop( replay_buffer *replay ){
- if( replay->cursor_frame == replay->tail )
- replay->cursor_frame = NULL;
- if( replay->statehead == replay->tail )
- replay->statehead = NULL;
-
- replay->tail = replay->tail->r;
-
- if( replay->tail )
- replay->tail->l = NULL;
- else
- replay->head = NULL;
-}
-
-static replay_frame *replay_newframe( replay_buffer *replay,
- u16 animator_size,
- u16 gamestate_size,
- u16 sfx_count,
- bool save_glider ){
- u16 data_table[ k_replay_framedata_rows ][2];
- data_table[ k_replay_framedata_animator ][1] = animator_size;
- data_table[ k_replay_framedata_gamestate ][1] = gamestate_size;
- data_table[ k_replay_framedata_sfx ][1] = sfx_count*sizeof(struct net_sfx);
- data_table[ k_replay_framedata_internal_gamestate ][1] = 0;
- if( gamestate_size )
- {
- data_table[ k_replay_framedata_internal_gamestate ][1] =
- sizeof( replay_gamestate );
- }
-
- data_table[ k_replay_framedata_glider ][1] = 0;
- if( save_glider )
- {
- data_table[ k_replay_framedata_glider ][1] =
- sizeof(struct replay_glider_data);
- }
-
- u32 nextsize = replay_frame_calculate_data_offsets( data_table );
-
- replay_frame *frame = NULL;
- if( replay->head )
- {
- u32 headsize = replay->head->total_size,
- nextpos = ((void *)replay->head - replay->data) + headsize;
-
- if( nextpos + nextsize > replay->size )
- {
- nextpos = 0;
-
- /* maintain contiguity */
- while( replay->tail )
- {
- if( (void *)replay->tail - replay->data )
- replay_tailpop( replay );
- else break;
- }
- }
-
-check_again:;
- u32 tailpos = (void *)replay->tail - replay->data;
-
- if( tailpos >= nextpos )
- {
- if( nextpos + nextsize > tailpos )
- {
- replay_tailpop( replay );
-
- if( replay->tail )
- goto check_again;
- }
- }
-
- frame = replay->data + nextpos;
-
- if( replay->head )
- replay->head->r = frame;
- }
- else
- frame = replay->data;
-
- for( u32 i=0; i<k_replay_framedata_rows; i++ )
- {
- frame->data_table[i][0] = data_table[i][0];
- frame->data_table[i][1] = data_table[i][1];
- }
-
- frame->total_size = nextsize;
- frame->l = replay->head;
- frame->r = NULL;
- replay->head = frame;
- if( !replay->tail ) replay->tail = frame;
- if( gamestate_size ) replay->statehead = frame;
-
- return frame;
-}
-
-static void replay_emit_frame_sounds( replay_frame *frame ){
- void *baseptr = frame;
- u16 *inf = frame->data_table[k_replay_framedata_sfx];
- struct net_sfx *buffer = baseptr + inf[0];
- u32 count = inf[1] / sizeof(struct net_sfx);
-
- for( u32 i=0; i<count; i ++ ){
- net_sfx_play( buffer + i );
- }
-}
-
-int replay_seek( replay_buffer *replay, f64 t )
-{
- if( !replay->head ) return 0;
-
- if( t < replay->tail->time ) t = replay->tail->time;
- if( t > replay->head->time ) t = replay->head->time;
-
- if( !replay->cursor_frame ) {
- replay->cursor = replay->head->time;
- replay->cursor_frame = replay->head;
-
- if( fabs(replay->head->time-t) > fabs(replay->tail->time-t) ){
- replay->cursor = replay->tail->time;
- replay->cursor_frame = replay->tail;
- }
- }
-
- f64 dir = t - replay->cursor;
- if( dir == 0.0 ) return 0;
- dir = vg_signf( dir );
-
-
- u32 i=4096;
- while( i --> 0 ){
- if( dir < 0.0 ){
- if( t > replay->cursor_frame->time ) {
- replay->cursor = t;
- return 1;
- }
- }
-
- replay_frame *next;
- if( dir > 0.0 ) next = replay->cursor_frame->r;
- else next = replay->cursor_frame->l;
-
- if( !next ) break;
-
- if( dir > 0.0 ){
- if( t < next->time ){
- replay->cursor = t;
- return 1;
- }
- }
-
- replay_emit_frame_sounds( next );
-
- replay->cursor_frame = next;
- replay->cursor = next->time;
-
- if( !i ) return 1;
- }
-
- replay->cursor = t;
- return 0;
-}
-
-replay_frame *replay_find_recent_stateframe( replay_buffer *replay )
-{
- replay_frame *frame = replay->cursor_frame;
- u32 i=4096;
- while( i --> 0 ){
- if( !frame ) return frame;
- if( frame->data_table[ k_replay_framedata_gamestate ][1] ) return frame;
- frame = frame->l;
- }
-
- return NULL;
-}
-
-f32 replay_subframe_time( replay_buffer *replay )
-{
- replay_frame *frame = replay->cursor_frame;
- if( !frame ) return 0.0f;
- replay_frame *next = frame->r;
- if( next )
- {
- f64 l = next->time - frame->time,
- t = (l <= (1.0/128.0))? 0.0: (replay->cursor - frame->time) / l;
- return vg_clampf( t, 0.0f, 1.0f );
- }
- else
- return 0.0f;
-}
-
-void replay_get_frame_camera( replay_frame *frame, vg_camera *cam )
-{
- cam->fov = frame->cam.fov;
- v3_copy( frame->cam.pos, cam->pos );
- v3_copy( frame->cam.angles, cam->angles );
-}
-
-void replay_get_camera( replay_buffer *replay, vg_camera *cam )
-{
- cam->nearz = 0.1f;
- cam->farz = 100.0f;
- if( replay->cursor_frame )
- {
- replay_frame *next = replay->cursor_frame->r;
-
- if( next )
- {
- vg_camera temp;
-
- replay_get_frame_camera( replay->cursor_frame, cam );
- replay_get_frame_camera( next, &temp );
- vg_camera_lerp( cam, &temp, replay_subframe_time( replay ), cam );
- }
- else
- {
- replay_get_frame_camera( replay->cursor_frame, cam );
- }
- }
- else
- {
- v3_zero( cam->pos );
- v3_zero( cam->angles );
- cam->fov = 90.0f;
- }
-}
-
-void skaterift_get_replay_cam( vg_camera *cam )
-{
- replay_buffer *replay = &player_replay.local;
-
- if( player_replay.active_keyframe != -1 )
- {
- replay_keyframe *kf =
- &player_replay.keyframes[player_replay.active_keyframe];
-
- v3_copy( kf->cam.pos, cam->pos );
- v3_copy( kf->cam.angles, cam->angles );
- cam->fov = kf->cam.fov;
- return;
- }
-
- if( player_replay.keyframe_count >= 2 )
- {
- for( u32 i=0; i<player_replay.keyframe_count-1; i ++ )
- {
- replay_keyframe *kf = &player_replay.keyframes[i];
-
- if( (kf[0].time<=replay->cursor) && (kf[1].time>replay->cursor) )
- {
- f64 l = kf[1].time - kf[0].time,
- t = (l <= (1.0/128.0))? 0.0: (replay->cursor-kf[0].time) / l;
-
- if( player_replay.keyframe_count >= 3 )
- {
- f32 m_start = 0.5f, m_end = 0.5f;
-
- if( i > 0 )
- {
- if( (t < 0.5f) || (i==player_replay.keyframe_count-2) )
- {
- kf --;
- }
- }
-
- u32 last = player_replay.keyframe_count-1;
- if( kf+0 == player_replay.keyframes ) m_start = 1.0f;
- if( kf+2 == player_replay.keyframes+last ) m_end = 1.0f;
-
- f32 ts = vg_lerpf( kf[0].time, kf[1].time, 1.0f-m_start ),
- te = vg_lerpf( kf[1].time, kf[2].time, m_end );
-
- l = te-ts;
- t = (replay->cursor-ts)/l;
-
- /*
- * Adjust t, so that its derivative matches at the endpoints.
- * Since t needs to go from 0 to 1, it will naturally change at
- * different rates between keyframes. So this smooths it out.
- *
- * Newton method, going through standard direct quadratic eq has
- * precision / other problems. Also we only care about 0>t>1.
- */
- f32 b = (kf[1].time-ts)/l,
- x0 = 1.0-t;
- for( u32 i=0; i<4; i ++ )
- {
- f32 ix0 = 1.0f-x0,
- fx_x0 = 2.0f*b*x0*ix0 + ix0*ix0 - t,
- fxd_x0 = 2.0f*(-2.0f*b*x0 + b + x0 - 1.0f);
- x0 = x0 - (fx_x0/fxd_x0);
- }
- t = 1.0-x0;
-
- f32 t0 = t*m_start+(1.0f-m_start),
- t1 = t*m_end;
-
- v3f ps, pe, as, ae;
- f32 fs, fe;
-
- /* first order */
- v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t0, ps );
- vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles,
- t0, as );
- fs = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t0 );
-
- v3_lerp( kf[1].cam.pos, kf[2].cam.pos, t1, pe );
- vg_camera_lerp_angles( kf[1].cam.angles, kf[2].cam.angles,
- t1, ae );
- fe = vg_lerpf( kf[1].cam.fov, kf[2].cam.fov, t1 );
-
- /* second order */
- v3_lerp( ps, pe, t, cam->pos );
- vg_camera_lerp_angles( as, ae, t, cam->angles );
- cam->fov = vg_lerpf( fs, fe, t );
- }
- else
- {
- v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t, cam->pos );
- vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles,
- t, cam->angles );
- cam->fov = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t );
- }
- return;
- }
- }
- }
-
- replay_get_camera( replay, cam );
-}
-
-struct replay_rb
-{
- v3f co, v, w;
- v4f q;
-};
-
-void skaterift_record_frame( replay_buffer *replay, int force_gamestate )
-{
- f64 delta = 9999999.9,
- statedelta = 9999999.9;
-
- if( replay->head )
- delta = vg.time - replay->head->time;
-
- if( replay->statehead )
- statedelta = vg.time - replay->statehead->time;
-
- const f64 k_replay_rate = 1.0/30.0,
- k_gamestate_rate = 0.5;
-
- int save_frame = 0,
- save_state = 0,
- save_glider = 0;
-
- if( force_gamestate ) save_state = 1;
- if( statedelta > k_gamestate_rate ) save_state = 1;
- if( delta > k_replay_rate ) save_frame = 1;
- if( save_state ) save_frame = 1;
-
- if( localplayer.have_glider || localplayer.glider_orphan ||
- localplayer.subsystem == k_player_subsystem_glide ){
- save_glider = 1;
- }
-
- if( !save_frame ) return;
-
- u16 gamestate_size = 0;
- if( save_state ){
- /* TODO: have as part of system struct */
- gamestate_size = (u32 []){
- [k_player_subsystem_walk ] = sizeof(struct player_walk_state),
- [k_player_subsystem_drive] = 0,
- [k_player_subsystem_skate] = sizeof(struct player_skate_state),
- [k_player_subsystem_dead ] = localplayer.ragdoll.part_count *
- sizeof(struct replay_rb),
- [k_player_subsystem_glide] = sizeof(struct replay_rb),
- }[ localplayer.subsystem ];
- }
-
- u16 animator_size = player_subsystems[localplayer.subsystem]->animator_size;
-
- replay_frame *frame = replay_newframe( replay,
- animator_size, gamestate_size,
- localplayer.local_sfx_buffer_count,
- save_glider );
- frame->system = localplayer.subsystem;
-
- if( save_state ){
- replay_gamestate *gs =
- replay_frame_data( frame, k_replay_framedata_internal_gamestate );
-
- gs->current_run_version = world_static.current_run_version;
- gs->drowned = localplayer.drowned;
-
- /* permanent block */
- memcpy( &gs->rb, &localplayer.rb, sizeof(rigidbody) );
- memcpy( &gs->glider_rb, &player_glide.rb, sizeof(rigidbody) );
- memcpy( &gs->cam_control, &localplayer.cam_control,
- sizeof(struct player_cam_controller) );
- v3_copy( localplayer.angles, gs->angles );
-
- void *dst = replay_frame_data( frame, k_replay_framedata_gamestate );
-
- /* subsytem/dynamic block */
- if( localplayer.subsystem == k_player_subsystem_walk )
- memcpy( dst, &player_walk.state, gamestate_size );
- else if( localplayer.subsystem == k_player_subsystem_skate )
- memcpy( dst, &player_skate.state, gamestate_size );
- else if( localplayer.subsystem == k_player_subsystem_dead ){
- struct replay_rb *arr = dst;
- for( u32 i=0; i<localplayer.ragdoll.part_count; i ++ ){
- rigidbody *rb = &localplayer.ragdoll.parts[i].rb;
- v3_copy( rb->co, arr[i].co );
- v3_copy( rb->w, arr[i].w );
- v3_copy( rb->v, arr[i].v );
- v4_copy( rb->q, arr[i].q );
- }
- }
- else if( localplayer.subsystem == k_player_subsystem_glide ){
- struct replay_rb *arr = dst;
- rigidbody *rb = &player_glide.rb;
- v3_copy( rb->co, arr[0].co );
- v3_copy( rb->w, arr[0].w );
- v3_copy( rb->v, arr[0].v );
- v4_copy( rb->q, arr[0].q );
- }
- }
-
- if( save_glider ){
- struct replay_glider_data *inf =
- replay_frame_data( frame, k_replay_framedata_glider );
-
- inf->have_glider = localplayer.have_glider;
- inf->glider_orphan = localplayer.glider_orphan;
- inf->t = player_glide.t;
- v3_copy( player_glide.rb.co, inf->co );
- v4_copy( player_glide.rb.q, inf->q );
- }
-
- replay->cursor = vg.time;
- replay->cursor_frame = frame;
- frame->time = vg.time;
-
- /* camera */
- v3_copy( localplayer.cam.pos, frame->cam.pos );
- if( localplayer.gate_waiting ){
- m4x3_mulv( localplayer.gate_waiting->transport,
- frame->cam.pos, frame->cam.pos );
-
- v3f v0;
- v3_angles_vector( localplayer.cam.angles, v0 );
- m3x3_mulv( localplayer.gate_waiting->transport, v0, v0 );
- v3_angles( v0, frame->cam.angles );
- }
- else
- v3_copy( localplayer.cam.angles, frame->cam.angles );
-
- frame->cam.fov = localplayer.cam.fov;
-
- /* animator */
- void *dst = replay_frame_data( frame, k_replay_framedata_animator ),
- *src = player_subsystems[localplayer.subsystem]->animator_data;
- memcpy( dst, src, animator_size );
-
- /* sound effects */
- memcpy( replay_frame_data( frame, k_replay_framedata_sfx ),
- localplayer.local_sfx_buffer,
- sizeof(struct net_sfx)*localplayer.local_sfx_buffer_count );
-
- localplayer.local_sfx_buffer_count = 0;
-}
-
-static void skaterift_restore_frame( replay_frame *frame )
-{
- replay_gamestate *gs =
- replay_frame_data( frame, k_replay_framedata_internal_gamestate );
- void *src = replay_frame_data( frame, k_replay_framedata_gamestate );
- u16 src_size = frame->data_table[ k_replay_framedata_gamestate ][1];
- world_static.current_run_version = gs->current_run_version;
- localplayer.drowned = gs->drowned;
-
- if(frame->system == k_player_subsystem_walk ){
- memcpy( &player_walk.state, src, src_size );
- }
- else if( frame->system == k_player_subsystem_skate ){
- memcpy( &player_skate.state, src, src_size );
- }
- else if( frame->system == k_player_subsystem_dead ){
- player__dead_transition(0);
- struct replay_rb *arr = src;
-
- for( u32 i=0; i<localplayer.ragdoll.part_count; i ++ ){
- struct ragdoll_part *part = &localplayer.ragdoll.parts[i];
- rigidbody *rb = &part->rb;
-
- v3_copy( arr[i].co, rb->co );
- v3_copy( arr[i].w, rb->w );
- v3_copy( arr[i].v, rb->v );
- v4_copy( arr[i].q, rb->q );
-
- v3_copy( arr[i].co, part->prev_co );
- v4_copy( arr[i].q, part->prev_q );
- rb_update_matrices( rb );
- }
- }
- else if( frame->system == k_player_subsystem_glide ){
- struct replay_rb *arr = src;
- rigidbody *rb = &player_glide.rb;
- v3_copy( arr[0].co, rb->co );
- v3_copy( arr[0].w, rb->w );
- v3_copy( arr[0].v, rb->v );
- v4_copy( arr[0].q, rb->q );
- rb_update_matrices( rb );
- }
-
- localplayer.subsystem = frame->system;
-
- /* restore the seperated glider data if we have it */
- if( frame->data_table[ k_replay_framedata_glider ][1] ){
- struct replay_glider_data *inf =
- replay_frame_data( frame, k_replay_framedata_glider );
-
- localplayer.have_glider = inf->have_glider;
- localplayer.glider_orphan = inf->glider_orphan;
- player_glide.t = inf->t;
- }
- else {
- localplayer.have_glider = 0;
- localplayer.glider_orphan = 0;
- player_glide.t = 0.0f;
- }
-
- memcpy( &localplayer.rb, &gs->rb, sizeof(rigidbody) );
- memcpy( &player_glide.rb, &gs->glider_rb, sizeof(rigidbody) );
- v3_copy( gs->angles, localplayer.angles );
-
- v3_copy( frame->cam.pos, localplayer.cam.pos );
- v3_copy( frame->cam.angles, localplayer.cam.angles );
- localplayer.cam.fov = frame->cam.fov;
-
- memcpy( &localplayer.cam_control, &gs->cam_control,
- sizeof(struct player_cam_controller) );
-
- /* chop end off replay */
- frame->r = NULL;
- player_replay.local.statehead = frame;
- player_replay.local.head = frame;
- player_replay.local.cursor_frame = frame;
- player_replay.local.cursor = frame->time;
- player_replay.replay_control = k_replay_control_scrub;
- skaterift.activity = k_skaterift_default;
- vg.time = frame->time;
-}
-
-static void skaterift_replay_resume(void){
- replay_frame *prev = replay_find_recent_stateframe(&player_replay.local);
-
- if( prev ){
- player_replay.replay_control = k_replay_control_resume;
- player_replay.resume_target = prev;
- player_replay.resume_begin = player_replay.local.cursor;
- player_replay.resume_transition = 0.0f;
- }
-
- gui_helper_clear();
-}
-
-static void skaterift_replay_update_helpers(void);
-
-void skaterift_replay_pre_update(void)
-{
- if( skaterift.activity != k_skaterift_replay ) return;
-
- bool input = player_replay.editor_mode^0x1;
-
- if( player_replay.replay_control == k_replay_control_resume )
- {
- if( player_replay.local.cursor_frame == player_replay.resume_target ||
- player_replay.local.cursor_frame == NULL )
- {
- skaterift_restore_frame( player_replay.resume_target );
- }
- else
- {
- vg_slewf( &player_replay.resume_transition, 1.0f,
- vg.time_frame_delta * (1.0f/1.0f) );
-
- if( player_replay.resume_transition >= 1.0f )
- skaterift_restore_frame( player_replay.resume_target );
- else {
- f64 target = vg_lerp( player_replay.resume_begin,
- player_replay.resume_target->time,
- vg_smoothstepf( player_replay.resume_transition ) );
- if( replay_seek( &player_replay.local, target ) )
- player_replay.track_velocity = 1.0f;
- else
- player_replay.track_velocity = 0.0f;
- }
- }
- }
- else
- {
- if( input && button_down( k_srbind_replay_play ) )
- player_replay.replay_control = k_replay_control_play;
- if( input && button_down( k_srbind_replay_freecam ) )
- {
- player_replay.use_freecam ^= 0x1;
-
- if( player_replay.use_freecam )
- {
- replay_get_camera( &player_replay.local,
- &player_replay.replay_freecam );
- }
- skaterift_replay_update_helpers();
- }
-
- f32 target_speed = 0.0f;
- if( input )
- target_speed = axis_state( k_sraxis_replay_h ) * 5.0;
-
- if( input && button_press( k_srbind_reset ) )
- target_speed += -2.0;
-
- if( fabsf(target_speed) > 0.01f )
- player_replay.replay_control = k_replay_control_scrub;
-
- if( player_replay.replay_control == k_replay_control_play )
- target_speed = 1.0;
-
- vg_slewf( &player_replay.track_velocity, target_speed,
- 18.0f*vg.time_frame_delta );
-
- if( fabsf( player_replay.track_velocity ) > 0.0001f )
- {
- f64 target = player_replay.local.cursor;
- target += player_replay.track_velocity * vg.time_frame_delta;
-
- if( !replay_seek( &player_replay.local, target ) )
- player_replay.track_velocity = 0.0f;
- }
-
- if( input && button_down( k_srbind_mback ) )
- {
- if( player_replay.local.statehead )
- skaterift_restore_frame( player_replay.local.statehead );
- else
- skaterift.activity = k_skaterift_default;
- srinput.state = k_input_state_resume;
- gui_helper_clear();
- }
-
- if( input )
- {
- if( player_replay.use_freecam )
- {
- freecam_preupdate();
- }
- else
- {
- if( button_down( k_srbind_replay_resume ) )
- {
- skaterift_replay_resume();
- }
- }
- }
- }
-}
-
-static void skaterift_replay_update_helpers(void)
-{
- player_replay.helper_resume->greyed = player_replay.use_freecam;
-
- vg_str freecam_text;
- vg_strnull( &freecam_text, player_replay.helper_freecam->text,
- GUI_HELPER_TEXT_LENGTH );
- vg_strcat( &freecam_text,
- player_replay.use_freecam? "Exit freecam": "Freecam" );
-}
-
-static void replay_show_helpers(void)
-{
- gui_helper_clear();
- vg_str text;
-
- if( gui_new_helper( input_axis_list[k_sraxis_replay_h], &text ) )
- vg_strcat( &text, "Scrub" );
-
- if( (player_replay.helper_resume = gui_new_helper(
- input_button_list[k_srbind_replay_resume], &text )) )
- vg_strcat( &text, "Resume" );
-
- if( gui_new_helper( input_button_list[k_srbind_replay_play], &text ))
- vg_strcat( &text, "Playback" );
-
- player_replay.helper_freecam = gui_new_helper(
- input_button_list[k_srbind_replay_freecam], &text );
-
- skaterift_replay_update_helpers();
-}
-
-void skaterift_replay_post_render(void)
-{
-#ifndef SR_ALLOW_REWIND_HUB
- if( world_static.active_instance != k_world_purpose_client )
- return;
-#endif
-
- /* capture the current resume frame at the very last point */
- if( button_down( k_srbind_reset ) )
- {
- if( skaterift.activity == k_skaterift_default )
- {
- localplayer.rewinded_since_last_gate = 1;
- skaterift.activity = k_skaterift_replay;
- skaterift_record_frame( &player_replay.local, 1 );
- if( player_replay.local.head )
- {
- player_replay.local.cursor = player_replay.local.head->time;
- player_replay.local.cursor_frame = player_replay.local.head;
- }
- player_replay.replay_control = k_replay_control_scrub;
- replay_show_helpers();
- }
- }
-}
-
-void skaterift_replay_init(void)
-{
- u32 bytes = 1024*1024*10;
- player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, bytes );
- player_replay.local.size = bytes;
- replay_clear( &player_replay.local );
-}
-
-void skaterift_replay_debug_info( ui_context *ctx )
-{
- player__debugtext( ctx, 2, "replay info" );
- replay_buffer *replay = &player_replay.local;
-
- u32 head = 0,
- tail = 0;
- if( replay->tail ) tail = (void *)replay->tail - replay->data;
- if( replay->head ) head = (void *)replay->head - replay->data;
-
- player__debugtext( ctx, 1, "head @%u | tail @%u\n", head, tail );
-
- if( replay->statehead )
- {
- for( u32 i=0; i<k_replay_framedata_rows; i++ )
- {
- player__debugtext( ctx, 1, "[%u]: [%hu, %hu]\n", i,
- replay->statehead->data_table[i][0],
- replay->statehead->data_table[i][1] );
- }
- u32 state = (void *)replay->statehead - replay->data;
- player__debugtext( ctx, 1, "gs @%u\n", state );
- player__debugtext( ctx, 1, "gamestate_size: %hu\n",
- replay->statehead->data_table[k_replay_framedata_gamestate][1] );
- }
- else
- player__debugtext( ctx, 1, "gs @NULL\n" );
-
- f64 start = replay->cursor,
- end = replay->cursor;
- if( replay->tail ) start = replay->tail->time;
- if( replay->head ) end = replay->head->time;
-
- f64 cur = replay->cursor - start,
- len = end - start;
-
- player__debugtext( ctx, 1, "cursor: %.2fs / %.2fs\n", cur, len );
-}
-
-static int _keyframe_cmp( const void *p1, const void *p2 )
-{
- const replay_keyframe *kf1 = p1, *kf2 = p2;
- return kf1->time > kf2->time;
-}
-
-static void replay_keyframe_sort(void)
-{
- qsort( player_replay.keyframes, player_replay.keyframe_count,
- sizeof(replay_keyframe), _keyframe_cmp );
-}
-
-static void replay_fly_edit_keyframe( ui_context *ctx, replay_keyframe *kf )
-{
- if( ui_click_down( ctx, UI_MOUSE_LEFT ) )
- {
- /* init freecam */
- v3_copy( kf->cam.pos, player_replay.replay_freecam.pos );
- v3_copy( kf->cam.angles, player_replay.replay_freecam.angles );
- v3_zero( player_replay.freecam_v );
- v3_zero( player_replay.freecam_w );
- player_replay.replay_freecam.fov = kf->cam.fov;
- }
-
- /* move freecam */
- ui_capture_mouse( ctx, 0 );
- freecam_preupdate();
-
- if( vg_getkey(SDLK_q) )
- player_replay.freecam_v[1] -= vg.time_frame_delta*6.0f*20.0f;
- if( vg_getkey(SDLK_e) )
- player_replay.freecam_v[1] += vg.time_frame_delta*6.0f*20.0f;
-
- v3_copy( player_replay.replay_freecam.pos, g_render.cam.pos );
- v3_copy( player_replay.replay_freecam.angles, g_render.cam.angles);
- g_render.cam.fov = player_replay.replay_freecam.fov;
-
- v3_copy( g_render.cam.pos, kf->cam.pos );
- v3_copy( g_render.cam.angles, kf->cam.angles );
- kf->cam.fov = g_render.cam.fov;
-}
-
-void skaterift_replay_imgui( ui_context *ctx )
-{
- if( skaterift.activity != k_skaterift_replay ) return;
-
- /* extra keys for entering editor */
- static u8 f1_key = 0;
- u8 f1_now = vg_getkey(SDLK_F1);
- if( f1_now && !f1_key && player_replay.show_ui )
- {
- player_replay.editor_mode ^= 0x1;
-
- if( player_replay.editor_mode )
- gui_helper_clear();
- else
- replay_show_helpers();
- }
- f1_key = f1_now;
-
- static u8 f2_key = 0;
- u8 f2_now = vg_getkey(SDLK_F2);
- if( f2_now && !f2_key )
- {
- player_replay.show_ui ^= 0x1;
- }
- f2_key = f2_now;
-
- if( player_replay.editor_mode )
- {
- static u8 space_key = 0;
- u8 space_now = vg_getkey(SDLK_SPACE);
- if( space_now & !space_key )
- {
- player_replay.replay_control ^= k_replay_control_play;
- }
- space_key = space_now;
- }
-
- if( !player_replay.show_ui ) return;
-
- if( player_replay.editor_mode )
- {
- u32 colour = ui_opacity( ui_colour(ctx,k_ui_fg), 0.3333f );
- ui_rect cx = { vg.window_x/2, 0, 1, vg.window_y },
- cy = { 0, vg.window_y/2, vg.window_x, 1 };
- ui_fill( ctx, cx, colour );
- ui_fill( ctx, cy, colour );
- }
-
- replay_buffer *replay = &player_replay.local;
- f64 start = replay->cursor,
- end = replay->cursor;
- if( replay->tail ) start = replay->tail->time;
- if( replay->head ) end = replay->head->time;
- f64 len = end - start,
- cur = (replay->cursor - start) / len;
-
- char buffer[ 128 ];
-
- /* mainbar */
- ui_px height = 32,
- cwidth = 2;
- ui_rect timeline = { 0, 0, vg.window_x, height };
- ui_fill( ctx, timeline, ui_colour( ctx, k_ui_bg ) );
-
- /* cursor frame block */
- if( replay->cursor_frame )
- {
- if( replay->cursor_frame->r )
- {
- f64 l = (replay->cursor_frame->r->time-replay->cursor_frame->time)/len,
- s = (replay->cursor_frame->time - start) / len;
- ui_rect box = { s*(f64)vg.window_x, 0,
- VG_MAX(4,(ui_px)(l*vg.window_x)), timeline[3]+2 };
- ui_fill( ctx, box, ui_colour( ctx, k_ui_bg+4 ) );
- }
- }
-
- /* cursor */
- ui_rect cusor = { cur * (f64)vg.window_x - (cwidth/2), 0,
- cwidth, (player_replay.editor_mode? 0: 16) + timeline[3] };
- ui_fill( ctx, cusor, ui_colour( ctx, k_ui_bg+7 ) );
-
- /* latest state marker */
- if( replay->statehead )
- {
- f64 t = (replay->statehead->time - start) / len;
- ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 };
- ui_fill( ctx, tag, ui_colour( ctx, k_ui_green+k_ui_brighter ) );
- }
-
- /* previous state marker */
- replay_frame *prev = replay_find_recent_stateframe( replay );
- if( prev )
- {
- f64 t = (prev->time - start) / len;
- ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 };
- ui_fill( ctx, tag, ui_colour( ctx, k_ui_yellow+k_ui_brighter ) );
- }
-
- snprintf( buffer, 128, "-%.2fs (F1: Edit replay)", (end-replay->cursor) );
- ui_text( ctx, timeline, buffer, 1, k_ui_align_middle_left, 0 );
- ui_text( ctx, timeline, "0s", 1, k_ui_align_middle_right, 0 );
-
- if( !player_replay.editor_mode ) return;
- ui_capture_mouse( ctx, 1 );
-
- ui_rect panel = { 0, timeline[3] + 20, 200, 400 };
- ui_fill( ctx, panel, ui_opacity( ui_colour( ctx, k_ui_bg ), 0.5f ) );
- ui_rect_pad( panel, (ui_px[2]){4,4} );
-
- if( ui_button( ctx, panel,
- (player_replay.replay_control == k_replay_control_play)?
- "Pause (space)": "Play (space)" ) == k_ui_button_click )
- {
- player_replay.replay_control ^= k_replay_control_play;
- }
-
- /* script bar */
- ui_rect script = { 0, height + 2, vg.window_x, 16 };
- ui_fill( ctx, script, ui_colour( ctx, k_ui_bg ) );
- f64 mouse_t = start + ((f64)ctx->mouse[0] / (f64)vg.window_x)*len;
-
- /* keyframe draw and select */
- bool absorb_by_keyframe = 0;
- ui_px lx = 0;
- for( u32 i=0; i<player_replay.keyframe_count; i ++ )
- {
- replay_keyframe *kf = &player_replay.keyframes[i];
- f64 t = (kf->time-start)/len;
-
- ui_px x = t*(f64)vg.window_x-8;
-
- /* draw connections between keyframes */
- if( i )
- {
- ui_rect con = { lx, script[1]+7, x-lx, 1 };
- ui_fill( ctx, con, ui_colour( ctx, k_ui_blue ) );
- vg_line( kf->cam.pos, player_replay.keyframes[i-1].cam.pos, VG__BLUE );
- }
-
- /* keyframe selection */
- ui_rect tag = { x, script[1], 16, 16 };
-
- if( ui_inside_rect( tag, ctx->mouse ) )
- {
- absorb_by_keyframe = 1;
-
- if( ui_click_down( ctx, UI_MOUSE_LEFT ) )
- {
- if( player_replay.active_keyframe != i )
- {
- player_replay.active_keyframe = i;
- replay_seek( &player_replay.local, kf->time );
- }
- }
- else
- {
- ui_outline( ctx, tag, 1, ui_colour(ctx, k_ui_fg), 0 );
- }
- }
-
- /* edit controls */
- u32 drag_colour = ui_opacity( ui_colour(ctx, k_ui_bg+2), 0.5f );
- if( i == player_replay.active_keyframe )
- {
- ui_outline( ctx, tag, 2, ui_colour(ctx, k_ui_fg), 0 );
-
- ui_rect tray = { tag[0]+8-32, tag[1]+16+2, 64, 16 };
- ui_rect dragbar = { tray[0]+16, tray[1], 32, 16 };
-
- bool pos_correct = 0;
-
- if( ui_inside_rect( dragbar, ctx->mouse_click ) )
- {
- if( ui_clicking( ctx, UI_MOUSE_LEFT ) )
- {
- drag_colour = ui_opacity( ui_colour(ctx,k_ui_fg), 0.5f );
- pos_correct = 1;
- replay_seek( &player_replay.local, mouse_t );
- }
- else if( ui_click_up( ctx, UI_MOUSE_LEFT ) )
- {
- pos_correct = 1;
- kf->time = mouse_t;
- replay_keyframe_sort();
-
- for( u32 j=0; j<player_replay.keyframe_count; j ++ )
- {
- if( player_replay.keyframes[j].time == mouse_t )
- {
- player_replay.active_keyframe = j;
- break;
- }
- }
- }
-
- if( pos_correct )
- {
- tag[0] = ctx->mouse[0]-8;
- tray[0] = tag[0]+8-32;
- dragbar[0] = tray[0]+16;
- }
- }
-
- if( ui_inside_rect( dragbar, ctx->mouse ) )
- {
- ctx->cursor = k_ui_cursor_hand;
- }
-
- if( !pos_correct )
- {
- ui_fill( ctx, tray,
- ui_opacity( ui_colour( ctx, k_ui_bg+2 ), 0.5f ) );
- }
-
- ui_fill( ctx, dragbar, drag_colour );
- ui_text( ctx, dragbar, ":::", 1, k_ui_align_middle_center, 0 );
-
- if( !pos_correct )
- {
- ui_rect btn = { tray[0], tray[1], 16, 16 };
- if( ui_button_text( ctx, btn, "X", 1 ) == k_ui_button_click )
- {
- for( u32 j=i; j<player_replay.keyframe_count-1; j ++ )
- player_replay.keyframes[j] = player_replay.keyframes[j+1];
-
- player_replay.keyframe_count --;
- player_replay.active_keyframe = -1;
- }
-
- ui_rect btn1 = { tray[0]+48, tray[1], 16, 16 };
-
- enum ui_button_state mask_using =
- k_ui_button_holding_inside |
- k_ui_button_holding_outside |
- k_ui_button_click;
-
- if( ui_button_text( ctx, btn1, "E", 1 ) & mask_using )
- {
- replay_fly_edit_keyframe( ctx, kf );
- vg_ui_set_mouse_pos( btn1[0]+8, btn1[1]+8 );
- }
- }
- }
-
- ui_fill( ctx, tag, ui_colour( ctx, k_ui_blue ) );
- lx = x;
- }
-
- /* adding keyframes */
- if( ui_inside_rect( script, ctx->mouse ) )
- {
- ctx->cursor = k_ui_cursor_hand;
-
- ui_rect cursor = { ctx->mouse[0], script[1], 4, 16 };
- ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) );
-
- if( !absorb_by_keyframe && ui_click_down( ctx, UI_MOUSE_LEFT ) )
- {
- u32 max = VG_ARRAY_LEN( player_replay.keyframes );
- if( player_replay.keyframe_count == max )
- {
- ui_start_modal( ctx, "Maximum keyframes reached", UI_MODAL_BAD );
- }
- else
- {
- replay_keyframe *kf =
- &player_replay.keyframes[player_replay.keyframe_count++];
-
- kf->time = mouse_t;
- v3_copy( g_render.cam.pos, kf->cam.pos );
- v3_copy( g_render.cam.angles, kf->cam.angles );
- kf->cam.fov = g_render.cam.fov;
-
- replay_keyframe_sort();
- }
- }
- }
-
- /* timeline scrub */
- bool start_in_timeline =
- ui_clicking(ctx, UI_MOUSE_LEFT) &&
- ui_inside_rect(timeline, ctx->mouse_click);
- if( (ui_inside_rect( timeline, ctx->mouse )) || start_in_timeline )
- {
- ui_rect cursor = { ctx->mouse[0], timeline[1], 4, timeline[3] };
- ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) );
- ctx->cursor = k_ui_cursor_ibeam;
-
- if( ui_clicking( ctx, UI_MOUSE_LEFT ) && start_in_timeline )
- {
- replay_seek( &player_replay.local, mouse_t );
- player_replay.active_keyframe = -1;
- }
- }
-
- if( ui_button( ctx, panel, "Clear keyframes" ) == k_ui_button_click )
- {
- player_replay.keyframe_count = 0;
- }
-
- if( (ui_button( ctx, panel, "Hide UI (F2)" ) == k_ui_button_click) )
- {
- player_replay.show_ui ^= 0x1;
- }
-
- if( player_replay.active_keyframe != -1 )
- {
- replay_keyframe *kf =
- &player_replay.keyframes[ player_replay.active_keyframe ];
-
- enum ui_button_state mask_using =
- k_ui_button_holding_inside |
- k_ui_button_holding_outside |
- k_ui_button_click;
-
- if( ui_button( ctx, panel, "Edit cam" ) & mask_using )
- {
- replay_fly_edit_keyframe( ctx, kf );
- }
- }
-
- ui_info( ctx, panel, "World settings" );
- f32 new_time = world_current_instance()->time;
- if( ui_slider( ctx, panel, "Time of day", 0, 1, &new_time ) )
- {
- world_current_instance()->time = new_time;
- }
-
- ui_info( ctx, panel, "" );
- if( ui_button( ctx, panel, "Exit editor (F1)" ) == k_ui_button_click )
- {
- player_replay.editor_mode = 0;
- replay_show_helpers();
- }
-
- /* TODO: Add Q/E scrub here too.
- * Add replay trimming
- */
-}
+++ /dev/null
-#pragma once
-#include "player_render.h"
-#include "vg/vg_rigidbody.h"
-
-typedef struct replay_buffer replay_buffer;
-typedef struct replay_frame replay_frame;
-typedef struct replay_keyframe replay_keyframe;
-
-typedef struct replay_gamestate replay_gamestate;
-typedef struct replay_sfx replay_sfx;
-
-struct replay_buffer {
- void *data;
- u32 size; /* bytes */
-
- replay_frame *head, *tail, *cursor_frame,
- *statehead;
- f64 cursor;
-};
-
-enum replay_framedata{
- k_replay_framedata_animator,
- k_replay_framedata_gamestate,
- k_replay_framedata_internal_gamestate,
- k_replay_framedata_sfx,
- k_replay_framedata_glider,
- k_replay_framedata_rows
-};
-
-struct replay_cam
-{
- v3f pos, angles;
- f32 fov;
-};
-
-struct replay_frame
-{
- struct replay_cam cam;
- f64 time;
-
- replay_frame *l, *r;
-
- enum player_subsystem system;
- u16 total_size;
- u16 data_table[k_replay_framedata_rows][2];
-};
-
-/* player-defined replay frames */
-struct replay_keyframe
-{
- struct replay_cam cam;
- f64 time;
-};
-
-struct replay_gamestate
-{
- rigidbody rb, glider_rb; /* TODO: these don't need to be saved with their
- full matrices */
- v3f angles;
- struct player_cam_controller cam_control;
- u32 current_run_version;
- bool drowned;
-};
-
-/* we save this per-anim-frame. if there glider is existing in any state */
-struct replay_glider_data
-{
- bool have_glider, glider_orphan;
- f32 t;
- v3f co;
- v4f q;
-};
-
-struct replay_sfx {
- u32 none;
-};
-
-struct replay_globals
-{
- replay_buffer local;
- replay_frame *resume_target;
- f64 resume_begin;
- f32 resume_transition;
-
- enum replay_control {
- k_replay_control_scrub = 0x00,
- k_replay_control_play = 0x01,
- k_replay_control_resume= 0x02
- }
- replay_control;
- f32 track_velocity;
- struct gui_helper *helper_resume, *helper_freecam;
-
- vg_camera replay_freecam;
-
- bool use_freecam;
- bool show_ui;
- v3f freecam_v, freecam_w;
-
- i32 editor_mode;
-
- replay_keyframe keyframes[32];
- u32 keyframe_count;
- i32 active_keyframe;
-}
-extern player_replay;
-
-int replay_seek( replay_buffer *replay, f64 t );
-
-replay_frame *replay_find_recent_stateframe( replay_buffer *replay );
-void replay_get_camera( replay_buffer *replay, vg_camera *cam );
-void replay_get_frame_camera( replay_frame *frame, vg_camera *cam );
-f32 replay_subframe_time( replay_buffer *replay );
-void replay_clear( replay_buffer *replay );
-void *
-replay_frame_data( replay_frame *frame, enum replay_framedata type );
-
-void skaterift_replay_pre_update(void);
-void skaterift_replay_imgui( ui_context *ctx );
-void skaterift_replay_debug_info( ui_context *ctx );
-void skaterift_record_frame( replay_buffer *replay,
- int force_gamestate );
-void skaterift_replay_post_render(void);
-void skaterift_replay_init(void);
-void skaterift_get_replay_cam( vg_camera *cam );
+++ /dev/null
-#include "player_skate.h"
-#include "player.h"
-#include "audio.h"
-#include "vg/vg_perlin.h"
-#include "vg/vg_lines.h"
-#include "menu.h"
-#include "ent_skateshop.h"
-#include "addon.h"
-#include "input.h"
-#include "ent_tornado.h"
-
-#include "vg/vg_rigidbody.h"
-#include "scene_rigidbody.h"
-#include "player_glide.h"
-#include "player_dead.h"
-#include "player_walk.h"
-#include <string.h>
-
-struct player_skate player_skate;
-struct player_subsystem_interface player_subsystem_skate =
-{
- .system_register = player__skate_register,
- .bind = player__skate_bind,
- .pre_update = player__skate_pre_update,
- .update = player__skate_update,
- .post_update = player__skate_post_update,
- .im_gui = player__skate_im_gui,
- .animate = player__skate_animate,
- .pose = player__skate_pose,
- .effects = player__skate_effects,
- .post_animate = player__skate_post_animate,
- .network_animator_exchange = player__skate_animator_exchange,
- .sfx_oneshot = player__skate_sfx_oneshot,
- .sfx_comp = player__skate_comp_audio,
- .sfx_kill = player__skate_kill_audio,
-
- .animator_data = &player_skate.animator,
- .animator_size = sizeof(player_skate.animator),
- .name = "Skate"
-};
-
-void player__skate_bind(void){
- struct skeleton *sk = &localplayer.skeleton;
- rb_update_matrices( &localplayer.rb );
-
- struct { struct skeleton_anim **anim; const char *name; }
- bindings[] = {
- { &player_skate.anim_grind, "pose_grind" },
- { &player_skate.anim_grind_jump, "pose_grind_jump" },
- { &player_skate.anim_stand, "pose_stand" },
- { &player_skate.anim_highg, "pose_highg" },
- { &player_skate.anim_air, "pose_air" },
- { &player_skate.anim_slide, "pose_slide" },
- { &player_skate.anim_push, "push" },
- { &player_skate.anim_push_reverse, "push_reverse" },
- { &player_skate.anim_ollie, "ollie" },
- { &player_skate.anim_ollie_reverse,"ollie_reverse" },
- { &player_skate.anim_grabs, "grabs" },
- { &player_skate.anim_handplant, "handplant" },
- };
-
- for( u32 i=0; i<VG_ARRAY_LEN(bindings); i++ )
- *bindings[i].anim = skeleton_get_anim( sk, bindings[i].name );
-}
-
-void player__skate_kill_audio(void){
- audio_lock();
- if( player_skate.aud_main ){
- player_skate.aud_main =
- audio_channel_fadeout( player_skate.aud_main, 0.1f );
- }
- if( player_skate.aud_air ){
- player_skate.aud_air =
- audio_channel_fadeout( player_skate.aud_air, 0.1f );
- }
- if( player_skate.aud_slide ){
- player_skate.aud_slide =
- audio_channel_fadeout( player_skate.aud_slide, 0.1f );
- }
- audio_unlock();
-}
-
-/*
- * Collision detection routines
- *
- *
- */
-
-/*
- * Does collision detection on a sphere vs world, and applies some smoothing
- * filters to the manifold afterwards
- */
-static int skate_collide_smooth( m4x3f mtx, f32 r, rb_ct *man ){
- world_instance *world = world_current_instance();
-
- int len = 0;
- len = rb_sphere__scene( mtx, r, NULL, world->geo_bh, man,
- k_material_flag_walking );
-
- for( int i=0; i<len; i++ ){
- man[i].rba = &localplayer.rb;
- man[i].rbb = NULL;
- }
-
- rb_manifold_filter_coplanar( man, len, 0.03f );
-
- if( len > 1 ){
- rb_manifold_filter_backface( man, len );
- rb_manifold_filter_joint_edges( man, len, 0.03f );
- rb_manifold_filter_pairs( man, len, 0.03f );
- }
- int new_len = rb_manifold_apply_filtered( man, len );
- if( len && !new_len )
- len = 1;
- else
- len = new_len;
-
- return len;
-}
-
-struct grind_info
-{
- v3f co, dir, n;
-};
-
-static int skate_grind_scansq( v3f pos, v3f dir, float r,
- struct grind_info *inf ){
- world_instance *world = world_current_instance();
-
- v4f plane;
- v3_copy( dir, plane );
- v3_normalize( plane );
- plane[3] = v3_dot( plane, pos );
-
- boxf box;
- v3_add( pos, (v3f){ r, r, r }, box[1] );
- v3_sub( pos, (v3f){ r, r, r }, box[0] );
-
- struct grind_sample{
- v2f co;
- v2f normal;
- v3f normal3,
- centroid;
- }
- samples[48];
- int sample_count = 0;
-
- v2f support_min,
- support_max;
-
- v3f support_axis;
- v3_cross( plane, (v3f){0,1,0}, support_axis );
- v3_normalize( support_axis );
-
- bh_iter it;
- bh_iter_init_box( 0, &it, box );
- i32 idx;
-
- while( bh_next( world->geo_bh, &it, &idx ) ){
- u32 *ptri = &world->scene_geo.arrindices[ idx*3 ];
- v3f tri[3];
-
- struct world_surface *surf = world_tri_index_surface(world,ptri[0]);
- if( !(surf->info.flags & k_material_flag_grindable) )
- continue;
-
- for( int j=0; j<3; j++ )
- v3_copy( world->scene_geo.arrvertices[ptri[j]].co, tri[j] );
-
- for( int j=0; j<3; j++ ){
- int i0 = j,
- i1 = (j+1) % 3;
-
- struct grind_sample *sample = &samples[ sample_count ];
- v3f co;
-
- if( plane_segment( plane, tri[i0], tri[i1], co ) ){
- v3f d;
- v3_sub( co, pos, d );
- if( v3_length2( d ) > r*r )
- continue;
-
- v3f va, vb, normal;
- v3_sub( tri[1], tri[0], va );
- v3_sub( tri[2], tri[0], vb );
- v3_cross( va, vb, normal );
-
- sample->normal[0] = v3_dot( support_axis, normal );
- sample->normal[1] = normal[1];
- sample->co[0] = v3_dot( support_axis, d );
- sample->co[1] = d[1];
-
- v3_copy( normal, sample->normal3 ); /* normalize later
- if we want to us it */
-
- v3_muls( tri[0], 1.0f/3.0f, sample->centroid );
- v3_muladds( sample->centroid, tri[1], 1.0f/3.0f, sample->centroid );
- v3_muladds( sample->centroid, tri[2], 1.0f/3.0f, sample->centroid );
-
- v2_normalize( sample->normal );
- sample_count ++;
-
- if( sample_count == VG_ARRAY_LEN( samples ) )
- goto too_many_samples;
- }
- }
- }
-
-too_many_samples:
-
- if( sample_count < 2 )
- return 0;
-
- v3f average_direction,
- average_normal;
-
- v2f min_co, max_co;
- v2_fill( min_co, INFINITY );
- v2_fill( max_co, -INFINITY );
-
- v3_zero( average_direction );
- v3_zero( average_normal );
-
- int passed_samples = 0;
-
- for( int i=0; i<sample_count-1; i++ ){
- struct grind_sample *si, *sj;
-
- si = &samples[i];
-
- for( int j=i+1; j<sample_count; j++ ){
- if( i == j )
- continue;
-
- sj = &samples[j];
-
- /* non overlapping */
- if( v2_dist2( si->co, sj->co ) >= (0.01f*0.01f) )
- continue;
-
- /* not sharp angle */
- if( v2_dot( si->normal, sj->normal ) >= 0.7f )
- continue;
-
- /* not convex */
- v3f v0;
- v3_sub( sj->centroid, si->centroid, v0 );
- if( v3_dot( v0, si->normal3 ) >= 0.0f ||
- v3_dot( v0, sj->normal3 ) <= 0.0f )
- continue;
-
- v2_minv( sj->co, min_co, min_co );
- v2_maxv( sj->co, max_co, max_co );
-
- v3f n0, n1, dir;
- v3_copy( si->normal3, n0 );
- v3_copy( sj->normal3, n1 );
- v3_cross( n0, n1, dir );
-
- if( v3_length2( dir ) <= 0.000001f )
- continue;
-
- v3_normalize( dir );
-
- /* make sure the directions all face a common hemisphere */
- v3_muls( dir, vg_signf(v3_dot(dir,plane)), dir );
- v3_add( average_direction, dir, average_direction );
-
- float yi = si->normal3[1],
- yj = sj->normal3[1];
-
- if( yi > yj ) v3_add( si->normal3, average_normal, average_normal );
- else v3_add( sj->normal3, average_normal, average_normal );
-
- passed_samples ++;
- }
- }
-
- if( !passed_samples )
- return 0;
-
- if( (v3_length2( average_direction ) <= 0.001f) ||
- (v3_length2( average_normal ) <= 0.001f ) )
- return 0;
-
- float div = 1.0f/(float)passed_samples;
- v3_normalize( average_direction );
- v3_normalize( average_normal );
-
- v2f average_coord;
- v2_add( min_co, max_co, average_coord );
- v2_muls( average_coord, 0.5f, average_coord );
-
- v3_muls( support_axis, average_coord[0], inf->co );
- inf->co[1] += average_coord[1];
- v3_add( pos, inf->co, inf->co );
- v3_copy( average_normal, inf->n );
- v3_copy( average_direction, inf->dir );
-
- vg_line_point( inf->co, 0.02f, VG__GREEN );
- vg_line_arrow( inf->co, average_direction, 0.3f, VG__GREEN );
- vg_line_arrow( inf->co, inf->n, 0.2f, VG__CYAN );
-
- return passed_samples;
-}
-
-static void reset_jump_info( jump_info *inf ){
- inf->log_length = 0;
- inf->land_dist = 0.0f;
- inf->score = 0.0f;
- inf->type = k_prediction_unset;
- v3_zero( inf->apex );
-}
-
-static int create_jumps_to_hit_target( jump_info *jumps,
- v3f target, float max_angle_delta,
- float gravity ){
- /* calculate the exact 2 solutions to jump onto that grind spot */
-
- v3f v0;
- v3_sub( target, localplayer.rb.co, v0 );
-
- v3f ax;
- v3_copy( v0, ax );
- ax[1] = 0.0f;
- v3_normalize( ax );
-
- v2f d = { v3_dot( ax, v0 ), v0[1] },
- v = { v3_dot( ax, localplayer.rb.v ), localplayer.rb.v[1] };
-
- float a = atan2f( v[1], v[0] ),
- m = v2_length( v ),
- root = m*m*m*m - gravity*(gravity*d[0]*d[0] + 2.0f*d[1]*m*m);
-
- int valid_count = 0;
-
- if( root > 0.0f ){
- root = sqrtf( root );
- float a0 = atanf( (m*m + root) / (gravity * d[0]) ),
- a1 = atanf( (m*m - root) / (gravity * d[0]) );
-
- if( fabsf(a0-a) < max_angle_delta ){
- jump_info *inf = &jumps[ valid_count ++ ];
- reset_jump_info( inf );
-
- v3_muls( ax, cosf( a0 ) * m, inf->v );
- inf->v[1] += sinf( a0 ) * m;
- inf->land_dist = d[0] / (cosf(a0)*m);
- inf->gravity = gravity;
-
- v3_copy( target, inf->log[inf->log_length ++] );
- }
-
- if( fabsf(a1-a) < max_angle_delta ){
- jump_info *inf = &jumps[ valid_count ++ ];
- reset_jump_info( inf );
-
- v3_muls( ax, cosf( a1 ) * m, inf->v );
- inf->v[1] += sinf( a1 ) * m;
- inf->land_dist = d[0] / (cosf(a1)*m);
- inf->gravity = gravity;
-
- v3_copy( target, inf->log[inf->log_length ++] );
- }
- }
-
- return valid_count;
-}
-
-void player__approximate_best_trajectory(void)
-{
- world_instance *world0 = world_current_instance();
-
- float k_trace_delta = vg.time_fixed_delta * 10.0f;
- struct player_skate_state *state = &player_skate.state;
-
- state->air_start = vg.time;
- v3_copy( localplayer.rb.v, state->air_init_v );
- v3_copy( localplayer.rb.co, state->air_init_co );
-
- player_skate.possible_jump_count = 0;
-
- v3f axis;
- v3_cross( localplayer.rb.v, localplayer.rb.to_world[1], axis );
- v3_normalize( axis );
-
- /* at high slopes, Y component is low */
- float upness = localplayer.rb.to_world[1][1],
- angle_begin = -(1.0f-fabsf( upness )),
- angle_end = 1.0f;
-
- struct grind_info grind;
- int grind_located = 0;
- float grind_located_gravity = k_gravity;
-
-
- v3f launch_v_bounds[2];
-
- for( int i=0; i<2; i++ ){
- v3_copy( localplayer.rb.v, launch_v_bounds[i] );
- float ang = (float[]){ angle_begin, angle_end }[ i ];
- ang *= 0.15f;
-
- v4f qbias;
- q_axis_angle( qbias, axis, ang );
- q_mulv( qbias, launch_v_bounds[i], launch_v_bounds[i] );
- }
-
- for( int m=0;m<=30; m++ ){
- jump_info *inf =
- &player_skate.possible_jumps[ player_skate.possible_jump_count ++ ];
- reset_jump_info( inf );
-
- v3f launch_co, launch_v, co0, co1;
- v3_copy( localplayer.rb.co, launch_co );
- v3_copy( localplayer.rb.v, launch_v );
- v3_copy( launch_co, co0 );
- world_instance *trace_world = world0;
-
- float vt = (float)m * (1.0f/30.0f),
- ang = vg_lerpf( angle_begin, angle_end, vt ) * 0.15f;
-
- v4f qbias;
- q_axis_angle( qbias, axis, ang );
- q_mulv( qbias, launch_v, launch_v );
-
- float yaw_sketch = 1.0f-fabsf(upness);
-
- float yaw_bias = ((float)(m%3) - 1.0f) * 0.08f * yaw_sketch;
- q_axis_angle( qbias, localplayer.rb.to_world[1], yaw_bias );
- q_mulv( qbias, launch_v, launch_v );
-
- float gravity_bias = vg_lerpf( 0.85f, 1.4f, vt ),
- gravity = k_gravity * gravity_bias;
- inf->gravity = gravity;
- v3_copy( launch_v, inf->v );
-
- /* initial conditions */
- v3f v;
- v3_copy( launch_v, v );
- v3_copy( launch_co, co1 );
-
- for( int i=1; i<=50; i++ ){
- f32 t = (f32)i * k_trace_delta;
-
- /* integrate forces */
- v3f a;
- ent_tornado_forces( co1, v, a );
- a[1] -= gravity;
-
- /* position */
- v3_muladds( co1, v, k_trace_delta, co1 );
- v3_muladds( co1, a, 0.5f*k_trace_delta*k_trace_delta, co1 );
-
- /* velocity */
- v3_muladds( v, a, k_trace_delta, v );
-
- int search_for_grind = 1;
- if( grind_located ) search_for_grind = 0;
- if( v[1] > 0.0f ) search_for_grind = 0;
-
- /* REFACTOR */
-
- v3f closest={0.0f,0.0f,0.0f};
- if( search_for_grind ){
- if( bh_closest_point(trace_world->geo_bh,co1,closest,1.0f) != -1 ){
- float min_dist = 0.75f;
- min_dist *= min_dist;
-
- if( v3_dist2( closest, launch_co ) < min_dist )
- search_for_grind = 0;
-
- v3f bound[2];
-
- for( int j=0; j<2; j++ ){
- v3_muls( launch_v_bounds[j], t, bound[j] );
- bound[j][1] += -0.5f*gravity*t*t;
- v3_add( launch_co, bound[j], bound[j] );
- }
-
- float limh = vg_minf( 2.0f, t ),
- minh = vg_minf( bound[0][1], bound[1][1] )-limh,
- maxh = vg_maxf( bound[0][1], bound[1][1] )+limh;
-
- if( (closest[1] < minh) || (closest[1] > maxh) ){
- search_for_grind = 0;
- }
- }
- else
- search_for_grind = 0;
- }
-
- if( search_for_grind ){
- if( skate_grind_scansq( closest, v, 0.5f, &grind ) ){
- /* check alignment */
- v2f v0 = { v[0], v[2] },
- v1 = { grind.dir[0], grind.dir[2] };
-
- v2_normalize( v0 );
- v2_normalize( v1 );
-
- float a = v2_dot( v0, v1 );
-
- float a_min = cosf( VG_PIf * 0.185f );
- if( state->grind_cooldown )
- a_min = cosf( VG_PIf * 0.05f );
-
- /* check speed */
- if( (fabsf(v3_dot( v, grind.dir ))>=k_grind_axel_min_vel) &&
- (a >= a_min) &&
- (fabsf(grind.dir[1]) < 0.70710678118654752f))
- {
- grind_located = 1;
- grind_located_gravity = inf->gravity;
- }
- }
- }
-
- if( trace_world->rendering_gate ){
- ent_gate *gate = trace_world->rendering_gate;
- if( gate_intersect( gate, co1, co0 ) ){
- m4x3_mulv( gate->transport, co0, co0 );
- m4x3_mulv( gate->transport, co1, co1 );
- m3x3_mulv( gate->transport, launch_v, launch_v);
- m4x3_mulv( gate->transport, launch_co, launch_co );
-
- if( gate->flags & k_ent_gate_nonlocal )
- trace_world = &world_static.instances[ gate->target ];
- }
- }
-
- float t1;
- v3f n;
-
- float scan_radius = k_board_radius;
- scan_radius *= vg_clampf( t, 0.02f, 1.0f );
-
- int idx = spherecast_world( trace_world, co0, co1, scan_radius, &t1, n,
- k_material_flag_walking );
- if( idx != -1 ){
- v3f co;
- v3_lerp( co0, co1, t1, co );
- v3_copy( co, inf->log[ inf->log_length ++ ] );
-
- v3_copy( n, inf->n );
- u32 *tri = &trace_world->scene_geo.arrindices[ idx*3 ];
- struct world_surface *surf =
- world_tri_index_surface( trace_world, tri[0] );
-
- inf->type = k_prediction_land;
- inf->score = -v3_dot( v, inf->n );
- inf->land_dist = t + k_trace_delta * t1;
-
- /* Bias prediction towords ramps */
- if( !(surf->info.flags & k_material_flag_skate_target) )
- inf->score *= 10.0f;
-
- if( surf->info.flags & k_material_flag_boundary )
- player_skate.possible_jump_count --;
-
- break;
- }
-
- if( i % 3 == 0 )
- v3_copy( co1, inf->log[ inf->log_length ++ ] );
- v3_copy( co1, co0 );
- }
-
- if( inf->type == k_prediction_unset )
- player_skate.possible_jump_count --;
- }
-
- if( grind_located ){
- jump_info grind_jumps[2];
-
- int valid_count =
- create_jumps_to_hit_target( grind_jumps, grind.co,
- 0.175f*VG_PIf, grind_located_gravity );
-
- /* knock out original landing points in the 1m area */
- for( u32 j=0; j<player_skate.possible_jump_count; j++ ){
- jump_info *jump = &player_skate.possible_jumps[ j ];
- float dist = v3_dist2( jump->log[jump->log_length-1], grind.co );
- float descale = 1.0f-vg_minf(1.0f,dist);
- jump->score += descale*3.0f;
- }
-
- for( int i=0; i<valid_count; i++ ){
- jump_info *jump = &grind_jumps[i];
- jump->type = k_prediction_grind;
-
- v3f launch_v, launch_co, co0, co1;
-
- v3_copy( jump->v, launch_v );
- v3_copy( localplayer.rb.co, launch_co );
-
- float t = 0.05f * jump->land_dist;
- v3_muls( launch_v, t, co0 );
- co0[1] += -0.5f * jump->gravity * t*t;
- v3_add( launch_co, co0, co0 );
-
- /* rough scan to make sure we dont collide with anything */
- for( int j=1; j<=16; j++ ){
- t = (float)j*(1.0f/16.0f);
- t *= 0.9f;
- t += 0.05f;
- t *= jump->land_dist;
-
- v3_muls( launch_v, t, co1 );
- co1[1] += -0.5f * jump->gravity * t*t;
- v3_add( launch_co, co1, co1 );
-
- float t1;
- v3f n;
-
- int idx = spherecast_world( world0, co0,co1,
- k_board_radius*0.1f, &t1, n,
- k_material_flag_walking );
- if( idx != -1 ){
- goto invalidated_grind;
- }
-
- v3_copy( co1, co0 );
- }
-
- v3_copy( grind.n, jump->n );
-
- /* determine score */
- v3f ve;
- v3_copy( jump->v, ve );
- ve[1] += -jump->gravity*jump->land_dist;
- jump->score = -v3_dot( ve, grind.n ) * 0.9f;
-
- player_skate.possible_jumps[ player_skate.possible_jump_count ++ ] =
- *jump;
-
- continue;
-invalidated_grind:;
- }
- }
-
-
- float score_min = INFINITY,
- score_max = -INFINITY;
-
- jump_info *best = NULL;
-
- for( int i=0; i<player_skate.possible_jump_count; i ++ ){
- jump_info *jump = &player_skate.possible_jumps[i];
-
- if( jump->score < score_min )
- best = jump;
-
- score_min = vg_minf( score_min, jump->score );
- score_max = vg_maxf( score_max, jump->score );
- }
-
- for( int i=0; i<player_skate.possible_jump_count; i ++ ){
- jump_info *jump = &player_skate.possible_jumps[i];
- float s = jump->score;
-
- s -= score_min;
- s /= (score_max-score_min);
- s = 1.0f - s;
-
- jump->score = s;
- jump->colour = s * 255.0f;
-
- if( jump == best )
- jump->colour <<= 16;
- else if( jump->type == k_prediction_land )
- jump->colour <<= 8;
-
- jump->colour |= 0xff000000;
- }
-
- if( best ){
- v3_copy( best->n, state->land_normal );
- v3_copy( best->v, localplayer.rb.v );
- state->land_dist = best->land_dist;
- state->gravity_bias = best->gravity;
-
- if( best->type == k_prediction_grind ){
- state->activity = k_skate_activity_air_to_grind;
- }
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
- v2_normalize_clamp( steer );
-
- if( (fabsf(steer[1]) > 0.5f) && (state->land_dist >= 1.5f) ){
- state->flip_rate = (1.0f/state->land_dist) * vg_signf(steer[1]) *
- state->reverse ;
- state->flip_time = 0.0f;
- v3_copy( localplayer.rb.to_world[0], state->flip_axis );
- }
- else{
- state->flip_rate = 0.0f;
- v3_zero( state->flip_axis );
- }
- }
- else
- v3_copy( (v3f){0,1,0}, state->land_normal );
-}
-
-/*
- *
- * Varius physics models
- * ------------------------------------------------
- */
-
-/*
- * Air control, no real physics
- */
-static void skate_apply_air_model(void){
- struct player_skate_state *state = &player_skate.state;
-
- if( state->activity_prev > k_skate_activity_air_to_grind )
- player__approximate_best_trajectory();
-
- float angle = v3_dot( localplayer.rb.to_world[1], state->land_normal );
- angle = vg_clampf( angle, -1.0f, 1.0f );
- v3f axis;
- v3_cross( localplayer.rb.to_world[1], state->land_normal, axis );
-
- v4f correction;
- q_axis_angle( correction, axis,
- acosf(angle)*2.0f*VG_TIMESTEP_FIXED );
- q_mul( correction, localplayer.rb.q, localplayer.rb.q );
-}
-
-static enum trick_type player_skate_trick_input(void);
-static void skate_apply_trick_model(void){
- struct player_skate_state *state = &player_skate.state;
-
- v3f Fd, Fs, F;
- v3f strength = { 3.7f, 3.6f, 8.0f };
-
- v3_muls( state->trick_residualv, -4.0f , Fd );
- v3_muls( state->trick_residuald, -10.0f, Fs );
- v3_add( Fd, Fs, F );
- v3_mul( strength, F, F );
-
- v3_muladds( state->trick_residualv, F, vg.time_fixed_delta,
- state->trick_residualv );
- v3_muladds( state->trick_residuald, state->trick_residualv,
- vg.time_fixed_delta, state->trick_residuald );
-
- if( state->activity <= k_skate_activity_air_to_grind ){
- if( v3_length2( state->trick_vel ) < 0.0001f )
- return;
-
- int carry_on = state->trick_type == player_skate_trick_input();
-
- /* we assume velocities share a common divisor, in which case the
- * interval is the minimum value (if not zero) */
-
- float min_rate = 99999.0f;
-
- for( int i=0; i<3; i++ ){
- float v = state->trick_vel[i];
- if( (v > 0.0f) && (v < min_rate) )
- min_rate = v;
- }
-
- float interval = 1.0f / min_rate,
- current = floorf( state->trick_time ),
- next_end = current+1.0f;
-
-
- /* integrate trick velocities */
- v3_muladds( state->trick_euler, state->trick_vel, vg.time_fixed_delta,
- state->trick_euler );
-
- if( !carry_on && (state->trick_time + vg.time_fixed_delta/interval >= next_end) ){
- state->trick_time = 0.0f;
- state->trick_euler[0] = roundf( state->trick_euler[0] );
- state->trick_euler[1] = roundf( state->trick_euler[1] );
- state->trick_euler[2] = roundf( state->trick_euler[2] );
- v3_copy( state->trick_vel, state->trick_residualv );
- v3_zero( state->trick_vel );
-
- audio_lock();
- audio_oneshot_3d( &audio_flips[vg_randu32(&vg.rand)%4],
- localplayer.rb.co, 40.0f, 1.0f );
- audio_unlock();
- }
- else
- state->trick_time += vg.time_fixed_delta / interval;
- }
- else{
- if( (v3_length2(state->trick_vel) >= 0.0001f ) &&
- state->trick_time > 0.2f)
- {
- vg_info( "player fell off due to lack of skill\n" );
- player__dead_transition( k_player_die_type_feet );
- }
-
- state->trick_euler[0] = roundf( state->trick_euler[0] );
- state->trick_euler[1] = roundf( state->trick_euler[1] );
- state->trick_euler[2] = roundf( state->trick_euler[2] );
- state->trick_time = 0.0f;
- v3_zero( state->trick_vel );
- }
-}
-
-static void skate_apply_grab_model(void){
- struct player_skate_state *state = &player_skate.state;
-
- float grabt = axis_state( k_sraxis_grab );
-
- if( grabt > 0.5f ){
- v2_muladds( state->grab_mouse_delta, vg.mouse_delta, 0.02f,
- state->grab_mouse_delta );
-
- v2_normalize_clamp( state->grab_mouse_delta );
- }
- else
- v2_zero( state->grab_mouse_delta );
-
- state->grabbing = vg_lerpf( state->grabbing, grabt, 8.4f*vg.time_fixed_delta );
-}
-
-static void skate_apply_steering_model(void){
- struct player_skate_state *state = &player_skate.state;
-
- v2f jsteer;
- joystick_state( k_srjoystick_steer, jsteer );
-
- /* Steering */
- float steer = jsteer[0],
- grab = axis_state( k_sraxis_grab );
-
- steer = vg_signf( steer ) * steer*steer * k_steer_ground;
-
- v3f steer_axis;
- v3_muls( localplayer.rb.to_world[1], -vg_signf( steer ), steer_axis );
-
- float rate = 26.0f,
- top = 1.0f;
-
- f32 skid_target = 0.0f;
-
- if( state->activity <= k_skate_activity_air_to_grind ){
- rate = 6.0f * fabsf(steer);
- top = 1.5f;
- }
- else{
- /* rotate slower when grabbing on ground */
- steer *= (1.0f-(state->jump_charge+grab)*0.4f);
-
- if( state->activity == k_skate_activity_grind_5050 ){
- rate = 0.0f;
- top = 0.0f;
- }
-
- else if( state->activity >= k_skate_activity_grind_any ){
- rate *= fabsf(steer);
-
- float a = 0.8f * -steer * vg.time_fixed_delta;
-
- v4f q;
- q_axis_angle( q, localplayer.rb.to_world[1], a );
- q_mulv( q, player_skate.grind_vec, player_skate.grind_vec );
-
- v3_normalize( player_skate.grind_vec );
- }
-
- else if( state->manual_direction ){
- rate = 35.0f;
- top = 1.5f;
- }
- else {
- f32 skid = axis_state(k_sraxis_skid);
-
- /* skids on keyboard lock to the first direction pressed */
- if( vg_input.display_input_method == k_input_method_kbm ){
- if( button_press(k_srbind_skid) && (fabsf(state->skid)<0.01f) &&
- (fabsf(steer) > 0.4f) ){
- state->skid = vg_signf( steer ) * 0.02f;
- }
-
- if( button_press(k_srbind_skid) && (fabsf(state->skid)>0.01f) ){
- skid_target = vg_signf( state->skid );
- }
- }
- else {
- if( fabsf(skid) > 0.1f ){
- skid_target = skid;
- }
- }
- }
-
- if( grab < 0.5f ){
- top *= 1.0f+v3_length( state->throw_v )*k_mmthrow_steer;
- }
- }
-
- vg_slewf( &state->skid, skid_target, vg.time_fixed_delta*(1.0f/0.1f) );
- steer = vg_lerpf( steer, state->skid*k_steer_ground*0.5f,
- fabsf(state->skid*0.8f) );
-
- float current = v3_dot( localplayer.rb.to_world[1], localplayer.rb.w ),
- addspeed = (steer * -top) - current,
- maxaccel = rate * vg.time_fixed_delta,
- accel = vg_clampf( addspeed, -maxaccel, maxaccel );
-
- v3_muladds( localplayer.rb.w, localplayer.rb.to_world[1],
- accel, localplayer.rb.w );
-}
-
-/*
- * Computes friction and surface interface model
- */
-static void skate_apply_friction_model(void){
- struct player_skate_state *state = &player_skate.state;
-
- /*
- * Computing localized friction forces for controlling the character
- * Friction across X is significantly more than Z
- */
-
- v3f vel;
- m3x3_mulv( localplayer.rb.to_local, localplayer.rb.v, vel );
- float slip = 0.0f;
-
- if( fabsf(vel[2]) > 0.01f )
- slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
-
- if( fabsf( slip ) > 1.2f )
- slip = vg_signf( slip ) * 1.2f;
-
- state->slip = slip;
- state->reverse = -vg_signf(vel[2]);
-
- f32 lat = k_friction_lat;
-
- if( fabsf(axis_state(k_sraxis_skid)) > 0.1f ){
- if( (player_skate.surface == k_surface_prop_snow) ||
- (player_skate.surface == k_surface_prop_sand) ){
- lat *= 8.0f;
- }
- else
- lat *= 1.5f;
- }
-
- if( player_skate.surface == k_surface_prop_snow )
- lat *= 0.5f;
- else if( player_skate.surface == k_surface_prop_sand )
- lat *= 0.6f;
-
- vel[0] += vg_cfrictf( vel[0], lat * vg.time_fixed_delta );
- vel[2] += vg_cfrictf( vel[2], k_friction_resistance * vg.time_fixed_delta );
-
- /* Pushing additive force */
-
- if( !button_press( k_srbind_jump ) && (fabsf(state->skid)<0.1f) ){
- if( button_press( k_srbind_push ) || (vg.time-state->start_push<0.75) ){
- if( (vg.time - state->cur_push) > 0.25 )
- state->start_push = vg.time;
-
- state->cur_push = vg.time;
-
- double push_time = vg.time - state->start_push;
-
- float cycle_time = push_time*k_push_cycle_rate,
- accel = k_push_accel * (sinf(cycle_time)*0.5f+0.5f),
- amt = accel * VG_TIMESTEP_FIXED,
- current = v3_length( vel ),
- new_vel = vg_minf( current + amt, k_max_push_speed ),
- delta = new_vel - vg_minf( current, k_max_push_speed );
-
- vel[2] += delta * -state->reverse;
- }
- }
-
- /* Send back to velocity */
- m3x3_mulv( localplayer.rb.to_world, vel, localplayer.rb.v );
-}
-
-static void skate_apply_jump_model(void){
- struct player_skate_state *state = &player_skate.state;
- int charging_jump_prev = state->charging_jump;
- state->charging_jump = button_press( k_srbind_jump );
-
- /* Cannot charge this in air */
- if( state->activity <= k_skate_activity_air_to_grind ){
- state->charging_jump = 0;
- return;
- }
-
- if( state->charging_jump ){
- state->jump_charge += vg.time_fixed_delta * k_jump_charge_speed;
-
- if( !charging_jump_prev )
- state->jump_dir = state->reverse>0.0f? 1: 0;
- }
- else{
- state->jump_charge -= k_jump_charge_speed * vg.time_fixed_delta;
- }
-
- state->jump_charge = vg_clampf( state->jump_charge, 0.0f, 1.0f );
-
- /* player let go after charging past 0.2: trigger jump */
- if( (!state->charging_jump) && (state->jump_charge > 0.2f) ){
- v3f jumpdir;
-
- /* Launch more up if alignment is up else improve velocity */
- float aup = localplayer.rb.to_world[1][1],
- mod = 0.5f,
- dir = mod + fabsf(aup)*(1.0f-mod);
-
- if( state->activity == k_skate_activity_ground ){
- v3_copy( localplayer.rb.v, jumpdir );
- v3_normalize( jumpdir );
- v3_muls( jumpdir, 1.0f-dir, jumpdir );
- v3_muladds( jumpdir, localplayer.rb.to_world[1], dir, jumpdir );
- v3_normalize( jumpdir );
- }else{
- v3_copy( state->up_dir, jumpdir );
- state->grind_cooldown = 30;
- state->activity = k_skate_activity_ground;
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- float tilt = steer[0] * 0.3f;
- tilt *= vg_signf(v3_dot( localplayer.rb.v,
- player_skate.grind_dir ));
-
- v4f qtilt;
- q_axis_angle( qtilt, player_skate.grind_dir, tilt );
- q_mulv( qtilt, jumpdir, jumpdir );
- }
- state->surface_cooldown = 10;
- state->trick_input_collect = 0.0f;
-
- float force = k_jump_force*state->jump_charge;
- v3_muladds( localplayer.rb.v, jumpdir, force, localplayer.rb.v );
- state->jump_charge = 0.0f;
- state->jump_time = vg.time;
- player__networked_sfx( k_player_subsystem_skate, 32,
- k_player_skate_soundeffect_jump,
- localplayer.rb.co, 1.0f );
- }
-}
-
-static void skate_apply_handplant_model(void){
- struct player_skate_state *state = &player_skate.state;
- if( localplayer.rb.to_world[1][1] < -0.1f ) return;
- if( localplayer.rb.to_world[1][1] > 0.6f ) return;
- if( !( button_press(k_srbind_skid) || (fabsf(state->skid)>0.1f)) ) return;
-
- v3f lco = { 0.0f, -0.2f, -state->reverse },
- co, dir;
- m4x3_mulv( localplayer.rb.to_world, lco, co );
- v3_muls( localplayer.rb.to_world[2], state->reverse, dir );
- vg_line_arrow( co, dir, 0.13f, 0xff000000 );
-
- ray_hit hit = { .dist = 2.0f };
- if( ray_world( world_current_instance(), co, dir,
- &hit, k_material_flag_ghosts )) {
- vg_line( co, hit.pos, 0xff000000 );
- vg_line_point( hit.pos, 0.1f, 0xff000000 );
-
- if( hit.normal[1] < 0.7f ) return;
- if( hit.dist < 0.95f ) return;
-
- state->activity = k_skate_activity_handplant;
- state->handplant_t = 0.0f;
- v3_copy( localplayer.rb.co, state->store_co );
- v3_copy( localplayer.rb.v, state->air_init_v );
- v4_copy( localplayer.rb.q, state->store_q );
- v3_copy( state->cog, state->store_cog );
- v3_copy( state->cog_v, state->store_cog_v );
- v4_copy( state->smoothed_rotation, state->store_smoothed );
- }
-}
-
-static void skate_apply_pump_model(void){
- struct player_skate_state *state = &player_skate.state;
-
- if( state->activity != k_skate_activity_ground ){
- v3_zero( state->throw_v );
- return;
- }
-
- /* Throw / collect routine
- */
- if( axis_state( k_sraxis_grab ) > 0.5f ){
- if( state->activity == k_skate_activity_ground ){
- /* Throw */
- v3_muls( localplayer.rb.to_world[1], k_mmthrow_scale, state->throw_v );
- }
- }
- else{
- /* Collect */
- f32 doty = v3_dot( localplayer.rb.to_world[1], state->throw_v );
-
- v3f Fl, Fv;
- v3_muladds( state->throw_v, localplayer.rb.to_world[1], -doty, Fl);
- player_skate.collect_feedback = v3_length(Fl) * 4.0f;
-
- if( state->activity == k_skate_activity_ground ){
- if( v3_length2(localplayer.rb.v)<(20.0f*20.0f) ){
- v3_muladds( localplayer.rb.v, Fl,
- k_mmcollect_lat, localplayer.rb.v );
- }
- v3_muladds( state->throw_v, Fl, -k_mmcollect_lat, state->throw_v );
- }
-
- v3_muls( localplayer.rb.to_world[1], -doty, Fv );
- v3_muladds( localplayer.rb.v, Fv, k_mmcollect_vert, localplayer.rb.v );
- v3_muladds( state->throw_v, Fv, k_mmcollect_vert, state->throw_v );
- }
-
- /* Decay */
- if( v3_length2( state->throw_v ) > 0.0001f ){
- v3f dir;
- v3_copy( state->throw_v, dir );
- v3_normalize( dir );
-
- float max = v3_dot( dir, state->throw_v ),
- amt = vg_minf( k_mmdecay * vg.time_fixed_delta, max );
- v3_muladds( state->throw_v, dir, -amt, state->throw_v );
- }
-}
-
-static void skate_apply_cog_model(void){
- struct player_skate_state *state = &player_skate.state;
-
- v3f ideal_cog, ideal_diff, ideal_dir;
- v3_copy( state->up_dir, ideal_dir );
- v3_normalize( ideal_dir );
-
- float grab = axis_state( k_sraxis_grab );
- v3_muladds( localplayer.rb.co, ideal_dir, 1.0f-grab, ideal_cog );
- v3_sub( ideal_cog, state->cog, ideal_diff );
-
- /* Apply velocities */
- v3f rv;
- v3_sub( localplayer.rb.v, state->cog_v, rv );
-
- v3f F;
- v3_muls( ideal_diff, -k_cog_spring * 60.0f, F );
- v3_muladds( F, rv, -k_cog_damp * 60.0f, F );
-
- float ra = k_cog_mass_ratio,
- rb = 1.0f-k_cog_mass_ratio;
-
- /* Apply forces & intergrate */
- v3_muladds( state->cog_v, F, -rb, state->cog_v );
- state->cog_v[1] += -9.8f * vg.time_fixed_delta;
- v3_muladds( state->cog, state->cog_v, vg.time_fixed_delta, state->cog );
-}
-
-static void skate_integrate(void){
- struct player_skate_state *state = &player_skate.state;
-
- float rate_x = 1.0f - (vg.time_fixed_delta * 3.0f),
- rate_z = rate_x,
- rate_y = 1.0f;
-
- if( state->activity >= k_skate_activity_grind_any ){
- rate_x = 1.0f-(16.0f*vg.time_fixed_delta);
- rate_y = 1.0f-(10.0f*vg.time_fixed_delta);
- rate_z = 1.0f-(40.0f*vg.time_fixed_delta);
- }
-
- float wx = v3_dot( localplayer.rb.w, localplayer.rb.to_world[0] ) * rate_x,
- wy = v3_dot( localplayer.rb.w, localplayer.rb.to_world[1] ) * rate_y,
- wz = v3_dot( localplayer.rb.w, localplayer.rb.to_world[2] ) * rate_z;
-
- v3_muls( localplayer.rb.to_world[0], wx, localplayer.rb.w );
- v3_muladds( localplayer.rb.w, localplayer.rb.to_world[1], wy,
- localplayer.rb.w );
- v3_muladds( localplayer.rb.w, localplayer.rb.to_world[2], wz,
- localplayer.rb.w );
-
- state->flip_time += state->flip_rate * vg.time_fixed_delta;
- rb_update_matrices( &localplayer.rb );
-}
-
-static enum trick_type player_skate_trick_input(void){
- return (button_press( k_srbind_trick0 ) ) |
- (button_press( k_srbind_trick1 ) << 1) |
- (button_press( k_srbind_trick2 ) << 1) |
- (button_press( k_srbind_trick2 ) );
-}
-
-void player__skate_pre_update(void){
- struct player_skate_state *state = &player_skate.state;
-
- if( state->activity == k_skate_activity_handplant ){
- state->handplant_t += vg.time_delta;
- mdl_keyframe hpose[32];
-
- struct skeleton_anim *anim = player_skate.anim_handplant;
-
- int end = !skeleton_sample_anim_clamped(
- &localplayer.skeleton, anim,
- state->handplant_t, hpose );
-
- if( state->reverse < 0.0f )
- player_mirror_pose( hpose, hpose );
-
- mdl_keyframe *kf_world = &hpose[ localplayer.id_world -1 ];
- m4x3f world, mmdl, world_view;
- q_m3x3( kf_world->q, world );
- v3_copy( kf_world->co, world[3] );
-
- /* original mtx */
- q_m3x3( state->store_q, mmdl );
- v3_copy( state->store_co, mmdl[3] );
- m4x3_mul( mmdl, world, world_view );
-
- vg_line_arrow( world_view[3], world_view[0], 1.0f, 0xff0000ff );
- vg_line_arrow( world_view[3], world_view[1], 1.0f, 0xff00ff00 );
- vg_line_arrow( world_view[3], world_view[2], 1.0f, 0xffff0000 );
-
- m4x3f invworld;
- m4x3_invert_affine( world, invworld );
- m4x3_mul( mmdl, invworld, world_view );
-
- v3_copy( world_view[3], localplayer.rb.co );
- m3x3_q( world_view, localplayer.rb.q );
-
- /* new * old^-1 = transfer function */
- m4x3f transfer;
- m4x3_invert_affine( mmdl, transfer );
- m4x3_mul( world_view, transfer, transfer );
-
- m3x3_mulv( transfer, state->air_init_v, localplayer.rb.v );
- m3x3_mulv( transfer, state->store_cog_v, state->cog_v );
-
- m4x3_mulv( transfer, state->store_cog, state->cog );
- v3_muladds( state->cog, localplayer.rb.to_world[1],
- -state->handplant_t*0.5f, state->cog );
-
- v4f qtransfer;
- m3x3_q( transfer, qtransfer );
- q_mul( qtransfer, state->store_smoothed, state->smoothed_rotation );
- q_normalize( state->smoothed_rotation );
- rb_update_matrices( &localplayer.rb );
-
- if( end ){
- state->activity = k_skate_activity_air;
- }
- else return;
- }
-
- if( button_down(k_srbind_use) && (v3_length2(state->trick_vel) < 0.01f) ){
- localplayer.subsystem = k_player_subsystem_walk;
-
- if( (state->activity <= k_skate_activity_air_to_grind) &&
- localplayer.have_glider ){
- player_glide_transition();
- return;
- }
-
- v3f angles;
- v3_copy( localplayer.cam.angles, localplayer.angles );
- localplayer.angles[2] = 0.0f;
-
- v3f newpos, offset;
- m4x3_mulv( localplayer.rb.to_world, (v3f){0.0f,1.0f,0.0f}, newpos );
- v3_add( newpos, (v3f){0.0f,-1.0f,0.0f}, newpos );
- v3_sub( localplayer.rb.co, newpos, offset );
- v3_copy( newpos, localplayer.rb.co );
- v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], -0.1f,
- localplayer.rb.co );
-
- player__begin_holdout( offset );
- player__walk_transition( state->activity <= k_skate_activity_air_to_grind?
- 0: 1, state->trick_euler[0] );
-
- return;
- }
-
- enum trick_type trick = player_skate_trick_input();
- if( trick )
- state->trick_input_collect += vg.time_frame_delta;
- else
- state->trick_input_collect = 0.0f;
-
- if( state->activity <= k_skate_activity_air_to_grind ){
- if( trick && (state->trick_input_collect < 0.1f) ){
- if( state->trick_time == 0.0f ){
- audio_lock();
- audio_oneshot_3d( &audio_flips[vg_randu32(&vg.rand)%4],
- localplayer.rb.co, 40.0f, 1.0f );
- audio_unlock();
- }
-
- if( state->trick_time < 0.1f ){
- v3_zero( state->trick_vel );
-
- if( trick == k_trick_type_kickflip ){
- state->trick_vel[0] = 3.0f;
- }
- else if( trick == k_trick_type_shuvit ){
- state->trick_vel[2] = 3.0f;
- }
- else if( trick == k_trick_type_treflip ){
- state->trick_vel[0] = 2.0f;
- state->trick_vel[2] = 2.0f;
- }
- state->trick_type = trick;
- }
- }
- }
- else
- state->trick_type = k_trick_type_none;
-}
-
-void player__skate_comp_audio( void *_animator ){
- struct player_skate_animator *animator = _animator;
- audio_lock();
-
- f32 air = ((animator->activity <= k_skate_activity_air_to_grind) ||
- (animator->activity == k_skate_activity_handplant))? 1.0f: 0.0f,
- speed = v3_length( animator->root_v ),
- attn = vg_minf( 1.0f, speed*0.1f ),
- slide = animator->slide;
-
- if( animator->activity >= k_skate_activity_grind_any )
- slide = 0.0f;
-
- f32 gate = skaterift.time_rate;
-
- if( skaterift.activity == k_skaterift_replay ){
- gate = vg_minf( 1.0f, fabsf(player_replay.track_velocity) );
- }
-
- f32
- vol_main = sqrtf( (1.0f-air)*attn*(1.0f-slide) * 0.4f ) * gate,
- vol_air = sqrtf( air *attn * 0.5f ) * gate,
- vol_slide = sqrtf( (1.0f-air)*attn*slide * 0.25f ) * gate;
-
- const u32 flags = AUDIO_FLAG_SPACIAL_3D|AUDIO_FLAG_LOOP;
-
- if( !player_skate.aud_air ){
- player_skate.aud_air = audio_get_first_idle_channel();
- if( player_skate.aud_air )
- audio_channel_init( player_skate.aud_air, &audio_board[1], flags );
- }
-
- if( !player_skate.aud_slide ){
- player_skate.aud_slide = audio_get_first_idle_channel();
- if( player_skate.aud_slide )
- audio_channel_init( player_skate.aud_slide, &audio_board[2], flags );
- }
-
-
- /* brrrrrrrrrrrt sound for tiles and stuff
- * --------------------------------------------------------*/
- float sidechain_amt = 0.0f,
- hz = vg_maxf( speed * 2.0f, 2.0f );
-
- if( (animator->surface == k_surface_prop_tiles) &&
- (animator->activity < k_skate_activity_grind_any) )
- sidechain_amt = 1.0f;
- else
- sidechain_amt = 0.0f;
-
- audio_set_lfo_frequency( 0, hz );
- audio_set_lfo_wave( 0, k_lfo_polynomial_bipolar,
- vg_lerpf( 250.0f, 80.0f, attn ) );
-
- if( player_skate.sample_change_cooldown > 0.0f ){
- player_skate.sample_change_cooldown -= vg.time_frame_delta;
- }
- else{
- int sample_type = k_skate_sample_concrete;
-
- if( animator->activity == k_skate_activity_grind_5050 ){
- if( animator->surface == k_surface_prop_metal )
- sample_type = k_skate_sample_metal_scrape_generic;
- else
- sample_type = k_skate_sample_concrete_scrape_metal;
- }
- else if( (animator->activity == k_skate_activity_grind_back50) ||
- (animator->activity == k_skate_activity_grind_front50) )
- {
- if( animator->surface == k_surface_prop_metal ){
- sample_type = k_skate_sample_metal_scrape_generic;
- }
- else{
-#if 0
- float a = v3_dot( localplayer.rb.to_world[2],
- player_skate.grind_dir );
- if( fabsf(a) > 0.70710678118654752f )
- sample_type = k_skate_sample_concrete_scrape_wood;
- else
- sample_type = k_skate_sample_concrete_scrape_metal;
-#endif
-
- sample_type = k_skate_sample_concrete_scrape_wood;
- }
- }
- else if( animator->activity == k_skate_activity_grind_boardslide ){
- if( animator->surface == k_surface_prop_metal )
- sample_type = k_skate_sample_metal_scrape_generic;
- else
- sample_type = k_skate_sample_concrete_scrape_wood;
- }
-
- audio_clip *relevant_samples[] = {
- &audio_board[0],
- &audio_board[0],
- &audio_board[7],
- &audio_board[6],
- &audio_board[5]
- };
-
- if( (player_skate.main_sample_type != sample_type) ||
- (!player_skate.aud_main) ){
-
- player_skate.aud_main =
- audio_channel_crossfade( player_skate.aud_main,
- relevant_samples[sample_type],
- 0.06f, flags );
- player_skate.sample_change_cooldown = 0.1f;
- player_skate.main_sample_type = sample_type;
- }
- }
-
- if( player_skate.aud_main ){
- player_skate.aud_main->colour = 0x00103efe;
- audio_channel_set_spacial( player_skate.aud_main,
- animator->root_co, 40.0f );
- //audio_channel_slope_volume( player_skate.aud_main, 0.05f, vol_main );
- audio_channel_edit_volume( player_skate.aud_main, vol_main, 1 );
- audio_channel_sidechain_lfo( player_skate.aud_main, 0, sidechain_amt );
-
- float rate = 1.0f + (attn-0.5f)*0.2f;
- audio_channel_set_sampling_rate( player_skate.aud_main, rate );
- }
-
- if( player_skate.aud_slide ){
- player_skate.aud_slide->colour = 0x00103efe;
- audio_channel_set_spacial( player_skate.aud_slide,
- animator->root_co, 40.0f );
- //audio_channel_slope_volume( player_skate.aud_slide, 0.05f, vol_slide );
- audio_channel_edit_volume( player_skate.aud_slide, vol_slide, 1 );
- audio_channel_sidechain_lfo( player_skate.aud_slide, 0, sidechain_amt );
- }
-
- if( player_skate.aud_air ){
- player_skate.aud_air->colour = 0x00103efe;
- audio_channel_set_spacial( player_skate.aud_air,
- animator->root_co, 40.0f );
- //audio_channel_slope_volume( player_skate.aud_air, 0.05f, vol_air );
- audio_channel_edit_volume( player_skate.aud_air, vol_air, 1 );
- }
-
- audio_unlock();
-}
-
-void player__skate_post_update(void){
- struct player_skate_state *state = &player_skate.state;
-
- for( int i=0; i<player_skate.possible_jump_count; i++ ){
- jump_info *jump = &player_skate.possible_jumps[i];
-
- if( jump->log_length == 0 ){
- vg_fatal_error( "assert: jump->log_length == 0\n" );
- }
-
- for( int j=0; j<jump->log_length - 1; j ++ ){
- float brightness = jump->score*jump->score*jump->score;
- v3f p1;
- v3_lerp( jump->log[j], jump->log[j+1], brightness, p1 );
- vg_line( jump->log[j], p1, jump->colour );
- }
-
- vg_line_cross( jump->log[jump->log_length-1], jump->colour, 0.25f );
-
- v3f p1;
- v3_add( jump->log[jump->log_length-1], jump->n, p1 );
- vg_line( jump->log[jump->log_length-1], p1, 0xffffffff );
-
- vg_line_point( jump->apex, 0.02f, 0xffffffff );
- }
-}
-
-/*
- * truck alignment model at ra(local)
- * returns 1 if valid surface:
- * surface_normal will be filled out with an averaged normal vector
- * axel_dir will be the direction from left to right wheels
- *
- * returns 0 if no good surface found
- */
-static
-int skate_compute_surface_alignment( v3f ra, u32 colour,
- v3f surface_normal, v3f axel_dir ){
- world_instance *world = world_current_instance();
-
- v3f truck, left, right;
- m4x3_mulv( localplayer.rb.to_world, ra, truck );
-
- v3_muladds( truck, localplayer.rb.to_world[0], -k_board_width, left );
- v3_muladds( truck, localplayer.rb.to_world[0], k_board_width, right );
- vg_line( left, right, colour );
-
- float k_max_truck_flex = VG_PIf * 0.25f;
-
- ray_hit ray_l, ray_r;
-
- v3f dir;
- v3_muls( localplayer.rb.to_world[1], -1.0f, dir );
-
- int res_l = 0, res_r = 0;
-
- for( int i=0; i<8; i++ ){
- float t = 1.0f - (float)i * (1.0f/8.0f);
- v3_muladds( truck, localplayer.rb.to_world[0], -k_board_radius*t, left );
- v3_muladds( left, localplayer.rb.to_world[1], k_board_radius, left );
- ray_l.dist = 2.1f * k_board_radius;
-
- res_l = ray_world( world, left, dir, &ray_l, k_material_flag_walking );
-
- if( res_l )
- break;
- }
-
- for( int i=0; i<8; i++ ){
- float t = 1.0f - (float)i * (1.0f/8.0f);
- v3_muladds( truck, localplayer.rb.to_world[0], k_board_radius*t, right );
- v3_muladds( right, localplayer.rb.to_world[1], k_board_radius, right );
- ray_r.dist = 2.1f * k_board_radius;
-
- res_r = ray_world( world, right, dir, &ray_r, k_material_flag_walking );
-
- if( res_r )
- break;
- }
-
- v3f v0;
- v3f midpoint;
- v3f tangent_average;
- v3_muladds( truck, localplayer.rb.to_world[1], -k_board_radius, midpoint );
- v3_zero( tangent_average );
-
- if( res_l || res_r ){
- v3f p0, p1, t;
- v3_copy( midpoint, p0 );
- v3_copy( midpoint, p1 );
-
- if( res_l ){
- v3_copy( ray_l.pos, p0 );
- v3_cross( ray_l.normal, localplayer.rb.to_world[0], t );
- v3_add( t, tangent_average, tangent_average );
- }
- if( res_r ){
- v3_copy( ray_r.pos, p1 );
- v3_cross( ray_r.normal, localplayer.rb.to_world[0], t );
- v3_add( t, tangent_average, tangent_average );
- }
-
- v3_sub( p1, p0, v0 );
- v3_normalize( v0 );
- }
- else{
- /* fallback: use the closes point to the trucks */
- v3f closest;
- int idx = bh_closest_point( world->geo_bh, midpoint, closest, 0.1f );
-
- if( idx != -1 ){
- u32 *tri = &world->scene_geo.arrindices[ idx * 3 ];
- v3f verts[3];
-
- for( int j=0; j<3; j++ )
- v3_copy( world->scene_geo.arrvertices[ tri[j] ].co, verts[j] );
-
- v3f vert0, vert1, n;
- v3_sub( verts[1], verts[0], vert0 );
- v3_sub( verts[2], verts[0], vert1 );
- v3_cross( vert0, vert1, n );
- v3_normalize( n );
-
- if( v3_dot( n, localplayer.rb.to_world[1] ) < 0.3f )
- return 0;
-
- v3_cross( n, localplayer.rb.to_world[2], v0 );
- v3_muladds( v0, localplayer.rb.to_world[2],
- -v3_dot( localplayer.rb.to_world[2], v0 ), v0 );
- v3_normalize( v0 );
-
- v3f t;
- v3_cross( n, localplayer.rb.to_world[0], t );
- v3_add( t, tangent_average, tangent_average );
- }
- else
- return 0;
- }
-
- v3_muladds( truck, v0, k_board_width, right );
- v3_muladds( truck, v0, -k_board_width, left );
-
- vg_line( left, right, VG__WHITE );
-
- v3_normalize( tangent_average );
- v3_cross( v0, tangent_average, surface_normal );
- v3_copy( v0, axel_dir );
-
- return 1;
-}
-
-static void skate_weight_distribute(void){
- struct player_skate_state *state = &player_skate.state;
- v3_zero( player_skate.weight_distribution );
-
- int reverse_dir = v3_dot( localplayer.rb.to_world[2],
- localplayer.rb.v ) < 0.0f?1:-1;
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- if( state->manual_direction == 0 ){
- if( (steer[1] > 0.7f) && (state->activity == k_skate_activity_ground) &&
- (state->jump_charge <= 0.01f) )
- state->manual_direction = reverse_dir;
- }
- else{
- if( steer[1] < 0.1f ){
- state->manual_direction = 0;
- }
- else{
- if( reverse_dir != state->manual_direction ){
- return;
- }
- }
- }
-
- if( state->manual_direction ){
- float amt = vg_minf( steer[1] * 8.0f, 1.0f );
- player_skate.weight_distribution[2] = k_board_length * amt *
- (float)state->manual_direction;
- }
-
- if( state->manual_direction ){
- v3f plane_z;
-
- m3x3_mulv( localplayer.rb.to_world, player_skate.weight_distribution,
- plane_z );
- v3_negate( plane_z, plane_z );
-
- v3_muladds( plane_z, player_skate.surface_picture,
- -v3_dot( plane_z, player_skate.surface_picture ), plane_z );
- v3_normalize( plane_z );
-
- v3_muladds( plane_z, player_skate.surface_picture, 0.3f, plane_z );
- v3_normalize( plane_z );
-
- v3f p1;
- v3_muladds( localplayer.rb.co, plane_z, 1.5f, p1 );
- vg_line( localplayer.rb.co, p1, VG__GREEN );
-
- v3f refdir;
- v3_muls( localplayer.rb.to_world[2], -(float)state->manual_direction,
- refdir );
-
- rb_effect_spring_target_vector( &localplayer.rb, refdir, plane_z,
- k_manul_spring, k_manul_dampener,
- player_skate.substep_delta );
- }
-}
-
-static void skate_adjust_up_direction(void){
- struct player_skate_state *state = &player_skate.state;
-
- if( state->activity == k_skate_activity_ground ){
- v3f target;
- v3_copy( player_skate.surface_picture, target );
-
- target[1] += 2.0f * player_skate.surface_picture[1];
- v3_normalize( target );
-
- v3_lerp( state->up_dir, target,
- 8.0f * player_skate.substep_delta, state->up_dir );
- }
- else if( state->activity <= k_skate_activity_air_to_grind ){
- v3_lerp( state->up_dir, localplayer.rb.to_world[1],
- 8.0f * player_skate.substep_delta, state->up_dir );
- }
- else{
- v3f avg;
- v3_add( localplayer.rb.to_world[1], (v3f){0,1,0}, avg );
- v3_normalize( avg );
-
- v3_lerp( state->up_dir, avg,
- 6.0f * player_skate.substep_delta, state->up_dir );
- }
-}
-
-static int skate_point_visible( v3f origin, v3f target ){
- v3f dir;
- v3_sub( target, origin, dir );
-
- ray_hit ray;
- ray.dist = v3_length( dir );
- v3_muls( dir, 1.0f/ray.dist, dir );
- ray.dist -= 0.025f;
-
- if( ray_world( world_current_instance(), origin, dir, &ray,
- k_material_flag_walking ) )
- return 0;
-
- return 1;
-}
-
-static void skate_grind_orient( struct grind_info *inf, m3x3f mtx ){
- v3_copy( inf->dir, mtx[0] );
- v3_copy( inf->n, mtx[1] );
- v3_cross( mtx[0], mtx[1], mtx[2] );
-}
-
-static void skate_grind_friction( struct grind_info *inf, float strength ){
- v3f v2;
- v3_muladds( localplayer.rb.to_world[2], inf->n,
- -v3_dot( localplayer.rb.to_world[2], inf->n ), v2 );
-
- float a = 1.0f-fabsf( v3_dot( v2, inf->dir ) ),
- dir = vg_signf( v3_dot( localplayer.rb.v, inf->dir ) ),
- F = a * -dir * k_grind_max_friction;
-
- v3_muladds( localplayer.rb.v, inf->dir, F*vg.time_fixed_delta*strength,
- localplayer.rb.v );
-}
-
-static void skate_grind_decay( struct grind_info *inf, float strength ){
- m3x3f mtx, mtx_inv;
- skate_grind_orient( inf, mtx );
- m3x3_transpose( mtx, mtx_inv );
-
- v3f v_grind;
- m3x3_mulv( mtx_inv, localplayer.rb.v, v_grind );
-
- float decay = 1.0f - ( vg.time_fixed_delta * k_grind_decayxy * strength );
- v3_mul( v_grind, (v3f){ 1.0f, decay, decay }, v_grind );
- m3x3_mulv( mtx, v_grind, localplayer.rb.v );
-}
-
-static void skate_grind_truck_apply( float sign, struct grind_info *inf,
- float strength ){
- struct player_skate_state *state = &player_skate.state;
- /* REFACTOR */
- v3f ra = { 0.0f, -k_board_radius, sign * k_board_length };
- v3f raw, wsp;
- m3x3_mulv( localplayer.rb.to_world, ra, raw );
- v3_add( localplayer.rb.co, raw, wsp );
-
- v3_copy( ra, player_skate.weight_distribution );
-
- v3f delta;
- v3_sub( inf->co, wsp, delta );
-
- /* spring force */
- v3_muladds( localplayer.rb.v, delta, k_spring_force*strength*vg.time_fixed_delta,
- localplayer.rb.v );
-
- skate_grind_decay( inf, strength );
- skate_grind_friction( inf, strength );
-
- /* yeah yeah yeah yeah */
- v3f raw_nplane, axis;
- v3_muladds( raw, inf->n, -v3_dot( inf->n, raw ), raw_nplane );
- v3_cross( raw_nplane, inf->n, axis );
- v3_normalize( axis );
-
- /* orientation */
- m3x3f mtx;
- skate_grind_orient( inf, mtx );
- v3f target_fwd, fwd, up, target_up;
- m3x3_mulv( mtx, player_skate.grind_vec, target_fwd );
- v3_copy( raw_nplane, fwd );
- v3_copy( localplayer.rb.to_world[1], up );
- v3_copy( inf->n, target_up );
-
- v3_muladds( target_fwd, inf->n, -v3_dot(inf->n,target_fwd), target_fwd );
- v3_muladds( fwd, inf->n, -v3_dot(inf->n,fwd), fwd );
-
- v3_normalize( target_fwd );
- v3_normalize( fwd );
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- float way = steer[1] * vg_signf( v3_dot( raw_nplane, localplayer.rb.v ) );
-
- v4f q;
- q_axis_angle( q, axis, VG_PIf*0.125f * way );
- q_mulv( q, target_up, target_up );
- q_mulv( q, target_fwd, target_fwd );
-
- rb_effect_spring_target_vector( &localplayer.rb, up, target_up,
- k_grind_spring,
- k_grind_dampener,
- vg.time_fixed_delta );
-
- rb_effect_spring_target_vector( &localplayer.rb, fwd, target_fwd,
- k_grind_spring*strength,
- k_grind_dampener*strength,
- vg.time_fixed_delta );
-
- vg_line_arrow( localplayer.rb.co, target_up, 1.0f, VG__GREEN );
- vg_line_arrow( localplayer.rb.co, fwd, 0.8f, VG__RED );
- vg_line_arrow( localplayer.rb.co, target_fwd, 1.0f, VG__YELOW );
-
- player_skate.grind_strength = strength;
-
- /* Fake contact */
- struct grind_limit *limit =
- &player_skate.limits[ player_skate.limit_count ++ ];
- m4x3_mulv( localplayer.rb.to_local, wsp, limit->ra );
- m3x3_mulv( localplayer.rb.to_local, inf->n, limit->n );
- limit->p = 0.0f;
-
- v3_copy( inf->dir, player_skate.grind_dir );
-}
-
-static void skate_5050_apply( struct grind_info *inf_front,
- struct grind_info *inf_back ){
- struct player_skate_state *state = &player_skate.state;
- struct grind_info inf_avg;
-
- v3_sub( inf_front->co, inf_back->co, inf_avg.dir );
- v3_muladds( inf_back->co, inf_avg.dir, 0.5f, inf_avg.co );
- v3_normalize( inf_avg.dir );
-
- /* dont ask */
- v3_muls( inf_avg.dir, vg_signf(v3_dot(inf_avg.dir,localplayer.rb.v)),
- inf_avg.dir );
-
- v3f axis_front, axis_back, axis;
- v3_cross( inf_front->dir, inf_front->n, axis_front );
- v3_cross( inf_back->dir, inf_back->n, axis_back );
- v3_add( axis_front, axis_back, axis );
- v3_normalize( axis );
-
- v3_cross( axis, inf_avg.dir, inf_avg.n );
- skate_grind_decay( &inf_avg, 1.0f );
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- float way = steer[1] * vg_signf( v3_dot( localplayer.rb.to_world[2],
- localplayer.rb.v ) );
- v4f q;
- v3f up, target_up;
- v3_copy( localplayer.rb.to_world[1], up );
- v3_copy( inf_avg.n, target_up );
- q_axis_angle( q, localplayer.rb.to_world[0], VG_PIf*0.25f * -way );
- q_mulv( q, target_up, target_up );
-
- v3_zero( player_skate.weight_distribution );
- player_skate.weight_distribution[2] = k_board_length * -way;
-
- rb_effect_spring_target_vector( &localplayer.rb, up, target_up,
- k_grind_spring,
- k_grind_dampener,
- vg.time_fixed_delta );
- vg_line_arrow( localplayer.rb.co, up, 1.0f, VG__GREEN );
- vg_line_arrow( localplayer.rb.co, target_up, 1.0f, VG__GREEN );
-
- v3f fwd_nplane, dir_nplane;
- v3_muladds( localplayer.rb.to_world[2], inf_avg.n,
- -v3_dot( localplayer.rb.to_world[2], inf_avg.n ), fwd_nplane );
-
- v3f dir;
- v3_muls( inf_avg.dir, v3_dot( fwd_nplane, inf_avg.dir ), dir );
- v3_muladds( dir, inf_avg.n, -v3_dot( dir, inf_avg.n ), dir_nplane );
-
- v3_normalize( fwd_nplane );
- v3_normalize( dir_nplane );
-
- rb_effect_spring_target_vector( &localplayer.rb, fwd_nplane, dir_nplane,
- 1000.0f,
- k_grind_dampener,
- vg.time_fixed_delta );
- vg_line_arrow( localplayer.rb.co, fwd_nplane, 0.8f, VG__RED );
- vg_line_arrow( localplayer.rb.co, dir_nplane, 0.8f, VG__RED );
-
- v3f pos_front = { 0.0f, -k_board_radius, -1.0f * k_board_length },
- pos_back = { 0.0f, -k_board_radius, 1.0f * k_board_length },
- delta_front, delta_back, delta_total;
-
- m4x3_mulv( localplayer.rb.to_world, pos_front, pos_front );
- m4x3_mulv( localplayer.rb.to_world, pos_back, pos_back );
-
- v3_sub( inf_front->co, pos_front, delta_front );
- v3_sub( inf_back->co, pos_back, delta_back );
- v3_add( delta_front, delta_back, delta_total );
-
- v3_muladds( localplayer.rb.v, delta_total, 50.0f * vg.time_fixed_delta,
- localplayer.rb.v );
-
- /* Fake contact */
- struct grind_limit *limit =
- &player_skate.limits[ player_skate.limit_count ++ ];
- v3_zero( limit->ra );
- m3x3_mulv( localplayer.rb.to_local, inf_avg.n, limit->n );
- limit->p = 0.0f;
-
- v3_copy( inf_avg.dir, player_skate.grind_dir );
-}
-
-static int skate_grind_truck_renew( f32 sign, struct grind_info *inf ){
- struct player_skate_state *state = &player_skate.state;
-
- v3f wheel_co = { 0.0f, 0.0f, sign * k_board_length },
- grind_co = { 0.0f, -k_board_radius, sign * k_board_length };
-
- m4x3_mulv( localplayer.rb.to_world, wheel_co, wheel_co );
- m4x3_mulv( localplayer.rb.to_world, grind_co, grind_co );
-
- /* Exit condition: lost grind tracking */
- if( !skate_grind_scansq( grind_co, localplayer.rb.v, 0.3f, inf ) )
- return 0;
-
- /* Exit condition: cant see grind target directly */
- if( !skate_point_visible( wheel_co, inf->co ) )
- return 0;
-
- /* Exit condition: minimum velocity not reached, but allow a bit of error */
- float dv = fabsf(v3_dot( localplayer.rb.v, inf->dir )),
- minv = k_grind_axel_min_vel*0.8f;
-
- if( dv < minv )
- return 0;
-
- if( fabsf(v3_dot( inf->dir, player_skate.grind_dir )) < k_grind_max_edge_angle )
- return 0;
-
- v3_copy( inf->dir, player_skate.grind_dir );
- return 1;
-}
-
-static int skate_grind_truck_entry( f32 sign, struct grind_info *inf ){
- struct player_skate_state *state = &player_skate.state;
-
- /* REFACTOR */
- v3f ra = { 0.0f, -k_board_radius, sign * k_board_length };
-
- v3f raw, wsp;
- m3x3_mulv( localplayer.rb.to_world, ra, raw );
- v3_add( localplayer.rb.co, raw, wsp );
-
- if( skate_grind_scansq( wsp, localplayer.rb.v, 0.3, inf ) ){
- if( fabsf(v3_dot( localplayer.rb.v, inf->dir )) < k_grind_axel_min_vel )
- return 0;
-
- /* velocity should be at least 60% aligned */
- v3f pv, axis;
- v3_cross( inf->n, inf->dir, axis );
- v3_muladds( localplayer.rb.v, inf->n,
- -v3_dot( localplayer.rb.v, inf->n ), pv );
-
- if( v3_length2( pv ) < 0.0001f )
- return 0;
- v3_normalize( pv );
-
- if( fabsf(v3_dot( pv, inf->dir )) < k_grind_axel_max_angle )
- return 0;
-
- if( v3_dot( localplayer.rb.v, inf->n ) > 0.5f )
- return 0;
-
- v3f local_co, local_dir, local_n;
- m4x3_mulv( localplayer.rb.to_local, inf->co, local_co );
- m3x3_mulv( localplayer.rb.to_local, inf->dir, local_dir );
- m3x3_mulv( localplayer.rb.to_local, inf->n, local_n );
-
- v2f delta = { local_co[0], local_co[2] - k_board_length*sign };
-
- float truck_height = -(k_board_radius+0.03f);
-
- v3f rv;
- v3_cross( localplayer.rb.w, raw, rv );
- v3_add( localplayer.rb.v, rv, rv );
-
- if( (local_co[1] >= truck_height) &&
- (v2_length2( delta ) <= k_board_radius*k_board_radius) )
- {
- return 1;
- }
- }
-
- return 0;
-}
-
-static void skate_boardslide_apply( struct grind_info *inf ){
- struct player_skate_state *state = &player_skate.state;
-
- v3f local_co, local_dir, local_n;
- m4x3_mulv( localplayer.rb.to_local, inf->co, local_co );
- m3x3_mulv( localplayer.rb.to_local, inf->dir, local_dir );
- m3x3_mulv( localplayer.rb.to_local, inf->n, local_n );
-
- v3f intersection;
- v3_muladds( local_co, local_dir, local_co[0]/-local_dir[0],
- intersection );
- v3_copy( intersection, player_skate.weight_distribution );
-
- skate_grind_decay( inf, 0.0125f );
- skate_grind_friction( inf, 0.25f );
-
- /* direction alignment */
- v3f dir, perp;
- v3_cross( local_dir, local_n, perp );
- v3_muls( local_dir, vg_signf(local_dir[0]), dir );
- v3_muls( perp, vg_signf(perp[2]), perp );
-
- m3x3_mulv( localplayer.rb.to_world, dir, dir );
- m3x3_mulv( localplayer.rb.to_world, perp, perp );
-
- v4f qbalance;
- q_axis_angle( qbalance, dir, local_co[0]*k_grind_balance );
- q_mulv( qbalance, perp, perp );
-
- rb_effect_spring_target_vector( &localplayer.rb, localplayer.rb.to_world[0],
- dir,
- k_grind_spring, k_grind_dampener,
- vg.time_fixed_delta );
-
- rb_effect_spring_target_vector( &localplayer.rb, localplayer.rb.to_world[2],
- perp,
- k_grind_spring, k_grind_dampener,
- vg.time_fixed_delta );
-
- vg_line_arrow( localplayer.rb.co, dir, 0.5f, VG__GREEN );
- vg_line_arrow( localplayer.rb.co, perp, 0.5f, VG__BLUE );
-
- v3_copy( inf->dir, player_skate.grind_dir );
-}
-
-static int skate_boardslide_entry( struct grind_info *inf ){
- struct player_skate_state *state = &player_skate.state;
-
- if( skate_grind_scansq( localplayer.rb.co,
- localplayer.rb.to_world[0], k_board_length,
- inf ) )
- {
- v3f local_co, local_dir;
- m4x3_mulv( localplayer.rb.to_local, inf->co, local_co );
- m3x3_mulv( localplayer.rb.to_local, inf->dir, local_dir );
-
- if( (fabsf(local_co[2]) <= k_board_length) && /* within wood area */
- (local_co[1] >= 0.0f) && /* at deck level */
- (fabsf(local_dir[0]) >= 0.25f) ) /* perpendicular to us */
- {
- if( fabsf(v3_dot( localplayer.rb.v, inf->dir )) < k_grind_axel_min_vel )
- return 0;
-
- return 1;
- }
- }
-
- return 0;
-}
-
-static int skate_boardslide_renew( struct grind_info *inf ){
- struct player_skate_state *state = &player_skate.state;
-
- if( !skate_grind_scansq( localplayer.rb.co,
- localplayer.rb.to_world[0], k_board_length,
- inf ) )
- return 0;
-
- /* Exit condition: cant see grind target directly */
- v3f vis;
- v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], 0.2f, vis );
- if( !skate_point_visible( vis, inf->co ) )
- return 0;
-
- /* Exit condition: minimum velocity not reached, but allow a bit of error */
- float dv = fabsf(v3_dot( localplayer.rb.v, inf->dir )),
- minv = k_grind_axel_min_vel*0.8f;
-
- if( dv < minv )
- return 0;
-
- if( fabsf(v3_dot( inf->dir, player_skate.grind_dir )) < k_grind_max_edge_angle )
- return 0;
-
- return 1;
-}
-
-static void skate_store_grind_vec( struct grind_info *inf ){
- struct player_skate_state *state = &player_skate.state;
-
- m3x3f mtx;
- skate_grind_orient( inf, mtx );
- m3x3_transpose( mtx, mtx );
-
- v3f raw;
- v3_sub( inf->co, localplayer.rb.co, raw );
-
- m3x3_mulv( mtx, raw, player_skate.grind_vec );
- v3_normalize( player_skate.grind_vec );
- v3_copy( inf->dir, player_skate.grind_dir );
-}
-
-static enum skate_activity skate_availible_grind(void){
- struct player_skate_state *state = &player_skate.state;
-
- if( state->grind_cooldown > 100 ){
- vg_fatal_error( "wth!\n" );
- }
-
- /* debounces this state manager a little bit */
- if( state->grind_cooldown ){
- state->grind_cooldown --;
- return k_skate_activity_undefined;
- }
-
- struct grind_info inf_back50,
- inf_front50,
- inf_slide;
-
- int res_back50 = 0,
- res_front50 = 0,
- res_slide = 0;
-
- int allow_back = 1,
- allow_front = 1;
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- if( state->activity == k_skate_activity_grind_5050 ||
- state->activity == k_skate_activity_grind_back50 ||
- state->activity == k_skate_activity_grind_front50 )
- {
- float tilt = steer[1];
-
- if( fabsf(tilt) >= 0.25f ){
- v3f raw = {0.0f,0.0f,tilt};
- m3x3_mulv( localplayer.rb.to_world, raw, raw );
-
- float way = tilt * vg_signf( v3_dot( raw, localplayer.rb.v ) );
-
- if( way < 0.0f ) allow_front = 0;
- else allow_back = 0;
- }
- }
-
- if( state->activity == k_skate_activity_grind_boardslide ){
- res_slide = skate_boardslide_renew( &inf_slide );
- }
- else if( state->activity == k_skate_activity_grind_back50 ){
- res_back50 = skate_grind_truck_renew( 1.0f, &inf_back50 );
-
- if( allow_front )
- res_front50 = skate_grind_truck_entry( -1.0f, &inf_front50 );
- }
- else if( state->activity == k_skate_activity_grind_front50 ){
- res_front50 = skate_grind_truck_renew( -1.0f, &inf_front50 );
-
- if( allow_back )
- res_back50 = skate_grind_truck_entry( 1.0f, &inf_back50 );
- }
- else if( state->activity == k_skate_activity_grind_5050 ){
- if( allow_front )
- res_front50 = skate_grind_truck_renew( -1.0f, &inf_front50 );
- if( allow_back )
- res_back50 = skate_grind_truck_renew( 1.0f, &inf_back50 );
- }
- else{
- res_slide = skate_boardslide_entry( &inf_slide );
-
- if( allow_back )
- res_back50 = skate_grind_truck_entry( 1.0f, &inf_back50 );
-
- if( allow_front )
- res_front50 = skate_grind_truck_entry( -1.0f, &inf_front50 );
-
- if( res_back50 != res_front50 ){
- int wants_to_do_that = fabsf(steer[1]) >= 0.25f;
-
- res_back50 &= wants_to_do_that;
- res_front50 &= wants_to_do_that;
- }
- }
-
- const enum skate_activity table[] =
- { /* slide | back | front */
- k_skate_activity_undefined, /* 0 0 0 */
- k_skate_activity_grind_front50, /* 0 0 1 */
- k_skate_activity_grind_back50, /* 0 1 0 */
- k_skate_activity_grind_5050, /* 0 1 1 */
-
- /* slide has priority always */
- k_skate_activity_grind_boardslide, /* 1 0 0 */
- k_skate_activity_grind_boardslide, /* 1 0 1 */
- k_skate_activity_grind_boardslide, /* 1 1 0 */
- k_skate_activity_grind_boardslide, /* 1 1 1 */
- }
- , new_activity = table[ res_slide << 2 | res_back50 << 1 | res_front50 ];
-
- if( new_activity == k_skate_activity_undefined ){
- if( state->activity >= k_skate_activity_grind_any ){
- state->grind_cooldown = 15;
- state->surface_cooldown = 10;
- }
- }
- else if( new_activity == k_skate_activity_grind_boardslide ){
- skate_boardslide_apply( &inf_slide );
- }
- else if( new_activity == k_skate_activity_grind_back50 ){
- if( state->activity != k_skate_activity_grind_back50 )
- skate_store_grind_vec( &inf_back50 );
-
- skate_grind_truck_apply( 1.0f, &inf_back50, 1.0f );
- }
- else if( new_activity == k_skate_activity_grind_front50 ){
- if( state->activity != k_skate_activity_grind_front50 )
- skate_store_grind_vec( &inf_front50 );
-
- skate_grind_truck_apply( -1.0f, &inf_front50, 1.0f );
- }
- else if( new_activity == k_skate_activity_grind_5050 )
- skate_5050_apply( &inf_front50, &inf_back50 );
-
- return new_activity;
-}
-
-void player__skate_update(void){
- struct player_skate_state *state = &player_skate.state;
- world_instance *world = world_current_instance();
-
- if( state->activity == k_skate_activity_handplant )
- return;
-
- if( !world_water_player_safe( world, 0.25f ) ) return;
-
- v3_copy( localplayer.rb.co, state->prev_pos );
- state->activity_prev = state->activity;
- v3f normal_total;
- v3_zero( normal_total );
-
- struct board_collider
- {
- v3f pos;
- float radius;
-
- u32 colour;
-
- enum board_collider_state
- {
- k_collider_state_default,
- k_collider_state_disabled,
- k_collider_state_colliding
- }
- state;
- }
- wheels[] =
- {
- {
- { 0.0f, 0.0f, -k_board_length },
- .radius = k_board_radius,
- .colour = VG__RED
- },
- {
- { 0.0f, 0.0f, k_board_length },
- .radius = k_board_radius,
- .colour = VG__GREEN
- }
- };
-
- float slap = 0.0f;
-
- if( state->activity <= k_skate_activity_air_to_grind ){
- float min_dist = 0.6f;
- for( int i=0; i<2; i++ ){
- v3f wpos, closest;
- m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, wpos );
-
- if( bh_closest_point( world->geo_bh, wpos, closest, min_dist ) != -1 ){
- min_dist = vg_minf( min_dist, v3_dist( closest, wpos ) );
- }
- }
- min_dist -= 0.2f;
- float vy = vg_maxf( 0.0f, localplayer.rb.v[1] );
- slap = vg_clampf( (min_dist/0.5f) + vy, 0.0f, 1.0f )*0.3f;
- }
- state->slap = vg_lerpf( state->slap, slap, 10.0f*vg.time_fixed_delta );
-
- wheels[0].pos[1] = state->slap;
- wheels[1].pos[1] = state->slap;
-
-
- const int k_wheel_count = 2;
-
- player_skate.substep = vg.time_fixed_delta;
- player_skate.substep_delta = player_skate.substep;
- player_skate.limit_count = 0;
-
- int substep_count = 0;
-
- v3_zero( player_skate.surface_picture );
-
- int prev_contacts[2];
-
- for( int i=0; i<k_wheel_count; i++ ){
- wheels[i].state = k_collider_state_default;
- prev_contacts[i] = player_skate.wheel_contacts[i];
- }
-
- /* check if we can enter or continue grind */
- enum skate_activity grindable_activity = skate_availible_grind();
- if( grindable_activity != k_skate_activity_undefined ){
- state->activity = grindable_activity;
- goto grinding;
- }
-
- int contact_count = 0;
- for( int i=0; i<2; i++ ){
- v3f normal, axel;
- v3_copy( localplayer.rb.to_world[0], axel );
-
- if( skate_compute_surface_alignment( wheels[i].pos,
- wheels[i].colour, normal, axel ) )
- {
- rb_effect_spring_target_vector( &localplayer.rb,
- localplayer.rb.to_world[0],
- axel,
- k_surface_spring, k_surface_dampener,
- player_skate.substep_delta );
-
- v3_add( normal, player_skate.surface_picture,
- player_skate.surface_picture );
- contact_count ++;
- player_skate.wheel_contacts[i] = 1;
- }
- else{
- player_skate.wheel_contacts[i] = 0;
- }
-
- m3x3_mulv( localplayer.rb.to_local, axel, player_skate.truckv0[i] );
- }
-
- if( state->surface_cooldown ){
- state->surface_cooldown --;
- contact_count = 0;
- }
-
- if( (prev_contacts[0]+prev_contacts[1] == 1) && (contact_count == 2) ){
- for( int i=0; i<2; i++ ){
- if( !prev_contacts[i] ){
- v3f co;
- m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, co );
- player__networked_sfx( k_player_subsystem_skate, 32,
- k_player_skate_soundeffect_tap,
- localplayer.rb.co, 0.75f );
- }
- }
- }
-
- if( contact_count ){
- state->activity = k_skate_activity_ground;
- state->gravity_bias = k_gravity;
- v3_normalize( player_skate.surface_picture );
-
- skate_apply_friction_model();
- skate_weight_distribute();
- }
- else{
- if( state->activity > k_skate_activity_air_to_grind )
- state->activity = k_skate_activity_air;
-
- v3_zero( player_skate.weight_distribution );
- skate_apply_air_model();
- }
-
-grinding:;
-
- if( state->activity == k_skate_activity_grind_back50 )
- wheels[1].state = k_collider_state_disabled;
- if( state->activity == k_skate_activity_grind_front50 )
- wheels[0].state = k_collider_state_disabled;
- if( state->activity == k_skate_activity_grind_5050 ){
- wheels[0].state = k_collider_state_disabled;
- wheels[1].state = k_collider_state_disabled;
- }
-
- /* all activities */
- skate_apply_steering_model();
- skate_adjust_up_direction();
- skate_apply_cog_model();
- skate_apply_jump_model();
- skate_apply_handplant_model();
- skate_apply_grab_model();
- skate_apply_trick_model();
- skate_apply_pump_model();
-
- ent_tornado_debug();
- v3f a;
- ent_tornado_forces( localplayer.rb.co, localplayer.rb.v, a );
- v3_muladds( localplayer.rb.v, a, vg.time_fixed_delta, localplayer.rb.v );
-
-begin_collision:;
-
- /*
- * Phase 0: Continous collision detection
- * --------------------------------------------------------------------------
- */
-
- v3f head_wp0, head_wp1, start_co;
- m4x3_mulv( localplayer.rb.to_world, state->head_position, head_wp0 );
- v3_copy( localplayer.rb.co, start_co );
-
- /* calculate transform one step into future */
- v3f future_co;
- v4f future_q;
- v3_muladds( localplayer.rb.co, localplayer.rb.v, player_skate.substep,
- future_co );
-
- if( v3_length2( localplayer.rb.w ) > 0.0f ){
- v4f rotation;
- v3f axis;
- v3_copy( localplayer.rb.w, axis );
-
- float mag = v3_length( axis );
- v3_divs( axis, mag, axis );
- q_axis_angle( rotation, axis, mag*player_skate.substep );
- q_mul( rotation, localplayer.rb.q, future_q );
- q_normalize( future_q );
- }
- else
- v4_copy( localplayer.rb.q, future_q );
-
- v3f future_cg, current_cg, cg_offset;
- q_mulv( localplayer.rb.q, player_skate.weight_distribution, current_cg );
- q_mulv( future_q, player_skate.weight_distribution, future_cg );
- v3_sub( future_cg, current_cg, cg_offset );
-
- /* calculate the minimum time we can move */
- float max_time = player_skate.substep;
-
- for( int i=0; i<k_wheel_count; i++ ){
- if( wheels[i].state == k_collider_state_disabled )
- continue;
-
- v3f current, future, r_cg;
-
- q_mulv( future_q, wheels[i].pos, future );
- v3_add( future, future_co, future );
- v3_add( cg_offset, future, future );
-
- q_mulv( localplayer.rb.q, wheels[i].pos, current );
- v3_add( current, localplayer.rb.co, current );
-
- float t;
- v3f n;
-
- float cast_radius = wheels[i].radius - k_penetration_slop * 2.0f;
- if( spherecast_world( world, current, future, cast_radius, &t, n,
- k_material_flag_walking ) != -1)
- max_time = vg_minf( max_time, t * player_skate.substep );
- }
-
- /* clamp to a fraction of delta, to prevent locking */
- float rate_lock = substep_count;
- rate_lock *= vg.time_fixed_delta * 0.1f;
- rate_lock *= rate_lock;
-
- max_time = vg_maxf( max_time, rate_lock );
- player_skate.substep_delta = max_time;
-
- /* integrate */
- v3_muladds( localplayer.rb.co, localplayer.rb.v,
- player_skate.substep_delta, localplayer.rb.co );
- if( v3_length2( localplayer.rb.w ) > 0.0f ){
- v4f rotation;
- v3f axis;
- v3_copy( localplayer.rb.w, axis );
-
- float mag = v3_length( axis );
- v3_divs( axis, mag, axis );
- q_axis_angle( rotation, axis, mag*player_skate.substep_delta );
- q_mul( rotation, localplayer.rb.q, localplayer.rb.q );
- q_normalize( localplayer.rb.q );
-
- q_mulv( localplayer.rb.q, player_skate.weight_distribution, future_cg );
- v3_sub( current_cg, future_cg, cg_offset );
- v3_add( localplayer.rb.co, cg_offset, localplayer.rb.co );
- }
-
- rb_update_matrices( &localplayer.rb );
- localplayer.rb.v[1] += -state->gravity_bias * player_skate.substep_delta;
-
- player_skate.substep -= player_skate.substep_delta;
-
- rb_ct manifold[128];
- int manifold_len = 0;
- /*
- * Phase -1: head detection
- * --------------------------------------------------------------------------
- */
- m4x3_mulv( localplayer.rb.to_world, state->head_position, head_wp1 );
-
- float t;
- v3f n;
- if( (v3_dist2( head_wp0, head_wp1 ) > 0.001f) &&
- (spherecast_world( world, head_wp0, head_wp1, 0.2f, &t, n,
- k_material_flag_walking ) != -1) )
- {
- v3_lerp( start_co, localplayer.rb.co, t, localplayer.rb.co );
- rb_update_matrices( &localplayer.rb );
-
- vg_info( "player fell of due to hitting head\n" );
- player__dead_transition( k_player_die_type_head );
- return;
- }
-
- /*
- * Phase 1: Regular collision detection
- * --------------------------------------------------------------------------
- */
-
- for( int i=0; i<k_wheel_count; i++ ){
- if( wheels[i].state == k_collider_state_disabled )
- continue;
-
- m4x3f mtx;
- m3x3_identity( mtx );
- m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, mtx[3] );
-
- rb_ct *man = &manifold[ manifold_len ];
-
- int l = skate_collide_smooth( mtx, wheels[i].radius, man );
- if( l )
- wheels[i].state = k_collider_state_colliding;
-
- manifold_len += l;
- }
-
- float grind_radius = k_board_radius * 0.75f;
- rb_capsule capsule = { .h = (k_board_length+0.2f)*2.0f,
- .r = grind_radius };
- m4x3f mtx;
- v3_muls( localplayer.rb.to_world[0], 1.0f, mtx[0] );
- v3_muls( localplayer.rb.to_world[2], -1.0f, mtx[1] );
- v3_muls( localplayer.rb.to_world[1], 1.0f, mtx[2] );
- v3_muladds( localplayer.rb.to_world[3], localplayer.rb.to_world[1],
- grind_radius + k_board_radius*0.25f+state->slap, mtx[3] );
-
- rb_ct *cman = &manifold[manifold_len];
-
- int l = rb_capsule__scene( mtx, &capsule, NULL, world->geo_bh,
- cman, k_material_flag_walking );
-
- /* weld joints */
- for( int i=0; i<l; i ++ )
- cman[l].type = k_contact_type_edge;
- rb_manifold_filter_joint_edges( cman, l, 0.03f );
- l = rb_manifold_apply_filtered( cman, l );
-
- manifold_len += l;
- vg_line_capsule( mtx, capsule.r, capsule.h, VG__WHITE );
-
- /* add limits */
- if( state->activity >= k_skate_activity_grind_any ){
- for( int i=0; i<player_skate.limit_count; i++ ){
- struct grind_limit *limit = &player_skate.limits[i];
- rb_ct *ct = &manifold[ manifold_len ++ ];
- m4x3_mulv( localplayer.rb.to_world, limit->ra, ct->co );
- m3x3_mulv( localplayer.rb.to_world, limit->n, ct->n );
- ct->p = limit->p;
- ct->type = k_contact_type_default;
- }
- }
-
- /*
- * Phase 3: Dynamics
- * --------------------------------------------------------------------------
- */
-
-
- v3f world_cog;
- m4x3_mulv( localplayer.rb.to_world,
- player_skate.weight_distribution, world_cog );
- vg_line_point( world_cog, 0.02f, VG__BLACK );
-
- for( int i=0; i<manifold_len; i ++ ){
- rb_prepare_contact( &manifold[i], player_skate.substep_delta );
- rb_debug_contact( &manifold[i] );
- }
-
- /* yes, we are currently rebuilding mass matrices every frame. too bad! */
- v3f extent = { k_board_width*10.0f, 0.1f, k_board_length };
- float ex2 = k_board_interia*extent[0]*extent[0],
- ey2 = k_board_interia*extent[1]*extent[1],
- ez2 = k_board_interia*extent[2]*extent[2];
-
- float mass = 2.0f * (extent[0]*extent[1]*extent[2]);
- float inv_mass = 1.0f/mass;
-
- v3f I;
- I[0] = ((1.0f/12.0f) * mass * (ey2+ez2));
- I[1] = ((1.0f/12.0f) * mass * (ex2+ez2));
- I[2] = ((1.0f/12.0f) * mass * (ex2+ey2));
-
- m3x3f iI;
- m3x3_identity( iI );
- iI[0][0] = I[0];
- iI[1][1] = I[1];
- iI[2][2] = I[2];
- m3x3_inv( iI, iI );
-
- m3x3f iIw;
- m3x3_mul( iI, localplayer.rb.to_local, iIw );
- m3x3_mul( localplayer.rb.to_world, iIw, iIw );
-
- for( int j=0; j<10; j++ ){
- for( int i=0; i<manifold_len; i++ ){
- /*
- * regular dance; calculate velocity & total mass, apply impulse.
- */
-
- rb_ct *ct = &manifold[i];
-
- v3f rv, delta;
- v3_sub( ct->co, world_cog, delta );
- v3_cross( localplayer.rb.w, delta, rv );
- v3_add( localplayer.rb.v, rv, rv );
-
- v3f raCn;
- v3_cross( delta, ct->n, raCn );
-
- v3f raCnI, rbCnI;
- m3x3_mulv( iIw, raCn, raCnI );
-
- float normal_mass = 1.0f / (inv_mass + v3_dot(raCn,raCnI)),
- vn = v3_dot( rv, ct->n ),
- lambda = normal_mass * ( -vn );
-
- float temp = ct->norm_impulse;
- ct->norm_impulse = vg_maxf( temp + lambda, 0.0f );
- lambda = ct->norm_impulse - temp;
-
- v3f impulse;
- v3_muls( ct->n, lambda, impulse );
-
- v3_muladds( normal_total, impulse, inv_mass, normal_total );
- v3_muladds( localplayer.rb.v, impulse, inv_mass, localplayer.rb.v );
- v3_cross( delta, impulse, impulse );
- m3x3_mulv( iIw, impulse, impulse );
- v3_add( impulse, localplayer.rb.w, localplayer.rb.w );
-
- v3_cross( localplayer.rb.w, delta, rv );
- v3_add( localplayer.rb.v, rv, rv );
- vn = v3_dot( rv, ct->n );
- }
- }
-
- v3f dt;
- rb_depenetrate( manifold, manifold_len, dt );
- v3_add( dt, localplayer.rb.co, localplayer.rb.co );
- rb_update_matrices( &localplayer.rb );
-
- substep_count ++;
-
- if( player_skate.substep >= 0.0001f )
- goto begin_collision; /* again! */
-
- /*
- * End of collision and dynamics routine
- * --------------------------------------------------------------------------
- */
-
- f32 nforce = v3_length(normal_total);
- if( nforce > 4.0f ){
- if( nforce > 17.6f ){
- vg_info( "player fell off due to hitting ground too hard\n" );
- v3_muladds( localplayer.rb.v, normal_total, -1.0f, localplayer.rb.v );
- player__dead_transition( k_player_die_type_feet );
- return;
- }
-
- f32 amt = k_cam_punch;
- if( localplayer.cam_control.camera_mode == k_cam_firstperson ){
- amt *= 0.25f;
- }
-
- v3_muladds( localplayer.cam_land_punch_v, normal_total, amt,
- localplayer.cam_land_punch_v );
- }
-
- player_skate.surface = k_surface_prop_concrete;
-
- for( int i=0; i<manifold_len; i++ ){
- rb_ct *ct = &manifold[i];
- struct world_surface *surf = world_contact_surface( world, ct );
-
- if( surf->info.surface_prop > player_skate.surface )
- player_skate.surface = surf->info.surface_prop;
- }
-
- for( int i=0; i<k_wheel_count; i++ ){
- m4x3f mtx;
- m3x3_copy( localplayer.rb.to_world, mtx );
- m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, mtx[3] );
- vg_line_sphere( mtx, wheels[i].radius,
- (u32[]){ VG__WHITE, VG__BLACK,
- wheels[i].colour }[ wheels[i].state ]);
- }
-
- skate_integrate();
- vg_line_point( state->cog, 0.02f, VG__WHITE );
-
- u32 id = world_intersect_gates( world, localplayer.rb.co, state->prev_pos );
-
- if( id ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, mdl_entity_id_id(id) );
-
- m4x3_mulv( gate->transport, localplayer.rb.co, localplayer.rb.co );
- m3x3_mulv( gate->transport, localplayer.rb.v, localplayer.rb.v );
- m4x3_mulv( gate->transport, state->cog, state->cog );
- m3x3_mulv( gate->transport, state->cog_v, state->cog_v );
- m3x3_mulv( gate->transport, state->throw_v, state->throw_v );
- m3x3_mulv( gate->transport, state->head_position,
- state->head_position );
- m3x3_mulv( gate->transport, state->up_dir, state->up_dir );
-
- v4f transport_rotation;
- m3x3_q( gate->transport, transport_rotation );
- q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
- q_mul( transport_rotation, state->smoothed_rotation,
- state->smoothed_rotation );
- q_normalize( localplayer.rb.q );
- q_normalize( state->smoothed_rotation );
- rb_update_matrices( &localplayer.rb );
- player__pass_gate( id );
- }
-
- /* FIXME: Rate limit */
- static int stick_frames = 0;
-
- if( state->activity >= k_skate_activity_ground )
- stick_frames ++;
- else
- stick_frames = 0;
-
- if( stick_frames > 5 ) stick_frames = 5;
-
- if( stick_frames == 4 ){
- if( state->activity == k_skate_activity_ground ){
- if( (fabsf(state->slip) > 0.75f) ){
- player__networked_sfx( k_player_subsystem_skate, 128,
- k_player_skate_soundeffect_land_bad,
- localplayer.rb.co, 0.6f );
- }
- else{
- player__networked_sfx( k_player_subsystem_skate, 128,
- k_player_skate_soundeffect_land_good,
- localplayer.rb.co, 1.0f );
- }
- }
- else if( player_skate.surface == k_surface_prop_metal ){
- player__networked_sfx( k_player_subsystem_skate, 128,
- k_player_skate_soundeffect_grind_metal,
- localplayer.rb.co, 1.0f );
- }
- else{
- player__networked_sfx( k_player_subsystem_skate, 128,
- k_player_skate_soundeffect_grind_wood,
- localplayer.rb.co, 1.0f );
- }
- } else if( stick_frames == 0 ){
- /* TODO: EXIT SOUNDS */
- }
-
- if( (state->activity_prev < k_skate_activity_grind_any) &&
- (state->activity >= k_skate_activity_grind_any) ){
- state->velocity_limit = v3_length( localplayer.rb.v )*1.0f;
- state->grind_y_start = localplayer.rb.co[1];
- }
-
- if( state->activity >= k_skate_activity_grind_any ){
- f32 dy = localplayer.rb.co[1] - state->grind_y_start;
- if( dy < 0.0f ){
- state->velocity_limit += -dy*0.2f;
- }
- state->grind_y_start = localplayer.rb.co[1];
-
-
- f32 speed_end = v3_length( localplayer.rb.v );
- if( speed_end > state->velocity_limit ){
- v3_muls( localplayer.rb.v, state->velocity_limit/speed_end,
- localplayer.rb.v );
- }
- }
-}
-
-void player__skate_im_gui( ui_context *ctx )
-{
- struct player_skate_state *state = &player_skate.state;
- player__debugtext( ctx, 1, "V: %5.2f %5.2f %5.2f",localplayer.rb.v[0],
- localplayer.rb.v[1],
- localplayer.rb.v[2] );
- player__debugtext( ctx, 1, "CO: %5.2f %5.2f %5.2f",localplayer.rb.co[0],
- localplayer.rb.co[1],
- localplayer.rb.co[2] );
- player__debugtext( ctx, 1, "W: %5.2f %5.2f %5.2f",localplayer.rb.w[0],
- localplayer.rb.w[1],
- localplayer.rb.w[2] );
-
- const char *activity_txt[] = {
- "air",
- "air_to_grind",
- "ground",
- "handplant",
- "undefined (INVALID)",
- "grind_any (INVALID)",
- "grind_boardslide",
- "grind_metallic (INVALID)",
- "grind_back50",
- "grind_front50",
- "grind_5050"
- };
-
- player__debugtext( ctx, 1, "activity: %s", activity_txt[state->activity] );
- player__debugtext( ctx, 1, "flip: %.4f %.4f", state->flip_rate,
- state->flip_time );
- player__debugtext( ctx, 1, "trickv: %.2f %.2f %.2f",
- state->trick_vel[0],
- state->trick_vel[1],
- state->trick_vel[2] );
- player__debugtext( ctx, 1, "tricke: %.2fs %.2f %.2f %.2f",
- state->trick_time,
- state->trick_euler[0],
- state->trick_euler[1],
- state->trick_euler[2] );
-}
-
-void player__skate_animate(void){
- struct player_skate_state *state = &player_skate.state;
- struct player_skate_animator *animator = &player_skate.animator;
-
- /* Head */
- float kheight = 2.0f,
- kleg = 0.6f;
-
- v3_zero( animator->offset );
-
- v3f cog_local, cog_ideal;
- m4x3_mulv( localplayer.rb.to_local, state->cog, cog_local );
-
- v3_copy( state->up_dir, cog_ideal );
- v3_normalize( cog_ideal );
- m3x3_mulv( localplayer.rb.to_local, cog_ideal, cog_ideal );
-
- v3_sub( cog_ideal, cog_local, animator->offset );
-
- v3_muls( animator->offset, 4.0f, animator->offset );
- animator->offset[1] *= -1.0f;
-
- float curspeed = v3_length( localplayer.rb.v ),
- kickspeed = vg_clampf( curspeed*(1.0f/40.0f), 0.0f, 1.0f ),
- kicks = (vg_randf64(&vg.rand)-0.5f)*2.0f*kickspeed,
- sign = vg_signf( kicks );
-
- animator->wobble[0] = vg_lerpf( animator->wobble[0], kicks*kicks*sign,
- 6.0f*vg.time_delta);
- animator->wobble[1] = vg_lerpf( animator->wobble[1], animator->wobble[0],
- 2.4f*vg.time_delta);
-
- animator->offset[0] *= 0.26f;
- animator->offset[0] += animator->wobble[1]*3.0f;
-
- animator->offset[1] *= -0.3f;
- animator->offset[2] *= 0.01f;
-
- animator->offset[0]=vg_clampf(animator->offset[0],-0.8f,0.8f)*
- (1.0f-fabsf(animator->slide)*0.9f);
- animator->offset[1]=vg_clampf(animator->offset[1],-0.5f,0.0f);
-
- v3f cam_offset;
- v3_mul( animator->offset, (v3f){1.0f,0.3f,1.0f}, cam_offset );
-
- /* localized vectors */
- m4x3_mulv( localplayer.rb.to_local, state->cog, animator->local_cog );
-
- /*
- * Animation blending
- * ===========================================
- */
-
- /* sliding */
- {
- float desired = 0.0f;
- if( state->activity == k_skate_activity_ground )
- desired = vg_clampf( vg_maxf(fabsf( state->slip ),
- fabsf( state->skid ) ), 0.0f, 1.0f );
-
- animator->slide = vg_lerpf( animator->slide, desired, 2.4f*vg.time_delta);
-
- f32 dirx = 0.0f;
- if( fabsf(state->slip) > fabsf(dirx) ) dirx = state->slip;
- if( fabsf(state->skid) > fabsf(dirx) ) dirx = state->skid;
- if( fabsf( dirx ) > 0.025f ) dirx = vg_signf( dirx );
- dirx = vg_signf( state->slip );
- vg_slewf( &animator->x, dirx, 2.6f*vg.time_delta );
- }
-
- cam_offset[0] += animator->slide * -animator->x;
- v3_copy( cam_offset, localplayer.cam_control.tpv_offset_extra );
-
- /* movement information */
- int iair = state->activity <= k_skate_activity_air_to_grind;
-
- float dirz = state->reverse > 0.0f? 0.0f: 1.0f,
- fly = iair? 1.0f: 0.0f,
- wdist= player_skate.weight_distribution[2] / k_board_length;
-
- if( state->activity >= k_skate_activity_grind_any )
- wdist = 0.0f;
-
- animator->z = vg_lerpf( animator->z, dirz, 2.4f*vg.time_delta );
- animator->skid = state->skid;
- animator->fly = vg_lerpf( animator->fly, fly, 3.4f*vg.time_delta );
- animator->weight = vg_lerpf( animator->weight, wdist, 9.0f*vg.time_delta );
-
- float stand = 1.0f - vg_clampf( curspeed * 0.03f, 0.0f, 1.0f );
- animator->stand = vg_lerpf( animator->stand, stand, 6.0f*vg.time_delta );
- animator->reverse = state->reverse;
-
- if( fabsf(state->slip) > 0.3f ){
- f32 slide_x = v3_dot(localplayer.rb.v, localplayer.rb.to_world[0]);
- state->delayed_slip_dir = vg_signf(slide_x);
- }
-
- /* grinding */
- f32 grind=state->activity >= k_skate_activity_grind_any? 1.0f: 0.0f;
- animator->grind = vg_lerpf( animator->grind, grind, 5.0f*vg.time_delta );
-
- f32 grind_frame = 0.5f;
-
- if( state->activity == k_skate_activity_grind_front50 )
- grind_frame = 0.0f;
- else if( state->activity == k_skate_activity_grind_back50 )
- grind_frame = 1.0f;
-
- animator->grind_balance = vg_lerpf( animator->grind_balance, grind_frame,
- 5.0f*vg.time_delta );
- animator->activity = state->activity;
- animator->surface = player_skate.surface;
-
- /* pushing */
- animator->push_time = vg.time - state->start_push;
- animator->push = vg_lerpf( animator->push,
- (vg.time - state->cur_push) < 0.125,
- 6.0f*vg.time_delta );
-
- /* jumping */
- animator->jump_charge = state->jump_charge;
- animator->jump = vg_lerpf( animator->jump, animator->jump_charge,
- 8.4f*vg.time_delta );
-
- /* trick setup */
- animator->jump_dir = state->jump_dir;
- f32 jump_start_frame = 14.0f/30.0f;
- animator->jump_time = animator->jump_charge * jump_start_frame;
- f32 jump_frame = (vg.time - state->jump_time) + jump_start_frame;
- if( jump_frame >= jump_start_frame && jump_frame <= (40.0f/30.0f) )
- animator->jump_time = jump_frame;
-
- /* trick */
- float jump_t = vg.time-state->jump_time;
- float k=17.0f;
- float h = k*jump_t;
- float extra = h*exp(1.0-h) * (state->jump_dir?1.0f:-1.0f);
- extra *= state->slap * 4.0f;
-
- v3_add( state->trick_euler, state->trick_residuald,
- animator->board_euler );
- v3_muls( animator->board_euler, VG_TAUf, animator->board_euler );
-
- animator->board_euler[0] *= 0.5f;
- animator->board_euler[1] += extra;
- animator->trick_type = state->trick_type;
-
- /* board lean */
- f32 lean1, lean2 = animator->steer[0] * animator->reverse * -0.36f,
- lean;
-
- lean1 = animator->slide * animator->delayed_slip_dir;
- if( fabsf(lean1)>fabsf(lean2) ) lean = lean1;
- else lean = lean2;
-
- if( ((int)roundf(animator->board_euler[0]/VG_PIf)) % 2 ) lean = -lean;
- lean = vg_clampf( lean, -1.0f, 1.0f );
- animator->board_lean =
- vg_lerpf(animator->board_lean, lean, vg.time_delta*18.0f);
-
- /* feet placement */
- struct player_board *board =
- addon_cache_item_if_loaded( k_addon_type_board,
- localplayer.board_view_slot );
- if( board ){
- if( animator->weight > 0.0f ){
- animator->foot_offset[0] =
- board->truck_positions[k_board_truck_back][2]+0.3f;
- }
- else{
- animator->foot_offset[1] =
- board->truck_positions[k_board_truck_front][2]-0.3f;
- }
- }
-
- f32 slapm = vg_maxf( 1.0f-v3_length2( state->trick_vel ), 0.0f );
- animator->slap = state->slap;
- animator->subslap = vg_lerpf( animator->subslap, slapm,
- vg.time_delta*10.0f );
-
-#if 0
- f32 l = ((state->activity < k_skate_activity_ground) &&
- v3_length2(state->trick_vel) > 0.1f )? 1: 0;
- animator->trick_foot = vg_lerpf( animator->trick_foot, l,
- 8.4f*vg.time_delta );
-#endif
-
- animator->trick_foot = vg_exp_impulse( state->trick_time, 5.0f );
-
- /* grab */
- v2f grab_input;
- joystick_state( k_srjoystick_grab, grab_input );
- v2_add( state->grab_mouse_delta, grab_input, grab_input );
-
- if( v2_length2( grab_input ) <= 0.001f ) grab_input[0] = -1.0f;
- else v2_normalize_clamp( grab_input );
- v2_lerp( animator->grab, grab_input, 2.4f*vg.time_delta, animator->grab );
- animator->grabbing = state->grabbing;
-
- /* steer */
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
- animator->airdir = vg_lerpf( animator->airdir,
- -steer[0], 2.4f*vg.time_delta );
-
- animator->steer[0] = steer[0];
- animator->steer[1] = vg_lerpf( animator->steer[1],
- steer[0], 4.0f*vg.time_delta );
-
-
- /* flip angle */
- if( (state->activity <= k_skate_activity_air_to_grind) &&
- (fabsf(state->flip_rate) > 0.01f) ){
- float substep = vg.time_fixed_extrapolate;
- float t = state->flip_time+state->flip_rate*substep*vg.time_fixed_delta;
- sign = vg_signf( t );
-
- t = 1.0f - vg_minf( 1.0f, fabsf( t * 1.1f ) );
- t = sign * (1.0f-t*t);
-
- f32 angle = vg_clampf( t, -1.0f, 1.0f ) * VG_TAUf,
- distm = state->land_dist * fabsf(state->flip_rate) * 3.0f,
- blend = vg_clampf( 1.0f-distm, 0.0f, 1.0f );
- angle = vg_lerpf( angle, vg_signf(state->flip_rate)*VG_TAUf, blend );
- q_axis_angle( animator->qflip, state->flip_axis, angle );
- }
- else
- q_identity( animator->qflip );
-
- /* counter-rotation */
- if( v3_length2( state->up_dir ) > 0.001f ){
- v4_lerp( state->smoothed_rotation, localplayer.rb.q,
- 2.0f*vg.time_frame_delta,
- state->smoothed_rotation );
- q_normalize( state->smoothed_rotation );
-
- v3f yaw_smooth = {1.0f,0.0f,0.0f};
- q_mulv( state->smoothed_rotation, yaw_smooth, yaw_smooth );
- m3x3_mulv( localplayer.rb.to_local, yaw_smooth, yaw_smooth );
- yaw_smooth[1] = 0.0f;
- v3_normalize( yaw_smooth );
-
- f32 yaw_counter_rotate = yaw_smooth[0];
- yaw_counter_rotate = vg_maxf( 0.7f, yaw_counter_rotate );
- yaw_counter_rotate = acosf( yaw_counter_rotate );
- yaw_counter_rotate *= 1.0f-animator->fly;
-
- v3f ndir;
- m3x3_mulv( localplayer.rb.to_local, state->up_dir, ndir );
- v3_normalize( ndir );
-
- v3f up = { 0.0f, 1.0f, 0.0f };
- float a = v3_dot( ndir, up );
- a = acosf( vg_clampf( a, -1.0f, 1.0f ) );
-
- v3f axis;
- v4f qcounteryaw, qfixup;
-
- v3_cross( up, ndir, axis );
- q_axis_angle( qfixup, axis, a*2.0f );
-
- v3_cross( (v3f){1.0f,0.0f,0.0f}, yaw_smooth, axis );
- q_axis_angle( qcounteryaw, axis, yaw_counter_rotate );
-
- q_mul( qcounteryaw, qfixup, animator->qfixuptotal );
- q_normalize( animator->qfixuptotal );
-
- v3f p1, p2;
- m3x3_mulv( localplayer.rb.to_world, up, p1 );
- m3x3_mulv( localplayer.rb.to_world, ndir, p2 );
-
- vg_line_arrow( localplayer.rb.co, p1, 0.5f, VG__PINK );
- vg_line_arrow( localplayer.rb.co, p2, 0.5f, VG__PINK );
- }
- else q_identity( animator->qfixuptotal );
-
- if( state->activity == k_skate_activity_handplant ){
- v3_copy( state->store_co, animator->root_co );
- v4_copy( state->store_q, animator->root_q );
- v3_zero( animator->root_v );
- }
- else {
- rb_extrapolate( &localplayer.rb, animator->root_co, animator->root_q );
- v3_copy( localplayer.rb.v, animator->root_v );
- }
-
- animator->handplant_t = state->handplant_t;
-}
-
-void player__skate_pose( void *_animator, player_pose *pose ){
- struct skeleton *sk = &localplayer.skeleton;
- struct player_skate_animator *animator = _animator;
-
- pose->type = k_player_pose_type_ik;
- v3_copy( animator->root_co, pose->root_co );
- v4_copy( animator->root_q, pose->root_q );
-
- /* transform */
- v3f ext_up,ext_co;
- q_mulv( pose->root_q, (v3f){0.0f,1.0f,0.0f}, ext_up );
- v3_copy( pose->root_co, ext_co );
- v3_muladds( pose->root_co, ext_up, -0.1f, pose->root_co );
-
- /* apply flip rotation at midpoint */
- q_mul( animator->qflip, pose->root_q, pose->root_q );
- q_normalize( pose->root_q );
-
- v3f rotation_point, rco;
- v3_muladds( ext_co, ext_up, 0.5f, rotation_point );
- v3_sub( pose->root_co, rotation_point, rco );
-
- q_mulv( animator->qflip, rco, rco );
- v3_add( rco, rotation_point, pose->root_co );
-
- /* ANIMATIONS
- * ---------------------------------------------------------------------- */
-
- mdl_keyframe apose[32], bpose[32];
- mdl_keyframe ground_pose[32];
- {
- /* stand/crouch */
- f32 dir_frame = animator->z * (15.0f/30.0f),
- stand_blend = animator->offset[1]*-2.0f;
-
- pose->board.lean = animator->board_lean;
-
- stand_blend = vg_clampf( 1.0f-animator->local_cog[1], 0, 1 );
-
- skeleton_sample_anim( sk, player_skate.anim_stand, dir_frame, apose );
- skeleton_sample_anim( sk, player_skate.anim_highg, dir_frame, bpose );
- skeleton_lerp_pose( sk, apose, bpose, stand_blend, apose );
-
- /* sliding */
- f32 slide_frame = animator->x * 0.25f + 0.25f;
- skeleton_sample_anim( sk, player_skate.anim_slide, slide_frame, bpose );
-
- mdl_keyframe mirrored[32];
- player_mirror_pose( bpose, mirrored );
- skeleton_lerp_pose( sk, bpose, mirrored, animator->z, bpose );
- skeleton_lerp_pose( sk, apose, bpose, animator->slide, apose );
-
- if( animator->reverse > 0.0f ){
- skeleton_sample_anim( sk, player_skate.anim_push, animator->push_time,
- bpose );
- }
- else{
- skeleton_sample_anim( sk, player_skate.anim_push_reverse,
- animator->push_time, bpose );
- }
- skeleton_lerp_pose( sk, apose, bpose, animator->push, apose );
-
- struct skeleton_anim *jump_anim = animator->jump_dir?
- player_skate.anim_ollie:
- player_skate.anim_ollie_reverse;
-
- f32 setup_blend = vg_minf( animator->jump, 1.0f );
- skeleton_sample_anim_clamped( sk, jump_anim, animator->jump_time, bpose );
- skeleton_lerp_pose( sk, apose, bpose, setup_blend, ground_pose );
- }
-
- mdl_keyframe air_pose[32];
- {
- float air_frame = (animator->airdir*0.5f+0.5f) * (15.0f/30.0f);
- skeleton_sample_anim( sk, player_skate.anim_air, air_frame, apose );
-
- float ang = atan2f( animator->grab[0], animator->grab[1] ),
- ang_unit = (ang+VG_PIf) * (1.0f/VG_TAUf),
- grab_frame = ang_unit * (15.0f/30.0f);
-
- skeleton_sample_anim( sk, player_skate.anim_grabs, grab_frame, bpose );
- skeleton_lerp_pose( sk, apose, bpose, animator->grabbing, air_pose );
- }
-
- skeleton_lerp_pose( sk, ground_pose, air_pose, animator->fly,
- pose->keyframes );
-
- mdl_keyframe *kf_board = &pose->keyframes[localplayer.id_board-1],
- *kf_foot_l = &pose->keyframes[localplayer.id_ik_foot_l-1],
- *kf_foot_r = &pose->keyframes[localplayer.id_ik_foot_r-1],
- *kf_knee_l = &pose->keyframes[localplayer.id_ik_knee_l-1],
- *kf_knee_r = &pose->keyframes[localplayer.id_ik_knee_r-1],
- *kf_hip = &pose->keyframes[localplayer.id_hip-1],
- *kf_wheels[] = { &pose->keyframes[localplayer.id_wheel_r-1],
- &pose->keyframes[localplayer.id_wheel_l-1] };
-
-
- mdl_keyframe grind_pose[32];
- {
- f32 frame = animator->grind_balance * 0.5f;
-
- skeleton_sample_anim( sk, player_skate.anim_grind, frame, apose );
- skeleton_sample_anim( sk, player_skate.anim_grind_jump, frame, bpose );
- skeleton_lerp_pose( sk, apose, bpose, animator->jump, grind_pose );
- }
- skeleton_lerp_pose( sk, pose->keyframes, grind_pose,
- animator->grind, pose->keyframes );
- float add_grab_mod = 1.0f - animator->fly;
-
- /* additive effects */
- u32 apply_to[] = { localplayer.id_hip,
- localplayer.id_ik_hand_l,
- localplayer.id_ik_hand_r,
- localplayer.id_ik_elbow_l,
- localplayer.id_ik_elbow_r };
-
- float apply_rates[] = { 1.0f,
- 0.75f,
- 0.75f,
- 0.75f,
- 0.75f };
-
- for( int i=0; i<VG_ARRAY_LEN(apply_to); i ++ ){
- pose->keyframes[apply_to[i]-1].co[0] += animator->offset[0]*add_grab_mod;
- pose->keyframes[apply_to[i]-1].co[2] += animator->offset[2]*add_grab_mod;
- }
-
-#if 1
- /* angle 'correction' */
- v3f origin;
- v3_add( sk->bones[localplayer.id_hip].co, kf_hip->co, origin );
-
- for( int i=0; i<VG_ARRAY_LEN(apply_to); i ++ ){
- mdl_keyframe *kf = &pose->keyframes[apply_to[i]-1];
- keyframe_rotate_around( kf, origin, sk->bones[apply_to[i]].co,
- animator->qfixuptotal );
- }
-#endif
-
-
- if( animator->activity == k_skate_activity_handplant ){
- struct skeleton_anim *anim = player_skate.anim_handplant;
-
- mdl_keyframe hpose[32];
- skeleton_sample_anim_clamped( sk, anim, animator->handplant_t, hpose );
- if( animator->reverse < 0.0f )
- player_mirror_pose( hpose, hpose );
-
- mdl_keyframe *kf_world = &hpose[ localplayer.id_world -1 ];
- m4x3f world, mmdl, world_view;
- q_m3x3( kf_world->q, world );
- v3_copy( kf_world->co, world[3] );
-
- q_m3x3( pose->root_q, mmdl );
- v3_copy( pose->root_co, mmdl[3] );
-
- m4x3_mul( mmdl, world, world_view );
-
- vg_line_arrow( world_view[3], world_view[0], 1.0f, 0xff0000ff );
- vg_line_arrow( world_view[3], world_view[1], 1.0f, 0xff00ff00 );
- vg_line_arrow( world_view[3], world_view[2], 1.0f, 0xffff0000 );
-
- m4x3f invworld;
- m4x3_invert_affine( world, invworld );
- m4x3_mul( mmdl, invworld, world_view );
-
- m3x3_q( world_view, pose->root_q );
- v3_copy( world_view[3], pose->root_co );
-
- f32 t = animator->handplant_t,
- frames = anim->length-1,
- length = animator->activity == k_skate_activity_handplant?
- frames / anim->rate:
- 999999,
- end_dist = vg_minf( t, length - t )/k_anim_transition,
- blend = vg_smoothstepf( vg_minf(1,end_dist) );
-
- skeleton_lerp_pose( sk, pose->keyframes, hpose, blend, pose->keyframes );
- }
-
-
- /* trick rotation */
- v4f qtrick, qyaw, qpitch, qroll;
- q_axis_angle( qyaw, (v3f){0.0f,1.0f,0.0f}, animator->board_euler[0] );
- q_axis_angle( qpitch, (v3f){1.0f,0.0f,0.0f}, animator->board_euler[1] );
- q_axis_angle( qroll, (v3f){0.0f,0.0f,1.0f}, animator->board_euler[2] );
-
- q_mul( qyaw, qroll, qtrick );
- q_mul( qpitch, qtrick, qtrick );
- q_mul( kf_board->q, qtrick, kf_board->q );
- q_normalize( kf_board->q );
-
- kf_foot_l->co[2] = vg_lerpf( kf_foot_l->co[2], animator->foot_offset[0],
- 0.5f * animator->weight );
- kf_foot_r->co[2] = vg_lerpf( kf_foot_r->co[2], animator->foot_offset[1],
- -0.5f * animator->weight );
-
- kf_foot_l->co[1] += animator->slap;
- kf_foot_r->co[1] += animator->slap;
- kf_knee_l->co[1] += animator->slap;
- kf_knee_r->co[1] += animator->slap;
- kf_board->co[1] += animator->slap * animator->subslap;
- kf_hip->co[1] += animator->slap * 0.25f;
-
- /* kickflip and shuvit are in the wrong order for some reason */
- if( animator->trick_type == k_trick_type_kickflip ){
- kf_foot_l->co[0] += animator->trick_foot * 0.15f;
- kf_foot_r->co[0] -= animator->trick_foot * 0.15f;
- kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
- kf_foot_r->co[1] -= animator->trick_foot * 0.18f;
- }
- else if( animator->trick_type == k_trick_type_shuvit ){
- kf_foot_l->co[0] += animator->trick_foot * 0.2f;
- kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
- kf_foot_r->co[0] -= animator->trick_foot * 0.1f;
- kf_foot_r->co[1] += animator->trick_foot * 0.09f;
- }
- else if( animator->trick_type == k_trick_type_treflip ){
- kf_foot_l->co[0] += animator->trick_foot * 0.2f;
- kf_foot_r->co[0] -= animator->trick_foot * 0.15f;
- kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
- kf_foot_r->co[1] -= animator->trick_foot * 0.18f;
- }
-
- /*
- * animation wishlist:
- * boardslide/grind jump animations
- * when tricking the slap should not appply or less apply
- * not animations however DONT target grinds that are vertically down.
- */
-
- /* truck rotation */
- for( int i=0; i<2; i++ ){
- float a = vg_minf( player_skate.truckv0[i][0], 1.0f );
- a = -acosf( a ) * vg_signf( player_skate.truckv0[i][1] );
-
- v4f q;
- q_axis_angle( q, (v3f){0.0f,0.0f,1.0f}, a );
- q_mul( q, kf_wheels[i]->q, kf_wheels[i]->q );
- q_normalize( kf_wheels[i]->q );
- }
-
-#if 1
- {
- mdl_keyframe
- *kf_head = &pose->keyframes[localplayer.id_head-1],
- *kf_elbow_l = &pose->keyframes[localplayer.id_ik_elbow_l-1],
- *kf_elbow_r = &pose->keyframes[localplayer.id_ik_elbow_r-1],
- *kf_hand_l = &pose->keyframes[localplayer.id_ik_hand_l-1],
- *kf_hand_r = &pose->keyframes[localplayer.id_ik_hand_r-1],
- *kf_hip = &pose->keyframes[localplayer.id_hip-1];
-
- float warble = vg_perlin_fract_1d( vg.time, 2.0f, 2, 300 );
- warble *= vg_maxf(animator->grind, fabsf(animator->weight)) * 0.3f;
-
- v4f qrot;
- q_axis_angle( qrot, (v3f){0.8f,0.7f,0.6f}, warble );
-
- v3f origin = {0.0f,0.2f,0.0f};
- keyframe_rotate_around( kf_hand_l, origin,
- sk->bones[localplayer.id_ik_hand_l].co, qrot );
- keyframe_rotate_around( kf_hand_r, origin,
- sk->bones[localplayer.id_ik_hand_r].co, qrot );
- keyframe_rotate_around( kf_hip, origin,
- sk->bones[localplayer.id_hip].co, qrot );
- keyframe_rotate_around( kf_elbow_r, origin,
- sk->bones[localplayer.id_ik_elbow_r].co, qrot );
- keyframe_rotate_around( kf_elbow_l, origin,
- sk->bones[localplayer.id_ik_elbow_l].co, qrot );
-
- q_inv( qrot, qrot );
- q_mul( qrot, kf_head->q, kf_head->q );
- q_normalize( kf_head->q );
-
-
- /* hand placement */
-
- u32 hand_id = animator->z < 0.5f?
- localplayer.id_ik_hand_l: localplayer.id_ik_hand_r;
-
- v3f sample_co;
- m4x3f mmdl;
- q_m3x3( pose->root_q, mmdl );
- q_mulv( pose->root_q, pose->keyframes[hand_id-1].co, mmdl[3] );
- v3_add( mmdl[3], pose->root_co, mmdl[3] );
- m4x3_mulv( mmdl, sk->bones[hand_id].co, sample_co );
-
- v3_muladds( sample_co, mmdl[1], 0.3f, sample_co );
- vg_line_point( sample_co, 0.04f, 0xff0000ff );
-
- v3f dir;
- v3_muls( mmdl[1], -1.0f, dir );
- ray_hit hit = { .dist = 1.5f };
- if(ray_world( world_current_instance(), sample_co, dir, &hit, 0 )){
- vg_line_cross( hit.pos, 0xff0000ff, 0.05f );
- vg_line( sample_co, hit.pos, 0xffffffff );
-
- f32 amt = vg_maxf( 0.0f, animator->slide-0.5f ) *
- 2.0f * fabsf(animator->z*2.0f-1.0f);
-
- f32 d = (hit.dist - 0.3f) * amt;
- pose->keyframes[hand_id-1].co[1] -= d;
- kf_hip->co[1] -= d*0.4f;
- }
-
- /* skid */
- f32 amt = vg_maxf(0.0f, (animator->slide - 0.5f) * 2.0f);
- u8 skidders[] = { localplayer.id_ik_foot_l,
- localplayer.id_ik_foot_r,
- localplayer.id_board };
- v4f qskid;
- q_axis_angle( qskid, (v3f){0,1,0}, -animator->steer[1]*0.2f );
-
- for( u32 i=0; i<VG_ARRAY_LEN(skidders); i ++ ){
- mdl_keyframe *kf = &pose->keyframes[ skidders[i]-1 ];
- keyframe_rotate_around( kf,
- (v3f){0,0,0.4f*(animator->z*2.0f-1.0f)*amt},
- sk->bones[skidders[i]].co, qskid );
- }
- }
-#endif
-}
-
-void player__skate_effects( void *_animator, m4x3f *final_mtx,
- struct player_board *board,
- struct player_effects_data *effect_data ){
- struct skeleton *sk = &localplayer.skeleton;
- struct player_skate_animator *animator = _animator;
-
- v3f vp0, vp1, vpc;
- if( board ){
- v3_copy((v3f){0.0f,0.02f, board->truck_positions[0][2]}, vp1 );
- v3_copy((v3f){0.0f,0.02f, board->truck_positions[1][2]}, vp0 );
- }
- else{
- v3_zero( vp0 );
- v3_zero( vp1 );
- }
-
- v3f *board_mtx = final_mtx[ localplayer.id_board ];
- m4x3_mulv( board_mtx, vp0, vp0 );
- m4x3_mulv( board_mtx, vp1, vp1 );
- v3_add( vp0, vp1, vpc );
- v3_muls( vpc, 0.5f, vpc );
-
- if( animator->surface == k_surface_prop_sand ){
- if( (animator->slide>0.4f) && (v3_length2(animator->root_v)>4.0f*4.0f) ){
- v3f v, co;
- v3_muls( animator->root_v, 0.5f, v );
- v3_lerp( vp0, vp1, vg_randf64(&vg.rand), co );
-
- effect_data->sand.colour = 0xff8ec4e6;
- effect_spark_apply( &effect_data->sand, co, v, vg.time_delta * 8.0 );
- }
- }
-
- if( animator->grind > 0.5f ){
- int back = 0, front = 0, mid = 0;
-
- if( animator->activity == k_skate_activity_grind_5050 ){
- back = 1;
- front = 1;
- }
- else if( animator->activity == k_skate_activity_grind_back50 ){
- back = 1;
- }
- else if( animator->activity == k_skate_activity_grind_front50 ){
- front = 1;
- }
- else if( animator->activity == k_skate_activity_grind_boardslide ){
- mid = 1;
- }
-
- if( back ){
- effect_spark_apply( &effect_data->spark, vp0,
- animator->root_v, vg.time_delta );
- }
-
- if( front ){
- effect_spark_apply( &effect_data->spark, vp1,
- animator->root_v, vg.time_delta );
- }
-
- if( mid ){
- effect_spark_apply( &effect_data->spark, vpc,
- animator->root_v, vg.time_delta );
- }
- }
-}
-
-void player__skate_post_animate(void){
- struct player_skate_state *state = &player_skate.state;
- localplayer.cam_velocity_influence = 1.0f;
- localplayer.cam_dist = 1.8f;
-
- v3f head = { 0.0f, 1.8f, 0.0f };
- m4x3_mulv( localplayer.final_mtx[ localplayer.id_head ],
- head, state->head_position );
- m4x3_mulv( localplayer.rb.to_local,
- state->head_position, state->head_position );
-}
-
-void player__skate_reset_animator(void){
- struct player_skate_state *state = &player_skate.state;
-
- memset( &player_skate.animator, 0, sizeof(player_skate.animator) );
-
- if( state->activity <= k_skate_activity_air_to_grind )
- player_skate.animator.fly = 1.0f;
- else
- player_skate.animator.fly = 0.0f;
-}
-
-void player__skate_clear_mechanics(void)
-{
- struct player_skate_state *state = &player_skate.state;
- state->jump_charge = 0.0f;
- state->charging_jump = 0;
- state->jump_dir = 0;
- v3_zero( state->flip_axis );
- state->flip_time = 0.0f;
- state->flip_rate = 0.0f;
- state->reverse = 0.0f;
- state->slip = 0.0f;
- state->grabbing = 0.0f;
- v2_zero( state->grab_mouse_delta );
- state->slap = 0.0f;
- state->jump_time = 0.0;
- state->start_push = 0.0;
- state->cur_push = 0.0;
- state->air_start = 0.0;
-
- v3_zero( state->air_init_v );
- v3_zero( state->air_init_co );
-
- state->gravity_bias = k_gravity;
- v3_copy( localplayer.rb.co, state->prev_pos );
- v4_copy( localplayer.rb.q, state->smoothed_rotation );
- v3_zero( state->throw_v );
- v3_zero( state->trick_vel );
- v3_zero( state->trick_euler );
- v3_zero( state->cog_v );
- state->grind_cooldown = 0;
- state->surface_cooldown = 0;
- v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], 1.0f, state->cog );
- v3_copy( localplayer.rb.to_world[1], state->up_dir );
- v3_copy( localplayer.rb.to_world[1], player_skate.surface_picture );
- v3_copy( localplayer.rb.co, state->prev_pos );
- v3_zero( player_skate.weight_distribution );
-
- v3f head = { 0.0f, 1.8f, 0.0f };
- m4x3_mulv( localplayer.rb.to_world, head, state->head_position );
-}
-
-#include "network_compression.h"
-
-void player__skate_animator_exchange( bitpack_ctx *ctx, void *data ){
- struct player_skate_animator *animator = data;
-
- bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
- bitpack_qquat( ctx, animator->root_q );
-
- bitpack_qv3f( ctx, 8, -1.0f, 1.0f, animator->offset );
- bitpack_qv3f( ctx, 8, -1.0f, 1.0f, animator->local_cog );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->slide );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->z );
- bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->x );
-
- /* these could likely be pressed down into single bits if needed */
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->fly );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->grind );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->stand );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->push );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->jump ); /*??*/
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->jump_charge ); /*??*/
-
- /* just the sign bit? */
- bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->reverse );
- bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->delayed_slip_dir );
- bitpack_bytes( ctx, 1, &animator->jump_dir );
- bitpack_bytes( ctx, 1, &animator->trick_type );
-
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->grind_balance );
- bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->airdir );
- bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->weight );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->trick_foot );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->slap );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->subslap );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->grabbing );
-
- /* animator->wobble is ommited */
-
- bitpack_qv2f( ctx, 8, -1.0f, 1.0f, animator->foot_offset );
- bitpack_qquat( ctx, animator->qfixuptotal );
- bitpack_qquat( ctx, animator->qflip );
-
- bitpack_qv3f( ctx, 16, -100.0f, 100.0f, animator->board_euler );
- bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->board_lean );
- bitpack_qv2f( ctx, 8, -1.0f, 1.0f, animator->steer );
- bitpack_qv2f( ctx, 8, -1.0f, 1.0f, animator->grab );
-
- bitpack_qf32( ctx, 16, 0.0f, 120.0f, &animator->push_time );
- bitpack_qf32( ctx, 16, 0.0f, 120.0f, &animator->jump_time );
- bitpack_qf32( ctx, 16, 0.0f, 4.0f, &animator->handplant_t );
- bitpack_qv3f( ctx, 16, -100.0f, 100.0f, animator->root_v );
- bitpack_bytes( ctx, 1, &animator->activity );
-}
-
-void player__skate_sfx_oneshot( u8 id, v3f pos, f32 volume ){
- audio_lock();
-
- if( id == k_player_skate_soundeffect_jump ){
- audio_oneshot_3d( &audio_jumps[vg_randu32(&vg.rand)%2],
- pos, 40.0f, volume );
- }
- else if( id == k_player_skate_soundeffect_tap ){
- audio_oneshot_3d( &audio_taps[vg_randu32(&vg.rand)%4],
- pos, 40.0f, volume );
- }
- else if( id == k_player_skate_soundeffect_land_good ){
- audio_oneshot_3d( &audio_lands[vg_randu32(&vg.rand)%3],
- pos, 40.0f, volume );
- }
- else if( id == k_player_skate_soundeffect_land_bad ){
- audio_oneshot_3d( &audio_lands[vg_randu32(&vg.rand)%2+3],
- pos, 40.0f, volume );
- }
- else if( id == k_player_skate_soundeffect_grind_metal ){
- audio_oneshot_3d( &audio_board[3], pos, 40.0f, volume );
- }
- else if( id == k_player_skate_soundeffect_grind_wood ){
- audio_oneshot_3d( &audio_board[8], pos, 40.0f, volume );
- }
-
- audio_unlock();
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_audio.h"
-#include "player.h"
-#include "player_api.h"
-
-typedef struct jump_info jump_info;
-
-struct player_skate{
- struct player_skate_state{
- enum skate_activity{
- k_skate_activity_air,
- k_skate_activity_air_to_grind,
- k_skate_activity_ground,
- k_skate_activity_handplant,
- k_skate_activity_undefined,
- k_skate_activity_grind_any,
- k_skate_activity_grind_boardslide,
- k_skate_activity_grind_metallic,
- k_skate_activity_grind_back50,
- k_skate_activity_grind_front50,
- k_skate_activity_grind_5050
- }
- activity,
- activity_prev;
-
- u32 grind_cooldown,
- surface_cooldown;
-
- f32 reverse, slip, delayed_slip_dir;
- int manual_direction;
-
- /* tricks */
- v3f flip_axis;
- float flip_time,
- flip_rate;
-
- v3f trick_vel, /* measured in units of TAU/s */
- trick_euler; /* measured in units of TAU */
- v3f trick_residualv, /* spring */
- trick_residuald;
-
- float trick_time;
- enum trick_type{
- k_trick_type_none,
- k_trick_type_kickflip,
- k_trick_type_shuvit,
- k_trick_type_treflip,
- }
- trick_type;
- float gravity_bias;
-
- f32 trick_input_collect;
-
- v3f up_dir;
- v3f head_position;
-
- v3f throw_v;
- v3f cog_v, cog;
-
- float grabbing;
- v2f grab_mouse_delta;
-
- int charging_jump, jump_dir;
- float jump_charge,
- slap;
-
- double jump_time;
- double start_push,
- cur_push;
-
- v3f prev_pos;
-
- /* initial launch conditions */
- double air_start;
- v3f air_init_v,
- air_init_co;
-
- float land_dist;
- v3f land_normal;
- v4f smoothed_rotation;
-
- f32 velocity_limit, grind_y_start, skid;
- f32 handplant_t;
-
- v3f store_cog_v, store_cog, store_co;
- v4f store_smoothed, store_q;
- }
- state;
-
- struct player_skate_animator {
- v3f root_co;
- v4f root_q;
- v3f root_v;
-
- v3f offset,
- local_cog;
-
- f32 slide,
- skid,
- z,
- x,
- fly,
- grind,
- grind_balance,
- stand,
- push,
- jump,
- airdir,
- weight,
- trick_foot,
- slap,
- subslap,
- reverse,
- delayed_slip_dir,
- grabbing;
-
- v2f wobble;
- f32 foot_offset[2];
-
- v4f qfixuptotal;
- v4f qflip;
-
- v3f board_euler;
- f32 board_lean;
- v2f steer, grab;
-
- f32 jump_charge;
-
- /* linear anims. TODO: we can union a bunch of variables here depending
- * on activity. */
- f32 push_time, jump_time, handplant_t;
- u8 jump_dir;
- u8 trick_type; /* todo: should encode grind type */
- u8 activity, surface;
- }
- animator;
-
- f32 collect_feedback;
-
- /* animation /audio
- * --------------------------------------------------------------*/
- struct skeleton_anim *anim_stand, *anim_highg, *anim_slide,
- *anim_air, *anim_grind, *anim_grind_jump,
- *anim_push, *anim_push_reverse,
- *anim_ollie, *anim_ollie_reverse,
- *anim_grabs, *anim_stop,
- *anim_handplant;
-
- /* vectors representing the direction of the axels in localspace */
- v3f truckv0[2];
-
- audio_channel *aud_main, *aud_slide, *aud_air;
- enum mdl_surface_prop surface, audio_surface;
-
- int wheel_contacts[2];
- float sample_change_cooldown;
-
- enum {
- k_skate_sample_concrete,
- k_skate_sample_wood,
- k_skate_sample_concrete_scrape_metal,
- k_skate_sample_concrete_scrape_wood,
- k_skate_sample_metal_scrape_generic
- }
- main_sample_type;
-
- /*
- * Physics
- * ----------------------------------------------------
- */
-
- float substep, substep_delta;
-
- struct jump_info{
- v3f log[50];
- v3f n;
- v3f apex;
- v3f v;
-
- float gravity;
-
- int log_length;
- float score,
- land_dist;
-
- enum prediction_type{
- k_prediction_none,
- k_prediction_unset,
- k_prediction_land,
- k_prediction_grind
- }
- type;
-
- u32 colour;
- }
- possible_jumps[36];
- u32 possible_jump_count;
-
- v3f surface_picture,
- weight_distribution,
- grind_vec,
- grind_dir;
-
- float grind_strength;
- struct grind_limit{
- v3f ra, n;
- float p;
- }
- limits[3];
- u32 limit_count;
-}
-extern player_skate;
-extern struct player_subsystem_interface player_subsystem_skate;
-
-enum player_skate_soundeffect {
- k_player_skate_soundeffect_jump,
- k_player_skate_soundeffect_tap,
- k_player_skate_soundeffect_land_good,
- k_player_skate_soundeffect_land_bad,
- k_player_skate_soundeffect_grind_metal,
- k_player_skate_soundeffect_grind_wood,
-};
-
-static float
- k_friction_lat = 12.0f,
- k_friction_resistance = 0.01f,
-
- k_max_push_speed = 16.0f,
- k_push_accel = 10.0f,
- k_push_cycle_rate = 8.0f,
-
- k_steer_ground = 2.5f,
- k_steer_air = 3.6f,
-
- k_jump_charge_speed = (1.0f/0.4f),
- k_jump_force = 5.0f,
-
- k_cog_spring = 0.2f,
- k_cog_damp = 0.02f,
- k_cog_mass_ratio = 0.9f,
-
- k_mmthrow_steer = 1.0f,
- k_mmthrow_scale = 6.0f,
- k_mmcollect_lat = 2.0f,
- k_mmcollect_vert = 0.0f,
- k_mmdecay = 12.0f,
- k_spring_angular = 1.0f,
-
- k_spring_force = 300.0f,
- k_spring_dampener = 5.0f,
-
- k_grind_spring = 50.0f,
- k_grind_aligment = 10.0f,
- k_grind_dampener = 5.0f,
-
- k_surface_spring = 100.0f,
- k_surface_dampener = 40.0f,
- k_manul_spring = 200.0f,
- k_manul_dampener = 30.0f,
- k_board_interia = 8.0f,
-
- k_grind_decayxy = 30.0f,
- k_grind_axel_min_vel = 1.0f,
- k_grind_axel_max_angle = 0.95f, /* cosine(|a|) */
- k_grind_axel_max_vangle = 0.4f,
- k_grind_max_friction = 3.0f,
- k_grind_max_edge_angle = 0.97f,
-
- k_board_length = 0.45f,
- k_board_width = 0.13f,
- k_board_end_radius = 0.1f,
- k_board_radius = 0.14f, /* 0.07 */
-
- k_grind_balance = -40.0f,
- k_anim_transition = 0.12f;
-
-static void player__skate_register(void)
-{
- VG_VAR_F32( k_grind_dampener, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_grind_spring, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_grind_aligment, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_surface_spring, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_surface_dampener, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_board_interia, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_grind_decayxy, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_grind_axel_min_vel, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_grind_axel_max_angle, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_grind_max_friction, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_grind_balance, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_friction_lat, flags=VG_VAR_CHEAT );
-
- VG_VAR_F32( k_cog_spring, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_cog_damp, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_cog_mass_ratio, flags=VG_VAR_CHEAT );
-
- VG_VAR_F32( k_spring_force, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_spring_dampener, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_spring_angular, flags=VG_VAR_CHEAT );
-
- VG_VAR_F32( k_mmthrow_scale, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_mmcollect_lat, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_mmcollect_vert, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_mmdecay, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_mmthrow_steer, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_anim_transition, flags=VG_VAR_CHEAT );
-}
-
-void player__skate_bind (void);
-void player__skate_pre_update (void);
-void player__skate_update (void);
-void player__skate_post_update (void);
-void player__skate_im_gui ( ui_context *ctx );
-void player__skate_animate (void);
-void player__skate_pose (void *animator, player_pose *pose);
-void player__skate_effects( void *_animator, m4x3f *final_mtx,
- struct player_board *board,
- struct player_effects_data *effect_data );
-void player__skate_post_animate (void);
-void player__skate_animator_exchange( bitpack_ctx *ctx, void *data );
-void player__skate_sfx_oneshot ( u8 id, v3f pos, f32 volume );
-
-void player__skate_clear_mechanics(void);
-void player__skate_reset_animator(void);
-void player__approximate_best_trajectory(void);
-void player__skate_comp_audio( void *animator );
-void player__skate_kill_audio(void);
+++ /dev/null
-#include "vg/vg_rigidbody_collision.h"
-
-#include "skaterift.h"
-#include "player_walk.h"
-#include "player_skate.h"
-#include "player_dead.h"
-#include "player.h"
-#include "input.h"
-#include "audio.h"
-#include "scene_rigidbody.h"
-
-struct player_walk player_walk;
-struct player_subsystem_interface player_subsystem_walk =
-{
- .system_register = player__walk_register,
- .bind = player__walk_bind,
- .pre_update = player__walk_pre_update,
- .update = player__walk_update,
- .post_update = player__walk_post_update,
- .im_gui = player__walk_im_gui,
- .animate = player__walk_animate,
- .post_animate = player__walk_post_animate,
- .pose = player__walk_pose,
- .network_animator_exchange = player__walk_animator_exchange,
- .sfx_oneshot = player__walk_sfx_oneshot,
-
- .animator_data = &player_walk.animator,
- .animator_size = sizeof(player_walk.animator),
- .name = "Walk"
-};
-
-
-static void player_walk_drop_in_vector( v3f vec ){
- v3f axis, init_dir;
- v3_cross( (v3f){0.0f,1.0f,0.0f}, player_walk.state.drop_in_normal, axis );
- v3_cross( axis, player_walk.state.drop_in_normal, init_dir );
- v3_normalize( init_dir );
- v3_muls( init_dir, 4.25f, vec );
-}
-
-static float player_xyspeed2(void){
- return v3_length2( (v3f){localplayer.rb.v[0], 0.0f, localplayer.rb.v[2]} );
-}
-
-static void player_walk_generic_to_skate( enum skate_activity init, f32 yaw ){
- localplayer.subsystem = k_player_subsystem_skate;
-
- v3f v;
-
- if( player_xyspeed2() < 0.1f * 0.1f )
- q_mulv( localplayer.rb.q, (v3f){0.0f,0.0f,1.6f}, v );
- else
- v3_copy( localplayer.rb.v, v );
-
- player_skate.state.activity_prev = k_skate_activity_ground;
- player_skate.state.activity = init;
-
- v3f dir;
- v3_copy( v, dir );
- v3_normalize( dir );
-
- q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
- atan2f(-dir[0],-dir[2]) );
- q_normalize( localplayer.rb.q );
-
- q_mulv( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f}, player_skate.state.cog );
- v3_add( player_skate.state.cog, localplayer.rb.co, player_skate.state.cog );
-
- v3_copy( v, player_skate.state.cog_v );
- v3_copy( v, localplayer.rb.v );
-
- player__begin_holdout( (v3f){0.0f,0.0f,0.0f} );
- player__skate_reset_animator();
- player__skate_clear_mechanics();
- rb_update_matrices( &localplayer.rb );
- v3_copy( (v3f){yaw,0.0f,0.0f}, player_skate.state.trick_euler );
-
- if( init == k_skate_activity_air )
- player__approximate_best_trajectory();
-}
-
-static void player_walk_drop_in_to_skate(void){
- localplayer.immobile = 0;
- localplayer.subsystem = k_player_subsystem_skate;
-
- player_skate.state.activity_prev = k_skate_activity_ground;
- player_skate.state.activity = k_skate_activity_ground;
-
- player__begin_holdout( (v3f){0,0,0} );
- player__skate_clear_mechanics();
- player__skate_reset_animator();
-
- v3f init_velocity;
- player_walk_drop_in_vector( init_velocity );
-
- rb_update_matrices( &localplayer.rb );
- v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], 1.0f,
- player_skate.state.cog );
- v3_copy( init_velocity, player_skate.state.cog_v );
- v3_copy( init_velocity, localplayer.rb.v );
- v3_copy( init_velocity, localplayer.cam_control.cam_velocity_smooth );
- v3_copy( (v3f){player_walk.animator.board_yaw+1.0f,0,0},
- player_skate.state.trick_euler );
-}
-
-static void player_walk_drop_in_overhang_transform( f32 t, v3f co, v4f q ){
- v3f axis;
- v3_cross( (v3f){0,1,0}, player_walk.state.drop_in_normal, axis );
- v3_normalize( axis );
-
- float a = acosf( player_walk.state.drop_in_normal[1] ) * t;
- q_axis_angle( q, axis, a );
-
- float l = t * 0.5f,
- heading_angle = player_walk.state.drop_in_angle;
-
- v3f overhang;
- overhang[0] = sinf( heading_angle ) * l;
- overhang[1] = 0.28f * l;
- overhang[2] = cosf( heading_angle ) * l;
-
- q_mulv( q, overhang, overhang );
- v3_add( player_walk.state.drop_in_target, overhang, co );
-}
-
-static int player_walk_scan_for_drop_in(void){
- world_instance *world = world_current_instance();
-
- v3f dir, center;
- q_mulv( localplayer.rb.q, (v3f){0.0f,0.0f,1.0f}, dir );
- v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], -1.0f, center );
-
- ray_hit samples[20];
- int sample_count = 0;
-
- for( int i=0; i<20; i ++ ){
- float t = (float)i * (1.0f/19.0f),
- s = sinf( t * VG_PIf * 0.25f ),
- c = cosf( t * VG_PIf * 0.25f );
-
- v3f ray_dir, pos;
- v3_muls ( localplayer.rb.to_world[1], -c, ray_dir );
- v3_muladds( ray_dir, dir, -s, ray_dir );
- v3_muladds( center, ray_dir, -2.0f, pos );
-
- ray_hit *ray = &samples[ sample_count ];
- ray->dist = 2.0f;
-
- if( ray_world( world, pos, ray_dir, ray, 0 ) ){
- vg_line( pos, ray->pos, VG__RED );
- vg_line_point( ray->pos, 0.025f, VG__BLACK );
-
- sample_count ++;
- }
- }
-
- float min_a = 0.70710678118654752f;
- ray_hit *candidate = NULL;
-
- if( sample_count >= 2 ){
- for( int i=0; i<sample_count-1; i++ ){
- ray_hit *s0 = &samples[i],
- *s1 = &samples[i+1];
-
- float a = v3_dot( s0->normal, s1->normal );
-
- if( (a < min_a) && (a >= -0.1f) && (s0->normal[1]>s1->normal[1]) ){
- min_a = a;
- candidate = s0;
- }
- }
- }
-
- if( candidate ){
- v4f pa, pb, pc;
-
- ray_hit *s0 = candidate,
- *s1 = candidate+1;
-
- vg_line( s0->pos, s1->pos, VG__WHITE );
-
- v3_copy( s0->normal, pa );
- v3_copy( s1->normal, pb );
- v3_cross( localplayer.rb.to_world[1], dir, pc );
- v3_normalize( pc );
-
- pa[3] = v3_dot( pa, s0->pos );
- pb[3] = v3_dot( pb, s1->pos );
- pc[3] = v3_dot( pc, localplayer.rb.co );
-
- v3f edge;
- if( plane_intersect3( pa, pb, pc, edge ) ){
- v3_copy( edge, player_walk.state.drop_in_target );
- v3_copy( s1->normal, player_walk.state.drop_in_normal );
- v3_copy( localplayer.rb.co, player_walk.state.drop_in_start );
-
- player_walk.state.drop_in_start_angle = player_get_heading_yaw();
- player_walk.state.drop_in_angle =
- atan2f( player_walk.state.drop_in_normal[0],
- player_walk.state.drop_in_normal[2] );
-
- /* TODO: scan multiple of these? */
- v3f oco;
- v4f oq;
- player_walk_drop_in_overhang_transform( 1.0f, oco, oq );
-
- v3f va = {0.0f,0.0f,-k_board_length - 0.3f},
- vb = {0.0f,0.0f, k_board_length + 0.3f};
-
- q_mulv( oq, va, va );
- q_mulv( oq, vb, vb );
- v3_add( oco, va, va );
- v3_add( oco, vb, vb );
-
- v3f v0;
- v3_sub( vb, va, v0 );
- v3_normalize( v0 );
-
- ray_hit ray;
- ray.dist = k_board_length*2.0f + 0.6f;
-
- if( ray_world( world, va, v0, &ray, 0 ) ){
- vg_line( va, vb, VG__RED );
- vg_line_point( ray.pos, 0.1f, VG__RED );
- vg_error( "invalidated\n" );
- return 0;
- }
-
- v3_muls( v0, -1.0f, v0 );
- if( ray_world( world, vb, v0, &ray, 0 ) ){
- vg_line( va, vb, VG__RED );
- vg_line_point( ray.pos, 0.1f, VG__RED );
- vg_error( "invalidated\n" );
- return 0;
- }
-
- player_walk_drop_in_vector( localplayer.rb.v );
- return 1;
- }
- else{
- vg_error( "failed to find intersection of drop in\n" );
- }
- }
-
- return 0;
-}
-
-static bool player__preupdate_anim( struct skeleton_anim *anim, f32 *t,
- f32 speed ){
- f32 length = (f32)(anim->length-1) / anim->rate;
- *t += (vg.time_delta * speed) / length;
-
- if( *t >= 1.0f ) return 1;
- else return 0;
-}
-
-static void player_walk_pre_sit(void){
- struct player_walk *w = &player_walk;
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
-
- vg_slewf( &w->state.transition_t, 1.0f, vg.time_delta );
-
- if( button_down(k_srbind_sit) || (v2_length2(steer)>0.2f) ||
- button_down(k_srbind_jump) ){
- w->state.activity = k_walk_activity_sit_up;
- }
- return;
-}
-
-static void player_walk_pre_sit_up(void){
- struct player_walk *w = &player_walk;
-
- if( w->state.transition_t > 0.0f )
- vg_slewf( &w->state.transition_t, 0.0f, vg.time_delta );
- else
- w->state.activity = k_walk_activity_ground;
-
- if( button_down(k_srbind_sit) )
- w->state.activity = k_walk_activity_sit;
-
- return;
-}
-
-static void player_walk_pre_ground(void){
- struct player_walk *w = &player_walk;
-
- if( button_down(k_srbind_sit) ){
- v3_zero( localplayer.rb.v );
- w->state.activity = k_walk_activity_sit;
- w->state.transition_t = 0.0f;
- return;
- }
-
- if( button_down( k_srbind_use ) ){
- if( player_walk_scan_for_drop_in() ){
- w->state.activity = k_walk_activity_odrop_in;
- }
- else{
- w->state.activity = k_walk_activity_oregular;
- }
-
- w->state.transition_t = 0.0f;
- }
-
- if( button_down( k_srbind_jump ) ){
- w->state.jump_queued = 1;
- w->state.jump_input_time = vg.time;
- }
-}
-
-static void player_walk_pre_air(void){
- struct player_walk *w = &player_walk;
- if( button_down( k_srbind_use ) ){
- w->state.activity = k_walk_activity_oair;
- w->state.transition_t = 0.0f;
- }
-
- if( button_down( k_srbind_jump ) ){
- w->state.jump_queued = 1;
- w->state.jump_input_time = vg.time;
- }
-}
-
-static void player_walk_pre_drop_in(void){
- struct player_walk *w = &player_walk;
- bool finished = player__preupdate_anim( w->anim_drop_in,
- &w->state.transition_t, 1.0f );
- if( finished )
- player_walk_drop_in_to_skate();
-}
-
-static void player_walk_pre_caveman(void){
- struct player_walk *w = &player_walk;
- bool finished = player__preupdate_anim( w->anim_jump_to_air,
- &w->state.transition_t, 1.0f );
- if( finished ){
- player_walk_generic_to_skate( k_skate_activity_air,
- player_walk.animator.board_yaw );
- }
-}
-
-static void player_walk_pre_running_start(void){
- struct player_walk *w = &player_walk;
- bool finished = player__preupdate_anim( w->anim_intro,
- &w->state.transition_t, 1.0f );
- if( finished ){
- /* TODO: get the derivative of the last keyframes to calculate new
- * velocity for player */
- player_walk_generic_to_skate( k_skate_activity_ground,
- player_walk.animator.board_yaw+1.0f );
- }
-}
-
-static void player_walk_pre_popoff(void){
- struct player_walk *w = &player_walk;
- bool finished = player__preupdate_anim( w->anim_popoff,
- &w->state.transition_t, 1.0f );
-
- if( finished ){
- w->state.activity = k_walk_activity_ground;
- w->animator.board_yaw += 1.0f;
- }
-}
-
-void player__walk_pre_update(void){
- struct player_walk *w = &player_walk;
-
- if( localplayer.immobile ) return;
- else player_look( localplayer.angles, skaterift.time_rate );
-
- enum walk_activity a = w->state.activity;
-
- if ( a == k_walk_activity_sit ) player_walk_pre_sit();
- else if( a == k_walk_activity_sit_up ) player_walk_pre_sit_up();
- else if( a == k_walk_activity_ground ) player_walk_pre_ground();
- else if( a == k_walk_activity_air ) player_walk_pre_air();
- else if( a == k_walk_activity_odrop_in ) player_walk_pre_drop_in();
- else if( a == k_walk_activity_oair ) player_walk_pre_caveman();
- else if( a == k_walk_activity_oregular ) player_walk_pre_running_start();
- else if( a == k_walk_activity_ipopoff ) player_walk_pre_popoff();
-}
-
-static int player_walk_normal_standable( v3f n ){
- return n[1] > 0.70710678118f;
-}
-
-static void player_accelerate( v3f v, v3f movedir, f32 speed, f32 accel ){
- float currentspeed = v3_dot( v, movedir ),
- addspeed = speed - currentspeed;
-
- if( addspeed <= 0 )
- return;
-
- float accelspeed = accel * vg.time_fixed_delta * speed;
-
- if( accelspeed > addspeed )
- accelspeed = addspeed;
-
- v3_muladds( v, movedir, accelspeed, v );
-}
-
-static void player_friction( v3f v, f32 friction ){
- float speed = v3_length( v ),
- drop = 0.0f,
- control = vg_maxf( speed, k_stopspeed );
-
- if( speed < 0.04f )
- return;
-
- drop += control * friction * vg.time_fixed_delta;
-
- float newspeed = vg_maxf( 0.0f, speed - drop );
- newspeed /= speed;
-
- v3_muls( v, newspeed, v );
-}
-
-static void player_walk_custom_filter( world_instance *world,
- rb_ct *man, int len, f32 w ){
- for( int i=0; i<len; i++ ){
- rb_ct *ci = &man[i];
- if( ci->type == k_contact_type_disabled ||
- ci->type == k_contact_type_edge )
- continue;
-
-
- float d1 = v3_dot( ci->co, ci->n );
-
- for( int j=0; j<len; j++ ){
- if( j == i )
- continue;
-
- rb_ct *cj = &man[j];
- if( cj->type == k_contact_type_disabled )
- continue;
-
- struct world_surface *si = world_contact_surface( world, ci ),
- *sj = world_contact_surface( world, cj );
-
- if( (sj->info.flags & k_material_flag_walking) &&
- !(si->info.flags & k_material_flag_walking)){
- continue;
- }
-
- float d2 = v3_dot( cj->co, ci->n ),
- d = d2-d1;
-
- if( fabsf( d ) <= w ){
- cj->type = k_contact_type_disabled;
- }
- }
- }
-}
-
-static void player_walk_update_generic(void){
- struct player_walk *w = &player_walk;
-
- if( (w->state.activity != k_walk_activity_oregular) &&
- (w->state.activity != k_walk_activity_oair) ){
- joystick_state( k_srjoystick_steer, w->state.steer );
- w->state.steer[2] = button_press(k_srbind_run)? k_runspeed: k_walkspeed;
- if( v2_length2(w->state.steer)>1.0f )
- v2_normalize(w->state.steer);
- }
-
- v3_copy( localplayer.rb.co, w->state.prev_pos );
- v3_zero( localplayer.rb.w );
-
- world_instance *world = world_current_instance();
- if( !world_water_player_safe( world, 0.4f ) ) return;
-
- enum walk_activity prev_state = w->state.activity;
-
- w->collider.h = 2.0f;
- w->collider.r = 0.3f;
-
- m4x3f mtx;
- m3x3_copy( localplayer.rb.to_world, mtx );
- v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
-
- vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__WHITE );
-
- rb_ct manifold[64];
- int len;
-
- float yaw = localplayer.angles[0];
-
- v3f forward_dir = { -sinf(yaw), 0.0f, cosf(yaw) };
- v3f right_dir = { forward_dir[2], 0.0f, -forward_dir[0] };
-
- /*
- * Collision detection
- */
-
- len = rb_capsule__scene( mtx, &w->collider, NULL,
- world->geo_bh, manifold, 0 );
- player_walk_custom_filter( world, manifold, len, 0.01f );
- len = rb_manifold_apply_filtered( manifold, len );
-
- v3f surface_avg = { 0.0f, 0.0f, 0.0f };
-
- w->state.activity = k_walk_activity_air;
- w->surface = k_surface_prop_concrete;
-
- for( int i=0; i<len; i++ ){
- rb_ct *ct = &manifold[i];
- rb_debug_contact( ct );
-
- if( player_walk_normal_standable( ct->n ) ){
- w->state.activity = k_walk_activity_ground;
-
- v3_add( surface_avg, ct->n, surface_avg );
-
- struct world_surface *surf = world_contact_surface( world, ct );
- if( surf->info.surface_prop > w->surface )
- w->surface = surf->info.surface_prop;
- }
-
- rb_prepare_contact( ct, vg.time_fixed_delta );
- }
-
- /*
- * Move & Friction
- */
- float accel_speed = 0.0f, nominal_speed = 0.0f;
- v3f movedir;
-
- v3_muls( right_dir, w->state.steer[0], movedir );
- v3_muladds( movedir, forward_dir, w->state.steer[1], movedir );
-
- if( w->state.activity == k_walk_activity_ground ){
- v3_normalize( surface_avg );
-
- v3f tx, ty;
- v3_tangent_basis( surface_avg, tx, ty );
-
- if( v2_length2(w->state.steer) > 0.001f ){
- /* clip movement to the surface */
- float d = v3_dot(surface_avg,movedir);
- v3_muladds( movedir, surface_avg, -d, movedir );
- }
-
- accel_speed = k_walk_accel;
- nominal_speed = w->state.steer[2];
-
- /* jump */
- if( w->state.jump_queued ){
- w->state.jump_queued = 0;
-
- f32 t = vg.time - w->state.jump_input_time;
- if( t < PLAYER_JUMP_EPSILON ){
- localplayer.rb.v[1] = 5.0f;
- w->state.activity = k_walk_activity_air;
- prev_state = k_walk_activity_air;
- accel_speed = k_walk_air_accel;
- nominal_speed = k_airspeed;
- }
- }
- else{
- player_friction( localplayer.rb.v, k_walk_friction );
- }
- }
- else{
- accel_speed = k_walk_air_accel;
- nominal_speed = k_airspeed;
- }
-
- if( v2_length2( w->state.steer ) > 0.001f ){
- player_accelerate( localplayer.rb.v, movedir,
- nominal_speed, accel_speed );
- v3_normalize( movedir );
- }
-
- /*
- * Resolve velocity constraints
- */
- for( int j=0; j<5; j++ ){
- for( int i=0; i<len; i++ ){
- rb_ct *ct = &manifold[i];
-
- /*normal */
- float vn = -v3_dot( localplayer.rb.v, ct->n );
-
- float temp = ct->norm_impulse;
- ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
- vn = ct->norm_impulse - temp;
-
- v3_muladds( localplayer.rb.v, ct->n, vn, localplayer.rb.v );
- }
- }
-
- /* stepping */
- if( w->state.activity == k_walk_activity_ground||
- prev_state == k_walk_activity_ground ){
- float max_dist = 0.4f;
-
- v3f pa, pb;
- v3_copy( localplayer.rb.co, pa );
- pa[1] += w->collider.r + max_dist;
- v3_add( pa, (v3f){0, -max_dist * 2.0f, 0}, pb );
- vg_line( pa, pb, 0xff000000 );
-
- v3f n;
- float t;
- if( spherecast_world( world, pa, pb,
- w->collider.r, &t, n, 0 ) != -1 ){
- if( player_walk_normal_standable(n) ){
- v3_lerp( pa, pb, t, localplayer.rb.co );
- localplayer.rb.co[1] += -w->collider.r - k_penetration_slop;
- w->state.activity = k_walk_activity_ground;
-
- float d = -v3_dot(n,localplayer.rb.v);
- v3_muladds( localplayer.rb.v, n, d, localplayer.rb.v );
- localplayer.rb.v[1] += -k_gravity * vg.time_fixed_delta;
- }
- }
- }
-
- /*
- * Depenetrate
- */
- v3f dt;
- rb_depenetrate( manifold, len, dt );
- v3_add( dt, localplayer.rb.co, localplayer.rb.co );
-
- /* integrate */
- if( w->state.activity == k_walk_activity_air ){
- localplayer.rb.v[1] += -k_gravity*vg.time_fixed_delta;
- }
-
- if( localplayer.immobile ){
- localplayer.rb.v[0] = 0.0f;
- localplayer.rb.v[2] = 0.0f;
- }
-
- v3_muladds( localplayer.rb.co, localplayer.rb.v, vg.time_fixed_delta,
- localplayer.rb.co );
- v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
- vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__GREEN );
-
- /*
- * CCD routine
- * ---------------------------------------------------
- *
- */
- v3f lwr_prev,
- lwr_now,
- lwr_offs = { 0.0f, w->collider.r, 0.0f };
-
- v3_add( lwr_offs, w->state.prev_pos, lwr_prev );
- v3_add( lwr_offs, localplayer.rb.co, lwr_now );
-
- v3f movedelta;
- v3_sub( localplayer.rb.co, w->state.prev_pos, movedelta );
-
- float movedist = v3_length( movedelta );
-
- if( movedist > 0.3f ){
- float t, sr = w->collider.r-0.04f;
- v3f n;
-
- if( spherecast_world( world, lwr_prev, lwr_now, sr, &t, n, 0 ) != -1 ){
- v3_lerp( lwr_prev, lwr_now, vg_maxf(0.01f,t), localplayer.rb.co );
- localplayer.rb.co[1] -= w->collider.r;
- rb_update_matrices( &localplayer.rb );
- v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
- vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__RED);
- }
- }
-
- u32 id = world_intersect_gates(world, localplayer.rb.co, w->state.prev_pos);
- if( id ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, mdl_entity_id_id(id) );
- m4x3_mulv( gate->transport, localplayer.rb.co, localplayer.rb.co );
- m3x3_mulv( gate->transport, localplayer.rb.v, localplayer.rb.v );
-
- v4f transport_rotation;
- m3x3_q( gate->transport, transport_rotation );
- q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
- q_normalize( localplayer.rb.q );
- rb_update_matrices( &localplayer.rb );
- player__pass_gate( id );
- }
- rb_update_matrices( &localplayer.rb );
-
- if( (prev_state == k_walk_activity_oregular) ||
- (prev_state == k_walk_activity_oair) ||
- (prev_state == k_walk_activity_ipopoff) ){
- w->state.activity = prev_state;
- }
-
- w->move_speed = vg_minf( v2_length( (v2f){ localplayer.rb.v[0],
- localplayer.rb.v[2] } ),
- k_runspeed );
-}
-
-void player__walk_post_update(void){
- struct player_walk *w = &player_walk;
-
- m4x3f mtx;
- m3x3_copy( localplayer.rb.to_world, mtx );
- v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
-
- float substep = vg.time_fixed_extrapolate;
- v3_muladds( mtx[3], localplayer.rb.v, vg.time_fixed_delta*substep, mtx[3] );
- vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__YELOW );
-
- /* Calculate header */
- v3f v;
- if( (player_xyspeed2() > 0.1f*0.1f) ){
- f32 r = 0.3f;
- if( (w->state.activity == k_walk_activity_ground) ||
- (w->state.activity == k_walk_activity_ipopoff) ||
- (w->state.activity == k_walk_activity_oregular) ){
- r = 0.07f;
- }
-
- f32 ta = atan2f( localplayer.rb.v[0], localplayer.rb.v[2] );
- v4f qt;
- q_axis_angle( qt, (v3f){0,1,0}, ta );
- q_nlerp( localplayer.rb.q, qt, vg.time_delta/r, localplayer.rb.q );
- }
-
- vg_line_point( w->state.drop_in_target, 0.1f, VG__GREEN );
- v3f p1;
- v3_muladds( w->state.drop_in_target, w->state.drop_in_normal, 0.3f, p1 );
- vg_line( w->state.drop_in_target, p1, VG__GREEN );
- v3_muladds( w->state.drop_in_target, localplayer.rb.to_world[1], 0.3f, p1 );
- vg_line( w->state.drop_in_target, p1, VG__GREEN );
-
- float a = player_get_heading_yaw();
- p1[0] = sinf( a );
- p1[1] = 0.0f;
- p1[2] = cosf( a );
-
- v3_add( localplayer.rb.co, p1, p1 );
- vg_line( localplayer.rb.co, p1, VG__PINK );
-
- int walk_phase = 0;
- if( vg_fractf(w->state.walk_timer) > 0.5f )
- walk_phase = 1;
- else
- walk_phase = 0;
-
- if( (w->state.step_phase != walk_phase) &&
- (w->state.activity == k_walk_activity_ground ) )
- {
- audio_lock();
- if( w->surface == k_surface_prop_concrete ){
- audio_oneshot_3d(
- &audio_footsteps[vg_randu32(&vg.rand) % 4],
- localplayer.rb.co, 40.0f, 1.0f
- );
- }
- else if( w->surface == k_surface_prop_grass ){
- audio_oneshot_3d(
- &audio_footsteps_grass[ vg_randu32(&vg.rand) % 6 ],
- localplayer.rb.co, 40.0f, 1.0f
- );
- }
- else if( w->surface == k_surface_prop_wood ){
- audio_oneshot_3d(
- &audio_footsteps_wood[ vg_randu32(&vg.rand) % 6 ],
- localplayer.rb.co, 40.0f, 1.0f
- );
- }
- audio_unlock();
- }
-
- w->state.step_phase = walk_phase;
-}
-
-void player__walk_update(void){
- struct player_walk *w = &player_walk;
-
- if( (w->state.activity == k_walk_activity_air) ||
- (w->state.activity == k_walk_activity_ground) ||
- (w->state.activity == k_walk_activity_oair) ||
- (w->state.activity == k_walk_activity_oregular) ||
- (w->state.activity == k_walk_activity_ipopoff) ){
- player_walk_update_generic();
- }
-}
-
-static void player_walk_animate_drop_in(void){
- struct player_walk *w = &player_walk;
- struct player_walk_animator *animator = &w->animator;
- struct skeleton_anim *anim = w->anim_drop_in;
-
- f32 length = (f32)(anim->length-1) / anim->rate,
- time = w->state.transition_t;
-
- f32 walk_yaw = vg_alerpf( w->state.drop_in_start_angle,
- w->state.drop_in_angle, animator->transition_t );
- v3_lerp( w->state.drop_in_start, w->state.drop_in_target,
- animator->transition_t, localplayer.rb.co );
-
- q_axis_angle( localplayer.rb.q, (v3f){0,1,0}, walk_yaw + VG_PIf );
-
- /* the drop in bit */
- v3f final_co;
- v4f final_q;
- player_walk_drop_in_overhang_transform( animator->transition_t,
- final_co, final_q );
-
- q_mul( final_q, localplayer.rb.q, localplayer.rb.q );
- v3_lerp( localplayer.rb.co, final_co, animator->transition_t,
- localplayer.rb.co );
-
- rb_update_matrices( &localplayer.rb );
-
- v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1],
- -0.1f*animator->transition_t, localplayer.rb.co );
-
- v3_copy( localplayer.rb.co, animator->root_co );
- v4_copy( localplayer.rb.q, animator->root_q );
-
- /* for the camera purposes only */
- v3f init_velocity;
- player_walk_drop_in_vector( init_velocity );
- v3_muls( init_velocity, animator->transition_t, localplayer.rb.v );
- v3_copy( localplayer.rb.v,
- localplayer.cam_control.cam_velocity_smooth );
-}
-
-static void player_walk_animate_generic(void){
- struct player_walk *w = &player_walk;
- struct player_walk_animator *animator = &w->animator;
-
- v4f _null;
- rb_extrapolate( &localplayer.rb, animator->root_co, _null );
-
- f32 walk_yaw = player_get_heading_yaw(),
- head_yaw = localplayer.angles[0] + VG_PIf,
- y = vg_angle_diff( head_yaw, -walk_yaw ),
- p = vg_clampf( localplayer.angles[1],
- -k_sit_pitch_limit, k_sit_pitch_limit );
-
- if( fabsf(y) > k_sit_yaw_limit ){
- y = 0.0f;
- p = 0.0f;
- }
-
- animator->yaw = vg_lerpf( animator->yaw, y, vg.time_delta*2.0f );
- animator->pitch = vg_lerpf( animator->pitch, p, vg.time_delta*2.8f );
- q_axis_angle( animator->root_q, (v3f){0,1,0}, walk_yaw + VG_PIf );
-
- v4f qrev;
- q_axis_angle( qrev, (v3f){0,1,0}, VG_TAUf*0.5f );
- q_mul( localplayer.rb.q, qrev, animator->root_q );
-}
-
-void player__walk_animate(void){
- struct player_walk *w = &player_walk;
- player_pose *pose = &localplayer.pose;
- struct player_walk_animator *animator = &w->animator;
-
- animator->activity = w->state.activity;
- animator->transition_t = w->state.transition_t;
-
- {
- f32 fly = (w->state.activity == k_walk_activity_air)? 1.0f: 0.0f,
- rate;
-
- if( w->state.activity == k_walk_activity_air ) rate = 2.4f;
- else rate = 9.0f;
-
- animator->fly = vg_lerpf( animator->fly, fly, rate*vg.time_delta );
- animator->run = vg_lerpf( animator->run, w->move_speed,
- 8.0f*vg.time_delta);
- }
-
- if( animator->run > 0.025f ){
- f32 walk_norm = 30.0f/(float)w->anim_walk->length,
- run_norm = 30.0f/(float)w->anim_run->length,
- l;
-
- if( animator->run <= k_walkspeed )
- l = (animator->run / k_walkspeed) * walk_norm;
- else {
- l = vg_lerpf( walk_norm, run_norm,
- (animator->run-k_walkspeed) / (k_runspeed-k_walkspeed) );
- }
- w->state.walk_timer += l * vg.time_delta;
- }
- else
- w->state.walk_timer = 0.0f;
-
- animator->walk_timer = w->state.walk_timer;
-
- player_walk_animate_generic();
- if( w->state.activity == k_walk_activity_odrop_in ){
- player_walk_animate_drop_in();
- }
-
- if( (w->state.activity == k_walk_activity_odrop_in) ||
- (w->state.activity == k_walk_activity_oregular) ||
- (w->state.activity == k_walk_activity_oair) ){
- localplayer.cam_velocity_influence = w->animator.transition_t;
- }
- else if( w->state.activity == k_walk_activity_ipopoff ){
- localplayer.cam_velocity_influence = 1.0f-w->animator.transition_t;
- }
- else
- localplayer.cam_velocity_influence = 0.0f;
-
- if( w->state.activity == k_walk_activity_sit ){
- localplayer.cam_dist = 3.8f;
- }
- else {
- localplayer.cam_dist = 1.8f;
- }
-}
-
-static void player_walk_pose_sit( struct player_walk_animator *animator,
- player_pose *pose )
-{
- mdl_keyframe bpose[32];
-
- struct player_walk *w = &player_walk;
- struct skeleton *sk = &localplayer.skeleton;
-
- f32 t = animator->transition_t,
- st = t * ((f32)(w->anim_sit->length-1)/30.0f);
- skeleton_sample_anim( sk, w->anim_sit, st, bpose );
-
- v4f qy,qp;
- f32 *qh = bpose[localplayer.id_head-1].q;
- q_axis_angle( qy, (v3f){0,1,0}, animator->yaw*0.5f*t );
- q_axis_angle( qp, (v3f){0,0,1}, animator->pitch*t );
- q_mul( qy, qh, qh );
- q_mul( qh, qp, qh );
- q_normalize( qh );
-
- qh = bpose[localplayer.id_chest-1].q;
- q_axis_angle( qy, (v3f){0,1,0}, animator->yaw*0.5f*t );
- q_mul( qy, qh, qh );
- q_normalize( qh );
-
- skeleton_lerp_pose( sk, pose->keyframes, bpose,
- vg_minf(1.0f,t*10.0f), pose->keyframes );
-}
-
-enum walk_transition_type {
- k_walk_transition_in,
- k_walk_transition_out,
- k_walk_transition_outin,
-};
-
-static void player_walk_pose_transition(
- struct player_walk_animator *animator, struct skeleton_anim *anim,
- enum walk_transition_type type,
- mdl_keyframe apose[32], f32 *mask, player_pose *pose ){
-
- mdl_keyframe bpose[32];
-
- struct player_walk *w = &player_walk;
- struct skeleton *sk = &localplayer.skeleton;
-
- f32 length = (f32)(anim->length-1) / anim->rate,
- t = animator->transition_t * length,
- blend = 1.0f;
-
- if( type == k_walk_transition_in || type == k_walk_transition_outin )
- blend = vg_minf( blend, length-t );
-
- if( type == k_walk_transition_out || type == k_walk_transition_outin )
- blend = vg_minf( blend, t );
-
- blend = vg_smoothstepf( vg_minf(1,blend/k_anim_transition) );
-
- skeleton_sample_anim_clamped( sk, anim, t, bpose );
-
- mdl_keyframe *kf_board = &bpose[localplayer.id_board-1];
- f32 yaw = animator->board_yaw * VG_TAUf * 0.5f;
-
- v4f qyaw;
- q_axis_angle( qyaw, (v3f){0,1,0}, yaw );
- q_mul( kf_board->q, qyaw, kf_board->q );
- q_normalize( kf_board->q );
-
- if( mask ){
- for( i32 i=0; i<sk->bone_count-1; i ++ )
- keyframe_lerp( apose+i, bpose+i, blend*mask[i], pose->keyframes+i );
- }
- else
- skeleton_lerp_pose( sk, apose, bpose, blend, pose->keyframes );
-}
-
-void player__walk_pose( void *_animator, player_pose *pose ){
- struct player_walk *w = &player_walk;
- struct player_walk_animator *animator = _animator;
- struct skeleton *sk = &localplayer.skeleton;
-
- v3_copy( animator->root_co, pose->root_co );
- v4_copy( animator->root_q, pose->root_q );
- pose->board.lean = 0.0f;
- pose->type = k_player_pose_type_ik;
-
- float walk_norm = (float)w->anim_walk->length/30.0f,
- run_norm = (float)w->anim_run->length/30.0f,
- t = animator->walk_timer;
-
- /* walk/run */
- mdl_keyframe apose[32], bpose[32];
- if( animator->run <= k_walkspeed ){
- /* walk / idle */
- f32 l = vg_minf( 1, (animator->run/k_walkspeed)*6.0f );
- skeleton_sample_anim( sk, w->anim_idle, vg.time*0.1f, apose );
- skeleton_sample_anim( sk, w->anim_walk, t*walk_norm, bpose );
- skeleton_lerp_pose( sk, apose, bpose, l, apose );
- }
- else {
- /* walk / run */
- f32 l = (animator->run-k_walkspeed) / (k_runspeed-k_walkspeed);
- skeleton_sample_anim( sk, w->anim_walk, t*walk_norm, apose );
- skeleton_sample_anim( sk, w->anim_run, t*run_norm, bpose );
- skeleton_lerp_pose( sk, apose, bpose, l, apose );
- }
-
- /* air */
- skeleton_sample_anim( sk, w->anim_jump, vg.time*0.6f, bpose );
- skeleton_lerp_pose( sk, apose, bpose, animator->fly, apose );
-
- mdl_keyframe *kf_board = &apose[localplayer.id_board-1];
- f32 yaw = animator->board_yaw;
-
- if( animator->activity == k_walk_activity_ipopoff )
- if( animator->transition_t > 0.5f )
- yaw += 1.0f;
-
- v4f qyaw;
- q_axis_angle( qyaw, (v3f){0,1,0}, yaw * VG_TAUf * 0.5f );
- q_mul( kf_board->q, qyaw, kf_board->q );
- q_normalize( kf_board->q );
-
- /* sit */
- if( (animator->activity == k_walk_activity_sit) ||
- (animator->activity == k_walk_activity_sit_up) )
- {
- skeleton_copy_pose( sk, apose, pose->keyframes );
- player_walk_pose_sit( animator, pose );
- }
- else if( animator->activity == k_walk_activity_odrop_in ){
- player_walk_pose_transition(
- animator, w->anim_drop_in, k_walk_transition_out, apose,
- NULL, pose );
- }
- else if( animator->activity == k_walk_activity_oair ){
- player_walk_pose_transition(
- animator, w->anim_jump_to_air, k_walk_transition_out, apose,
- NULL, pose );
- }
- else if( animator->activity == k_walk_activity_oregular ){
- player_walk_pose_transition(
- animator, w->anim_intro, k_walk_transition_out, apose,
- NULL, pose );
- }
- else if( animator->activity == k_walk_activity_ipopoff ){
- if( animator->run > 0.2f ){
- f32 t = 1.0f-vg_minf( animator->run-0.2f, 1.0f ),
- mask[ 32 ];
-
- for( u32 i=0; i<32; i ++ )
- mask[i] = 1.0f;
-
- mask[ localplayer.id_ik_foot_l-1 ] = t;
- mask[ localplayer.id_ik_foot_r-1 ] = t;
- mask[ localplayer.id_ik_knee_l-1 ] = t;
- mask[ localplayer.id_ik_knee_r-1 ] = t;
- mask[ localplayer.id_hip-1 ] = t;
- player_walk_pose_transition(
- animator, w->anim_popoff, k_walk_transition_in, apose,
- mask, pose );
- }
- else{
- player_walk_pose_transition(
- animator, w->anim_popoff, k_walk_transition_in, apose,
- NULL, pose );
- }
- }
- else {
- skeleton_copy_pose( sk, apose, pose->keyframes );
- }
-}
-
-void player__walk_post_animate(void){
- /*
- * Camera
- */
- struct player_walk *w = &player_walk;
-
-}
-
-void player__walk_im_gui( ui_context *ctx )
-{
- struct player_walk *w = &player_walk;
- player__debugtext( ctx, 1, "V: %5.2f %5.2f %5.2f (%5.2fm/s)",
- localplayer.rb.v[0], localplayer.rb.v[1], localplayer.rb.v[2],
- v3_length(localplayer.rb.v) );
- player__debugtext( ctx,
- 1, "CO: %5.2f %5.2f %5.2f",localplayer.rb.co[0],
- localplayer.rb.co[1],
- localplayer.rb.co[2] );
- player__debugtext( ctx, 1, "transition: %5.2f ", w->state.transition_t );
- player__debugtext( ctx, 1, "activity: %s\n",
- (const char *[]){ "air",
- "ground",
- "sit",
- "sit_up",
- "inone",
- "ipopoff",
- "oair",
- "odrop_in",
- "oregular" }
- [w->state.activity] );
- player__debugtext( ctx, 1, "surface: %s\n",
- (const char *[]){ "concrete",
- "wood",
- "grass",
- "tiles",
- "metal",
- "snow",
- "sand" }
- [w->surface] );
-}
-
-void player__walk_bind(void){
- struct player_walk *w = &player_walk;
- struct skeleton *sk = &localplayer.skeleton;
-
- w->anim_idle = skeleton_get_anim( sk, "idle_cycle+y" );
- w->anim_walk = skeleton_get_anim( sk, "walk+y" );
- w->anim_run = skeleton_get_anim( sk, "run+y" );
- w->anim_jump = skeleton_get_anim( sk, "jump+y" );
- w->anim_jump_to_air = skeleton_get_anim( sk, "jump_to_air" );
- w->anim_drop_in = skeleton_get_anim( sk, "drop_in" );
- w->anim_intro = skeleton_get_anim( sk, "into_skate" );
- w->anim_sit = skeleton_get_anim( sk, "sit" );
- w->anim_popoff = skeleton_get_anim( sk, "pop_off_short" );
-}
-
-void player__walk_transition( bool grounded, f32 board_yaw ){
- struct player_walk *w = &player_walk;
- w->state.activity = k_walk_activity_air;
-
- if( grounded ){
- w->state.activity = k_walk_activity_ipopoff;
- }
-
- w->state.transition_t = 0.0f;
- w->state.jump_queued = 0;
- w->state.jump_input_time = 0.0;
- w->state.walk_timer = 0.0f;
- w->state.step_phase = 0;
- w->animator.board_yaw = fmodf( board_yaw, 2.0f );
- rb_update_matrices( &localplayer.rb );
-}
-
-void player__walk_reset(void)
-{
- struct player_walk *w = &player_walk;
- w->state.activity = k_walk_activity_air;
- w->state.transition_t = 0.0f;
-
- v3f fwd = { 0.0f, 0.0f, 1.0f };
- q_mulv( localplayer.rb.q, fwd, fwd );
- q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
- atan2f(fwd[0], fwd[2]) );
-
- rb_update_matrices( &localplayer.rb );
-}
-
-void player__walk_animator_exchange( bitpack_ctx *ctx, void *data ){
- struct player_walk_animator *animator = data;
-
- bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
- bitpack_qquat( ctx, animator->root_q );
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->fly );
- bitpack_qf32( ctx, 8, 0.0f, k_runspeed, &animator->run );
- bitpack_qf32( ctx, 16, 0.0f, 120.0f, &animator->walk_timer );
-
- for( int i=0; i<1; i++ ){ /* without this you get a warning from gcc. lol */
- bitpack_bytes( ctx, 8, &animator->activity );
- }
-
- bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->transition_t );
-
- if( (animator->activity == k_walk_activity_sit) ||
- (animator->activity == k_walk_activity_sit_up) ){
- bitpack_qf32( ctx, 8, -k_sit_yaw_limit, k_sit_yaw_limit, &animator->yaw );
- bitpack_qf32( ctx, 8, -k_sit_pitch_limit, k_sit_pitch_limit,
- &animator->pitch );
- }
-
- bitpack_qf32( ctx, 16, -100.0f, 100.0f, &animator->board_yaw );
-}
-
-void player__walk_sfx_oneshot( u8 id, v3f pos, f32 volume )
-{
- audio_lock();
-
- if( id == k_player_walk_soundeffect_splash ){
- audio_oneshot_3d( &audio_splash, pos, 40.0f, 1.0f );
- }
-
- audio_unlock();
-}
+++ /dev/null
-#pragma once
-#include "player.h"
-#include "player_api.h"
-#include "vg/vg_rigidbody.h"
-
-#define PLAYER_JUMP_EPSILON 0.1 /* 100ms jump allowance */
-
-struct player_walk
-{
- rb_capsule collider;
-
- struct player_walk_state{
- v3f prev_pos;
- v3f drop_in_target,
- drop_in_start,
- drop_in_normal;
-
- float drop_in_start_angle,
- drop_in_angle;
-
- enum walk_activity{
- k_walk_activity_air,
- k_walk_activity_ground,
- k_walk_activity_sit,
- k_walk_activity_sit_up,
-
- /* transitions */
- k_walk_activity_inone,
- k_walk_activity_ipopoff,
- k_walk_activity_oair,
- k_walk_activity_odrop_in,
- k_walk_activity_oregular,
-
- k_walk_activity_max,
- }
- activity;
-
- f32 transition_t;
-
- int jump_queued;
- f64 jump_input_time;
-
- f32 walk_timer;
- int step_phase;
- v3f steer;
- }
- state;
-
- f32 move_speed;
-
- enum mdl_surface_prop surface;
- struct skeleton_anim *anim_walk, *anim_run, *anim_idle, *anim_jump,
- *anim_jump_to_air, *anim_drop_in, *anim_intro,
- *anim_sit, *anim_popoff;
-
- struct player_walk_animator {
- v3f root_co;
- v4f root_q;
- f32 fly,
- run,
- walk;
-
- f32 walk_timer, yaw, pitch, board_yaw;
-
- enum walk_activity activity;
- f32 transition_t;
- }
- animator;
-}
-extern player_walk;
-extern struct player_subsystem_interface player_subsystem_walk;
-
-enum player_walk_soundeffect {
- k_player_walk_soundeffect_splash
-};
-
-static f32
- k_walkspeed = 4.4f,
- k_runspeed = 10.0f,
- k_airspeed = 1.2f,
- k_stopspeed = 4.0f,
- k_walk_accel = 10.0f,
- k_walk_air_accel = 7.0f,
- k_walk_friction = 6.0f,
- k_walk_step_height = 0.2f,
-
- k_sit_yaw_limit = VG_PIf/1.7f,
- k_sit_pitch_limit = VG_PIf/4.0f;
-
-static void player__walk_register(void)
-{
- VG_VAR_F32( k_walkspeed, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_runspeed, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_stopspeed, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_airspeed, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_walk_friction, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_walk_air_accel, flags=VG_VAR_CHEAT );
- VG_VAR_F32( k_walk_accel, flags=VG_VAR_CHEAT );
-}
-
-void player__walk_pre_update (void);
-void player__walk_update (void);
-void player__walk_post_update (void);
-void player__walk_animate (void);
-void player__walk_pose (void *animator, player_pose *pose);
-void player__walk_post_animate(void);
-void player__walk_im_gui ( ui_context *ctx );
-void player__walk_bind (void);
-void player__walk_reset (void);
-void player__walk_restore (void);
-void player__walk_animator_exchange( bitpack_ctx *ctx, void *data );
-void player__walk_transition( bool grounded, f32 board_yaw );
-void player__walk_sfx_oneshot( u8 id, v3f pos, f32 volume );
+++ /dev/null
-#include "render.h"
-#include "vg/vg_engine.h"
-#include "vg/vg_platform.h"
-#include "vg/vg_framebuffer.h"
-
-static void async_render_init( void *payload, u32 size )
-{
- f32 rh = 0x1p-4f, ih = 0.3f;
-
- float quad[] = {
- 0.00f,0.00f, 1.00f,1.00f, 0.00f,1.00f, /* fsquad */
- 0.00f,0.00f, 1.00f,0.00f, 1.00f,1.00f,
-
- 0.00f,0.00f, 1.00f,rh, 0.00f,rh, /* fsquad1 */
- 0.00f,0.00f, 1.00f,0.00f, 1.00f,rh,
- 0.00f,1.00f, 0.00f,1.0f-rh,1.00f,1.0f-rh,
- 0.00f,1.00f, 1.00f,1.0f-rh,1.00f,1.0f,
-
- /* 9x9 debug grid */
- /* row0 */
- 0.00f,0.00f, 0.30f,0.30f, 0.00f,0.30f,
- 0.00f,0.00f, 0.30f,0.00f, 0.30f,0.30f,
- 0.30f,0.00f, 0.60f,0.30f, 0.30f,0.30f,
- 0.30f,0.00f, 0.60f,0.00f, 0.60f,0.30f,
- 0.60f,0.00f, 0.90f,0.30f, 0.60f,0.30f,
- 0.60f,0.00f, 0.90f,0.00f, 0.90f,0.30f,
- /* row1 */
- 0.00f,0.30f, 0.30f,0.60f, 0.00f,0.60f,
- 0.00f,0.30f, 0.30f,0.30f, 0.30f,0.60f,
- 0.30f,0.30f, 0.60f,0.60f, 0.30f,0.60f,
- 0.30f,0.30f, 0.60f,0.30f, 0.60f,0.60f,
- 0.60f,0.30f, 0.90f,0.60f, 0.60f,0.60f,
- 0.60f,0.30f, 0.90f,0.30f, 0.90f,0.60f,
- /* row2 */
- 0.00f,0.60f, 0.30f,0.90f, 0.00f,0.90f,
- 0.00f,0.60f, 0.30f,0.60f, 0.30f,0.90f,
- 0.30f,0.60f, 0.60f,0.90f, 0.30f,0.90f,
- 0.30f,0.60f, 0.60f,0.60f, 0.60f,0.90f,
- 0.60f,0.60f, 0.90f,0.90f, 0.60f,0.90f,
- 0.60f,0.60f, 0.90f,0.60f, 0.90f,0.90f,
-
- 0.00f,ih, 1.00f,ih+rh, 0.00f,ih+rh, /* fsquad2 */
- 0.00f,ih, 1.00f,ih, 1.00f,ih+rh,
- };
-
- glGenVertexArrays( 1, &g_render.fsquad.vao );
- glGenBuffers( 1, &g_render.fsquad.vbo );
- glBindVertexArray( g_render.fsquad.vao );
- glBindBuffer( GL_ARRAY_BUFFER, g_render.fsquad.vbo );
- glBufferData( GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW );
- glBindVertexArray( g_render.fsquad.vao );
- glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE,
- sizeof(float)*2, (void*)0 );
- glEnableVertexAttribArray( 0 );
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- g_render.ready = 1;
-}
-
-void render_init(void)
-{
- vg_console_reg_var( "blur_strength", &k_blur_strength, k_var_dtype_f32, 0 );
- vg_console_reg_var( "render_scale", &k_render_scale,
- k_var_dtype_f32, VG_VAR_PERSISTENT );
- vg_console_reg_var( "fov", &k_fov, k_var_dtype_f32, VG_VAR_PERSISTENT );
- vg_console_reg_var( "cam_height", &k_cam_height,
- k_var_dtype_f32, VG_VAR_PERSISTENT );
- vg_console_reg_var( "blur_effect", &k_blur_effect,
- k_var_dtype_i32, VG_VAR_PERSISTENT );
-
- void *alloc = vg_mem.rtmemory;
-
- /*
- * Main framebuffer
- */
- g_render.fb_main = vg_framebuffer_allocate( alloc, 3, 1 );
- g_render.fb_main->display_name = "main";
- g_render.fb_main->resolution_div = 1;
- g_render.fb_main->attachments[0] = (vg_framebuffer_attachment)
- {
- "colour", k_framebuffer_attachment_type_texture,
-
- .internalformat = GL_RGB,
- .format = GL_RGB,
- .type = GL_UNSIGNED_BYTE,
- .attachment = GL_COLOR_ATTACHMENT0
- };
- g_render.fb_main->attachments[1] = (vg_framebuffer_attachment)
- {
- "motion", k_framebuffer_attachment_type_texture,
-
- .quality = k_framebuffer_quality_high_only,
- .internalformat = GL_RG16F,
- .format = GL_RG,
- .type = GL_FLOAT,
- .attachment = GL_COLOR_ATTACHMENT1
- };
- g_render.fb_main->attachments[2] = (vg_framebuffer_attachment)
- {
- "depth_stencil", k_framebuffer_attachment_type_texture_depth,
- .internalformat = GL_DEPTH24_STENCIL8,
- .format = GL_DEPTH_STENCIL,
- .type = GL_UNSIGNED_INT_24_8,
- .attachment = GL_DEPTH_STENCIL_ATTACHMENT
- };
- vg_framebuffer_create( g_render.fb_main );
-
- /*
- * Water reflection
- */
- g_render.fb_water_reflection = vg_framebuffer_allocate( alloc, 2, 1 );
- g_render.fb_water_reflection->display_name = "water_reflection";
- g_render.fb_water_reflection->resolution_div = 2;
- g_render.fb_water_reflection->attachments[0] = (vg_framebuffer_attachment)
- {
- "colour", k_framebuffer_attachment_type_texture,
- .internalformat = GL_RGB,
- .format = GL_RGB,
- .type = GL_UNSIGNED_BYTE,
- .attachment = GL_COLOR_ATTACHMENT0
- };
- g_render.fb_water_reflection->attachments[1] = (vg_framebuffer_attachment)
- {
- "depth_stencil", k_framebuffer_attachment_type_renderbuffer,
- .internalformat = GL_DEPTH24_STENCIL8,
- .attachment = GL_DEPTH_STENCIL_ATTACHMENT
- };
- vg_framebuffer_create( g_render.fb_water_reflection );
-
- /*
- * Thid rendered view from the perspective of the camera, but just
- * captures stuff thats under the water
- */
- g_render.fb_water_beneath = vg_framebuffer_allocate( alloc, 2, 1 );
- g_render.fb_water_beneath->display_name = "water_beneath";
- g_render.fb_water_beneath->resolution_div = 2;
- g_render.fb_water_beneath->attachments[0] = (vg_framebuffer_attachment)
- {
- "colour", k_framebuffer_attachment_type_texture,
- .internalformat = GL_RED,
- .format = GL_RED,
- .type = GL_UNSIGNED_BYTE,
- .attachment = GL_COLOR_ATTACHMENT0
- };
- g_render.fb_water_beneath->attachments[1] = (vg_framebuffer_attachment)
- {
- "depth_stencil", k_framebuffer_attachment_type_renderbuffer,
- .internalformat = GL_DEPTH24_STENCIL8,
- .attachment = GL_DEPTH_STENCIL_ATTACHMENT
- };
- vg_framebuffer_create( g_render.fb_water_beneath );
-
- /*
- * Workshop preview
- */
- g_render.fb_workshop_preview = vg_framebuffer_allocate( alloc, 2, 1 );
- g_render.fb_workshop_preview->display_name = "workshop_preview";
- g_render.fb_workshop_preview->resolution_div = 0;
- g_render.fb_workshop_preview->fixed_w = WORKSHOP_PREVIEW_WIDTH;
- g_render.fb_workshop_preview->fixed_h = WORKSHOP_PREVIEW_HEIGHT;
- g_render.fb_workshop_preview->attachments[0] = (vg_framebuffer_attachment)
- {
- "colour", k_framebuffer_attachment_type_texture,
- .internalformat = GL_RGB,
- .format = GL_RGB,
- .type = GL_UNSIGNED_BYTE,
- .attachment = GL_COLOR_ATTACHMENT0
- };
- g_render.fb_workshop_preview->attachments[1] = (vg_framebuffer_attachment)
- {
- "depth_stencil", k_framebuffer_attachment_type_renderbuffer,
- .internalformat = GL_DEPTH24_STENCIL8,
- .attachment = GL_DEPTH_STENCIL_ATTACHMENT
- };
- vg_framebuffer_create( g_render.fb_workshop_preview );
-
- /*
- * Network status
- */
- g_render.fb_network_status = vg_framebuffer_allocate( alloc, 1, 1 );
- g_render.fb_network_status->display_name = "network_status_ui";
- g_render.fb_network_status->resolution_div = 0;
- g_render.fb_network_status->fixed_w = 128;
- g_render.fb_network_status->fixed_h = 48;
- g_render.fb_network_status->attachments[0] = (vg_framebuffer_attachment)
- {
- "colour", k_framebuffer_attachment_type_texture,
- .internalformat = GL_RGB,
- .format = GL_RGB,
- .type = GL_UNSIGNED_BYTE,
- .attachment = GL_COLOR_ATTACHMENT0
- };
- vg_framebuffer_create( g_render.fb_network_status );
-
- vg_async_call( async_render_init, NULL, 0 );
-}
-
-/*
- * Utility
- */
-void render_fsquad(void)
-{
- glBindVertexArray( g_render.fsquad.vao );
- glDrawArrays( GL_TRIANGLES, 0, 6 );
-}
-
-void render_fsquad1(void)
-{
- glBindVertexArray( g_render.fsquad.vao );
- glDrawArrays( GL_TRIANGLES, 6, 6+6 );
-}
-
-void render_fsquad2(void)
-{
- glBindVertexArray( g_render.fsquad.vao );
- glDrawArrays( GL_TRIANGLES, 66+6,6 );
-}
-
-void postprocess_to_screen( vg_framebuffer *fb )
-{
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glViewport( 0,0, vg.window_x, vg.window_y );
-
- glEnable(GL_BLEND);
- glDisable(GL_DEPTH_TEST);
- glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
- glBlendEquation(GL_FUNC_ADD);
-
- v2f inverse;
- vg_framebuffer_inverse_ratio( fb, inverse );
-
- if( k_blur_effect )
- {
- shader_blitblur_use();
- shader_blitblur_uTexMain( 0 );
- shader_blitblur_uTexMotion( 1 );
- shader_blitblur_uBlurStrength( k_blur_strength /
- (vg.time_frame_delta*60.0) );
- shader_blitblur_uInverseRatio( inverse );
-
- inverse[0] -= 0.0001f;
- inverse[1] -= 0.0001f;
- shader_blitblur_uClampUv( inverse );
- shader_blitblur_uOverrideDir( g_render.blur_override );
-
- vg_framebuffer_bind_texture( fb, 0, 0 );
- vg_framebuffer_bind_texture( fb, 1, 1 );
- }
- else
- {
- shader_blit_use();
- shader_blit_uTexMain( 0 );
- shader_blit_uInverseRatio( inverse );
- vg_framebuffer_bind_texture( fb, 0, 0 );
- }
-
- render_fsquad();
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-#pragma once
-#include "common.h"
-#include "model.h"
-#include "shader_props.h"
-#include "vg/vg_framebuffer.h"
-#include "vg/vg_camera.h"
-
-#include "shaders/blit.h"
-#include "shaders/blitblur.h"
-#include "shaders/blitcolour.h"
-#include "shaders/blit_transition.h"
-
-#define WORKSHOP_PREVIEW_WIDTH 504
-#define WORKSHOP_PREVIEW_HEIGHT 336
-
-static f32 k_render_scale = 1.0f;
-static i32 k_blur_effect = 1;
-static f32 k_blur_strength = 0.3f;
-static f32 k_fov = 0.86f;
-static f32 k_cam_height = 0.8f;
-
-/*
- * All standard buffers used in rendering
- */
-struct pipeline
-{
- glmesh fsquad;
-
- vg_framebuffer *fb_main,
- *fb_water_reflection,
- *fb_water_beneath,
- *fb_workshop_preview,
- *fb_network_status;
- int ready;
-
- v2f blur_override;
- vg_camera cam;
-}
-static g_render;
-
-void render_init(void);
-void render_fsquad(void);
-void render_fsquad1(void);
-void render_fsquad2(void);
-void postprocess_to_screen( vg_framebuffer *fb );
+++ /dev/null
-#include "skaterift.h"
-#include "save.h"
-#include "addon.h"
-#include "vg/vg_msg.h"
-#include "vg/vg_log.h"
-#include "vg/vg_loader.h"
-#include "world.h"
-#include "player.h"
-
-static const char *str_skaterift_main_save = "save.bkv";
-static f64 last_autosave;
-
-void savedata_file_write( savedata_file *file )
-{
- savedata_file *sav = file;
- FILE *fp = fopen( sav->path, "wb" );
- if( fp ){
- fwrite( sav->buf, sav->len, 1, fp );
- fclose( fp );
- vg_success( "savedata written to '%s'\n", sav->path );
- }
- else {
- vg_error( "Error writing savedata (%s)\n", sav->path );
- }
-}
-
-void savedata_group_write( savedata_group *group )
-{
- for( u32 i=0; i<group->file_count; i++ ){
- savedata_file_write( &group->files[i] );
- }
-}
-
-void savedata_file_read( savedata_file *file )
-{
- FILE *fp = fopen( file->path, "rb" );
- if( fp ){
- file->len = fread( file->buf, 1, sizeof(file->buf), fp );
- fclose( fp );
- }
- else{
- file->len = 0;
- vg_warn( "Error reading savedata (%s)\n", file->path );
- }
-}
-
-static void skaterift_write_addon_alias( vg_msg *msg, const char *key,
- addon_alias *alias ){
- if( alias->workshop_id )
- vg_msg_wkvnum( msg, key, k_vg_msg_u64, 1, &alias->workshop_id );
- else
- vg_msg_wkvstr( msg, key, alias->foldername );
-}
-
-static void skaterift_write_viewslot( vg_msg *msg, const char *key,
- enum addon_type type, u16 cache_id ){
- if( !cache_id ) return;
-
- struct addon_cache *cache = &addon_system.cache[type];
- addon_cache_entry *entry = vg_pool_item( &cache->pool, cache_id );
- addon_reg *reg = entry->reg_ptr;
-
- if( reg )
- skaterift_write_addon_alias( msg, key, ®->alias );
-}
-
-void skaterift_read_addon_alias( vg_msg *msg, const char *key,
- enum addon_type type,
- addon_alias *alias )
-{
- alias->foldername[0] = '\0';
- alias->workshop_id = 0;
- alias->type = type;
-
- vg_msg_cmd kv;
- if( vg_msg_getkvcmd( msg, key, &kv ) ){
- if( kv.code == k_vg_msg_kvstring ){
- vg_strncpy( kv.value, alias->foldername, sizeof(alias->foldername),
- k_strncpy_allow_cutoff );
- }
- else
- vg_msg_cast( kv.value, kv.code, &alias->workshop_id, k_vg_msg_u64 );
- }
-}
-
-static void skaterift_populate_world_savedata( savedata_file *file,
- enum world_purpose which ){
- file->path[0] = '\0';
- file->len = 0;
- addon_reg *reg = world_static.instance_addons[ which ];
-
- if( !reg ){
- vg_error( "Tried to save unspecified world (reg was null)\n" );
- return;
- }
-
- skaterift_world_get_save_path( which, file->path );
-
- vg_msg sav;
- vg_msg_init( &sav, file->buf, sizeof(file->buf) );
-
- world_instance *instance = &world_static.instances[which];
- world_entity_serialize( instance, &sav );
-
- vg_msg_frame( &sav, "player" );
- {
- vg_msg_wkvnum( &sav, "position", k_vg_msg_float|k_vg_msg_32b, 3,
- (which == world_static.active_instance)?
- localplayer.rb.co:
- instance->player_co );
- }
- vg_msg_end_frame( &sav );
-
- file->len = sav.cur.co;
-}
-
-static void skaterift_populate_main_savedata( savedata_file *file )
-{
- strcpy( file->path, str_skaterift_main_save );
-
- vg_msg sav;
- vg_msg_init( &sav, file->buf, sizeof(file->buf) );
- vg_msg_wkvnum( &sav, "ach", k_vg_msg_u32, 1, &skaterift.achievements );
-
- vg_msg_frame( &sav, "player" );
- {
- skaterift_write_viewslot( &sav, "board", k_addon_type_board,
- localplayer.board_view_slot );
- skaterift_write_viewslot( &sav, "playermodel", k_addon_type_player,
- localplayer.playermodel_view_slot );
- }
- vg_msg_end_frame( &sav );
-
- file->len = sav.cur.co;
-}
-
-void skaterift_read_main_savedata( savedata_file *file )
-{
- strcpy( file->path, str_skaterift_main_save );
- savedata_file_read( file );
-}
-
-int skaterift_autosave( int async )
-{
- if( async )
- if( !vg_loader_availible() ) return 0;
-
- u32 save_files = 2;
- if( world_static.instances[k_world_purpose_client].status
- == k_world_status_loaded ){
- save_files ++;
- }
-
- vg_linear_clear( vg_async.buffer );
- u32 size = sizeof(savedata_group) + sizeof(savedata_file) * save_files;
-
- savedata_group *group;
- if( async ){
- size = vg_align8( size );
- group = vg_linear_alloc( vg_async.buffer, size );
- }
- else
- group = alloca( size );
-
- group->file_count = save_files;
- skaterift_populate_main_savedata( &group->files[0] );
- skaterift_populate_world_savedata( &group->files[1], k_world_purpose_hub );
-
- if( world_static.instances[ k_world_purpose_client ].status
- == k_world_status_loaded ){
- skaterift_populate_world_savedata( &group->files[2],
- k_world_purpose_client );
- }
-
- if( async )
- vg_loader_start( (void *)savedata_group_write, group );
- else
- savedata_group_write( group );
-
- return 1;
-}
-
-void skaterift_autosave_synchronous(void)
-{
- skaterift_autosave(0);
-}
-
-void skaterift_autosave_update(void)
-{
- if( vg.time - last_autosave > 20.0 ){
- if( skaterift_autosave(1) ){
- last_autosave = vg.time;
- }
- }
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-#include "vg/vg_msg.h"
-#include "addon.h"
-
-typedef struct savedata_file savedata_file;
-typedef struct savedata_group savedata_group;
-
-struct savedata_group {
- u32 file_count;
- struct savedata_file {
- char path[128];
- u8 buf[2048];
- u32 len;
- }
- files[];
-};
-
-void savedata_file_read( savedata_file *file );
-void savedata_file_write( savedata_file *file );
-void savedata_group_write( savedata_group *group );
-int skaterift_autosave(int async);
-void skaterift_autosave_synchronous(void);
-void skaterift_autosave_update(void);
-void skaterift_read_addon_alias( vg_msg *msg, const char *key,
- enum addon_type type,
- addon_alias *alias );
-
-void skaterift_read_main_savedata( savedata_file *file );
+++ /dev/null
-#include "scene.h"
-
-u32 scene_mem_required( scene_context *ctx )
-{
- u32 vertex_length = vg_align8(ctx->max_vertices * sizeof(scene_vert)),
- index_length = vg_align8(ctx->max_indices * sizeof(u32));
-
- return vertex_length + index_length;
-}
-
-void scene_init( scene_context *ctx, u32 max_vertices, u32 max_indices )
-{
- ctx->vertex_count = 0;
- ctx->indice_count = 0;
- ctx->max_vertices = max_vertices;
- ctx->max_indices = max_indices;
- ctx->arrindices = NULL; /* must be filled out by user */
- ctx->arrvertices = NULL;
-
- memset( &ctx->submesh, 0, sizeof(mdl_submesh) );
-
- v3_fill( ctx->bbx[0], 999999.9f );
- v3_fill( ctx->bbx[1], -999999.9f );
-}
-
-void scene_supply_buffer( scene_context *ctx, void *buffer )
-{
- u32 vertex_length = vg_align8( ctx->max_vertices * sizeof(scene_vert) );
-
- ctx->arrvertices = buffer;
- ctx->arrindices = (u32*)(((u8*)buffer) + vertex_length);
-}
-
-void scene_vert_pack_norm( scene_vert *vert, v3f norm, f32 blend )
-{
- v3f n;
- v3_muls( norm, 127.0f, n );
- v3_minv( n, (v3f){ 127.0f, 127.0f, 127.0f }, n );
- v3_maxv( n, (v3f){ -127.0f, -127.0f, -127.0f }, n );
- vert->norm[0] = n[0];
- vert->norm[1] = n[1];
- vert->norm[2] = n[2];
- vert->norm[3] = blend * 127.0f;
-}
-
-/*
- * Append a model into the scene with a given transform
- */
-void scene_add_mdl_submesh( scene_context *ctx, mdl_context *mdl,
- mdl_submesh *sm, m4x3f transform )
-{
- if( ctx->vertex_count + sm->vertex_count > ctx->max_vertices ){
- vg_fatal_error( "Scene vertex buffer overflow (%u exceeds %u)\n",
- ctx->vertex_count + sm->vertex_count,
- ctx->max_vertices );
- }
-
- if( ctx->indice_count + sm->indice_count > ctx->max_indices ){
- vg_fatal_error( "Scene index buffer overflow (%u exceeds %u)\n",
- ctx->indice_count + sm->indice_count,
- ctx->max_indices );
- }
-
- mdl_vert *src_verts = mdl_arritm( &mdl->verts, sm->vertex_start );
- scene_vert *dst_verts = &ctx->arrvertices[ ctx->vertex_count ];
-
- u32 *src_indices = mdl_arritm( &mdl->indices, sm->indice_start ),
- *dst_indices = &ctx->arrindices[ ctx->indice_count ];
-
- /* Transform and place vertices */
- boxf bbxnew;
- box_init_inf( bbxnew );
- m4x3_expand_aabb_aabb( transform, bbxnew, sm->bbx );
- box_concat( ctx->bbx, bbxnew );
-
- m3x3f normal_matrix;
- m3x3_copy( transform, normal_matrix );
- v3_normalize( normal_matrix[0] );
- v3_normalize( normal_matrix[1] );
- v3_normalize( normal_matrix[2] );
-
- for( u32 i=0; i<sm->vertex_count; i++ ){
- mdl_vert *src = &src_verts[i];
- scene_vert *pvert = &dst_verts[i];
-
- m4x3_mulv( transform, src->co, pvert->co );
-
- v3f normal;
- m3x3_mulv( normal_matrix, src->norm, normal );
- scene_vert_pack_norm( pvert, normal, src->colour[0]*(1.0f/255.0f) );
-
- v2_copy( src->uv, pvert->uv );
- }
-
- u32 real_indices = 0;
- for( u32 i=0; i<sm->indice_count/3; i++ ){
- u32 *src = &src_indices[i*3],
- *dst = &dst_indices[real_indices];
-
- v3f ab, ac, tn;
- v3_sub( src_verts[src[2]].co, src_verts[src[0]].co, ab );
- v3_sub( src_verts[src[1]].co, src_verts[src[0]].co, ac );
- v3_cross( ac, ab, tn );
-
-#if 0
- if( v3_length2( tn ) <= 0.00001f )
- continue;
-#endif
-
- dst[0] = src[0] + ctx->vertex_count;
- dst[1] = src[1] + ctx->vertex_count;
- dst[2] = src[2] + ctx->vertex_count;
-
- real_indices += 3;
- }
-
- if( real_indices != sm->indice_count )
- vg_warn( "Zero area triangles in model\n" );
-
- ctx->vertex_count += sm->vertex_count;
- ctx->indice_count += real_indices;
-}
-
-/*
- * One by one adders for simplified access (mostly procedural stuff)
- */
-void scene_push_tri( scene_context *ctx, u32 tri[3] )
-{
- if( ctx->indice_count + 3 > ctx->max_indices )
- vg_fatal_error( "Scene indice buffer overflow (%u exceeds %u)\n",
- ctx->indice_count+3, ctx->max_indices );
-
- u32 *dst = &ctx->arrindices[ ctx->indice_count ];
-
- dst[0] = tri[0];
- dst[1] = tri[1];
- dst[2] = tri[2];
-
- ctx->indice_count += 3;
-}
-
-void scene_push_vert( scene_context *ctx, scene_vert *v )
-{
- if( ctx->vertex_count + 1 > ctx->max_vertices )
- vg_fatal_error( "Scene vertex buffer overflow (%u exceeds %u)\n",
- ctx->vertex_count+1, ctx->max_vertices );
-
- scene_vert *dst = &ctx->arrvertices[ ctx->vertex_count ];
- *dst = *v;
-
- ctx->vertex_count ++;
-}
-
-void scene_copy_slice( scene_context *ctx, mdl_submesh *sm )
-{
- sm->indice_start = ctx->submesh.indice_start;
- sm->indice_count = ctx->indice_count - sm->indice_start;
-
- sm->vertex_start = ctx->submesh.vertex_start;
- sm->vertex_count = ctx->vertex_count - sm->vertex_start;
-
- ctx->submesh.indice_start = ctx->indice_count;
- ctx->submesh.vertex_start = ctx->vertex_count;
-}
-
-void scene_set_vertex_flags( scene_context *ctx,
- u32 start, u32 count, u16 flags )
-{
- for( u32 i=0; i<count; i++ )
- ctx->arrvertices[ start + i ].flags = flags;
-}
-
-struct scene_upload_info{
- scene_context *ctx;
- glmesh *mesh;
-};
-
-void async_scene_upload( void *payload, u32 size )
-{
- struct scene_upload_info *info = payload;
-
- //assert( mesh->loaded == 0 );
-
- glmesh *mesh = info->mesh;
- scene_context *ctx = info->ctx;
-
- glGenVertexArrays( 1, &mesh->vao );
- glGenBuffers( 1, &mesh->vbo );
- glGenBuffers( 1, &mesh->ebo );
- glBindVertexArray( mesh->vao );
-
- size_t stride = sizeof(scene_vert);
-
- glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo );
- glBufferData( GL_ARRAY_BUFFER, ctx->vertex_count*stride,
- ctx->arrvertices, GL_STATIC_DRAW );
-
- glBindVertexArray( mesh->vao );
- glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo );
- glBufferData( GL_ELEMENT_ARRAY_BUFFER, ctx->indice_count*sizeof(u32),
- ctx->arrindices, GL_STATIC_DRAW );
-
- /* 0: coordinates */
- glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
- glEnableVertexAttribArray( 0 );
-
- /* 1: normal */
- glVertexAttribPointer( 1, 4, GL_BYTE, GL_TRUE,
- stride, (void *)offsetof(scene_vert, norm) );
- glEnableVertexAttribArray( 1 );
-
- /* 2: uv */
- glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE,
- stride, (void *)offsetof(scene_vert, uv) );
- glEnableVertexAttribArray( 2 );
-
- mesh->indice_count = ctx->indice_count;
- mesh->loaded = 1;
-
- vg_info( "Scene upload ( XYZ_f32 UV_f32 XYZW_i8 )[ u32 ]\n" );
- vg_info( " indices:%u\n", ctx->indice_count );
- vg_info( " verts:%u\n", ctx->vertex_count );
-}
-
-void scene_upload_async( scene_context *ctx, glmesh *mesh )
-{
- vg_async_item *call = vg_async_alloc( sizeof(struct scene_upload_info) );
-
- struct scene_upload_info *info = call->payload;
- info->mesh = mesh;
- info->ctx = ctx;
-
- vg_async_dispatch( call, async_scene_upload );
-}
-
-vg_async_item *scene_alloc_async( scene_context *scene, glmesh *mesh,
- u32 max_vertices, u32 max_indices )
-{
- scene_init( scene, max_vertices, max_indices );
- u32 buf_size = scene_mem_required( scene );
-
- u32 hdr_size = vg_align8(sizeof(struct scene_upload_info));
- vg_async_item *call = vg_async_alloc( hdr_size + buf_size );
-
- struct scene_upload_info *info = call->payload;
-
- info->mesh = mesh;
- info->ctx = scene;
-
- void *buffer = ((u8*)call->payload)+hdr_size;
- scene_supply_buffer( scene, buffer );
-
- return call;
-}
-
-
-/*
- * BVH implementation
- */
-
-static void scene_bh_expand_bound( void *user, boxf bound, u32 item_index )
-{
- scene_context *s = user;
- scene_vert *pa = &s->arrvertices[ s->arrindices[item_index*3+0] ],
- *pb = &s->arrvertices[ s->arrindices[item_index*3+1] ],
- *pc = &s->arrvertices[ s->arrindices[item_index*3+2] ];
-
- box_addpt( bound, pa->co );
- box_addpt( bound, pb->co );
- box_addpt( bound, pc->co );
-}
-
-static float scene_bh_centroid( void *user, u32 item_index, int axis )
-{
- scene_context *s = user;
- scene_vert *pa = &s->arrvertices[ s->arrindices[item_index*3+0] ],
- *pb = &s->arrvertices[ s->arrindices[item_index*3+1] ],
- *pc = &s->arrvertices[ s->arrindices[item_index*3+2] ];
-
- #if 0
-
- float min, max;
-
- min = vg_minf( pa->co[axis], pb->co[axis] );
- max = vg_maxf( pa->co[axis], pb->co[axis] );
- min = vg_minf( min, pc->co[axis] );
- max = vg_maxf( max, pc->co[axis] );
-
- return (min+max) * 0.5f;
-
- #else
- return (pa->co[axis] + pb->co[axis] + pc->co[axis]) * (1.0f/3.0f);
- #endif
-}
-
-static void scene_bh_swap( void *user, u32 ia, u32 ib )
-{
- scene_context *s = user;
-
- u32 *ti = &s->arrindices[ia*3];
- u32 *tj = &s->arrindices[ib*3];
-
- u32 temp[3];
- temp[0] = ti[0];
- temp[1] = ti[1];
- temp[2] = ti[2];
-
- ti[0] = tj[0];
- ti[1] = tj[1];
- ti[2] = tj[2];
-
- tj[0] = temp[0];
- tj[1] = temp[1];
- tj[2] = temp[2];
-}
-
-static void scene_bh_debug( void *user, u32 item_index )
-{
- scene_context *s = user;
- u32 idx = item_index*3;
- scene_vert *pa = &s->arrvertices[ s->arrindices[ idx+0 ] ],
- *pb = &s->arrvertices[ s->arrindices[ idx+1 ] ],
- *pc = &s->arrvertices[ s->arrindices[ idx+2 ] ];
-
- vg_line( pa->co, pb->co, 0xff0000ff );
- vg_line( pb->co, pc->co, 0xff0000ff );
- vg_line( pc->co, pa->co, 0xff0000ff );
-}
-
-static void scene_bh_closest( void *user, u32 index, v3f point, v3f closest )
-{
- scene_context *s = user;
-
- v3f positions[3];
- u32 *tri = &s->arrindices[ index*3 ];
- for( int i=0; i<3; i++ )
- v3_copy( s->arrvertices[tri[i]].co, positions[i] );
-
- closest_on_triangle_1( point, positions, closest );
-}
-
-bh_system bh_system_scene =
-{
- .expand_bound = scene_bh_expand_bound,
- .item_centroid = scene_bh_centroid,
- .item_closest = scene_bh_closest,
- .item_swap = scene_bh_swap,
- .item_debug = scene_bh_debug,
-};
-
-/*
- * An extra step is added onto the end to calculate the hit normal
- */
-int scene_raycast( scene_context *s, bh_tree *bh,
- v3f co, v3f dir, ray_hit *hit, u16 ignore )
-{
- hit->tri = NULL;
-
- bh_iter it;
- bh_iter_init_ray( 0, &it, co, dir, hit->dist );
- i32 idx;
-
- while( bh_next( bh, &it, &idx ) ){
- u32 *tri = &s->arrindices[ idx*3 ];
-
- if( s->arrvertices[tri[0]].flags & ignore ) continue;
-
- v3f vs[3];
- for( u32 i=0; i<3; i++ )
- v3_copy( s->arrvertices[tri[i]].co, vs[i] );
-
- f32 t;
- if( ray_tri( vs, co, dir, &t, 0 ) ){
- if( t < hit->dist ){
- hit->dist = t;
- hit->tri = tri;
- }
- }
- }
-
- if( hit->tri ){
- v3f v0, v1;
-
- float *pa = s->arrvertices[hit->tri[0]].co,
- *pb = s->arrvertices[hit->tri[1]].co,
- *pc = s->arrvertices[hit->tri[2]].co;
-
- v3_sub( pa, pb, v0 );
- v3_sub( pc, pb, v1 );
- v3_cross( v1, v0, hit->normal );
- v3_normalize( hit->normal );
- v3_muladds( co, dir, hit->dist, hit->pos );
- }
-
- return hit->tri?1:0;
-}
-
-bh_tree *scene_bh_create( void *lin_alloc, scene_context *s )
-{
- u32 triangle_count = s->indice_count / 3;
- return bh_create( lin_alloc, &bh_system_scene, s, triangle_count, 2 );
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_bvh.h"
-#include "vg/vg_async.h"
-#include "common.h"
-#include "model.h"
-
-typedef struct scene_context scene_context;
-typedef struct scene_vert scene_vert;
-
-#pragma pack(push,1)
-
-/* 32 byte vertexs, we don't care about the normals too much,
- * maybe possible to bring down uv to i16s too */
-struct scene_vert
-{
- v3f co; /* 3*32 */
- v2f uv; /* 2*32 */
- i8 norm[4]; /* 4*8 */
- u16 flags; /* only for the cpu. its junk on the gpu */
- u16 unused[3];
-};
-
-#pragma pack(pop)
-
-/*
- * 1. this should probably be a CONTEXT based approach unlike this mess.
- * take a bit of the mdl_context ideas and redo this header. its messed up
- * pretty bad right now.
- */
-
-struct scene_context
-{
- scene_vert *arrvertices;
- u32 *arrindices;
-
- u32 vertex_count, indice_count,
- max_vertices, max_indices;
-
- boxf bbx;
- mdl_submesh submesh;
-};
-
-extern bh_system bh_system_scene;
-bh_tree *scene_bh_create( void *lin_alloc, scene_context *s );
-int scene_raycast( scene_context *s, bh_tree *bh,
- v3f co, v3f dir, ray_hit *hit, u16 ignore );
-vg_async_item *scene_alloc_async( scene_context *scene, glmesh *mesh,
- u32 max_vertices, u32 max_indices );
-void scene_copy_slice( scene_context *ctx, mdl_submesh *sm );
-void scene_push_vert( scene_context *ctx, scene_vert *v );
-void scene_vert_pack_norm( scene_vert *vert, v3f norm, f32 blend );
-void scene_push_tri( scene_context *ctx, u32 tri[3] );
-void scene_add_mdl_submesh( scene_context *ctx, mdl_context *mdl,
- mdl_submesh *sm, m4x3f transform );
-void scene_set_vertex_flags( scene_context *ctx,
- u32 start, u32 count, u16 flags );
-void scene_supply_buffer( scene_context *ctx, void *buffer );
-void scene_init( scene_context *ctx, u32 max_vertices, u32 max_indices );
-u32 scene_mem_required( scene_context *ctx );
-void async_scene_upload( void *payload, u32 size );
-void scene_upload_async( scene_context *ctx, glmesh *mesh );
+++ /dev/null
-#pragma once
-
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved
- *
- * Describes intereactions between vg rigidbody objects and skaterift's scene
- * description
- */
-
-#include "scene.h"
-#include "vg/vg_rigidbody.h"
-#include "vg/vg_rigidbody_collision.h"
-
-static int rb_sphere__scene( m4x3f mtxA, f32 r,
- m4x3f mtxB, bh_tree *scene_bh, rb_ct *buf,
- u16 ignore ){
- scene_context *sc = scene_bh->user;
-
- int count = 0;
-
- boxf box;
- v3_sub( mtxA[3], (v3f){ r,r,r }, box[0] );
- v3_add( mtxA[3], (v3f){ r,r,r }, box[1] );
-
- bh_iter it;
- i32 idx;
- bh_iter_init_box( 0, &it, box );
-
- while( bh_next( scene_bh, &it, &idx ) ){
- u32 *ptri = &sc->arrindices[ idx*3 ];
- v3f tri[3];
-
- if( sc->arrvertices[ptri[0]].flags & ignore ) continue;
-
- for( int j=0; j<3; j++ )
- v3_copy( sc->arrvertices[ptri[j]].co, tri[j] );
-
- buf[ count ].element_id = ptri[0];
-
- vg_line( tri[0],tri[1],0x70ff6000 );
- vg_line( tri[1],tri[2],0x70ff6000 );
- vg_line( tri[2],tri[0],0x70ff6000 );
-
- int contact = rb_sphere__triangle( mtxA, r, tri, &buf[count] );
- count += contact;
-
- if( count == 16 ){
- vg_warn( "Exceeding sphere_vs_scene capacity. Geometry too dense!\n" );
- return count;
- }
- }
-
- return count;
-}
-
-static int rb_box__scene( m4x3f mtxA, boxf bbx,
- m4x3f mtxB, bh_tree *scene_bh,
- rb_ct *buf, u16 ignore ){
- scene_context *sc = scene_bh->user;
- v3f tri[3];
-
- v3f extent, center;
- v3_sub( bbx[1], bbx[0], extent );
- v3_muls( extent, 0.5f, extent );
- v3_add( bbx[0], extent, center );
-
- f32 r = v3_length(extent);
- boxf world_bbx;
- v3_fill( world_bbx[0], -r );
- v3_fill( world_bbx[1], r );
- for( int i=0; i<2; i++ ){
- v3_add( center, world_bbx[i], world_bbx[i] );
- v3_add( mtxA[3], world_bbx[i], world_bbx[i] );
- }
-
- m4x3f to_local;
- m4x3_invert_affine( mtxA, to_local );
-
- bh_iter it;
- bh_iter_init_box( 0, &it, world_bbx );
- int idx;
- int count = 0;
-
- vg_line_boxf( world_bbx, VG__RED );
-
- while( bh_next( scene_bh, &it, &idx ) ){
- u32 *ptri = &sc->arrindices[ idx*3 ];
- if( sc->arrvertices[ptri[0]].flags & ignore ) continue;
-
- for( int j=0; j<3; j++ )
- v3_copy( sc->arrvertices[ptri[j]].co, tri[j] );
-
- if( rb_box_triangle_sat( extent, center, to_local, tri ) ){
- vg_line(tri[0],tri[1],0xff50ff00 );
- vg_line(tri[1],tri[2],0xff50ff00 );
- vg_line(tri[2],tri[0],0xff50ff00 );
- }
- else{
- vg_line(tri[0],tri[1],0xff0000ff );
- vg_line(tri[1],tri[2],0xff0000ff );
- vg_line(tri[2],tri[0],0xff0000ff );
- continue;
- }
-
- v3f v0,v1,n;
- v3_sub( tri[1], tri[0], v0 );
- v3_sub( tri[2], tri[0], v1 );
- v3_cross( v0, v1, n );
-
- if( v3_length2( n ) <= 0.00001f ){
-#ifdef RIGIDBODY_CRY_ABOUT_EVERYTHING
- vg_error( "Zero area triangle!\n" );
-#endif
- return 0;
- }
-
- v3_normalize( n );
-
- /* find best feature */
- f32 best = v3_dot( mtxA[0], n );
- int axis = 0;
-
- for( int i=1; i<3; i++ ){
- f32 c = v3_dot( mtxA[i], n );
-
- if( fabsf(c) > fabsf(best) ){
- best = c;
- axis = i;
- }
- }
-
- v3f manifold[4];
-
- if( axis == 0 ){
- f32 px = best > 0.0f? bbx[0][0]: bbx[1][0];
- manifold[0][0] = px;
- manifold[0][1] = bbx[0][1];
- manifold[0][2] = bbx[0][2];
- manifold[1][0] = px;
- manifold[1][1] = bbx[1][1];
- manifold[1][2] = bbx[0][2];
- manifold[2][0] = px;
- manifold[2][1] = bbx[1][1];
- manifold[2][2] = bbx[1][2];
- manifold[3][0] = px;
- manifold[3][1] = bbx[0][1];
- manifold[3][2] = bbx[1][2];
- }
- else if( axis == 1 ){
- f32 py = best > 0.0f? bbx[0][1]: bbx[1][1];
- manifold[0][0] = bbx[0][0];
- manifold[0][1] = py;
- manifold[0][2] = bbx[0][2];
- manifold[1][0] = bbx[1][0];
- manifold[1][1] = py;
- manifold[1][2] = bbx[0][2];
- manifold[2][0] = bbx[1][0];
- manifold[2][1] = py;
- manifold[2][2] = bbx[1][2];
- manifold[3][0] = bbx[0][0];
- manifold[3][1] = py;
- manifold[3][2] = bbx[1][2];
- }
- else{
- f32 pz = best > 0.0f? bbx[0][2]: bbx[1][2];
- manifold[0][0] = bbx[0][0];
- manifold[0][1] = bbx[0][1];
- manifold[0][2] = pz;
- manifold[1][0] = bbx[1][0];
- manifold[1][1] = bbx[0][1];
- manifold[1][2] = pz;
- manifold[2][0] = bbx[1][0];
- manifold[2][1] = bbx[1][1];
- manifold[2][2] = pz;
- manifold[3][0] = bbx[0][0];
- manifold[3][1] = bbx[1][1];
- manifold[3][2] = pz;
- }
-
- for( int j=0; j<4; j++ )
- m4x3_mulv( mtxA, manifold[j], manifold[j] );
-
- vg_line( manifold[0], manifold[1], 0xffffffff );
- vg_line( manifold[1], manifold[2], 0xffffffff );
- vg_line( manifold[2], manifold[3], 0xffffffff );
- vg_line( manifold[3], manifold[0], 0xffffffff );
-
- for( int j=0; j<4; j++ ){
- rb_ct *ct = buf+count;
-
- v3_copy( manifold[j], ct->co );
- v3_copy( n, ct->n );
-
- f32 l0 = v3_dot( tri[0], n ),
- l1 = v3_dot( manifold[j], n );
-
- ct->p = (l0-l1)*0.5f;
- if( ct->p < 0.0f )
- continue;
-
- ct->type = k_contact_type_default;
- count ++;
-
- if( count >= 12 )
- return count;
- }
- }
- return count;
-}
-
-/* mtxB is defined only for tradition; it is not used currently */
-static int rb_capsule__scene( m4x3f mtxA, rb_capsule *c,
- m4x3f mtxB, bh_tree *scene_bh,
- rb_ct *buf, u16 ignore ){
- int count = 0;
-
- boxf bbx;
- v3_sub( mtxA[3], (v3f){ c->h, c->h, c->h }, bbx[0] );
- v3_add( mtxA[3], (v3f){ c->h, c->h, c->h }, bbx[1] );
-
- scene_context *sc = scene_bh->user;
-
- bh_iter it;
- bh_iter_init_box( 0, &it, bbx );
- i32 idx;
- while( bh_next( scene_bh, &it, &idx ) ){
- u32 *ptri = &sc->arrindices[ idx*3 ];
- if( sc->arrvertices[ptri[0]].flags & ignore ) continue;
-
- v3f tri[3];
- for( int j=0; j<3; j++ )
- v3_copy( sc->arrvertices[ptri[j]].co, tri[j] );
-
- buf[ count ].element_id = ptri[0];
-
- int contact = rb_capsule__triangle( mtxA, c, tri, &buf[count] );
- count += contact;
-
- if( count >= 16 ){
- vg_warn("Exceeding capsule_vs_scene capacity. Geometry too dense!\n");
- return count;
- }
- }
-
- return count;
-}
-
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-
-struct shader_props_standard
-{
- u32 tex_diffuse;
-};
-
-struct shader_props_terrain
-{
- u32 tex_diffuse;
- v2f blend_offset;
- v4f sand_colour;
-};
-
-struct shader_props_vertex_blend
-{
- u32 tex_diffuse;
- v2f blend_offset;
-};
-
-struct shader_props_water
-{
- v4f shore_colour;
- v4f deep_colour;
- f32 fog_scale;
- f32 fresnel;
- f32 water_sale;
- v4f wave_speed;
-};
-
-struct shader_props_cubemapped
-{
- u32 tex_diffuse;
- u32 cubemap_entity;
- v4f tint;
-};
+++ /dev/null
-/*
- * =============================================================================
- *
- * Copyright . . . -----, ,----- ,---. .---.
- * 2021-2024 |\ /| | / | | | | /|
- * | \ / | +-- / +----- +---' | / |
- * | \ / | | / | | \ | / |
- * | \/ | | / | | \ | / |
- * ' ' '--' [] '----- '----- ' ' '---' SOFTWARE
- *
- * =============================================================================
- */
-
-#define SR_ALLOW_REWIND_HUB
-
-#ifdef _WIN32
- #include <winsock2.h>
-#endif
-
-/*
- * system headers
- * --------------------- */
-
-#include "vg/vg_opt.h"
-#include "vg/vg_loader.h"
-#include "vg/vg_io.h"
-
-#include "skaterift.h"
-#include "steam.h"
-#include "render.h"
-#include "world.h"
-#include "font.h"
-#include "player.h"
-#include "network.h"
-#include "menu.h"
-#include "vehicle.h"
-#include "save.h"
-#include "player_remote.h"
-#include "particle.h"
-#include "trail.h"
-#include "freecam.h"
-#include "ent_tornado.h"
-#include "ent_miniworld.h"
-#include "ent_skateshop.h"
-#include "ent_npc.h"
-#include "ent_camera.h"
-#include "world_map.h"
-#include "gui.h"
-#include "workshop.h"
-#include "audio.h"
-#include "player_render.h"
-#include "control_overlay.h"
-#include "client.h"
-
-struct skaterift_globals skaterift =
-{
- .time_rate = 1.0f,
- .hub_world = "maps/dev_hub",
-};
-
-void game_launch_opt(void)
-{
- const char *arg;
- if( (arg = vg_long_opt_arg( "world" )) )
- skaterift.hub_world = arg;
-}
-
-static void async_skaterift_player_start( void *payload, u32 size ){
- world_switch_instance(0);
-}
-
-static void skaterift_restore_state(void)
-{
- savedata_file sav;
- skaterift_read_main_savedata( &sav );
-
- vg_msg kvsav;
- vg_msg_init( &kvsav, sav.buf, sizeof(sav.buf) );
-
- u32 ach;
- vg_msg_getkvintg( &kvsav, "ach", k_vg_msg_u32, &ach, NULL );
- skaterift.achievements |= ach;
-
- u32 board_reg_id = time(NULL) % addon_count( k_addon_type_board, 0 ),
- player_reg_id = (time(NULL)+44) % addon_count( k_addon_type_player, 0 );
-
- vg_msg_cursor orig = kvsav.cur;
- if( vg_msg_seekframe( &kvsav, "player" ) ){
- addon_alias q;
-
- /* board */
- skaterift_read_addon_alias( &kvsav, "board", k_addon_type_board, &q );
- u32 reg_id = addon_match( &q );
- if( reg_id != 0xffffffff )
- board_reg_id = reg_id;
-
- /* playermodel */
- skaterift_read_addon_alias( &kvsav, "playermodel",
- k_addon_type_player, &q );
- reg_id = addon_match( &q );
- if( reg_id != 0xffffffff )
- player_reg_id = reg_id;
- }
-
- localplayer.board_view_slot =
- addon_cache_create_viewer( k_addon_type_board, board_reg_id );
- localplayer.playermodel_view_slot =
- addon_cache_create_viewer( k_addon_type_player, player_reg_id );
-
- kvsav.cur = orig;
-}
-
-static addon_reg *skaterift_mount_world_unloadable( const char *path, u32 ext ){
- addon_reg *reg = addon_mount_local_addon( path, k_addon_type_world, ".mdl" );
- if( !reg ) vg_fatal_error( "world not found\n" );
- reg->flags |= (ADDON_REG_HIDDEN | ext);
- return reg;
-}
-
-static void skaterift_load_world_content(void){
- /* hub world */
- addon_reg *hub = skaterift_mount_world_unloadable( skaterift.hub_world, 0 );
- skaterift_mount_world_unloadable( "maps/mp_spawn",
- ADDON_REG_CITY|ADDON_REG_PREMIUM );
- skaterift_mount_world_unloadable( "maps/mp_mtzero",
- ADDON_REG_MTZERO|ADDON_REG_PREMIUM );
- skaterift_mount_world_unloadable( "maps/dev_tutorial", 0 );
- skaterift_mount_world_unloadable( "maps/dev_flatworld", 0 );
- skaterift_mount_world_unloadable( "maps/mp_line1", ADDON_REG_PREMIUM );
-
- world_static.load_state = k_world_loader_load;
-
- struct world_load_args args = {
- .purpose = k_world_purpose_hub,
- .reg = hub
- };
- skaterift_world_load_thread( &args );
-}
-
-static void skaterift_load_player_content(void)
-{
- particle_alloc( &particles_grind, 300 );
- particle_alloc( &particles_env, 200 );
-
- player_load_animation_reference( "models/ch_none.mdl" );
- player_model_load( &localplayer.fallback_model, "models/ch_none.mdl" );
- player__bind();
- player_board_load( &localplayer.fallback_board, "models/board_none.mdl" );
-}
-
-void game_load(void)
-{
- vg_console_reg_cmd( "load_world", skaterift_load_world_command, NULL );
- vg_console_reg_var( "immobile", &localplayer.immobile, k_var_dtype_i32, 0 );
- vg_loader_step( menu_init, NULL );
-
- vg_loader_step( control_overlay_init, NULL );
- vg_loader_step( world_init, NULL );
- vg_loader_step( vehicle_init, NULL );
- vg_loader_step( gui_init, NULL );
-
- vg_loader_step( player_init, NULL );
- vg_loader_step( player_ragdoll_init, NULL );
- vg_loader_step( npc_init, NULL );
-
- /* content stuff */
- vg_loader_step( addon_system_init, NULL );
- vg_loader_step( workshop_init, NULL );
- vg_loader_step( skateshop_init, NULL );
- vg_loader_step( ent_tornado_init, NULL );
- vg_loader_step( skaterift_replay_init, NULL );
- vg_loader_step( skaterift_load_player_content, NULL );
-
- vg_bake_shaders();
- vg_loader_step( audio_init, NULL );
-
- vg_loader_step( skaterift_load_world_content, NULL );
- vg_async_call( async_skaterift_player_start, NULL, 0 );
- vg_async_stall();
-
- vg_console_load_autos();
-
- addon_mount_content_folder( k_addon_type_player,
- "playermodels", ".mdl" );
- addon_mount_content_folder( k_addon_type_board, "boards", ".mdl" );
- addon_mount_content_folder( k_addon_type_world, "maps", ".mdl" );
- addon_mount_workshop_items();
- vg_async_call( async_addon_reg_update, NULL, 0 );
- vg_async_stall();
-
- skaterift_restore_state();
- update_ach_models();
-
- vg_loader_step( NULL, skaterift_autosave_synchronous );
-}
-
-static void draw_origin_axis(void)
-{
- vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 1.0f, 0.0f, 0.0f }, 0xffff0000 );
- vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 1.0f, 0.0f }, 0xff00ff00 );
- vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 0.0f, 1.0f }, 0xff0000ff );
-}
-void skaterift_change_client_world_preupdate(void);
-
-/*
- * UPDATE LOOP
- * ---------------------------------------------------------------------------*/
-
-void vg_pre_update(void)
-{
- skaterift_preupdate_inputs();
-
- steam_update();
- skaterift_change_client_world_preupdate();
-
- if( !g_client.loaded ) return;
-
- draw_origin_axis();
- addon_system_pre_update();
- skateshop_world_preview_preupdate();
- network_update();
-
- /* time rate */
- f32 target = 1;
- if( skaterift.activity & k_skaterift_replay )
- target = 0;
-
- v3f listen_co;
- v3_copy( localplayer.rb.co, listen_co );
-
- if( skaterift.activity & k_skaterift_menu )
- {
- if( menu.bg_cam )
- {
- v3_copy( menu.bg_cam->transform.co, listen_co );
- }
- else target = 0;
- }
-
- vg_slewf( &skaterift.time_rate, target, vg.time_frame_delta * (1.0f/0.3f) );
- vg.time_rate = vg_smoothstepf( skaterift.time_rate );
-
- /* TODO: how can we compress this? */
- ent_miniworld_preupdate();
- world_entity_focus_preupdate();
-
- if( skaterift.activity != k_skaterift_menu )
- {
- player__pre_update();
- }
-
- skaterift_replay_pre_update();
- remote_sfx_pre_update();
- skateshop_world_preupdate( world_current_instance() );
-
- world_update( world_current_instance(), localplayer.rb.co );
- audio_ambient_sprites_update( world_current_instance(), listen_co );
- world_map_pre_update();
-}
-
-void vg_fixed_update(void)
-{
- if( !g_client.loaded ) return;
-
- world_routes_fixedupdate( world_current_instance() );
- player__update();
- vehicle_update_fixed();
-}
-
-void vg_post_update(void)
-{
- if( !g_client.loaded ) return;
-
- player__post_update();
-
- float dist;
- int sample_index;
- world_audio_sample_distances( localplayer.rb.co, &sample_index, &dist );
-
- audio_lock();
- vg_dsp.echo_distances[sample_index] = dist;
-
- v3f ears = { 1.0f,0.0f,0.0f };
- m3x3_mulv( g_render.cam.transform, ears, ears );
- v3_copy( ears, vg_audio.external_listener_ears );
- v3_copy( g_render.cam.transform[3], vg_audio.external_listener_pos );
-
- if( localplayer.gate_waiting ){
- m4x3_mulv( localplayer.gate_waiting->transport,
- vg_audio.external_listener_pos,
- vg_audio.external_listener_pos );
- }
-
- v3_copy( localplayer.rb.v, vg_audio.external_lister_velocity );
- audio_unlock();
-
- vehicle_update_post();
- skaterift_autosave_update();
-}
-
-/*
- * RENDERING
- * ---------------------------------------------------------------------------*/
-
-static void render_player_transparent(void)
-{
- if( (skaterift.activity == k_skaterift_menu) &&
- (menu.page == k_menu_page_main) &&
- (menu.main_index == k_menu_main_guide) )
- {
- return;
- }
-
- static vg_camera small_cam; /* DOES NOT NEED TO BE STATIC BUT MINGW
- SAIS OTHERWISE */
-
- m4x3_copy( g_render.cam.transform, small_cam.transform );
-
- small_cam.fov = g_render.cam.fov;
- small_cam.nearz = 0.05f;
- small_cam.farz = 60.0f;
-
- vg_camera_update_view( &small_cam );
- vg_camera_update_projection( &small_cam );
- vg_camera_finalize( &small_cam );
-
- /* Draw player to window buffer and blend background ontop */
- player__render( &small_cam );
-}
-
-static world_instance *get_view_world(void)
-{
- if( (skaterift.activity & k_skaterift_menu) &&
- (menu.page == k_menu_page_main) &&
- (menu.main_index == k_menu_main_guide) )
- {
- return &world_static.instances[0];
- }
-
- world_instance *view_world = world_current_instance();
- if( localplayer.gate_waiting &&
- (localplayer.gate_waiting->flags & k_ent_gate_nonlocal) ){
- view_world = &world_static.instances[world_static.active_instance ^ 0x1];
- }
-
- return view_world;
-}
-
-static void render_scene(void)
-{
- /* Draw world */
- glEnable( GL_DEPTH_TEST );
-
- for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ )
- {
- if( world_static.instances[i].status == k_world_status_loaded )
- {
- world_prerender( &world_static.instances[i] );
- }
- }
-
- if( menu_viewing_map() )
- {
- world_instance *world = world_current_instance();
- glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
-
- v3f bg;
- v3_muls( world->ub_lighting.g_daysky_colour,
- world->ub_lighting.g_day_phase -
- world->ub_lighting.g_sunset_phase*0.1f, bg );
-
- v3_muladds( bg, world->ub_lighting.g_sunset_colour,
- (1.0f-0.5f)*world->ub_lighting.g_sunset_phase, bg );
-
- v3_muladds( bg, world->ub_lighting.g_nightsky_colour,
- (1.0f-world->ub_lighting.g_day_phase), bg );
-
- glClearColor( bg[0], bg[1], bg[2], 0.0f );
- glClear( GL_COLOR_BUFFER_BIT );
- glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0,
- GL_COLOR_ATTACHMENT1 } );
-
- m4x3f identity;
- m4x3_identity( identity );
- render_world_override( world, world, identity, &g_render.cam,
- world_map.close_spawn,
- (v4f){world->tar_min, world->tar_max, 1.0f, 0.0f});
- render_world_routes( world, world, identity, &g_render.cam, 0, 1 );
- return;
- }
-
- world_instance *view_world = get_view_world();
- render_world( view_world, &g_render.cam, 0, 0, 1, 1 );
-
- particle_system_update( &particles_grind, vg.time_delta );
- //particle_system_debug( &particles_grind );
- particle_system_prerender( &particles_grind );
- particle_system_render( &particles_grind, &g_render.cam );
-
- ent_tornado_pre_update();
- particle_system_update( &particles_env, vg.time_delta );
- particle_system_prerender( &particles_env );
- particle_system_render( &particles_env, &g_render.cam );
-
- player_glide_render_effects( &g_render.cam );
-
- /*
- * render transition
- */
- if( global_miniworld.transition == 0 )
- return;
-
- world_instance *holdout_world = NULL;
- f32 t = 0.0f;
-
- if( global_miniworld.transition == 1 ){
- holdout_world = &world_static.instances[ k_world_purpose_hub ];
- t = global_miniworld.t;
- }
- else{
- holdout_world = &world_static.instances[ k_world_purpose_client ];
- t = 1.0f-global_miniworld.t;
- }
-
- if( holdout_world->status != k_world_status_loaded )
- return;
-
- t = vg_smoothstepf( t );
-
- glEnable( GL_STENCIL_TEST );
- glDisable( GL_DEPTH_TEST );
- glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
- glStencilFunc( GL_ALWAYS, 1, 0xFF );
- glStencilMask( 0xFF );
-
- shader_blit_transition_use();
- shader_blit_transition_uInverseRatio( (v2f){1.0f,1.0f} );
- shader_blit_transition_uT( -(sqrtf(2)+0.5f) * t );
-
- render_fsquad();
- render_world( holdout_world, &global_miniworld.cam, 1, 0, 1, 1 );
-}
-
-static void skaterift_composite_maincamera(void)
-{
- vg_camera_lerp( &localplayer.cam, &world_static.focus_cam,
- vg_smoothstepf(world_static.focus_strength), &g_render.cam );
-
- if( skaterift.activity == k_skaterift_replay )
- {
- if( player_replay.use_freecam )
- {
- freecam_preupdate();
- v3_copy( player_replay.replay_freecam.pos, g_render.cam.pos );
- v3_copy( player_replay.replay_freecam.angles, g_render.cam.angles );
- g_render.cam.fov = player_replay.replay_freecam.fov;
- }
- else
- {
- skaterift_get_replay_cam( &g_render.cam );
- }
- }
-
- g_render.cam.nearz = 0.1f;
- g_render.cam.farz = 2100.0f;
-
- if( (skaterift.activity == k_skaterift_menu) && menu.bg_cam )
- {
- ent_camera_unpack( menu.bg_cam, &g_render.cam );
- }
-
- if( menu_viewing_map() )
- {
- vg_camera_copy( &world_map.cam, &g_render.cam );
- g_render.cam.nearz = 4.0f;
- g_render.cam.farz = 3100.0f;
- }
-
- if( global_miniworld.transition ){
- f32 dt = vg.time_frame_delta / 2.0f,
- s = vg_signf( global_miniworld.transition );
- global_miniworld.t += s * dt;
-
- if( (global_miniworld.t > 1.0f) || (global_miniworld.t < 0.0f) ){
- global_miniworld.t = vg_clampf( global_miniworld.t, 0.0f, 1.0f );
- global_miniworld.transition = 0;
- }
- }
-
- vg_camera_update_transform( &g_render.cam );
- vg_camera_update_view( &g_render.cam );
- vg_camera_update_projection( &g_render.cam );
- vg_camera_finalize( &g_render.cam );
-}
-
-static void render_main_game(void)
-{
- if( skaterift.activity == k_skaterift_replay )
- {
- player__animate_from_replay( &player_replay.local );
- }
- else{
- player__animate();
- skaterift_record_frame( &player_replay.local,
- localplayer.deferred_frame_record );
- localplayer.deferred_frame_record = 0;
- }
- animate_remote_players();
- player__pre_render();
-
- skaterift_composite_maincamera();
-
- /* --------------------------------------------------------------------- */
- if( !menu_viewing_map() )
- {
- world_instance *world = world_current_instance();
- render_world_cubemaps( world );
-
- ent_gate *nlg = world->rendering_gate;
- if( nlg && (nlg->flags & k_ent_gate_nonlocal) )
- render_world_cubemaps( &world_static.instances[nlg->target] );
- }
-
- /* variable res target */
- vg_framebuffer_bind( g_render.fb_main, k_render_scale );
- glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT );
-
- render_scene();
- glEnable( GL_DEPTH_TEST );
-
- /* full res target */
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glViewport( 0,0, vg.window_x, vg.window_y );
-
- render_player_transparent(); /* needs to read the depth buffer before we fuck
- it up with the oblique rendering inside the
- portals */
-
- /* continue with variable rate */
- if( !global_miniworld.transition && !menu_viewing_map() )
- {
- vg_framebuffer_bind( g_render.fb_main, k_render_scale );
- render_world_gates( get_view_world(), &g_render.cam );
- }
-
- /* composite */
-
- if( (skaterift.activity == k_skaterift_menu) && menu.bg_blur )
- v2_muls( (v2f){ 0.04f, 0.001f }, 1.0f-skaterift.time_rate,
- g_render.blur_override );
- else
- v2_zero( g_render.blur_override );
- postprocess_to_screen( g_render.fb_main );
-
- skaterift_replay_post_render();
- control_overlay_render();
-}
-
-void vg_render(void)
-{
- if( !g_client.loaded )
- {
- vg_loader_render();
- return;
- }
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
-
- glViewport( 0,0, vg.window_x, vg.window_y );
- glDisable( GL_DEPTH_TEST );
- glDisable( GL_BLEND );
-
- glClearColor( 1.0f, 0.0f, 0.0f, 0.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
-
- render_main_game();
- m4x4_copy( g_render.cam.mtx.pv, vg.pv );
-
- /* Other shite */
- glDisable(GL_BLEND);
- glDisable(GL_DEPTH_TEST);
- vg_lines_drawall();
- glViewport( 0,0, vg.window_x, vg.window_y );
-
- gui_render_icons();
-}
-
-void vg_gui( ui_context *ctx )
-{
- if( !g_client.loaded ) return;
-
- gui_draw( ctx );
-
- if( k_light_editor )
- imgui_world_light_edit( ctx, world_current_instance() );
-
- vg_ui.tex_bg = g_render.fb_main->attachments[0].id;
- vg_framebuffer_inverse_ratio( g_render.fb_main, vg_ui.bg_inverse_ratio );
-
- menu_gui( ctx );
- player__im_gui( ctx );
- world_instance *world = world_current_instance();
-
- world_routes_imgui( ctx, world );
- skaterift_replay_imgui( ctx );
- workshop_form_gui( ctx );
- remote_player_network_imgui( ctx, vg.pv );
-
- if( menu_viewing_map() )
- {
- remote_players_imgui_world( ctx, world_current_instance(),
- vg.pv, 2000.0f, 0 );
- remote_players_imgui_lobby( ctx );
- }
- else
- {
- remote_players_chat_imgui( ctx ); /* TODO: conditional */
- remote_players_imgui_world( ctx, world_current_instance(),
- vg.pv, 100.0f, 1 );
- }
-}
-
-#include "addon.c"
-#include "addon_types.c"
-#include "audio.c"
-#include "ent_challenge.c"
-#include "ent_glider.c"
-#include "entity.c"
-#include "ent_miniworld.c"
-#include "ent_objective.c"
-#include "ent_region.c"
-#include "ent_relay.c"
-#include "ent_route.c"
-#include "ent_skateshop.c"
-#include "ent_tornado.c"
-#include "ent_traffic.c"
-#include "freecam.c"
-#include "menu.c"
-#include "network.c"
-#include "particle.c"
-#include "player_basic_info.c"
-#include "player.c"
-#include "player_common.c"
-#include "player_dead.c"
-#include "player_drive.c"
-#include "player_effects.c"
-#include "player_glide.c"
-#include "player_ragdoll.c"
-#include "player_remote.c"
-#include "player_render.c"
-#include "player_replay.c"
-#include "player_skate.c"
-#include "player_walk.c"
-#include "render.c"
-#include "save.c"
-#include "scene.c"
-#include "steam.c"
-#include "trail.c"
-#include "vehicle.c"
-#include "workshop.c"
-#include "world_audio.c"
-#include "world.c"
-#include "world_entity.c"
-#include "world_gate.c"
-#include "world_gen.c"
-#include "world_load.c"
-#include "world_map.c"
-#include "world_physics.c"
-#include "world_render.c"
-#include "world_routes.c"
-#include "world_routes_ui.c"
-#include "world_sfd.c"
-#include "world_volumes.c"
-#include "world_water.c"
-#include "ent_npc.c"
-#include "model.c"
-#include "control_overlay.c"
-#include "ent_camera.c"
+++ /dev/null
-#pragma once
-#define SKATERIFT
-#define SKATERIFT_APPID 2103940
-
-#include "vg/vg_engine.h"
-#include "vg/vg_camera.h"
-
-enum skaterift_rt
-{
- k_skaterift_rt_workshop_preview,
- k_skaterift_rt_server_status,
- k_skaterift_rt_max
-};
-
-struct skaterift_globals
-{
- f32 time_rate;
-
- enum skaterift_activity {
- k_skaterift_default = 0x00,
- k_skaterift_replay = 0x01,
- k_skaterift_ent_focus = 0x02,
- k_skaterift_menu = 0x04,
- }
- activity;
- GLuint rt_textures[k_skaterift_rt_max];
-
- u32 achievements;
- int demo_mode;
-
- const char *hub_world;
-}
-extern skaterift;
+++ /dev/null
-#define QOI_IMPLEMENTATION
-#include "vg/submodules/qoi/qoi.h"
-#include "vg/vg_platform.h"
-#include "vg/vg_m.h"
-
-u8 *qoi_encode_rgbaf32( f32 *data, u32 width, u32 height, int *length )
-{
- u8 *buf = (u8 *)data;
- for( u32 i=0; i<width*height*4; i ++ )
- {
- buf[i] = vg_clampf( data[i] * 255.0f, 0.0f, 255.0f );
- }
-
- qoi_desc desc =
- {
- .channels=4,
- .colorspace=0,
- .width=width,
- .height=height
- };
-
- return qoi_encode( buf, &desc, length );
-}
-
-void qoi_free( u8 *ptr )
-{
- free( ptr );
-}
-
-#include "vg/vg_tool.h"
-#include "vg/vg_tool.c"
+++ /dev/null
-/*
- * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-#include "vg/vg_lines.h"
-#include "model.h"
-
-struct skeleton
-{
- struct skeleton_bone
- {
- v3f co, end;
- u32 parent;
-
- u32 flags;
- int defer;
-
- mdl_keyframe kf;
- mdl_bone *orig_bone;
-
- u32 collider;
- boxf hitbox;
- const char *name;
- }
- *bones;
- u32 bone_count;
-
- struct skeleton_anim
- {
- const char *name;
- u32 length;
-
- float rate;
- mdl_keyframe *anim_data;
- }
- *anims;
- u32 anim_count;
-
-#if 0
- m4x3f *final_mtx;
-#endif
-
- struct skeleton_ik
- {
- u32 lower, upper, target, pole;
- m3x3f ia, ib;
- }
- *ik;
- u32 ik_count;
-
- u32
- collider_count,
- bindable_count;
-};
-
-static u32 skeleton_bone_id( struct skeleton *skele, const char *name )
-{
- for( u32 i=1; i<skele->bone_count; i++ ){
- if( !strcmp( skele->bones[i].name, name ))
- return i;
- }
-
- vg_error( "skeleton_bone_id( *, \"%s\" );\n", name );
- vg_fatal_error( "Bone does not exist\n" );
-
- return 0;
-}
-
-static void keyframe_copy_pose( mdl_keyframe *kfa, mdl_keyframe *kfb,
- int num )
-{
- for( int i=0; i<num; i++ )
- kfb[i] = kfa[i];
-}
-
-
-/* apply a rotation from the perspective of root */
-static void keyframe_rotate_around( mdl_keyframe *kf,
- v3f origin, v3f offset, v4f q )
-{
- v3f v0, co;
- v3_add( kf->co, offset, co );
- v3_sub( co, origin, v0 );
- q_mulv( q, v0, v0 );
- v3_add( v0, origin, co );
- v3_sub( co, offset, kf->co );
-
- q_mul( q, kf->q, kf->q );
- q_normalize( kf->q );
-}
-
-static void keyframe_lerp( mdl_keyframe *kfa, mdl_keyframe *kfb, f32 t,
- mdl_keyframe *kfd ){
- v3_lerp( kfa->co, kfb->co, t, kfd->co );
- q_nlerp( kfa->q, kfb->q, t, kfd->q );
- v3_lerp( kfa->s, kfb->s, t, kfd->s );
-}
-
-/*
- * Lerp between two sets of keyframes and store in dest. Rotations use Nlerp.
- */
-static void keyframe_lerp_pose( mdl_keyframe *kfa, mdl_keyframe *kfb,
- float t, mdl_keyframe *kfd, int count ){
- if( t <= 0.0001f ){
- keyframe_copy_pose( kfa, kfd, count );
- return;
- }
- else if( t >= 0.9999f ){
- keyframe_copy_pose( kfb, kfd, count );
- return;
- }
-
- for( int i=0; i<count; i++ )
- keyframe_lerp( kfa+i, kfb+i, t, kfd+i );
-}
-
-static
-void skeleton_lerp_pose( struct skeleton *skele,
- mdl_keyframe *kfa, mdl_keyframe *kfb, float t,
- mdl_keyframe *kfd )
-{
- keyframe_lerp_pose( kfa, kfb, t, kfd, skele->bone_count-1 );
-}
-
-static void skeleton_copy_pose( struct skeleton *skele,
- mdl_keyframe *kfa, mdl_keyframe *kfd )
-{
- keyframe_copy_pose( kfa, kfd, skele->bone_count-1 );
-}
-
-/*
- * Sample animation between 2 closest frames using time value. Output is a
- * keyframe buffer that is allocated with an appropriate size
- */
-static void skeleton_sample_anim( struct skeleton *skele,
- struct skeleton_anim *anim,
- float time,
- mdl_keyframe *output )
-{
- f32 animtime = fmodf( time*anim->rate, anim->length ),
- animframe = floorf( animtime ),
- t = animtime - animframe;
-
- u32 frame = (u32)animframe % anim->length,
- next = (frame+1) % anim->length;
-
- mdl_keyframe *base = anim->anim_data + (skele->bone_count-1)*frame,
- *nbase = anim->anim_data + (skele->bone_count-1)*next;
-
- skeleton_lerp_pose( skele, base, nbase, t, output );
-}
-
-static int skeleton_sample_anim_clamped( struct skeleton *skele,
- struct skeleton_anim *anim,
- float time,
- mdl_keyframe *output )
-{
- float end = (float)(anim->length-1) / anim->rate;
- skeleton_sample_anim( skele, anim, vg_minf( end, time ), output );
-
- if( time > end )
- return 0;
- else
- return 1;
-}
-
-typedef enum anim_apply
-{
- k_anim_apply_always,
- k_anim_apply_defer_ik,
- k_anim_apply_deffered_only,
- k_anim_apply_absolute
-}
-anim_apply;
-
-static
-int should_apply_bone( struct skeleton *skele, u32 id, anim_apply type )
-{
- struct skeleton_bone *sb = &skele->bones[ id ],
- *sp = &skele->bones[ sb->parent ];
-
- if( type == k_anim_apply_defer_ik ){
- if( ((sp->flags & k_bone_flag_ik) && !(sb->flags & k_bone_flag_ik))
- || sp->defer )
- {
- sb->defer = 1;
- return 0;
- }
- else{
- sb->defer = 0;
- return 1;
- }
- }
- else if( type == k_anim_apply_deffered_only ){
- if( sb->defer )
- return 1;
- else
- return 0;
- }
-
- return 1;
-}
-
-/*
- * Apply block of keyframes to skeletons final pose
- */
-static void skeleton_apply_pose( struct skeleton *skele, mdl_keyframe *pose,
- anim_apply passtype, m4x3f *final_mtx ){
- if( passtype == k_anim_apply_absolute ){
- for( u32 i=1; i<skele->bone_count; i++ ){
- mdl_keyframe *kf = &pose[i-1];
-
- v3f *posemtx = final_mtx[i];
-
- q_m3x3( kf->q, posemtx );
- m3x3_scale( posemtx, kf->s );
- v3_copy( kf->co, posemtx[3] );
- }
- return;
- }
-
- m4x3_identity( final_mtx[0] );
- skele->bones[0].defer = 0;
- skele->bones[0].flags &= ~k_bone_flag_ik;
-
- for( u32 i=1; i<skele->bone_count; i++ ){
- struct skeleton_bone *sb = &skele->bones[i],
- *sp = &skele->bones[sb->parent];
-
- if( !should_apply_bone( skele, i, passtype ) )
- continue;
-
- sb->defer = 0;
-
- /* process pose */
- m4x3f posemtx;
-
- v3f temp_delta;
- v3_sub( skele->bones[i].co, skele->bones[sb->parent].co, temp_delta );
-
- /* pose matrix */
- mdl_keyframe *kf = &pose[i-1];
- q_m3x3( kf->q, posemtx );
- m3x3_scale( posemtx, kf->s );
- v3_copy( kf->co, posemtx[3] );
- v3_add( temp_delta, posemtx[3], posemtx[3] );
-
- /* final matrix */
- m4x3_mul( final_mtx[ sb->parent ], posemtx, final_mtx[i] );
- }
-}
-
-/*
- * Take the final matrices and decompose it into an absolute positioned anim
- */
-static void skeleton_decompose_mtx_absolute( struct skeleton *skele,
- mdl_keyframe *anim,
- m4x3f *final_mtx ){
- for( u32 i=1; i<skele->bone_count; i++ ){
- struct skeleton_bone *sb = &skele->bones[i];
- mdl_keyframe *kf = &anim[i-1];
- m4x3_decompose( final_mtx[i], kf->co, kf->q, kf->s );
- }
-}
-
-/*
- * creates the reference inverse matrix for an IK bone, as it has an initial
- * intrisic rotation based on the direction that the IK is setup..
- */
-static void skeleton_inverse_for_ik( struct skeleton *skele,
- v3f ivaxis,
- u32 id, m3x3f inverse )
-{
- v3_copy( ivaxis, inverse[0] );
- v3_copy( skele->bones[id].end, inverse[1] );
- v3_normalize( inverse[1] );
- v3_cross( inverse[0], inverse[1], inverse[2] );
- m3x3_transpose( inverse, inverse );
-}
-
-/*
- * Creates inverse rotation matrices which the IK system uses.
- */
-static void skeleton_create_inverses( struct skeleton *skele )
-{
- /* IK: inverse 'plane-bone space' axis '(^axis,^bone,...)[base] */
- for( u32 i=0; i<skele->ik_count; i++ ){
- struct skeleton_ik *ik = &skele->ik[i];
-
- m4x3f inverse;
- v3f iv0, iv1, ivaxis;
- v3_sub( skele->bones[ik->target].co, skele->bones[ik->lower].co, iv0 );
- v3_sub( skele->bones[ik->pole].co, skele->bones[ik->lower].co, iv1 );
- v3_cross( iv0, iv1, ivaxis );
- v3_normalize( ivaxis );
-
- skeleton_inverse_for_ik( skele, ivaxis, ik->lower, ik->ia );
- skeleton_inverse_for_ik( skele, ivaxis, ik->upper, ik->ib );
- }
-}
-
-/*
- * Apply a model matrix to all bones, should be done last
- */
-static
-void skeleton_apply_transform( struct skeleton *skele, m4x3f transform,
- m4x3f *final_mtx )
-{
- for( u32 i=0; i<skele->bone_count; i++ ){
- struct skeleton_bone *sb = &skele->bones[i];
- m4x3_mul( transform, final_mtx[i], final_mtx[i] );
- }
-}
-
-/*
- * Apply an inverse matrix to all bones which maps vertices from bind space into
- * bone relative positions
- */
-static void skeleton_apply_inverses( struct skeleton *skele, m4x3f *final_mtx ){
- for( u32 i=0; i<skele->bone_count; i++ ){
- struct skeleton_bone *sb = &skele->bones[i];
- m4x3f inverse;
- m3x3_identity( inverse );
- v3_negate( sb->co, inverse[3] );
-
- m4x3_mul( final_mtx[i], inverse, final_mtx[i] );
- }
-}
-
-/*
- * Apply all IK modifiers (2 bone ik reference from blender is supported)
- */
-static void skeleton_apply_ik_pass( struct skeleton *skele, m4x3f *final_mtx ){
- for( u32 i=0; i<skele->ik_count; i++ ){
- struct skeleton_ik *ik = &skele->ik[i];
-
- v3f v0, /* base -> target */
- v1, /* base -> pole */
- vaxis;
-
- v3f co_base,
- co_target,
- co_pole;
-
- v3_copy( final_mtx[ik->lower][3], co_base );
- v3_copy( final_mtx[ik->target][3], co_target );
- v3_copy( final_mtx[ik->pole][3], co_pole );
-
- v3_sub( co_target, co_base, v0 );
- v3_sub( co_pole, co_base, v1 );
- v3_cross( v0, v1, vaxis );
- v3_normalize( vaxis );
- v3_normalize( v0 );
- v3_cross( vaxis, v0, v1 );
-
- /* localize problem into [x:v0,y:v1] 2d plane */
- v2f base = { v3_dot( v0, co_base ), v3_dot( v1, co_base ) },
- end = { v3_dot( v0, co_target ), v3_dot( v1, co_target ) },
- knee;
-
- /* Compute angles (basic trig)*/
- v2f delta;
- v2_sub( end, base, delta );
-
- float
- l1 = v3_length( skele->bones[ik->lower].end ),
- l2 = v3_length( skele->bones[ik->upper].end ),
- d = vg_clampf( v2_length(delta), fabsf(l1 - l2), l1+l2-0.00001f ),
- c = acosf( (l1*l1 + d*d - l2*l2) / (2.0f*l1*d) ),
- rot = atan2f( delta[1], delta[0] ) + c - VG_PIf/2.0f;
-
- knee[0] = sinf(-rot) * l1;
- knee[1] = cosf(-rot) * l1;
-
- m4x3_identity( final_mtx[ik->lower] );
- m4x3_identity( final_mtx[ik->upper] );
-
- /* create rotation matrix */
- v3f co_knee;
- v3_muladds( co_base, v0, knee[0], co_knee );
- v3_muladds( co_knee, v1, knee[1], co_knee );
- vg_line( co_base, co_knee, 0xff00ff00 );
-
- m4x3f transform;
- v3_copy( vaxis, transform[0] );
- v3_muls( v0, knee[0], transform[1] );
- v3_muladds( transform[1], v1, knee[1], transform[1] );
- v3_normalize( transform[1] );
- v3_cross( transform[0], transform[1], transform[2] );
- v3_copy( co_base, transform[3] );
-
- m3x3_mul( transform, ik->ia, transform );
- m4x3_copy( transform, final_mtx[ik->lower] );
-
- /* upper/knee bone */
- v3_copy( vaxis, transform[0] );
- v3_sub( co_target, co_knee, transform[1] );
- v3_normalize( transform[1] );
- v3_cross( transform[0], transform[1], transform[2] );
- v3_copy( co_knee, transform[3] );
-
- m3x3_mul( transform, ik->ib, transform );
- m4x3_copy( transform, final_mtx[ik->upper] );
- }
-}
-
-/*
- * Applies the typical operations that you want for an IK rig:
- * Pose, IK, Pose(deferred), Inverses, Transform
- */
-static void skeleton_apply_standard( struct skeleton *skele, mdl_keyframe *pose,
- m4x3f transform, m4x3f *final_mtx ){
- skeleton_apply_pose( skele, pose, k_anim_apply_defer_ik, final_mtx );
- skeleton_apply_ik_pass( skele, final_mtx );
- skeleton_apply_pose( skele, pose, k_anim_apply_deffered_only, final_mtx );
- skeleton_apply_inverses( skele, final_mtx );
- skeleton_apply_transform( skele, transform, final_mtx );
-}
-
-/*
- * Get an animation by name
- */
-static struct skeleton_anim *skeleton_get_anim( struct skeleton *skele,
- const char *name ){
- for( u32 i=0; i<skele->anim_count; i++ ){
- struct skeleton_anim *anim = &skele->anims[i];
-
- if( !strcmp( anim->name, name ) )
- return anim;
- }
-
- vg_error( "skeleton_get_anim( *, \"%s\" )\n", name );
- vg_fatal_error( "Invalid animation name\n" );
-
- return NULL;
-}
-
-static void skeleton_alloc_from( struct skeleton *skele,
- void *lin_alloc,
- mdl_context *mdl,
- mdl_armature *armature ){
- skele->bone_count = armature->bone_count+1;
- skele->anim_count = armature->anim_count;
- skele->ik_count = 0;
- skele->collider_count = 0;
-
- for( u32 i=0; i<armature->bone_count; i++ ){
- mdl_bone *bone = mdl_arritm( &mdl->bones, armature->bone_start+i );
-
- if( bone->flags & k_bone_flag_ik )
- skele->ik_count ++;
-
- if( bone->collider )
- skele->collider_count ++;
- }
-
- u32 bone_size = sizeof(struct skeleton_bone) * skele->bone_count,
- ik_size = sizeof(struct skeleton_ik) * skele->ik_count,
- mtx_size = sizeof(m4x3f) * skele->bone_count,
- anim_size = sizeof(struct skeleton_anim) * skele->anim_count;
-
- skele->bones = vg_linear_alloc( lin_alloc, bone_size );
- skele->ik = vg_linear_alloc( lin_alloc, ik_size );
- //skele->final_mtx = vg_linear_alloc( lin_alloc, mtx_size );
- skele->anims = vg_linear_alloc( lin_alloc, anim_size );
-
- memset( skele->bones, 0, bone_size );
- memset( skele->ik, 0, ik_size );
- //memset( skele->final_mtx, 0, mtx_size );
- memset( skele->anims, 0, anim_size );
-}
-
-static void skeleton_fatal_err(void){
- vg_fatal_error( "Skeleton setup failed" );
-}
-
-/* Setup an animated skeleton from model. mdl's metadata should stick around */
-static void skeleton_setup( struct skeleton *skele,
- void *lin_alloc, mdl_context *mdl ){
- u32 ik_count = 0, collider_count = 0;
- skele->bone_count = 0;
- skele->bones = NULL;
- //skele->final_mtx = NULL;
- skele->anims = NULL;
-
- if( !mdl->armatures.count ){
- vg_error( "No skeleton in model\n" );
- skeleton_fatal_err();
- }
-
- mdl_armature *armature = mdl_arritm( &mdl->armatures, 0 );
- skeleton_alloc_from( skele, lin_alloc, mdl, armature );
-
- for( u32 i=0; i<armature->bone_count; i++ ){
- mdl_bone *bone = mdl_arritm( &mdl->bones, armature->bone_start+i );
- struct skeleton_bone *sb = &skele->bones[i+1];
-
- v3_copy( bone->co, sb->co );
- v3_copy( bone->end, sb->end );
-
- sb->parent = bone->parent;
- sb->name = mdl_pstr( mdl, bone->pstr_name );
- sb->flags = bone->flags;
- sb->collider = bone->collider;
- sb->orig_bone = bone;
-
- if( sb->flags & k_bone_flag_ik ){
- skele->bones[ sb->parent ].flags |= k_bone_flag_ik;
-
- if( ik_count == skele->ik_count ){
- vg_error( "Too many ik bones, corrupt model file\n" );
- skeleton_fatal_err();
- }
-
- struct skeleton_ik *ik = &skele->ik[ ik_count ++ ];
- ik->upper = i+1;
- ik->lower = bone->parent;
- ik->target = bone->ik_target;
- ik->pole = bone->ik_pole;
- }
-
- box_copy( bone->hitbox, sb->hitbox );
-
- if( bone->collider ){
- if( collider_count == skele->collider_count ){
- vg_error( "Too many collider bones\n" );
- skeleton_fatal_err();
- }
-
- collider_count ++;
- }
- }
-
- /* fill in implicit root bone */
- v3_zero( skele->bones[0].co );
- v3_copy( (v3f){0.0f,1.0f,0.0f}, skele->bones[0].end );
- skele->bones[0].parent = 0xffffffff;
- skele->bones[0].flags = 0;
- skele->bones[0].name = "[root]";
-
- /* process animation quick refs */
- for( u32 i=0; i<skele->anim_count; i++ ){
- mdl_animation *anim =
- mdl_arritm( &mdl->animations, armature->anim_start+i );
-
- skele->anims[i].rate = anim->rate;
- skele->anims[i].length = anim->length;
- skele->anims[i].name = mdl_pstr(mdl, anim->pstr_name);
- skele->anims[i].anim_data =
- mdl_arritm( &mdl->keyframes, anim->offset );
-
- vg_info( "animation[ %f, %u ] '%s'\n", anim->rate,
- anim->length,
- skele->anims[i].name );
- }
-
- skeleton_create_inverses( skele );
- vg_success( "Loaded skeleton with %u bones\n", skele->bone_count );
- vg_success( " %u colliders\n", skele->collider_count );
-}
-
-static void skeleton_debug( struct skeleton *skele, m4x3f *final_mtx ){
- for( u32 i=1; i<skele->bone_count; i ++ ){
- struct skeleton_bone *sb = &skele->bones[i];
-
- v3f p0, p1;
- v3_copy( sb->co, p0 );
- v3_add( p0, sb->end, p1 );
-
- m4x3_mulv( final_mtx[i], p0, p0 );
- m4x3_mulv( final_mtx[i], p1, p1 );
-
- if( sb->flags & k_bone_flag_deform ){
- if( sb->flags & k_bone_flag_ik ){
- vg_line( p0, p1, 0xff0000ff );
- }
- else{
- vg_line( p0, p1, 0xffcccccc );
- }
- }
- else
- vg_line( p0, p1, 0xff00ffff );
- }
-}
--- /dev/null
+#include "vg/vg_engine.h"
+#include "vg/vg_io.h"
+#include "vg/vg_loader.h"
+#include "addon.h"
+#include "addon_types.h"
+#include "vg/vg_msg.h"
+#include "steam.h"
+#include "workshop.h"
+#include <string.h>
+
+struct addon_system addon_system;
+
+u32 addon_count( enum addon_type type, u32 ignoreflags )
+{
+ if( ignoreflags ){
+ u32 typecount = 0, count = 0;
+ for( u32 i=0; typecount<addon_count( type, 0 ); i++ ){
+ addon_reg *reg = &addon_system.registry[i];
+ if( reg->alias.type == type ){
+ typecount ++;
+
+ if( reg->flags & ignoreflags )
+ continue;
+
+ count ++;
+ }
+ }
+
+ return count;
+ }
+ else
+ return addon_system.registry_type_counts[ type ];
+}
+
+
+/* these kind of suck, oh well. */
+addon_reg *get_addon_from_index( enum addon_type type, u32 index,
+ u32 ignoreflags )
+{
+ u32 typecount = 0, count = 0;
+ for( u32 i=0; typecount<addon_count(type,0); i++ ){
+ addon_reg *reg = &addon_system.registry[i];
+ if( reg->alias.type == type ){
+ typecount ++;
+
+ if( reg->flags & ignoreflags )
+ continue;
+
+ if( index == count )
+ return reg;
+
+ count ++;
+ }
+ }
+
+ return NULL;
+}
+
+u32 get_index_from_addon( enum addon_type type, addon_reg *a )
+{
+ u32 count = 0;
+ for( u32 i=0; count<addon_system.registry_type_counts[type]; i++ ){
+ addon_reg *reg = &addon_system.registry[i];
+ if( reg->alias.type == type ){
+ if( reg == a )
+ return count;
+
+ count ++;
+ }
+ }
+
+ return 0xffffffff;
+}
+
+u32 addon_match( addon_alias *alias )
+{
+ if( alias->type == k_addon_type_none ) return 0xffffffff;
+
+ u32 foldername_djb2 = 0;
+ if( !alias->workshop_id )
+ foldername_djb2 = vg_strdjb2( alias->foldername );
+
+ u32 count = 0;
+ for( u32 i=0; count<addon_system.registry_type_counts[alias->type]; i++ ){
+ addon_reg *reg = &addon_system.registry[i];
+ if( reg->alias.type == alias->type ){
+
+ if( alias->workshop_id ){
+ if( alias->workshop_id == reg->alias.workshop_id )
+ return count;
+ }
+ else{
+ if( reg->foldername_hash == foldername_djb2 ){
+ if( !strcmp( reg->alias.foldername, alias->foldername ) ){
+ return count;
+ }
+ }
+ }
+
+ count ++;
+ }
+ }
+
+ return 0xffffffff;
+}
+
+/*
+ * Create a string version of addon alias in buf
+ */
+void addon_alias_uid( addon_alias *alias, char buf[ADDON_UID_MAX] )
+{
+ if( alias->workshop_id ){
+ snprintf( buf, 128, "sr%03d-steam-"PRINTF_U64,
+ alias->type, alias->workshop_id );
+ }
+ else {
+ snprintf( buf, 128, "sr%03d-local-%s",
+ alias->type, alias->foldername );
+ }
+}
+
+/*
+ * equality check
+ */
+int addon_alias_eq( addon_alias *a, addon_alias *b )
+{
+ if( a->type == b->type ){
+ if( a->workshop_id == b->workshop_id ){
+ if( a->workshop_id )
+ return 1;
+ else
+ return !strcmp( a->foldername, b->foldername );
+ }
+ else
+ return 0;
+ }
+ else return 0;
+}
+
+/*
+ * make alias represent NULL.
+ */
+void invalidate_addon_alias( addon_alias *alias )
+{
+ alias->type = k_addon_type_none;
+ alias->workshop_id = 0;
+ alias->foldername[0] = '\0';
+}
+
+/*
+ * parse uid to alias. returns 1 if successful
+ */
+int addon_uid_to_alias( const char *uid, addon_alias *alias )
+{
+/* 1
+ * 01234567890123
+ * sr&&&-@@@@@-#*
+ * | | |
+ * type | id
+ * |
+ * location
+ */
+ if( strlen(uid) < 13 ){
+ invalidate_addon_alias( alias );
+ return 0;
+ }
+ if( !((uid[0] == 's') && (uid[1] == 'r')) ){
+ invalidate_addon_alias( alias );
+ return 0;
+ }
+
+ char type[4];
+ memcpy( type, uid+2, 3 );
+ type[3] = '\0';
+ alias->type = atoi(type);
+
+ char location[6];
+ memcpy( location, uid+6, 5 );
+ location[5] = '\0';
+
+ if( !strcmp(location,"steam") )
+ alias->workshop_id = atoll( uid+12 );
+ else if( !strcmp(location,"local") ){
+ alias->workshop_id = 0;
+ vg_strncpy( uid+12, alias->foldername, 64, k_strncpy_always_add_null );
+ }
+ else{
+ invalidate_addon_alias( alias );
+ return 0;
+ }
+
+ return 1;
+}
+
+void addon_system_init( void )
+{
+ u32 reg_size = sizeof(addon_reg)*ADDON_MOUNTED_MAX;
+ addon_system.registry = vg_linear_alloc( vg_mem.rtmemory, reg_size );
+
+ for( u32 type=0; type<k_addon_type_max; type++ ){
+ struct addon_type_info *inf = &addon_type_infos[type];
+ struct addon_cache *cache = &addon_system.cache[type];
+
+ if( inf->cache_count ){
+ /* create the allocations pool */
+ u32 alloc_size = sizeof(struct addon_cache_entry)*inf->cache_count;
+ cache->allocs = vg_linear_alloc( vg_mem.rtmemory, alloc_size );
+ memset( cache->allocs, 0, alloc_size );
+
+ cache->pool.buffer = cache->allocs;
+ cache->pool.count = inf->cache_count;
+ cache->pool.stride = sizeof( struct addon_cache_entry );
+ cache->pool.offset = offsetof( struct addon_cache_entry, poolnode );
+ vg_pool_init( &cache->pool );
+
+ /* create the real memory */
+ u32 cache_size = inf->cache_stride*inf->cache_count;
+ cache->items = vg_linear_alloc( vg_mem.rtmemory, cache_size );
+ cache->stride = inf->cache_stride;
+ memset( cache->items, 0, cache_size );
+
+ for( i32 j=0; j<inf->cache_count; j++ ){
+ struct addon_cache_entry *alloc = &cache->allocs[j];
+ alloc->reg_ptr = NULL;
+ alloc->reg_index = 0xffffffff;
+ }
+ }
+ }
+}
+
+/*
+ * Scanning routines
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * Reciever for scan completion. copies the registry counts back into main fred
+ */
+void async_addon_reg_update( void *data, u32 size )
+{
+ vg_info( "Registry update notify\n" );
+
+ for( u32 i=0; i<k_addon_type_max; i++ ){
+ addon_system.registry_type_counts[i] = 0;
+ }
+
+ for( u32 i=0; i<addon_system.registry_count; i++ ){
+ enum addon_type type = addon_system.registry[i].alias.type;
+ addon_system.registry_type_counts[ type ] ++;
+ }
+}
+
+static void addon_set_foldername( addon_reg *reg, const char name[64] ){
+ vg_strncpy( name, reg->alias.foldername, 64, k_strncpy_always_add_null );
+ reg->foldername_hash = vg_strdjb2( reg->alias.foldername );
+}
+
+/*
+ * Create a new registry
+ */
+static addon_reg *addon_alloc_reg( PublishedFileId_t workshop_id,
+ enum addon_type type ){
+ if( addon_system.registry_count == ADDON_MOUNTED_MAX ){
+ vg_error( "You have too many addons installed!\n" );
+ return NULL;
+ }
+
+ addon_reg *reg = &addon_system.registry[ addon_system.registry_count ];
+ reg->flags = 0;
+ reg->metadata_len = 0;
+ reg->cache_id = 0;
+ reg->state = k_addon_state_indexed;
+ reg->alias.workshop_id = workshop_id;
+ reg->alias.foldername[0] = '\0';
+ reg->alias.type = type;
+
+ if( workshop_id ){
+ char foldername[64];
+ snprintf( foldername, 64, PRINTF_U64, workshop_id );
+ addon_set_foldername( reg, foldername );
+ }
+ return reg;
+}
+
+/*
+ * If the addon.inf exists int the folder, load into the reg
+ */
+static int addon_try_load_metadata( addon_reg *reg, vg_str folder_path ){
+ vg_str meta_path = folder_path;
+ vg_strcat( &meta_path, "/addon.inf" );
+ if( !vg_strgood( &meta_path ) ){
+ vg_error( "The metadata path is too long\n" );
+ return 0;
+ }
+
+ FILE *fp = fopen( meta_path.buffer, "rb" );
+ if( !fp ){
+ vg_error( "Could not open the '%s'\n", meta_path.buffer );
+ return 0;
+ }
+
+ reg->metadata_len = fread( reg->metadata, 1, 512, fp );
+ if( reg->metadata_len != 512 ){
+ if( !feof(fp) ){
+ fclose(fp);
+ vg_error( "unknown error codition" );
+ reg->metadata_len = 0;
+ return 0;
+ }
+ }
+ fclose(fp);
+ return 1;
+}
+
+static void addon_print_info( addon_reg *reg ){
+ vg_info( "addon_reg #%u{\n", addon_system.registry_count );
+ vg_info( " type: %d\n", reg->alias.type );
+ vg_info( " workshop_id: " PRINTF_U64 "\n", reg->alias.workshop_id );
+ vg_info( " folder: [%u]%s\n", reg->foldername_hash, reg->alias.foldername );
+ vg_info( " metadata_len: %u\n", reg->metadata_len );
+ vg_info( " cache_id: %hu\n", reg->cache_id );
+ vg_info( "}\n" );
+}
+
+static void addon_mount_finish( addon_reg *reg ){
+#if 0
+ addon_print_info( reg );
+#endif
+ addon_system.registry_count ++;
+}
+
+/*
+ * Mount a fully packaged addon, one that certainly has a addon.inf
+ */
+static addon_reg *addon_mount_workshop_folder( PublishedFileId_t workshop_id,
+ vg_str folder_path )
+{
+ addon_reg *reg = addon_alloc_reg( workshop_id, k_addon_type_none );
+ if( !reg ) return NULL;
+
+ if( !addon_try_load_metadata( reg, folder_path ) ){
+ return NULL;
+ }
+
+ enum addon_type type = k_addon_type_none;
+ vg_msg msg;
+ vg_msg_init( &msg, reg->metadata, reg->metadata_len );
+
+ if( vg_msg_seekframe( &msg, "workshop" ))
+ {
+ vg_msg_getkvintg( &msg, "type", k_vg_msg_u32, &type, NULL );
+ }
+
+ if( type == k_addon_type_none )
+ {
+ vg_error( "Cannot determine addon type\n" );
+ return NULL;
+ }
+
+ reg->alias.type = type;
+ addon_mount_finish( reg );
+ return reg;
+}
+
+/*
+ * Mount a local folder. may or may not have addon.inf
+ */
+addon_reg *addon_mount_local_addon( const char *folder,
+ enum addon_type type,
+ const char *content_ext )
+{
+ char folder_path_buf[4096];
+ vg_str folder_path;
+ vg_strnull( &folder_path, folder_path_buf, 4096 );
+ vg_strcat( &folder_path, folder );
+
+ const char *folder_name = vg_strch( &folder_path, '/' )+1;
+ u32 folder_hash = vg_strdjb2(folder_name);
+ for( u32 i=0; i<addon_system.registry_count; i++ ){
+ addon_reg *reg = &addon_system.registry[i];
+
+ if( (reg->alias.type == type) && (reg->foldername_hash == folder_hash) ){
+ if( !strcmp( reg->alias.foldername, folder_name ) ){
+ reg->state = k_addon_state_indexed;
+ return reg;
+ }
+ }
+ }
+
+ addon_reg *reg = addon_alloc_reg( 0, type );
+ if( !reg ) return NULL;
+ addon_set_foldername( reg, folder_name );
+ addon_try_load_metadata( reg, folder_path );
+
+ if( reg->metadata_len == 0 ){
+ /* create our own content commands */
+ vg_msg msg;
+ vg_msg_init( &msg, reg->metadata, sizeof(reg->metadata) );
+
+ u32 content_count = 0;
+
+ vg_strcat( &folder_path, "" );
+ vg_warn( "Creating own metadata for: %s\n", folder_path.buffer );
+
+ vg_dir subdir;
+ if( !vg_dir_open(&subdir, folder_path.buffer) ){
+ vg_error( "Failed to open '%s'\n", folder_path.buffer );
+ return NULL;
+ }
+
+ while( vg_dir_next_entry(&subdir) ){
+ if( vg_dir_entry_type(&subdir) == k_vg_entry_type_file ){
+ const char *fname = vg_dir_entry_name(&subdir);
+ vg_str file = folder_path;
+ vg_strcat( &file, "/" );
+ vg_strcat( &file, fname );
+ if( !vg_strgood( &file ) ) continue;
+
+ char *ext = vg_strch( &file, '.' );
+ if( !ext ) continue;
+ if( strcmp(ext,content_ext) ) continue;
+
+ vg_msg_wkvstr( &msg, "content", fname );
+ content_count ++;
+ }
+ }
+ vg_dir_close(&subdir);
+
+ if( !content_count ) return NULL;
+ if( msg.error == k_vg_msg_error_OK )
+ reg->metadata_len = msg.cur.co;
+ else{
+ vg_error( "Error creating metadata: %d\n", msg.error );
+ return NULL;
+ }
+ }
+
+ addon_mount_finish( reg );
+ return reg;
+}
+
+/*
+ * Check all subscribed items
+ */
+void addon_mount_workshop_items(void)
+{
+ if( skaterift.demo_mode ){
+ vg_info( "Won't load workshop items in demo mode\n" );
+ return;
+ }
+ if( !steam_ready ) return;
+
+ /*
+ * Steam workshop scan
+ */
+ vg_info( "Mounting steam workshop subscriptions\n" );
+ PublishedFileId_t workshop_ids[ ADDON_MOUNTED_MAX ];
+ u32 workshop_count = ADDON_MOUNTED_MAX;
+
+ vg_async_item *call = vg_async_alloc(
+ sizeof(struct async_workshop_installed_files_info));
+ struct async_workshop_installed_files_info *info = call->payload;
+ info->buffer = workshop_ids;
+ info->len = &workshop_count;
+ vg_async_dispatch( call, async_workshop_get_installed_files );
+ vg_async_stall();
+
+ for( u32 j=0; j<workshop_count; j++ ){
+ /* check for existance in both our caches
+ * ----------------------------------------------------------*/
+ PublishedFileId_t id = workshop_ids[j];
+ for( u32 i=0; i<addon_system.registry_count; i++ ){
+ addon_reg *reg = &addon_system.registry[i];
+
+ if( reg->alias.workshop_id == id ){
+ reg->state = k_addon_state_indexed;
+ goto next_file_workshop;
+ }
+ }
+
+ vg_async_item *call1 =
+ vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
+
+ char path[ 4096 ];
+
+ struct async_workshop_filepath_info *info = call1->payload;
+ info->buf = path;
+ info->id = id;
+ info->len = VG_ARRAY_LEN(path);
+ vg_async_dispatch( call1, async_workshop_get_filepath );
+ vg_async_stall(); /* too bad! */
+
+ vg_str folder = {.buffer = path, .i=strlen(path), .len=4096};
+ addon_mount_workshop_folder( id, folder );
+next_file_workshop:;
+ }
+}
+
+/*
+ * Scan a local content folder for addons. It must find at least one file with
+ * the specified content_ext to be considered.
+ */
+void addon_mount_content_folder( enum addon_type type,
+ const char *base_folder,
+ const char *content_ext )
+{
+ vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n",
+ type, base_folder, content_ext );
+
+ char path_buf[4096];
+ vg_str path;
+ vg_strnull( &path, path_buf, 4096 );
+ vg_strcat( &path, base_folder );
+
+ vg_dir dir;
+ if( !vg_dir_open(&dir,path.buffer) ){
+ vg_error( "vg_dir_open('%s') failed\n", path.buffer );
+ return;
+ }
+
+ vg_strcat(&path,"/");
+
+ while( vg_dir_next_entry(&dir) ){
+ if( vg_dir_entry_type(&dir) == k_vg_entry_type_dir ){
+ const char *d_name = vg_dir_entry_name(&dir);
+
+ vg_str folder = path;
+ if( strlen( d_name ) > ADDON_FOLDERNAME_MAX ){
+ vg_warn( "folder too long: %s\n", d_name );
+ continue;
+ }
+
+ vg_strcat( &folder, d_name );
+ if( !vg_strgood( &folder ) ) continue;
+
+ addon_mount_local_addon( folder.buffer, type, content_ext );
+ }
+ }
+ vg_dir_close(&dir);
+}
+
+/*
+ * write the full path of the addon's folder into the vg_str
+ */
+int addon_get_content_folder( addon_reg *reg, vg_str *folder, int async)
+{
+ if( reg->alias.workshop_id ){
+ struct async_workshop_filepath_info *info = NULL;
+ vg_async_item *call = NULL;
+
+ if( async ){
+ call = vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
+ info = call->payload;
+ }
+ else
+ info = alloca( sizeof(struct async_workshop_filepath_info) );
+
+ info->buf = folder->buffer;
+ info->id = reg->alias.workshop_id;
+ info->len = folder->len;
+
+ if( async ){
+ vg_async_dispatch( call, async_workshop_get_filepath );
+ vg_async_stall(); /* too bad! */
+ }
+ else {
+ async_workshop_get_filepath( info, 0 );
+ }
+
+ if( info->buf[0] == '\0' ){
+ vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64 ")\n",
+ reg->alias.workshop_id );
+ return 0;
+ }
+ folder->i = strlen( folder->buffer );
+ return 1;
+ }
+ else{
+ folder->i = 0;
+
+ const char *local_folder =
+ addon_type_infos[reg->alias.type].local_content_folder;
+
+ if( !local_folder ) return 0;
+ vg_strcat( folder, local_folder );
+ vg_strcat( folder, reg->alias.foldername );
+ return 1;
+ }
+}
+
+/*
+ * Return existing cache id if reg_index points to a registry with its cache
+ * already set.
+ */
+u16 addon_cache_fetch( enum addon_type type, u32 reg_index )
+{
+ addon_reg *reg = NULL;
+
+ if( reg_index < addon_count( type, 0 ) ){
+ reg = get_addon_from_index( type, reg_index, 0 );
+ if( reg->cache_id )
+ return reg->cache_id;
+ }
+
+ return 0;
+}
+
+/*
+ * Allocate a new cache item from the pool
+ */
+u16 addon_cache_alloc( enum addon_type type, u32 reg_index )
+{
+ struct addon_cache *cache = &addon_system.cache[ type ];
+
+ u16 new_id = vg_pool_lru( &cache->pool );
+ struct addon_cache_entry *new_entry = vg_pool_item( &cache->pool, new_id );
+
+ addon_reg *reg = NULL;
+ if( reg_index < addon_count( type, 0 ) )
+ reg = get_addon_from_index( type, reg_index, 0 );
+
+ if( new_entry ){
+ if( new_entry->reg_ptr )
+ new_entry->reg_ptr->cache_id = 0;
+
+ if( reg )
+ reg->cache_id = new_id;
+
+ new_entry->reg_ptr = reg;
+ new_entry->reg_index = reg_index;
+ return new_id;
+ }
+ else{
+ vg_error( "cache full (type: %u)!\n", type );
+ return 0;
+ }
+}
+
+/*
+ * Get the real item data for cache id
+ */
+void *addon_cache_item( enum addon_type type, u16 id )
+{
+ if( !id ) return NULL;
+
+ struct addon_cache *cache = &addon_system.cache[type];
+ return cache->items + ((size_t)(id-1) * cache->stride);
+}
+
+/*
+ * Get the real item data for cache id ONLY if the item is completely loaded.
+ */
+void *addon_cache_item_if_loaded( enum addon_type type, u16 id )
+{
+ if( !id ) return NULL;
+
+ struct addon_cache *cache = &addon_system.cache[type];
+ struct addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
+
+ if( entry->state == k_addon_cache_state_loaded )
+ return addon_cache_item( type, id );
+ else return NULL;
+}
+
+/*
+ * Updates the item state from the main thread
+ */
+void async_addon_setstate( void *_entry, u32 _state )
+{
+ addon_cache_entry *entry = _entry;
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ entry->state = _state;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ vg_success( " loaded (%s)\n", entry->reg_ptr->alias.foldername );
+}
+
+/*
+ * Handles the loading of an individual item
+ */
+static int addon_cache_load_request( enum addon_type type, u16 id,
+ addon_reg *reg, vg_str folder ){
+
+ /* load content files
+ * --------------------------------- */
+ vg_str content_path = folder;
+
+ vg_msg msg;
+ vg_msg_init( &msg, reg->metadata, reg->metadata_len );
+
+ const char *kv_content = vg_msg_getkvstr( &msg, "content" );
+ if( kv_content ){
+ vg_strcat( &content_path, "/" );
+ vg_strcat( &content_path, kv_content );
+ }
+ else{
+ vg_error( " No content paths in metadata\n" );
+ return 0;
+ }
+
+ if( !vg_strgood( &content_path ) ) {
+ vg_error( " Metadata path too long\n" );
+ return 0;
+ }
+
+ if( type == k_addon_type_board ){
+ struct player_board *board = addon_cache_item( type, id );
+ player_board_load( board, content_path.buffer );
+ return 1;
+ }
+ else if( type == k_addon_type_player ){
+ struct player_model *model = addon_cache_item( type, id );
+ player_model_load( model, content_path.buffer );
+ return 1;
+ }
+ else {
+ return 0;
+ }
+
+ return 0;
+}
+
+static void addon_cache_free_item( enum addon_type type, u16 id ){
+ if( type == k_addon_type_board ){
+ struct player_board *board = addon_cache_item( type, id );
+ player_board_unload( board );
+ }
+ else if( type == k_addon_type_player ){
+ struct player_model *model = addon_cache_item( type, id );
+ player_model_unload( model );
+ }
+}
+
+/*
+ * Goes over cache item load requests and calls the above ^
+ */
+static void T1_addon_cache_load_loop(void *_)
+{
+ vg_info( "Running load loop\n" );
+ char path_buf[4096];
+
+ for( u32 type=0; type<k_addon_type_max; type++ )
+ {
+ struct addon_cache *cache = &addon_system.cache[type];
+
+ for( u32 id=1; id<=cache->pool.count; id++ )
+ {
+ addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ if( entry->state == k_addon_cache_state_load_request )
+ {
+ vg_info( "process cache load request (%u#%u, reg:%u)\n",
+ type, id, entry->reg_index );
+
+ if( entry->reg_index >= addon_count(type,0) )
+ {
+ /* should maybe have a different value for this case */
+ entry->state = k_addon_cache_state_none;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ continue;
+ }
+
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+
+ /* continue with the request */
+ addon_reg *reg = get_addon_from_index( type, entry->reg_index, 0 );
+ entry->reg_ptr = reg;
+
+ vg_str folder;
+ vg_strnull( &folder, path_buf, 4096 );
+ if( addon_get_content_folder( reg, &folder, 1 ) )
+ {
+ if( addon_cache_load_request( type, id, reg, folder ) )
+ {
+ vg_async_call( async_addon_setstate,
+ entry, k_addon_cache_state_loaded );
+ continue;
+ }
+ }
+
+ vg_warn( "cache item did not load (%u#%u)\n", type, id );
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ entry->state = k_addon_cache_state_none;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ }
+ else
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ }
+ }
+}
+
+void addon_system_pre_update(void)
+{
+ if( !vg_loader_availible() ) return;
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ for( u32 type=0; type<k_addon_type_max; type++ )
+ {
+ struct addon_cache *cache = &addon_system.cache[type];
+
+ for( u32 id=1; id<=cache->pool.count; id++ )
+ {
+ addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
+ if( entry->state == k_addon_cache_state_load_request )
+ {
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ vg_loader_start( T1_addon_cache_load_loop, NULL );
+ return;
+ }
+ }
+ }
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+}
+
+/*
+ * Perform the cache interactions required to create a viewslot which will
+ * eventually be loaded by other parts of the system.
+ */
+u16 addon_cache_create_viewer( enum addon_type type, u16 reg_id )
+{
+ struct addon_cache *cache = &addon_system.cache[type];
+ vg_pool *pool = &cache->pool;
+
+ u16 cache_id = addon_cache_fetch( type, reg_id );
+ if( !cache_id ){
+ cache_id = addon_cache_alloc( type, reg_id );
+
+ if( cache_id ){
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ addon_cache_entry *entry = vg_pool_item( pool, cache_id );
+
+ if( entry->state == k_addon_cache_state_loaded ){
+ addon_cache_free_item( type, cache_id );
+ }
+
+ entry->state = k_addon_cache_state_load_request;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ }
+ }
+
+ if( cache_id )
+ vg_pool_watch( pool, cache_id );
+
+ return cache_id;
+}
+
+u16 addon_cache_create_viewer_from_uid( enum addon_type type,
+ char uid[ADDON_UID_MAX] )
+{
+ addon_alias q;
+ if( !addon_uid_to_alias( uid, &q ) ) return 0;
+ if( q.type != type ) return 0;
+
+ u32 reg_id = addon_match( &q );
+
+ if( reg_id == 0xffffffff ){
+ vg_warn( "We dont have the addon '%s' installed.\n", uid );
+ return 0;
+ }
+ else {
+ return addon_cache_create_viewer( type, reg_id );
+ }
+}
+
+void addon_cache_watch( enum addon_type type, u16 cache_id )
+{
+ if( !cache_id ) return;
+
+ struct addon_cache *cache = &addon_system.cache[type];
+ vg_pool *pool = &cache->pool;
+ vg_pool_watch( pool, cache_id );
+}
+
+void addon_cache_unwatch( enum addon_type type, u16 cache_id )
+{
+ if( !cache_id ) return;
+
+ struct addon_cache *cache = &addon_system.cache[type];
+ vg_pool *pool = &cache->pool;
+ vg_pool_unwatch( pool, cache_id );
+}
--- /dev/null
+#pragma once
+#include "vg/vg_steam_ugc.h"
+#include "vg/vg_mem_pool.h"
+#include "vg/vg_string.h"
+#include "addon_types.h"
+
+typedef struct addon_reg addon_reg;
+typedef struct addon_cache_entry addon_cache_entry;
+typedef struct addon_alias addon_alias;
+
+struct addon_alias
+{
+ enum addon_type type;
+ PublishedFileId_t workshop_id;
+ char foldername[ ADDON_FOLDERNAME_MAX ];
+};
+
+#define ADDON_REG_HIDDEN 0x1
+#define ADDON_REG_MTZERO 0x2
+#define ADDON_REG_CITY 0x4
+#define ADDON_REG_PREMIUM 0x8
+
+struct addon_system
+{
+ struct addon_reg
+ {
+ addon_alias alias;
+ u32 foldername_hash;
+ u8 metadata[512]; /* vg_msg buffer */
+ u32 metadata_len;
+ u32 flags;
+
+ u16 cache_id;
+
+ enum addon_state{
+ k_addon_state_none,
+ k_addon_state_indexed,
+ k_addon_state_indexed_absent /* gone but not forgotten */
+ }
+ state;
+ }
+ *registry;
+ u32 registry_count;
+
+ /* deffered: updates in main thread */
+ u32 registry_type_counts[k_addon_type_max];
+
+ struct addon_cache
+ {
+ struct addon_cache_entry
+ {
+ u32 reg_index;
+ addon_reg *reg_ptr; /* TODO: only use reg_index? */
+
+ vg_pool_node poolnode;
+
+ enum addon_cache_state{
+ k_addon_cache_state_none,
+ k_addon_cache_state_loaded,
+ k_addon_cache_state_load_request
+ }
+ state;
+ }
+ *allocs;
+ vg_pool pool;
+
+ void *items; /* the real data */
+ size_t stride;
+ }
+ cache[k_addon_type_max];
+ SDL_SpinLock sl_cache_using_resources;
+}
+extern addon_system;
+
+void addon_system_init( void );
+u32 addon_count( enum addon_type type, u32 ignoreflags );
+addon_reg *get_addon_from_index( enum addon_type type, u32 index,
+ u32 ignoreflags );
+u32 get_index_from_addon( enum addon_type type, addon_reg *a );
+int addon_get_content_folder( addon_reg *reg, vg_str *folder, int async);
+
+/* scanning routines */
+u32 addon_match( addon_alias *alias );
+int addon_alias_eq( addon_alias *a, addon_alias *b );
+void addon_alias_uid( addon_alias *alias, char buf[ADDON_UID_MAX] );
+int addon_uid_to_alias( const char *uid, addon_alias *alias );
+void invalidate_addon_alias( addon_alias *alias );
+void addon_mount_content_folder( enum addon_type type,
+ const char *base_folder,
+ const char *content_ext );
+void addon_mount_workshop_items(void);
+void async_addon_reg_update( void *data, u32 size );
+addon_reg *addon_mount_local_addon( const char *folder,
+ enum addon_type type,
+ const char *content_ext );
+u16 addon_cache_fetch( enum addon_type type, u32 reg_index );
+u16 addon_cache_alloc( enum addon_type type, u32 reg_index );
+void *addon_cache_item( enum addon_type type, u16 id );
+void *addon_cache_item_if_loaded( enum addon_type type, u16 id );
+void async_addon_setstate( void *data, u32 size );
+
+void addon_system_pre_update(void);
+u16 addon_cache_create_viewer( enum addon_type type, u16 reg_id);
+
+void addon_cache_watch( enum addon_type type, u16 cache_id );
+void addon_cache_unwatch( enum addon_type type, u16 cache_id );
+u16 addon_cache_create_viewer_from_uid( enum addon_type type,
+ char uid[ADDON_UID_MAX] );
--- /dev/null
+#include "player.h"
+#include "player_render.h"
+#include "player_api.h"
+
+struct addon_type_info addon_type_infos[] =
+{
+ [k_addon_type_board] = {
+ .local_content_folder = "boards/",
+ .cache_stride = sizeof(struct player_board),
+ .cache_count = 20
+ },
+ [k_addon_type_player] = {
+ .local_content_folder = "playermodels/",
+ .cache_stride = sizeof(struct player_model),
+ .cache_count = 20
+ },
+ [k_addon_type_world] = {
+ .local_content_folder = "maps/"
+ }
+};
--- /dev/null
+#pragma once
+
+enum addon_type{
+ k_addon_type_none = 0,
+ k_addon_type_board = 1,
+ k_addon_type_world = 2,
+ k_addon_type_player = 3,
+ k_addon_type_max
+};
+
+#define ADDON_FOLDERNAME_MAX 64
+#define ADDON_MOUNTED_MAX 128 /* total count that we have knowledge of */
+#define ADDON_UID_MAX 76
+
+#ifdef VG_ENGINE
+
+struct addon_type_info {
+ size_t cache_stride;
+ u16 cache_count;
+ const char *local_content_folder;
+}
+extern addon_type_infos[];
+
+#endif
--- /dev/null
+#include "world.h"
+#include "audio.h"
+#include "vg/vg_audio_dsp.h"
+
+audio_clip audio_board[] =
+{
+ { .path="sound/skate_hpf.ogg" },
+ { .path="sound/wheel.ogg" },
+ { .path="sound/slide.ogg" },
+ { .path="sound/grind_enter.ogg" },
+ { .path="sound/grind_exit.ogg" },
+ { .path="sound/grind_loop.ogg" },
+ { .path="sound/woodslide.ogg" },
+ { .path="sound/metalscrape.ogg" },
+ { .path="sound/slidetap.ogg" }
+};
+
+audio_clip audio_taps[] =
+{
+ { .path="sound/tap0.ogg" },
+ { .path="sound/tap1.ogg" },
+ { .path="sound/tap2.ogg" },
+ { .path="sound/tap3.ogg" }
+};
+
+audio_clip audio_flips[] =
+{
+ { .path="sound/lf0.ogg" },
+ { .path="sound/lf1.ogg" },
+ { .path="sound/lf2.ogg" },
+ { .path="sound/lf3.ogg" },
+};
+
+audio_clip audio_hits[] =
+{
+ { .path="sound/hit0.ogg" },
+ { .path="sound/hit1.ogg" },
+ { .path="sound/hit2.ogg" },
+ { .path="sound/hit3.ogg" },
+ { .path="sound/hit4.ogg" }
+};
+
+audio_clip audio_splash =
+{ .path = "sound/splash.ogg" };
+
+audio_clip audio_jumps[] = {
+ { .path = "sound/jump0.ogg" },
+ { .path = "sound/jump1.ogg" },
+};
+
+audio_clip audio_footsteps[] = {
+ {.path = "sound/step_concrete0.ogg" },
+ {.path = "sound/step_concrete1.ogg" },
+ {.path = "sound/step_concrete2.ogg" },
+ {.path = "sound/step_concrete3.ogg" }
+};
+
+audio_clip audio_footsteps_grass[] = {
+ {.path = "sound/step_bush0.ogg" },
+ {.path = "sound/step_bush1.ogg" },
+ {.path = "sound/step_bush2.ogg" },
+ {.path = "sound/step_bush3.ogg" },
+ {.path = "sound/step_bush4.ogg" },
+ {.path = "sound/step_bush5.ogg" }
+};
+
+audio_clip audio_footsteps_wood[] = {
+ {.path = "sound/step_wood0.ogg" },
+ {.path = "sound/step_wood1.ogg" },
+ {.path = "sound/step_wood2.ogg" },
+ {.path = "sound/step_wood3.ogg" },
+ {.path = "sound/step_wood4.ogg" },
+ {.path = "sound/step_wood5.ogg" }
+};
+
+audio_clip audio_lands[] = {
+ { .path = "sound/land0.ogg" },
+ { .path = "sound/land1.ogg" },
+ { .path = "sound/land2.ogg" },
+ { .path = "sound/landsk0.ogg" },
+ { .path = "sound/landsk1.ogg" },
+ { .path = "sound/onto.ogg" },
+ { .path = "sound/outo.ogg" },
+};
+
+audio_clip audio_water[] = {
+ { .path = "sound/wave0.ogg" },
+ { .path = "sound/wave1.ogg" },
+ { .path = "sound/wave2.ogg" },
+ { .path = "sound/wave3.ogg" },
+ { .path = "sound/wave4.ogg" },
+ { .path = "sound/wave5.ogg" }
+};
+
+audio_clip audio_grass[] = {
+ { .path = "sound/grass0.ogg" },
+ { .path = "sound/grass1.ogg" },
+ { .path = "sound/grass2.ogg" },
+ { .path = "sound/grass3.ogg" },
+};
+
+audio_clip audio_ambience[] =
+{
+ { .path="sound/town_generic.ogg" }
+};
+
+audio_clip audio_gate_pass = {
+ .path = "sound/gate_pass.ogg"
+};
+
+audio_clip audio_gate_lap = {
+ .path = "sound/gate_lap.ogg"
+};
+
+audio_clip audio_gate_ambient = {
+.path = "sound/gate_ambient.ogg"
+};
+
+audio_clip audio_rewind[] = {
+{ .path = "sound/rewind_start.ogg" },
+{ .path = "sound/rewind_end_1.5.ogg" },
+{ .path = "sound/rewind_end_2.5.ogg" },
+{ .path = "sound/rewind_end_6.5.ogg" },
+{ .path = "sound/rewind_clack.ogg" },
+};
+
+audio_clip audio_ui[] = {
+ { .path = "sound/ui_click.ogg" },
+ { .path = "sound/ui_ding.ogg" },
+ { .path = "sound/teleport.ogg" },
+ { .path = "sound/ui_move.ogg" }
+};
+
+audio_clip audio_challenge[] = {
+ { .path = "sound/objective0.ogg" },
+ { .path = "sound/objective1.ogg" },
+ { .path = "sound/objective_win.ogg" },
+ { .path = "sound/ui_good.ogg" },
+ { .path = "sound/ui_inf.ogg" },
+ { .path = "sound/ui_ok.ogg" },
+ { .path = "sound/objective_fail.ogg" }
+};
+
+struct air_synth_data air_audio_data;
+
+static void audio_air_synth_get_samples( void *_data, f32 *buf, u32 count ){
+ struct air_synth_data *data = _data;
+
+ SDL_AtomicLock( &data->sl );
+ f32 spd = data->speed;
+ SDL_AtomicUnlock( &data->sl );
+
+ f32 s0 = sinf(data->t*2.0f),
+ s1 = sinf(data->t*0.43f),
+ s2 = sinf(data->t*1.333f),
+ sm = vg_clampf( data->speed / 45.0f, 0, 1 ),
+ ft = (s0*s1*s2)*0.5f+0.5f,
+ f = vg_lerpf( 200.0f, 1200.0f, sm*0.7f + ft*0.3f ),
+ vol = 0.25f * sm;
+
+ dsp_init_biquad_butterworth_lpf( &data->lpf, f );
+
+ for( u32 i=0; i<count; i ++ ){
+ f32 v = (vg_randf64(&vg_dsp.rand) * 2.0f - 1.0f) * vol;
+ v = dsp_biquad_process( &data->lpf, v );
+
+ buf[i*2+0] = v;
+ buf[i*2+1] = v;
+ }
+
+ data->t += (f32)(count)/44100.0f;
+};
+
+static audio_clip air_synth = {
+ .flags = k_audio_format_gen,
+ .size = 0,
+ .func = audio_air_synth_get_samples,
+ .data = &air_audio_data
+};
+
+void audio_init(void)
+{
+ audio_clip_loadn( audio_board, VG_ARRAY_LEN(audio_board), NULL );
+ audio_clip_loadn( audio_taps, VG_ARRAY_LEN(audio_taps), NULL );
+ audio_clip_loadn( audio_flips, VG_ARRAY_LEN(audio_flips), NULL );
+ audio_clip_loadn( audio_hits, VG_ARRAY_LEN(audio_hits), NULL );
+ audio_clip_loadn( audio_ambience, VG_ARRAY_LEN(audio_ambience), NULL );
+ audio_clip_loadn( &audio_splash, 1, NULL );
+ audio_clip_loadn( &audio_gate_pass, 1, NULL );
+ audio_clip_loadn( &audio_gate_lap, 1, NULL );
+ audio_clip_loadn( &audio_gate_ambient, 1, NULL );
+
+ audio_clip_loadn( audio_jumps, VG_ARRAY_LEN(audio_jumps), NULL );
+ audio_clip_loadn( audio_lands, VG_ARRAY_LEN(audio_lands), NULL );
+ audio_clip_loadn( audio_water, VG_ARRAY_LEN(audio_water), NULL );
+ audio_clip_loadn( audio_grass, VG_ARRAY_LEN(audio_grass), NULL );
+ audio_clip_loadn( audio_footsteps, VG_ARRAY_LEN(audio_footsteps), NULL );
+ audio_clip_loadn( audio_footsteps_grass,
+ VG_ARRAY_LEN(audio_footsteps_grass), NULL );
+ audio_clip_loadn( audio_footsteps_wood,
+ VG_ARRAY_LEN(audio_footsteps_wood), NULL );
+ audio_clip_loadn( audio_rewind, VG_ARRAY_LEN(audio_rewind), NULL );
+ audio_clip_loadn( audio_ui, VG_ARRAY_LEN(audio_ui), NULL );
+ audio_clip_loadn( audio_challenge, VG_ARRAY_LEN(audio_challenge), NULL );
+
+ audio_lock();
+ audio_set_lfo_wave( 0, k_lfo_polynomial_bipolar, 80.0f );
+ audio_set_lfo_frequency( 0, 20.0f );
+
+ air_audio_data.channel = audio_get_first_idle_channel();
+ if( air_audio_data.channel )
+ audio_channel_init( air_audio_data.channel, &air_synth, 0 );
+
+ audio_unlock();
+}
+
+void audio_ambient_sprite_play( v3f co, audio_clip *clip )
+{
+ audio_lock();
+ u16 group_id = 0xfff0;
+ audio_channel *ch = audio_get_group_idle_channel( group_id, 4 );
+
+ if( ch ){
+ audio_channel_init( ch, clip, AUDIO_FLAG_SPACIAL_3D );
+ audio_channel_group( ch, group_id );
+ audio_channel_set_spacial( ch, co, 80.0f );
+ audio_channel_edit_volume( ch, 1.0f, 1 );
+ ch = audio_relinquish_channel( ch );
+ }
+ audio_unlock();
+}
+
+enum audio_sprite_type world_audio_sample_sprite_random(v3f origin, v3f output);
+void audio_ambient_sprites_update( world_instance *world, v3f co )
+{
+ static float accum = 0.0f;
+ accum += vg.time_delta;
+
+ if( accum > 0.1f )
+ accum -= 0.1f;
+ else return;
+
+ v3f sprite_pos;
+ enum audio_sprite_type sprite_type =
+ world_audio_sample_sprite_random( co, sprite_pos );
+
+ if( sprite_type != k_audio_sprite_type_none ){
+ if( sprite_type == k_audio_sprite_type_grass ){
+ audio_ambient_sprite_play( sprite_pos,
+ &audio_grass[vg_randu32(&vg.rand)%4] );
+ }
+ else if( sprite_type == k_audio_sprite_type_water ){
+ if( world->water.enabled ){
+ audio_ambient_sprite_play( sprite_pos,
+ &audio_water[vg_randu32(&vg.rand)%6] );
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+
+#include "vg/vg_engine.h"
+#include "vg/vg_audio.h"
+#include "vg/vg_audio_dsp.h"
+#include "world.h"
+
+struct air_synth_data {
+ f32 speed;
+
+ /* internal */
+ f32 t;
+ struct dsp_biquad lpf;
+ SDL_SpinLock sl;
+
+ /* not used in locking */
+ audio_channel *channel;
+}
+extern air_audio_data;
+
+void audio_init(void);
+void audio_ambient_sprite_play( v3f co, audio_clip *clip );
+void audio_ambient_sprites_update( world_instance *world, v3f co );
+
+/* TODO(ASSETS):
+ * Have these as asignable ID's and not a bunch of different arrays.
+ */
+extern audio_clip audio_board[];
+extern audio_clip audio_taps[];
+extern audio_clip audio_flips[];
+extern audio_clip audio_hits[];
+extern audio_clip audio_splash;
+extern audio_clip audio_jumps[];
+extern audio_clip audio_footsteps[];
+extern audio_clip audio_footsteps_grass[];
+extern audio_clip audio_footsteps_wood[];
+extern audio_clip audio_lands[];
+extern audio_clip audio_water[];
+extern audio_clip audio_grass[];
+extern audio_clip audio_ambience[];
+extern audio_clip audio_gate_pass;
+extern audio_clip audio_gate_lap;
+extern audio_clip audio_gate_ambient;
+extern audio_clip audio_rewind[];
+extern audio_clip audio_ui[];
+extern audio_clip audio_challenge[];
+
+enum audio_sprite_type
+{
+ k_audio_sprite_type_none,
+ k_audio_sprite_type_grass,
+ k_audio_sprite_type_water
+};
--- /dev/null
+/*
+ * Script to load the overlay model and generate an enum referencing each
+ * submesh by its name.
+ */
+void build_control_overlay(void)
+{
+ FILE *hdr = fopen( "src/control_overlay.h.generated", "w" );
+ mdl_context ctx;
+ mdl_open( &ctx, "content_skaterift/models/rs_overlay.mdl", NULL );
+ mdl_load_metadata_block( &ctx, NULL );
+ mdl_close( &ctx );
+
+ for( u32 i=0; i<mdl_arrcount( &ctx.meshs ); i ++ )
+ {
+ mdl_mesh *mesh = mdl_arritm( &ctx.meshs, i );
+ fprintf( hdr, " %s = %u,\n",
+ mdl_pstr( &ctx, mesh->pstr_name ), mesh->submesh_start );
+ }
+
+ fclose( hdr );
+}
--- /dev/null
+#include "vg/vg_opt.h"
+#include "vg/vg_loader.h"
+#include "vg/vg_io.h"
+#include "vg/vg_audio.h"
+#include "vg/vg_async.h"
+
+#include "client.h"
+#include "render.h"
+#include "network.h"
+#include "player_remote.h"
+#include "menu.h"
+
+const char* __asan_default_options() { return "detect_leaks=0"; }
+
+struct game_client g_client =
+{
+ .demo_mode = 1
+};
+
+static void async_client_ready( void *payload, u32 size )
+{
+ g_client.loaded = 1;
+
+ if( network_client.auto_connect )
+ network_client.user_intent = k_server_intent_online;
+
+ menu_at_begin();
+}
+
+void vg_load(void)
+{
+ vg_audio.always_keep_compressed = 1;
+ vg_loader_step( render_init, NULL );
+
+ game_load();
+
+ vg_async_call( async_client_ready, NULL, 0 );
+}
+
+void vg_preload(void)
+{
+vg_info(" Copyright . . . -----, ,----- ,---. .---. \n" );
+vg_info(" 2021-2024 |\\ /| | / | | | | /| \n" );
+vg_info(" | \\ / | +-- / +----- +---' | / | \n" );
+vg_info(" | \\ / | | / | | \\ | / | \n" );
+vg_info(" | \\/ | | / | | \\ | / | \n" );
+vg_info(" ' ' '--' [] '----- '----- ' ' '---' "
+ "SOFTWARE\n" );
+
+ /* please forgive me! */
+ u32 sz; char *drm;
+ if( (drm = vg_file_read_text( vg_mem.scratch, "DRM", &sz )) )
+ if( !strcmp(drm, "blibby!") )
+ g_client.demo_mode = 0;
+
+ vg_loader_step( remote_players_init, NULL );
+
+ steam_init();
+ vg_loader_step( NULL, steam_end );
+ vg_loader_step( network_init, network_end );
+}
+
+void vg_launch_opt(void)
+{
+ const char *arg;
+
+ if( vg_long_opt( "noauth" ) )
+ network_client.auth_mode = eServerModeNoAuthentication;
+
+ if( (arg = vg_long_opt_arg( "server" )) )
+ network_set_host( arg, NULL );
+
+ if( vg_long_opt( "demo" ) )
+ g_client.demo_mode = 1;
+
+ game_launch_opt();
+}
+
+int main( int argc, char *argv[] )
+{
+ network_set_host( "skaterift.com", NULL );
+ vg_mem.use_libc_malloc = 1;
+ vg_set_mem_quota( 160*1024*1024 );
+ vg_enter( argc, argv, "Voyager Game Engine" );
+ return 0;
+}
+
+#include "skaterift.c"
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+
+/*
+ * client - entry point. window, common things like render init.. etc
+ * vg - backend code
+ * game - top layer: game content, state
+ */
+
+struct game_client
+{
+ bool loaded, demo_mode;
+}
+extern g_client;
+
+/* game defined */
+void game_launch_opt( void );
+void game_load( void );
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#endif /* COMMON_H */
--- /dev/null
+#include "control_overlay.h"
+#include "model.h"
+#include "input.h"
+#include "player.h"
+#include "player_skate.h"
+#include "player_walk.h"
+#include "shaders/model_menu.h"
+#include "vg/vg_engine.h"
+#include "vg/vg_mem.h"
+#include "vg/vg_m.h"
+
+struct control_overlay control_overlay = { .enabled = 1 };
+
+static void render_overlay_mesh( enum control_overlay_mesh index )
+{
+ mdl_draw_submesh( mdl_arritm( &control_overlay.mdl.submeshs, index ) );
+}
+
+void control_overlay_init(void)
+{
+ void *alloc = vg_mem.rtmemory;
+ mdl_context *mdl = &control_overlay.mdl;
+
+ mdl_open( mdl, "models/rs_overlay.mdl", alloc );
+ mdl_load_metadata_block( mdl, alloc );
+ mdl_async_full_load_std( mdl );
+ mdl_close( mdl );
+
+ vg_async_stall();
+
+ if( mdl_arrcount( &mdl->textures ) )
+ {
+ mdl_texture *tex = mdl_arritm( &mdl->textures, 0 );
+ control_overlay.tex = tex->glname;
+ }
+ else
+ {
+ control_overlay.tex = vg.tex_missing;
+ vg_error( "No texture in control overlay\n" );
+ }
+
+ vg_console_reg_var( "control_overlay", &control_overlay.enabled,
+ k_var_dtype_i32, VG_VAR_PERSISTENT );
+}
+
+static void draw_key( bool press, bool wide )
+{
+ if( wide ) render_overlay_mesh( press? ov_shift_down: ov_shift );
+ else render_overlay_mesh( press? ov_key_down: ov_key );
+}
+
+static void colorize( bool press, bool condition )
+{
+ v4f cnorm = { 1,1,1,0.76f },
+ cdis = { 1,1,1,0.35f },
+ chit = { 1,0.5f,0.2f,0.8f };
+
+ if( condition )
+ if( press )
+ shader_model_menu_uColour( chit );
+ else
+ shader_model_menu_uColour( cnorm );
+ else
+ shader_model_menu_uColour( cdis );
+}
+
+void control_overlay_render(void)
+{
+ if( !control_overlay.enabled ) return;
+ if( skaterift.activity != k_skaterift_default ) return;
+
+ glEnable(GL_BLEND);
+ glDisable(GL_DEPTH_TEST);
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ glBlendEquation(GL_FUNC_ADD);
+
+ m4x4f ortho;
+ f32 r = (f32)vg.window_x / (f32)vg.window_y,
+ fl = -r,
+ fr = r,
+ fb = 1.0f,
+ ft = -1.0f,
+ rl = 1.0f / (fr-fl),
+ tb = 1.0f / (ft-fb);
+
+ m4x4_zero( ortho );
+ ortho[0][0] = 2.0f * rl;
+ ortho[2][1] = 2.0f * tb;
+ ortho[3][0] = (fr + fl) * -rl;
+ ortho[3][1] = (ft + fb) * -tb;
+ ortho[3][3] = 1.0f;
+
+ v4f cnorm = { 1,1,1,0.76f },
+ cdis = { 1,1,1,0.35f },
+ chit = { 1,0.5f,0.2f,0.8f };
+
+ shader_model_menu_use();
+ shader_model_menu_uTexMain( 1 );
+ shader_model_menu_uPv( ortho );
+ shader_model_menu_uColour( cnorm );
+
+ mdl_context *mdl = &control_overlay.mdl;
+ mesh_bind( &mdl->mesh );
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, control_overlay.tex );
+
+ enum player_subsystem subsytem = localplayer.subsystem;
+
+ m4x3f mmdl;
+ m4x3_identity( mmdl );
+
+ bool in_air = 0, grinding = 0;
+
+ if( subsytem == k_player_subsystem_walk )
+ in_air = player_walk.state.activity == k_walk_activity_air;
+ else if( subsytem == k_player_subsystem_skate )
+ in_air = player_skate.state.activity < k_skate_activity_ground;
+
+ grinding = (subsytem == k_player_subsystem_skate) &&
+ (player_skate.state.activity >= k_skate_activity_grind_any);
+
+ if( vg_input.display_input_method == k_input_method_controller )
+ {
+ bool press_jump = player_skate.state.jump_charge > 0.2f;
+ u8 lb_down = 0, rb_down = 0;
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, vg_end
+ }, &rb_down );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, vg_end
+ }, &lb_down );
+ f32 lt_amt = 0.0f, rt_amt = 0.0f;
+ vg_exec_input_program( k_vg_input_type_axis_f32,
+ (vg_input_op[]){ vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERLEFT, vg_end },
+ <_amt );
+ vg_exec_input_program( k_vg_input_type_axis_f32,
+ (vg_input_op[]){ vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, vg_end },
+ &rt_amt );
+
+ /* joystick L */
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ mmdl[3][0] = -r + 0.375f;
+ mmdl[3][2] = 1.0f - 0.375f;
+ shader_model_menu_uMdl( mmdl );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( 0, 1 );
+ render_overlay_mesh( ov_ls_circ_skate );
+
+ colorize( steer[1]>=0.5f, press_jump );
+ render_overlay_mesh( ov_ls_circ_backflip );
+ colorize( steer[1]<=-0.5f, press_jump );
+ render_overlay_mesh( ov_ls_circ_frontflip );
+
+ colorize( steer[1] > 0.7f, !press_jump && !in_air );
+ render_overlay_mesh( ov_ls_circ_manual );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( 0, 1 );
+ render_overlay_mesh( ov_ls_circ_walk );
+ }
+
+ mmdl[3][0] += steer[0]*0.125f*0.75f;
+ mmdl[3][2] += steer[1]*0.125f*0.75f;
+
+ colorize( 0, 1 );
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_ls );
+
+ /* joystick R */
+ mmdl[3][0] = r - 0.375f;
+ mmdl[3][2] = 1.0f - 0.375f;
+ shader_model_menu_uMdl( mmdl );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( rt_amt > 0.5f, in_air );
+ render_overlay_mesh( ov_rs_circ_grab );
+ colorize( 0, in_air );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( 0, 1 );
+ render_overlay_mesh( ov_rs_circ_look );
+ }
+
+ v2f jlook;
+ joystick_state( k_srjoystick_look, jlook );
+
+ mmdl[3][0] += jlook[0]*0.125f*0.75f;
+ mmdl[3][2] += jlook[1]*0.125f*0.75f;
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_rs );
+
+
+
+ /* LEFT UPPERS */
+ mmdl[3][0] = -r;
+ mmdl[3][2] = -1.0f;
+ shader_model_menu_uMdl( mmdl );
+
+ /* LB -------------------------------------------------------------- */
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( lb_down, !in_air );
+ render_overlay_mesh( ov_carve_l );
+ }
+ else
+ colorize( 0, 0 );
+
+ render_overlay_mesh( lb_down? ov_lb_down: ov_lb );
+
+ /* LT ---------------------------------------------------------------- */
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( 0, 0 );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( lt_amt>0.2f, 1 );
+ render_overlay_mesh( ov_lt_run );
+ }
+
+ render_overlay_mesh( ov_lt );
+
+ mmdl[3][2] += lt_amt*0.125f*0.5f;
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_lt_act );
+
+ /* RIGHT UPPERS */
+ mmdl[3][0] = r;
+ mmdl[3][2] = -1.0f;
+ shader_model_menu_uMdl( mmdl );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( rb_down, !in_air );
+ render_overlay_mesh( ov_carve_r );
+ }
+ else
+ colorize( 0, 0 );
+
+ render_overlay_mesh( rb_down? ov_rb_down: ov_rb );
+
+ /* RT ---------------------------------------------------------------- */
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( rt_amt>0.2f, in_air );
+ render_overlay_mesh( ov_rt_grab );
+ colorize( rt_amt>0.2f, !in_air );
+ render_overlay_mesh( ov_rt_crouch );
+ colorize( rt_amt>0.2f, 1 );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( 0, 0 );
+ }
+
+ render_overlay_mesh( ov_rt );
+
+ mmdl[3][2] += rt_amt*0.125f*0.5f;
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_rt_act );
+
+ /* RIGHT SIDE BUTTONS */
+ bool press_a = 0, press_b = 0, press_x = 0, press_y = 0,
+ press_dpad_w = 0, press_dpad_e = 0, press_dpad_n = 0, press_dpad_s = 0,
+ press_menu = 0, press_back = 0;
+
+ bool is_ps = 0;
+ if( (vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS3) ||
+ (vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS4) ||
+ (vg_input.display_input_type == SDL_CONTROLLER_TYPE_PS5) )
+ {
+ is_ps = 1;
+ }
+
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_A, vg_end }, &press_a );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_B, vg_end }, &press_b );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_X, vg_end }, &press_x );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_Y, vg_end }, &press_y );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_LEFT, vg_end }, &press_dpad_w );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, vg_end }, &press_dpad_e );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_UP, vg_end }, &press_dpad_n );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_DPAD_DOWN, vg_end }, &press_dpad_s );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_BACK, vg_end }, &press_back );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_START, vg_end }, &press_menu );
+
+ mmdl[3][0] = r;
+ mmdl[3][2] = 0.0f;
+ shader_model_menu_uMdl( mmdl );
+
+ /* B / KICKFLIP / PUSH */
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( press_b, !in_air );
+ render_overlay_mesh( ov_text_b_push );
+ colorize( press_b, in_air );
+ render_overlay_mesh( ov_text_b_kickflip );
+ colorize( press_b, 1 );
+ }
+ else
+ {
+ colorize( 0, 0 );
+ }
+
+ if( is_ps ) render_overlay_mesh( press_b? ov_b_down_ps: ov_b_ps );
+ else render_overlay_mesh( press_b? ov_b_down: ov_b );
+
+ /* Y / SKATE / WALK / GLIDE */
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ if( localplayer.have_glider )
+ {
+ colorize( press_y, !in_air );
+ render_overlay_mesh( ov_text_y_walk_lwr );
+ colorize( press_y, in_air );
+ render_overlay_mesh( ov_text_y_glide );
+ }
+ else
+ {
+ colorize( press_y, 1 );
+ render_overlay_mesh( ov_text_y_walk );
+ }
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( press_y, player_walk.state.activity < k_walk_activity_inone );
+ render_overlay_mesh( ov_text_y_skate );
+ }
+ else if( subsytem == k_player_subsystem_glide )
+ {
+ colorize( press_y, 1 );
+ render_overlay_mesh( ov_text_y_skate );
+ }
+ else
+ colorize( 0, 0 );
+
+ if( is_ps ) render_overlay_mesh( press_y? ov_y_down_ps: ov_y_ps );
+ else render_overlay_mesh( press_y? ov_y_down: ov_y );
+
+ /* X / TREFLIP */
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( press_x, in_air );
+ render_overlay_mesh( ov_text_x_treflip );
+ }
+ else
+ colorize( press_x, 0 );
+
+ if( is_ps ) render_overlay_mesh( press_x? ov_x_down_ps: ov_x_ps );
+ else render_overlay_mesh( press_x? ov_x_down: ov_x );
+
+ /* A / JUMP / SHUVIT */
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( press_a, !in_air );
+ render_overlay_mesh( ov_text_a_jump );
+ colorize( press_a, in_air );
+ render_overlay_mesh( ov_text_a_shuvit );
+ colorize( press_a, 1 );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( press_a, !in_air );
+ render_overlay_mesh( ov_text_a_jump_mid );
+ }
+
+ if( is_ps ) render_overlay_mesh( press_a? ov_a_down_ps: ov_a_ps );
+ else render_overlay_mesh( press_a? ov_a_down: ov_a );
+
+ /* JUMP CHARGE */
+ if( subsytem == k_player_subsystem_skate )
+ {
+ if( player_skate.state.jump_charge > 0.01f )
+ {
+ mmdl[0][0] = player_skate.state.jump_charge * 0.465193f;
+ mmdl[3][0] += -0.4375f;
+ mmdl[3][2] += 0.09375f;
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_jump_ind );
+ mmdl[0][0] = 1.0f;
+ }
+ }
+
+
+ /* DPAD --------------------------------------------------- */
+
+ mmdl[3][0] = -r;
+ mmdl[3][2] = 0.0f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( 0, 1 );
+ render_overlay_mesh( ov_dpad );
+
+ colorize( press_dpad_e, 1 );
+ render_overlay_mesh( ov_text_de_camera );
+ if( press_dpad_e )
+ render_overlay_mesh( ov_dpad_e );
+
+ colorize( press_dpad_w, 1 );
+ render_overlay_mesh( ov_text_dw_rewind );
+ if( press_dpad_w )
+ render_overlay_mesh( ov_dpad_w );
+
+ if( subsytem == k_player_subsystem_dead )
+ {
+ colorize( press_dpad_n, 1 );
+ render_overlay_mesh( ov_text_dn_respawn );
+ }
+ else colorize( press_dpad_n, 0 );
+ if( press_dpad_n )
+ render_overlay_mesh( ov_dpad_n );
+
+ colorize( press_dpad_s, 0 );
+ if( press_dpad_s )
+ render_overlay_mesh( ov_dpad_s );
+
+
+ /* WEIGHT */
+ if( subsytem == k_player_subsystem_skate )
+ {
+ /* stored indicator text */
+ mmdl[3][0] = r -0.842671f;
+ mmdl[3][2] = -1.0f + 0.435484f;
+ colorize( 0, !in_air );
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_text_stored );
+
+ mmdl[0][0] = v3_length( player_skate.state.throw_v ) / k_mmthrow_scale;
+ shader_model_menu_uMdl( mmdl );
+ colorize( 0, !in_air );
+ render_overlay_mesh( ov_stored_ind );
+
+ static f32 collect = 0.0f;
+ collect = vg_lerpf( collect, player_skate.collect_feedback,
+ vg.time_frame_delta * 15.0f );
+ collect = vg_clampf( collect, 0.0f, 1.0f );
+
+ mmdl[0][0] = collect;
+ mmdl[3][2] += 0.015625f;
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_stored_ind );
+ }
+
+ mmdl[0][0] = 1.0f;
+ mmdl[3][0] = 0.0f;
+ mmdl[3][2] = -1.0f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_menu, 1 );
+ render_overlay_mesh( press_menu? ov_met_r_down: ov_met_r );
+ render_overlay_mesh( ov_text_met_menu );
+
+ colorize( press_back, 0 );
+ render_overlay_mesh( press_back? ov_met_l_down: ov_met_l );
+
+ colorize( 0, 0 );
+ render_overlay_mesh( ov_met );
+ }
+ else
+ {
+ static v2f gd;
+ v2_lerp( gd, player_skate.state.grab_mouse_delta, vg.time_frame_delta*20.0f, gd );
+
+ /* CTRL || CARVE */
+ if( subsytem == k_player_subsystem_skate )
+ {
+ bool press_ctrl = vg_getkey(SDLK_LCTRL);
+ mmdl[3][0] = -r + 0.25f;
+ mmdl[3][2] = 1.0f - 0.125f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_ctrl, !in_air && !grinding );
+ draw_key( press_ctrl, 1 );
+ render_overlay_mesh( ov_text_carve );
+ }
+
+ /* SHIFT || CROUCH / GRAB / RUN */
+ bool press_shift = vg_getkey( SDLK_LSHIFT );
+ if( subsytem == k_player_subsystem_skate ||
+ subsytem == k_player_subsystem_walk )
+ {
+ mmdl[3][0] = -r + 0.25f;
+ mmdl[3][2] = 1.0f - 0.125f - 0.25f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_shift, !grinding );
+ draw_key( press_shift, 1 );
+ render_overlay_mesh( ov_text_shift );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( press_shift, !in_air && !grinding );
+ render_overlay_mesh( ov_text_crouch );
+ colorize( press_shift, in_air && !grinding );
+ render_overlay_mesh( ov_text_grab );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ render_overlay_mesh( ov_text_run );
+ }
+ }
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ /* stored indicator text */
+ mmdl[3][0] = -r + 0.25f + 0.203125f + 0.007812f;
+ colorize( 0, !in_air );
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_text_stored );
+
+ mmdl[0][0] = v3_length( player_skate.state.throw_v ) / k_mmthrow_scale;
+ shader_model_menu_uMdl( mmdl );
+ colorize( 0, !in_air );
+ render_overlay_mesh( ov_stored_ind );
+
+ static f32 collect = 0.0f;
+ collect = vg_lerpf( collect, player_skate.collect_feedback,
+ vg.time_frame_delta * 15.0f );
+ collect = vg_clampf( collect, 0.0f, 1.0f );
+
+ mmdl[0][0] = collect;
+ mmdl[3][2] += 0.015625f;
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_stored_ind );
+ }
+
+ /* -1 */
+ if( subsytem != k_player_subsystem_dead )
+ {
+ bool press_c = vg_getkey(SDLK_c);
+ mmdl[0][0] = 1.0f;
+ mmdl[3][0] = -r + 0.125f + 1.0f;
+ mmdl[3][2] = 1.0f - 0.125f - 0.25f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_c, 1 );
+ draw_key( press_c, 0 );
+ render_overlay_mesh( ov_text_camera );
+ }
+
+ /* +0 */
+ mmdl[0][0] = 1.0f;
+ mmdl[3][2] = 1.0f - 0.125f - 0.25f - 0.25f;
+
+ /* A || LEFT */
+ if( subsytem != k_player_subsystem_dead )
+ {
+ bool press_a = vg_getkey(SDLK_a);
+ mmdl[3][0] = -r + 0.125f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_a, 1 );
+ draw_key( press_a, 0 );
+ render_overlay_mesh( ov_text_left );
+ }
+
+ bool press_jump = player_skate.state.jump_charge < 0.2f;
+
+ /* S || MANUAL / BACKFLIP */
+ bool press_s = vg_getkey(SDLK_s);
+ mmdl[3][0] = -r + 0.125f + 0.25f;
+ shader_model_menu_uMdl( mmdl );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( press_s, !in_air );
+ draw_key( press_s, 0 );
+ render_overlay_mesh( ov_text_s );
+ /* backflip/manual */
+ colorize( press_s, !in_air && !press_jump );
+ render_overlay_mesh( ov_text_back_flip );
+ colorize( press_s, !in_air && press_jump );
+ render_overlay_mesh( ov_text_manual );
+ }
+ else if( subsytem != k_player_subsystem_dead )
+ {
+ colorize( press_s, 1 );
+ draw_key( press_s, 0 );
+ render_overlay_mesh( ov_text_s );
+ render_overlay_mesh( ov_text_back );
+ }
+
+ /* D || RIGHT */
+ if( subsytem != k_player_subsystem_dead )
+ {
+ bool press_d = vg_getkey(SDLK_d);
+ mmdl[3][0] = -r + 0.125f + 0.25f + 0.25f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_d, 1 );
+ draw_key( press_d, 0 );
+ render_overlay_mesh( ov_text_right );
+ }
+
+ /* +1 */
+ mmdl[3][2] = 1.0f - 0.125f - 0.25f - 0.25f - 0.25f;
+
+ /* Q */
+ if( subsytem == k_player_subsystem_dead )
+ {
+ bool press_q = vg_getkey(SDLK_q);
+ mmdl[3][0] = -r + 0.125f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_q, 1 );
+ draw_key( press_q, 0 );
+ render_overlay_mesh( ov_text_respawn );
+ }
+
+ /* W || PUSH / FRONTFLIP */
+ bool press_w = vg_getkey(SDLK_w);
+ mmdl[3][0] = -r + 0.125f + 0.25f;
+ shader_model_menu_uMdl( mmdl );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( press_w, !in_air );
+ draw_key( press_w, 0 );
+ render_overlay_mesh( ov_text_w );
+ /* frontflip/push */
+ colorize( press_w, !in_air && !press_jump );
+ render_overlay_mesh( ov_text_front_flip );
+ colorize( press_w, !in_air && press_jump );
+ render_overlay_mesh( ov_text_push );
+ }
+ else if( subsytem != k_player_subsystem_dead )
+ {
+ colorize( press_w, 1 );
+ draw_key( press_w, 0 );
+ render_overlay_mesh( ov_text_w );
+ render_overlay_mesh( ov_text_forward );
+ }
+
+ /* E */
+ bool press_e = vg_getkey(SDLK_e);
+ mmdl[3][0] = -r + 0.125f + 0.25f + 0.25f;
+
+ shader_model_menu_uMdl( mmdl );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ if( localplayer.have_glider )
+ {
+ colorize( press_e, !in_air );
+ render_overlay_mesh( ov_text_walk_lwr );
+ colorize( press_e, in_air );
+ render_overlay_mesh( ov_text_glide );
+ }
+ else
+ {
+ colorize( press_e, 1 );
+ render_overlay_mesh( ov_text_walk );
+ }
+ }
+ else if( subsytem == k_player_subsystem_glide )
+ {
+ colorize( press_e, 1 );
+ render_overlay_mesh( ov_text_skate );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( press_e, player_walk.state.activity < k_walk_activity_inone );
+ render_overlay_mesh( ov_text_skate );
+ }
+
+ if( subsytem != k_player_subsystem_dead )
+ {
+ draw_key( press_e, 0 );
+ render_overlay_mesh( ov_text_e );
+ }
+
+ /* R */
+ bool press_r = vg_getkey(SDLK_r);
+ mmdl[3][0] = -r + 0.125f + 0.25f + 0.25f + 0.25f;
+ shader_model_menu_uColour( cnorm );
+ shader_model_menu_uMdl( mmdl );
+
+ colorize( press_r, 1 );
+ draw_key( press_r, 0 );
+ render_overlay_mesh( ov_text_rewind );
+
+ /* space */
+ bool press_space = vg_getkey(SDLK_SPACE);
+ mmdl[3][0] = 0.0f;
+ mmdl[3][2] = 1.0f - 0.125f;
+
+
+ if( subsytem == k_player_subsystem_skate ||
+ subsytem == k_player_subsystem_walk )
+ {
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_space, !in_air );
+
+ render_overlay_mesh( press_space?
+ ov_space_down: ov_space );
+ render_overlay_mesh( ov_text_jump );
+ }
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ if( player_skate.state.jump_charge > 0.01f )
+ {
+ mmdl[0][0] = player_skate.state.jump_charge;
+ mmdl[3][0] = -0.4375f;
+ shader_model_menu_uMdl( mmdl );
+ render_overlay_mesh( ov_jump_ind );
+ }
+ }
+
+ bool press_esc = vg_getkey(SDLK_ESCAPE);
+ mmdl[0][0] = 1.0f;
+ mmdl[3][0] = -r + 0.125f;;
+ mmdl[3][2] = -1.0f + 0.125f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_esc, 1 );
+ render_overlay_mesh( ov_text_menu );
+ render_overlay_mesh( press_esc? ov_key_menu_down: ov_key_menu );
+ mmdl[3][0] = r - 0.38f;
+ mmdl[3][2] = 0.0f;
+ shader_model_menu_uMdl( mmdl );
+ colorize( press_shift, in_air );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ render_overlay_mesh( ov_mouse_grabs );
+
+ if( in_air && press_shift )
+ {
+ mmdl[3][0] += gd[0]*0.125f;
+ mmdl[3][2] += gd[1]*0.125f;
+ }
+ }
+
+ shader_model_menu_uMdl( mmdl );
+
+ bool lmb = button_press( k_srbind_trick0 ),
+ rmb = button_press( k_srbind_trick1 );
+
+ if( subsytem == k_player_subsystem_skate )
+ {
+ colorize( 0, press_space || in_air );
+ render_overlay_mesh( ov_mouse );
+
+ colorize( lmb&&!rmb, press_space || in_air );
+ render_overlay_mesh( ov_text_shuvit );
+
+ colorize( lmb, press_space || in_air );
+ render_overlay_mesh( lmb? ov_lmb_down: ov_lmb );
+
+ colorize( rmb&&!lmb, press_space || in_air );
+ render_overlay_mesh( ov_text_kickflip );
+
+ colorize( rmb, press_space || in_air );
+ render_overlay_mesh( rmb? ov_rmb_down: ov_rmb );
+
+ colorize( rmb&&lmb, press_space || in_air );
+ render_overlay_mesh( ov_text_treflip );
+ }
+ else if( subsytem == k_player_subsystem_walk )
+ {
+ colorize( 0, 1 );
+ render_overlay_mesh( ov_mouse );
+ render_overlay_mesh( ov_text_look );
+
+ render_overlay_mesh( lmb? ov_lmb_down: ov_lmb );
+ render_overlay_mesh( rmb? ov_rmb_down: ov_rmb );
+ }
+ }
+}
--- /dev/null
+#pragma once
+
+enum control_overlay_mesh
+{
+ #include "control_overlay.h.generated"
+};
+
+struct control_overlay
+{
+ mdl_context mdl;
+ GLuint tex;
+ i32 enabled;
+}
+extern control_overlay;
+
+void control_overlay_render(void);
+void control_overlay_init(void);
--- /dev/null
+#pragma once
+#include "vg/vg_m.h"
+#include "vg/vg_framebuffer.h"
+#include "skaterift.h"
+#include "render.h"
+
+static inline void depth_compare_bind(
+ void (*uTexSceneDepth)(int),
+ void (*uInverseRatioDepth)(v3f),
+ void (*uInverseRatioMain)(v3f),
+ vg_camera *cam )
+{
+ uTexSceneDepth( 5 );
+ vg_framebuffer_bind_texture( g_render.fb_main, 2, 5 );
+ v3f inverse;
+ vg_framebuffer_inverse_ratio( g_render.fb_main, inverse );
+ inverse[2] = g_render.cam.farz-g_render.cam.nearz;
+
+ uInverseRatioDepth( inverse );
+ vg_framebuffer_inverse_ratio( NULL, inverse );
+ inverse[2] = cam->farz-cam->nearz;
+ uInverseRatioMain( inverse );
+}
--- /dev/null
+#include "entity.h"
+
+void ent_camera_unpack( ent_camera *ent, vg_camera *cam )
+{
+ v3f dir = {0.0f,-1.0f,0.0f};
+ mdl_transform_vector( &ent->transform, dir, dir );
+ v3_angles( dir, cam->angles );
+ v3_copy( ent->transform.co, cam->pos );
+ cam->fov = ent->fov;
+}
--- /dev/null
+#include "entity.h"
+
+void ent_camera_unpack( ent_camera *ent, vg_camera *cam );
--- /dev/null
+#include "vg/vg_engine.h"
+#include "entity.h"
+#include "input.h"
+#include "gui.h"
+#include "audio.h"
+
+entity_call_result ent_challenge_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
+
+ if( call->function == 0 ) /* unlock() */
+ {
+ if( !challenge->status )
+ {
+ vg_info( "challenge( '%s' )\n",
+ mdl_pstr( &world->meta, challenge->pstr_alias) );
+ ent_call call;
+ call.data = NULL;
+ call.function = challenge->target_event;
+ call.id = challenge->target;
+ entity_call( world, &call );
+ }
+ challenge->status = 1;
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == 1 ) /* view() */
+ {
+ if( (localplayer.subsystem == k_player_subsystem_walk) &&
+ (world_static.challenge_target == NULL) )
+ {
+ world_static.challenge_target = NULL;
+ world_entity_set_focus( call->id );
+ world_entity_focus_modal();
+
+ gui_helper_clear();
+ vg_str text;
+ if( gui_new_helper( input_button_list[k_srbind_maccept], &text ))
+ vg_strcat( &text, "Start" );
+ if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
+ vg_strcat( &text, "Exit" );
+ }
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
+
+void ent_challenge_preupdate( ent_focus_context *ctx )
+{
+ world_instance *world = ctx->world;
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, ctx->index );
+
+ /* maximum distance from active challenge */
+ if( !ctx->active )
+ {
+ f32 min_dist2 = 999999.9f;
+
+ if( mdl_entity_id_type( challenge->first ) == k_ent_objective )
+ {
+ u32 next = challenge->first;
+ while( mdl_entity_id_type(next) == k_ent_objective ){
+ u32 index = mdl_entity_id_id( next );
+ ent_objective *objective = mdl_arritm(&world->ent_objective,index);
+ next = objective->id_next;
+
+ f32 d2 = v3_dist2( localplayer.rb.co, objective->transform.co );
+ if( d2 < min_dist2 )
+ min_dist2 = d2;
+ }
+ }
+
+ f32 max_dist = 100.0f;
+ if( min_dist2 > max_dist*max_dist ){
+ world_static.challenge_target = NULL;
+ world_static.challenge_timer = 0.0f;
+ world_entity_clear_focus();
+ audio_lock();
+ audio_oneshot_3d( &audio_challenge[6], localplayer.rb.co,
+ 30.0f, 1.0f );
+ audio_unlock();
+ }
+ return;
+ }
+
+ world_entity_focus_camera( world, challenge->camera );
+
+ if( mdl_entity_id_type( challenge->first ) == k_ent_objective ){
+ if( button_down( k_srbind_maccept ) ){
+ u32 index = mdl_entity_id_id( challenge->first );
+ world_static.challenge_target = mdl_arritm( &world->ent_objective,
+ index );
+ world_static.challenge_timer = 0.0f;
+ world_entity_exit_modal();
+ gui_helper_clear();
+
+ u32 next = challenge->first;
+ while( mdl_entity_id_type(next) == k_ent_objective ){
+ u32 index = mdl_entity_id_id( next );
+ ent_objective *objective = mdl_arritm(&world->ent_objective,index);
+ objective->flags &= ~k_ent_objective_passed;
+ next = objective->id_next;
+ v3_fill( objective->transform.s, 1.0f );
+ }
+ audio_lock();
+ audio_oneshot( &audio_challenge[5], 1.0f, 0.0f );
+ audio_unlock();
+ return;
+ }
+ }
+
+ if( button_down( k_srbind_mback ) )
+ {
+ world_static.challenge_target = NULL;
+ world_entity_exit_modal();
+ world_entity_clear_focus();
+ gui_helper_clear();
+ audio_lock();
+ audio_oneshot( &audio_challenge[4], 1.0f, 0.0f );
+ audio_unlock();
+ return;
+ }
+}
+
+static void ent_challenge_render( ent_challenge *challenge ){
+
+}
--- /dev/null
+#pragma once
+#include "entity.h"
+
+void ent_challenge_preupdate( ent_focus_context *ctx );
+entity_call_result ent_challenge_call( world_instance *world, ent_call *call );
--- /dev/null
+#pragma once
+#include "entity.h"
+#include "player_glide.h"
+
+entity_call_result ent_glider_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_glider *glider = mdl_arritm( &world->ent_glider, index );
+
+ if( call->function == 0 )
+ {
+ glider->flags |= 0x1;
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == 1 )
+ {
+ if( glider->flags & 0x1 )
+ {
+ player_glide_equip_glider();
+ }
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
--- /dev/null
+#pragma once
+#include "entity.h"
+
+entity_call_result ent_glider_call( world_instance *world, ent_call *call );
--- /dev/null
+#include "entity.h"
+#include "ent_miniworld.h"
+#include "world_render.h"
+#include "world_load.h"
+#include "input.h"
+#include "gui.h"
+#include "menu.h"
+#include "audio.h"
+
+struct global_miniworld global_miniworld;
+
+entity_call_result ent_miniworld_call( world_instance *world, ent_call *call )
+{
+ ent_miniworld *miniworld = mdl_arritm( &world->ent_miniworld,
+ mdl_entity_id_id(call->id) );
+
+ int world_id = world - world_static.instances;
+
+ if( call->function == 0 ) /* zone() */
+ {
+ const char *uid = mdl_pstr( &world->meta, miniworld->pstr_world );
+ skaterift_load_world_command( 1, (const char *[]){ uid } );
+
+ mdl_transform_m4x3( &miniworld->transform, global_miniworld.mmdl );
+ global_miniworld.active = miniworld;
+
+ gui_helper_clear();
+ vg_str text;
+
+ if( gui_new_helper( input_button_list[k_srbind_miniworld_resume], &text ))
+ vg_strcat( &text, "Enter World" );
+
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == 1 )
+ {
+ global_miniworld.active = NULL;
+ gui_helper_clear();
+
+ if( miniworld->proxy )
+ {
+ ent_prop *prop = mdl_arritm( &world->ent_prop,
+ mdl_entity_id_id(miniworld->proxy) );
+ prop->flags &= ~0x1;
+ }
+
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
+
+static void miniworld_icon( vg_camera *cam, enum gui_icon icon,
+ v3f pos, f32 size)
+{
+ m4x3f mmdl;
+ v3_copy( cam->transform[2], mmdl[2] );
+ mmdl[2][1] = 0.0f;
+ v3_normalize( mmdl[2] );
+ v3_copy( (v3f){0,1,0}, mmdl[1] );
+ v3_cross( mmdl[1], mmdl[2], mmdl[0] );
+ m4x3_mulv( global_miniworld.mmdl, pos, mmdl[3] );
+
+ shader_model_font_uMdl( mmdl );
+ shader_model_font_uOffset( (v4f){0,0,0,20.0f*size} );
+
+ m4x4f m4mdl;
+ m4x3_expand( mmdl, m4mdl );
+ m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
+ shader_model_font_uPvmPrev( m4mdl );
+
+ mdl_submesh *sm = gui.icons[ icon ];
+ if( sm )
+ mdl_draw_submesh( sm );
+}
+
+void ent_miniworld_render( world_instance *host_world, vg_camera *cam )
+{
+ if( host_world != &world_static.instances[k_world_purpose_hub] )
+ return;
+
+ ent_miniworld *miniworld = global_miniworld.active;
+
+ if( !miniworld )
+ return;
+
+ world_instance *dest_world = &world_static.instances[k_world_purpose_client];
+
+ int rendering = 1;
+ if( dest_world->status != k_world_status_loaded )
+ rendering = 0;
+
+ if( miniworld->proxy ){
+ ent_prop *prop = mdl_arritm( &host_world->ent_prop,
+ mdl_entity_id_id(miniworld->proxy) );
+ if( !rendering )
+ prop->flags &= ~0x1;
+ else
+ prop->flags |= 0x1;
+ }
+
+
+ if( !rendering )
+ return;
+
+ render_world_override( dest_world, host_world, global_miniworld.mmdl, cam,
+ NULL, (v4f){dest_world->tar_min,10000.0f,0.0f,0.0f} );
+ render_world_routes( dest_world, host_world,
+ global_miniworld.mmdl, cam, 0, 1 );
+
+ /* icons
+ * ---------------------*/
+ font3d_bind( &gui.font, k_font_shader_default, 0, NULL, cam );
+ mesh_bind( &gui.icons_mesh );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, gui.icons_texture );
+ shader_model_font_uTexMain( 0 );
+ shader_model_font_uColour( (v4f){1,1,1,1} );
+
+ miniworld_icon( cam, k_gui_icon_player, dest_world->player_co,
+ 1.0f + sinf(vg.time)*0.2f );
+
+ for( u32 i=0; i<mdl_arrcount(&dest_world->ent_challenge); i++ ){
+ ent_challenge *challenge = mdl_arritm( &dest_world->ent_challenge, i );
+
+ enum gui_icon icon = k_gui_icon_exclaim;
+ if( challenge->status )
+ icon = k_gui_icon_tick;
+
+ miniworld_icon( cam, icon, challenge->transform.co, 1.0f );
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&dest_world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &dest_world->ent_route, i );
+
+ if( route->flags & k_ent_route_flag_achieve_gold ){
+ miniworld_icon( cam, k_gui_icon_rift_run_gold,
+ route->board_transform[3],1.0f);
+ }
+ else if( route->flags & k_ent_route_flag_achieve_silver ){
+ miniworld_icon( cam, k_gui_icon_rift_run_silver,
+ route->board_transform[3],1.0f);
+ }
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&dest_world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &dest_world->ent_route, i );
+
+ v4f colour;
+ v4_copy( route->colour, colour );
+ v3_muls( colour, 1.6666f, colour );
+ shader_model_font_uColour( colour );
+ miniworld_icon( cam, k_gui_icon_rift_run, route->board_transform[3],1.0f);
+ }
+}
+
+void ent_miniworld_preupdate(void)
+{
+ world_instance *hub = world_current_instance(),
+ *dest = &world_static.instances[k_world_purpose_client];
+
+ ent_miniworld *miniworld = global_miniworld.active;
+
+ if( (localplayer.subsystem != k_player_subsystem_walk) ||
+ (global_miniworld.transition) ||
+ (world_static.active_instance != k_world_purpose_hub) ||
+ (!miniworld) ||
+ (dest->status != k_world_status_loaded) ||
+ (skaterift.activity != k_skaterift_default)) {
+ return;
+ }
+
+ if( button_down( k_srbind_miniworld_resume ) )
+ {
+ if( skaterift.demo_mode )
+ {
+ if( world_static.instance_addons[1]->flags & ADDON_REG_PREMIUM )
+ {
+ menu_open( k_menu_page_premium );
+ return;
+ }
+ }
+
+ global_miniworld.transition = 1;
+ global_miniworld.t = 0.0f;
+ global_miniworld.cam = g_render.cam;
+
+ world_switch_instance(1);
+ srinput.state = k_input_state_resume;
+ menu.disable_open = 0;
+ gui_helper_clear();
+ audio_lock();
+ audio_oneshot( &audio_ui[2], 1.0f, 0.0f );
+ audio_unlock();
+ }
+}
+
+void ent_miniworld_goback(void)
+{
+ audio_lock();
+ audio_oneshot( &audio_ui[2], 1.0f, 0.0f );
+ audio_unlock();
+
+ global_miniworld.transition = -1;
+ global_miniworld.t = 1.0f;
+
+ global_miniworld.cam = g_render.cam;
+ vg_m4x3_transform_camera( global_miniworld.mmdl, &global_miniworld.cam );
+ world_switch_instance(0);
+}
--- /dev/null
+#pragma once
+#include "entity.h"
+
+struct global_miniworld
+{
+ ent_miniworld *active;
+ int transition;
+ f32 t;
+
+ m4x3f mmdl;
+ vg_camera cam;
+}
+extern global_miniworld;
+
+entity_call_result ent_miniworld_call( world_instance *world, ent_call *call );
+void ent_miniworld_render( world_instance *host_world, vg_camera *cam );
+void ent_miniworld_goback(void);
+void ent_miniworld_preupdate(void);
--- /dev/null
+#include "vg/vg_mem.h"
+#include "ent_npc.h"
+#include "shaders/model_character_view.h"
+#include "input.h"
+#include "player.h"
+#include "gui.h"
+
+struct npc npc_gumpa, npc_slowmo, npc_volc_flight;
+static struct skeleton_anim *gumpa_idle;
+static struct skeleton_anim *slowmo_momentum, *slowmo_slide, *slowmo_rewind,
+ *anim_tutorial_cam;
+static float slowmo_opacity = 0.0f;
+static f64 volc_start_preview = 0.0;
+
+void npc_load_model( struct npc *npc, const char *path )
+{
+ vg_linear_clear( vg_mem.scratch );
+
+ mdl_context *meta = &npc->meta;
+ mdl_open( meta, path, vg_mem.rtmemory );
+ mdl_load_metadata_block( meta, vg_mem.rtmemory );
+ mdl_load_animation_block( meta, vg_mem.rtmemory );
+
+ struct skeleton *sk = &npc->skeleton;
+ skeleton_setup( sk, vg_mem.rtmemory, meta );
+
+ u32 mtx_size = sizeof(m4x3f)*sk->bone_count;
+ npc->final_mtx = vg_linear_alloc( vg_mem.rtmemory, mtx_size );
+
+ if( mdl_arrcount( &meta->textures ) )
+ {
+ mdl_texture *tex0 = mdl_arritm( &meta->textures, 0 );
+ void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
+ mdl_fread_pack_file( meta, &tex0->file, data );
+
+ vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
+ VG_TEX2D_NEAREST|VG_TEX2D_CLAMP,
+ &npc->texture );
+ }
+ else
+ {
+ npc->texture = vg.tex_missing;
+ }
+
+ mdl_async_load_glmesh( meta, &npc->mesh, NULL );
+ mdl_close( meta );
+}
+
+void npc_init(void)
+{
+ npc_load_model( &npc_gumpa, "models/gumpa.mdl" );
+ gumpa_idle = skeleton_get_anim( &npc_gumpa.skeleton, "gumpa_idle" );
+
+ npc_load_model( &npc_slowmo, "models/slowmos.mdl" );
+ slowmo_momentum =
+ skeleton_get_anim( &npc_slowmo.skeleton, "slowmo_momentum" );
+ slowmo_slide = skeleton_get_anim( &npc_slowmo.skeleton, "slowmo_slide" );
+ slowmo_rewind = skeleton_get_anim( &npc_slowmo.skeleton, "slowmo_rewind" );
+
+ npc_load_model( &npc_volc_flight, "models/volc_flight.mdl" );
+ anim_tutorial_cam =
+ skeleton_get_anim( &npc_volc_flight.skeleton, "tutorial" );
+}
+
+static struct npc *npc_resolve( u32 id )
+{
+ if( id == 1 ) return &npc_gumpa;
+ else if( id == 2 ) return &npc_slowmo;
+ else if( id == 3 ) return &npc_volc_flight;
+ else return NULL;
+}
+
+static entity_call_result npc_slowmo_call( ent_npc *npc, ent_call *call )
+{
+ if( call->function == 0 )
+ {
+ gui_helper_clear();
+ vg_str text;
+
+ if( npc->context == 2 )
+ {
+ if( gui_new_helper( input_axis_list[k_sraxis_grab], &text ))
+ vg_strcat( &text, "Crouch (store energy)" );
+ if( gui_new_helper( input_joy_list[k_srjoystick_steer], &text ))
+ vg_strcat( &text, "Slide" );
+ }
+ else if( npc->context == 1 )
+ {
+ if( gui_new_helper( input_axis_list[k_sraxis_grab], &text ))
+ vg_strcat( &text, "Crouch (store energy)" );
+ }
+ else if( npc->context == 3 )
+ {
+ if( gui_new_helper( input_button_list[k_srbind_reset], &text ))
+ vg_strcat( &text, "Rewind time" );
+ if( gui_new_helper( input_button_list[k_srbind_replay_resume], &text ))
+ vg_strcat( &text, "Resume" );
+ }
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == -1 )
+ {
+ world_entity_clear_focus();
+ gui_helper_clear();
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
+
+entity_call_result ent_npc_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_npc *npc = mdl_arritm( &world->ent_npc, index );
+
+ if( npc->id == 2 )
+ {
+ return npc_slowmo_call( npc, call );
+ }
+ else if( npc->id == 3 )
+ {
+ if( call->function == 0 )
+ {
+ world_entity_set_focus( call->id );
+ gui_helper_clear();
+ vg_str text;
+ if( gui_new_helper( input_button_list[k_srbind_maccept], &text ))
+ vg_strcat( &text, "Preview course" );
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == -1 )
+ {
+ world_entity_clear_focus();
+ gui_helper_clear();
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+ }
+ else if( npc->id == 4 )
+ {
+ if( call->function == 0 )
+ {
+ gui_helper_clear();
+ vg_str text;
+ if( gui_new_helper( input_button_list[k_srbind_camera], &text ))
+ vg_strcat( &text, "First/Thirdperson" );
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == -1 )
+ {
+ gui_helper_clear();
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+ }
+ else
+ {
+ if( call->function == 0 )
+ {
+ world_entity_set_focus( call->id );
+ gui_helper_clear();
+ vg_str text;
+ if( gui_new_helper( input_button_list[k_srbind_maccept], &text ))
+ vg_strcat( &text, "Talk to ???" );
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == -1 )
+ {
+ world_entity_clear_focus();
+ gui_helper_clear();
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+ }
+}
+
+void ent_npc_preupdate( ent_focus_context *ctx )
+{
+ world_instance *world = ctx->world;
+ ent_npc *ent = mdl_arritm( &world->ent_npc, ctx->index );
+
+ if( !ctx->active )
+ {
+ if( button_down(k_srbind_maccept) )
+ {
+ world_entity_focus_modal();
+ gui_helper_clear();
+ vg_str text;
+ if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
+ vg_strcat( &text, "leave" );
+
+ volc_start_preview = vg.time;
+ }
+
+ return;
+ }
+
+ if( ent->id == 3 )
+ {
+ player_pose pose;
+ struct skeleton *sk = &npc_volc_flight.skeleton;
+
+ f64 t = (vg.time - volc_start_preview) * 0.5;
+ skeleton_sample_anim_clamped( sk, anim_tutorial_cam, t, pose.keyframes );
+
+ ent_camera *cam = mdl_arritm( &world->ent_camera,
+ mdl_entity_id_id(ent->camera) );
+ v3_copy( pose.keyframes[0].co, cam->transform.co );
+
+ v4f qp;
+ q_axis_angle( qp, (v3f){1,0,0}, VG_TAUf*0.25f );
+ q_mul( pose.keyframes[0].q, qp, cam->transform.q );
+ q_normalize( cam->transform.q );
+
+ v3_add( ent->transform.co, cam->transform.co, cam->transform.co );
+ }
+
+ world_entity_focus_camera( world, ent->camera );
+
+ if( button_down( k_srbind_mback ) )
+ {
+ world_entity_exit_modal();
+ world_entity_clear_focus();
+ gui_helper_clear();
+ }
+}
+
+void npc_update( ent_npc *ent )
+{
+ if( ent->id == 3 ) return;
+ if( ent->id == 4 ) return;
+
+ struct npc *npc_def = npc_resolve( ent->id );
+ VG_ASSERT( npc_def );
+
+ player_pose pose;
+ struct skeleton *sk = &npc_def->skeleton;
+ pose.type = k_player_pose_type_ik;
+ pose.board.lean = 0.0f;
+
+ if( ent->id == 1 )
+ {
+ skeleton_sample_anim( sk, gumpa_idle, vg.time, pose.keyframes );
+ }
+ else if( ent->id == 2 )
+ {
+ struct skeleton_anim *anim = NULL;
+ if( ent->context == 1 ) anim = slowmo_momentum;
+ else if( ent->context == 2 ) anim = slowmo_slide;
+ else if( ent->context == 3 ) anim = slowmo_rewind;
+
+ VG_ASSERT( anim );
+
+ f32 t = vg.time*0.5f,
+ animtime = fmodf( t*anim->rate, anim->length ),
+ lt = animtime / (f32)anim->length;
+ skeleton_sample_anim( sk, anim, t, pose.keyframes );
+ slowmo_opacity = vg_clampf(fabsf(lt-0.5f)*9.0f-3.0f,0,1);
+ }
+
+ v3_copy( ent->transform.co, pose.root_co );
+ v4_copy( ent->transform.q, pose.root_q );
+ apply_full_skeleton_pose( &npc_def->skeleton, &pose, npc_def->final_mtx );
+}
+
+void npc_render( ent_npc *ent, world_instance *world, vg_camera *cam )
+{
+ if( ent->id == 3 ) return;
+ if( ent->id == 4 ) return;
+
+ struct npc *npc_def = npc_resolve( ent->id );
+ VG_ASSERT( npc_def );
+
+ shader_model_character_view_use();
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, npc_def->texture );
+ shader_model_character_view_uTexMain( 0 );
+ shader_model_character_view_uCamera( cam->transform[3] );
+ shader_model_character_view_uPv( cam->mtx.pv );
+
+ if( ent->id == 2 )
+ {
+ shader_model_character_view_uDepthMode( 2 );
+ shader_model_character_view_uDitherCutoff( slowmo_opacity );
+ }
+ else
+ {
+ shader_model_character_view_uDepthMode( 0 );
+ }
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_character_view );
+
+ glUniformMatrix4x3fv( _uniform_model_character_view_uTransforms,
+ npc_def->skeleton.bone_count,
+ 0,
+ (const GLfloat *)npc_def->final_mtx );
+
+ mesh_bind( &npc_def->mesh );
+ mesh_draw( &npc_def->mesh );
+}
--- /dev/null
+#pragma once
+#include "player_render.h"
+#include "entity.h"
+
+struct npc
+{
+ glmesh mesh;
+ GLuint texture;
+
+ mdl_context meta;
+ struct skeleton skeleton;
+
+ m4x3f *final_mtx;
+}
+extern npc_gumpa;
+
+enum npc_id
+{
+ k_npc_id_none = 0,
+ k_npc_id_gumpa = 1
+};
+
+void npc_load_model( struct npc *npc, const char *path );
+void ent_npc_preupdate( ent_focus_context *context );
+entity_call_result ent_npc_call( world_instance *world, ent_call *call );
+void npc_update( ent_npc *ent );
+void npc_render( ent_npc *ent, world_instance *world, vg_camera *cam );
+void npc_init(void);
--- /dev/null
+#include "world.h"
+#include "world_load.h"
+#include "entity.h"
+#include "audio.h"
+#include "steam.h"
+#include "ent_region.h"
+#include "player.h"
+#include "player_skate.h"
+
+static void ent_objective_pass( world_instance *world,
+ ent_objective *objective ){
+ if( objective->id_next ){
+ world_static.challenge_timer += objective->filter;
+
+ u32 index = mdl_entity_id_id( objective->id_next );
+ ent_objective *next = mdl_arritm( &world->ent_objective, index );
+ world_static.challenge_target = next;
+ objective->flags |= k_ent_objective_passed;
+
+ if( next->filter & k_ent_objective_filter_passthrough )
+ ent_objective_pass( world, next );
+ else{
+ vg_info( "pass challenge point\n" );
+ audio_lock();
+ audio_oneshot_3d( &audio_challenge[0], localplayer.rb.co,
+ 30.0f, 1.0f );
+ audio_unlock();
+ }
+ }
+ else {
+ vg_success( "challenge win\n" );
+ audio_lock();
+ audio_oneshot( &audio_challenge[2], 1.0f, 0.0f );
+ audio_unlock();
+ world_static.challenge_target = NULL;
+ world_static.challenge_timer = 0.0f;
+ world_static.focused_entity = 0;
+
+ if( objective->id_win ){
+ ent_call call;
+ call.data = NULL;
+ call.function = objective->win_event;
+ call.id = objective->id_win;
+ entity_call( world, &call );
+ }
+
+ ent_region_re_eval( world );
+ }
+}
+
+static int ent_objective_check_filter( ent_objective *objective ){
+ if( objective->filter ){
+ struct player_skate_state *s = &player_skate.state;
+ enum trick_type trick = s->trick_type;
+
+ u32 state = 0x00;
+
+ if( trick == k_trick_type_shuvit )
+ state |= k_ent_objective_filter_trick_shuvit;
+ if( trick == k_trick_type_treflip )
+ state |= k_ent_objective_filter_trick_treflip;
+ if( trick == k_trick_type_kickflip )
+ state |= k_ent_objective_filter_trick_kickflip;
+
+ if( s->flip_rate < -0.0001f ) state |= k_ent_objective_filter_flip_back;
+ if( s->flip_rate > 0.0001f ) state |= k_ent_objective_filter_flip_front;
+
+ if( s->activity == k_skate_activity_grind_5050 ||
+ s->activity == k_skate_activity_grind_back50 ||
+ s->activity == k_skate_activity_grind_front50 )
+ state |= k_ent_objective_filter_grind_truck_any;
+
+ if( s->activity == k_skate_activity_grind_boardslide )
+ state |= k_ent_objective_filter_grind_board_any;
+
+ return ((objective->filter & state) || !objective->filter) &&
+ ((objective->filter2 & state) || !objective->filter2);
+ }
+ else {
+ return 1;
+ }
+}
+
+entity_call_result ent_objective_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_objective *objective = mdl_arritm( &world->ent_objective, index );
+
+ if( call->function == 0 )
+ {
+ if( objective->flags & (k_ent_objective_hidden|k_ent_objective_passed))
+ {
+ return k_entity_call_result_OK;
+ }
+
+ if( world_static.challenge_target )
+ {
+ if( (world_static.challenge_target == objective) &&
+ ent_objective_check_filter( objective )){
+ ent_objective_pass( world, objective );
+ }
+ else
+ {
+ audio_lock();
+ audio_oneshot_3d( &audio_challenge[6], localplayer.rb.co,
+ 30.0f, 1.0f );
+ audio_unlock();
+ vg_error( "challenge failed\n" );
+ world_static.challenge_target = NULL;
+ world_static.challenge_timer = 0.0f;
+ world_static.focused_entity = 0;
+ }
+ }
+
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == 2 )
+ {
+ objective->flags &= ~k_ent_objective_hidden;
+
+ if( mdl_entity_id_type( objective->id_next ) == k_ent_objective ){
+ call->id = objective->id_next;
+ entity_call( world, call );
+ }
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == 3 )
+ {
+ objective->flags |= k_ent_objective_hidden;
+
+ if( mdl_entity_id_type( objective->id_next ) == k_ent_objective ){
+ call->id = objective->id_next;
+ entity_call( world, call );
+ }
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
--- /dev/null
+#pragma once
+#include "entity.h"
+#include "world.h"
+entity_call_result ent_objective_call( world_instance *world, ent_call *call );
--- /dev/null
+#include "ent_region.h"
+#include "gui.h"
+#include "network_common.h"
+#include "network.h"
+
+struct global_ent_region global_ent_region;
+
+u32 region_spark_colour( u32 flags )
+{
+ if( flags & k_ent_route_flag_achieve_gold )
+ return 0xff8ce0fa;
+ else if( flags & k_ent_route_flag_achieve_silver )
+ return 0xffc2c2c2;
+ else
+ return 0x00;
+}
+
+entity_call_result ent_region_call( world_instance *world, ent_call *call )
+{
+ ent_region *region =
+ mdl_arritm( &world->ent_region, mdl_entity_id_id(call->id) );
+
+ if( !region->zone_volume )
+ return k_entity_call_result_invalid;
+
+ ent_volume *volume =
+ mdl_arritm( &world->ent_volume, mdl_entity_id_id(region->zone_volume) );
+
+ if( call->function == 0 ) /* enter */
+ {
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i ++ )
+ {
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ v3f local;
+ m4x3_mulv( volume->to_local, route->board_transform[3], local );
+ if( (fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f) )
+ {
+ route->flags &= ~k_ent_route_flag_out_of_zone;
+ }
+ else
+ {
+ route->flags |= k_ent_route_flag_out_of_zone;
+ }
+ }
+
+ gui_location_print_ccmd( 1, (const char *[]){
+ mdl_pstr(&world->meta,region->pstr_title)} );
+
+ vg_strncpy( mdl_pstr(&world->meta,region->pstr_title),
+ global_ent_region.location, NETWORK_REGION_MAX,
+ k_strncpy_always_add_null );
+ global_ent_region.flags = region->flags;
+ network_send_region();
+
+ localplayer.effect_data.spark.colour = region_spark_colour(region->flags);
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == 1 ) /* leave */
+ {
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i ++ )
+ {
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+ route->flags |= k_ent_route_flag_out_of_zone;
+ }
+ localplayer.effect_data.spark.colour = 0x00;
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
+
+/*
+ * reevaluate all achievements to calculate the compiled achievement
+ */
+void ent_region_re_eval( world_instance *world )
+{
+ u32 world_total = k_ent_route_flag_achieve_gold |
+ k_ent_route_flag_achieve_silver;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_region); i ++ ){
+ ent_region *region = mdl_arritm(&world->ent_region, i);
+
+ if( !region->zone_volume )
+ continue;
+
+ ent_volume *volume = mdl_arritm(&world->ent_volume,
+ mdl_entity_id_id(region->zone_volume));
+
+ u32 combined = k_ent_route_flag_achieve_gold |
+ k_ent_route_flag_achieve_silver;
+
+ for( u32 j=0; j<mdl_arrcount(&world->ent_route); j ++ ){
+ ent_route *route = mdl_arritm(&world->ent_route, j );
+
+ v3f local;
+ m4x3_mulv( volume->to_local, route->board_transform[3], local );
+ if( !((fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f)) ){
+ continue;
+ }
+
+ combined &= route->flags;
+ }
+
+ for( u32 j=0; j<mdl_arrcount(&world->ent_challenge); j ++ ){
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, j );
+
+ v3f local;
+ m4x3_mulv( volume->to_local, challenge->transform.co, local );
+ if( !((fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f)) ){
+ continue;
+ }
+
+ u32 flags = 0x00;
+ if( challenge->status ){
+ flags |= k_ent_route_flag_achieve_gold;
+ flags |= k_ent_route_flag_achieve_silver;
+ }
+
+ combined &= flags;
+ }
+
+ region->flags = combined;
+ world_total &= combined;
+
+ /* run unlock triggers. v105+ */
+ if( world->meta.info.version >= 105 ){
+ if( region->flags & (k_ent_route_flag_achieve_gold|
+ k_ent_route_flag_achieve_silver) ){
+ if( region->target0[0] ){
+ ent_call call;
+ call.data = NULL;
+ call.id = region->target0[0];
+ call.function = region->target0[1];
+ entity_call( world, &call );
+ }
+ }
+ }
+ }
+
+ u32 instance_id = world - world_static.instances;
+
+ if( world_static.instance_addons[instance_id]->flags & ADDON_REG_MTZERO ){
+ if( world_total & k_ent_route_flag_achieve_gold ){
+ steam_set_achievement( "MTZERO_GOLD" );
+ steam_store_achievements();
+ }
+
+ if( world_total & k_ent_route_flag_achieve_silver ){
+ steam_set_achievement( "MTZERO_SILVER" );
+ steam_store_achievements();
+ }
+ }
+
+ if( world_static.instance_addons[instance_id]->flags & ADDON_REG_CITY ){
+ steam_set_achievement( "CITY_COMPLETE" );
+ steam_store_achievements();
+ }
+}
--- /dev/null
+#pragma once
+#include "world_entity.h"
+#include "network_common.h"
+
+struct global_ent_region
+{
+ char location[ NETWORK_REGION_MAX ];
+ u32 flags;
+}
+extern global_ent_region;
+
+u32 region_spark_colour( u32 flags );
+void ent_region_re_eval( world_instance *world );
+entity_call_result ent_region_call( world_instance *world, ent_call *call );
--- /dev/null
+#include "ent_relay.h"
+
+entity_call_result ent_relay_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_relay *relay = mdl_arritm( &world->ent_relay, index );
+
+ if( call->function == 0 )
+ {
+ for( u32 i=0; i<VG_ARRAY_LEN(relay->targets); i++ )
+ {
+ if( relay->targets[i][0] )
+ {
+ ent_call call;
+ call.data = NULL;
+ call.function = relay->targets[i][1];
+ call.id = relay->targets[i][0];
+ entity_call( world, &call );
+ }
+ }
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
--- /dev/null
+#pragma once
+#include "entity.h"
+entity_call_result ent_relay_call( world_instance *world, ent_call *call );
--- /dev/null
+#include "ent_route.h"
+#include "input.h"
+#include "gui.h"
+
+struct global_ent_route global_ent_route;
+
+entity_call_result ent_route_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_route *route = mdl_arritm( &world->ent_route, index );
+
+ if( call->function == 0 )
+ { /* view() */
+ if( localplayer.subsystem == k_player_subsystem_walk )
+ {
+ world_entity_set_focus( call->id );
+ world_entity_focus_modal();
+
+ gui_helper_clear();
+ vg_str text;
+
+ if( (global_ent_route.helper_weekly =
+ gui_new_helper( input_button_list[k_srbind_mleft], &text )))
+ vg_strcat( &text, "Weekly" );
+
+ if( (global_ent_route.helper_alltime =
+ gui_new_helper( input_button_list[k_srbind_mright], &text )))
+ vg_strcat( &text, "All time" );
+
+ if( gui_new_helper( input_button_list[k_srbind_mback], &text ) )
+ vg_strcat( &text, "Exit" );
+ }
+
+ return k_entity_call_result_OK;
+ }
+
+ return k_entity_call_result_unhandled;
+}
+
+void ent_route_preupdate( ent_focus_context *ctx )
+{
+ if( !ctx->active )
+ return;
+
+ world_instance *world = ctx->world;
+ ent_route *route = mdl_arritm( &world->ent_route, ctx->index );
+
+ u32 cam_id = 0;
+
+ if( __builtin_expect( world->meta.info.version >= 103, 1 ) )
+ cam_id = route->id_camera;
+
+ world_entity_focus_camera( world, cam_id );
+
+ if( button_down( k_srbind_mleft ) ){
+ world_sfd.view_weekly = 1;
+ world_sfd_compile_active_scores();
+ }
+
+ if( button_down( k_srbind_mright ) ){
+ world_sfd.view_weekly = 0;
+ world_sfd_compile_active_scores();
+ }
+
+ global_ent_route.helper_alltime->greyed =!world_sfd.view_weekly;
+ global_ent_route.helper_weekly->greyed = world_sfd.view_weekly;
+
+ if( button_down( k_srbind_mback ) )
+ {
+ world_entity_exit_modal();
+ world_entity_clear_focus();
+ gui_helper_clear();
+ return;
+ }
+}
--- /dev/null
+#pragma once
+#include "entity.h"
+
+struct global_ent_route
+{
+ struct gui_helper *helper_weekly, *helper_alltime;
+}
+extern global_ent_route;
+
+entity_call_result ent_route_call( world_instance *world, ent_call *call );
+void ent_route_preupdate( ent_focus_context *ctx );
--- /dev/null
+#include "vg/vg_steam_ugc.h"
+#include "vg/vg_msg.h"
+#include "vg/vg_tex.h"
+#include "vg/vg_image.h"
+#include "vg/vg_loader.h"
+#include "ent_skateshop.h"
+#include "world.h"
+#include "player.h"
+#include "gui.h"
+#include "menu.h"
+#include "steam.h"
+#include "addon.h"
+#include "save.h"
+#include "network.h"
+
+struct global_skateshop global_skateshop =
+{
+ .render={.reg_id=0xffffffff,.world_reg=0xffffffff}
+};
+
+/*
+ * Checks string equality but does a hash check first
+ */
+static inline int const_str_eq( u32 hash, const char *str, const char *cmp )
+{
+ if( hash == vg_strdjb2(cmp) )
+ if( !strcmp( str, cmp ) )
+ return 1;
+ return 0;
+}
+
+static void skateshop_update_viewpage(void){
+ u32 page = global_skateshop.selected_board_id/SKATESHOP_VIEW_SLOT_MAX;
+
+ for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
+ u32 j = SKATESHOP_VIEW_SLOT_MAX-1-i;
+ struct shop_view_slot *slot = &global_skateshop.shop_view_slots[j];
+ addon_cache_unwatch( k_addon_type_board, slot->cache_id );
+ }
+
+ for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
+ struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
+ u32 request_id = page*SKATESHOP_VIEW_SLOT_MAX + i;
+ slot->cache_id = addon_cache_create_viewer( k_addon_type_board,
+ request_id );
+ }
+}
+
+struct async_preview_load_thread_data{
+ void *data;
+ addon_reg *reg;
+};
+
+static void skateshop_async_preview_imageload( void *data, u32 len ){
+ struct async_preview_load_thread_data *inf = data;
+
+ if( inf->data ){
+ glBindTexture( GL_TEXTURE_2D, global_skateshop.tex_preview );
+ glTexSubImage2D( GL_TEXTURE_2D, 0,0,0,
+ WORKSHOP_PREVIEW_WIDTH, WORKSHOP_PREVIEW_HEIGHT,
+ GL_RGB, GL_UNSIGNED_BYTE, inf->data );
+ glGenerateMipmap( GL_TEXTURE_2D );
+ stbi_image_free( inf->data );
+
+ skaterift.rt_textures[k_skaterift_rt_workshop_preview] =
+ global_skateshop.tex_preview;
+ }
+ else {
+ skaterift.rt_textures[k_skaterift_rt_workshop_preview] = vg.tex_missing;
+ }
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ global_skateshop.reg_loaded_preview = inf->reg;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+}
+
+static void skateshop_update_preview_image_thread(void *_args)
+{
+ char path_buf[4096];
+ vg_str folder;
+ vg_strnull( &folder, path_buf, sizeof(path_buf) );
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ addon_reg *reg_preview = global_skateshop.reg_preview;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+
+ if( !addon_get_content_folder( reg_preview, &folder, 1 ) )
+ {
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ global_skateshop.reg_loaded_preview = reg_preview;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ return;
+ }
+
+ vg_strcat( &folder, "/preview.jpg" );
+ vg_async_item *call =
+ vg_async_alloc( sizeof(struct async_preview_load_thread_data) );
+ struct async_preview_load_thread_data *inf = call->payload;
+
+ inf->reg = reg_preview;
+
+ if( vg_strgood( &folder ) )
+ {
+ stbi_set_flip_vertically_on_load(1);
+ int x, y, nc;
+ inf->data = stbi_load( folder.buffer, &x, &y, &nc, 3 );
+
+ if( inf->data )
+ {
+ if( (x != WORKSHOP_PREVIEW_WIDTH) || (y != WORKSHOP_PREVIEW_HEIGHT) )
+ {
+ vg_error( "Resolution does not match framebuffer, so we can't"
+ " show it\n" );
+ stbi_image_free( inf->data );
+ inf->data = NULL;
+ }
+ }
+
+ vg_async_dispatch( call, skateshop_async_preview_imageload );
+ }
+ else
+ {
+ vg_error( "Path too long to workshop preview image.\n" );
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ global_skateshop.reg_loaded_preview = reg_preview;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ }
+}
+
+void skateshop_world_preview_preupdate(void)
+{
+ /* try to load preview image if we availible to do. */
+ if( vg_loader_availible() )
+ {
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ if( global_skateshop.reg_preview != global_skateshop.reg_loaded_preview )
+ {
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ vg_loader_start( skateshop_update_preview_image_thread, NULL );
+ }
+ else SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ }
+}
+
+/*
+ * op/subroutine: k_workshop_op_item_load
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * Regular stuff
+ * -----------------------------------------------------------------------------
+ */
+
+static void skateshop_init_async(void *_data,u32 size){
+ glGenTextures( 1, &global_skateshop.tex_preview );
+ glBindTexture( GL_TEXTURE_2D, global_skateshop.tex_preview );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB,
+ WORKSHOP_PREVIEW_WIDTH, WORKSHOP_PREVIEW_HEIGHT,
+ 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
+
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+ GL_LINEAR_MIPMAP_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+
+ skaterift.rt_textures[ k_skaterift_rt_workshop_preview ] = vg.tex_missing;
+ skaterift.rt_textures[ k_skaterift_rt_server_status ] = vg.tex_missing;
+ render_server_status_gui();
+}
+
+/*
+ * VG event init
+ */
+void skateshop_init(void)
+{
+ vg_async_call( skateshop_init_async, NULL, 0 );
+}
+
+static u16 skateshop_selected_cache_id(void){
+ if( addon_count(k_addon_type_board, ADDON_REG_HIDDEN) ){
+ addon_reg *reg = get_addon_from_index(
+ k_addon_type_board, global_skateshop.selected_board_id,
+ ADDON_REG_HIDDEN );
+ return reg->cache_id;
+ }
+ else return 0;
+}
+
+static void skateshop_server_helper_update(void){
+ vg_str text;
+ vg_strnull( &text, global_skateshop.helper_toggle->text,
+ sizeof(global_skateshop.helper_toggle->text) );
+
+ if( skaterift.demo_mode ){
+ vg_strcat( &text, "Not availible in demo" );
+ }
+ else {
+ if( network_client.user_intent == k_server_intent_online )
+ vg_strcat( &text, "Disconnect" );
+ else
+ vg_strcat( &text, "Go Online" );
+ }
+}
+
+/*
+ * VG event preupdate
+ */
+void temp_update_playermodel(void);
+void ent_skateshop_preupdate( ent_focus_context *ctx )
+{
+ if( !ctx->active )
+ return;
+
+ world_instance *world = ctx->world;
+ ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, ctx->index );
+
+ /* camera positioning */
+ ent_camera *ref = mdl_arritm( &world->ent_camera,
+ mdl_entity_id_id(shop->id_camera) );
+
+ v3f dir = {0.0f,-1.0f,0.0f};
+ mdl_transform_vector( &ref->transform, dir, dir );
+ v3_angles( dir, world_static.focus_cam.angles );
+
+ v3f lookat;
+ if( shop->type == k_skateshop_type_boardshop ||
+ shop->type == k_skateshop_type_worldshop ){
+ ent_marker *display = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(shop->boards.id_display) );
+ v3_sub( display->transform.co, localplayer.rb.co, lookat );
+ }
+ else if( shop->type == k_skateshop_type_charshop ){
+ v3_sub( ref->transform.co, localplayer.rb.co, lookat );
+ }
+ else if( shop->type == k_skateshop_type_server ){
+ ent_prop *prop = mdl_arritm( &world->ent_prop,
+ mdl_entity_id_id(shop->server.id_lever) );
+ v3_sub( prop->transform.co, localplayer.rb.co, lookat );
+ }
+ else
+ vg_fatal_error( "Unknown store (%u)\n", shop->type );
+
+ q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
+ atan2f(lookat[0],lookat[2]) );
+
+ v3_copy( ref->transform.co, world_static.focus_cam.pos );
+ world_static.focus_cam.fov = ref->fov;
+
+ /* input */
+ if( shop->type == k_skateshop_type_boardshop ){
+ if( !vg_loader_availible() ) return;
+
+ u16 cache_id = skateshop_selected_cache_id();
+ global_skateshop.helper_pick->greyed = !cache_id;
+
+ /*
+ * Controls
+ * ----------------------
+ */
+ u32 opage = global_skateshop.selected_board_id/SKATESHOP_VIEW_SLOT_MAX;
+
+ if( button_down( k_srbind_mleft ) ){
+ if( global_skateshop.selected_board_id > 0 ){
+ global_skateshop.selected_board_id --;
+ }
+ }
+
+ u32 valid_count = addon_count( k_addon_type_board, 0 );
+ if( button_down( k_srbind_mright ) ){
+ if( global_skateshop.selected_board_id+1 < valid_count ){
+ global_skateshop.selected_board_id ++;
+ }
+ }
+
+ u32 npage = global_skateshop.selected_board_id/SKATESHOP_VIEW_SLOT_MAX;
+
+ if( opage != npage ){
+ skateshop_update_viewpage();
+ }
+ else if( cache_id && button_down( k_srbind_maccept )){
+ vg_info( "chose board from skateshop (%u)\n",
+ global_skateshop.selected_board_id );
+
+ addon_cache_unwatch( k_addon_type_board, localplayer.board_view_slot );
+ addon_cache_watch( k_addon_type_board, cache_id );
+ localplayer.board_view_slot = cache_id;
+ network_send_item( k_netmsg_playeritem_board );
+
+ world_entity_exit_modal();
+ world_entity_clear_focus();
+ gui_helper_clear();
+ skaterift_autosave(1);
+ return;
+ }
+ }
+ else if( shop->type == k_skateshop_type_charshop ){
+ if( !vg_loader_availible() ) return;
+
+ int changed = 0;
+ u32 valid_count = addon_count( k_addon_type_player, ADDON_REG_HIDDEN );
+
+ if( button_down( k_srbind_mleft ) ){
+ if( global_skateshop.selected_player_id > 0 ){
+ global_skateshop.selected_player_id --;
+ }
+ else{
+ global_skateshop.selected_player_id = valid_count-1;
+ }
+
+ changed = 1;
+ }
+
+ if( button_down( k_srbind_mright ) ){
+ if( global_skateshop.selected_player_id+1 < valid_count ){
+ global_skateshop.selected_player_id ++;
+ }
+ else{
+ global_skateshop.selected_player_id = 0;
+ }
+
+ changed = 1;
+ }
+
+ if( changed ){
+ addon_reg *addon = get_addon_from_index(
+ k_addon_type_player, global_skateshop.selected_player_id,
+ ADDON_REG_HIDDEN );
+
+ u32 real_id = get_index_from_addon(
+ k_addon_type_player, addon );
+
+ player__use_model( real_id );
+ }
+
+ if( button_down( k_srbind_maccept ) ){
+ network_send_item( k_netmsg_playeritem_player );
+ world_entity_exit_modal();
+ world_entity_clear_focus();
+ gui_helper_clear();
+ }
+ }
+ else if( shop->type == k_skateshop_type_worldshop ){
+ int browseable = 0,
+ loadable = 0;
+
+ u32 valid_count = addon_count( k_addon_type_world, ADDON_REG_HIDDEN );
+
+ if( valid_count && vg_loader_availible() )
+ browseable = 1;
+
+ if( valid_count && vg_loader_availible() )
+ loadable = 1;
+
+ global_skateshop.helper_browse->greyed = !browseable;
+ global_skateshop.helper_pick->greyed = !loadable;
+
+ addon_reg *selected_world = NULL;
+
+ int change = 0;
+ if( browseable ){
+ if( button_down( k_srbind_mleft ) ){
+ if( global_skateshop.selected_world_id > 0 ){
+ global_skateshop.selected_world_id --;
+ change = 1;
+ }
+ }
+
+ if( button_down( k_srbind_mright ) ){
+ if( global_skateshop.selected_world_id+1 < valid_count ){
+ global_skateshop.selected_world_id ++;
+ change = 1;
+ }
+ }
+
+ selected_world = get_addon_from_index( k_addon_type_world,
+ global_skateshop.selected_world_id, ADDON_REG_HIDDEN );
+
+ if( change || (global_skateshop.reg_preview == NULL) ){
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ global_skateshop.reg_preview = selected_world;
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ }
+ }
+
+ if( loadable ){
+ if( button_down( k_srbind_maccept ) ){
+ skaterift_change_world_start( selected_world );
+ }
+ }
+ }
+ else if( shop->type == k_skateshop_type_server ){
+ f64 delta = vg.time_real - network_client.last_intent_change;
+
+ if( (delta > 5.0) && (!skaterift.demo_mode) ){
+ global_skateshop.helper_pick->greyed = 0;
+ if( button_down( k_srbind_maccept ) ){
+ network_client.user_intent = !network_client.user_intent;
+ network_client.last_intent_change = vg.time_real;
+ skateshop_server_helper_update();
+ render_server_status_gui();
+ }
+ }
+ else {
+ global_skateshop.helper_pick->greyed = 1;
+ }
+ }
+ else{
+ vg_fatal_error( "Unknown store (%u)\n", shop->type );
+ }
+
+ if( button_down( k_srbind_mback ) )
+ {
+ if( shop->type == k_skateshop_type_charshop )
+ network_send_item( k_netmsg_playeritem_player );
+
+ world_entity_exit_modal();
+ world_entity_clear_focus();
+ gui_helper_clear();
+ return;
+ }
+}
+
+void skateshop_world_preupdate( world_instance *world )
+{
+ for( u32 i=0; i<mdl_arrcount(&world->ent_skateshop); i++ ){
+ ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, i );
+
+ if( shop->type == k_skateshop_type_server ){
+ f32 a = network_client.user_intent;
+
+ vg_slewf( &network_client.fintent, a, vg.time_frame_delta );
+ a = (vg_smoothstepf( network_client.fintent ) - 0.5f) * (VG_PIf/2.0f);
+
+ ent_prop *lever = mdl_arritm( &world->ent_prop,
+ mdl_entity_id_id(shop->server.id_lever) );
+
+ /* we need parent transforms now? */
+ q_axis_angle( lever->transform.q, (v3f){0,0,1}, a );
+ }
+ }
+}
+
+static void skateshop_render_boardshop( ent_skateshop *shop ){
+ world_instance *world = world_current_instance();
+ u32 slot_count = VG_ARRAY_LEN(global_skateshop.shop_view_slots);
+
+ ent_marker *mark_rack = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(shop->boards.id_rack)),
+ *mark_display = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(shop->boards.id_display));
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ struct addon_cache *cache = &addon_system.cache[k_addon_type_board];
+
+ /* Render loaded boards in the view slots */
+ for( u32 i=0; i<slot_count; i++ ){
+ struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
+ float selected = 0.0f;
+
+ if( !slot->cache_id )
+ goto fade_out;
+
+ addon_cache_entry *entry = vg_pool_item( &cache->pool, slot->cache_id );
+
+ if( entry->state != k_addon_cache_state_loaded )
+ goto fade_out;
+
+ struct player_board *board =
+ addon_cache_item( k_addon_type_board, slot->cache_id );
+
+ mdl_transform xform;
+ transform_identity( &xform );
+
+ xform.co[0] = -((float)i - ((float)slot_count)*0.5f)*0.45f;
+ mdl_transform_mul( &mark_rack->transform, &xform, &xform );
+
+
+ if( entry->reg_index == global_skateshop.selected_board_id ){
+ selected = 1.0f;
+ }
+
+ float t = slot->view_blend;
+ v3_lerp( xform.co, mark_display->transform.co, t, xform.co );
+ q_nlerp( xform.q, mark_display->transform.q, t, xform.q );
+ v3_lerp( xform.s, mark_display->transform.s, t, xform.s );
+
+ struct player_board_pose pose = {0};
+ m4x3f mmdl;
+ mdl_transform_m4x3( &xform, mmdl );
+ render_board( &g_render.cam, world, board, mmdl,
+ &pose, k_board_shader_entity );
+
+fade_out:;
+ float rate = 5.0f*vg.time_delta;
+ slot->view_blend = vg_lerpf( slot->view_blend, selected, rate );
+ }
+
+ ent_marker *mark_info = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(shop->boards.id_info));
+ m4x3f mtext, mrack;
+ mdl_transform_m4x3( &mark_info->transform, mtext );
+ mdl_transform_m4x3( &mark_rack->transform, mrack );
+
+ m4x3f mlocal, mmdl;
+ m4x3_identity( mlocal );
+
+ float scale = 0.2f,
+ thickness = 0.03f;
+
+ font3d_bind( &gui.font, k_font_shader_default, 0, world, &g_render.cam );
+ shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
+
+ /* Selection counter
+ * ------------------------------------------------------------------ */
+ m3x3_zero( mlocal );
+ v3_zero( mlocal[3] );
+ mlocal[0][0] = -scale*2.0f;
+ mlocal[1][2] = -scale*2.0f;
+ mlocal[2][1] = -thickness;
+ mlocal[3][2] = -0.7f;
+ m4x3_mul( mrack, mlocal, mmdl );
+
+ u32 valid_count = addon_count(k_addon_type_board,0);
+ if( valid_count ){
+ char buf[16];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_strcati32( &str, global_skateshop.selected_board_id+1 );
+ vg_strcatch( &str, '/' );
+ vg_strcati32( &str, valid_count );
+ font3d_simple_draw( 0, buf, &g_render.cam, mmdl );
+ }
+ else{
+ font3d_simple_draw( 0, "Nothing installed", &g_render.cam, mmdl );
+ }
+
+ u16 cache_id = skateshop_selected_cache_id();
+ struct addon_cache_entry *entry = vg_pool_item( &cache->pool, cache_id );
+ addon_reg *reg = NULL;
+
+ if( entry ) reg = entry->reg_ptr;
+
+ if( !reg ){
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ global_skateshop.render.item_title = "";
+ global_skateshop.render.item_desc = "";
+ return;
+ }
+
+ if( global_skateshop.render.reg_id != global_skateshop.selected_board_id ){
+ global_skateshop.render.item_title = "";
+ global_skateshop.render.item_desc = "";
+ vg_msg msg;
+ vg_msg_init( &msg, reg->metadata, reg->metadata_len );
+
+ if( vg_msg_seekframe( &msg, "workshop" ) ){
+ const char *title = vg_msg_getkvstr( &msg, "title" );
+ if( title ) global_skateshop.render.item_title = title;
+
+ const char *dsc = vg_msg_getkvstr( &msg, "author" );
+ if( dsc ) global_skateshop.render.item_desc = dsc;
+ vg_msg_skip_frame( &msg );
+ }
+
+ global_skateshop.render.reg_id = global_skateshop.selected_board_id;
+ }
+
+ /* Skin title
+ * ----------------------------------------------------------------- */
+ m3x3_zero( mlocal );
+ m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
+ mlocal[3][0] = -font3d_string_width( 0, global_skateshop.render.item_title );
+ mlocal[3][0] *= scale*0.5f;
+ mlocal[3][1] = 0.1f;
+ mlocal[3][2] = 0.0f;
+ m4x3_mul( mtext, mlocal, mmdl );
+ font3d_simple_draw( 0, global_skateshop.render.item_title,
+ &g_render.cam, mmdl );
+
+ /* Author name
+ * ----------------------------------------------------------------- */
+ scale *= 0.4f;
+ m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
+ mlocal[3][0] = -font3d_string_width( 0, global_skateshop.render.item_desc );
+ mlocal[3][0] *= scale*0.5f;
+ mlocal[3][1] = 0.0f;
+ mlocal[3][2] = 0.0f;
+ m4x3_mul( mtext, mlocal, mmdl );
+ font3d_simple_draw( 0, global_skateshop.render.item_desc,
+ &g_render.cam, mmdl );
+
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+}
+
+static void skateshop_render_charshop( ent_skateshop *shop ){
+}
+
+static void skateshop_render_worldshop( ent_skateshop *shop ){
+ world_instance *world = world_current_instance();
+
+ ent_marker *mark_display = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(shop->worlds.id_display)),
+ *mark_info = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(shop->boards.id_info));
+
+ if( global_skateshop.render.world_reg != global_skateshop.selected_world_id){
+ global_skateshop.render.world_title = "missing: workshop.title";
+
+ addon_reg *reg = get_addon_from_index( k_addon_type_world,
+ global_skateshop.selected_world_id, ADDON_REG_HIDDEN );
+
+ if( !reg )
+ goto none;
+
+ if( reg->alias.workshop_id )
+ {
+ vg_msg msg;
+ vg_msg_init( &msg, reg->metadata, reg->metadata_len );
+
+ global_skateshop.render.world_loc = vg_msg_getkvstr(&msg,"location");
+ global_skateshop.render.world_reg = global_skateshop.selected_world_id;
+
+ if( vg_msg_seekframe( &msg, "workshop" ) )
+ {
+ global_skateshop.render.world_title = vg_msg_getkvstr(&msg,"title");
+ vg_msg_skip_frame( &msg );
+ }
+ else {
+ vg_warn( "No workshop body\n" );
+ }
+ }
+ else {
+ global_skateshop.render.world_title = reg->alias.foldername;
+ }
+ }
+
+none:;
+
+ /* Text */
+ char buftext[128], bufsubtext[128];
+ vg_str info, subtext;
+ vg_strnull( &info, buftext, 128 );
+ vg_strnull( &subtext, bufsubtext, 128 );
+
+ u32 valid_count = addon_count(k_addon_type_world,ADDON_REG_HIDDEN);
+ if( valid_count )
+ {
+ vg_strcati32( &info, global_skateshop.selected_world_id+1 );
+ vg_strcatch( &info, '/' );
+ vg_strcati32( &info, valid_count );
+ vg_strcatch( &info, ' ' );
+ vg_strcat( &info, global_skateshop.render.world_title );
+
+ if( !vg_loader_availible() )
+ {
+ vg_strcat( &subtext, "Loading..." );
+ }
+ else
+ {
+ addon_reg *reg = get_addon_from_index( k_addon_type_world,
+ global_skateshop.selected_world_id, ADDON_REG_HIDDEN );
+
+ if( reg->alias.workshop_id )
+ vg_strcat( &subtext, "(Workshop) " );
+
+ vg_strcat( &subtext, global_skateshop.render.world_loc );
+ }
+ }
+ else
+ {
+ vg_strcat( &info, "No workshop worlds installed" );
+ }
+
+ m4x3f mtext,mlocal,mtextmdl;
+ mdl_transform_m4x3( &mark_info->transform, mtext );
+
+ font3d_bind( &gui.font, k_font_shader_default, 0, NULL, &g_render.cam );
+ shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
+
+ float scale = 0.2f, thickness = 0.015f, scale1 = 0.08f;
+ m3x3_zero( mlocal );
+ m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
+ mlocal[3][0] = -font3d_string_width( 0, buftext );
+ mlocal[3][0] *= scale*0.5f;
+ mlocal[3][1] = 0.1f;
+ mlocal[3][2] = 0.0f;
+ m4x3_mul( mtext, mlocal, mtextmdl );
+ font3d_simple_draw( 0, buftext, &g_render.cam, mtextmdl );
+
+ m3x3_setdiagonalv3( mlocal, (v3f){ scale1, scale1, thickness } );
+ mlocal[3][0] = -font3d_string_width( 0, bufsubtext );
+ mlocal[3][0] *= scale1*0.5f;
+ mlocal[3][1] = -scale1*0.3f;
+ m4x3_mul( mtext, mlocal, mtextmdl );
+ font3d_simple_draw( 0, bufsubtext, &g_render.cam, mtextmdl );
+}
+
+/*
+ * World: render event
+ */
+void skateshop_render( ent_skateshop *shop )
+{
+ if( shop->type == k_skateshop_type_boardshop )
+ skateshop_render_boardshop( shop );
+ else if( shop->type == k_skateshop_type_charshop )
+ skateshop_render_charshop( shop );
+ else if( shop->type == k_skateshop_type_worldshop )
+ skateshop_render_worldshop( shop );
+ else if( shop->type == k_skateshop_type_server ){
+ }
+ else
+ vg_fatal_error( "Unknown store (%u)\n", shop->type );
+}
+
+void skateshop_render_nonfocused( world_instance *world, vg_camera *cam )
+{
+ for( u32 j=0; j<mdl_arrcount( &world->ent_skateshop ); j ++ )
+ {
+ ent_skateshop *shop = mdl_arritm(&world->ent_skateshop, j );
+
+ if( shop->type != k_skateshop_type_boardshop ) continue;
+
+ f32 dist2 = v3_dist2( cam->pos, shop->transform.co ),
+ maxdist = 50.0f;
+
+ if( dist2 > maxdist*maxdist ) continue;
+ ent_marker *mark_rack = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(shop->boards.id_rack));
+
+ if( !mark_rack )
+ continue;
+
+ u32 slot_count = VG_ARRAY_LEN(global_skateshop.shop_view_slots);
+ for( u32 i=0; i<slot_count; i++ )
+ {
+ struct player_board *board = &localplayer.fallback_board;
+
+ mdl_transform xform;
+ transform_identity( &xform );
+
+ xform.co[0] = -((float)i - ((float)slot_count)*0.5f)*0.45f;
+ mdl_transform_mul( &mark_rack->transform, &xform, &xform );
+
+ struct player_board_pose pose = {0};
+ m4x3f mmdl;
+ mdl_transform_m4x3( &xform, mmdl );
+ render_board( cam, world, board, mmdl, &pose, k_board_shader_entity );
+ }
+ }
+}
+
+static void ent_skateshop_helpers_pickable( const char *acceptance )
+{
+ vg_str text;
+
+ if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
+ vg_strcat( &text, "Exit" );
+
+ if( (global_skateshop.helper_pick = gui_new_helper(
+ input_button_list[k_srbind_maccept], &text))){
+ vg_strcat( &text, acceptance );
+ }
+
+ if( (global_skateshop.helper_browse = gui_new_helper(
+ input_axis_list[k_sraxis_mbrowse_h], &text ))){
+ vg_strcat( &text, "Browse" );
+ }
+}
+
+static void board_scan_thread( void *_args )
+{
+ addon_mount_content_folder( k_addon_type_board, "boards", ".mdl" );
+ addon_mount_workshop_items();
+ vg_async_call( async_addon_reg_update, NULL, 0 );
+ vg_async_stall();
+
+ /* 04.03.24
+ * REVIEW: This is removed as it *should* be done on the preupdate of the
+ * addon system.
+ *
+ * Verify that it works the same.
+ */
+#if 0
+ board_processview_thread(NULL);
+#endif
+}
+
+static void world_scan_thread( void *_args )
+{
+ addon_mount_content_folder( k_addon_type_world, "maps", ".mdl" );
+ addon_mount_workshop_items();
+ vg_async_call( async_addon_reg_update, NULL, 0 );
+}
+
+/*
+ * Entity logic: entrance event
+ */
+entity_call_result ent_skateshop_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, index );
+ vg_info( "skateshop_call\n" );
+
+ if( (skaterift.activity != k_skaterift_default) ||
+ !vg_loader_availible() )
+ return k_entity_call_result_invalid;
+
+ if( call->function == k_ent_function_trigger )
+ {
+ if( localplayer.subsystem != k_player_subsystem_walk )
+ return k_entity_call_result_OK;
+
+ vg_info( "Entering skateshop\n" );
+
+ world_entity_set_focus( call->id );
+ world_entity_focus_modal();
+ gui_helper_clear();
+
+ if( shop->type == k_skateshop_type_boardshop )
+ {
+ skateshop_update_viewpage();
+ vg_loader_start( board_scan_thread, NULL );
+ ent_skateshop_helpers_pickable( "Pick" );
+ }
+ else if( shop->type == k_skateshop_type_charshop )
+ {
+ ent_skateshop_helpers_pickable( "Pick" );
+ }
+ else if( shop->type == k_skateshop_type_worldshop )
+ {
+ ent_skateshop_helpers_pickable( "Open rift" );
+ vg_loader_start( world_scan_thread, NULL );
+ }
+ else if( shop->type == k_skateshop_type_server )
+ {
+ vg_str text;
+ global_skateshop.helper_pick = gui_new_helper(
+ input_button_list[k_srbind_maccept], &text);
+ if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
+ vg_strcat( &text, "exit" );
+ skateshop_server_helper_update();
+ }
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
--- /dev/null
+#pragma once
+#include "world.h"
+#include "world_load.h"
+#include "player.h"
+#include "vg/vg_steam_remote_storage.h"
+#include "workshop.h"
+#include "addon.h"
+
+#define SKATESHOP_VIEW_SLOT_MAX 5
+
+struct global_skateshop
+{
+ v3f look_target;
+
+ struct shop_view_slot{
+ u16 cache_id;
+ float view_blend;
+ }
+ shop_view_slots[ SKATESHOP_VIEW_SLOT_MAX ];
+
+ u32 selected_world_id,
+ selected_board_id,
+ selected_player_id,
+ pointcloud_world_id;
+
+ struct {
+ const char *item_title, *item_desc;
+ u32 reg_id;
+
+ const char *world_title, *world_loc;
+ u32 world_reg;
+ }
+ render;
+
+ union {
+ struct gui_helper *helper_pick, *helper_toggle;
+ };
+
+ struct gui_helper *helper_browse;
+
+
+ addon_reg *reg_preview, *reg_loaded_preview;
+ GLuint tex_preview;
+}
+extern global_skateshop;
+
+void skateshop_init(void);
+void ent_skateshop_preupdate( ent_focus_context *ctx );
+void skateshop_render( ent_skateshop *shop );
+void skateshop_render_nonfocused( world_instance *world, vg_camera *cam );
+void skateshop_autostart_loading(void);
+void skateshop_world_preupdate( world_instance *world );
+entity_call_result ent_skateshop_call( world_instance *world, ent_call *call );
+void skateshop_world_preview_preupdate(void);
--- /dev/null
+#include "world.h"
+#include "particle.h"
+
+static f32 k_tornado_strength = 0.0f,
+ k_tornado_ratio = 0.5f,
+ k_tornado_range = 10.f;
+
+void ent_tornado_init(void)
+{
+ vg_console_reg_var( "k_tonado_strength", &k_tornado_strength,
+ k_var_dtype_f32, VG_VAR_PERSISTENT|VG_VAR_CHEAT );
+ vg_console_reg_var( "k_tonado_ratio", &k_tornado_ratio,
+ k_var_dtype_f32, VG_VAR_PERSISTENT|VG_VAR_CHEAT );
+ vg_console_reg_var( "k_tonado_range", &k_tornado_range,
+ k_var_dtype_f32, VG_VAR_PERSISTENT|VG_VAR_CHEAT );
+}
+
+void ent_tornado_debug(void)
+{
+ world_instance *world = world_current_instance();
+ for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i ++ ){
+ ent_marker *marker = mdl_arritm( &world->ent_marker, i );
+
+ if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tornado" ) ){
+ v3f p1;
+ v3_add( marker->transform.co, (v3f){0,20,0}, p1 );
+ vg_line( marker->transform.co, p1, VG__RED );
+
+ m4x3f mmdl;
+ m4x3_identity( mmdl );
+ v3_copy( marker->transform.co, mmdl[3] );
+ vg_line_sphere( mmdl, k_tornado_range, 0 );
+ }
+ }
+}
+
+void ent_tornado_forces( v3f co, v3f cv, v3f out_a )
+{
+ world_instance *world = world_current_instance();
+ v3_zero( out_a );
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i ++ ){
+ ent_marker *marker = mdl_arritm( &world->ent_marker, i );
+
+ if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tornado" ) ){
+ v3f d, dir;
+ v3_sub( co, marker->transform.co, d );
+ d[1] = 0.0f;
+
+ f32 dist = v3_length( d );
+ v3_normalize( d );
+
+ v3_cross( d, (v3f){0,1,0}, dir );
+ if( v3_dot( dir, cv ) < 0.0f )
+ v3_negate( dir, dir );
+
+ f32 s = vg_maxf(0.0f, 1.0f-dist/k_tornado_range),
+ F0 = s*k_tornado_strength,
+ F1 = s*s*k_tornado_strength;
+
+ v3_muladds( out_a, dir, F0 * k_tornado_ratio, out_a );
+ v3_muladds( out_a, d, F1 * -(1.0f-k_tornado_ratio), out_a );
+ }
+ }
+}
+
+void ent_tornado_pre_update(void)
+{
+ world_instance *world = world_current_instance();
+ for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i ++ ){
+ ent_marker *marker = mdl_arritm( &world->ent_marker, i );
+
+ if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tornado" ) ){
+ v3f co;
+ vg_rand_sphere( &vg.rand, co );
+
+ v3f tangent = { co[2], 0, co[0] };
+
+ f32 s = vg_signf( co[1] );
+ v3_muls( tangent, s*10.0f, tangent );
+ co[1] *= s;
+
+ v3_muladds( marker->transform.co, co, k_tornado_range, co );
+ particle_spawn( &particles_env, co, tangent, 2.0f, 0xffffffff );
+ }
+ }
+}
--- /dev/null
+#pragma once
+
+void ent_tornado_init(void);
+void ent_tornado_debug(void);
+void ent_tornado_forces( v3f co, v3f cv, v3f out_a );
+void ent_tornado_pre_update(void);
--- /dev/null
+#include "world.h"
+
+void ent_traffic_update( world_instance *world, v3f pos )
+{
+ for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
+ ent_traffic *traffic = mdl_arritm( &world->ent_traffic, i );
+
+ u32 i1 = traffic->index,
+ i0,
+ i2 = i1+1;
+
+ if( i1 == 0 ) i0 = traffic->node_count-1;
+ else i0 = i1-1;
+
+ if( i2 >= traffic->node_count ) i2 = 0;
+
+ i0 += traffic->start_node;
+ i1 += traffic->start_node;
+ i2 += traffic->start_node;
+
+ v3f h[3];
+
+ ent_route_node *rn0 = mdl_arritm( &world->ent_route_node, i0 ),
+ *rn1 = mdl_arritm( &world->ent_route_node, i1 ),
+ *rn2 = mdl_arritm( &world->ent_route_node, i2 );
+
+ v3_copy( rn1->co, h[1] );
+ v3_lerp( rn0->co, rn1->co, 0.5f, h[0] );
+ v3_lerp( rn1->co, rn2->co, 0.5f, h[2] );
+
+ float const k_sample_dist = 0.0025f;
+ v3f pc, pd;
+ eval_bezier3( h[0], h[1], h[2], traffic->t, pc );
+ eval_bezier3( h[0], h[1], h[2], traffic->t+k_sample_dist, pd );
+
+ v3f v0;
+ v3_sub( pd, pc, v0 );
+ float length = vg_maxf( 0.0001f, v3_length( v0 ) );
+ v3_muls( v0, 1.0f/length, v0 );
+
+ float mod = k_sample_dist / length;
+
+ traffic->t += traffic->speed * vg.time_delta * mod;
+
+ if( traffic->t > 1.0f ){
+ traffic->t -= 1.0f;
+
+ if( traffic->t > 1.0f ) traffic->t = 0.0f;
+
+ traffic->index ++;
+
+ if( traffic->index >= traffic->node_count )
+ traffic->index = 0;
+ }
+
+ v3_copy( pc, traffic->transform.co );
+
+ float a = atan2f( -v0[0], v0[2] );
+ q_axis_angle( traffic->transform.q, (v3f){0.0f,1.0f,0.0f}, -a );
+
+ vg_line_point( traffic->transform.co, 0.3f, VG__BLUE );
+ }
+}
--- /dev/null
+#pragma once
+#include "world.h"
+void ent_traffic_update( world_instance *world, v3f pos );
--- /dev/null
+#include "world.h"
+#include "entity.h"
+#include "world_entity.h"
+
+#include "ent_objective.h"
+#include "ent_skateshop.h"
+#include "ent_relay.h"
+#include "ent_challenge.h"
+#include "ent_route.h"
+#include "ent_miniworld.h"
+#include "ent_region.h"
+#include "ent_glider.h"
+#include "ent_npc.h"
+#include "world_water.h"
+
+#include <string.h>
+
+void entity_call( world_instance *world, ent_call *call )
+{
+ u32 type = mdl_entity_id_type( call->id ),
+ index = mdl_entity_id_id( call->id );
+
+ fn_entity_call_handler table[] = {
+ [k_ent_volume] = ent_volume_call,
+ [k_ent_audio] = ent_audio_call,
+ [k_ent_skateshop] = ent_skateshop_call,
+ [k_ent_objective] = ent_objective_call,
+ [k_ent_ccmd] = ent_ccmd_call,
+ [k_ent_gate] = ent_gate_call,
+ [k_ent_relay] = ent_relay_call,
+ [k_ent_challenge] = ent_challenge_call,
+ [k_ent_route] = ent_route_call,
+ [k_ent_miniworld] = ent_miniworld_call,
+ [k_ent_region] = ent_region_call,
+ [k_ent_glider] = ent_glider_call,
+ [k_ent_npc] = ent_npc_call,
+ [k_ent_water] = ent_water_call,
+ };
+
+ if( type >= VG_ARRAY_LEN(table) ){
+ vg_error( "call to entity type: %u is out of range\n", type );
+ return;
+ }
+
+ fn_entity_call_handler fn = table[ type ];
+
+ if( !fn )
+ {
+ vg_error( "Entity type %u does not have a call handler, "
+ "but was called anyway\n", type );
+ return;
+ }
+
+ enum entity_call_result res = fn( world, call );
+
+ if( res == k_entity_call_result_unhandled )
+ {
+ vg_warn( "Call to entity %u#%u was unhandled.\n", type, index );
+ }
+}
+
+ent_marker *ent_find_marker( mdl_context *mdl, mdl_array_ptr *arr,
+ const char *alias )
+{
+ for( u32 i=0; i<mdl_arrcount(arr); i++ )
+ {
+ ent_marker *marker = mdl_arritm( arr, i );
+
+ if( !strcmp( mdl_pstr( mdl, marker->pstr_alias ), alias ) )
+ {
+ return marker;
+ }
+ }
+
+ return NULL;
+}
+
--- /dev/null
+#pragma once
+
+#include "vg/vg_audio.h"
+#include "vg/vg_ui/imgui.h"
+#include "model.h"
+
+typedef struct ent_spawn ent_spawn;
+typedef struct ent_light ent_light;
+typedef struct ent_gate ent_gate;
+typedef struct ent_route_node ent_route_node;
+typedef struct ent_path_index ent_path_index;
+typedef struct ent_checkpoint ent_checkpoint;
+typedef struct ent_route ent_route;
+typedef struct ent_water ent_water;
+typedef struct ent_audio_clip ent_audio_clip;
+typedef struct volume_particles volume_particles;
+typedef struct volume_trigger volume_trigger;
+typedef struct ent_volume ent_volume;
+typedef struct ent_audio ent_audio;
+typedef struct ent_marker ent_marker;
+typedef struct ent_traffic ent_traffic;
+typedef struct ent_font ent_font;
+typedef struct ent_font_variant ent_font_variant;
+typedef struct ent_glyph ent_glyph;
+typedef struct ent_skateshop ent_skateshop;
+typedef struct ent_camera ent_camera;
+typedef struct ent_swspreview ent_swspreview;
+typedef struct ent_worldinfo ent_worldinfo;
+typedef struct ent_ccmd ent_ccmd;
+typedef struct ent_objective ent_objective;
+typedef struct ent_challenge ent_challenge;
+typedef struct ent_relay ent_relay;
+typedef struct ent_cubemap ent_cubemap;
+typedef struct ent_miniworld ent_miniworld;
+typedef struct ent_prop ent_prop;
+typedef struct ent_region ent_region;
+typedef struct ent_list ent_list;
+typedef struct ent_glider ent_glider;
+typedef struct ent_npc ent_npc;
+
+enum entity_alias{
+ k_ent_none = 0,
+ k_ent_gate = 1,
+ k_ent_spawn = 2,
+ k_ent_route_node = 3,
+ k_ent_route = 4,
+ k_ent_water = 5,
+ k_ent_volume = 6,
+ k_ent_audio = 7,
+ k_ent_marker = 8,
+ k_ent_font = 9,
+ k_ent_font_variant= 10,
+ k_ent_traffic = 11,
+ k_ent_skateshop = 12,
+ k_ent_camera = 13,
+ k_ent_swspreview = 14,
+ k_ent_menuitem = 15,
+ k_ent_worldinfo = 16,
+ k_ent_ccmd = 17,
+ k_ent_objective = 18,
+ k_ent_challenge = 19,
+ k_ent_relay = 20,
+ k_ent_cubemap = 21,
+ k_ent_miniworld = 22,
+ k_ent_prop = 23,
+ k_ent_list = 24,
+ k_ent_region = 25,
+ k_ent_glider = 26,
+ k_ent_npc = 27
+};
+
+typedef struct ent_call ent_call;
+typedef enum entity_call_result entity_call_result;
+enum entity_call_result
+{
+ k_entity_call_result_OK,
+ k_entity_call_result_unhandled,
+ k_entity_call_result_invalid
+};
+
+static inline u32 mdl_entity_id_type( u32 entity_id )
+{
+ return (entity_id & 0x0fff0000) >> 16;
+}
+
+static inline u32 mdl_entity_id_id( u32 entity_id )
+{
+ return entity_id & 0x0000ffff;
+}
+
+static inline u32 mdl_entity_id( u32 type, u32 index )
+{
+ return (type & 0xfffff)<<16 | (index & 0xfffff);
+}
+
+enum entity_function
+{
+ k_ent_function_trigger,
+ k_ent_function_particle_spawn,
+ k_ent_function_trigger_leave
+};
+
+struct ent_spawn{
+ mdl_transform transform;
+ u32 pstr_name;
+};
+
+enum light_type{
+ k_light_type_point = 0,
+ k_light_type_spot = 1
+};
+
+struct ent_light{
+ mdl_transform transform;
+ u32 daytime,
+ type;
+
+ v4f colour;
+ float angle,
+ range;
+
+ m4x3f inverse_world;
+ v2f angle_sin_cos;
+};
+
+/* v101 */
+#if 0
+enum gate_type{
+ k_gate_type_unlinked = 0,
+ k_gate_type_teleport = 1,
+ k_gate_type_nonlocal_unlinked = 2,
+ k_gate_type_nonlocel = 3
+};
+#endif
+
+/* v102+ */
+enum ent_gate_flag{
+ k_ent_gate_linked = 0x1, /* this is a working portal */
+ k_ent_gate_nonlocal = 0x2, /* use the key string to link this portal.
+ NOTE: if set, it adds the flip flag. */
+ k_ent_gate_flip = 0x4, /* flip direction 180* for exiting portal */
+ k_ent_gate_custom_mesh = 0x8, /* use a custom submesh instead of default */
+ k_ent_gate_locked = 0x10,/* has to be unlocked to be useful */
+
+ k_ent_gate_clean_pass = 0x20,/* player didn't rewind while getting here */
+};
+
+struct ent_gate{
+ u32 flags,
+ target,
+ key;
+
+ v3f dimensions,
+ co[2];
+
+ v4f q[2];
+
+ /* runtime */
+ m4x3f to_world, transport;
+
+ union{
+ u32 timing_version;
+
+ struct{
+ u8 ref_count;
+ };
+ };
+
+ double timing_time;
+ u16 routes[4]; /* routes that pass through this gate */
+ u8 route_count;
+
+ /* v102+ */
+ u32 submesh_start, submesh_count;
+};
+
+struct ent_route_node{
+ v3f co;
+ u8 ref_count, ref_total;
+};
+
+struct ent_path_index{
+ u16 index;
+};
+
+struct ent_checkpoint{
+ u16 gate_index,
+ path_start,
+ path_count;
+
+ /* EXTENSION */
+ f32 best_time;
+};
+
+enum ent_route_flag {
+ k_ent_route_flag_achieve_silver = 0x1,
+ k_ent_route_flag_achieve_gold = 0x2,
+
+ k_ent_route_flag_out_of_zone = 0x10,
+ k_ent_region_flag_hasname = 0x20
+};
+
+struct ent_route{
+ union{
+ mdl_transform transform;
+ u32 official_track_id; /* TODO: remove this */
+ }
+ anon;
+
+ u32 pstr_name;
+ u16 checkpoints_start,
+ checkpoints_count;
+
+ v4f colour;
+
+ /* runtime */
+ u16 active_checkpoint,
+ valid_checkpoints;
+
+ f32 factive;
+ m4x3f board_transform;
+ mdl_submesh sm;
+ f64 timing_base;
+
+ u32 id_camera; /* v103+ */
+
+ /* v104+, but always accessible */
+ u32 flags;
+ f64 best_laptime;
+ f32 ui_stopper, ui_residual;
+
+ ui_px ui_first_block_width, ui_residual_block_w;
+};
+
+struct ent_water{
+ mdl_transform transform;
+ float max_dist;
+ u32 reserved0, reserved1;
+};
+
+struct ent_audio_clip{
+ union{
+ mdl_file file;
+ audio_clip clip;
+ }_;
+
+ float probability;
+};
+
+struct volume_particles{
+ u32 blank, blank2;
+};
+
+struct volume_trigger{
+ i32 event, event_leave;
+};
+
+enum ent_volume_flag {
+ k_ent_volume_flag_particles = 0x1,
+ k_ent_volume_flag_disabled = 0x2
+};
+
+struct ent_volume{
+ mdl_transform transform;
+ m4x3f to_world, to_local;
+ u32 flags;
+
+ u32 target;
+ union{
+ volume_trigger trigger;
+ volume_particles particles;
+ };
+};
+
+struct ent_audio{
+ mdl_transform transform;
+ u32 flags,
+ clip_start,
+ clip_count;
+ float volume, crossfade;
+ u32 behaviour,
+ group,
+ probability_curve,
+ max_channels;
+};
+
+struct ent_marker{
+ mdl_transform transform;
+ u32 pstr_alias;
+};
+
+enum skateshop_type{
+ k_skateshop_type_boardshop = 0,
+ k_skateshop_type_charshop = 1,
+ k_skateshop_type_worldshop = 2,
+ k_skateshop_type_DELETED = 3,
+ k_skateshop_type_server = 4
+};
+
+struct ent_skateshop{
+ mdl_transform transform;
+ u32 type, id_camera;
+
+ union{
+ struct{
+ u32 id_display,
+ id_info,
+ id_rack;
+ }
+ boards;
+
+ struct{
+ u32 id_display,
+ id_info;
+ }
+ character;
+
+ struct{
+ u32 id_display,
+ id_info;
+ }
+ worlds;
+
+ struct{
+ u32 id_lever;
+ }
+ server;
+ };
+};
+
+struct ent_swspreview{
+ u32 id_camera, id_display, id_display1;
+};
+
+struct ent_traffic{
+ mdl_transform transform;
+ u32 submesh_start,
+ submesh_count,
+ start_node,
+ node_count;
+ float speed,
+ t;
+ u32 index; /* into the path */
+};
+
+struct ent_camera{
+ mdl_transform transform;
+ float fov;
+};
+
+enum ent_menuitem_type{
+ k_ent_menuitem_type_visual = 0,
+ k_ent_menuitem_type_event_button = 1,
+ k_ent_menuitem_type_page_button = 2,
+ k_ent_menuitem_type_toggle = 3,
+ k_ent_menuitem_type_slider = 4,
+ k_ent_menuitem_type_page = 5,
+ k_ent_menuitem_type_binding = 6,
+ k_ent_menuitem_type_visual_nocol = 7,
+ k_ent_menuitem_type_disabled = 90
+};
+
+enum ent_menuitem_stack_behaviour{
+ k_ent_menuitem_stack_append = 0,
+ k_ent_menuitem_stack_replace = 1
+};
+
+typedef struct ent_menuitem ent_menuitem;
+struct ent_menuitem{
+ u32 type, groups,
+ id_links[4]; /* ent_menuitem */
+ f32 factive, fvisible;
+
+ mdl_transform transform;
+ u32 submesh_start, submesh_count;
+
+ union{ u64 _u64; /* force storage for 64bit pointers */
+ i32 *pi32;
+ f32 *pf32;
+ void *pvoid;
+ };
+
+ union{
+ struct{
+ u32 pstr_name;
+ }
+ visual;
+
+ struct{
+ u32 id_min, /* ent_marker */
+ id_max, /* . */
+ id_handle, /* ent_menuitem */
+ pstr_data;
+ }
+ slider;
+
+ struct{
+ u32 pstr,
+ stack_behaviour;
+ }
+ button;
+
+ struct{
+ u32 id_check, /* ent_menuitem */
+ pstr_data;
+ v3f offset; /* relative to parent */
+ }
+ checkmark;
+
+ struct{
+ u32 pstr_name,
+ id_entrypoint, /* ent_menuitem */
+ id_viewpoint; /* ent_camera */
+ }
+ page;
+
+ struct{
+ u32 pstr_bind,
+ font_variant;
+ }
+ binding;
+ };
+};
+
+struct ent_worldinfo{
+ u32 pstr_name, pstr_author, pstr_desc;
+ f32 timezone;
+ u32 pstr_skybox;
+ u32 flags;
+};
+
+ent_marker *ent_find_marker( mdl_context *mdl, mdl_array_ptr *arr,
+ const char *alias );
+
+enum channel_behaviour{
+ k_channel_behaviour_unlimited = 0,
+ k_channel_behaviour_discard_if_full = 1,
+ k_channel_behaviour_crossfade_if_full = 2
+};
+
+enum probability_curve{
+ k_probability_curve_constant = 0,
+ k_probability_curve_wildlife_day = 1,
+ k_probability_curve_wildlife_night = 2
+};
+
+struct ent_font{
+ u32 alias,
+ variant_start,
+ variant_count,
+ glyph_start,
+ glyph_count,
+ glyph_utf32_base;
+};
+
+struct ent_font_variant{
+ u32 name,
+ material_id;
+};
+
+struct ent_glyph{
+ v2f size;
+ u32 indice_start,
+ indice_count;
+};
+
+struct ent_ccmd{
+ u32 pstr_command;
+};
+
+enum ent_objective_filter{
+ k_ent_objective_filter_none = 0x00000000,
+ k_ent_objective_filter_trick_shuvit = 0x00000001,
+ k_ent_objective_filter_trick_kickflip = 0x00000002,
+ k_ent_objective_filter_trick_treflip = 0x00000004,
+ k_ent_objective_filter_trick_any =
+ k_ent_objective_filter_trick_shuvit|
+ k_ent_objective_filter_trick_treflip|
+ k_ent_objective_filter_trick_kickflip,
+ k_ent_objective_filter_flip_back = 0x00000008,
+ k_ent_objective_filter_flip_front = 0x00000010,
+ k_ent_objective_filter_flip_any =
+ k_ent_objective_filter_flip_back|
+ k_ent_objective_filter_flip_front,
+ k_ent_objective_filter_grind_truck_any = 0x00000020,
+ k_ent_objective_filter_grind_board_any = 0x00000040,
+ k_ent_objective_filter_grind_any =
+ k_ent_objective_filter_grind_truck_any|
+ k_ent_objective_filter_grind_board_any,
+ k_ent_objective_filter_footplant = 0x00000080,
+ k_ent_objective_filter_passthrough = 0x00000100
+};
+
+enum ent_objective_flag {
+ k_ent_objective_hidden = 0x1,
+ k_ent_objective_passed = 0x2
+};
+
+struct ent_objective{
+ mdl_transform transform;
+ u32 submesh_start,
+ submesh_count,
+ flags,
+ id_next,
+ filter,filter2,
+ id_win;
+ i32 win_event;
+ f32 time_limit;
+};
+
+enum ent_challenge_flag {
+ k_ent_challenge_timelimit = 0x1
+};
+
+struct ent_challenge{
+ mdl_transform transform;
+ u32 pstr_alias,
+ flags,
+ target;
+ i32 target_event;
+ u32 reset;
+ i32 reset_event;
+ u32 first,
+ camera,
+ status;
+};
+
+struct ent_relay {
+ u32 targets[4][2];
+ i32 targets_events[4];
+};
+
+struct ent_cubemap {
+ v3f co;
+ u32 resolution, live, texture_id,
+ framebuffer_id, renderbuffer_id, placeholder[2];
+};
+
+struct ent_miniworld {
+ mdl_transform transform;
+ u32 pstr_world;
+ u32 camera;
+ u32 proxy;
+};
+
+struct ent_prop {
+ mdl_transform transform;
+ u32 submesh_start, submesh_count, flags, pstr_alias;
+};
+
+struct ent_region {
+ mdl_transform transform;
+ u32 submesh_start, submesh_count, pstr_title, flags, zone_volume,
+
+ /* 105+ */
+ target0[2];
+};
+
+struct ent_glider {
+ mdl_transform transform;
+ u32 flags;
+ f32 cooldown;
+};
+
+struct ent_npc
+{
+ mdl_transform transform;
+ u32 id, context, camera;
+};
+
+#include "world.h"
+
+struct ent_call{
+ u32 id;
+ i32 function;
+ void *data;
+};
+
+typedef enum entity_call_result
+ (*fn_entity_call_handler)( world_instance *, ent_call *);
+
+void entity_call( world_instance *world, ent_call *call );
--- /dev/null
+#pragma once
+#include "model.h"
+#include "entity.h"
+#include "vg/vg_camera.h"
+#include "shaders/model_font.h"
+#include "shaders/scene_font.h"
+#include "world_render.h"
+#include "depth_compare.h"
+#include "vg/vg_tex.h"
+#include <string.h>
+
+enum efont_SRglyph{
+ k_SRglyph_end = 0x00, /* control characters */
+ k_SRglyph_ctrl_variant = 0x01,
+ k_SRglyph_ctrl_size = 0x02, /* normalized 0-1 */
+ k_SRglyph_ctrl_center = 0x03, /* useful when text is scaled down */
+ k_SRglyph_ctrl_baseline = 0x04, /* . */
+ k_SRglyph_ctrl_top = 0x05, /* . */
+ k_SRglyph_mod_circle = 0x1e, /* surround and center next charater */
+ k_SRglyph_mod_square = 0x1f, /* surround and center next character */
+ k_SRglyph_ascii_min = 0x20, /* standard ascii */
+ k_SRglyph_ascii_max = 0x7e,
+ k_SRglyph_ps4_square = 0x7f,/* playstation buttons */
+ k_SRglyph_ps4_triangle = 0x80,
+ k_SRglyph_ps4_circle = 0x81,
+ k_SRglyph_ps4_cross = 0x82,
+ k_SRglyph_xb1_x = 0x83,/* xbox buttons */
+ k_SRglyph_xb1_y = 0x84,
+ k_SRglyph_xb1_a = 0x85,
+ k_SRglyph_xb1_b = 0x86,
+ k_SRglyph_gen_ls = 0x87,/* generic gamepad */
+ k_SRglyph_gen_lsh = 0x88,
+ k_SRglyph_gen_lsv = 0x89,
+ k_SRglyph_gen_lshv = 0x8a,
+ k_SRglyph_gen_rs = 0x8b,
+ k_SRglyph_gen_rsh = 0x8c,
+ k_SRglyph_gen_rsv = 0x8d,
+ k_SRglyph_gen_rshv = 0x8e,
+ k_SRglyph_gen_lt = 0x8f,
+ k_SRglyph_gen_rt = 0x90,
+ k_SRglyph_gen_lb = 0x91,
+ k_SRglyph_gen_rb = 0x92,
+ k_SRglyph_gen_left = 0x93,
+ k_SRglyph_gen_up = 0x94,
+ k_SRglyph_gen_right = 0x95,
+ k_SRglyph_gen_down = 0x96,
+ k_SRglyph_gen_options = 0x97,
+ k_SRglyph_gen_shareview = 0x98,
+ k_SRglyph_kbm_m0 = 0x99,/* mouse */
+ k_SRglyph_kbm_m1 = 0x9a,
+ k_SRglyph_kbm_m01 = 0x9b,
+ k_SRglyph_kbm_m2 = 0x9c,
+ k_SRglyph_kbm_m2s = 0x9d,
+ k_SRglyph_kbm_shift = 0x9e,/* modifiers */
+ k_SRglyph_kbm_ctrl = 0x9f,
+ k_SRglyph_kbm_alt = 0xa0,
+ k_SRglyph_kbm_space = 0xa1,
+ k_SRglyph_kbm_return = 0xa2,
+ k_SRglyph_kbm_escape = 0xa3,
+ k_SRglyph_kbm_mousemove = 0xa4,
+
+#if 0
+ k_SRglyph_vg_ret = 0xa5,
+ k_SRglyph_vg_link = 0xa6,
+ k_SRglyph_vg_square = 0xa7,
+ k_SRglyph_vg_triangle = 0xa8,
+ k_SRglyph_vg_circle = 0xa9
+#endif
+};
+
+typedef struct font3d font3d;
+struct font3d{
+ mdl_context mdl;
+ GLuint texture;
+ glmesh mesh;
+
+ ent_font info;
+ mdl_array_ptr font_variants,
+ glyphs;
+};
+
+static void font3d_load( font3d *font, const char *mdl_path, void *alloc ){
+ mdl_open( &font->mdl, mdl_path, alloc );
+ mdl_load_metadata_block( &font->mdl, alloc );
+
+ vg_linear_clear( vg_mem.scratch );
+ mdl_array_ptr fonts;
+ MDL_LOAD_ARRAY( &font->mdl, &fonts, ent_font, vg_mem.scratch );
+ font->info = *((ent_font *)mdl_arritm(&fonts,0));
+
+ MDL_LOAD_ARRAY( &font->mdl, &font->font_variants, ent_font_variant, alloc);
+ MDL_LOAD_ARRAY( &font->mdl, &font->glyphs, ent_glyph, alloc );
+
+ vg_linear_clear( vg_mem.scratch );
+
+ if( !mdl_arrcount( &font->mdl.textures ) )
+ vg_fatal_error( "No texture in font file" );
+
+ mdl_texture *tex0 = mdl_arritm( &font->mdl.textures, 0 );
+ void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
+ mdl_fread_pack_file( &font->mdl, &tex0->file, data );
+
+ mdl_async_load_glmesh( &font->mdl, &font->mesh, NULL );
+ vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
+ VG_TEX2D_LINEAR|VG_TEX2D_CLAMP,
+ &font->texture );
+
+ mdl_close( &font->mdl );
+}
+
+static u32 font3d_find_variant( font3d *font, const char *name ){
+ for( u32 i=0; i<mdl_arrcount( &font->font_variants ); i ++ ){
+ ent_font_variant *variant = mdl_arritm( &font->font_variants, i );
+
+ if( !strcmp( mdl_pstr( &font->mdl, variant->name ), name ) ){
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+struct _font3d_render{
+ v4f offset;
+ font3d *font;
+ u32 variant_id;
+
+ enum font_shader {
+ k_font_shader_default,
+ k_font_shader_world
+ }
+ shader;
+}
+static gui_font3d;
+
+/*
+ * world can be null if not using world shader
+ */
+static void font3d_bind( font3d *font, enum font_shader shader,
+ int depth_compare, world_instance *world,
+ vg_camera *cam ){
+ gui_font3d.shader = shader;
+ gui_font3d.font = font;
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, font->texture );
+
+ if( shader == k_font_shader_default )
+ {
+ shader_model_font_use();
+ shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
+ shader_model_font_uTexMain( 1 );
+ shader_model_font_uDepthMode( depth_compare );
+
+ if( depth_compare ){
+ depth_compare_bind(
+ shader_model_font_uTexSceneDepth,
+ shader_model_font_uInverseRatioDepth,
+ shader_model_font_uInverseRatioMain, cam );
+ }
+
+ shader_model_font_uPv( cam->mtx.pv );
+ }
+ else if( shader == k_font_shader_world )
+ {
+ shader_scene_font_use();
+ shader_scene_font_uTexGarbage(0);
+ shader_scene_font_uTexMain(1);
+
+ shader_scene_font_uPv( g_render.cam.mtx.pv );
+ shader_scene_font_uTime( vg.time );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_font );
+
+ bind_terrain_noise();
+ shader_scene_font_uCamera( g_render.cam.transform[3] );
+ }
+ mesh_bind( &font->mesh );
+}
+
+static ent_glyph *font3d_glyph( font3d *font, u32 variant_id, u32 utf32 ){
+ if( utf32 < font->info.glyph_utf32_base ) return NULL;
+ if( utf32 >= font->info.glyph_utf32_base+font->info.glyph_count) return NULL;
+
+ u32 index = utf32 - font->info.glyph_utf32_base;
+ index += font->info.glyph_start;
+ index += font->info.glyph_count * variant_id;
+ return mdl_arritm( &font->glyphs, index );
+}
+
+static void font3d_set_transform( const char *text,
+ vg_camera *cam, m4x3f transform ){
+ v4_copy( (v4f){0.0f,0.0f,0.0f,1.0f}, gui_font3d.offset );
+
+ m4x4f prev_mtx;
+ m4x3_expand( transform, prev_mtx );
+ m4x4_mul( cam->mtx_prev.pv, prev_mtx, prev_mtx );
+
+ if( gui_font3d.shader == k_font_shader_default ){
+ shader_model_font_uPvmPrev( prev_mtx );
+ shader_model_font_uMdl( transform );
+ }
+ else if( gui_font3d.shader == k_font_shader_world ){
+ shader_scene_font_uPvmPrev( prev_mtx );
+ shader_scene_font_uMdl( transform );
+ }
+}
+
+static void font3d_setoffset( v4f offset ){
+ if( gui_font3d.shader == k_font_shader_default )
+ shader_model_font_uOffset( offset );
+ else if( gui_font3d.shader == k_font_shader_world )
+ shader_scene_font_uOffset( offset );
+}
+
+static void font3d_setcolour( v4f colour ){
+ if( gui_font3d.shader == k_font_shader_default )
+ shader_model_font_uColour( colour );
+#if 0
+ else if( gui_font3d.shader == k_font_shader_world )
+ shader_scene_font_uColour( colour );
+#endif
+}
+
+static void font3d_draw( const char *text ){
+ u8 *u8pch = (u8*)text;
+
+ u32 max_chars = 512;
+ while( u8pch && max_chars ){
+ max_chars --;
+
+ u32 c0 = *u8pch, c1;
+ u8pch ++;
+
+ if( !c0 ) break;
+
+ ent_glyph *glyph0 = font3d_glyph( gui_font3d.font,
+ gui_font3d.variant_id, c0 ),
+ *glyph1 = NULL;
+
+ /* multibyte characters */
+ if( c0 >= 1 && c0 < k_SRglyph_ascii_min ){
+ c1 = *u8pch;
+ if( !c1 ) break;
+ glyph1 = font3d_glyph( gui_font3d.font, gui_font3d.variant_id, c1 );
+ }
+
+ if( c0 == k_SRglyph_ctrl_variant ){
+ gui_font3d.variant_id = c1;
+ u8pch ++;
+ continue;
+ }
+ else if( c0 == k_SRglyph_ctrl_size ){
+ gui_font3d.offset[3] = (float)c1 * (1.0f/255.0f);
+ u8pch ++;
+ continue;
+ }
+ else if( c0 == k_SRglyph_ctrl_baseline ){
+ gui_font3d.offset[1] = 0.0f;
+ continue;
+ }
+ else if( c0 == k_SRglyph_ctrl_center ){
+ if( glyph1 ){
+ float diff = glyph1->size[1] - glyph1->size[1]*gui_font3d.offset[3];
+ gui_font3d.offset[1] = diff * 0.5f;
+ }
+ continue;
+ }
+ else if( c0 == k_SRglyph_ctrl_top ){
+ if( glyph1 ){
+ float diff = glyph1->size[1] - glyph1->size[1]*gui_font3d.offset[3];
+ gui_font3d.offset[1] = diff;
+ }
+ continue;
+ }
+
+ if( !glyph0 ) continue;
+
+ if( glyph1 && (c0 == k_SRglyph_mod_square || c0 == k_SRglyph_mod_circle)){
+ v4f v0;
+ v2_sub( glyph0->size, glyph1->size, v0 );
+ v2_muladds( gui_font3d.offset, v0, -0.5f, v0 );
+ v0[2] = gui_font3d.offset[2];
+ v0[3] = gui_font3d.offset[3];
+
+ font3d_setoffset( v0 );
+ mesh_drawn( glyph0->indice_start, glyph0->indice_count );
+ continue;
+ }
+ else{
+ font3d_setoffset( gui_font3d.offset );
+ mesh_drawn( glyph0->indice_start, glyph0->indice_count );
+ }
+
+ gui_font3d.offset[0] += glyph0->size[0]*gui_font3d.offset[3];
+ }
+}
+
+static f32 font3d_simple_draw( u32 variant_id, const char *text,
+ vg_camera *cam, m4x3f transform ){
+ if( !text ) return 0.0f;
+
+ gui_font3d.variant_id = variant_id;
+ font3d_set_transform( text, cam, transform );
+ font3d_draw( text );
+ return gui_font3d.offset[0];
+}
+
+static f32 font3d_string_width( u32 variant_id, const char *text ){
+ if( !text ) return 0.0f;
+ float width = 0.0f;
+
+ const u8 *buf = (const u8 *)text;
+ for( int i=0;; i++ ){
+ u32 c = buf[i];
+ if(!c) break;
+
+ ent_glyph *glyph = font3d_glyph( gui_font3d.font, variant_id, c );
+ if( !glyph ) continue;
+
+ width += glyph->size[0];
+ }
+
+ return width;
+}
--- /dev/null
+#include "skaterift.h"
+#include "player.h"
+#include "player_render.h"
+#include "player_replay.h"
+#include "input.h"
+
+void freecam_preupdate(void)
+{
+ vg_camera *cam = &player_replay.replay_freecam;
+ v3f angles;
+ v3_copy( cam->angles, angles );
+ player_look( angles, 1.0f );
+
+ f32 decay = vg_maxf(0.0f,1.0f-vg.time_frame_delta*10.0f);
+
+ v3f d;
+ v3_sub( angles, cam->angles, d );
+ v3_muladds( player_replay.freecam_w, d, 20.0f, player_replay.freecam_w );
+ v3_muls( player_replay.freecam_w, decay, player_replay.freecam_w );
+ v3_muladds( cam->angles, player_replay.freecam_w, vg.time_frame_delta,
+ cam->angles );
+ cam->angles[1] = vg_clampf( cam->angles[1], -VG_PIf*0.5f,VG_PIf*0.5f);
+
+ vg_camera_update_transform( cam );
+
+ v3f lookdir = { 0.0f, 0.0f, -1.0f },
+ sidedir = { 1.0f, 0.0f, 0.0f };
+
+ m3x3_mulv( cam->transform, lookdir, lookdir );
+ m3x3_mulv( cam->transform, sidedir, sidedir );
+
+ v2f input;
+ joystick_state( k_srjoystick_steer, input );
+ v2_muls( input, vg.time_frame_delta*6.0f*20.0f, input );
+
+ v3_muladds( player_replay.freecam_v, lookdir, -input[1],
+ player_replay.freecam_v );
+ v3_muladds( player_replay.freecam_v, sidedir, input[0],
+ player_replay.freecam_v );
+
+ v3_muls( player_replay.freecam_v, decay, player_replay.freecam_v );
+ v3_muladds( cam->pos,
+ player_replay.freecam_v, vg.time_frame_delta, cam->pos );
+}
--- /dev/null
+#pragma once
+void freecam_preupdate(void);
+int freecam_cmd( int argc, const char *argv[] );
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#define _DEFAULT_SOURCE
+#include <signal.h>
+#include <unistd.h>
+#include <time.h>
+#include <string.h>
+
+volatile sig_atomic_t sig_stop;
+
+#include "gameserver.h"
+#include "vg/vg_opt.h"
+#include "network_common.h"
+#include "gameserver_db.h"
+#include "vg/vg_m.h"
+#include "vg/vg_msg.h"
+
+static u64 const k_steamid_max = 0xffffffffffffffff;
+
+static void inthandler( int signum ) {
+ sig_stop = 1;
+}
+
+static void release_message( SteamNetworkingMessage_t *msg )
+{
+ msg->m_nUserData --;
+
+ if( msg->m_nUserData == 0 )
+ SteamAPI_SteamNetworkingMessage_t_Release( msg );
+}
+
+/*
+ * Send message to single client, with authentication checking
+ */
+static void gameserver_send_to_client( i32 client_id,
+ const void *pData, u32 cbData,
+ int nSendFlags )
+{
+ struct gameserver_client *client = &gameserver.clients[ client_id ];
+
+ if( gameserver.loopback_test && !client->connection )
+ return;
+
+ if( !client->steamid )
+ return;
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, client->connection,
+ pData, cbData, nSendFlags, NULL );
+}
+
+/*
+ * Send message to all clients if they are authenticated
+ */
+static void gameserver_send_to_all( int ignore,
+ const void *pData, u32 cbData,
+ int nSendFlags )
+{
+ for( int i=0; i<vg_list_size(gameserver.clients); i++ )
+ {
+ struct gameserver_client *client = &gameserver.clients[i];
+
+ if( i != ignore )
+ gameserver_send_to_client( i, pData, cbData, nSendFlags );
+ }
+}
+
+static void gameserver_send_version_to_client( int index )
+{
+ struct gameserver_client *client = &gameserver.clients[index];
+
+ if( gameserver.loopback_test && !client->connection )
+ return;
+
+ netmsg_version version;
+ version.inetmsg_id = k_inetmsg_version;
+ version.version = NETWORK_SKATERIFT_VERSION;
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, client->connection,
+ &version, sizeof(netmsg_version),
+ k_nSteamNetworkingSend_Reliable, NULL );
+}
+
+/*
+ * handle server update that client #'index' has joined
+ */
+static void gameserver_player_join( int index )
+{
+ struct gameserver_client *joiner = &gameserver.clients[index];
+
+ netmsg_playerjoin join = { .inetmsg_id = k_inetmsg_playerjoin,
+ .index = index,
+ .steamid = joiner->steamid };
+
+ gameserver_send_to_all( index, &join, sizeof(join),
+ k_nSteamNetworkingSend_Reliable );
+
+ /*
+ * update the joining user about current connections and our version
+ */
+ gameserver_send_version_to_client( index );
+
+ netmsg_playerusername *username =
+ alloca( sizeof(netmsg_playerusername) + NETWORK_USERNAME_MAX );
+ username->inetmsg_id = k_inetmsg_playerusername;
+
+ netmsg_playeritem *item =
+ alloca( sizeof(netmsg_playeritem) + ADDON_UID_MAX );
+ item->inetmsg_id = k_inetmsg_playeritem;
+
+ netmsg_region *region = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
+ region->inetmsg_id = k_inetmsg_region;
+
+ for( int i=0; i<vg_list_size(gameserver.clients); i++ )
+ {
+ struct gameserver_client *client = &gameserver.clients[i];
+
+ if( (i == index) || !client->steamid )
+ continue;
+
+ /* join */
+ netmsg_playerjoin init = { .inetmsg_id = k_inetmsg_playerjoin,
+ .index = i,
+ .steamid = client->steamid };
+ gameserver_send_to_client( index, &init, sizeof(init),
+ k_nSteamNetworkingSend_Reliable );
+
+ /* username */
+ username->index = i;
+ u32 chs = vg_strncpy( client->username, username->name,
+ NETWORK_USERNAME_MAX,
+ k_strncpy_always_add_null );
+ u32 size = sizeof(netmsg_playerusername) + chs + 1;
+ gameserver_send_to_client( index, username, size,
+ k_nSteamNetworkingSend_Reliable );
+
+ /* items */
+ for( int j=0; j<k_netmsg_playeritem_max; j++ )
+ {
+ chs = vg_strncpy( client->items[j].uid, item->uid, ADDON_UID_MAX,
+ k_strncpy_always_add_null );
+ item->type_index = j;
+ item->client = i;
+ size = sizeof(netmsg_playeritem) + chs + 1;
+ gameserver_send_to_client( index, item, size,
+ k_nSteamNetworkingSend_Reliable );
+ }
+
+ /* region */
+
+ region->client = i;
+ region->flags = client->region_flags;
+ u32 l = vg_strncpy( client->region, region->loc, NETWORK_REGION_MAX,
+ k_strncpy_always_add_null );
+ size = sizeof(netmsg_region) + l + 1;
+
+ gameserver_send_to_client( index, region, size,
+ k_nSteamNetworkingSend_Reliable );
+ }
+}
+
+/*
+ * Handle server update that player has left
+ */
+static void gameserver_player_leave( int index ){
+ if( gameserver.auth_mode == eServerModeAuthentication ){
+ if( !gameserver.clients[ index ].steamid )
+ return;
+ }
+
+ netmsg_playerleave leave;
+ leave.inetmsg_id = k_inetmsg_playerleave;
+ leave.index = index;
+
+ vg_info( "Player leave (%d)\n", index );
+ gameserver_send_to_all( index, &leave, sizeof(leave),
+ k_nSteamNetworkingSend_Reliable );
+}
+
+static void gameserver_update_all_knowledge( int client, int clear );
+
+/*
+ * Deletes client at index and disconnects the connection handle if it was
+ * set.
+ */
+static void remove_client( int index ){
+ struct gameserver_client *client = &gameserver.clients[index];
+ if( client->connection ){
+ SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
+ hSteamNetworkingSockets, client->connection, -1 );
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, client->connection,
+ k_ESteamNetConnectionEnd_Misc_InternalError,
+ NULL, 1 );
+ }
+ memset( client, 0, sizeof(struct gameserver_client) );
+ gameserver_update_all_knowledge( index, 1 );
+}
+
+/*
+ * Handle incoming new connection and init flags on the steam handle. if the
+ * server is full the userdata (client_id) will be set to -1 on the handle.
+ */
+static void handle_new_connection( HSteamNetConnection conn )
+{
+ SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
+ hSteamNetworkingSockets, conn, -1 );
+
+ int index = -1;
+
+ for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
+ if( !gameserver.clients[i].active ){
+ index = i;
+ break;
+ }
+ }
+
+ if( index == -1 ){
+ vg_error( "Server full\n" );
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, conn,
+ 4500,
+ NULL, 1 );
+ return;
+ }
+
+ struct gameserver_client *client = &gameserver.clients[index];
+ EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
+ hSteamNetworkingSockets, conn );
+
+ if( accept_status == k_EResultOK )
+ {
+ vg_success( "Accepted client (id: %u, index: %d)\n", conn, index );
+
+ client->active = 1;
+ client->connection = conn;
+
+ SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
+ hSteamNetworkingSockets, conn, gameserver.client_group );
+
+ SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
+ hSteamNetworkingSockets, conn, index );
+
+ if( gameserver.loopback_test )
+ {
+ vg_warn( "[DEV] Creating loopback client\n" );
+ struct gameserver_client *loopback = &gameserver.clients[1];
+ loopback->active = 1;
+ loopback->connection = 0;
+ }
+ }
+ else
+ {
+ vg_warn( "Error accepting connection (id: %u)\n", conn );
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, conn,
+ k_ESteamNetConnectionEnd_Misc_InternalError,
+ NULL, 1 );
+ }
+}
+
+static void on_auth_status( CallbackMsg_t *msg ){
+ SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam;
+ vg_info( " Authentication availibility: %s\n",
+ string_ESteamNetworkingAvailability(info->m_eAvail) );
+ vg_info( " %s\n", info->m_debugMsg );
+}
+
+/*
+ * Get client id of connection handle. Will be -1 if unkown to us either because
+ * the server is full or we already disconnected them
+ */
+static i32 gameserver_conid( HSteamNetConnection hconn )
+{
+ i64 id;
+
+ if( hconn == 0 )
+ {
+ if( gameserver.loopback_test )
+ return 1;
+ else
+ return -1;
+ }
+ else
+ id = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
+ hSteamNetworkingSockets, hconn );
+
+ if( (id < 0) || (id >= NETWORK_MAX_PLAYERS) )
+ return -1;
+
+ return id;
+}
+
+/*
+ * Callback for steam connection state change
+ */
+static void on_connect_status( CallbackMsg_t *msg )
+{
+ SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
+ vg_info( " Connection status changed for %lu\n", info->m_hConn );
+
+ vg_info( " %s -> %s\n",
+ string_ESteamNetworkingConnectionState(info->m_eOldState),
+ string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
+
+ if( info->m_info.m_eState==k_ESteamNetworkingConnectionState_Connecting )
+ {
+ handle_new_connection( info->m_hConn );
+ }
+
+ if( (info->m_info.m_eState ==
+ k_ESteamNetworkingConnectionState_ClosedByPeer ) ||
+ (info->m_info.m_eState ==
+ k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) ||
+ (info->m_info.m_eState ==
+ k_ESteamNetworkingConnectionState_Dead) ||
+ (info->m_info.m_eState ==
+ k_ESteamNetworkingConnectionState_None) )
+ {
+ vg_info( "End reason: %d\n", info->m_info.m_eEndReason );
+
+ int client_id = gameserver_conid( info->m_hConn );
+ if( client_id != -1 )
+ {
+ gameserver_player_leave( client_id );
+ remove_client( client_id );
+
+ if( gameserver.loopback_test )
+ {
+ gameserver_player_leave( 1 );
+ remove_client( 1 );
+ }
+ }
+ else
+ {
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, info->m_hConn, 0, NULL, 0 );
+ }
+ }
+}
+
+static void gameserver_rx_version( SteamNetworkingMessage_t *msg )
+{
+ netmsg_version *version = msg->m_pData;
+
+ int client_id = gameserver_conid( msg->m_conn );
+ if( client_id == -1 )
+ {
+ vg_warn( "Recieved version from unkown connection (%u)\n", msg->m_conn );
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, msg->m_conn,
+ k_ESteamNetConnectionEnd_Misc_InternalError,
+ NULL, 1 );
+ return;
+ }
+
+ struct gameserver_client *client = &gameserver.clients[ client_id ];
+
+ if( client->version )
+ {
+ vg_warn( "Already have version for this client (%d conn: %u)",
+ client_id, msg->m_conn );
+ return;
+ }
+
+ client->version = version->version;
+
+ if( client->version != NETWORK_SKATERIFT_VERSION )
+ {
+ gameserver_send_version_to_client( client_id );
+ remove_client( client_id );
+ return;
+ }
+
+ /* this is the sign on point for non-auth servers,
+ * for auth servers it comes at the end of rx_auth
+ */
+ if( gameserver.auth_mode != eServerModeAuthentication )
+ {
+ client->steamid = k_steamid_max;
+ gameserver_player_join( client_id );
+
+ if( gameserver.loopback_test )
+ {
+ struct gameserver_client *loopback = &gameserver.clients[1];
+ loopback->steamid = k_steamid_max;
+ gameserver_player_join( 1 );
+ }
+ }
+}
+
+/*
+ * recieve auth ticket from connection. will only accept it if we've added them
+ * to the client list first.
+ */
+static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){
+ if( gameserver.auth_mode != eServerModeAuthentication ){
+ vg_warn( "Running server without authentication. "
+ "Connection %u tried to authenticate.\n", msg->m_conn );
+ return;
+ }
+
+ int client_id = gameserver_conid( msg->m_conn );
+ if( client_id == -1 ) {
+ vg_warn( "Recieved auth ticket from unkown connection (%u)\n",
+ msg->m_conn );
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, msg->m_conn,
+ k_ESteamNetConnectionEnd_Misc_InternalError, NULL, 1 );
+ return;
+ }
+
+ struct gameserver_client *client = &gameserver.clients[ client_id ];
+ if( client->steamid ){
+ vg_warn( "Already authorized this user but another app ticket was sent"
+ " again (%d conn: %u)\n", client_id, msg->m_conn );
+ return;
+ }
+
+ if( client->version == 0 ){
+ vg_error( "Client has not sent their version yet (%u)\n", msg->m_conn );
+ remove_client( client_id );
+ return;
+ }
+
+ vg_low( "Attempting to verify user\n" );
+
+ if( msg->m_cbSize < sizeof(netmsg_auth) ){
+ vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn );
+ remove_client( client_id );
+ return;
+ }
+
+ netmsg_auth *auth = msg->m_pData;
+
+ if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length ||
+ auth->ticket_length > 1024 ){
+ vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
+ auth->ticket_length );
+ remove_client( client_id );
+ return;
+ }
+
+ u8 decrypted[1024];
+ u32 ticket_len = 1024;
+
+ int success = SteamEncryptedAppTicket_BDecryptTicket(
+ auth->ticket, auth->ticket_length, decrypted,
+ &ticket_len, gameserver.app_symmetric_key,
+ k_nSteamEncryptedAppTicketSymmetricKeyLen );
+
+ if( !success ){
+ vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn );
+ vg_error( " ticket length: %u\n", auth->ticket_length );
+ remove_client( client_id );
+ return;
+ }
+
+ if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len )){
+ RTime32 ctime = time(NULL),
+ tickettime = SteamEncryptedAppTicket_GetTicketIssueTime(
+ decrypted, ticket_len ),
+ expiretime = tickettime + 24*3*60*60;
+
+ if( ctime > expiretime ){
+ vg_error( "Ticket expired (client %u)\n", msg->m_conn );
+ remove_client( client_id );
+ return;
+ }
+ }
+
+ CSteamID steamid;
+ SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid );
+ vg_success( "User is authenticated! steamid %lu (%u)\n",
+ steamid.m_unAll64Bits, msg->m_conn );
+
+ client->steamid = steamid.m_unAll64Bits;
+ gameserver_player_join( client_id );
+}
+
+/*
+ * Player updates sent to us
+ * -----------------------------------------------------------------------------
+ */
+
+static int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){
+ if( msg->m_cbSize < size ) {
+ vg_error( "Invalid packet size (must be at least %u)\n", size );
+ return 0;
+ }
+ else{
+ return 1;
+ }
+}
+
+struct db_set_username_thread_data {
+ u64 steamid;
+ char username[ NETWORK_USERNAME_MAX ];
+};
+
+static void gameserver_update_db_username( db_request *db_req ){
+ struct db_set_username_thread_data *inf = (void *)db_req->data;
+
+ if( inf->steamid == k_steamid_max )
+ return;
+
+ int admin = 0;
+ if( inf->steamid == 76561198072130043 )
+ admin = 2;
+
+ db_updateuser( inf->steamid, inf->username, admin );
+}
+
+static int gameserver_item_eq( struct gameserver_item *ia,
+ struct gameserver_item *ib ){
+ if( ia->hash == ib->hash )
+ if( !strcmp(ia->uid,ib->uid) )
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Match addons between two player IDs. if clear is set, then the flags between
+ * those two IDs will all be set to 0.
+ */
+static void gameserver_update_knowledge_table( int client0, int client1,
+ int clear ){
+ u32 idx = network_pair_index( client0, client1 );
+
+ struct gameserver_client *c0 = &gameserver.clients[client0],
+ *c1 = &gameserver.clients[client1];
+
+ u8 flags = 0x00;
+
+ if( !clear ){
+ if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world0],
+ &c1->items[k_netmsg_playeritem_world0]))
+ flags |= CLIENT_KNOWLEDGE_SAME_WORLD0;
+
+ if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world1],
+ &c1->items[k_netmsg_playeritem_world1]))
+ flags |= CLIENT_KNOWLEDGE_SAME_WORLD1;
+ }
+
+ gameserver.client_knowledge_mask[idx] = flags;
+}
+
+/*
+ * If a change has been made on this client, then it will adjust the entire
+ * table of other players. if clear is set, all references to client will be set
+ * to 0.
+ */
+static void gameserver_update_all_knowledge( int client, int clear ){
+ for( int i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
+ if( i == client )
+ continue;
+
+ struct gameserver_client *ci = &gameserver.clients[i];
+
+ if( ci->steamid )
+ gameserver_update_knowledge_table( client, i, clear );
+ }
+}
+
+static void gameserver_propogate_player_frame( int client_id,
+ netmsg_playerframe *frame,
+ u32 size ){
+ u32 basic_size = sizeof(netmsg_playerframe) + ((24*3)/8);
+ netmsg_playerframe *full = alloca(size),
+ *basic= alloca(basic_size);
+
+ memcpy( full, frame, size );
+ memcpy( basic, frame, basic_size );
+
+ full->client = client_id;
+ basic->client = client_id;
+ basic->subsystem = 4; /* (.._basic_info: 24f*3 animator ) */
+ basic->sound_effects = 0;
+
+ struct gameserver_client *c0 = &gameserver.clients[client_id];
+ c0->instance = frame->flags & NETMSG_PLAYERFRAME_INSTANCE_ID;
+
+ for( int i=0; i<vg_list_size(gameserver.clients); i++ )
+ {
+ if( i == client_id )
+ continue;
+
+ struct gameserver_client *ci = &gameserver.clients[i];
+
+ int send_full = 0;
+
+ if( c0->instance == ci->instance )
+ {
+ u32 k_index = network_pair_index( client_id, i );
+ u8 k_mask = gameserver.client_knowledge_mask[ k_index ];
+
+ if( (k_mask & (CLIENT_KNOWLEDGE_SAME_WORLD0<<c0->instance)) )
+ send_full = 1;
+ }
+
+ if( send_full )
+ {
+ gameserver_send_to_client( i, full, size,
+ k_nSteamNetworkingSend_Unreliable );
+ }
+ else
+ {
+ gameserver_send_to_client( i, basic, basic_size,
+ k_nSteamNetworkingSend_Unreliable );
+ }
+ }
+}
+
+static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg )
+{
+ netmsg_blank *tmp = msg->m_pData;
+
+ int client_id = gameserver_conid( msg->m_conn );
+ if( client_id == -1 ) return;
+
+ struct gameserver_client *client = &gameserver.clients[ client_id ];
+
+ if( tmp->inetmsg_id == k_inetmsg_playerusername )
+ {
+ if( !packet_minsize( msg, sizeof(netmsg_playerusername)+1 ))
+ return;
+
+ netmsg_playerusername *src = msg->m_pData;
+
+ u32 name_len = network_msgstring( src->name, msg->m_cbSize,
+ sizeof(netmsg_playerusername),
+ client->username,
+ NETWORK_USERNAME_MAX );
+
+ /* update other users about this change */
+ netmsg_playerusername *prop = alloca(sizeof(netmsg_playerusername)+
+ NETWORK_USERNAME_MAX );
+
+ prop->inetmsg_id = k_inetmsg_playerusername;
+ prop->index = client_id;
+ u32 chs = vg_strncpy( client->username, prop->name, NETWORK_USERNAME_MAX,
+ k_strncpy_always_add_null );
+
+ vg_info( "client #%d changed name to: %s\n", client_id, prop->name );
+
+ u32 propsize = sizeof(netmsg_playerusername) + chs + 1;
+ gameserver_send_to_all( client_id, prop, propsize,
+ k_nSteamNetworkingSend_Reliable );
+
+ /* update database about this */
+ db_request *call = db_alloc_request(
+ sizeof(struct db_set_username_thread_data) );
+ struct db_set_username_thread_data *inf = (void *)call->data;
+ inf->steamid = client->steamid;
+ vg_strncpy( client->username, inf->username,
+ sizeof(inf->username), k_strncpy_always_add_null );
+ call->handler = gameserver_update_db_username;
+ db_send_request( call );
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_playerframe )
+ {
+ gameserver_propogate_player_frame( client_id,
+ msg->m_pData, msg->m_cbSize );
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_playeritem )
+ {
+ netmsg_playeritem *item = msg->m_pData;
+
+ /* record */
+ if( item->type_index >= k_netmsg_playeritem_max )
+ {
+ vg_warn( "Client #%d invalid equip type %u\n",
+ client_id, (u32)item->type_index );
+ return;
+ }
+
+ char *dest = client->items[ item->type_index ].uid;
+
+ network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem),
+ dest, ADDON_UID_MAX );
+
+ vg_info( "Client #%d equiped: [%s] %s\n",
+ client_id,
+ (const char *[]){[k_netmsg_playeritem_board]="board",
+ [k_netmsg_playeritem_player]="player",
+ [k_netmsg_playeritem_world0]="world0",
+ [k_netmsg_playeritem_world1]="world1"
+ }[item->type_index], item->uid );
+
+ gameserver_update_all_knowledge( client_id, 0 );
+
+ /* propogate */
+ netmsg_playeritem *prop = alloca(msg->m_cbSize);
+ memcpy( prop, msg->m_pData, msg->m_cbSize );
+ prop->client = client_id;
+ gameserver_send_to_all( client_id, prop, msg->m_cbSize,
+ k_nSteamNetworkingSend_Reliable );
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_chat )
+ {
+ netmsg_chat *chat = msg->m_pData,
+ *prop = alloca( sizeof(netmsg_chat) + NETWORK_MAX_CHAT );
+ prop->inetmsg_id = k_inetmsg_chat;
+ prop->client = client_id;
+
+ u32 l = network_msgstring( chat->msg, msg->m_cbSize, sizeof(netmsg_chat),
+ prop->msg, NETWORK_MAX_CHAT );
+ vg_info( "[%d]: %s\n", client_id, prop->msg );
+
+ gameserver_send_to_all( client_id, prop, sizeof(netmsg_chat)+l+1,
+ k_nSteamNetworkingSend_Reliable );
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_region )
+ {
+ netmsg_region *region = msg->m_pData,
+ *prop = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
+
+ prop->inetmsg_id = k_inetmsg_region;
+ prop->client = client_id;
+ prop->flags = region->flags;
+
+ u32 l = network_msgstring(
+ region->loc, msg->m_cbSize, sizeof(netmsg_region),
+ client->region, NETWORK_REGION_MAX );
+ client->region_flags = region->flags;
+
+ l = vg_strncpy( client->region, prop->loc, NETWORK_REGION_MAX,
+ k_strncpy_always_add_null );
+
+ gameserver_send_to_all( client_id, prop, sizeof(netmsg_region)+l+1,
+ k_nSteamNetworkingSend_Reliable );
+ vg_info( "client %d moved to region: %s\n", client_id, client->region );
+ }
+ else
+ {
+ vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
+ tmp->inetmsg_id );
+ }
+}
+
+static void gameserver_request_respond( enum request_status status,
+ netmsg_request *res, vg_msg *body,
+ SteamNetworkingMessage_t *msg ){
+ int client_id = gameserver_conid( msg->m_conn );
+ u32 len = 0;
+ if( body ){
+ len = body->cur.co;
+ vg_low( "[%d#%d] Response: %d\n", client_id, (i32)res->id, status );
+ vg_msg_print( body, len );
+ }
+
+ res->status = status;
+
+ if( gameserver.loopback_test && !msg->m_conn )
+ {
+ release_message( msg );
+ return;
+ }
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, msg->m_conn,
+ res, sizeof(netmsg_request) + len,
+ k_nSteamNetworkingSend_Reliable, NULL );
+
+ release_message( msg );
+}
+
+struct user_request_thread_data {
+ SteamNetworkingMessage_t *msg;
+};
+
+static u32 gameserver_get_current_week(void){
+ return time(NULL) / (7*24*60*60);
+}
+
+static enum request_status gameserver_cat_table(
+ vg_msg *msg,
+ const char *mod, const char *route, u32 week, const char *alias )
+{
+ char table_name[ DB_TABLE_UID_MAX ];
+ if( !db_get_highscore_table_name( mod, route, week, table_name ) )
+ return k_request_status_out_of_memory;
+
+ char buf[512];
+ vg_str q;
+ vg_strnull( &q, buf, 512 );
+ vg_strcat( &q, "SELECT * FROM \"" );
+ vg_strcat( &q, table_name );
+ vg_strcat( &q, "\" ORDER BY time ASC LIMIT 10;" );
+ if( !vg_strgood(&q) )
+ return k_request_status_out_of_memory;
+
+ sqlite3_stmt *stmt = db_stmt( q.buffer );
+ if( !stmt )
+ return k_request_status_database_error;
+
+ vg_msg_frame( msg, alias );
+ for( u32 i=0; i<10; i ++ ){
+ int fc = sqlite3_step( stmt );
+
+ if( fc == SQLITE_ROW ){
+ i32 time = sqlite3_column_int( stmt, 1 );
+ i64 steamid_i64 = sqlite3_column_int64( stmt, 0 );
+ u64 steamid = *((u64 *)&steamid_i64);
+
+ if( steamid == k_steamid_max )
+ continue;
+
+ vg_msg_frame( msg, "" );
+ vg_msg_wkvnum( msg, "time", k_vg_msg_u32, 1, &time );
+ vg_msg_wkvnum( msg, "steamid", k_vg_msg_u64, 1, &steamid );
+
+ char username[32];
+ if( db_getuserinfo( steamid, username, sizeof(username), NULL ) )
+ vg_msg_wkvstr( msg, "username", username );
+ vg_msg_end_frame( msg );
+ }
+ else if( fc == SQLITE_DONE ){
+ break;
+ }
+ else {
+ log_sqlite3( fc );
+ break;
+ }
+ }
+
+ sqlite3_finalize( stmt );
+ vg_msg_end_frame( msg );
+ return k_request_status_ok;
+}
+
+static void gameserver_process_user_request( db_request *db_req )
+{
+ struct user_request_thread_data *inf = (void *)db_req->data;
+ SteamNetworkingMessage_t *msg = inf->msg;
+
+ int client_id = gameserver_conid( msg->m_conn );
+ if( client_id == -1 )
+ {
+ release_message( msg );
+ return;
+ }
+
+ struct gameserver_client *client = &gameserver.clients[ client_id ];
+
+ netmsg_request *req = (netmsg_request *)msg->m_pData;
+ vg_msg data;
+ vg_msg_init( &data, req->q, msg->m_cbSize - sizeof(netmsg_request) );
+
+ /* create response packet */
+ netmsg_request *res = alloca( sizeof(netmsg_request) + NETWORK_REQUEST_MAX );
+ res->inetmsg_id = k_inetmsg_response;
+ res->id = req->id;
+ vg_msg body;
+ vg_msg_init( &body, res->q, NETWORK_REQUEST_MAX );
+
+ const char *endpoint = vg_msg_getkvstr( &data, "endpoint" );
+
+ if( !endpoint ){
+ gameserver_request_respond( k_request_status_invalid_endpoint,
+ res, NULL, msg );
+ return;
+ }
+
+ if( !strcmp( endpoint, "scoreboard" ) ){
+ const char *mod = vg_msg_getkvstr( &data, "mod" );
+ const char *route = vg_msg_getkvstr( &data, "route" );
+ u32 week;
+ vg_msg_getkvintg( &data, "week", k_vg_msg_u32, &week, NULL );
+
+ if( week == NETWORK_LEADERBOARD_CURRENT_WEEK ){
+ gameserver_cat_table( &body, mod, route,
+ gameserver_get_current_week(), "rows_weekly" );
+ }
+ else if( week == NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK ){
+ gameserver_cat_table( &body, mod, route, 0, "rows" );
+ gameserver_cat_table( &body, mod, route,
+ gameserver_get_current_week(), "rows_weekly" );
+ }
+ else
+ gameserver_cat_table( &body, mod, route, week, "rows" );
+
+ if( body.error != k_vg_msg_error_OK ){
+ gameserver_request_respond( k_request_status_out_of_memory,
+ res, NULL, msg );
+ return;
+ }
+
+ gameserver_request_respond( k_request_status_ok, res, &body, msg );
+ }
+ else if( !strcmp( endpoint, "setlap" ) ){
+ if( client->steamid == k_steamid_max ){
+ gameserver_request_respond( k_request_status_unauthorized,
+ res, NULL, msg );
+ return;
+ }
+
+ const char *mod = vg_msg_getkvstr( &data, "mod" );
+ const char *route = vg_msg_getkvstr( &data, "route" );
+
+ char weekly_table[ DB_TABLE_UID_MAX ],
+ alltime_table[ DB_TABLE_UID_MAX ];
+
+ u32 week = gameserver_get_current_week();
+
+ if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) ||
+ !db_get_highscore_table_name( mod, route, week, weekly_table ) ){
+ gameserver_request_respond( k_request_status_out_of_memory,
+ res, NULL, msg );
+ return;
+ }
+
+ i32 centiseconds;
+ vg_msg_getkvintg( &data, "time", k_vg_msg_i32, ¢iseconds, NULL );
+ if( centiseconds < 5*100 ){
+ gameserver_request_respond( k_request_status_client_error,
+ res, NULL, msg );
+ return;
+ }
+
+ db_writeusertime( alltime_table, client->steamid, centiseconds, 1 );
+ db_writeusertime( weekly_table, client->steamid, centiseconds, 1 );
+ gameserver_request_respond( k_request_status_ok, res, NULL, msg );
+ }
+ else{
+ gameserver_request_respond( k_request_status_invalid_endpoint,
+ res, NULL, msg );
+ }
+}
+
+static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg )
+{
+ netmsg_blank *tmp = msg->m_pData;
+
+ int client_id = gameserver_conid( msg->m_conn );
+ if( client_id == -1 )
+ {
+ release_message( msg );
+ return;
+ }
+
+ if( tmp->inetmsg_id == k_inetmsg_request )
+ {
+ if( gameserver.loopback_test && (client_id == 1) )
+ {
+ release_message( msg );
+ return;
+ }
+
+ if( !packet_minsize( msg, sizeof(netmsg_request)+1 ))
+ {
+ release_message( msg );
+ return;
+ }
+
+ db_request *call = db_alloc_request(
+ sizeof(struct user_request_thread_data) );
+ struct user_request_thread_data *inf = (void *)call->data;
+ inf->msg = msg;
+ call->handler = gameserver_process_user_request;
+ db_send_request( call );
+ }
+ else
+ {
+ vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
+ tmp->inetmsg_id );
+ release_message( msg );
+ }
+}
+
+static void process_network_message( SteamNetworkingMessage_t *msg )
+{
+ if( msg->m_cbSize < sizeof(netmsg_blank) ){
+ vg_warn( "Discarding message (too small: %d)\n",
+ msg->m_cbSize );
+ return;
+ }
+
+ netmsg_blank *tmp = msg->m_pData;
+
+ if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) )
+ {
+ gameserver_rx_200_300( msg );
+ release_message( msg );
+ }
+ else if( (tmp->inetmsg_id >= 300) && (tmp->inetmsg_id < 400) )
+ {
+ gameserver_rx_300_400( msg );
+ }
+ else{
+ if( tmp->inetmsg_id == k_inetmsg_auth )
+ gameserver_rx_auth( msg );
+ else if( tmp->inetmsg_id == k_inetmsg_version ){
+ gameserver_rx_version( msg );
+ }
+ else {
+ vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
+ tmp->inetmsg_id );
+ }
+ release_message( msg );
+ }
+}
+
+static void poll_connections(void)
+{
+ SteamNetworkingMessage_t *messages[32];
+ int len;
+
+ while(1)
+ {
+ len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
+ hSteamNetworkingSockets,
+ gameserver.client_group, messages, vg_list_size(messages) );
+
+ if( len <= 0 )
+ return;
+
+ for( int i=0; i<len; i++ )
+ {
+ SteamNetworkingMessage_t *msg = messages[i];
+ msg->m_nUserData = 1;
+
+ if( gameserver.loopback_test )
+ {
+ HSteamNetConnection conid = msg->m_conn;
+ msg->m_conn = 0;
+ msg->m_nUserData ++;
+ process_network_message( msg );
+ msg->m_conn = conid;
+ }
+
+ process_network_message( msg );
+ }
+ }
+}
+
+static u64 seconds_to_server_ticks( double s ){
+ return s / 0.01;
+}
+
+int main( int argc, char *argv[] ){
+ signal( SIGINT, inthandler );
+ signal( SIGQUIT, inthandler );
+ signal( SIGPIPE, SIG_IGN );
+
+ char *arg;
+ while( vg_argp( argc, argv ) )
+ {
+ if( vg_long_opt( "noauth" ) )
+ gameserver.auth_mode = eServerModeNoAuthentication;
+
+ if( vg_long_opt( "loopback" ) )
+ gameserver.loopback_test = 1;
+ }
+
+ vg_set_mem_quota( 80*1024*1024 );
+ vg_alloc_quota();
+ db_init();
+
+ /* steamworks init
+ * --------------------------------------------------------------- */
+ steamworks_ensure_txt( "2103940" );
+ if( gameserver.auth_mode == eServerModeAuthentication ){
+ if( !vg_load_steam_symetric_key( "application_key",
+ gameserver.app_symmetric_key )){
+ return 0;
+ }
+ }
+ else{
+ vg_warn( "Running without user authentication.\n" );
+ }
+
+ if( !SteamGameServer_Init( 0, NETWORK_PORT, NETWORK_PORT+1,
+ gameserver.auth_mode, "1.0.0.0" ) ){
+ vg_error( "SteamGameServer_Init failed\n" );
+ return 0;
+ }
+
+ void *hSteamGameServer = SteamAPI_SteamGameServer();
+ SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
+
+ SteamAPI_ManualDispatch_Init();
+ HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
+ hSteamNetworkingSockets =
+ SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
+
+ steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status );
+ steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
+ on_connect_status );
+
+ vg_success( "Steamworks API running\n" );
+ steamworks_event_loop( hsteampipe );
+
+ /*
+ * Create a listener
+ */
+ HSteamListenSocket listener;
+ SteamNetworkingIPAddr localAddr;
+ SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
+ localAddr.m_port = NETWORK_PORT;
+
+ listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
+ hSteamNetworkingSockets, &localAddr, 0, NULL );
+ gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
+ hSteamNetworkingSockets );
+
+ u64 server_ticks = 8000,
+ last_record_save = 8000,
+ last_scoreboard_gen = 0;
+
+ while( !sig_stop ){
+ steamworks_event_loop( hsteampipe );
+ poll_connections();
+
+ usleep(10000);
+ server_ticks ++;
+
+ if( db_killed() )
+ break;
+ }
+
+ SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
+ gameserver.client_group );
+ SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
+ hSteamNetworkingSockets, listener );
+
+ vg_info( "Shutting down\n..." );
+ SteamGameServer_Shutdown();
+ db_kill();
+ db_free();
+
+ return 0;
+}
--- /dev/null
+#pragma once
+#define VG_SERVER
+
+#include "vg/vg_platform.h"
+#include "vg/vg_steam.h"
+#include "vg/vg_steam_networking.h"
+#include "vg/vg_steam_http.h"
+#include "vg/vg_steam_auth.h"
+#include "network_msg.h"
+#include "network_common.h"
+#include <sys/socket.h>
+
+#define CLIENT_KNOWLEDGE_SAME_WORLD0 0x1
+#define CLIENT_KNOWLEDGE_SAME_WORLD1 0x2
+#define CLIENT_KNOWLEDGE_FRIENDS 0x4 /* unused */
+
+struct {
+ HSteamNetPollGroup client_group;
+ EServerMode auth_mode;
+
+ struct gameserver_client {
+ int active;
+ u32 version;
+ int authenticated;
+ HSteamNetConnection connection;
+ char username[ NETWORK_USERNAME_MAX ];
+
+ u8 instance;
+
+ struct gameserver_item {
+ char uid[ADDON_UID_MAX];
+ u32 hash;
+ }
+ items[k_netmsg_playeritem_max];
+
+ char region[ NETWORK_REGION_MAX ];
+ u32 region_flags;
+
+ u64 steamid;
+ }
+ clients[ NETWORK_MAX_PLAYERS ];
+
+ u8 client_knowledge_mask[ (NETWORK_MAX_PLAYERS*(NETWORK_MAX_PLAYERS-1))/2 ];
+ u8 app_symmetric_key[ k_nSteamEncryptedAppTicketSymmetricKeyLen ];
+
+ bool loopback_test;
+}
+static gameserver = {
+ .auth_mode = eServerModeAuthentication
+};
+
+static ISteamNetworkingSockets *hSteamNetworkingSockets = NULL;
--- /dev/null
+#ifndef GAMESERVER_DB_H
+#define GAMESERVER_DB_H
+
+#include "vg/vg_log.h"
+#include "vg/vg_mem_queue.h"
+#include "network_common.h"
+#include "dep/sqlite3/sqlite3.h"
+#include <pthread.h>
+#include <unistd.h>
+
+#define DB_COURSE_UID_MAX 32
+#define DB_TABLE_UID_MAX (ADDON_UID_MAX+DB_COURSE_UID_MAX+32)
+//#define DB_CRASH_ON_SQLITE_ERROR
+#define DB_LOG_SQL_STATEMENTS
+#define DB_REQUEST_BUFFER_SIZE (1024*2)
+
+typedef struct db_request db_request;
+struct db_request {
+ void (*handler)( db_request *req );
+ u32 size,_;
+ u8 data[];
+};
+
+struct {
+ sqlite3 *db;
+ pthread_t thread;
+ pthread_mutex_t mux;
+
+ vg_queue queue;
+ int kill;
+}
+static database;
+
+/*
+ * Log the error code (or carry on if its OK).
+ */
+static void log_sqlite3( int code ){
+ if( code == SQLITE_OK ) return;
+ vg_print_backtrace();
+ vg_error( "sqlite3(%d): %s\n", code, sqlite3_errstr(code) );
+
+#ifdef DB_CRASH_ON_SQLITE_ERROR
+ int crash = *((int*)2);
+#endif
+}
+
+/*
+ * Perpare statement and auto throw away if fails. Returns NULL on failure.
+ */
+static sqlite3_stmt *db_stmt( const char *code ){
+#ifdef DB_LOG_SQL_STATEMENTS
+ vg_low( code );
+#endif
+
+ sqlite3_stmt *stmt;
+ int fc = sqlite3_prepare_v2( database.db, code, -1, &stmt, NULL );
+
+ if( fc != SQLITE_OK ){
+ log_sqlite3( fc );
+ sqlite3_finalize( stmt );
+ return NULL;
+ }
+
+ return stmt;
+}
+
+/*
+ * bind zero terminated string
+ */
+static int db_sqlite3_bind_sz( sqlite3_stmt *stmt, int pos, const char *sz ){
+ return sqlite3_bind_text( stmt, pos, sz, -1, SQLITE_STATIC );
+}
+
+/*
+ * Allowed characters in sqlite table names. We use "" as delimiters.
+ */
+static int db_verify_charset( const char *str, int mincount ){
+ for( int i=0; ; i++ ){
+ char c = str[i];
+ if( c == '\0' ){
+ if( i < mincount ) return 0;
+ else return 1;
+ }
+
+ if( !((c==' ')||(c=='!')||(c>='#'&&c<='~')) ) return 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Find table name from mod UID and course UID, plus the week number
+ */
+static int db_get_highscore_table_name( const char *mod_uid,
+ const char *run_uid,
+ u32 week,
+ char table_name[DB_TABLE_UID_MAX] ){
+ if( !db_verify_charset( mod_uid, 13 ) ||
+ !db_verify_charset( run_uid, 1 ) ) return 0;
+
+ vg_str a;
+ vg_strnull( &a, table_name, DB_TABLE_UID_MAX );
+ vg_strcat( &a, mod_uid );
+ vg_strcat( &a, ":" );
+ vg_strcat( &a, run_uid );
+
+ if( week ){
+ vg_strcat( &a, "#" );
+ vg_strcati32( &a, week );
+ }
+
+ return vg_strgood( &a );
+}
+
+/*
+ * Read value from highscore table. If not found or error, returns 0
+ */
+static i32 db_readusertime( char table[DB_TABLE_UID_MAX], u64 steamid ){
+ char buf[ 512 ];
+ vg_str q;
+ vg_strnull( &q, buf, 512 );
+ vg_strcat( &q, "SELECT time FROM \"" );
+ vg_strcat( &q, table );
+ vg_strcat( &q, "\" WHERE steamid = ?;" );
+ if( !vg_strgood(&q) ) return 0;
+
+ sqlite3_stmt *stmt = db_stmt( q.buffer );
+ if( stmt ){
+ sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
+ int fc = sqlite3_step( stmt );
+
+ i32 result = 0;
+
+ if( fc == SQLITE_ROW )
+ result = sqlite3_column_int( stmt, 0 );
+ else if( fc != SQLITE_DONE )
+ log_sqlite3(fc);
+
+ sqlite3_finalize( stmt );
+ return result;
+ }
+ else return 0;
+}
+
+/*
+ * Write to highscore table
+ */
+static int db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid,
+ i32 score, int only_if_faster ){
+ /* auto create table
+ * ------------------------------------------*/
+ char buf[ 512 ];
+ vg_str q;
+ vg_strnull( &q, buf, 512 );
+ vg_strcat( &q, "CREATE TABLE IF NOT EXISTS \n \"" );
+ vg_strcat( &q, table );
+ vg_strcat( &q, "\"\n (steamid BIGINT UNIQUE, time INT);" );
+ if( !vg_strgood(&q) ) return 0;
+
+ vg_str str;
+ sqlite3_stmt *create_table = db_stmt( q.buffer );
+
+ if( create_table ){
+ db_sqlite3_bind_sz( create_table, 1, table );
+
+ int fc = sqlite3_step( create_table );
+ sqlite3_finalize( create_table );
+ if( fc != SQLITE_DONE )
+ return 0;
+ }
+ else return 0;
+
+ if( only_if_faster ){
+ i32 current = db_readusertime( table, steamid );
+ if( (current != 0) && (score > current) )
+ return 1;
+ }
+
+ /* insert score
+ * -------------------------------------------------*/
+ vg_strnull( &q, buf, 512 );
+ vg_strcat( &q, "REPLACE INTO \"" );
+ vg_strcat( &q, table );
+ vg_strcat( &q, "\"(steamid,time)\n VALUES (?,?);" );
+ if( !vg_strgood(&q) ) return 0;
+
+ sqlite3_stmt *stmt = db_stmt( q.buffer );
+
+ if( stmt ){
+ sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
+ sqlite3_bind_int( stmt, 2, score );
+
+ int fc = sqlite3_step( stmt );
+ sqlite3_finalize( stmt );
+ if( fc != SQLITE_DONE )
+ return 0;
+ else
+ return 1;
+ }
+ else return 0;
+}
+
+/*
+ * Set username and type
+ */
+static int db_updateuser( u64 steamid, const char *username, int admin ){
+ sqlite3_stmt *stmt = db_stmt(
+ "INSERT OR REPLACE INTO users (steamid, name, type) "
+ "VALUES (?,?,?);" );
+
+ if( stmt ){
+ sqlite3_bind_int64( stmt, 1, *((i64*)(&steamid)) );
+ db_sqlite3_bind_sz( stmt, 2, username );
+ sqlite3_bind_int( stmt, 3, admin );
+
+ int fc = sqlite3_step( stmt );
+ sqlite3_finalize(stmt);
+
+ if( fc == SQLITE_DONE ){
+ vg_success( "Inserted %lu (%s), type: %d\n",
+ steamid, username, admin );
+ return 1;
+ }
+ else{
+ log_sqlite3( fc );
+ return 0;
+ }
+ }
+ else return 0;
+}
+
+/*
+ * Get user info
+ */
+static int db_getuserinfo( u64 steamid, char *out_username, u32 username_max,
+ i32 *out_type ){
+ sqlite3_stmt *stmt = db_stmt( "SELECT * FROM users WHERE steamid = ?;" );
+ if( !stmt ) return 0;
+
+ sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
+ int fc = sqlite3_step( stmt );
+
+ if( fc != SQLITE_ROW ){
+ log_sqlite3( fc );
+ sqlite3_finalize( stmt );
+ return 0;
+ }
+
+ if( out_username ){
+ const char *name = (const char *)sqlite3_column_text( stmt, 1 );
+ vg_strncpy( name, out_username, username_max, k_strncpy_allow_cutoff );
+ }
+
+ if( out_type )
+ *out_type = sqlite3_column_int( stmt, 2 );
+
+ sqlite3_finalize( stmt );
+ return 1;
+}
+
+static void _db_thread_end(void){
+ pthread_mutex_lock( &database.mux );
+ database.kill = 1;
+ pthread_mutex_unlock( &database.mux );
+ sqlite3_close( database.db );
+}
+
+static void *db_loop(void *_){
+ int rc = sqlite3_open( "highscores.db", &database.db );
+
+ if( rc ){
+ vg_error( "database failure: %s\n", sqlite3_errmsg(database.db) );
+ _db_thread_end();
+ return NULL;
+ }
+
+ sqlite3_stmt *stmt = db_stmt(
+ "CREATE TABLE IF NOT EXISTS \n"
+ " users(steamid BIGINT UNIQUE, name VARCHAR(128), type INT);" );
+
+ if( stmt ){
+ int fc = sqlite3_step( stmt );
+ sqlite3_finalize(stmt);
+
+ if( fc == SQLITE_DONE ){
+ vg_success( "Created users table\n" );
+ db_updateuser( 76561198072130043, "harry", 2 );
+ }
+ else{
+ log_sqlite3( fc );
+ _db_thread_end();
+ return NULL;
+ }
+ }
+ else {
+ _db_thread_end();
+ return NULL;
+ }
+
+ /*
+ * Request processing loop
+ */
+ while(1){
+ pthread_mutex_lock( &database.mux );
+
+ if( database.kill ){
+ pthread_mutex_unlock( &database.mux );
+ _db_thread_end();
+ break;
+ }
+
+ u32 processed = 0;
+
+ for( u32 i=0; i<16; i ++ ){
+ db_request *req = NULL;
+ if( database.queue.tail ){
+ req = (db_request *)database.queue.tail->data;
+ pthread_mutex_unlock( &database.mux );
+ }
+ else{
+ pthread_mutex_unlock( &database.mux );
+ break;
+ }
+
+ req->handler( req );
+ processed ++;
+
+ pthread_mutex_lock( &database.mux );
+ vg_queue_pop( &database.queue );
+ }
+
+ if( processed )
+ vg_low( "Processed %u database requests.\n", processed );
+
+ usleep(50000);
+ }
+
+ vg_low( "Database thread terminates.\n" );
+ return NULL;
+}
+
+/*
+ * Create database connection and users table
+ */
+static int db_init(void){
+ database.queue.buffer =
+ (u8 *)vg_linear_alloc( vg_mem.rtmemory, DB_REQUEST_BUFFER_SIZE ),
+ database.queue.size = DB_REQUEST_BUFFER_SIZE;
+
+ if( pthread_mutex_init( &database.mux, NULL ) )
+ return 0;
+
+ if( pthread_create( &database.thread, NULL, db_loop, NULL ) )
+ return 0;
+
+ return 1;
+}
+
+static int db_killed(void){
+ pthread_mutex_lock( &database.mux );
+ int result = database.kill;
+ pthread_mutex_unlock( &database.mux );
+ return result;
+}
+
+static void db_kill(void){
+ pthread_mutex_lock( &database.mux );
+ database.kill = 1;
+ pthread_mutex_unlock( &database.mux );
+ pthread_join( database.thread, NULL );
+}
+
+static void db_free(void){
+ pthread_mutex_destroy( &database.mux );
+}
+
+static db_request *db_alloc_request( u32 size ){
+ u32 total = sizeof(db_request) + size;
+
+ pthread_mutex_lock( &database.mux );
+ vg_queue_frame *frame = vg_queue_alloc( &database.queue, total );
+
+ if( frame ){
+ db_request *req = (db_request *)frame->data;
+ req->size = size;
+ return req;
+ }
+ else {
+ pthread_mutex_unlock( &database.mux );
+ return NULL;
+ }
+}
+
+static void db_send_request( db_request *request ){
+ pthread_mutex_unlock( &database.mux );
+}
+
+#endif /* GAMESERVER_DB_H */
--- /dev/null
+#pragma once
+#include "font.h"
+#include "input.h"
+#include "player.h"
+#include "vg/vg_engine.h"
+#include "vg/vg_ui/imgui.h"
+
+#define GUI_COL_DARK ui_opacity( 0x00000000, 0.7f )
+#define GUI_COL_NORM ui_opacity( 0x00101010, 0.7f )
+#define GUI_COL_ACTIVE ui_opacity( 0x00444444, 0.7f )
+#define GUI_COL_CLICK ui_opacity( 0x00858585, 0.7f )
+#define GUI_COL_HI ui_opacity( 0x00ffffff, 0.8f )
+
+enum gui_icon {
+ k_gui_icon_tick = 0,
+ k_gui_icon_tick_2d,
+ k_gui_icon_exclaim,
+ k_gui_icon_exclaim_2d,
+ k_gui_icon_board,
+ k_gui_icon_world,
+ k_gui_icon_rift,
+ k_gui_icon_rift_run,
+ k_gui_icon_rift_run_2d,
+ k_gui_icon_friend,
+ k_gui_icon_player,
+ k_gui_icon_rift_run_gold,
+ k_gui_icon_rift_run_silver,
+ k_gui_icon_glider,
+ k_gui_icon_spawn,
+ k_gui_icon_spawn_select,
+
+ k_gui_icon_count,
+};
+
+#define GUI_HELPER_TEXT_LENGTH 32
+
+struct{
+ struct gui_helper{
+ vg_input_op *binding;
+ char text[GUI_HELPER_TEXT_LENGTH];
+ int greyed;
+ }
+ helpers[4];
+ u32 helper_count;
+
+ int active_positional_helper;
+
+ struct icon_call {
+ enum gui_icon icon;
+ v4f location;
+ v4f colour;
+ int colour_changed;
+ }
+ icon_draw_buffer[64];
+ u32 icon_draw_count;
+ v4f cur_icon_colour;
+ int colour_changed;
+
+ char location[64];
+ f64 location_time;
+
+ f32 factive;
+ font3d font;
+
+ v3f trick_co;
+
+ mdl_context model_icons;
+ GLuint icons_texture;
+ glmesh icons_mesh;
+
+ mdl_submesh *icons[ k_gui_icon_count ];
+}
+static gui = {.cur_icon_colour = {1.0f,1.0f,1.0f,1.0f},.colour_changed=1};
+
+static void gui_helper_clear(void){
+ gui.helper_count = 0;
+ gui.active_positional_helper = 0;
+}
+
+static struct gui_helper *gui_new_helper( vg_input_op *bind, vg_str *out_text ){
+ if( gui.helper_count >= VG_ARRAY_LEN(gui.helpers) ){
+ vg_error( "Too many helpers\n" );
+ return NULL;
+ }
+
+ struct gui_helper *helper = &gui.helpers[ gui.helper_count ++ ];
+ helper->greyed = 0;
+ helper->binding = bind;
+ vg_strnull( out_text, helper->text, sizeof(helper->text) );
+ return helper;
+}
+
+static void gui_render_icons(void)
+{
+ vg_camera ortho;
+
+ float fl = 0.0f,
+ fr = vg.window_x,
+ fb = 0.0f,
+ ft = vg.window_y,
+ rl = 1.0f / (fr-fl),
+ tb = 1.0f / (ft-fb);
+
+ m4x4_zero( ortho.mtx.p );
+ ortho.mtx.p[0][0] = 2.0f * rl;
+ ortho.mtx.p[1][1] = 2.0f * tb;
+ ortho.mtx.p[3][0] = (fr + fl) * -rl;
+ ortho.mtx.p[3][1] = (ft + fb) * -tb;
+ ortho.mtx.p[3][3] = 1.0f;
+ m4x3_identity( ortho.transform );
+ vg_camera_update_view( &ortho );
+ m4x4_mul( ortho.mtx.p, ortho.mtx.v, ortho.mtx.pv ); /* HACK */
+ vg_camera_finalize( &ortho );
+
+ /* icons */
+ font3d_bind( &gui.font, k_font_shader_default, 0, NULL, &ortho );
+ mesh_bind( &gui.icons_mesh );
+
+ m4x3f mmdl;
+ m4x3_identity( mmdl );
+ shader_model_font_uMdl( mmdl );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, gui.icons_texture );
+ shader_model_font_uTexMain( 0 );
+
+ for( u32 i=0; i<gui.icon_draw_count; i++ ){
+ struct icon_call *call = &gui.icon_draw_buffer[i];
+
+ if( call->colour_changed )
+ shader_model_font_uColour( call->colour );
+
+ shader_model_font_uOffset( call->location );
+
+ mdl_submesh *sm = gui.icons[ call->icon ];
+ if( sm )
+ mdl_draw_submesh( sm );
+ }
+
+ gui.icon_draw_count = 0;
+}
+
+static void gui_draw( ui_context *ctx )
+{
+ if( gui.active_positional_helper &&
+ (v3_dist2(localplayer.rb.co,gui.trick_co) > 2.0f) )
+ gui_helper_clear();
+
+ /* helpers
+ * ----------------------------------------------------------------- */
+
+ gui.factive = vg_lerpf( gui.factive, gui.helper_count?1.0f:0.0f,
+ vg.time_frame_delta*2.0f );
+
+ ctx->font = &vgf_default_title;
+ ui_px height = ctx->font->ch + 16;
+ ui_rect lwr = { 0, vg.window_y - height, vg.window_x, height };
+
+ ui_px x = 0;
+ for( u32 i=0; i<gui.helper_count; i++ )
+ {
+ struct gui_helper *helper = &gui.helpers[i];
+
+ char buf[128];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_input_string( &str, helper->binding, 1 );
+
+ ui_rect box = { x, lwr[1], 1000, lwr[3] };
+
+ u32 fg = 0;
+ f32 opacity = 0.4f;
+ if( helper->greyed )
+ {
+ fg = ui_colour(ctx, k_ui_fg+2);
+ opacity = 0.1f;
+ }
+
+ struct ui_vert *bg = ui_fill( ctx, box,
+ ui_opacity( GUI_COL_DARK, opacity ) );
+
+ u32 w;
+ box[0] += 16;
+ w = ui_text( ctx, box, buf, 1, k_ui_align_middle_left, fg );
+ w *= ctx->font->sx;
+ bg[1].co[0] = x + w + 32;
+ bg[2].co[0] = x + w + 32;
+ x += w + 32;
+
+ box[0] = x;
+ bg = ui_fill( ctx, box, ui_opacity( GUI_COL_NORM, opacity*0.7f ) );
+ box[0] += 8;
+ w = ui_text( ctx, box, helper->text, 1, k_ui_align_middle_left, fg );
+ w *= ctx->font->sx;
+ bg[1].co[0] = box[0] + w + 16;
+ bg[2].co[0] = box[0] + w + 16;
+ x += w + 32;
+ }
+
+ vg_ui.frosting = gui.factive*0.015f;
+ ui_flush( ctx, k_ui_shader_colour, NULL );
+ vg_ui.frosting = 0.0f;
+
+
+ f64 loc_t = (vg.time_real - gui.location_time) / 5.0;
+ if( (loc_t < 1.0) && (gui.location_time != 0.0) )
+ {
+ f32 t = 1.0f-vg_minf(1.0f,vg_minf(loc_t*20.0f,2.0f-loc_t*2.0f)),
+ o = 1.0f-t*t*(2.0f-t);
+
+ ui_rect box = { 0, (vg.window_y*2)/3 - height/2, vg.window_x, height };
+ ui_fill( ctx, box, ui_opacity( GUI_COL_NORM, 0.5f ) );
+ ui_text( ctx, box, gui.location, 1, k_ui_align_middle_center, 0 );
+
+ vg_ui.colour[3] = o;
+ ui_flush( ctx, k_ui_shader_colour, NULL );
+ }
+
+ vg_ui.colour[3] = 1.0f;
+ ctx->font = &vgf_default_small;
+}
+
+static int gui_location_print_ccmd( int argc, const char *argv[] )
+{
+ if( argc > 0 )
+ {
+ char new_loc[64];
+ vg_str str;
+ vg_strnull( &str, new_loc, 64 );
+ for( int i=0; i<argc; i++ )
+ {
+ vg_strcat( &str, argv[i] );
+ vg_strcat( &str, " " );
+ }
+ if( !strcmp(gui.location,new_loc) ) return 0;
+ vg_strncpy( new_loc, gui.location, 64, k_strncpy_always_add_null );
+ gui.location_time = vg.time_real;
+ }
+ return 0;
+}
+
+static int gui_showtrick_ccmd( int argc, const char *argv[] )
+{
+ if( argc == 1 )
+ {
+ gui_helper_clear();
+ vg_str text;
+
+ if( !strcmp( argv[0], "pump" ) ){
+ if( gui_new_helper( input_axis_list[k_sraxis_grab], &text ) )
+ vg_strcat( &text, "Pump" );
+ }
+ else if( !strcmp( argv[0], "flip" ) ){
+ if( gui_new_helper( input_joy_list[k_srjoystick_steer], &text ) )
+ vg_strcat( &text, "Flip" );
+ }
+ else if( !strcmp( argv[0], "ollie" ) ){
+ if( gui_new_helper( input_button_list[k_srbind_jump], &text ) )
+ vg_strcat( &text, "Ollie" );
+ }
+ else if( !strcmp( argv[0], "trick" ) ){
+ if( gui_new_helper( input_button_list[k_srbind_trick0], &text ) )
+ vg_strcat( &text, "Shuvit" );
+ if( gui_new_helper( input_button_list[k_srbind_trick1], &text ) )
+ vg_strcat( &text, "Kickflip" );
+ if( gui_new_helper( input_button_list[k_srbind_trick2], &text ) )
+ vg_strcat( &text, "Tre-Flip" );
+ }
+ else if( !strcmp( argv[0], "misc" ) ){
+ if( gui_new_helper( input_button_list[k_srbind_camera], &text ) )
+ vg_strcat( &text, "Camera" );
+ if( gui_new_helper( input_button_list[k_srbind_use], &text ) )
+ vg_strcat( &text, "Skate/Walk" );
+ }
+ else return 1;
+
+ v3_copy( localplayer.rb.co, gui.trick_co );
+ gui.active_positional_helper = 1;
+ return 0;
+ }
+ return 1;
+}
+
+static void gui_draw_icon( enum gui_icon icon, v2f co, f32 size )
+{
+ if( gui.icon_draw_count == VG_ARRAY_LEN(gui.icon_draw_buffer) )
+ return;
+
+ struct icon_call *call = &gui.icon_draw_buffer[ gui.icon_draw_count ++ ];
+
+ call->icon = icon;
+ call->location[0] = co[0] * (f32)vg.window_x;
+ call->location[1] = co[1] * (f32)vg.window_y;
+ call->location[2] = 0.0f;
+ call->location[3] = size * (f32)vg.window_x;
+
+ v4_copy( gui.cur_icon_colour, call->colour );
+ call->colour_changed = gui.colour_changed;
+ gui.colour_changed = 0;
+}
+
+static void gui_icon_setcolour( v4f colour ){
+ gui.colour_changed = 1;
+ v4_copy( colour, gui.cur_icon_colour );
+}
+
+static mdl_submesh *gui_find_icon( const char *name ){
+ mdl_mesh *mesh = mdl_find_mesh( &gui.model_icons, name );
+ if( mesh ){
+ if( mesh->submesh_count ){
+ return mdl_arritm( &gui.model_icons.submeshs, mesh->submesh_start );
+ }
+ }
+
+ return NULL;
+}
+
+static void gui_init(void)
+{
+ font3d_load( &gui.font, "models/rs_font.mdl", vg_mem.rtmemory );
+ vg_console_reg_cmd( "gui_location", gui_location_print_ccmd, NULL );
+ vg_console_reg_cmd( "showtrick", gui_showtrick_ccmd, NULL );
+
+ /* load icons */
+ void *alloc = vg_mem.rtmemory;
+ mdl_open( &gui.model_icons, "models/rs_icons.mdl", alloc );
+ mdl_load_metadata_block( &gui.model_icons, alloc );
+
+ gui.icons[ k_gui_icon_tick ] = gui_find_icon( "icon_tick" );
+ gui.icons[ k_gui_icon_tick_2d ] = gui_find_icon( "icon_tick2d" );
+ gui.icons[ k_gui_icon_exclaim ] = gui_find_icon( "icon_exclaim" );
+ gui.icons[ k_gui_icon_exclaim_2d ] = gui_find_icon( "icon_exclaim2d" );
+ gui.icons[ k_gui_icon_board ] = gui_find_icon( "icon_board" );
+ gui.icons[ k_gui_icon_world ] = gui_find_icon( "icon_world" );
+ gui.icons[ k_gui_icon_rift ] = gui_find_icon( "icon_rift" );
+ gui.icons[ k_gui_icon_rift_run ] = gui_find_icon( "icon_rift_run" );
+ gui.icons[ k_gui_icon_rift_run_2d ] = gui_find_icon( "icon_rift_run2d" );
+ gui.icons[ k_gui_icon_friend ] = gui_find_icon( "icon_friend" );
+ gui.icons[ k_gui_icon_player ] = gui_find_icon( "icon_player" );
+ gui.icons[ k_gui_icon_glider ] = gui_find_icon( "icon_glider" );
+ gui.icons[ k_gui_icon_spawn ] = gui_find_icon( "icon_spawn" );
+ gui.icons[ k_gui_icon_spawn_select ] = gui_find_icon( "icon_spawn_select" );
+ gui.icons[ k_gui_icon_rift_run_gold ] =
+ gui_find_icon("icon_rift_run_medal_gold");
+ gui.icons[ k_gui_icon_rift_run_silver]=
+ gui_find_icon("icon_rift_run_medal_silver");
+
+ vg_linear_clear( vg_mem.scratch );
+ if( !mdl_arrcount( &gui.model_icons.textures ) )
+ vg_fatal_error( "No texture in menu file" );
+ mdl_texture *tex0 = mdl_arritm( &gui.model_icons.textures, 0 );
+ void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
+ mdl_fread_pack_file( &gui.model_icons, &tex0->file, data );
+ vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
+ VG_TEX2D_LINEAR|VG_TEX2D_CLAMP,
+ &gui.icons_texture );
+
+ mdl_async_load_glmesh( &gui.model_icons, &gui.icons_mesh, NULL );
+ mdl_close( &gui.model_icons );
+}
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+#include "vg/vg_console.h"
+#include "vg/vg_input.h"
+#include "vg/vg_m.h"
+#include "font.h"
+
+enum sr_bind
+{
+ k_srbind_jump = 0,
+ k_srbind_push,
+ k_srbind_skid,
+ k_srbind_trick0,
+ k_srbind_trick1,
+ k_srbind_trick2,
+ k_srbind_sit,
+ k_srbind_use,
+ k_srbind_reset,
+ k_srbind_dead_respawn,
+ k_srbind_camera,
+ k_srbind_mleft,
+ k_srbind_mright,
+ k_srbind_mup,
+ k_srbind_mdown,
+ k_srbind_mback,
+ k_srbind_maccept,
+ k_srbind_mopen,
+ k_srbind_mhub,
+ k_srbind_replay_play,
+ k_srbind_replay_freecam,
+ k_srbind_replay_resume,
+ k_srbind_world_left,
+ k_srbind_world_right,
+ k_srbind_home,
+ k_srbind_lobby,
+ k_srbind_chat,
+ k_srbind_run,
+
+ k_srbind_miniworld_teleport,
+ k_srbind_miniworld_resume,
+ k_srbind_devbutton,
+ k_srbind_max,
+};
+
+enum sr_joystick{
+ k_srjoystick_steer = 0,
+ k_srjoystick_grab,
+ k_srjoystick_look,
+ k_srjoystick_max
+};
+
+enum sr_axis{
+ k_sraxis_grab = 0,
+ k_sraxis_mbrowse_h,
+ k_sraxis_mbrowse_v,
+ k_sraxis_replay_h,
+ k_sraxis_skid,
+ k_sraxis_max
+};
+
+
+#define INPUT_BASIC( KB, JS ) \
+ (vg_input_op[]){vg_keyboard, KB, vg_joy_button, JS, vg_end}
+
+static vg_input_op *input_button_list[] = {
+[k_srbind_jump] = INPUT_BASIC( SDLK_SPACE, SDL_CONTROLLER_BUTTON_A ),
+[k_srbind_push] = INPUT_BASIC( SDLK_w, SDL_CONTROLLER_BUTTON_B ),
+[k_srbind_trick0] = (vg_input_op[]){
+ vg_mouse, SDL_BUTTON_LEFT,
+ vg_joy_button, SDL_CONTROLLER_BUTTON_A, vg_end
+},
+[k_srbind_trick1] = (vg_input_op[]){
+ vg_mouse, SDL_BUTTON_RIGHT,
+ vg_joy_button, SDL_CONTROLLER_BUTTON_B, vg_end
+},
+[k_srbind_trick2] = (vg_input_op[]){
+ vg_mouse, SDL_BUTTON_LEFT, vg_mode_mul, vg_mouse, SDL_BUTTON_RIGHT,
+ vg_mode_absmax, vg_joy_button, SDL_CONTROLLER_BUTTON_X, vg_end
+},
+[k_srbind_use] = INPUT_BASIC( SDLK_e, SDL_CONTROLLER_BUTTON_Y ),
+[k_srbind_reset] = INPUT_BASIC( SDLK_r, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
+[k_srbind_dead_respawn] =
+ INPUT_BASIC( SDLK_q, SDL_CONTROLLER_BUTTON_DPAD_UP ),
+[k_srbind_camera]= INPUT_BASIC( SDLK_c, SDL_CONTROLLER_BUTTON_DPAD_RIGHT ),
+[k_srbind_mleft] = INPUT_BASIC( SDLK_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
+[k_srbind_mright]= INPUT_BASIC( SDLK_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT ),
+[k_srbind_world_left] =
+ INPUT_BASIC( SDLK_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
+[k_srbind_world_right] =
+ INPUT_BASIC( SDLK_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT ),
+[k_srbind_mup] = INPUT_BASIC( SDLK_UP, SDL_CONTROLLER_BUTTON_DPAD_UP ),
+[k_srbind_mdown] = INPUT_BASIC( SDLK_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN ),
+[k_srbind_mback] = INPUT_BASIC( SDLK_ESCAPE, SDL_CONTROLLER_BUTTON_B ),
+[k_srbind_mopen] = INPUT_BASIC( SDLK_ESCAPE, SDL_CONTROLLER_BUTTON_START ),
+[k_srbind_mhub] = INPUT_BASIC( SDLK_h, SDL_CONTROLLER_BUTTON_Y ),
+[k_srbind_maccept] = (vg_input_op[]){
+ vg_keyboard, SDLK_e, vg_gui_visible, 0,
+ vg_keyboard, SDLK_RETURN, vg_keyboard, SDLK_RETURN2,
+ vg_gui_visible, 1,
+ vg_joy_button, SDL_CONTROLLER_BUTTON_A, vg_end
+},
+[k_srbind_replay_play] = INPUT_BASIC( SDLK_g, SDL_CONTROLLER_BUTTON_X ),
+[k_srbind_replay_resume] = INPUT_BASIC( SDLK_SPACE, SDL_CONTROLLER_BUTTON_A ),
+[k_srbind_replay_freecam] = INPUT_BASIC( SDLK_f, SDL_CONTROLLER_BUTTON_Y ),
+[k_srbind_sit] = INPUT_BASIC( SDLK_z, SDL_CONTROLLER_BUTTON_B ),
+[k_srbind_lobby] = INPUT_BASIC( SDLK_TAB, SDL_CONTROLLER_BUTTON_DPAD_LEFT ),
+[k_srbind_chat ] = (vg_input_op[]){ vg_keyboard, SDLK_y, vg_end },
+[k_srbind_run ] = (vg_input_op[]){ vg_keyboard, SDLK_LSHIFT,
+ vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERLEFT, vg_end },
+
+[k_srbind_miniworld_resume] = (vg_input_op[]){
+ vg_keyboard, SDLK_RETURN, vg_gui_visible, 0,
+ vg_keyboard, SDLK_RETURN2,
+ vg_gui_visible, 1,
+ vg_joy_button, SDL_CONTROLLER_BUTTON_X, vg_end
+},
+[k_srbind_miniworld_teleport]= INPUT_BASIC( SDLK_q,
+ SDL_CONTROLLER_BUTTON_LEFTSHOULDER ),
+[k_srbind_skid] = (vg_input_op[]){ vg_keyboard, SDLK_LCTRL, vg_end },
+[k_srbind_devbutton] = (vg_input_op[]){ vg_keyboard, SDLK_3, vg_end },
+[k_srbind_max]=NULL
+};
+
+static vg_input_op *input_axis_list[] = {
+[k_sraxis_grab] = (vg_input_op[]){
+ vg_keyboard, SDLK_LSHIFT,
+ vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, vg_end
+},
+[k_sraxis_mbrowse_h] = (vg_input_op[]){
+ vg_mode_sub, vg_keyboard, SDLK_LEFT,
+ vg_mode_add, vg_keyboard, SDLK_RIGHT,
+ vg_mode_add, vg_joy_axis, SDL_CONTROLLER_AXIS_LEFTX,
+ vg_end
+},
+[k_sraxis_mbrowse_v] = (vg_input_op[]){
+ vg_mode_sub, vg_keyboard, SDLK_DOWN,
+ vg_mode_add, vg_keyboard, SDLK_UP,
+ vg_mode_sub, vg_joy_axis, SDL_CONTROLLER_AXIS_LEFTY,
+ vg_end
+},
+[k_sraxis_replay_h] = (vg_input_op[]){
+ vg_mode_sub, vg_keyboard, SDLK_q,
+ vg_mode_add, vg_keyboard, SDLK_e,
+ vg_mode_sub, vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERLEFT,
+ vg_mode_add, vg_joy_axis, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
+ vg_end
+},
+[k_sraxis_skid] = (vg_input_op[]){
+ vg_mode_sub, vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
+ vg_mode_add, vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
+ vg_end
+},
+[k_sraxis_max]=NULL
+};
+
+static vg_input_op *input_joy_list[] = {
+[k_srjoystick_steer] = (vg_input_op[]){
+ vg_index, 0, vg_mode_sub, vg_keyboard, SDLK_a,
+ vg_mode_add, vg_keyboard, SDLK_d,
+ vg_index, 1, vg_mode_sub, vg_keyboard, SDLK_w,
+ vg_mode_add, vg_keyboard, SDLK_s,
+ vg_mode_absmax, vg_joy_ls,
+ vg_end
+},
+[k_srjoystick_grab] = (vg_input_op[]){
+ vg_joy_rs, vg_end
+},
+[k_srjoystick_look] = (vg_input_op[]){
+ vg_joy_rs, vg_end
+},
+[k_srjoystick_max]=NULL
+};
+
+struct {
+ float axis_states[ k_sraxis_max ][2];
+ v2f joystick_states[ k_srjoystick_max ][2];
+ u8 button_states[ k_srbind_max ][2];
+
+ enum input_state {
+ k_input_state_enabled,
+ k_input_state_resume,
+ k_input_state_resuming,
+ k_input_state_pause
+ }
+ state;
+}
+static srinput;
+
+static int input_filter_generic(void){
+ if( (srinput.state != k_input_state_enabled) || vg_console.enabled ||
+ (workshop_form.page != k_workshop_form_hidden) )
+ return 1;
+ else
+ return 0;
+}
+
+static int buttons_filter_fixed(void){
+ if( input_filter_generic() )
+ return 1;
+
+ if( vg.engine_stage == k_engine_stage_update_fixed )
+ if( vg.fixed_iterations > 0 )
+ return 1;
+
+ return 0;
+}
+
+/* Rising edge of button */
+static int button_down( enum sr_bind button ){
+ if( buttons_filter_fixed() ) return 0;
+
+ if( srinput.button_states[ button ][0] &&
+ !srinput.button_states[ button ][1] )
+ return 1;
+ else
+ return 0;
+}
+
+/* Falling edge of button */
+static int button_up( enum sr_bind button ){
+ if( buttons_filter_fixed() ) return 0;
+
+ if( !srinput.button_states[ button ][0] &&
+ srinput.button_states[ button ][1] )
+ return 1;
+ else
+ return 0;
+}
+
+/* State of button */
+static int button_press( enum sr_bind button ){
+ if( input_filter_generic() )
+ return 0;
+ return
+ srinput.button_states[ button ][0];
+}
+
+static void joystick_state( enum sr_joystick joystick, v2f state ){
+ if( input_filter_generic() )
+ v2_zero( state );
+ else
+ v2_copy( srinput.joystick_states[ joystick ][0], state );
+}
+
+static float axis_state( enum sr_axis axis ){
+ if( input_filter_generic() )
+ return 0.0f;
+ else
+ return srinput.axis_states[axis][0];
+}
+
+static void skaterift_preupdate_inputs(void){
+ if( srinput.state == k_input_state_resuming )
+ srinput.state = k_input_state_enabled;
+
+ if( srinput.state == k_input_state_resume )
+ srinput.state = k_input_state_resuming;
+
+ for( u32 i=0; i<k_srbind_max; i++ ){
+ srinput.button_states[i][1] = srinput.button_states[i][0];
+ srinput.button_states[i][0] = 0;
+ }
+
+ for( u32 i=0; i<k_srjoystick_max; i++ ){
+ v2_copy( srinput.joystick_states[i][0], srinput.joystick_states[i][1] );
+ v2_zero( srinput.joystick_states[i][0] );
+ }
+
+ for( u32 i=0; i<k_sraxis_max; i++ ){
+ srinput.axis_states[i][1] = srinput.axis_states[i][0];
+ srinput.axis_states[i][0] = 0.0f;
+ }
+
+ for( int i=0; i<k_srbind_max; i++ ){
+ vg_input_op *prog = input_button_list[i];
+ if( prog ){
+ vg_exec_input_program( k_vg_input_type_button_u8, prog,
+ &srinput.button_states[i][0] );
+ }
+ }
+
+ for( int i=0; i<k_sraxis_max; i++ ){
+ vg_input_op *prog = input_axis_list[i];
+ if( prog ){
+ vg_exec_input_program( k_vg_input_type_axis_f32, prog,
+ &srinput.axis_states[i][0] );
+ }
+ }
+
+ for( int i=0; i<k_srjoystick_max; i++ ){
+ vg_input_op *prog = input_joy_list[i];
+ if( prog ){
+ vg_exec_input_program( k_vg_input_type_joy_v2f, prog,
+ srinput.joystick_states[i][0] );
+ }
+ }
+
+ f32 x = srinput.axis_states[k_sraxis_mbrowse_h][0],
+ y = srinput.axis_states[k_sraxis_mbrowse_v][0],
+ sensitivity = 0.35f;
+
+ if( fabsf(x) > sensitivity ){
+ if( x > 0.0f ) srinput.button_states[k_srbind_mright][0] = 1;
+ else srinput.button_states[k_srbind_mleft][0] = 1;
+ }
+
+ if( fabsf(y) > sensitivity ){
+ if( y > 0.0f ) srinput.button_states[k_srbind_mup][0] = 1;
+ else srinput.button_states[k_srbind_mdown][0] = 1;
+ }
+}
--- /dev/null
+#pragma once
+#include "skaterift.h"
+#include "menu.h"
+#include "model.h"
+#include "entity.h"
+#include "input.h"
+#include "world_map.h"
+#include "ent_miniworld.h"
+#include "audio.h"
+#include "workshop.h"
+#include "gui.h"
+#include "control_overlay.h"
+#include "network.h"
+#include "shaders/model_menu.h"
+
+struct global_menu menu = { .skip_starter = 0 };
+
+void menu_at_begin(void)
+{
+ if( menu.skip_starter ) return;
+
+ skaterift.activity = k_skaterift_menu;
+ menu.page = k_menu_page_starter;
+}
+
+void menu_init(void)
+{
+ vg_console_reg_var( "skip_starter_menu", &menu.skip_starter,
+ k_var_dtype_i32, VG_VAR_PERSISTENT );
+ vg_tex2d_load_qoi_async_file( "textures/prem.qoi",
+ VG_TEX2D_CLAMP|VG_TEX2D_NOMIP|VG_TEX2D_NEAREST,
+ &menu.prem_tex );
+}
+
+void menu_open( enum menu_page page )
+{
+ skaterift.activity = k_skaterift_menu;
+
+ if( page != k_menu_page_any )
+ {
+ menu.page = page;
+ }
+}
+
+bool menu_viewing_map(void)
+{
+ return (skaterift.activity == k_skaterift_menu) &&
+ (menu.page == k_menu_page_main) &&
+ (menu.main_index == k_menu_main_map);
+}
+
+static void menu_decor_select( ui_context *ctx, ui_rect rect )
+{
+ ui_px b = ctx->font->sx, hb = b/2;
+ ui_rect a0 = { rect[0] - 20 - hb, rect[1] + rect[3]/2 - hb, b,b },
+ a1 = { rect[0] + rect[2] + 20 + hb, rect[1] + rect[3]/2 - hb, b,b };
+
+ ui_text( ctx, a0, "\x95", 1, k_ui_align_middle_center, 0 );
+ ui_text( ctx, a1, "\x93", 1, k_ui_align_middle_center, 0 );
+}
+
+static void menu_standard_widget( ui_context *ctx,
+ ui_rect inout_panel, ui_rect rect, ui_px s )
+{
+ ui_split( inout_panel, k_ui_axis_h, ctx->font->sy*s*2,
+ 8, rect, inout_panel );
+}
+
+static bool menu_slider( ui_context *ctx,
+ ui_rect inout_panel, bool select, const char *label,
+ const f32 disp_min, const f32 disp_max, f32 *value,
+ const char *format )
+{
+ ui_rect rect, box;
+ menu_standard_widget( ctx, inout_panel, rect, 1 );
+ ui_label( ctx, rect, label, 1, 8, box );
+
+ f32 t;
+ enum ui_button_state state = ui_slider_base( ctx, box, 0, 1, value, &t ),
+ mask_using =
+ k_ui_button_holding_inside |
+ k_ui_button_holding_outside |
+ k_ui_button_click,
+ mask_brighter = mask_using | k_ui_button_hover;
+
+ if( vg_input.display_input_method == k_input_method_controller )
+ {
+ if( select )
+ {
+ f32 m = axis_state( k_sraxis_mbrowse_h );
+ if( fabsf(m) > 0.5f )
+ {
+ *value += m * vg.time_frame_delta * (1.0f/2.0f);
+ *value = vg_clampf( *value, 0, 1 );
+ }
+
+ menu_decor_select( ctx, rect );
+ state |= k_ui_button_hover;
+ }
+ else
+ state = k_ui_button_none;
+ }
+
+ ui_rect line = { box[0], box[1], t * (f32)box[2], box[3] };
+ ui_fill( ctx, line, state&mask_brighter? GUI_COL_ACTIVE: GUI_COL_NORM );
+ ui_fill( ctx, (ui_rect){ box[0]+line[2], box[1], box[2]-line[2], box[3] },
+ GUI_COL_DARK );
+
+ ui_outline( ctx, box, 1, state? GUI_COL_HI: GUI_COL_ACTIVE, 0 );
+ ui_slider_text( ctx, box,
+ format, disp_min + (*value)*( disp_max-disp_min ) );
+
+ return (state & mask_using) && 1;
+}
+
+static bool menu_button( ui_context *ctx,
+ ui_rect inout_panel, bool select, const char *text )
+{
+ ui_rect rect;
+ menu_standard_widget( ctx, inout_panel, rect, 1 );
+
+ enum ui_button_state state = k_ui_button_none;
+
+ if( vg_input.display_input_method == k_input_method_controller )
+ {
+ if( select )
+ {
+ menu_decor_select( ctx, rect );
+
+ if( button_down( k_srbind_maccept ) )
+ state = k_ui_button_click;
+ }
+ }
+ else
+ {
+ state = ui_button_base( ctx, rect );
+ select = 0;
+ }
+
+ if( state == k_ui_button_click )
+ {
+ ui_fill( ctx, rect, GUI_COL_DARK );
+ }
+ else if( state == k_ui_button_holding_inside )
+ {
+ ui_fill( ctx, rect, GUI_COL_DARK );
+ }
+ else if( state == k_ui_button_holding_outside )
+ {
+ ui_fill( ctx, rect, GUI_COL_DARK );
+ ui_outline( ctx, rect, 1, GUI_COL_CLICK, 0 );
+ }
+ else if( state == k_ui_button_hover )
+ {
+ ui_fill( ctx, rect, GUI_COL_ACTIVE );
+ ui_outline( ctx, rect, 1, GUI_COL_CLICK, 0 );
+ }
+ else
+ {
+ ui_fill( ctx, rect, select? GUI_COL_ACTIVE: GUI_COL_NORM );
+ if( select )
+ ui_outline( ctx, rect, 1, GUI_COL_HI, 0 );
+ }
+
+ ui_text( ctx, rect, text, 1, k_ui_align_middle_center, 0 );
+
+ if( state == k_ui_button_click )
+ {
+ audio_lock();
+ audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
+ audio_unlock();
+ return 1;
+ }
+ else return 0;
+}
+
+static bool menu_checkbox( ui_context *ctx, ui_rect inout_panel, bool select,
+ const char *str_label, i32 *data )
+{
+ ui_rect rect, label, box;
+ menu_standard_widget( ctx, inout_panel, rect, 1 );
+
+ ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box );
+ ui_text( ctx, label, str_label, ctx->scale, k_ui_align_middle_left, 0 );
+
+ enum ui_button_state state = k_ui_button_none;
+
+ if( vg_input.display_input_method == k_input_method_controller )
+ {
+ if( select )
+ {
+ menu_decor_select( ctx, rect );
+
+ if( button_down( k_srbind_maccept ) )
+ {
+ *data = (*data) ^ 0x1;
+ state = k_ui_button_click;
+ }
+ }
+ }
+ else
+ {
+ state = ui_checkbox_base( ctx, box, data );
+ select = 0;
+ }
+
+ if( state == k_ui_button_holding_inside )
+ {
+ ui_fill( ctx, box, GUI_COL_ACTIVE );
+ ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
+ }
+ else if( state == k_ui_button_holding_outside )
+ {
+ ui_fill( ctx, box, GUI_COL_DARK );
+ ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
+ }
+ else if( state == k_ui_button_hover )
+ {
+ ui_fill( ctx, box, GUI_COL_ACTIVE );
+ ui_outline( ctx, box, 1, GUI_COL_HI, 0 );
+ }
+ else
+ {
+ ui_fill( ctx, box, select? GUI_COL_ACTIVE: GUI_COL_DARK );
+ ui_outline( ctx, box, 1, select? GUI_COL_HI: GUI_COL_NORM, 0 );
+ }
+
+ if( *data )
+ {
+ ui_rect_pad( box, (ui_px[2]){8,8} );
+ ui_fill( ctx, box, GUI_COL_HI );
+ }
+
+ if( state == k_ui_button_click )
+ {
+ audio_lock();
+ audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
+ audio_unlock();
+ return 1;
+ }
+ else return 0;
+}
+
+static void menu_heading( ui_context *ctx,
+ ui_rect inout_panel, const char *label, u32 colour )
+{
+ ui_rect rect;
+ menu_standard_widget( ctx, inout_panel, rect, 1 );
+
+ rect[0] -= 8;
+ rect[2] += 16;
+
+ u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f ),
+ c1 = ui_opacity( GUI_COL_DARK, 0.5f );
+
+ struct ui_vert *vs = ui_fill( ctx, rect, c0 );
+
+ vs[0].colour = c1;
+ vs[1].colour = c1;
+
+ rect[1] += 4;
+ ui_text( ctx, rect, label, 1, k_ui_align_middle_center, 1 );
+ rect[0] += 1;
+ rect[1] -= 1;
+ ui_text( ctx, rect, label, 1, k_ui_align_middle_center, colour? colour:
+ ui_colour(ctx, k_ui_blue+k_ui_brighter) );
+}
+
+static u32 medal_colour( ui_context *ctx, u32 flags )
+{
+ if( flags & k_ent_route_flag_achieve_gold )
+ return ui_colour( ctx, k_ui_yellow );
+ else if( flags & k_ent_route_flag_achieve_silver )
+ return ui_colour( ctx, k_ui_fg );
+ else return 0;
+}
+
+static i32 menu_nav( i32 *p_row, int mv, i32 max )
+{
+ i32 row_prev = *p_row;
+
+ if( mv < 0 ) *p_row = vg_min( max, (*p_row) +1 );
+ if( mv > 0 ) *p_row = vg_max( 0, (*p_row) -1 );
+
+ if( *p_row != row_prev )
+ {
+ audio_lock();
+ audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
+ audio_unlock();
+ }
+
+ return *p_row;
+}
+
+static void menu_try_find_cam( i32 id )
+{
+ world_instance *world = &world_static.instances[0];
+ for( u32 i=0; i<mdl_arrcount(&world->ent_npc); i ++ )
+ {
+ ent_npc *fnpc = mdl_arritm( &world->ent_npc, i );
+ if( (fnpc->id == 50) && (fnpc->context == id) )
+ {
+ if( mdl_entity_id_type(fnpc->camera) == k_ent_camera )
+ {
+ u32 index = mdl_entity_id_id( fnpc->camera );
+ menu.bg_cam = mdl_arritm( &world->ent_camera, index );
+ menu.bg_blur = 0;
+ }
+ }
+ }
+}
+
+static void menu_link_modal( const char *url )
+{
+ menu.web_link = url;
+
+ /* Only reset the choice of browser if 'No' was selected. */
+ if( menu.web_choice == 2 )
+ menu.web_choice = 0;
+}
+
+void menu_gui( ui_context *ctx )
+{
+ if( button_down( k_srbind_mopen ) )
+ {
+ if( skaterift.activity == k_skaterift_default )
+ {
+ menu_open( k_menu_page_main );
+ return;
+ }
+ }
+
+ if( skaterift.activity != k_skaterift_menu )
+ return;
+
+ menu.bg_blur = 1;
+ menu.bg_cam = NULL;
+
+ /* get buttons inputs
+ * -------------------------------------------------------------------*/
+ int ml = button_press( k_srbind_mleft ),
+ mr = button_press( k_srbind_mright ),
+ mu = button_press( k_srbind_mup ),
+ md = button_press( k_srbind_mdown ),
+ mh = ml-mr,
+ mv = mu-md,
+ enter = button_down( k_srbind_maccept );
+
+ /* TAB CONTROL */
+ u8 lb_down = 0, rb_down = 0;
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, vg_end
+ }, &rb_down );
+ vg_exec_input_program( k_vg_input_type_button_u8,
+ (vg_input_op[]){
+ vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, vg_end
+ }, &lb_down );
+
+ int mtab = (i32)rb_down - (i32)lb_down;
+
+ if( menu.repeater > 0.0f )
+ {
+ menu.repeater -= vg_minf( vg.time_frame_delta, 0.5f );
+ mv = 0;
+ mh = 0;
+ mtab = 0;
+ }
+ else
+ {
+ if( mv || mh || mtab )
+ menu.repeater += 0.2f;
+ }
+
+ if( vg_input.display_input_method == k_input_method_kbm )
+ {
+ ui_capture_mouse(ctx, 1);
+ }
+
+ if( skaterift.activity != k_skaterift_menu ) return;
+
+
+ if( menu.web_link )
+ {
+ menu_try_find_cam( 3 );
+
+ ui_rect panel = { 0,0, 800, 200 },
+ screen = { 0,0, vg.window_x,vg.window_y };
+ ui_rect_center( screen, panel );
+ ui_fill( ctx, panel, GUI_COL_DARK );
+ ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
+ ui_rect_pad( panel, (ui_px[]){8,8} );
+
+ ui_rect title;
+ ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
+ ctx->font = &vgf_default_title;
+ ui_text( ctx, title, "Open Link?", 1, k_ui_align_middle_center, 0 );
+
+ ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
+ ctx->font = &vgf_default_large;
+ ui_text( ctx, title, menu.web_link, 1, k_ui_align_middle_center, 0 );
+
+ ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 };
+
+ ui_rect a,b,c;
+ ui_split_ratio( end, k_ui_axis_v, 2.0/3.0, 2, a, c );
+ ui_split_ratio( a, k_ui_axis_v, 1.0/2.0, 2, a, b );
+
+ i32 R = menu_nav( &menu.web_choice, mh, 2 );
+
+ if( menu_button( ctx, a, R==0, "Steam Overlay" ) )
+ {
+ if( steam_ready )
+ {
+ ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
+ SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends,
+ menu.web_link,
+ k_EActivateGameOverlayToWebPageMode_Default );
+ menu.web_link = NULL;
+ }
+ }
+
+ if( menu_button( ctx, b, R==1, "Web Browser" ) )
+ {
+ char buf[512];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+#ifdef _WIN32
+ vg_strcat( &str, "start " );
+#else
+ vg_strcat( &str, "xdg-open " );
+#endif
+ vg_strcat( &str, menu.web_link );
+
+ if( vg_strgood(&str) )
+ system( buf );
+
+ menu.web_link = NULL;
+ }
+
+ if( menu_button( ctx, c, R==2, "No" ) || button_down( k_srbind_mback ) )
+ {
+ audio_lock();
+ audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
+ audio_unlock();
+ menu.web_link = NULL;
+ }
+
+ goto menu_draw;
+ }
+
+
+ if( vg.settings_open )
+ {
+ if( button_down( k_srbind_mback ) )
+ {
+ audio_lock();
+ audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
+ audio_unlock();
+ vg_settings_close();
+ srinput.state = k_input_state_resume;
+ }
+
+ return;
+ }
+
+ if( menu.page == k_menu_page_credits )
+ {
+ ui_rect panel = { 0,0, 600, 400 },
+ screen = { 0,0, vg.window_x,vg.window_y };
+ ui_rect_center( screen, panel );
+ ui_fill( ctx, panel, GUI_COL_DARK );
+ ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
+ ui_rect_pad( panel, (ui_px[]){8,8} );
+
+ ui_rect title;
+ ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
+ ctx->font = &vgf_default_title;
+ ui_text( ctx, title, "Skate Rift - Credits",
+ 1, k_ui_align_middle_center, 0 );
+
+ ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
+ ctx->font = &vgf_default_large;
+ ui_text( ctx, title,
+ "Mt.Zero Software", 1, k_ui_align_middle_center, 0 );
+
+ ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
+ ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
+ ctx->font = &vgf_default_title;
+ ui_text( ctx, title, "Free Software", 1, k_ui_align_middle_center, 0 );
+
+ ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
+ ctx->font = &vgf_default_large;
+ ui_text( ctx, panel,
+ "Sam Lantinga - SDL2 - libsdl.org\n"
+ "Hunter WB - Anyascii\n"
+ "David Herberth - GLAD\n"
+ "Dominic Szablewski - QOI - qoiformat.org\n"
+ "Sean Barrett - stb_image, stb_vorbis,\n"
+ " stb_include\n"
+ "Khronos Group - OpenGL\n"
+ , 1, k_ui_align_left, 0 );
+
+ ui_rect end = { panel[0], panel[1] + panel[3] - 64, panel[2], 64 };
+
+ if( menu_button( ctx, end, 1, "Back" ) || button_down( k_srbind_mback ) )
+ {
+ menu.page = k_menu_page_main;
+ }
+
+ goto menu_draw;
+ }
+ else if( menu.page == k_menu_page_starter )
+ {
+ i32 R = menu_nav( &menu.intro_row, mv, 3 );
+ ui_rect panel = { 0,0, 600, 400 },
+ screen = { 0,0, vg.window_x,vg.window_y };
+ ui_rect_center( screen, panel );
+ ui_fill( ctx, panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
+ ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
+ ui_rect_pad( panel, (ui_px[]){8,8} );
+
+ ui_rect title;
+ ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
+ ctx->font = &vgf_default_title;
+ ui_text( ctx, title,
+ "Welcome to Skate Rift", 1, k_ui_align_middle_center, 0 );
+
+ ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
+ ctx->font = &vgf_default_large;
+
+ menu_checkbox( ctx, panel, R == 0,
+ "Show controls overlay (good for new players)",
+ &control_overlay.enabled );
+ menu_checkbox( ctx, panel, R == 1, "Auto connect to global server",
+ &network_client.auto_connect );
+
+ ui_rect end = { panel[0], panel[1] + panel[3] - 100, panel[2], 100 };
+ menu_checkbox( ctx, end, R == 2,
+ "Don't show this again", &menu.skip_starter );
+ if( menu_button( ctx, end, R == 3, "OK" ) )
+ {
+ menu.page = k_menu_page_main;
+ skaterift.activity = k_skaterift_default;
+ }
+
+ menu_try_find_cam( 3 );
+ goto menu_draw;
+ }
+ else if( menu.page == k_menu_page_premium )
+ {
+ i32 R = menu_nav( &menu.prem_row, mh, 1 );
+ ui_rect panel = { 0,0, 600, 400+240 },
+ screen = { 0,0, vg.window_x,vg.window_y };
+ ui_rect_center( screen, panel );
+ ui_fill( ctx, panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
+ ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
+ ui_rect_pad( panel, (ui_px[]){8,8} );
+
+ ui_rect title;
+ ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
+ ctx->font = &vgf_default_title;
+ ui_text( ctx, title, "Content is in the full game.",
+ 1, k_ui_align_middle_center, 0 );
+
+ ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
+ ctx->font = &vgf_default_large;
+
+ ui_rect img;
+ ui_split( panel, k_ui_axis_h, 456, 0, img, panel );
+ ui_image( ctx, img, &menu.prem_tex );
+
+ ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 }, a,b;
+ ui_split_ratio( end, k_ui_axis_v, 0.5f, 2, a, b );
+
+ if( menu_button( ctx, a, R == 0, "Store Page" ) )
+ {
+ if( steam_ready )
+ SteamAPI_ISteamFriends_ActivateGameOverlayToStore(
+ SteamAPI_SteamFriends(), 2103940, k_EOverlayToStoreFlag_None);
+ }
+
+ if( menu_button( ctx, b, R == 1, "Nah" ) || button_down( k_srbind_mback ) )
+ {
+ audio_lock();
+ audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
+ audio_unlock();
+ skaterift.activity = k_skaterift_default;
+ return;
+ }
+
+ goto menu_draw;
+ }
+
+ /* TOP BAR
+ * -------------------------------------------------------------------*/
+
+ ctx->font = &vgf_default_title;
+ ui_px height = ctx->font->ch + 16;
+ ui_rect topbar = { 0, 0, vg.window_x, height };
+
+ const char *opts[] = {
+ [k_menu_main_main] = "Menu",
+ [k_menu_main_map] = "Map",
+ [k_menu_main_settings ] = "Settings",
+ [k_menu_main_guide ] = "Guides"
+ };
+
+ if( mtab )
+ {
+ menu.main_index += mtab;
+
+ if( menu.main_index == -1 )
+ menu.main_index ++;
+
+ if( menu.main_index == VG_ARRAY_LEN(opts) )
+ menu.main_index --;
+
+ audio_lock();
+ audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
+ audio_unlock();
+ }
+
+ ui_px x = 0, spacer = 8;
+ for( u32 draw=0; draw<2; draw ++ )
+ {
+ if( vg_input.display_input_method == k_input_method_controller )
+ {
+ if( draw )
+ {
+ ui_rect inf_lb = { x, 0, 32, height };
+ ui_fill( ctx, inf_lb, lb_down? GUI_COL_NORM: GUI_COL_NORM );
+ ui_text( ctx, inf_lb, "\x91", 1, k_ui_align_middle_center, 0 );
+ }
+ x += 32 + spacer;
+ }
+
+ for( i32 i=0; i<VG_ARRAY_LEN(opts); i ++ )
+ {
+ ui_rect box = { x, 0, ui_text_line_width(ctx, opts[i]) + 32, height };
+
+ if( draw )
+ {
+ enum ui_button_state state = ui_button_base( ctx, box );
+ if( state == k_ui_button_click )
+ {
+ ui_fill( ctx, box, GUI_COL_DARK );
+ menu.main_index = i;
+ }
+ else if( state == k_ui_button_holding_inside )
+ {
+ ui_fill( ctx, box, GUI_COL_DARK );
+ }
+ else if( state == k_ui_button_holding_outside )
+ {
+ ui_fill( ctx, box, GUI_COL_DARK );
+ ui_outline( ctx, box, 1, GUI_COL_CLICK, 0 );
+ }
+ else if( state == k_ui_button_hover )
+ {
+ ui_fill( ctx, box, GUI_COL_NORM );
+ ui_outline( ctx, box, 1, GUI_COL_ACTIVE, 0 );
+ }
+ else
+ ui_fill( ctx, box,
+ i==menu.main_index? GUI_COL_ACTIVE: GUI_COL_NORM );
+
+ ui_text( ctx, box, opts[i], 1, k_ui_align_middle_center, 0 );
+ }
+
+ x += box[2] + spacer;
+ }
+
+ if( vg_input.display_input_method == k_input_method_controller )
+ {
+ if( draw )
+ {
+ ui_rect inf_rb = { x, 0, 32, height };
+ ui_fill( ctx, inf_rb, rb_down? GUI_COL_NORM: GUI_COL_NORM );
+ ui_text( ctx, inf_rb, "\x92", 1, k_ui_align_middle_center, 0 );
+ }
+ x += 32;
+ }
+
+ if( draw )
+ ui_fill( ctx,
+ (ui_rect){ x+8,0, vg.window_x-(x+8),height }, GUI_COL_NORM );
+ else
+ {
+ x = vg.window_x/2 - x/2;
+ ui_fill( ctx,
+ (ui_rect){ 0, 0, x-8, height }, GUI_COL_NORM );
+ }
+ }
+
+ if( menu.main_index == k_menu_main_map )
+ {
+ menu.bg_blur = 0;
+ ctx->font = &vgf_default_large;
+ ui_rect title = { vg.window_x/2- 512/2, height+8, 512, 64 };
+
+ ui_px x = 8,
+ y = height+8;
+
+ struct ui_vert *vs =
+ ui_fill( ctx, (ui_rect){ x,y, 0,height },
+ world_static.active_instance? GUI_COL_DARK: GUI_COL_ACTIVE );
+
+ char buf[64];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_strcat( &str, "Hub World" );
+
+ if( world_static.active_instance )
+ {
+ vg_strcat( &str, " (" );
+ vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
+ vg_strcatch( &str, '\x06' );
+ vg_strcatch( &str, '\x00' );
+ vg_strcat( &str, "\x1B[0m)" );
+ }
+
+ ui_px w = ui_text( ctx, (ui_rect){ x+8, y, 1000, height }, buf, 1,
+ k_ui_align_middle_left, 0 );
+ w *= ctx->font->sx;
+ x += w + 16;
+
+ vs[1].co[0] = x + 8;
+ vs[2].co[0] = x;
+
+ x += 2;
+
+ world_instance *world = &world_static.instances[1];
+ if( world->status == k_world_status_loaded )
+ {
+ const char *world_name =
+ mdl_pstr( &world->meta, world->info.pstr_name );
+
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_strcat( &str, world_name );
+
+ if( !world_static.active_instance &&
+ (vg_input.display_input_method == k_input_method_controller) )
+ {
+ vg_strcat( &str, " (" );
+ vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
+ vg_strcatch( &str, '\x06' );
+ vg_strcatch( &str, '\x00' );
+ vg_strcat( &str, "\x1B[0m)" );
+ }
+
+ vs = ui_fill( ctx, (ui_rect){ x,y, 1000,height },
+ world_static.active_instance? GUI_COL_ACTIVE: GUI_COL_DARK );
+ w = ui_text( ctx, (ui_rect){ x+16,y, 1000,height }, buf,
+ 1, k_ui_align_middle_left, 0 );
+
+ w = w*ctx->font->sx + 8*3;
+ x += w;
+
+ if( button_down( k_srbind_mhub ) ||
+ ui_button_base( ctx, (ui_rect){0,y,x,height} ) == k_ui_button_click )
+ {
+ world_switch_instance( world_static.active_instance ^ 0x1 );
+ skaterift.activity = k_skaterift_default;
+ world_map.view_ready = 0;
+ }
+
+ vs[0].co[0] += 8;
+ vs[1].co[0] = x + 8;
+ vs[2].co[0] = x;
+ }
+
+ x = 8;
+ y += 8 + height;
+
+ if( world_static.active_instance )
+ {
+ ui_rect stat_panel = { x,y, 256,vg.window_y-y };
+ u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f );
+ struct ui_vert *vs = ui_fill( ctx, stat_panel, c0 );
+
+ ui_rect_pad( stat_panel, (ui_px[2]){8,0} );
+
+ for( u32 i=0; i<mdl_arrcount( &world->ent_region ); i ++ )
+ {
+ ent_region *region = mdl_arritm( &world->ent_region, i );
+
+ if( !region->zone_volume )
+ continue;
+
+ const char *title = mdl_pstr( &world->meta, region->pstr_title );
+ ctx->font = &vgf_default_large;
+
+ ui_rect title_box;
+ menu_standard_widget( ctx, stat_panel, title_box, 1 );
+
+ stat_panel[0] += 16;
+ stat_panel[2] -= 16;
+ ctx->font = &vgf_default_small;
+
+ ent_volume *volume = mdl_arritm(&world->ent_volume,
+ mdl_entity_id_id(region->zone_volume));
+
+ u32 combined = k_ent_route_flag_achieve_gold |
+ k_ent_route_flag_achieve_silver;
+
+ char buf[128];
+ vg_str str;
+
+ for( u32 j=0; j<mdl_arrcount(&world->ent_route); j ++ )
+ {
+ ent_route *route = mdl_arritm(&world->ent_route, j );
+
+ v3f local;
+ m4x3_mulv( volume->to_local, route->board_transform[3], local );
+ if( !((fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f)) )
+ {
+ continue;
+ }
+
+ combined &= route->flags;
+
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_strcat( &str, "(Race) " );
+ vg_strcat( &str, mdl_pstr(&world->meta, route->pstr_name));
+
+ if( route->flags & k_ent_route_flag_achieve_silver )
+ vg_strcat( &str, " \xb3");
+ if( route->flags & k_ent_route_flag_achieve_gold )
+ vg_strcat( &str, "\xb3");
+
+ ui_rect r;
+ ui_standard_widget( ctx, stat_panel, r, 1 );
+ ui_text( ctx, r, buf, 1, k_ui_align_middle_left,
+ medal_colour( ctx, route->flags ) );
+ }
+
+ for( u32 j=0; j<mdl_arrcount(&world->ent_challenge); j ++ )
+ {
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, j );
+
+ v3f local;
+ m4x3_mulv( volume->to_local, challenge->transform.co, local );
+ if( !((fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f)) )
+ {
+ continue;
+ }
+
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_strcat( &str, mdl_pstr(&world->meta, challenge->pstr_alias));
+
+ u32 flags = 0x00;
+ if( challenge->status )
+ {
+ flags |= k_ent_route_flag_achieve_gold;
+ flags |= k_ent_route_flag_achieve_silver;
+ vg_strcat( &str, " \xb3\xb3" );
+ }
+
+ combined &= flags;
+
+ ui_rect r;
+ ui_standard_widget( ctx, stat_panel, r, 1 );
+ ui_text( ctx, r, buf, 1,
+ k_ui_align_middle_left, medal_colour( ctx, flags ) );
+ }
+
+ stat_panel[0] -= 16;
+ stat_panel[2] += 16;
+
+ u32 title_col = 0;
+ if( combined & k_ent_route_flag_achieve_gold )
+ title_col = ui_colour( ctx, k_ui_yellow );
+ else if( combined & k_ent_route_flag_achieve_silver )
+ title_col = ui_colour( ctx, k_ui_fg );
+
+ menu_heading( ctx, title_box, title, title_col );
+ }
+
+ vs[2].co[1] = stat_panel[1];
+ vs[3].co[1] = stat_panel[1];
+ }
+ }
+ else
+ {
+ if( button_down( k_srbind_mback ) )
+ {
+ audio_lock();
+ audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
+ audio_unlock();
+ skaterift.activity = k_skaterift_default;
+ return;
+ }
+
+ ui_rect list0 = { vg.window_x/2 - 512/2, height+32,
+ 512, vg.window_y-height-64 }, list;
+ rect_copy( list0, list );
+ ui_rect_pad( list, (ui_px[2]){8,8} );
+
+ /* MAIN / MAIN
+ * -------------------------------------------------------------------*/
+
+ if( menu.main_index == k_menu_main_main )
+ {
+ i32 R = menu_nav( &menu.main_row, mv, 2 );
+
+ if( menu_button( ctx, list, R == 0, "Resume" ) )
+ {
+ skaterift.activity = k_skaterift_default;
+ return;
+ }
+
+ if( menu_button( ctx, list, R == 1, "Credits" ) )
+ {
+ menu.page = k_menu_page_credits;
+ }
+
+ ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
+ if( menu_button( ctx, end, R == 2, "Quit Game" ) )
+ {
+ vg.window_should_close = 1;
+ }
+ }
+ else if( menu.main_index == k_menu_main_settings )
+ {
+ ui_fill( ctx, list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
+ ui_outline( ctx, list0, 1, GUI_COL_NORM, 0 );
+ i32 R = menu_nav( &menu.settings_row, mv, 8 );
+
+ ctx->font = &vgf_default_large;
+ list[1] -= 8;
+ menu_heading( ctx, list, "Game", 0 );
+ menu_checkbox( ctx, list, R == 0, "Show controls overlay",
+ &control_overlay.enabled );
+ menu_checkbox( ctx, list, R == 1, "Auto connect to global server",
+ &network_client.auto_connect );
+
+ menu_heading( ctx, list, "Audio/Video", 0 );
+ menu_slider( ctx, list, R == 2, "Volume", 0, 100,
+ &vg_audio.external_global_volume, "%.f%%" );
+ menu_slider( ctx, list, R == 3, "Resolution", 0, 100,
+ &k_render_scale, "%.f%%" );
+ menu_checkbox( ctx, list, R == 4, "Motion Blur", &k_blur_effect );
+
+ menu_heading( ctx, list, "Camera", 0 );
+ menu_slider( ctx, list, R == 5, "Fov", 97, 135,
+ &k_fov, "%.1f\xb0" );
+ menu_slider( ctx, list, R == 6, "Cam Height", -0.4f, +1.4f,
+ &k_cam_height, vg_lerpf(-0.4f,1.4f,k_cam_height)>=0.0f?
+ "+%.2fm": "%.2fm" );
+ menu_checkbox( ctx, list, R == 7, "Invert Y Axis", &k_invert_y );
+
+
+ ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
+ ctx->font = &vgf_default_small;
+ menu_heading( ctx, end, "Advanced", 0 );
+ if( menu_button( ctx, end, R == 8, "Open Engine Settings" ) )
+ {
+ vg_settings_open();
+ }
+ }
+ else if( menu.main_index == k_menu_main_guide )
+ {
+ list0[0] = 8;
+ list[0] = 16;
+
+ ui_px w = 700,
+ marg = list0[0]+list0[2],
+ pw = vg.window_x - marg,
+ infx = pw/2 - w/2 + marg;
+ ui_rect inf = { infx, height +32, w, 800 };
+
+ struct ui_vert *vs = NULL;
+
+ if( menu.guide_sel && (menu.guide_sel <= 3) )
+ {
+ vs = ui_fill( ctx, inf, ui_opacity( GUI_COL_DARK, 0.20f ) );
+ ui_rect_pad( inf, (ui_px[]){8,8} );
+ }
+
+ ui_fill( ctx, list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
+ ui_outline( ctx, list0, 1, GUI_COL_NORM, 0 );
+ i32 R = menu_nav( &menu.guides_row, mv, 6 );
+
+ ctx->font = &vgf_default_large;
+ list[1] -= 8;
+ menu_heading( ctx, list, "Info", 0 );
+ if( menu.guide_sel == 1 )
+ {
+ menu_try_find_cam( 1 );
+
+ ui_rect title;
+ ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
+ ctx->font = &vgf_default_title;
+ ui_text( ctx,
+ title, "Where to go", 1, k_ui_align_middle_center, 0 );
+
+ ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
+ ctx->font = &vgf_default_large;
+ ui_text( ctx, inf,
+ "Visit the sandcastles built by John Cockroach to be\n"
+ "transported into the real location. Use the map in the\n"
+ "menu to return back this hub island.\n"
+ "\n"
+ "You can begin training at the Volcano Island, where you\n"
+ "can learn movement skills. Then travel to Mt.Zero Island\n"
+ "or Bort Downtown to test them out. Finally the most\n"
+ "challenging course can be taken on at the Valley.\n"
+ "\n"
+ "Also visit the Steam Workshop for community made\n"
+ "locations to skate!"
+ , 1, k_ui_align_left, 0 );
+
+ vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*11 + 16;
+ vs[3].co[1] = vs[2].co[1];
+ }
+ if( menu_button( ctx, list, R == 0, "Where to go" ) )
+ menu.guide_sel = 1;
+
+ if( menu.guide_sel == 3 )
+ {
+ menu_try_find_cam( 2 );
+
+ ui_rect title;
+ ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
+ ctx->font = &vgf_default_title;
+ ui_text( ctx, title, "Online", 1, k_ui_align_middle_center, 0 );
+
+ ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
+ ctx->font = &vgf_default_large;
+ ui_text( ctx, inf,
+ "Connection to the global server is managed by this radar\n"
+ "dish in the hub island. Come back here to turn the\n"
+ "connection on or off.\n"
+ "\n"
+ "You'll then see other players or your friends if you go\n"
+ "to the same location, and your highscores will be saved\n"
+ "onto the scoreboards."
+ , 1, k_ui_align_left, 0 );
+
+ vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*7 + 16;
+ vs[3].co[1] = vs[2].co[1];
+ }
+ if( menu_button( ctx, list, R == 1, "Playing Online" ) )
+ menu.guide_sel = 3;
+
+ menu_heading( ctx, list, "Controls", 0 );
+ if( menu_button( ctx, list, R == 2, "Skating \xb2" ) )
+ {
+ menu.guide_sel = 0;
+ menu_link_modal(
+ "https://skaterift.com/index.php?page=movement" );
+ }
+ if( menu.guide_sel == 0 || menu.guide_sel > 3 ) menu_try_find_cam( 3 );
+
+ if( menu_button( ctx, list, R == 3, "Tricks \xb2" ) )
+ {
+ menu.guide_sel = 0;
+ menu_link_modal(
+ "https://skaterift.com/index.php?page=tricks" );
+ }
+
+ menu_heading( ctx, list, "Workshop", 0 );
+ if( menu_button( ctx, list, R == 4, "Create a Board \xb2" ) )
+ {
+ menu.guide_sel = 0;
+ menu_link_modal(
+ "https://skaterift.com/index.php?page=workshop_board" );
+ }
+ if( menu_button( ctx, list, R == 5, "Create a World \xb2" ) )
+ {
+ menu.guide_sel = 0;
+ menu_link_modal(
+ "https://skaterift.com/index.php?page=workshop_world" );
+ }
+ if( menu_button( ctx, list, R == 6, "Create a Playermodel \xb2" ) )
+ {
+ menu.guide_sel = 0;
+ menu_link_modal(
+ "https://skaterift.com/index.php?page=workshop_player" );
+ }
+ }
+ }
+
+menu_draw:
+
+ vg_ui.frosting = 0.015f;
+ ui_flush( ctx, k_ui_shader_colour, NULL );
+ vg_ui.frosting = 0.0f;
+ ctx->font = &vgf_default_small;
+}
--- /dev/null
+#pragma once
+
+#define MENU_STACK_SIZE 8
+
+#include "vg/vg_engine.h"
+#include "entity.h"
+
+enum menu_page
+{
+ k_menu_page_any,
+ k_menu_page_starter,
+ k_menu_page_premium,
+ k_menu_page_main,
+ k_menu_page_credits,
+ k_menu_page_help,
+};
+
+enum menu_main_subpage
+{
+ k_menu_main_main = 0,
+ k_menu_main_map = 1,
+ k_menu_main_settings = 2,
+ k_menu_main_guide = 3
+};
+
+struct global_menu
+{
+ int disable_open;
+ i32 skip_starter;
+ enum menu_page page;
+ i32 main_index,
+ main_row,
+ settings_row,
+ guides_row,
+ intro_row,
+ guide_sel,
+ prem_row;
+ f32 mouse_dist; /* used for waking up mouse */
+
+ f32 repeater;
+
+ bool bg_blur;
+ ent_camera *bg_cam;
+
+ const char *web_link; /* if set; modal */
+ i32 web_choice;
+
+ GLuint prem_tex;
+}
+extern menu;
+
+void menu_init(void);
+void menu_at_begin(void);
+void menu_gui( ui_context *ctx );
+void menu_open( enum menu_page page );
+bool menu_viewing_map(void);
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+
+#include "vg/vg_io.h"
+
+#ifdef VG_3D
+#include "vg/vg_async.h"
+#include "vg/vg_tex.h"
+#endif
+
+#include "vg/vg_msg.h"
+#include "vg/vg_string.h"
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "model.h"
+#include "shader_props.h"
+
+static void mdl_load_fatal_corrupt( mdl_context *mdl )
+{
+ vg_fatal_condition();
+ vg_file_error_info( mdl->file );
+ fclose( mdl->file );
+ vg_fatal_exit();
+}
+
+/*
+ * Model implementation
+ */
+
+void mdl_fread_pack_file( mdl_context *mdl, mdl_file *info, void *dst )
+{
+ if( !info->pack_size )
+ {
+ vg_fatal_condition();
+ vg_info( "Packed file is only a header; it is not packed" );
+ vg_info( "path: %s\n", mdl_pstr( mdl, info->pstr_path ) );
+ vg_fatal_exit();
+ }
+
+ fseek( mdl->file, mdl->pack_base_offset+info->pack_offset, SEEK_SET );
+ u64 l = fread( dst, info->pack_size, 1, mdl->file );
+
+ if( l != 1 ) mdl_load_fatal_corrupt( mdl );
+}
+
+/* TODO: Rename these */
+static void mdl_load_array_file_buffer( mdl_context *mdl, mdl_array *arr,
+ void *buffer, u32 stride )
+{
+ if( arr->item_count )
+ {
+ fseek( mdl->file, arr->file_offset, SEEK_SET );
+
+ if( stride == arr->item_size )
+ {
+ u64 l = fread( buffer, arr->item_size*arr->item_count, 1, mdl->file );
+ if( l != 1 ) mdl_load_fatal_corrupt( mdl );
+ }
+ else
+ {
+ vg_warn( "Applying alignment fixup to array @%p [%u -> %u] x %u\n",
+ buffer, arr->item_size, stride, arr->item_count );
+
+ if( stride > arr->item_size )
+ memset( buffer, 0, stride*arr->item_count );
+
+ u32 read_size = VG_MIN( stride, arr->item_size );
+
+ for( u32 i=0; i<arr->item_count; i++ )
+ {
+ u64 l = fread( buffer+i*stride, read_size, 1, mdl->file );
+ if( stride < arr->item_size )
+ fseek( mdl->file, arr->item_size-stride, SEEK_CUR );
+
+ if( l != 1 ) mdl_load_fatal_corrupt( mdl );
+ }
+ }
+ }
+}
+
+static void mdl_load_array_file( mdl_context *mdl, mdl_array_ptr *ptr,
+ mdl_array *arr, void *lin_alloc, u32 stride )
+{
+ if( arr->item_count )
+ {
+ u32 size = stride*arr->item_count;
+ ptr->data = lin_alloc? vg_linear_alloc( lin_alloc, vg_align8(size) ):
+ malloc( size );
+ mdl_load_array_file_buffer( mdl, arr, ptr->data, stride );
+ }
+ else
+ {
+ ptr->data = NULL;
+ }
+
+ ptr->stride = stride;
+ ptr->count = arr->item_count;
+}
+
+void *mdl_arritm( mdl_array_ptr *arr, u32 index )
+{
+ return ((u8 *)arr->data) + index*arr->stride;
+}
+
+u32 mdl_arrcount( mdl_array_ptr *arr )
+{
+ return arr->count;
+}
+
+static mdl_array *mdl_find_array( mdl_context *mdl, const char *name )
+{
+ for( u32 i=0; i<mdl_arrcount(&mdl->index); i++ )
+ {
+ mdl_array *arr = mdl_arritm( &mdl->index, i );
+
+ if( !strncmp(arr->name,name,16) )
+ return arr;
+ }
+
+ return NULL;
+}
+
+int _mdl_load_array( mdl_context *mdl, mdl_array_ptr *ptr,
+ const char *name, void *lin_alloc, u32 stride )
+{
+ mdl_array *arr = mdl_find_array( mdl, name );
+
+ if( arr )
+ {
+ mdl_load_array_file( mdl, ptr, arr, lin_alloc, stride );
+ return 1;
+ }
+ else
+ {
+ ptr->data = NULL;
+ ptr->count = 0;
+ ptr->stride = 0;
+ return 0;
+ }
+}
+
+int mdl_load_mesh_block( mdl_context *mdl, void *lin_alloc )
+{
+ int success = 1;
+
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->verts, mdl_vert, lin_alloc );
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->indices, mdl_indice, lin_alloc );
+
+ return success;
+}
+
+int mdl_load_metadata_block( mdl_context *mdl, void *lin_alloc )
+{
+ int success = 1;
+
+ success &= _mdl_load_array( mdl, &mdl->strings, "strings", lin_alloc, 1 );
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->meshs, mdl_mesh, lin_alloc );
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->submeshs, mdl_submesh, lin_alloc );
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->textures, mdl_texture, lin_alloc );
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->armatures, mdl_armature, lin_alloc );
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->bones, mdl_bone, lin_alloc );
+ success &= MDL_LOAD_ARRAY( mdl, &mdl->animations,mdl_animation,lin_alloc );
+
+ success &= mdl_load_materials( mdl, lin_alloc );
+
+ return success;
+}
+
+int mdl_load_animation_block( mdl_context *mdl, void *lin_alloc )
+{
+ return MDL_LOAD_ARRAY( mdl, &mdl->keyframes, mdl_keyframe, lin_alloc );
+}
+
+void *mdl_shader_standard( vg_msg *msg, void *alloc )
+{
+ struct shader_props_standard *props =
+ vg_linear_alloc( alloc, sizeof(struct shader_props_standard) );
+
+ vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
+ NULL );
+
+ return props;
+}
+
+void *mdl_shader_terrain( vg_msg *msg, void *alloc )
+{
+ struct shader_props_terrain *props =
+ vg_linear_alloc( alloc, sizeof(struct shader_props_terrain) );
+
+ vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
+ NULL );
+ vg_msg_getkvvecf( msg, "sand_colour", k_vg_msg_v4f,
+ props->sand_colour, (v4f){ 0.79, 0.63, 0.48, 1.0 } );
+ vg_msg_getkvvecf( msg, "blend_offset", k_vg_msg_v2f,
+ props->blend_offset, (v2f){ 0.5, 0.0 } );
+
+ return props;
+}
+
+void *mdl_shader_vertex_blend( vg_msg *msg, void *alloc )
+{
+ struct shader_props_vertex_blend *props =
+ vg_linear_alloc( alloc, sizeof(struct shader_props_vertex_blend) );
+
+ vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
+ NULL );
+ vg_msg_getkvvecf( msg, "blend_offset", k_vg_msg_v2f,
+ props->blend_offset, (v2f){ 0.5, 0.0 } );
+ return props;
+}
+
+void *mdl_shader_water( vg_msg *msg, void *alloc )
+{
+ struct shader_props_water *props =
+ vg_linear_alloc( alloc, sizeof(struct shader_props_water) );
+
+ vg_msg_getkvvecf( msg, "shore_colour", k_vg_msg_v4f,
+ props->shore_colour, (v4f){0.03,0.32,0.61,1.0} );
+ vg_msg_getkvvecf( msg, "deep_colour", k_vg_msg_v4f,
+ props->deep_colour, (v4f){0.0,0.006,0.03,1.0} );
+ vg_msg_getkvintg( msg, "fog_scale", k_vg_msg_f32, &props->fog_scale,
+ (f32[]){0.04} );
+ vg_msg_getkvintg( msg, "fresnel", k_vg_msg_f32, &props->fresnel,
+ (f32[]){5.0} );
+ vg_msg_getkvintg( msg, "water_scale", k_vg_msg_f32, &props->water_sale,
+ (f32[]){ 0.008 } );
+ vg_msg_getkvvecf( msg, "wave_speed", k_vg_msg_v4f,
+ props->wave_speed, (v4f){0.008,0.006,0.003,0.03} );
+ return props;
+}
+
+void *mdl_shader_cubemapped( vg_msg *msg, void *alloc )
+{
+ struct shader_props_cubemapped *props =
+ vg_linear_alloc( alloc, sizeof(struct shader_props_cubemapped) );
+
+ vg_msg_getkvintg( msg, "tex_diffuse", k_vg_msg_u32, &props->tex_diffuse,
+ NULL );
+ vg_msg_getkvintg( msg, "cubemap_entity", k_vg_msg_u32,
+ &props->cubemap_entity, NULL );
+ vg_msg_getkvvecf( msg, "tint", k_vg_msg_v4f,
+ props->tint, (v4f){1.0,1.0,1.0,1.0} );
+ return props;
+}
+
+bool _mdl_legacy_v105_properties( struct mdl_material_v105 *mat, vg_msg *dst )
+{
+ vg_msg_wkvnum( dst, "tex_diffuse", k_vg_msg_u32, 1, &mat->tex_diffuse );
+
+ if( mat->shader == k_shader_cubemap )
+ {
+ vg_msg_wkvnum( dst, "cubemap", k_vg_msg_u32, 1, &mat->tex_none0 );
+ vg_msg_wkvnum( dst, "tint", k_vg_msg_f32, 4, mat->colour );
+ }
+ else if( mat->shader == k_shader_terrain_blend )
+ {
+ vg_msg_wkvnum( dst, "sand_colour", k_vg_msg_f32, 4, mat->colour );
+ vg_msg_wkvnum( dst, "blend_offset", k_vg_msg_f32, 2, mat->colour1 );
+ }
+ else if( mat->shader == k_shader_standard_vertex_blend )
+ {
+ vg_msg_wkvnum( dst, "blend_offset", k_vg_msg_f32, 2, mat->colour1 );
+ }
+ else if( mat->shader == k_shader_water )
+ {
+ vg_msg_wkvnum( dst, "shore_colour", k_vg_msg_f32, 4, mat->colour );
+ vg_msg_wkvnum( dst, "deep_colour", k_vg_msg_f32, 4, mat->colour1 );
+ }
+
+ return 1;
+}
+
+int mdl_load_materials( mdl_context *mdl, void *lin_alloc )
+{
+ MDL_LOAD_ARRAY( mdl, &mdl->materials, mdl_material, lin_alloc );
+
+#if (MDL_VERSION_MIN <= 105)
+ /* load legacy material data into scratch */
+ mdl_array_ptr legacy_materials;
+ if( mdl->info.version <= 105 )
+ {
+ _mdl_load_array( mdl, &legacy_materials, "mdl_material", vg_mem.scratch,
+ sizeof(struct mdl_material_v105) );
+ }
+#endif
+
+ mdl_array_ptr data;
+ _mdl_load_array( mdl, &data, "shader_data", vg_mem.scratch, 1 );
+
+ if( !lin_alloc )
+ return 1;
+
+ for( u32 i=0; i<mdl_arrcount(&mdl->materials); i ++ )
+ {
+ mdl_material *mat = mdl_arritm( &mdl->materials, i );
+ vg_msg msg;
+
+#if (MDL_VERSION_MIN <= 105)
+ u8 legacy_buf[512];
+ if( mdl->info.version <= 105 )
+ {
+ vg_msg_init( &msg, legacy_buf, sizeof(legacy_buf) );
+ _mdl_legacy_v105_properties( mdl_arritm( &legacy_materials,i ), &msg );
+ vg_msg_init( &msg, legacy_buf, msg.cur.co );
+ }
+ else
+#endif
+ {
+ vg_msg_init( &msg, data.data + mat->props.kvs.offset,
+ mat->props.kvs.size );
+ }
+
+ if( mat->shader == k_shader_standard ||
+ mat->shader == k_shader_standard_cutout ||
+ mat->shader == k_shader_foliage ||
+ mat->shader == k_shader_fxglow )
+ {
+ mat->props.compiled = mdl_shader_standard( &msg, lin_alloc );
+ }
+ else if( mat->shader == k_shader_standard_vertex_blend )
+ {
+ mat->props.compiled = mdl_shader_vertex_blend( &msg, lin_alloc );
+ }
+ else if( mat->shader == k_shader_cubemap )
+ {
+ mat->props.compiled = mdl_shader_cubemapped( &msg, lin_alloc );
+ }
+ else if( mat->shader == k_shader_terrain_blend )
+ {
+ mat->props.compiled = mdl_shader_terrain( &msg, lin_alloc );
+ }
+ else if( mat->shader == k_shader_water )
+ {
+ mat->props.compiled = mdl_shader_water( &msg, lin_alloc );
+ }
+ else
+ mat->props.compiled = NULL;
+ }
+
+ return 1;
+}
+
+/*
+ * if calling mdl_open, and the file does not exist, the game will fatal quit
+ */
+void mdl_open( mdl_context *mdl, const char *path, void *lin_alloc )
+{
+ memset( mdl, 0, sizeof( mdl_context ) );
+ mdl->file = fopen( path, "rb" );
+
+ if( !mdl->file )
+ {
+ vg_fatal_condition();
+ vg_info( "mdl_open('%s'): %s\n", path, strerror(errno) );
+ vg_fatal_exit();
+ }
+
+ u64 l = fread( &mdl->info, sizeof(mdl_header), 1, mdl->file );
+ if( l != 1 )
+ mdl_load_fatal_corrupt( mdl );
+
+ if( mdl->info.version < MDL_VERSION_MIN )
+ {
+ vg_fatal_condition();
+ vg_info( "Legacy model version incompatable" );
+ vg_info( "For model: %s\n", path );
+ vg_info( " version: %u (min: %u, current: %u)\n",
+ mdl->info.version, MDL_VERSION_MIN, MDL_VERSION_NR );
+ vg_fatal_exit();
+ }
+
+ mdl_load_array_file( mdl, &mdl->index, &mdl->info.index, lin_alloc,
+ sizeof(mdl_array) );
+
+ mdl_array *pack = mdl_find_array( mdl, "pack" );
+ if( pack ) mdl->pack_base_offset = pack->file_offset;
+ else mdl->pack_base_offset = 0;
+}
+
+/*
+ * close file handle
+ */
+void mdl_close( mdl_context *mdl )
+{
+ fclose( mdl->file );
+ mdl->file = NULL;
+}
+
+/* useful things you can do with the model */
+
+void mdl_transform_m4x3( mdl_transform *transform, m4x3f mtx )
+{
+ q_m3x3( transform->q, mtx );
+ v3_muls( mtx[0], transform->s[0], mtx[0] );
+ v3_muls( mtx[1], transform->s[1], mtx[1] );
+ v3_muls( mtx[2], transform->s[2], mtx[2] );
+ v3_copy( transform->co, mtx[3] );
+}
+
+const char *mdl_pstr( mdl_context *mdl, u32 pstr )
+{
+ return ((char *)mdl_arritm( &mdl->strings, pstr )) + 4;
+}
+
+
+int mdl_pstreq( mdl_context *mdl, u32 pstr, const char *str, u32 djb2 )
+{
+ u32 hash = *((u32 *)mdl_arritm( &mdl->strings, pstr ));
+ if( hash == djb2 ){
+ if( !strcmp( str, mdl_pstr( mdl, pstr ))) return 1;
+ else return 0;
+ }
+ else return 0;
+}
+
+/*
+ * Simple mesh interface for OpenGL
+ * ----------------------------------------------------------------------------
+ */
+
+#ifdef VG_3D
+static void mesh_upload( glmesh *mesh,
+ mdl_vert *verts, u32 vert_count,
+ u32 *indices, u32 indice_count )
+{
+ glGenVertexArrays( 1, &mesh->vao );
+ glGenBuffers( 1, &mesh->vbo );
+ glGenBuffers( 1, &mesh->ebo );
+ glBindVertexArray( mesh->vao );
+
+ size_t stride = sizeof(mdl_vert);
+
+ glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo );
+ glBufferData( GL_ARRAY_BUFFER, vert_count*stride, verts, GL_STATIC_DRAW );
+
+ glBindVertexArray( mesh->vao );
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER, indice_count*sizeof(u32),
+ indices, GL_STATIC_DRAW );
+
+ /* 0: coordinates */
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ /* 1: normal */
+ glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE,
+ stride, (void *)offsetof(mdl_vert, norm) );
+ glEnableVertexAttribArray( 1 );
+
+ /* 2: uv */
+ glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE,
+ stride, (void *)offsetof(mdl_vert, uv) );
+ glEnableVertexAttribArray( 2 );
+
+ /* 3: colour */
+ glVertexAttribPointer( 3, 4, GL_UNSIGNED_BYTE, GL_TRUE,
+ stride, (void *)offsetof(mdl_vert, colour) );
+ glEnableVertexAttribArray( 3 );
+
+ /* 4: weights */
+ glVertexAttribPointer( 4, 4, GL_UNSIGNED_SHORT, GL_TRUE,
+ stride, (void *)offsetof(mdl_vert, weights) );
+ glEnableVertexAttribArray( 4 );
+
+ /* 5: groups */
+ glVertexAttribIPointer( 5, 4, GL_UNSIGNED_BYTE,
+ stride, (void *)offsetof(mdl_vert, groups) );
+ glEnableVertexAttribArray( 5 );
+
+ mesh->indice_count = indice_count;
+ mesh->loaded = 1;
+}
+
+void mesh_bind( glmesh *mesh )
+{
+ glBindVertexArray( mesh->vao );
+}
+
+void mesh_drawn( u32 start, u32 count )
+{
+ glDrawElements( GL_TRIANGLES, count, GL_UNSIGNED_INT,
+ (void *)(start*sizeof(u32)) );
+}
+
+void mesh_draw( glmesh *mesh )
+{
+ mesh_drawn( 0, mesh->indice_count );
+}
+
+void mesh_free( glmesh *mesh )
+{
+ if( mesh->loaded )
+ {
+ glDeleteVertexArrays( 1, &mesh->vao );
+ glDeleteBuffers( 1, &mesh->ebo );
+ glDeleteBuffers( 1, &mesh->vbo );
+ mesh->loaded = 0;
+ }
+}
+
+void mdl_draw_submesh( mdl_submesh *sm )
+{
+ mesh_drawn( sm->indice_start, sm->indice_count );
+}
+#endif
+
+mdl_mesh *mdl_find_mesh( mdl_context *mdl, const char *name )
+{
+ for( u32 i=0; i<mdl_arrcount( &mdl->meshs ); i++ )
+ {
+ mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
+ if( !strcmp( name, mdl_pstr( mdl, mesh->pstr_name )))
+ {
+ return mesh;
+ }
+ }
+ return NULL;
+}
+
+mdl_submesh *mdl_find_submesh( mdl_context *mdl, const char *mesh_name )
+{
+ mdl_mesh *mesh = mdl_find_mesh( mdl, mesh_name );
+
+ if( !mesh ) return NULL;
+ if( !mesh->submesh_count ) return NULL;
+
+ return mdl_arritm( &mdl->submeshs, mesh->submesh_start );
+}
+
+#ifdef VG_3D
+struct payload_glmesh_load
+{
+ mdl_vert *verts;
+ u32 *indices;
+
+ u32 vertex_count,
+ indice_count;
+
+ glmesh *mesh;
+};
+
+static void _sync_mdl_load_glmesh( void *payload, u32 size )
+{
+ struct payload_glmesh_load *job = payload;
+ mesh_upload( job->mesh, job->verts, job->vertex_count,
+ job->indices, job->indice_count );
+}
+
+void mdl_async_load_glmesh( mdl_context *mdl, glmesh *mesh, u32 *fixup_table )
+{
+ mdl_array *arr_vertices = mdl_find_array( mdl, "mdl_vert" );
+ mdl_array *arr_indices = mdl_find_array( mdl, "mdl_indice" );
+
+ if( arr_vertices && arr_indices )
+ {
+ u32 size_verts = vg_align8(sizeof(mdl_vert)*arr_vertices->item_count),
+ size_indices = vg_align8(sizeof(mdl_indice)*arr_indices->item_count),
+ size_hdr = vg_align8(sizeof(struct payload_glmesh_load)),
+ total = size_hdr + size_verts + size_indices;
+
+ vg_async_item *call = vg_async_alloc( total );
+ struct payload_glmesh_load *job = call->payload;
+
+ u8 *payload = call->payload;
+
+ job->mesh = mesh;
+ job->verts = (void*)(payload + size_hdr);
+ job->indices = (void*)(payload + size_hdr + size_verts);
+ job->vertex_count = arr_vertices->item_count;
+ job->indice_count = arr_indices->item_count;
+
+ mdl_load_array_file_buffer( mdl, arr_vertices,
+ job->verts, sizeof(mdl_vert) );
+ mdl_load_array_file_buffer( mdl, arr_indices, job->indices,
+ sizeof(mdl_indice) );
+
+ if( fixup_table )
+ {
+ for( u32 i=0; i<job->vertex_count; i ++ )
+ {
+ mdl_vert *vert = &job->verts[i];
+
+ for( u32 j=0; j<4; j++ )
+ {
+ vert->groups[j] = fixup_table[vert->groups[j]];
+ }
+ }
+ }
+
+ /*
+ * Unpack the indices (if there are meshes)
+ * ---------------------------------------------------------
+ */
+
+ if( mdl_arrcount( &mdl->submeshs ) )
+ {
+ mdl_submesh *sm = mdl_arritm( &mdl->submeshs, 0 );
+ u32 offset = sm->vertex_count;
+
+ for( u32 i=1; i<mdl_arrcount( &mdl->submeshs ); i++ )
+ {
+ mdl_submesh *sm = mdl_arritm( &mdl->submeshs, i );
+ u32 *indices = job->indices + sm->indice_start;
+
+ for( u32 j=0; j<sm->indice_count; j++ )
+ indices[j] += offset;
+
+ offset += sm->vertex_count;
+ }
+ }
+
+ /*
+ * Dispatch
+ * -------------------------
+ */
+
+ vg_async_dispatch( call, _sync_mdl_load_glmesh );
+ }
+ else
+ {
+ vg_fatal_condition();
+ vg_info( "No vertex/indice data in model file\n" );
+ vg_fatal_exit();
+ }
+}
+
+/* uploads the glmesh, and textures. everything is saved into the mdl_context */
+void mdl_async_full_load_std( mdl_context *mdl )
+{
+ mdl_async_load_glmesh( mdl, &mdl->mesh, NULL );
+
+ for( u32 i=0; i<mdl_arrcount( &mdl->textures ); i ++ )
+ {
+ vg_linear_clear( vg_mem.scratch );
+ mdl_texture *tex = mdl_arritm( &mdl->textures, i );
+
+ void *data = vg_linear_alloc( vg_mem.scratch, tex->file.pack_size );
+ mdl_fread_pack_file( mdl, &tex->file, data );
+
+ vg_tex2d_load_qoi_async( data, tex->file.pack_size,
+ VG_TEX2D_CLAMP|VG_TEX2D_NEAREST, &tex->glname );
+ }
+}
+#endif
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+
+#define MDL_VERSION_MIN 101
+#define MDL_VERSION_NR 106
+
+enum mdl_shader{
+ k_shader_standard = 0,
+ k_shader_standard_cutout = 1,
+ k_shader_terrain_blend = 2,
+ k_shader_standard_vertex_blend = 3,
+ k_shader_water = 4,
+ k_shader_invisible = 5,
+ k_shader_boundary = 6,
+ k_shader_fxglow = 7,
+ k_shader_cubemap = 8,
+ k_shader_walking = 9,
+ k_shader_foliage = 10,
+ k_shader_override = 30000
+};
+
+enum mdl_surface_prop{
+ k_surface_prop_concrete = 0,
+ k_surface_prop_wood = 1,
+ k_surface_prop_grass = 2,
+ k_surface_prop_tiles = 3,
+ k_surface_prop_metal = 4,
+ k_surface_prop_snow = 5,
+ k_surface_prop_sand = 6
+};
+
+enum material_flag{
+ k_material_flag_skate_target = 0x0001,
+ k_material_flag_collision = 0x0002,
+ k_material_flag_grow_grass = 0x0004,
+ k_material_flag_grindable = 0x0008,
+ k_material_flag_invisible = 0x0010,
+ k_material_flag_boundary = 0x0020,
+ k_material_flag_preview_visibile = 0x0040,
+ k_material_flag_walking = 0x0080,
+
+ k_material_flag_ghosts =
+ k_material_flag_boundary|
+ k_material_flag_invisible|
+ k_material_flag_walking
+};
+
+#pragma pack(push,1)
+
+/* 48 byte */
+struct mdl_vert
+{
+ v3f co, /* 3*32 */
+ norm; /* 3*32 */
+ v2f uv; /* 2*32 */
+
+ u8 colour[4]; /* 4*8 */
+ u16 weights[4];/* 4*16 */
+ u8 groups[4]; /* 4*8 */
+};
+
+#pragma pack(pop)
+
+typedef u32 mdl_indice;
+
+typedef struct mdl_context mdl_context;
+typedef struct mdl_array_ptr mdl_array_ptr;
+typedef struct mdl_vert mdl_vert;
+typedef struct mdl_transform mdl_transform;
+typedef struct mdl_submesh mdl_submesh;
+typedef struct mdl_material mdl_material;
+typedef struct mdl_bone mdl_bone;
+typedef struct mdl_armature mdl_armature;
+typedef struct mdl_animation mdl_animation;
+typedef struct mdl_transform mdl_keyframe;
+typedef struct mdl_mesh mdl_mesh;
+typedef struct mdl_file mdl_file;
+typedef struct mdl_texture mdl_texture;
+typedef struct mdl_array mdl_array;
+typedef struct mdl_header mdl_header;
+
+typedef struct glmesh glmesh;
+struct glmesh
+{
+ u32 vao, vbo, ebo;
+ u32 indice_count;
+ u32 loaded;
+};
+
+struct mdl_transform
+{
+ v3f co, s;
+ v4f q;
+};
+
+static void transform_identity( mdl_transform *transform )
+{
+ v3_zero( transform->co );
+ q_identity( transform->q );
+ v3_fill( transform->s, 1.0f );
+}
+
+static void mdl_transform_vector( mdl_transform *transform, v3f vec, v3f dest )
+{
+ v3_mul( transform->s, vec, dest );
+ q_mulv( transform->q, dest, dest );
+}
+
+static void mdl_transform_point( mdl_transform *transform, v3f co, v3f dest )
+{
+ mdl_transform_vector( transform, co, dest );
+ v3_add( transform->co, dest, dest );
+}
+
+static void mdl_transform_mul( mdl_transform *a, mdl_transform *b,
+ mdl_transform *d )
+{
+ mdl_transform_point( a, b->co, d->co );
+ q_mul( a->q, b->q, d->q );
+ q_normalize( d->q );
+ v3_mul( a->s, b->s, d->s );
+}
+
+struct mdl_file
+{
+ u32 pstr_path,
+ pack_offset,
+ pack_size;
+};
+
+#if (MDL_VERSION_MIN <= 105)
+struct mdl_material_v105
+{
+ u32 pstr_name,
+ shader,
+ flags,
+ surface_prop;
+
+ v4f colour,
+ colour1;
+
+ u32 tex_diffuse, /* Indexes start from 1. 0 if missing. */
+ tex_none0,
+ tex_none1;
+};
+#endif
+
+struct mdl_material
+{
+ u32 pstr_name,
+ shader,
+ flags,
+ surface_prop;
+
+ union
+ {
+ struct
+ {
+ u32 offset, size;
+ /* -> vg_msg containing KV properties */
+ }
+ kvs;
+ void *compiled; /* -> shader specific structure for render */
+ }
+ props;
+};
+
+struct mdl_bone
+{
+ v3f co, end;
+ u32 parent,
+ collider,
+ ik_target,
+ ik_pole,
+ flags,
+ pstr_name;
+
+ boxf hitbox;
+ v3f conevx, conevy, coneva;
+ float conet;
+};
+
+enum bone_flag
+{
+ k_bone_flag_deform = 0x00000001,
+ k_bone_flag_ik = 0x00000002,
+ k_bone_flag_cone_constraint = 0x00000004
+};
+
+enum bone_collider
+{
+ k_bone_collider_none = 0,
+ k_bone_collider_box = 1,
+ k_bone_collider_capsule = 2
+};
+
+struct mdl_armature
+{
+ mdl_transform transform;
+ u32 bone_start,
+ bone_count,
+ anim_start,
+ anim_count;
+};
+
+struct mdl_animation
+{
+ u32 pstr_name,
+ length;
+ float rate;
+ u32 offset;
+};
+
+struct mdl_submesh
+{
+ u32 indice_start,
+ indice_count,
+ vertex_start,
+ vertex_count;
+
+ boxf bbx;
+ u16 material_id, flags;
+};
+
+enum esubmesh_flags
+{
+ k_submesh_flag_none = 0x0000,
+ k_submesh_flag_consumed = 0x0001
+};
+
+struct mdl_mesh
+{
+ mdl_transform transform;
+ u32 submesh_start,
+ submesh_count,
+ pstr_name,
+ entity_id, /* upper 16 bits: type, lower 16 bits: index */
+ armature_id;
+};
+
+struct mdl_texture
+{
+ mdl_file file;
+ u32 glname;
+};
+
+struct mdl_array
+{
+ u32 file_offset,
+ item_count,
+ item_size;
+
+ char name[16];
+};
+
+struct mdl_header
+{
+ u32 version;
+ mdl_array index;
+};
+
+struct mdl_context
+{
+ FILE *file;
+ mdl_header info;
+
+ struct mdl_array_ptr
+ {
+ void *data;
+ u32 count, stride;
+ }
+ index,
+
+ /* metadata */
+ strings,
+ meshs,
+ submeshs,
+ materials,
+ textures,
+ armatures,
+ bones,
+ animations,
+
+ /* animation buffers */
+ keyframes,
+
+ /* mesh buffers */
+ verts,
+ indices;
+ u32 pack_base_offset;
+
+ /* runtime */
+ glmesh mesh;
+};
+
+void mesh_bind( glmesh *mesh );
+void mesh_drawn( u32 start, u32 count );
+void mesh_draw( glmesh *mesh );
+void mesh_free( glmesh *mesh );
+
+/* file context management */
+void mdl_open( mdl_context *mdl, const char *path, void *lin_alloc );
+void mdl_close( mdl_context *mdl );
+
+/* array loading */
+int _mdl_load_array( mdl_context *mdl, mdl_array_ptr *ptr,
+ const char *name, void *lin_alloc, u32 stride );
+#define MDL_LOAD_ARRAY( MDL, PTR, STRUCT, ALLOCATOR ) \
+ _mdl_load_array( MDL, PTR, #STRUCT, ALLOCATOR, sizeof(STRUCT) )
+
+/* array access */
+void *mdl_arritm( mdl_array_ptr *arr, u32 index );
+u32 mdl_arrcount( mdl_array_ptr *arr );
+
+/* pack access */
+void mdl_fread_pack_file( mdl_context *mdl, mdl_file *info, void *dst );
+
+/* standard array groups */
+int mdl_load_animation_block( mdl_context *mdl, void *lin_alloc );
+int mdl_load_metadata_block( mdl_context *mdl, void *lin_alloc );
+int mdl_load_mesh_block( mdl_context *mdl, void *lin_alloc );
+int mdl_load_materials( mdl_context *mdl, void *lin_alloc );
+
+/* load mesh */
+void mdl_async_load_glmesh( mdl_context *mdl, glmesh *mesh, u32 *fixup_table );
+
+/* load textures and mesh */
+void mdl_async_full_load_std( mdl_context *mdl );
+
+/* rendering */
+void mdl_draw_submesh( mdl_submesh *sm );
+mdl_mesh *mdl_find_mesh( mdl_context *mdl, const char *name );
+mdl_submesh *mdl_find_submesh( mdl_context *mdl, const char *mesh_name );
+
+/* pstrs */
+const char *mdl_pstr( mdl_context *mdl, u32 pstr );
+int mdl_pstreq( mdl_context *mdl, u32 pstr, const char *str, u32 djb2 );
+#define MDL_CONST_PSTREQ( MDL, Q, CONSTSTR )\
+ mdl_pstreq( MDL, Q, CONSTSTR, vg_strdjb2( CONSTSTR ) )
+
+void mdl_transform_m4x3( mdl_transform *transform, m4x3f mtx );
+
--- /dev/null
+#include "skaterift.h"
+#include "vg/vg_steam.h"
+#include "vg/vg_steam_networking.h"
+#include "vg/vg_steam_auth.h"
+#include "vg/vg_steam_friends.h"
+#include "player.h"
+#include "network.h"
+#include "network_msg.h"
+#include "network_common.h"
+#include "player_remote.h"
+#include "world.h"
+#include "world_sfd.h"
+#include "world_routes.h"
+#include "vg/vg_ui/imgui.h"
+#include "gui.h"
+#include "ent_region.h"
+#include "vg/vg_loader.h"
+
+#ifdef _WIN32
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+#else
+ #include <sys/socket.h>
+ #include <sys/types.h>
+ #include <netdb.h>
+#endif
+
+struct network_client network_client =
+{
+ .auth_mode = eServerModeAuthentication,
+ .state = k_ESteamNetworkingConnectionState_None,
+ .last_intent_change = -99999.9
+};
+
+static void scores_update(void);
+
+int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){
+ if( msg->m_cbSize < size ) {
+ vg_error( "Invalid packet size (must be at least %u)\n", size );
+ return 0;
+ }
+ else{
+ return 1;
+ }
+}
+
+static void on_auth_ticket_recieved( void *result, void *context ){
+ EncryptedAppTicketResponse_t *response = result;
+
+ if( response->m_eResult == k_EResultOK ){
+ vg_info( " New app ticket ready\n" );
+ }
+ else{
+ vg_warn( " Could not request new encrypted app ticket (%u)\n",
+ response->m_eResult );
+ }
+
+ if( SteamAPI_ISteamUser_GetEncryptedAppTicket( hSteamUser,
+ network_client.app_symmetric_key,
+ VG_ARRAY_LEN(network_client.app_symmetric_key),
+ &network_client.app_key_length )){
+ vg_success( " Loaded app ticket\n" );
+ }
+ else{
+ vg_error( " No ticket availible\n" );
+ network_client.app_key_length = 0;
+ }
+}
+
+static void request_auth_ticket(void){
+ /*
+ * TODO Check for one thats cached on the disk and load it.
+ * This might be OK though because steam seems to cache the result
+ */
+
+ vg_info( "Requesting new authorization ticket\n" );
+
+ vg_steam_async_call *call = vg_alloc_async_steam_api_call();
+ call->userdata = NULL;
+ call->p_handler = on_auth_ticket_recieved;
+ call->id =
+ SteamAPI_ISteamUser_RequestEncryptedAppTicket( hSteamUser, NULL, 0 );
+}
+
+static void network_send_username(void){
+ if( !network_connected() )
+ return;
+
+ netmsg_playerusername *update = alloca( sizeof(netmsg_playerusername)+
+ NETWORK_USERNAME_MAX );
+ update->inetmsg_id = k_inetmsg_playerusername;
+ update->index = 0xff;
+
+ ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
+ const char *username = SteamAPI_ISteamFriends_GetPersonaName(hSteamFriends);
+ u32 chs = str_utf8_collapse( username, update->name, NETWORK_USERNAME_MAX );
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote,
+ update, sizeof(netmsg_playerusername)+chs+1,
+ k_nSteamNetworkingSend_Reliable, NULL );
+}
+
+void network_send_region(void)
+{
+ if( !network_connected() )
+ return;
+
+ netmsg_region *region = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
+
+ region->inetmsg_id = k_inetmsg_region;
+ region->client = 0;
+ region->flags = global_ent_region.flags;
+
+ u32 l = vg_strncpy( global_ent_region.location, region->loc,
+ NETWORK_REGION_MAX, k_strncpy_always_add_null );
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote,
+ region, sizeof(netmsg_region)+l+1,
+ k_nSteamNetworkingSend_Reliable, NULL );
+}
+
+static void network_send_request( netmsg_request *req, vg_msg *body,
+ void (*callback)(
+ netmsg_request *res, vg_msg *body,
+ u64 userdata),
+ u64 userdata ){
+ u32 len = 0;
+ if( body ){
+ len = body->cur.co;
+ vg_info( "Request scoreboard. Info (%u):\n", body->cur.co );
+ vg_msg_print( body, len );
+
+ if( body->error != k_vg_msg_error_OK ){
+ vg_error( "Body not OK\n" );
+ return;
+ }
+ }
+
+ if( callback ){
+ req->id = vg_pool_lru( &network_client.request_pool );
+ if( req->id ){
+ vg_pool_watch( &network_client.request_pool, req->id );
+ struct network_request *pn =
+ vg_pool_item( &network_client.request_pool, req->id );
+ pn->callback = callback;
+ pn->sendtime = vg.time_real;
+ pn->userdata = userdata;
+ }
+ else{
+ vg_error( "Unable to send request. Pool is full.\n" );
+ return;
+ }
+ }
+ else
+ req->id = 0;
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote,
+ req, sizeof(netmsg_request)+len,
+ k_nSteamNetworkingSend_Reliable, NULL );
+}
+
+static void network_scoreboard_callback( netmsg_request *res, vg_msg *body,
+ u64 userdata ){
+ world_instance *world = world_current_instance();
+
+ world_routes_recv_scoreboard( world, body, userdata, res->status );
+ if( userdata == world_sfd.active_route_board )
+ world_sfd_compile_active_scores();
+}
+
+
+
+/* mod_uid: world mod uid,
+ * route_uid: run name (just a string)
+ * week:
+ * 0 ALL TIME
+ * 1 CURRENT WEEK
+ * 2 ALL TIME + CURRENT WEEK
+ * .
+ * 10+ specific week index
+ */
+void network_request_scoreboard( const char *mod_uid,
+ const char *route_uid,
+ u32 week, u64 userdata ){
+ if( !network_connected() )
+ return;
+
+ netmsg_request *req = alloca( sizeof(netmsg_request) + 512 );
+ req->inetmsg_id = k_inetmsg_request;
+
+ vg_msg data;
+ vg_msg_init( &data, req->q, 512 );
+ vg_msg_wkvstr( &data, "endpoint", "scoreboard" );
+ vg_msg_wkvstr( &data, "mod", mod_uid );
+ vg_msg_wkvstr( &data, "route", route_uid );
+ vg_msg_wkvnum( &data, "week", k_vg_msg_u32, 1, &week );
+ network_send_request( req, &data, network_scoreboard_callback, userdata );
+}
+
+static void network_publish_callback( netmsg_request *res, vg_msg *body,
+ u64 userdata ){
+ if( res->status != k_request_status_ok ){
+ vg_error( "Publish laptime, server error #%d\n", (i32)res->status );
+ }
+}
+
+void network_publish_laptime( const char *mod_uid,
+ const char *route_uid, f64 lap_time ){
+ if( !network_connected() )
+ return;
+
+ i32 time_centiseconds = lap_time * 100.0;
+
+ netmsg_request *req = alloca( sizeof(netmsg_request) + 512 );
+ req->inetmsg_id = k_inetmsg_request;
+
+ vg_msg data;
+ vg_msg_init( &data, req->q, 512 );
+ vg_msg_wkvstr( &data, "endpoint", "setlap" );
+ vg_msg_wkvstr( &data, "mod", mod_uid );
+ vg_msg_wkvstr( &data, "route", route_uid );
+ vg_msg_wkvnum( &data, "time", k_vg_msg_i32, 1, &time_centiseconds );
+ network_send_request( req, &data, network_publish_callback, 0 );
+}
+
+static void network_request_rx_300_400( SteamNetworkingMessage_t *msg ){
+ netmsg_blank *tmp = msg->m_pData;
+
+ if( tmp->inetmsg_id == k_inetmsg_request ){
+
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_response ){
+ netmsg_request *res = (netmsg_request *)msg->m_pData;
+
+ vg_msg *body = NULL;
+
+ vg_msg data;
+ if( res->status == k_request_status_ok ){
+ vg_msg_init( &data, res->q, msg->m_cbSize - sizeof(netmsg_request) );
+ vg_success( "Response to #%d:\n", (i32)res->id );
+ vg_msg_print( &data, data.max );
+ body = &data;
+ }
+ else {
+ vg_warn( "Server response to #%d: %d\n", (i32)res->id, res->status );
+ }
+
+ if( res->id ){
+ struct network_request *pn =
+ vg_pool_item( &network_client.request_pool, res->id );
+ pn->callback( res, body, pn->userdata );
+ vg_pool_unwatch( &network_client.request_pool, res->id );
+ }
+ }
+}
+
+void network_send_item( enum netmsg_playeritem_type type )
+{
+ if( !network_connected() )
+ return;
+
+ netmsg_playeritem *item =
+ alloca( sizeof(netmsg_playeritem) + ADDON_UID_MAX );
+ item->inetmsg_id = k_inetmsg_playeritem;
+ item->type_index = type;
+ item->client = 0;
+
+ if( (type == k_netmsg_playeritem_world0) ||
+ (type == k_netmsg_playeritem_world1) ){
+
+ enum world_purpose purpose = type - k_netmsg_playeritem_world0;
+ addon_reg *reg = world_static.instance_addons[ purpose ];
+
+ if( reg )
+ addon_alias_uid( ®->alias, item->uid );
+ else
+ item->uid[0] = '\0';
+ }
+ else{
+ u16 view_id = 0;
+ enum addon_type addon_type = k_addon_type_none;
+ if( type == k_netmsg_playeritem_board ){
+ view_id = localplayer.board_view_slot;
+ addon_type = k_addon_type_board;
+ }
+ else if( type == k_netmsg_playeritem_player ){
+ view_id = localplayer.playermodel_view_slot;
+ addon_type = k_addon_type_player;
+ }
+
+ struct addon_cache *cache = &addon_system.cache[addon_type];
+ vg_pool *pool = &cache->pool;
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+ addon_cache_entry *entry = vg_pool_item( pool, view_id );
+ addon_alias_uid( &entry->reg_ptr->alias, item->uid );
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+ }
+
+ vg_info( "send equip: [%u] %s\n",
+ item->type_index, item->uid );
+ u32 chs = strlen(item->uid);
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote,
+ item, sizeof(netmsg_playeritem)+chs+1,
+ k_nSteamNetworkingSend_Reliable, NULL );
+}
+
+static void network_disconnect(void){
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, network_client.remote, 0, NULL, 0 );
+ network_client.remote = 0;
+ network_client.state = k_ESteamNetworkingConnectionState_None;
+
+ for( int i=0; i<VG_ARRAY_LEN(netplayers.list); i++ ){
+ netplayers.list[i].active = 0;
+ }
+}
+
+void network_status_string( vg_str *str, u32 *colour )
+{
+ if( skaterift.demo_mode ){
+ vg_strcat( str, "Offline" );
+ return;
+ }
+
+ if( steam_ready ){
+ if( network_client.user_intent == k_server_intent_offline ){
+ vg_strcat( str, "Offline" );
+ }
+ else {
+ ESteamNetworkingConnectionState state = network_client.state;
+
+ if( state == k_ESteamNetworkingConnectionState_None )
+ vg_strcat( str, "No Connection" );
+ else if( state == k_ESteamNetworkingConnectionState_Connecting )
+ {
+ vg_strcatf( str, "Connecting...\n%s", network_client.host_adress );
+
+ if( network_client.retries ){
+ vg_strcat( str, "\n(" );
+ vg_strcati32( str, network_client.retries );
+ vg_strcat( str, " retries)" );
+ }
+ }
+ else if( state == k_ESteamNetworkingConnectionState_Connected ){
+ vg_strcatf( str, "Connected to:\n%s", network_client.host_adress );
+ *colour = 0xff00a020;
+ }
+ else if( state == k_ESteamNetworkingConnectionState_ClosedByPeer )
+ vg_strcat( str, "Connection Closed" );
+ else if( state == k_ESteamNetworkingConnectionState_FindingRoute )
+ vg_strcat( str, "Finding Route" );
+ else if( state ==
+ k_ESteamNetworkingConnectionState_ProblemDetectedLocally){
+ vg_strcat( str, "Problem Detected\nLocally" );
+ *colour = 0xff0000a0;
+ }
+ else
+ vg_strcat( str, "???" );
+ }
+ }
+ else {
+ vg_strcat( str, "Steam Offline" );
+ *colour = 0xff0000a0;
+ }
+}
+
+void render_server_status_gui(void)
+{
+ vg_framebuffer_bind( g_render.fb_network_status, 1.0f );
+
+ vg_ui_set_screen( 128, 48 );
+ ui_context *ctx = &vg_ui.ctx;
+
+ /* HACK */
+ ctx->cur_vert = 0;
+ ctx->cur_indice = 0;
+ ctx->vert_start = 0;
+ ctx->indice_start = 0;
+
+ ui_rect r = { 0, 0, 128, 48 };
+
+ char buf[128];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+
+ u32 bg = 0xff000000;
+ network_status_string( &str, &bg );
+
+ ui_fill( ctx, r, bg );
+ ui_text( ctx, r, buf, 1, k_ui_align_center, 0 );
+ ui_flush( ctx, k_ui_shader_colour, NULL );
+
+ skaterift.rt_textures[ k_skaterift_rt_server_status ] =
+ g_render.fb_network_status->attachments[0].id;
+}
+
+static void on_server_connect_status( CallbackMsg_t *msg ){
+ SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
+ vg_info( " Connection status changed for %lu\n", info->m_hConn );
+ vg_info( " %s -> %s\n",
+ string_ESteamNetworkingConnectionState(info->m_eOldState),
+ string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
+
+ if( info->m_hConn == network_client.remote ){
+ network_client.state = info->m_info.m_eState;
+
+ if( info->m_info.m_eState ==
+ k_ESteamNetworkingConnectionState_Connected ){
+ vg_success(" Connected to remote server.. authenticating\n");
+
+ /* send version info to server */
+ netmsg_version version;
+ version.inetmsg_id = k_inetmsg_version;
+ version.version = NETWORK_SKATERIFT_VERSION;
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote, &version,
+ sizeof(netmsg_version), k_nSteamNetworkingSend_Reliable, NULL );
+
+ /* TODO: We should really wait to see if the server is in auth mode
+ * first... */
+ u32 size = sizeof(netmsg_auth) + network_client.app_key_length;
+ netmsg_auth *auth = alloca(size);
+ auth->inetmsg_id = k_inetmsg_auth;
+ auth->ticket_length = network_client.app_key_length;
+ for( int i=0; i<network_client.app_key_length; i++ )
+ auth->ticket[i] = network_client.app_symmetric_key[i];
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote, auth, size,
+ k_nSteamNetworkingSend_Reliable, NULL );
+ }
+ else if( info->m_info.m_eState ==
+ k_ESteamNetworkingConnectionState_ClosedByPeer ){
+
+ if( info->m_info.m_eEndReason ==
+ k_ESteamNetConnectionEnd_Misc_InternalError ){
+ network_client.retries = 40;
+ }
+ network_disconnect();
+ }
+ else if( info->m_info.m_eState ==
+ k_ESteamNetworkingConnectionState_ProblemDetectedLocally ){
+ network_disconnect();
+ }
+ }
+ else{
+ //vg_warn( " Recieved signal from unknown connection\n" );
+ }
+
+ render_server_status_gui();
+}
+
+static void on_persona_state_change( CallbackMsg_t *msg ){
+ if( !network_connected() )
+ return;
+
+ PersonaStateChange_t *info = (void *)msg->m_pubParam;
+ ISteamUser *hSteamUser = SteamAPI_SteamUser();
+
+ vg_info( "User: %llu, change: %u\n", info->m_ulSteamID,
+ info->m_nChangeFlags );
+
+ if( info->m_ulSteamID == SteamAPI_ISteamUser_GetSteamID(hSteamUser) ){
+ if( info->m_nChangeFlags & k_EPersonaChangeName ){
+ network_send_username();
+ }
+ }
+
+ if( info->m_nChangeFlags & k_EPersonaChangeRelationshipChanged ){
+ for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
+ struct network_player *rp = &netplayers.list[i];
+ if( rp->steamid == info->m_ulSteamID ){
+ player_remote_update_friendflags( rp );
+ }
+ }
+ }
+}
+
+void network_set_host( const char *host_str, const char *port_str )
+{
+ vg_strncpy( host_str, network_client.host_adress,
+ sizeof(network_client.host_adress), k_strncpy_overflow_fatal );
+
+ memset( &network_client.ip, 0, sizeof(network_client.ip) );
+ network_client.ip_resolved = 0;
+
+ if( port_str )
+ {
+ vg_strncpy( port_str, network_client.host_port,
+ sizeof(network_client.host_port), k_strncpy_overflow_fatal );
+ }
+ else
+ {
+ vg_str str;
+ vg_strnull( &str, network_client.host_port,
+ sizeof(network_client.host_port) );
+ vg_strcati32( &str, NETWORK_PORT );
+ }
+
+ network_client.ip.m_port = atoi( network_client.host_port );
+}
+
+static void network_connect(void)
+{
+ VG_ASSERT( network_client.ip_resolved );
+
+ vg_info( "connecting...\n" );
+ network_client.remote = SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(
+ hSteamNetworkingSockets, &network_client.ip, 0, NULL );
+}
+
+static void network_sign_on_complete(void){
+ vg_success( "Sign on completed\n" );
+
+ /* send our init info */
+ network_send_username();
+ for( u32 i=0; i<k_netmsg_playeritem_max; i ++ ){
+ network_send_item(i);
+ }
+ network_send_region();
+}
+
+static void poll_remote_connection(void){
+ SteamNetworkingMessage_t *messages[32];
+ int len;
+
+ for( int i=0; i<10; i++ ){
+ len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(
+ hSteamNetworkingSockets, network_client.remote,
+ messages, VG_ARRAY_LEN(messages));
+
+ if( len <= 0 )
+ return;
+
+ for( int i=0; i<len; i++ ){
+ SteamNetworkingMessage_t *msg = messages[i];
+
+ if( msg->m_cbSize < sizeof(netmsg_blank) ){
+ vg_warn( "Discarding message (too small: %d)\n", msg->m_cbSize );
+ continue;
+ }
+
+ netmsg_blank *tmp = msg->m_pData;
+
+ if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) ){
+ player_remote_rx_200_300( msg );
+ }
+ else if( (tmp->inetmsg_id >= 300) && (tmp->inetmsg_id < 400) ){
+ network_request_rx_300_400( msg );
+ }
+ else {
+ if( tmp->inetmsg_id == k_inetmsg_version ){
+ netmsg_version *version = msg->m_pData;
+ if( version->version != NETWORK_SKATERIFT_VERSION ){
+ network_disconnect();
+ /* we dont want to connect to this server ever */
+ network_client.retries = 999;
+ network_client.last_attempt = 999999999.9;
+ vg_error( "version mismatch with server\n" );
+ }
+ else {
+ network_client.remote_version = version->version;
+ network_sign_on_complete();
+ }
+ }
+ }
+
+ SteamAPI_SteamNetworkingMessage_t_Release( msg );
+ }
+ }
+}
+
+static void network_resolve_host_async( void *payload, u32 size )
+{
+ u32 *status = payload;
+ network_client.ip_resolved = *status;
+
+ char buf[256];
+ SteamAPI_SteamNetworkingIPAddr_ToString( &network_client.ip, buf, 256, 1 );
+ vg_info( "Resolved host address to: %s\n", buf );
+}
+
+static void network_resolve_host_thread( void *_ )
+{
+ vg_async_item *call = vg_async_alloc(8);
+ u32 *status = call->payload;
+ *status = 0;
+
+ if( (network_client.host_adress[0] >= '0') &&
+ (network_client.host_adress[0] <= '9') )
+ {
+ SteamAPI_SteamNetworkingIPAddr_ParseString(
+ &network_client.ip,
+ network_client.host_adress );
+ network_client.ip.m_port = atoi( network_client.host_port );
+ *status = 1;
+ goto end;
+ }
+
+ vg_info( "Resolving host.. %s (:%s)\n",
+ network_client.host_adress, network_client.host_port );
+
+ struct addrinfo hints;
+ struct addrinfo *result;
+
+ /* Obtain address(es) matching host/port. */
+
+ memset( &hints, 0, sizeof(hints) );
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
+ hints.ai_protocol = 0;
+
+ int s = getaddrinfo( network_client.host_adress, network_client.host_port,
+ &hints, &result);
+ if( s != 0 )
+ {
+#ifndef _WIN32
+ vg_error( "getaddrinfo: %s\n", gai_strerror(s) );
+#endif
+
+ if( !strcmp( network_client.host_adress, "skaterift.com" ) )
+ {
+ vg_warn( "getaddrinfo failed for skaterift.com;\n "
+ "falling back to a hardcoded IPv4\n" );
+ strcpy( network_client.host_adress, "46.101.34.155" );
+ SteamAPI_SteamNetworkingIPAddr_ParseString(
+ &network_client.ip,
+ network_client.host_adress );
+ network_client.ip.m_port = NETWORK_PORT;
+ *status = 1;
+ }
+
+ goto end;
+ }
+
+ struct sockaddr_in6 *inaddr = (struct sockaddr_in6 *)result->ai_addr;
+ memcpy( network_client.ip.m_ipv6, &inaddr->sin6_addr, 16 );
+ freeaddrinfo( result );
+
+ *status = 1;
+
+end: vg_async_dispatch( call, network_resolve_host_async );
+}
+
+void network_update(void)
+{
+ if( !steam_ready )
+ return;
+
+ ESteamNetworkingConnectionState state = network_client.state;
+
+ if( network_client.user_intent == k_server_intent_offline )
+ {
+ if( state != k_ESteamNetworkingConnectionState_None )
+ network_disconnect();
+
+ return;
+ }
+
+ if( state == k_ESteamNetworkingConnectionState_Connected )
+ {
+ poll_remote_connection();
+ f64 frame_delta = vg.time_real - network_client.last_frame;
+
+ if( frame_delta > NETWORK_FRAMERATE )
+ {
+ network_client.last_frame = vg.time_real;
+ remote_player_send_playerframe();
+ localplayer.sfx_buffer_count = 0;
+ }
+
+ remote_player_debug_update();
+ }
+ else
+ {
+ if( (state == k_ESteamNetworkingConnectionState_Connecting) ||
+ (state == k_ESteamNetworkingConnectionState_FindingRoute) )
+ {
+ return;
+ }
+ else
+ {
+ f64 waited = vg.time_real - network_client.last_attempt,
+ min_wait = 1.0;
+
+ if( network_client.retries > 5 )
+ min_wait = 60.0;
+
+ if( waited < min_wait )
+ return;
+
+ if( !network_client.ip_resolved )
+ {
+ if( vg_loader_availible() )
+ {
+ vg_loader_start( network_resolve_host_thread, NULL );
+ }
+ else return;
+ }
+ else
+ network_connect();
+
+ network_client.retries ++;
+ network_client.last_attempt = vg.time_real;
+ }
+ }
+}
+
+void chat_send_message( const char *message )
+{
+ if( !network_connected() ){
+ return;
+ }
+
+ netmsg_chat *chat = alloca( sizeof(netmsg_chat) + NETWORK_MAX_CHAT );
+ chat->inetmsg_id = k_inetmsg_chat;
+ chat->client = 0;
+
+ u32 l = vg_strncpy( message, chat->msg, NETWORK_MAX_CHAT,
+ k_strncpy_always_add_null );
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote,
+ chat, sizeof(netmsg_chat)+l+1,
+ k_nSteamNetworkingSend_Reliable, NULL );
+}
+
+static int cmd_network_send_message( int argc, const char *argv[] ){
+ char buf[ NETWORK_MAX_CHAT ];
+ vg_str str;
+ vg_strnull( &str, buf, NETWORK_MAX_CHAT );
+
+ for( int i=0; i<argc; i ++ ){
+ vg_strcat( &str, argv[i] );
+
+ if( i < argc-1 )
+ vg_strcatch( &str, ' ' );
+ }
+
+ chat_send_message( buf );
+ return 0;
+}
+
+void network_init(void)
+{
+ vg_console_reg_var( "network_info", &network_client.network_info,
+ k_var_dtype_i32, VG_VAR_PERSISTENT );
+ vg_console_reg_var( "auto_connect", &network_client.auto_connect,
+ k_var_dtype_i32, VG_VAR_PERSISTENT );
+ if( steam_ready ){
+ u32 alloc_size = sizeof(struct network_request)*NETWORK_MAX_REQUESTS;
+ network_client.request_buffer =
+ vg_linear_alloc( vg_mem.rtmemory, alloc_size );
+ memset( network_client.request_buffer, 0, alloc_size );
+
+ vg_pool *pool = &network_client.request_pool;
+ pool->buffer = network_client.request_buffer;
+ pool->count = NETWORK_MAX_REQUESTS;
+ pool->stride = sizeof( struct network_request );
+ pool->offset = offsetof( struct network_request, poolnode );
+ vg_pool_init( pool );
+
+ steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
+ on_server_connect_status );
+ steam_register_callback( k_iPersonaStateChange,
+ on_persona_state_change );
+ request_auth_ticket();
+
+ vg_console_reg_cmd( "say", cmd_network_send_message, NULL );
+ }
+}
+
+void network_end(void)
+{
+ /* TODO: Send buffered highscores that were not already */
+ if( (network_client.state == k_ESteamNetworkingConnectionState_Connected) ||
+ (network_client.state == k_ESteamNetworkingConnectionState_Connecting) )
+ {
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets, network_client.remote, 0, NULL, 1 );
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * All trademarks are property of their respective owners
+ */
+
+#pragma once
+#include "vg/vg_platform.h"
+#include "vg/vg_steam_networking.h"
+#include "vg/vg_mem_pool.h"
+#include "vg/vg_msg.h"
+#include "steam.h"
+#include "network_common.h"
+#include "network_msg.h"
+#include "addon_types.h"
+
+#define NETWORK_MAX_REQUESTS 8
+
+/*
+ * Interface
+ */
+
+/* Call it at start; Connects us to the gameserver */
+void network_init(void);
+
+/* Run this from main loop */
+void network_update(void);
+
+/* Call it at shutdown */
+void network_end(void);
+
+/*
+ * Can buffer up a bunch of these by calling many times, they will be
+ * sent at the next connection
+ */
+void network_submit_highscore( u32 trackid, u16 points, u16 time );
+
+/*
+ * Game endpoints are provided with the same names to allow running without a
+ * network connection.
+ */
+
+struct network_client
+{
+ u8 app_symmetric_key[ 1024 ];
+ u32 app_key_length;
+ EServerMode auth_mode;
+
+ HSteamNetConnection remote;
+ ESteamNetworkingConnectionState state;
+ u32 remote_version;
+
+ f64 last_attempt, last_frame;
+ u32 retries;
+
+ i32 network_info;
+ i32 auto_connect;
+
+ struct network_request {
+ vg_pool_node poolnode;
+ void (*callback)( netmsg_request *res, vg_msg *body, u64 userdata );
+ f64 sendtime;
+ u64 userdata;
+ }
+ *request_buffer;
+ vg_pool request_pool;
+
+ SteamNetworkingIPAddr ip;
+ char host_port[8], host_adress[256];
+ bool ip_resolved;
+
+ enum server_intent {
+ k_server_intent_offline,
+ k_server_intent_online
+ }
+ user_intent;
+ f64 last_intent_change;
+ f32 fintent; /* yeah this shit really shouldnt be here but oh well */
+}
+extern network_client;
+
+int packet_minsize( SteamNetworkingMessage_t *msg, u32 size );
+void network_send_item( enum netmsg_playeritem_type type );
+void network_request_scoreboard( const char *mod_uid,
+ const char *route_uid,
+ u32 week, u64 userdata );
+void network_publish_laptime( const char *mod_uid,
+ const char *route_uid, f64 lap_time );
+void chat_send_message( const char *message );
+void render_server_status_gui(void);
+void network_status_string( vg_str *str, u32 *colour );
+void network_send_region(void);
+void network_set_host( const char *host_str, const char *port_str );
+
+static inline int network_connected(void)
+{
+ if( network_client.remote_version != NETWORK_SKATERIFT_VERSION ) return 0;
+ return network_client.state == k_ESteamNetworkingConnectionState_Connected;
+}
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+#include "vg/vg_string.h"
+
+#define NETWORK_USERNAME_MAX 32
+#define NETWORK_MAX_PLAYERS 20
+#define NETWORK_FRAMERATE 0.1
+#define NETWORK_BUFFERFRAMES 6
+#define NETWORK_MAX_CHAT 128
+#define NETWORK_REGION_MAX 32
+#define NETWORK_SKATERIFT_VERSION 10
+#define NETWORK_REQUEST_MAX 2048
+
+#define NETWORK_LEADERBOARD_ALLTIME 0
+#define NETWORK_LEADERBOARD_CURRENT_WEEK 1
+#define NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK 2
+#define NETWORK_PORT 27403
+#define NETWORK_PORT_STR(STR, X) STR #X
+
+#include "addon_types.h"
+
+static u32 network_msgstring( const char *src,
+ u32 m_cbSize, u32 base_size,
+ char *buf, u32 buf_size ){
+
+ u32 string_len = VG_MIN( m_cbSize - base_size, buf_size );
+ return vg_strncpy( src, buf, string_len, k_strncpy_always_add_null );
+}
+
+static u32 network_pair_index( u32 _a, u32 _b ){
+ const u32 N = NETWORK_MAX_PLAYERS;
+
+ if( !((_a != _b) && (_a<N) && (_b<N) ) )
+ {
+ vg_fatal_error( "Programming error\n" );
+ }
+
+ u32 a = VG_MIN( _a, _b ),
+ b = VG_MAX( _a, _b );
+
+ return ((N-a)*((N-a)-1))/2 - b + a;
+}
--- /dev/null
+#ifndef NETWORK_COMPRESSION_H
+#define NETWORK_COMPRESSION_H
+
+#include "vg/vg_platform.h"
+#include "vg/vg_m.h"
+
+typedef struct bitpack_ctx bitpack_ctx;
+struct bitpack_ctx {
+ enum bitpack_mode {
+ k_bitpack_compress,
+ k_bitpack_decompress
+ }
+ mode;
+
+ u8 *buffer;
+ u32 bytes, buffer_len;
+};
+
+static void bitpack_bytes( bitpack_ctx *ctx, u32 bytes, void *data ){
+ u8 *ext = data;
+ for( u32 i=0; i<bytes; i++ ){
+ u32 index = ctx->bytes+i;
+ if( ctx->mode == k_bitpack_compress ){
+ if( index < ctx->buffer_len )
+ ctx->buffer[index] = ext[i];
+ }
+ else{
+ if( index < ctx->buffer_len )
+ ext[i] = ctx->buffer[index];
+ else
+ ext[i] = 0x00;
+ }
+ }
+ ctx->bytes += bytes;
+}
+
+static u32 bitpack_qf32( bitpack_ctx *ctx, u32 bits,
+ f32 min, f32 max, f32 *v ){
+ u32 mask = (0x1 << bits) - 1;
+
+ if( ctx->mode == k_bitpack_compress ){
+ u32 a = vg_quantf( *v, bits, min, max );
+ bitpack_bytes( ctx, bits/8, &a );
+ return a;
+ }
+ else {
+ u32 a = 0;
+ bitpack_bytes( ctx, bits/8, &a );
+ *v = vg_dequantf( a, bits, min, max );
+ return a;
+ }
+}
+
+static void bitpack_qv2f( bitpack_ctx *ctx, u32 bits,
+ f32 min, f32 max, v2f v ){
+ for( u32 i=0; i<2; i ++ )
+ bitpack_qf32( ctx, bits, min, max, v+i );
+}
+
+static void bitpack_qv3f( bitpack_ctx *ctx, u32 bits,
+ f32 min, f32 max, v3f v ){
+ for( u32 i=0; i<3; i ++ )
+ bitpack_qf32( ctx, bits, min, max, v+i );
+}
+
+static void bitpack_qv4f( bitpack_ctx *ctx, u32 bits,
+ f32 min, f32 max, v4f v ){
+ for( u32 i=0; i<4; i ++ )
+ bitpack_qf32( ctx, bits, min, max, v+i );
+}
+
+static void bitpack_qquat( bitpack_ctx *ctx, v4f quat ){
+ const f32 k_domain = 0.70710678118f;
+
+ if( ctx->mode == k_bitpack_compress ){
+ v4f qabs;
+ for( u32 i=0; i<4; i++ )
+ qabs[i] = fabsf(quat[i]);
+
+ u32 lxy = qabs[1]>qabs[0],
+ lzw = (qabs[3]>qabs[2])+2,
+ l = qabs[lzw]>qabs[lxy]? lzw: lxy;
+
+ f32 sign = vg_signf(quat[l]);
+
+ u32 smallest[3];
+ for( u32 i=0, j=0; i<4; i ++ )
+ if( i != l )
+ smallest[j ++] = vg_quantf( quat[i]*sign, 10, -k_domain, k_domain );
+
+ u32 comp = (smallest[0]<<2) | (smallest[1]<<12) | (smallest[2]<<22) | l;
+ bitpack_bytes( ctx, 4, &comp );
+ }
+ else {
+ u32 comp;
+ bitpack_bytes( ctx, 4, &comp );
+
+ u32 smallest[3] = {(comp>>2 )&0x3ff,
+ (comp>>12)&0x3ff,
+ (comp>>22)&0x3ff},
+ l = comp & 0x3;
+
+ f32 m = 1.0f;
+
+ for( u32 i=0, j=0; i<4; i ++ ){
+ if( i != l ){
+ quat[i] = vg_dequantf( smallest[j ++], 10, -k_domain, k_domain );
+ m -= quat[i]*quat[i];
+ }
+ }
+
+ quat[l] = sqrtf(m);
+ q_normalize( quat );
+ }
+}
+
+#endif /* NETWORK_COMPRESSION_H */
--- /dev/null
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef NETWORK_MSG_H
+#define NETWORK_MSG_H
+
+#include "world_info.h"
+#include "vg/vg_platform.h"
+;
+
+#pragma pack(push,1)
+
+typedef struct netmsg_blank netmsg_blank;
+enum{ k_inetmsg_blank = 0 };
+struct netmsg_blank{
+ u16 inetmsg_id;
+};
+
+/* send after version */
+typedef struct netmsg_auth netmsg_auth;
+enum{ k_inetmsg_auth = 1 };
+struct netmsg_auth
+{
+ u16 inetmsg_id;
+
+ u32 ticket_length;
+ u8 ticket[];
+};
+
+/* version should be sent before auth */
+typedef struct netmsg_version netmsg_version;
+enum{ k_inetmsg_version = 2 };
+struct netmsg_version{
+ u16 inetmsg_id;
+ u32 version;
+};
+
+/* server control 100 */
+
+/* player updates 200 */
+
+#define NETMSG_BOUNDARY_BIT 0x8000
+#define NETMSG_GATE_BOUNDARY_BIT 0x4000
+#define NETMSG_BOUNDARY_MASK (NETMSG_BOUNDARY_BIT|NETMSG_GATE_BOUNDARY_BIT)
+#define NETMSG_PLAYERFRAME_INSTANCE_ID 0x3
+#define NETMSG_PLAYERFRAME_HAVE_GLIDER 0x4
+#define NETMSG_PLAYERFRAME_GLIDER_ORPHAN 0x8
+
+typedef struct netmsg_playerframe netmsg_playerframe;
+enum{ k_inetmsg_playerframe = 200 };
+struct netmsg_playerframe{
+ u16 inetmsg_id;
+ f64 timestamp;
+
+ u8 client, subsystem,
+ flags, sound_effects;
+ u16 boundary_hash; /* used for animating correctly through gates, teleport..
+ msb is a flip flop for teleporting
+ second msb is flip flop for gate */
+
+ u8 animdata[];
+};
+
+typedef struct netmsg_playerjoin netmsg_playerjoin;
+enum{ k_inetmsg_playerjoin = 201 };
+struct netmsg_playerjoin{
+ u16 inetmsg_id;
+ u8 index;
+ u64 steamid;
+};
+
+typedef struct netmsg_playerleave netmsg_playerleave;
+enum{ k_inetmsg_playerleave = 202 };
+struct netmsg_playerleave{
+ u16 inetmsg_id;
+ u8 index;
+};
+
+typedef struct netmsg_playerusername netmsg_playerusername;
+enum{ k_inetmsg_playerusername = 203 };
+struct netmsg_playerusername{
+ u16 inetmsg_id;
+ u8 index;
+ char name[];
+};
+
+typedef struct netmsg_playeritem netmsg_playeritem;
+enum{ k_inetmsg_playeritem = 204 };
+struct netmsg_playeritem{
+ u16 inetmsg_id;
+ u8 client;
+ u8 type_index;
+ char uid[];
+};
+enum netmsg_playeritem_type {
+ k_netmsg_playeritem_board = 0,
+ k_netmsg_playeritem_player,
+ k_netmsg_playeritem_world0,
+ k_netmsg_playeritem_world1,
+ k_netmsg_playeritem_max
+};
+
+typedef struct netmsg_chat netmsg_chat;
+enum{ k_inetmsg_chat = 205 };
+struct netmsg_chat {
+ u16 inetmsg_id;
+ u8 client;
+ char msg[];
+};
+
+typedef struct netmsg_region netmsg_region;
+enum{ k_inetmsg_region = 206 };
+struct netmsg_region {
+ u16 inetmsg_id;
+ u8 client;
+ u32 flags;
+ char loc[];
+};
+
+/* requests 300 */
+typedef struct netmsg_request netmsg_request;
+enum{ k_inetmsg_request = 300, k_inetmsg_response = 301 };
+struct netmsg_request {
+ u16 inetmsg_id;
+ u8 id, status;
+ u8 q[];
+};
+
+enum request_status {
+ k_request_status_client_error = 0,
+ k_request_status_invalid_endpoint = 1,
+ k_request_status_unauthorized = 2,
+
+ k_request_status_server_error = 100,
+ k_request_status_out_of_memory = 101,
+ k_request_status_database_error = 102,
+
+ k_request_status_ok = 200,
+ k_request_status_not_found = 201
+};
+
+#pragma pack(pop)
+#endif /* NETWORK_MSG_H */
--- /dev/null
+#include "vg/vg_lines.h"
+#include "vg/vg_async.h"
+#include "particle.h"
+#include "shaders/particle.h"
+
+struct particle_system particles_grind = {
+ .scale = 0.02f,
+ .velocity_scale = 0.001f,
+ .width = 0.0125f
+},
+particles_env = {
+ .scale = 0.04f,
+ .velocity_scale = 0.001f,
+ .width = 0.25f
+};
+
+void particle_spawn( particle_system *sys, v3f co, v3f v,
+ f32 lifetime, u32 colour )
+{
+ if( sys->alive == sys->max ) return;
+
+ particle *p = &sys->array[ sys->alive ++ ];
+ v3_copy( co, p->co );
+ v3_copy( v, p->v );
+ p->life = lifetime;
+ p->colour = colour;
+}
+
+void particle_spawn_cone( particle_system *sys,
+ v3f co, v3f dir, f32 angle, f32 speed,
+ f32 lifetime, u32 colour )
+{
+ if( sys->alive == sys->max ) return;
+
+ particle *p = &sys->array[ sys->alive ++ ];
+
+ v3f tx, ty;
+ v3_tangent_basis( dir, tx, ty );
+
+ v3f rand;
+ vg_rand_cone( &vg.rand, rand, angle );
+ v3_muls( tx, rand[0]*speed, p->v );
+ v3_muladds( p->v, ty, rand[1]*speed, p->v );
+ v3_muladds( p->v, dir, rand[2]*speed, p->v );
+
+ p->life = lifetime;
+ p->colour = colour;
+ v3_copy( co, p->co );
+}
+
+void particle_system_update( particle_system *sys, f32 dt )
+{
+ u32 i = 0;
+iter: if( i == sys->alive ) return;
+
+ particle *p = &sys->array[i];
+ p->life -= dt;
+
+ if( p->life < 0.0f ){
+ *p = sys->array[ -- sys->alive ];
+ goto iter;
+ }
+
+ v3_muladds( p->co, p->v, dt, p->co );
+ p->v[1] += -9.8f * dt;
+
+ i ++;
+ goto iter;
+}
+
+void particle_system_debug( particle_system *sys )
+{
+ for( u32 i=0; i<sys->alive; i ++ ){
+ particle *p = &sys->array[i];
+ v3f p1;
+ v3_muladds( p->co, p->v, 0.2f, p1 );
+ vg_line( p->co, p1, p->colour );
+ }
+}
+
+struct particle_init_args {
+ particle_system *sys;
+ u16 indices[];
+};
+
+static void async_particle_init( void *payload, u32 size ){
+ struct particle_init_args *args = payload;
+ particle_system *sys = args->sys;
+
+ glGenVertexArrays( 1, &sys->vao );
+ glGenBuffers( 1, &sys->vbo );
+ glGenBuffers( 1, &sys->ebo );
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(particle_vert);
+
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferData( GL_ARRAY_BUFFER, sys->max*stride*4, NULL, GL_DYNAMIC_DRAW );
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, sys->ebo );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER,
+ sys->max*sizeof(u16)*6, args->indices, GL_STATIC_DRAW );
+
+ /* 0: coordinates */
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ /* 3: colour */
+ glVertexAttribPointer( 1, 4, GL_UNSIGNED_BYTE, GL_TRUE,
+ stride, (void *)offsetof(particle_vert, colour) );
+ glEnableVertexAttribArray( 1 );
+}
+
+void particle_alloc( particle_system *sys, u32 max )
+{
+ size_t stride = sizeof(particle_vert);
+
+ sys->max = max;
+ sys->array = vg_linear_alloc( vg_mem.rtmemory, max*sizeof(particle) );
+ sys->vertices = vg_linear_alloc( vg_mem.rtmemory, max*stride*4 );
+
+ vg_async_item *call =
+ vg_async_alloc( sizeof(particle_system *) + max*sizeof(u16)*6 );
+ struct particle_init_args *init = call->payload;
+ init->sys = sys;
+
+ for( u32 i=0; i<max; i ++ ){
+ init->indices[i*6+0] = i*4;
+ init->indices[i*6+1] = i*4+1;
+ init->indices[i*6+2] = i*4+2;
+ init->indices[i*6+3] = i*4;
+ init->indices[i*6+4] = i*4+2;
+ init->indices[i*6+5] = i*4+3;
+ }
+
+ vg_async_dispatch( call, async_particle_init );
+}
+
+void particle_system_prerender( particle_system *sys )
+{
+ for( u32 i=0; i<sys->alive; i ++ ){
+ particle *p = &sys->array[i];
+ particle_vert *vs = &sys->vertices[i*4];
+
+ v3f v, right;
+ v3_copy( p->v, v );
+
+ f32 vm = v3_length( p->v );
+ v3_muls( v, 1.0f/vm, v );
+ v3_cross( v, (v3f){0,1,0}, right );
+
+ f32 l = (sys->scale+sys->velocity_scale*vm),
+ w = sys->width;
+
+ v3f p0, p1;
+ v3_muladds( p->co, p->v, l, p0 );
+ v3_muladds( p->co, p->v, -l, p1 );
+
+ v3_muladds( p0, right, w, vs[0].co );
+ v3_muladds( p1, right, w, vs[1].co );
+ v3_muladds( p1, right, -w, vs[2].co );
+ v3_muladds( p0, right, -w, vs[3].co );
+
+ vs[0].colour = p->colour;
+ vs[1].colour = p->colour;
+ vs[2].colour = p->colour;
+ vs[3].colour = p->colour;
+ }
+
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(particle_vert);
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferSubData( GL_ARRAY_BUFFER, 0, sys->alive*stride*4, sys->vertices );
+}
+
+void particle_system_render( particle_system *sys, vg_camera *cam )
+{
+ glDisable( GL_CULL_FACE );
+ glEnable( GL_DEPTH_TEST );
+
+ shader_particle_use();
+ shader_particle_uPv( cam->mtx.pv );
+ shader_particle_uPvPrev( cam->mtx_prev.pv );
+
+ glBindVertexArray( sys->vao );
+ glDrawElements( GL_TRIANGLES, sys->alive*6, GL_UNSIGNED_SHORT, NULL );
+}
--- /dev/null
+#pragma once
+#include "skaterift.h"
+
+typedef struct particle_system particle_system;
+typedef struct particle particle;
+typedef struct particle_vert particle_vert;
+
+struct particle_system {
+ struct particle {
+ v3f co, v;
+ f32 life;
+ u32 colour;
+ }
+ *array;
+
+#pragma pack(push,1)
+ struct particle_vert {
+ v3f co;
+ u32 colour;
+ }
+ *vertices;
+#pragma pack(pop)
+
+ u32 alive, max;
+ GLuint vao, vbo, ebo;
+
+ /* render settings */
+ f32 scale, velocity_scale, width;
+}
+extern particles_grind, particles_env;
+
+void particle_alloc( particle_system *sys, u32 max );
+void particle_system_update( particle_system *sys, f32 dt );
+void particle_system_debug( particle_system *sys );
+void particle_system_prerender( particle_system *sys );
+void particle_system_render( particle_system *sys, vg_camera *cam );
+
+void particle_spawn( particle_system *sys,
+ v3f co, v3f v, f32 lifetime, u32 colour );
+void particle_spawn_cone( particle_system *sys,
+ v3f co, v3f dir, f32 angle, f32 speed,
+ f32 lifetime, u32 colour );
--- /dev/null
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef PHYSICS_TEST_H
+#define PHYSICS_TEST_H
+
+#include "rigidbody.h"
+#include "player.h"
+
+rigidbody ground = { .type = k_rb_shape_box,
+ .bbx = {{-100.0f,-1.0f,-100.0f},{100.0f,0.0f,100.0f}},
+ .co = {0.0f, 0.0f, 0.0f},
+ .q = {0.0f,0.0f,0.0f,1.0f},
+ .is_world = 1 };
+
+rigidbody blocky =
+ {
+ .type = k_rb_shape_box,
+ .bbx = {{-2.0f,-1.0f,-3.0f},{2.0f,1.0f,2.0f}},
+ .co = {30.0f,2.0f,30.0f},
+ .q = {0.0f,0.0f,0.0f,1.0f},
+ .is_world = 1
+ };
+
+rigidbody marko =
+{
+ .type = k_rb_shape_box,
+ .bbx = {{-0.5f,-0.5f,-0.5f},{0.5f,0.5f,0.5f}},
+ .co = {-36.0f,8.0f,-36.0f},
+ .q = {0.0f,0.0f,0.0f,1.0f},
+ .is_world = 0
+};
+
+scene epic_scene;
+
+rigidbody epic_scene_rb =
+{
+ .type = k_rb_shape_scene,
+ .co = {0.0f,0.0f,0.0f},
+ .q = {0.0f,0.0f,0.0f,1.0f},
+ .is_world = 1,
+ .inf.scene = { .pscene = &epic_scene }
+};
+
+rigidbody funnel[4] = {
+ {
+ .type = k_rb_shape_box,
+ .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
+ .co = {-10.0f,5.0f,0.0f},
+ .is_world = 1
+ },
+ {
+ .type = k_rb_shape_box,
+ .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
+ .co = { 10.0f,5.0f,0.0f},
+ .is_world = 1
+ },
+ {
+ .type = k_rb_shape_box,
+ .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
+ .co = { 0.0f,5.0f,10.0f},
+ .is_world = 1
+ },
+ {
+ .type = k_rb_shape_box,
+ .bbx = {{-20.0f,-1.0f,-20.0f},{20.0f,1.0f,20.0f}},
+ .co = {0.0f,5.0f,-10.0f},
+ .is_world = 1
+ }
+};
+
+rigidbody jeff1 = { .type = k_rb_shape_capsule,
+ .inf.capsule = { .radius = 0.75f, .height = 3.0f },
+ .co = {30.0f, 4.0f, 30.0f },
+ .q = {1.0f,0.0f,0.0f,0.0f}
+};
+
+rigidbody ball = { .type = k_rb_shape_sphere,
+ .inf.sphere = { .radius = 2.0f },
+ .co = {0.0f,20.0f,2.0f},
+ .q = {0.0f,0.0f,0.0f,1.0f}},
+
+ ball1= { .type = k_rb_shape_sphere,
+ .inf.sphere = { .radius = 2.0f },
+ .co = {0.1f,25.0f,0.2f},
+ .q = {0.0f,0.0f,0.0f,1.0f}};
+
+rigidbody jeffs[16];
+
+static void reorg_jeffs(void)
+{
+ for( int i=0; i<vg_list_size(jeffs); i++ )
+ {
+ v3_copy( (v3f){ (vg_randf()-0.5f) * 10.0f,
+ (vg_randf()-0.5f) * 10.0f + 17.0f,
+ (vg_randf()-0.5f) * 10.0f }, jeffs[i].co );
+ v4_copy( (v4f){ vg_randf(), vg_randf(), vg_randf(), vg_randf() },
+ jeffs[i].q );
+ q_normalize( jeffs[i].q );
+
+ jeffs[i].type = k_rb_shape_capsule;
+ jeffs[i].inf.capsule.radius = 0.75f;
+ jeffs[i].inf.capsule.height = 3.0f;
+
+ rb_init( &jeffs[i] );
+ }
+}
+
+static void physics_test_start(void)
+{
+ q_axis_angle( funnel[0].q, (v3f){1.0f,0.0f,0.0f}, 0.6f );
+ q_axis_angle( funnel[1].q, (v3f){1.0f,0.0f,0.0f}, -0.6f );
+ q_axis_angle( funnel[2].q, (v3f){0.0f,0.0f,1.0f}, 0.6f );
+ q_axis_angle( funnel[3].q, (v3f){0.0f,0.0f,1.0f}, -0.6f );
+
+ for( int i=0; i<4; i++ )
+ rb_init( &funnel[i] );
+
+ reorg_jeffs();
+
+ rb_init( &ground );
+ rb_init( &ball );
+ rb_init( &ball1 );
+ rb_init( &jeff1 );
+ rb_init( &blocky );
+
+ scene_init( &epic_scene );
+
+ mdl_header *mdl = mdl_load( "models/epic_scene.mdl" );
+
+ m4x3f transform;
+ m4x3_identity( transform );
+
+ for( int i=0; i<mdl->node_count; i++ )
+ {
+ mdl_node *pnode = mdl_node_from_id( mdl, i );
+
+ for( int j=0; j<pnode->submesh_count; j++ )
+ {
+ mdl_submesh *sm = mdl_node_submesh( mdl, pnode, j );
+ scene_add_submesh( &epic_scene, mdl, sm, transform );
+ }
+ }
+
+ vg_free( mdl );
+ scene_bh_create( &epic_scene );
+
+ rb_init( &epic_scene_rb );
+ rb_init( &marko );
+}
+
+static void physics_test_update(void)
+{
+ player_freecam();
+ player_camera_update();
+
+ for( int i=0; i<4; i++ )
+ rb_debug( &funnel[i], 0xff0060e0 );
+ rb_debug( &ground, 0xff00ff00 );
+ rb_debug( &ball, 0xffe00040 );
+ rb_debug( &ball1, 0xff00e050 );
+
+ rb_debug( &blocky, 0xffcccccc );
+ rb_debug( &jeff1, 0xff00ffff );
+
+ rb_debug( &epic_scene_rb, 0xffcccccc );
+ rb_debug( &marko, 0xffffcc00 );
+
+ {
+
+ rb_solver_reset();
+
+ for( int i=0; i<4; i++ )
+ {
+ rigidbody *fn = &funnel[i];
+ rb_collide( &ball, fn );
+ rb_collide( &ball1, fn );
+ rb_collide( &jeff1, fn );
+
+ for( int i=0; i<vg_list_size(jeffs); i++ )
+ rb_collide( jeffs+i, fn );
+ }
+
+ for( int i=0; i<vg_list_size(jeffs)-1; i++ )
+ {
+ for( int j=i+1; j<vg_list_size(jeffs); j++ )
+ {
+ rb_collide( jeffs+i, jeffs+j );
+ }
+ }
+
+ for( int i=0; i<vg_list_size(jeffs); i++ )
+ {
+ rb_collide( jeffs+i, &ground );
+ rb_collide( jeffs+i, &ball );
+ rb_collide( jeffs+i, &ball1 );
+ rb_collide( jeffs+i, &jeff1 );
+ }
+
+ rb_collide( &jeff1, &ground );
+ rb_collide( &jeff1, &blocky );
+ rb_collide( &jeff1, &ball );
+ rb_collide( &jeff1, &ball1 );
+
+ rb_collide( &ball, &ground );
+ rb_collide( &ball1, &ground );
+ rb_collide( &ball1, &ball );
+ rb_collide( &marko, &epic_scene_rb );
+
+ rb_presolve_contacts( rb_contact_buffer, rb_contact_count );
+ for( int i=0; i<8; i++ )
+ rb_solve_contacts( rb_contact_buffer, rb_contact_count );
+
+
+ /* ITERATE */
+ {
+ for( int i=0; i<vg_list_size(jeffs); i++ )
+ {
+ rb_debug( &jeffs[i], (u32[]){ 0xff0000ff, 0xff00ff00, 0xff00ffff,
+ 0xffff0000, 0xffff00ff, 0xffffff00,
+ }[i%6] );
+ rb_iter( jeffs+i );
+ }
+
+ rb_iter( &ball );
+ rb_iter( &ball1 );
+ rb_iter( &jeff1 );
+ rb_iter( &marko );
+ }
+
+ /* POSITION OVERRIDE */
+ {
+ if(glfwGetKey( vg.window, GLFW_KEY_L ))
+ {
+ m4x3_mulv( player.camera, (v3f){0.0f,0.0f,-5.0f}, marko.co );
+ v3_zero( marko.v );
+ v3_zero( marko.w );
+ }
+ if(glfwGetKey( vg.window, GLFW_KEY_K ))
+ {
+ m4x3_mulv( player.camera, (v3f){0.0f,0.0f,-5.0f}, ball.co );
+ v3_zero( ball.v );
+ v3_zero( ball.w );
+ }
+ if(glfwGetKey( vg.window, GLFW_KEY_J ))
+ {
+ m4x3_mulv( player.camera, (v3f){0.0f,0.0f,-5.0f}, ball1.co );
+ v3_zero( ball1.v );
+ v3_zero( ball1.w );
+ }
+
+ if(glfwGetKey( vg.window, GLFW_KEY_H ))
+ {
+ reorg_jeffs();
+ }
+ }
+
+ /* UPDATE TRANSFORMS */
+ for( int i=0; i<vg_list_size(jeffs); i++ )
+ {
+ rb_update_transform(jeffs+i);
+ }
+
+ rb_update_transform( &ball );
+ rb_update_transform( &ball1 );
+ rb_update_transform( &jeff1 );
+ rb_update_transform( &marko );
+
+ }
+}
+
+static void physics_test_render(void)
+{
+ m4x4f world_4x4;
+ m4x3_expand( player.camera_inverse, world_4x4 );
+
+ gpipeline.fov = 60.0f;
+ m4x4_projection( vg_pv, gpipeline.fov,
+ (float)vg_window_x / (float)vg_window_y,
+ 0.1f, 2100.0f );
+
+ m4x4_mul( vg_pv, world_4x4, vg_pv );
+ glEnable( GL_DEPTH_TEST );
+
+ glDisable( GL_DEPTH_TEST );
+ vg_lines_drawall( (float *)vg_pv );
+}
+
+#endif /* PHYSICS_TEST_H */
--- /dev/null
+#include "player.h"
+#include "addon.h"
+#include "player_model.h"
+#include "input.h"
+#include "world.h"
+#include "audio.h"
+#include "player_replay.h"
+#include "network.h"
+#include "network_common.h"
+#include "world_routes.h"
+#include "ent_miniworld.h"
+#include "gui.h"
+
+#include "shaders/model_entity.h"
+#include "shaders/model_character_view.h"
+#include "shaders/model_board_view.h"
+
+#include "player_walk.h"
+#include "player_dead.h"
+#include "player_drive.h"
+#include "player_skate.h"
+#include "player_basic_info.h"
+#include "player_glide.h"
+#include <string.h>
+
+i32 k_invert_y = 0;
+struct localplayer localplayer =
+{
+ .rb =
+ {
+ .co = { 0,0,0 },
+ .w = { 0,0,0 },
+ .v = { 0,0,0 },
+ .q = { 0,0,0,1 },
+ .to_world = M4X3_IDENTITY,
+ .to_local = M4X3_IDENTITY
+ }
+};
+
+struct player_subsystem_interface *player_subsystems[] =
+{
+ [k_player_subsystem_walk] = &player_subsystem_walk,
+ [k_player_subsystem_dead] = &player_subsystem_dead,
+ [k_player_subsystem_drive] = &player_subsystem_drive,
+ [k_player_subsystem_skate] = &player_subsystem_skate,
+ [k_player_subsystem_basic_info]=&player_subsystem_basic_info,
+ [k_player_subsystem_glide] = &player_subsystem_glide,
+};
+
+int localplayer_cmd_respawn( int argc, const char *argv[] )
+{
+ ent_spawn *rp = NULL, *r;
+ world_instance *world = world_current_instance();
+
+ if( argc == 1 ){
+ rp = world_find_spawn_by_name( world, argv[0] );
+ }
+ else if( argc == 0 ){
+ rp = world_find_closest_spawn( world, localplayer.rb.co );
+ }
+
+ if( !rp )
+ return 0;
+
+ player__spawn( rp );
+ return 1;
+}
+
+void player_init(void)
+{
+ for( u32 i=0; i<k_player_subsystem_max; i++ )
+ {
+ struct player_subsystem_interface *sys = player_subsystems[i];
+ if( sys->system_register ) sys->system_register();
+ }
+
+ vg_console_reg_cmd( "respawn", localplayer_cmd_respawn, NULL );
+ VG_VAR_F32( k_cam_damp );
+ VG_VAR_F32( k_cam_spring );
+ VG_VAR_F32( k_cam_punch );
+ VG_VAR_F32( k_cam_shake_strength );
+ VG_VAR_F32( k_cam_shake_trackspeed );
+ VG_VAR_I32( k_player_debug_info, flags=VG_VAR_PERSISTENT );
+
+#if 0
+ vg_console_reg_var( "cinema", &k_cinema, k_var_dtype_f32, 0 );
+ vg_console_reg_var( "cinema_fixed", &k_cinema_fixed, k_var_dtype_i32, 0 );
+#endif
+ vg_console_reg_var( "invert_y", &k_invert_y,
+ k_var_dtype_i32, VG_VAR_PERSISTENT );
+}
+
+void player__debugtext( ui_context *ctx, int size, const char *fmt, ... )
+{
+ char buffer[ 1024 ];
+
+ va_list args;
+ va_start( args, fmt );
+ vsnprintf( buffer, 1024, fmt, args );
+ va_end( args );
+
+ ui_text( ctx, g_player_debugger, buffer, size, k_ui_align_left, 0 );
+ g_player_debugger[1] += size*16;
+}
+
+/*
+ * Appearence
+ */
+
+void player__use_model( u16 reg_id )
+{
+ addon_cache_unwatch( k_addon_type_player,
+ localplayer.playermodel_view_slot );
+ localplayer.playermodel_view_slot =
+ addon_cache_create_viewer( k_addon_type_player, reg_id );
+}
+
+void player__bind(void)
+{
+ for( u32 i=0; i<k_player_subsystem_max; i++ )
+ {
+ struct player_subsystem_interface *sys = player_subsystems[i];
+
+ if( sys->bind ) sys->bind();
+ }
+}
+
+/*
+ * Gameloop events
+ * ----------------------------------------------------------------------------
+ */
+
+void player__pre_update(void)
+{
+ if( button_down( k_srbind_camera ) && !localplayer.immobile &&
+ (localplayer.subsystem != k_player_subsystem_dead) ){
+ if( localplayer.cam_control.camera_mode == k_cam_firstperson )
+ localplayer.cam_control.camera_mode = k_cam_thirdperson;
+ else
+ localplayer.cam_control.camera_mode = k_cam_firstperson;
+ }
+
+ if( player_subsystems[ localplayer.subsystem ]->pre_update )
+ player_subsystems[ localplayer.subsystem ]->pre_update();
+}
+
+void player__update(void)
+{
+ if( player_subsystems[ localplayer.subsystem ]->update )
+ player_subsystems[ localplayer.subsystem ]->update();
+
+ if( localplayer.glider_orphan &&
+ (skaterift.activity != k_skaterift_replay) )
+ glider_physics( (v2f){0,0} );
+}
+
+void player__post_update(void)
+{
+ struct player_subsystem_interface *sys =
+ player_subsystems[ localplayer.subsystem ];
+
+ if( sys->post_update ) sys->post_update();
+
+ SDL_AtomicLock( &air_audio_data.sl );
+ air_audio_data.speed = v3_length( localplayer.rb.v ) * vg.time_rate;
+ SDL_AtomicUnlock( &air_audio_data.sl );
+}
+
+/*
+ * Applies gate transport to a player_interface
+ */
+void player__pass_gate( u32 id )
+{
+ world_instance *world = world_current_instance();
+ skaterift_record_frame( &player_replay.local, 1 );
+
+ /* update boundary hash (network animation) */
+ u16 index = mdl_entity_id_id(id) & ~NETMSG_BOUNDARY_MASK;
+ localplayer.boundary_hash ^= NETMSG_GATE_BOUNDARY_BIT;
+ localplayer.boundary_hash &= ~NETMSG_BOUNDARY_MASK;
+ localplayer.boundary_hash |= index;
+
+ ent_gate *gate = mdl_arritm( &world->ent_gate, mdl_entity_id_id(id) );
+ world_routes_fracture( world, gate, localplayer.rb.co, localplayer.rb.v );
+
+ localplayer.gate_waiting = gate;
+ localplayer.deferred_frame_record = 1;
+
+ struct player_cam_controller *cc = &localplayer.cam_control;
+ m4x3_mulv( gate->transport, cc->tpv_lpf, cc->tpv_lpf );
+ m3x3_mulv( gate->transport, cc->cam_velocity_smooth,
+ cc->cam_velocity_smooth );
+
+ m4x3_mulv( gate->transport, localplayer.cam.pos, localplayer.cam.pos );
+
+ if( gate->flags & k_ent_gate_nonlocal )
+ {
+ world_default_spawn_pos( world, world->player_co );
+ world_static.active_instance = gate->target;
+ player__clean_refs();
+
+ replay_clear( &player_replay.local );
+ }
+ else
+ {
+ world_routes_activate_entry_gate( world, gate );
+ }
+
+ v3f v0;
+ v3_angles_vector( localplayer.angles, v0 );
+ m3x3_mulv( gate->transport, v0, v0 );
+ v3_angles( v0, localplayer.angles );
+
+ audio_lock();
+ audio_oneshot( &audio_gate_pass, 1.0f, 0.0f );
+ audio_unlock();
+}
+
+void player_apply_transport_to_cam( m4x3f transport )
+{
+ /* Pre-emptively edit the camera matrices so that the motion vectors
+ * are correct */
+ m4x3f transport_i;
+ m4x4f transport_4;
+ m4x3_invert_affine( transport, transport_i );
+ m4x3_expand( transport_i, transport_4 );
+ m4x4_mul( g_render.cam.mtx.pv, transport_4, g_render.cam.mtx.pv );
+ m4x4_mul( g_render.cam.mtx.v, transport_4, g_render.cam.mtx.v );
+
+ /* we want the regular transform here no the inversion */
+ m4x3_expand( transport, transport_4 );
+ m4x4_mul( world_gates.cam.mtx.pv, transport_4, world_gates.cam.mtx.pv );
+ m4x4_mul( world_gates.cam.mtx.v, transport_4, world_gates.cam.mtx.v );
+}
+
+void player__im_gui( ui_context *ctx )
+{
+ if( !k_player_debug_info ) return;
+
+ ui_rect box = {
+ vg.window_x - 300,
+ 0,
+ 300,
+ vg.window_y
+ };
+
+ ui_fill( ctx, box, (ui_colour(ctx, k_ui_bg)&0x00ffffff)|0x50000000 );
+
+ g_player_debugger[0] = box[0];
+ g_player_debugger[1] = 0;
+ g_player_debugger[2] = 300;
+ g_player_debugger[3] = 32;
+
+ player__debugtext( ctx, 2, "instance #%u", world_static.active_instance );
+
+ char buf[96];
+ for( u32 i=0; i<k_world_max; i++ )
+ {
+ if( world_static.instance_addons[ i ] )
+ addon_alias_uid( &world_static.instance_addons[ i ]->alias, buf );
+ else
+ strcpy( buf, "none" );
+
+ player__debugtext( ctx, 1, "world #%u: %s", i, buf );
+ }
+
+ player__debugtext( ctx, 2, "director" );
+ player__debugtext( ctx, 1, "activity: %s",
+ (const char *[]){ [k_skaterift_menu] = "menu",
+ [k_skaterift_replay] = "replay",
+ [k_skaterift_ent_focus] = "ent_focus",
+ [k_skaterift_default] = "default",
+ } [skaterift.activity] );
+ player__debugtext( ctx, 1, "time_rate: %.4f", skaterift.time_rate );
+
+ player__debugtext( ctx, 2, "player" );
+ player__debugtext( ctx, 1, "angles: " PRINTF_v3f( localplayer.cam.angles ) );
+
+ if( player_subsystems[ localplayer.subsystem ]->im_gui )
+ player_subsystems[ localplayer.subsystem ]->im_gui( ctx );
+
+ skaterift_replay_debug_info( ctx );
+}
+
+void player__setpos( v3f pos )
+{
+ v3_copy( pos, localplayer.rb.co );
+ v3_zero( localplayer.rb.v );
+ rb_update_matrices( &localplayer.rb );
+}
+
+void player__clean_refs(void)
+{
+ replay_clear( &player_replay.local );
+ gui_helper_clear();
+
+ world_static.challenge_target = NULL;
+ world_static.challenge_timer = 0.0f;
+ world_static.active_trigger_volume_count = 0;
+ world_static.last_use = 0.0;
+ world_entity_exit_modal();
+ world_entity_clear_focus();
+
+ localplayer.boundary_hash ^= NETMSG_BOUNDARY_BIT;
+
+ for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ ){
+ world_instance *instance = &world_static.instances[i];
+ if( instance->status == k_world_status_loaded ){
+ world_routes_clear( instance );
+ }
+ }
+}
+
+void player__reset(void)
+{
+ v3_zero( localplayer.rb.v );
+ v3_zero( localplayer.rb.w );
+
+ f32 l = v4_length( localplayer.rb.q );
+ if( (l < 0.9f) || (l > 1.1f) )
+ q_identity( localplayer.rb.q );
+
+ rb_update_matrices( &localplayer.rb );
+
+ localplayer.subsystem = k_player_subsystem_walk;
+ player__walk_reset();
+
+ localplayer.immobile = 0;
+ localplayer.gate_waiting = NULL;
+ localplayer.have_glider = 0;
+ localplayer.glider_orphan = 0;
+ localplayer.drowned = 0;
+
+ v3_copy( localplayer.rb.co, localplayer.cam_control.tpv_lpf );
+ player__clean_refs();
+}
+
+void player__spawn( ent_spawn *rp )
+{
+ player__setpos( rp->transform.co );
+ player__reset();
+}
+
+
+void player__kill(void)
+{
+}
+
+void player__begin_holdout( v3f offset )
+{
+ memcpy( &localplayer.holdout_pose, &localplayer.pose,
+ sizeof(localplayer.pose) );
+ v3_copy( offset, localplayer.holdout_pose.root_co );
+ localplayer.holdout_time = 1.0f;
+}
+
+void net_sfx_exchange( bitpack_ctx *ctx, struct net_sfx *sfx )
+{
+ bitpack_bytes( ctx, 1, &sfx->system );
+ bitpack_bytes( ctx, 1, &sfx->priority );
+ bitpack_bytes( ctx, 1, &sfx->id );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &sfx->subframe );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &sfx->volume );
+ bitpack_qv3f( ctx, 16, -1024.0f, 1024.0f, sfx->location );
+}
+
+void net_sfx_play( struct net_sfx *sfx )
+{
+ if( sfx->system < k_player_subsystem_max ){
+ struct player_subsystem_interface *sys = player_subsystems[sfx->system];
+ if( sys->sfx_oneshot ){
+ sys->sfx_oneshot( sfx->id, sfx->location, sfx->volume );
+ }
+ }
+};
+
+static struct net_sfx *find_lower_priority_sfx( struct net_sfx *buffer, u32 len,
+ u32 *count, u8 priority ){
+ struct net_sfx *p_sfx = NULL;
+ if( *count < len ){
+ p_sfx = &buffer[ *count ];
+ *count = *count+1;
+ }
+ else {
+ for( u32 i=0; i<len; i++ ){
+ struct net_sfx *a = &buffer[i];
+ if( a->priority < priority ){
+ p_sfx = a;
+ break;
+ }
+ }
+ }
+
+ return p_sfx;
+}
+
+void player__networked_sfx( u8 system, u8 priority, u8 id,
+ v3f pos, f32 volume )
+{
+ struct net_sfx sfx,
+ *p_net = find_lower_priority_sfx(
+ localplayer.sfx_buffer, 4,
+ &localplayer.sfx_buffer_count, priority ),
+ *p_replay = find_lower_priority_sfx(
+ localplayer.local_sfx_buffer, 2,
+ &localplayer.local_sfx_buffer_count, priority );
+
+ sfx.id = id;
+ sfx.priority = priority;
+ sfx.volume = volume;
+ v3_copy( pos, sfx.location );
+ sfx.system = system;
+
+ /* we only care about subframe in networked sfx. local replays run at a
+ * high enough framerate. */
+ f32 t = (vg.time_real - network_client.last_frame) / NETWORK_FRAMERATE;
+ sfx.subframe = vg_clampf( t, 0.0f, 1.0f );
+
+ if( p_net ) *p_net = sfx;
+ if( p_replay ) *p_replay = sfx;
+
+ net_sfx_play( &sfx );
+}
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+
+struct player_cam_controller {
+ enum camera_mode{
+ k_cam_firstperson = 1,
+ k_cam_thirdperson = 0
+ }
+ camera_mode;
+ f32 camera_type_blend;
+
+ v3f fpv_offset, /* expressed relative to rigidbody */
+ tpv_offset,
+ tpv_offset_extra,
+ fpv_viewpoint, /* expressed relative to neck bone inverse final*/
+ fpv_offset_smooth,
+ fpv_viewpoint_smooth,
+ tpv_offset_smooth,
+ tpv_lpf,
+ cam_velocity_smooth;
+};
+
+#include "player_common.h"
+#include "network_compression.h"
+#include "player_effects.h"
+#include "player_api.h"
+#include "player_ragdoll.h"
+#include "player_model.h"
+#include "player_render.h"
+
+struct player_subsystem_interface
+{
+ void(*system_register)(void);
+ void(*bind)(void);
+ void(*pre_update)(void);
+ void(*update)(void);
+ void(*post_update)(void);
+ void(*im_gui)( ui_context *ctx );
+ void(*animate)(void);
+ void(*pose)( void *animator, player_pose *pose );
+ void(*effects)( void *animator, m4x3f *final_mtx, struct player_board *board,
+ struct player_effects_data *effect_data );
+ void(*post_animate)(void);
+
+ void(*network_animator_exchange)( bitpack_ctx *ctx, void *data );
+ void(*sfx_oneshot)( u8 id, v3f pos, f32 volume );
+
+ void(*sfx_comp)(void *animator);
+ void(*sfx_kill)(void);
+
+ void *animator_data;
+ u32 animator_size;
+
+ const char *name;
+};
+
+#define PLAYER_REWIND_FRAMES 60*4
+#define RESET_MAX_TIME 45.0
+
+extern i32 k_invert_y;
+struct localplayer
+{
+ /* transform definition */
+ rigidbody rb;
+ v3f angles;
+
+ bool have_glider, glider_orphan, drowned;
+
+ /*
+ * Camera management
+ * ---------------------------
+ */
+ vg_camera cam;
+ struct player_cam_controller cam_control;
+ f32 cam_trackshake;
+
+ float cam_velocity_influence,
+ cam_velocity_coefficient,
+ cam_velocity_constant,
+ cam_velocity_coefficient_smooth,
+ cam_velocity_constant_smooth,
+ cam_velocity_influence_smooth,
+ cam_dist,
+ cam_dist_smooth;
+
+ v3f cam_land_punch, cam_land_punch_v;
+ ent_gate *gate_waiting;
+ int deferred_frame_record;
+
+ int immobile;
+
+ int rewinded_since_last_gate;
+
+ /*
+ * Network
+ * --------------------------------------------------
+ */
+ u16 boundary_hash;
+ struct net_sfx {
+ u8 system, priority, id;
+ f32 subframe, volume;
+ v3f location;
+ }
+ sfx_buffer[4], /* large timeframe 1/10s; for networking */
+ local_sfx_buffer[2]; /* per framerate 1/30s; for replay */
+ u32 sfx_buffer_count,
+ local_sfx_buffer_count;
+
+ /*
+ * Animation
+ * --------------------------------------------------
+ */
+
+ struct player_ragdoll ragdoll;
+ struct player_model fallback_model;
+ struct player_board fallback_board;
+
+ u16 board_view_slot, playermodel_view_slot;
+
+ player_pose pose;
+ player_pose holdout_pose;
+ float holdout_time;
+
+ m4x3f *final_mtx;
+
+ /*
+ * Subsystems
+ * -------------------------------------------------
+ */
+
+ enum player_subsystem subsystem,
+ observing_system;
+
+ /*
+ * Rendering
+ */
+ mdl_context skeleton_meta;
+ struct skeleton skeleton;
+
+ u8 id_hip,
+ id_chest,
+ id_ik_hand_l,
+ id_ik_hand_r,
+ id_ik_elbow_l,
+ id_ik_elbow_r,
+ id_head,
+ id_foot_l,
+ id_foot_r,
+ id_ik_foot_l,
+ id_ik_foot_r,
+ id_ik_knee_l,
+ id_ik_knee_r,
+ id_wheel_l,
+ id_wheel_r,
+ id_board,
+ id_eyes,
+ id_world;
+
+ u8 skeleton_mirror[32];
+
+ struct player_effects_data effect_data;
+}
+extern localplayer;
+extern struct player_subsystem_interface *player_subsystems[];
+
+/*
+ * Gameloop tables
+ * ---------------------------------------------------------
+ */
+
+void player_init(void);
+void player__debugtext( ui_context *ctx, int size, const char *fmt, ... );
+void player__use_mesh( glmesh *mesh );
+void player__use_model( u16 reg_id );
+
+void player__bind(void);
+void player__pre_update(void);
+void player__update(void);
+void player__post_update(void);
+
+void player__pass_gate( u32 id );
+void player__im_gui( ui_context *ctx );
+void player__setpos( v3f pos );
+void player__spawn( ent_spawn *rp );
+void player__clean_refs(void);
+void player__reset(void);
+void player__kill(void);
+void player__begin_holdout( v3f offset );
+
+int localplayer_cmd_respawn( int argc, const char *argv[] );
+void player_apply_transport_to_cam( m4x3f transport );
+
+void player__clear_sfx_buffer(void);
+void player__networked_sfx( u8 system, u8 priority, u8 id,
+ v3f pos, f32 volume );
+void net_sfx_exchange( bitpack_ctx *ctx, struct net_sfx *sfx );
+void net_sfx_play( struct net_sfx *sfx );
--- /dev/null
+#pragma once
+#include "model.h"
+
+typedef struct player_instance player_instance;
+typedef struct player_pose player_pose;
+
+struct player_pose{
+ enum player_pose_type {
+ k_player_pose_type_ik, /* regular IK animation */
+ k_player_pose_type_fk_2,
+ }
+ type;
+
+ v3f root_co;
+ v4f root_q;
+
+ mdl_keyframe keyframes[32];
+
+ struct player_board_pose {
+ f32 lean;
+ }
+ board;
+};
+
+enum player_subsystem{
+ k_player_subsystem_walk = 0,
+ k_player_subsystem_skate = 1,
+ k_player_subsystem_dead = 2,
+ k_player_subsystem_drive = 3,
+ k_player_subsystem_basic_info = 4,
+ k_player_subsystem_glide = 5,
+ k_player_subsystem_max,
+ k_player_subsystem_invalid = 255
+};
--- /dev/null
+#include "player_basic_info.h"
+#include "network_compression.h"
+
+struct player_basic_info player_basic_info;
+struct player_subsystem_interface player_subsystem_basic_info =
+{
+ .pose = player__basic_info_pose,
+ .network_animator_exchange = player__basic_info_animator_exchange,
+ .animator_data = &player_basic_info.animator,
+ .animator_size = sizeof(player_basic_info.animator),
+ .name = "Basic Info"
+};
+
+void player__basic_info_animator_exchange(bitpack_ctx *ctx, void *data)
+{
+ struct player_basic_info_animator *animator = data;
+ /* TODO: This range needs to be standardized in a common header */
+ bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
+}
+
+void player__basic_info_pose( void *_animator, player_pose *pose )
+{
+ struct player_basic_info_animator *animator = _animator;
+ v3_copy( animator->root_co, pose->root_co );
+ q_identity( pose->root_q );
+ pose->type = k_player_pose_type_fk_2;
+ pose->board.lean = 0.0f;
+
+ for( int i=0; i<localplayer.skeleton.bone_count; i ++ ){
+ v3_zero(pose->keyframes[i].co);
+ q_identity(pose->keyframes[i].q);
+ v3_fill(pose->keyframes[i].s,1.0f);
+ }
+}
--- /dev/null
+#pragma once
+#include "player.h"
+#include "player_api.h"
+
+struct player_basic_info
+{
+ struct player_basic_info_animator
+ {
+ v3f root_co;
+ }
+ animator;
+}
+extern player_basic_info;
+extern struct player_subsystem_interface player_subsystem_basic_info;
+
+void player__basic_info_animator_exchange(bitpack_ctx *ctx, void *data);
+void player__basic_info_pose( void *_animator, player_pose *pose );
+
--- /dev/null
+#include "ent_skateshop.h"
+#include "player.h"
+#include "input.h"
+#include "menu.h"
+#include "vg/vg_perlin.h"
+
+float player_get_heading_yaw(void)
+{
+ v3f xz;
+ q_mulv( localplayer.rb.q, (v3f){ 0.0f,0.0f,1.0f }, xz );
+ return atan2f( xz[0], xz[2] );
+}
+
+static void player_camera_portal_correction(void)
+{
+ if( localplayer.gate_waiting ){
+ /* construct plane equation for reciever gate */
+ v4f plane;
+ q_mulv( localplayer.gate_waiting->q[1], (v3f){0.0f,0.0f,1.0f}, plane );
+ plane[3] = v3_dot( plane, localplayer.gate_waiting->co[1] );
+
+ f32 pol = v3_dot( localplayer.cam.pos, plane ) - plane[3];
+
+ int cleared = (pol < 0.0f) || (pol > 5.0f);
+
+ if( cleared ){
+ vg_success( "Plane cleared\n" );
+ }
+
+ m4x3f inverse;
+ m4x3_invert_affine( localplayer.gate_waiting->transport, inverse );
+
+ /* de-transform camera and player back */
+ v3f v0;
+ m4x3_mulv( inverse, localplayer.cam.pos, localplayer.cam.pos );
+ v3_angles_vector( localplayer.cam.angles, v0 );
+ m3x3_mulv( inverse, v0, v0 );
+ v3_angles( v0, localplayer.cam.angles );
+
+ skeleton_apply_transform( &localplayer.skeleton, inverse,
+ localplayer.final_mtx );
+
+ /* record and re-put things again */
+ if( cleared )
+ {
+ skaterift_record_frame( &player_replay.local, 1 );
+ localplayer.deferred_frame_record = 1;
+
+ skeleton_apply_transform( &localplayer.skeleton,
+ localplayer.gate_waiting->transport,
+ localplayer.final_mtx );
+
+ m4x3_mulv( localplayer.gate_waiting->transport,
+ localplayer.cam.pos, localplayer.cam.pos );
+ v3_angles_vector( localplayer.cam.angles, v0 );
+ m3x3_mulv( localplayer.gate_waiting->transport, v0, v0 );
+ v3_angles( v0, localplayer.cam.angles );
+ player_apply_transport_to_cam( localplayer.gate_waiting->transport );
+ localplayer.gate_waiting = NULL;
+ }
+ }
+}
+
+void player__cam_iterate(void)
+{
+ struct player_cam_controller *cc = &localplayer.cam_control;
+
+ if( localplayer.subsystem == k_player_subsystem_walk ){
+ v3_copy( (v3f){-0.1f,1.8f,0.0f}, cc->fpv_viewpoint );
+ v3_copy( (v3f){0.0f,0.0f,0.0f}, cc->fpv_offset );
+ v3_copy( (v3f){0.0f,1.8f,0.0f}, cc->tpv_offset );
+ }
+ else if( localplayer.subsystem == k_player_subsystem_glide ){
+ v3_copy( (v3f){-0.15f,1.75f,0.0f}, cc->fpv_viewpoint );
+ v3_copy( (v3f){0.0f,0.0f,0.0f}, cc->fpv_offset );
+ v3_copy( (v3f){0.0f,-1.0f,0.0f}, cc->tpv_offset );
+ v3_add( cc->tpv_offset_extra, cc->tpv_offset, cc->tpv_offset );
+ }
+ else{
+ v3_copy( (v3f){-0.15f,1.75f,0.0f}, cc->fpv_viewpoint );
+ v3_copy( (v3f){0.0f,0.0f,0.0f}, cc->fpv_offset );
+
+ f32 h = vg_lerpf( 0.4f, 1.4f, k_cam_height );
+ v3_copy( (v3f){0.0f,h,0.0f}, cc->tpv_offset );
+ v3_add( cc->tpv_offset_extra, cc->tpv_offset, cc->tpv_offset );
+ }
+
+ localplayer.cam_velocity_constant = 0.25f;
+ localplayer.cam_velocity_coefficient = 0.7f;
+
+ /* lerping */
+
+ if( localplayer.cam_dist_smooth == 0.0f ){
+ localplayer.cam_dist_smooth = localplayer.cam_dist;
+ }
+ else {
+ localplayer.cam_dist_smooth = vg_lerpf(
+ localplayer.cam_dist_smooth,
+ localplayer.cam_dist,
+ vg.time_frame_delta * 8.0f );
+ }
+
+ localplayer.cam_velocity_influence_smooth = vg_lerpf(
+ localplayer.cam_velocity_influence_smooth,
+ localplayer.cam_velocity_influence,
+ vg.time_frame_delta * 8.0f );
+
+ localplayer.cam_velocity_coefficient_smooth = vg_lerpf(
+ localplayer.cam_velocity_coefficient_smooth,
+ localplayer.cam_velocity_coefficient,
+ vg.time_frame_delta * 8.0f );
+
+ localplayer.cam_velocity_constant_smooth = vg_lerpf(
+ localplayer.cam_velocity_constant_smooth,
+ localplayer.cam_velocity_constant,
+ vg.time_frame_delta * 8.0f );
+
+ enum camera_mode target_mode = cc->camera_mode;
+
+ if( localplayer.subsystem == k_player_subsystem_dead )
+ target_mode = k_cam_thirdperson;
+
+ cc->camera_type_blend =
+ vg_lerpf( cc->camera_type_blend,
+ (target_mode == k_cam_firstperson)? 1.0f: 0.0f,
+ 5.0f * vg.time_frame_delta );
+
+ v3_lerp( cc->fpv_viewpoint_smooth, cc->fpv_viewpoint,
+ vg.time_frame_delta * 8.0f, cc->fpv_viewpoint_smooth );
+
+ v3_lerp( cc->fpv_offset_smooth, cc->fpv_offset,
+ vg.time_frame_delta * 8.0f, cc->fpv_offset_smooth );
+
+ v3_lerp( cc->tpv_offset_smooth, cc->tpv_offset,
+ vg.time_frame_delta * 8.0f, cc->tpv_offset_smooth );
+
+ /* fov -- simple blend */
+ float fov_skate = vg_lerpf( 97.0f, 135.0f, k_fov ),
+ fov_walk = vg_lerpf( 90.0f, 110.0f, k_fov );
+
+ localplayer.cam.fov = vg_lerpf( fov_walk, fov_skate, cc->camera_type_blend );
+
+ /*
+ * first person camera
+ */
+
+ /* position */
+ v3f fpv_pos, fpv_offset;
+ m4x3_mulv( localplayer.final_mtx[ localplayer.id_head-1 ],
+ cc->fpv_viewpoint_smooth, fpv_pos );
+ m3x3_mulv( localplayer.rb.to_world, cc->fpv_offset_smooth, fpv_offset );
+ v3_add( fpv_offset, fpv_pos, fpv_pos );
+
+ /* angles */
+ v3f velocity_angles;
+ v3_lerp( cc->cam_velocity_smooth, localplayer.rb.v, 4.0f*vg.time_frame_delta,
+ cc->cam_velocity_smooth );
+
+ v3_angles( cc->cam_velocity_smooth, velocity_angles );
+ velocity_angles[1] *= localplayer.cam_velocity_coefficient_smooth;
+ velocity_angles[1] += localplayer.cam_velocity_constant_smooth;
+
+ float inf_fpv = localplayer.cam_velocity_influence_smooth *
+ cc->camera_type_blend,
+ inf_tpv = localplayer.cam_velocity_influence_smooth *
+ (1.0f-cc->camera_type_blend);
+
+ vg_camera_lerp_angles( localplayer.angles, velocity_angles,
+ inf_fpv,
+ localplayer.angles );
+
+ /*
+ * Third person camera
+ */
+
+ /* no idea what this technique is called, it acts like clamped position based
+ * on some derivative of where the final camera would end up ....
+ *
+ * it is done in the local basis then transformed back */
+
+ v3f future;
+ v3_muls( localplayer.rb.v, 0.4f*vg.time_frame_delta, future );
+
+ v3f camera_follow_dir =
+ { -sinf( localplayer.angles[0] ) * cosf( localplayer.angles[1] ),
+ sinf( localplayer.angles[1] ),
+ cosf( localplayer.angles[0] ) * cosf( localplayer.angles[1] ) };
+
+ v3f v0;
+ v3_sub( camera_follow_dir, future, v0 );
+
+ v3f follow_angles;
+ v3_copy( localplayer.angles, follow_angles );
+ follow_angles[0] = atan2f( -v0[0], v0[2] );
+ follow_angles[1] = 0.3f + velocity_angles[1] * 0.2f;
+
+ float ya = atan2f( -cc->cam_velocity_smooth[1], 30.0f );
+
+ follow_angles[1] = 0.3f + ya;
+ vg_camera_lerp_angles( localplayer.angles, follow_angles,
+ inf_tpv,
+ localplayer.angles );
+
+ v3f pco;
+ v4f pq;
+ rb_extrapolate( &localplayer.rb, pco, pq );
+ v3_muladds( pco, localplayer.holdout_pose.root_co,
+ localplayer.holdout_time, pco );
+ v3_lerp( cc->tpv_lpf, pco, 20.0f*vg.time_frame_delta, cc->tpv_lpf );
+
+ /* now move into world */
+ v3f tpv_pos, tpv_offset, tpv_origin;
+
+ /* TODO: whats up with CC and not CC but both sets of variables are doing
+ * the same ideas just saved in different places?
+ */
+ /* origin */
+ q_mulv( pq, cc->tpv_offset_smooth, tpv_origin );
+ v3_add( tpv_origin, cc->tpv_lpf, tpv_origin );
+
+ /* offset */
+ v3_muls( camera_follow_dir, localplayer.cam_dist_smooth, tpv_offset );
+ v3_muladds( tpv_offset, cc->cam_velocity_smooth, -0.025f, tpv_offset );
+
+ v3_add( tpv_origin, tpv_offset, tpv_pos );
+
+#if 0
+ if( localplayer.subsystem == k_player_subsystem_walk )
+ {
+ v3f fwd, right;
+ v3_angles_vector( localplayer.angles, fwd );
+ v3_cross( fwd, (v3f){0,1.001f,0}, right );
+ right[1] = 0.0f;
+ v3_normalize( right );
+ v3_muladds( tpv_pos, right, 0.5f, tpv_pos );
+ }
+#endif
+
+ /*
+ * Blend cameras
+ */
+ v3_lerp( tpv_pos, fpv_pos, cc->camera_type_blend, localplayer.cam.pos );
+ v3_copy( localplayer.angles, localplayer.cam.angles );
+
+ /* Camera shake */
+ f32 speed = v3_length(localplayer.rb.v),
+ strength = k_cam_shake_strength * speed;
+ localplayer.cam_trackshake +=
+ speed*k_cam_shake_trackspeed*vg.time_frame_delta;
+
+ v2f rnd = {vg_perlin_fract_1d( localplayer.cam_trackshake, 1.0f, 4, 20 ),
+ vg_perlin_fract_1d( localplayer.cam_trackshake, 1.0f, 4, 63 ) };
+ v2_muladds( localplayer.cam.angles, rnd, strength, localplayer.cam.angles );
+
+ v3f Fd, Fs, F;
+ v3_muls( localplayer.cam_land_punch_v, -k_cam_damp, Fd );
+ v3_muls( localplayer.cam_land_punch, -k_cam_spring, Fs );
+ v3_muladds( localplayer.cam_land_punch, localplayer.cam_land_punch_v,
+ vg.time_frame_delta, localplayer.cam_land_punch );
+ v3_add( Fd, Fs, F );
+ v3_muladds( localplayer.cam_land_punch_v, F, vg.time_frame_delta,
+ localplayer.cam_land_punch_v );
+ v3_add( localplayer.cam_land_punch, localplayer.cam.pos,
+ localplayer.cam.pos );
+
+ /* portal transitions */
+ player_camera_portal_correction();
+}
+
+void player_look( v3f angles, float speed )
+{
+ if( vg_ui.ctx.wants_mouse ) return;
+
+ angles[2] = 0.0f;
+
+ v2f mouse_input;
+ v2_copy( vg.mouse_delta, mouse_input );
+ if( k_invert_y ) mouse_input[1] *= -1.0f;
+ v2_muladds( angles, mouse_input, 0.0025f * speed, angles );
+
+ v2f jlook;
+ joystick_state( k_srjoystick_look, jlook );
+
+ angles[0] += jlook[0] * vg.time_frame_delta * 4.0f * speed;
+ float input_y = jlook[1] * vg.time_frame_delta * 4.0f;
+ if( k_invert_y ) input_y *= -1.0f;
+
+ angles[1] += input_y * speed;
+ angles[1] = vg_clampf( angles[1], -VG_PIf*0.5f, VG_PIf*0.5f );
+}
--- /dev/null
+#pragma once
+#include "player_api.h"
+
+static float
+ k_cam_spring = 20.0f,
+ k_cam_damp = 6.7f,
+ k_cam_punch = -1.0f,
+ k_cam_shake_strength = 0.0001f,
+ k_cam_shake_trackspeed = 0.2f;
+
+static i32 k_player_debug_info = 0;
+static ui_rect g_player_debugger;
+
+void player_look( v3f angles, float speed );
+void player__cam_iterate(void);
+f32 player_get_heading_yaw(void);
--- /dev/null
+#include "skaterift.h"
+#include "player_dead.h"
+#include "gui.h"
+
+struct player_dead player_dead;
+struct player_subsystem_interface player_subsystem_dead = {
+ .update = player__dead_update,
+ .post_update = player__dead_post_update,
+ .animate = player__dead_animate,
+ .pose = player__dead_pose,
+ .post_animate = player__dead_post_animate,
+ .im_gui = player__dead_im_gui,
+ .bind = player__dead_bind,
+
+ .animator_data = &player_dead.animator,
+ .animator_size = sizeof(player_dead.animator),
+ .network_animator_exchange = player__dead_animator_exchange,
+ .name = "Dead"
+};
+
+void player__dead_update(void)
+{
+ player_ragdoll_iter( &localplayer.ragdoll );
+
+ world_instance *world = world_current_instance();
+ world_water_player_safe( world, 0.2f );
+}
+
+void player__dead_post_update(void){
+ struct ragdoll_part *part =
+ &localplayer.ragdoll.parts[ localplayer.id_hip-1 ];
+ struct player_dead *d = &player_dead;
+
+ v3f ext_co;
+ v4f ext_q;
+ rb_extrapolate( &part->rb, ext_co, ext_q );
+
+ v3_lerp( d->co_lpf, ext_co, vg.time_frame_delta*4.0f, d->co_lpf );
+ v3_lerp( d->v_lpf, part->rb.v, vg.time_frame_delta*4.0f, d->v_lpf );
+ v3_lerp( d->w_lpf, part->rb.w, vg.time_frame_delta*4.0f, d->w_lpf );
+
+ v3_copy( d->co_lpf, localplayer.rb.co );
+ v3_zero( localplayer.rb.v );
+ v3_zero( localplayer.rb.w );
+
+ if( (skaterift.activity == k_skaterift_default) &&
+ button_down(k_srbind_dead_respawn) ){
+ ent_spawn *spawn = world_find_closest_spawn(
+ world_current_instance(), localplayer.rb.co );
+
+ if( spawn ){
+ v3_copy( spawn->transform.co, localplayer.rb.co );
+ player__reset();
+ srinput.state = k_input_state_resume;
+ }
+ else {
+ vg_error( "No spawns!\n" );
+ }
+ }
+}
+
+void player__dead_animate(void){
+ struct player_dead *d = &player_dead;
+ struct player_dead_animator *animator = &d->animator;
+ struct player_ragdoll *rd = &localplayer.ragdoll;
+ struct skeleton *sk = &localplayer.skeleton;
+
+ m4x3f transforms[ 32 ];
+
+ /* root transform */
+ q_m3x3( localplayer.rb.q, transforms[0] );
+ v3_copy( localplayer.rb.co, transforms[0][3] );
+
+ v4_copy( localplayer.rb.q, animator->transforms[0].q );
+ v3_copy( localplayer.rb.co, animator->transforms[0].co );
+
+ /* colliders with bones transforms */
+ for( int i=0; i<rd->part_count; i++ ){
+ struct ragdoll_part *part = &rd->parts[i];
+
+ m4x3f mtx;
+
+ v4f q_int;
+ v3f co_int;
+
+ float substep = vg.time_fixed_extrapolate;
+ v3_lerp( part->prev_co, part->rb.co, substep, co_int );
+ q_nlerp( part->prev_q, part->rb.q, substep, q_int );
+ v4_copy( part->rb.q, q_int );
+
+ q_m3x3( q_int, mtx );
+ v3_copy( co_int, mtx[3] );
+
+ m4x3_mul( mtx, part->inv_collider_mtx, transforms[part->bone_id] );
+ }
+
+ /* bones without colliders transforms */
+ for( u32 i=1; i<sk->bone_count; i++ ){
+ struct skeleton_bone *sb = &sk->bones[i];
+
+ if( sb->parent && !sb->collider ){
+ v3f delta;
+ v3_sub( sk->bones[i].co, sk->bones[sb->parent].co, delta );
+
+ m4x3f posemtx;
+ m3x3_identity( posemtx );
+ v3_copy( delta, posemtx[3] );
+
+ /* final matrix */
+ m4x3_mul( transforms[sb->parent], posemtx, transforms[i] );
+ }
+ }
+
+ /* measurements */
+ for( u32 i=1; i<sk->bone_count; i++ ){
+ struct skeleton_bone *sb = &sk->bones[i];
+
+ v3_zero( animator->transforms[i].co );
+ q_identity( animator->transforms[i].q );
+
+ m4x3f parent, inverse, local;
+ m3x3_identity( parent );
+ v3_sub( sk->bones[i].co, sk->bones[sb->parent].co, parent[3] );
+ m4x3_mul( transforms[ sb->parent ], parent, parent );
+ m4x3_invert_affine( parent, inverse );
+
+ v3f _s;
+ m4x3_mul( inverse, transforms[i], local );
+ m4x3_decompose( local, animator->transforms[i].co,
+ animator->transforms[i].q, _s );
+ }
+}
+
+void player__dead_pose( void *_animator, player_pose *pose )
+{
+ struct player_dead_animator *animator = _animator;
+ struct player_ragdoll *rd = &localplayer.ragdoll;
+ struct skeleton *sk = &localplayer.skeleton;
+
+ pose->type = k_player_pose_type_fk_2;
+ pose->board.lean = 0.0f;
+
+ v3_copy( animator->transforms[0].co, pose->root_co );
+ v4_copy( animator->transforms[0].q, pose->root_q );
+
+ for( u32 i=1; i<sk->bone_count; i++ ){
+ v3_copy( animator->transforms[i].co, pose->keyframes[i-1].co );
+ v4_copy( animator->transforms[i].q, pose->keyframes[i-1].q );
+ v3_fill( pose->keyframes[i-1].s, 1.0f );
+ }
+}
+
+void player__dead_post_animate(void)
+{
+ localplayer.cam_velocity_influence = 1.0f;
+}
+
+void player__dead_im_gui( ui_context *ctx )
+{
+}
+
+void player__dead_transition( enum player_die_type type )
+{
+ if( localplayer.subsystem == k_player_subsystem_dead )
+ return;
+
+ localplayer.subsystem = k_player_subsystem_dead;
+ copy_localplayer_to_ragdoll( &localplayer.ragdoll, type );
+
+ struct ragdoll_part *part =
+ &localplayer.ragdoll.parts[ localplayer.id_hip-1 ];
+ v3_copy( part->rb.co, player_dead.co_lpf );
+ v3_copy( part->rb.v, player_dead.v_lpf );
+ v3_copy( part->rb.w, player_dead.w_lpf );
+
+ gui_helper_clear();
+ vg_str str;
+
+ struct gui_helper *h;
+ if( (h = gui_new_helper(input_button_list[k_srbind_reset], &str) )){
+ vg_strcat( &str, "Rewind" );
+
+ if( world_static.active_instance == k_world_purpose_hub )
+ h->greyed = 1;
+ }
+
+ if( gui_new_helper(input_button_list[k_srbind_dead_respawn], &str ))
+ vg_strcat( &str, "Spawn" );
+}
+
+void player__dead_animator_exchange( bitpack_ctx *ctx, void *data )
+{
+ struct player_dead_animator *animator = data;
+
+ for( u32 i=0; i<localplayer.skeleton.bone_count; i ++ ){
+ bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->transforms[i].co );
+ bitpack_qquat( ctx, animator->transforms[i].q );
+ }
+}
+
+void player__dead_bind(void)
+{
+ struct skeleton *sk = &localplayer.skeleton;
+ player_dead.anim_bail = skeleton_get_anim( sk, "pose_bail_ball" );
+}
--- /dev/null
+#pragma once
+#include "player.h"
+#include "player_api.h"
+
+struct player_dead
+{
+ v3f co_lpf, v_lpf, w_lpf;
+
+ struct player_dead_animator{
+ struct {
+ v3f co;
+ v4f q;
+ }
+ transforms[ 32 ];
+ }
+ animator;
+
+ struct skeleton_anim *anim_bail;
+}
+extern player_dead;
+extern struct player_subsystem_interface player_subsystem_dead;
+
+void player__dead_update (void);
+void player__dead_post_update (void);
+void player__dead_animate (void);
+void player__dead_pose (void *animator, player_pose *pose);
+void player__dead_post_animate(void);
+void player__dead_im_gui ( ui_context *ctx );
+void player__dead_bind (void);
+void player__dead_transition ( enum player_die_type type );
+void player__dead_animator_exchange( bitpack_ctx *ctx, void *data );
+
--- /dev/null
+#include "player_drive.h"
+#include "input.h"
+
+struct player_drive player_drive;
+struct player_subsystem_interface player_subsystem_drive =
+{
+ .pre_update = player__drive_pre_update,
+ .update = player__drive_update,
+ .post_update = player__drive_post_update,
+ .animate = player__drive_animate,
+ .pose = player__drive_pose,
+ .post_animate = player__drive_post_animate,
+ .im_gui = player__drive_im_gui,
+ .bind = player__drive_bind,
+
+ .animator_data = NULL,
+ .animator_size = 0,
+ .name = "Drive"
+};
+
+void player__drive_pre_update(void)
+{
+ drivable_vehicle *vehc = player_drive.vehicle;
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ vehc->steer = vg_lerpf( vehc->steer, steer[0] * 0.4f,
+ vg.time_fixed_delta * 8.0f );
+ vehc->drive = steer[1];
+}
+
+void player__drive_update(void){}
+
+void player__drive_post_update(void)
+{
+ v3_copy( player_drive.vehicle->rb.co,localplayer.rb.co );
+ v3_copy( player_drive.vehicle->rb.v, localplayer.rb.v );
+ v4_copy( player_drive.vehicle->rb.q, localplayer.rb.q );
+ v3_copy( player_drive.vehicle->rb.w, localplayer.rb.w );
+}
+
+void player__drive_animate(void){}
+
+void player__drive_pose( void *animator, player_pose *pose )
+{
+ struct skeleton *sk = &localplayer.skeleton;
+
+ skeleton_sample_anim( sk, player_drive.anim_drive, 0.0f, pose->keyframes );
+ v3_copy( localplayer.rb.co, pose->root_co );
+ v4_copy( localplayer.rb.q, pose->root_q );
+}
+
+void player__drive_post_animate(void)
+{
+ if( localplayer.cam_control.camera_mode == k_cam_firstperson )
+ localplayer.cam_velocity_influence = 0.0f;
+ else
+ localplayer.cam_velocity_influence = 1.0f;
+
+ rigidbody *rb = &gzoomer.rb;
+ float yaw = atan2f( -rb->to_world[2][0], rb->to_world[2][2] ),
+ pitch = atan2f
+ (
+ -rb->to_world[2][1],
+ sqrtf
+ (
+ rb->to_world[2][0]*rb->to_world[2][0] +
+ rb->to_world[2][2]*rb->to_world[2][2]
+ )
+ );
+
+ localplayer.angles[0] = yaw;
+ localplayer.angles[1] = pitch;
+}
+
+void player__drive_im_gui( ui_context *ctx )
+{
+ player__debugtext( ctx, 1, "Nothing here" );
+}
+
+void player__drive_bind(void)
+{
+ struct skeleton *sk = &localplayer.skeleton;
+ player_drive.vehicle = &gzoomer;
+ player_drive.anim_drive = skeleton_get_anim( sk, "idle_cycle+y" );
+}
--- /dev/null
+#pragma once
+#include "player.h"
+#include "vehicle.h"
+
+struct player_drive
+{
+ drivable_vehicle *vehicle;
+ struct skeleton_anim *anim_drive;
+}
+extern player_drive;
+extern struct player_subsystem_interface player_subsystem_drive;
+
+void player__drive_pre_update(void);
+void player__drive_update(void);
+void player__drive_post_update(void);
+void player__drive_animate(void);
+void player__drive_pose( void *animator, player_pose *pose );
+
+void player__drive_post_animate(void);
+void player__drive_im_gui( ui_context *ctx );
+void player__drive_bind(void);
--- /dev/null
+#include "player.h"
+#include "player_effects.h"
+#include "player_render.h"
+#include "particle.h"
+
+void effect_blink_apply( effect_blink *ef, player_pose *pose, f32 dt )
+{
+ if( ef->t < 0.0f ){
+ ef->t = (1.0f-powf(vg_randf64(&vg.rand),4.0f))*4.0f;
+ ef->l = 0.08f;
+ }
+
+ pose->keyframes[ localplayer.id_eyes-1 ].s[1] = ef->l > 0.0f? 0.2f: 1.0f;
+
+ ef->t -= dt;
+ ef->l -= dt;
+}
+
+void effect_spark_apply( effect_spark *ef, v3f co, v3f v, f32 dt )
+{
+ if( !ef->colour ) return;
+
+ if( ef->t < 0.0f ){
+ ef->t += 0.05f+vg_randf64(&vg.rand)*0.1f;
+
+ v3f dir;
+ v3_copy( v, dir );
+ dir[1] += 1.0f;
+ f32 l = v3_length(dir);
+ v3_muls( dir, 1.0f/l, dir );
+
+ particle_spawn_cone( &particles_grind, co, dir, VG_PIf/2.0f, l,
+ 4.0f, ef->colour );
+ }
+ else
+ ef->t -= dt;
+}
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+#include "player_render.h"
+
+typedef struct effect_blink effect_blink;
+typedef struct effect_spark effect_spark;
+
+struct effect_blink
+{
+ f32 t, l;
+};
+
+struct effect_spark
+{
+ u32 colour;
+ f32 t;
+};
+
+void effect_blink_apply( effect_blink *ef, player_pose *pose, f32 dt );
+void effect_spark_apply( effect_spark *ef, v3f co, v3f v, f32 dt );
+
+struct player_effects_data
+{
+ effect_blink blink;
+ effect_spark spark, sand;
+};
--- /dev/null
+#include "player_glide.h"
+#include "vg/vg_rigidbody.h"
+#include "scene_rigidbody.h"
+#include "shaders/model_board_view.h"
+#include "shaders/model_entity.h"
+#include "input.h"
+#include "skaterift.h"
+
+#include "player_dead.h"
+#include "player_skate.h"
+
+trail_system trails_glider[] = {
+ {
+ .width = 0.035f,
+ .lifetime = 5.0f,
+ .min_dist = 0.5f
+ },
+ {
+ .width = 0.035f,
+ .lifetime = 5.0f,
+ .min_dist = 0.5f
+ },
+};
+
+struct player_glide player_glide =
+{
+ .parts = {
+ {
+ .co = { 1.0f, 0.5f, -1.0f },
+ .euler = { VG_TAUf*0.25f, VG_TAUf*0.125f, 0.0f },
+ .shape = k_rb_shape_capsule,
+ .inf = { .h = 2.82842712475f, .r = 0.25f },
+ },
+ {
+ .co = { -1.0f, 0.5f, -1.0f },
+ .euler = { VG_TAUf*0.25f, -VG_TAUf*0.125f, 0.0f },
+ .shape = k_rb_shape_capsule,
+ .inf = { .h = 2.82842712475f, .r = 0.25f },
+ },
+ {
+ .co = { 0.0f, 0.5f, 1.0f },
+ .euler = { VG_TAUf*0.25f, VG_TAUf*0.25f, 0.0f },
+ .shape = k_rb_shape_capsule,
+ .inf = { .h = 6.0f, .r = 0.25f },
+ },
+ {
+ .co = { 0.0f, -0.5f, 0.0f },
+ .euler = { VG_TAUf*0.25f, VG_TAUf*0.25f, 0.0f },
+ .shape = k_rb_shape_capsule,
+ .inf = { .h = 2.0f, .r = 0.25f },
+ .is_damage = 1,
+ },
+ }
+};
+
+struct player_subsystem_interface player_subsystem_glide =
+{
+ .pre_update = player_glide_pre_update,
+ .update = player_glide_update,
+ .post_update = player_glide_post_update,
+ .animate = player_glide_animate,
+ .pose = player_glide_pose,
+ .post_animate = player_glide_post_animate,
+ .network_animator_exchange = player_glide_animator_exchange,
+ .im_gui = player_glide_im_gui,
+ .bind = player_glide_bind,
+
+ .animator_data = &player_glide.animator,
+ .animator_size = sizeof(player_glide.animator),
+ .name = "Glide"
+};
+
+static f32 k_glide_steer = 2.0f,
+ k_glide_cl = 0.04f,
+ k_glide_cs = 0.02f,
+ k_glide_drag = 0.0001f,
+ k_glide_slip_yaw = 0.1f,
+ k_glide_lift_pitch = 0.0f,
+ k_glide_wing_orient = -0.1f,
+ k_glide_balance = 1.0f;
+
+static i32 k_glide_pause = 0;
+
+void player_glide_pre_update(void)
+{
+ if( button_down(k_srbind_use) ){
+ localplayer.subsystem = k_player_subsystem_skate;
+ localplayer.glider_orphan = 1;
+
+ player_skate.state.activity = k_skate_activity_air;
+ player_skate.state.activity_prev = k_skate_activity_air;
+
+ q_mulv( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f}, player_skate.state.cog );
+ v3_add( player_skate.state.cog, localplayer.rb.co,
+ player_skate.state.cog );
+ v3_copy( localplayer.rb.v, player_skate.state.cog_v );
+
+ player__begin_holdout( (v3f){0.0f,0.0f,0.0f} );
+ player__skate_reset_animator();
+ player__skate_clear_mechanics();
+ v3_copy( (v3f){0.0f,0.0f,0.0f}, player_skate.state.trick_euler );
+
+ player__approximate_best_trajectory();
+ }
+}
+
+static void massless_accel( rigidbody *rb, v3f delta, v3f impulse ){
+ /* linear */
+ v3_muladds( rb->v, impulse, vg.time_fixed_delta, rb->v );
+
+ /* Angular velocity */
+ v3f wa;
+ v3_cross( delta, impulse, wa );
+ v3_muladds( rb->w, wa, vg.time_fixed_delta, rb->w );
+}
+
+static void calculate_lift( v3f vl, f32 aoa_bias,
+ v3f axis, v3f back, f32 power,
+ v3f out_force ){
+ v3f up;
+ v3_cross( back, axis, up );
+
+ v3f wind;
+ v3_muladds( vl, axis, -v3_dot(axis,vl), wind );
+
+ f32 windv2 = v3_length2(wind),
+ aoa = atan2f( v3_dot( up, wind ), v3_dot( back, wind ) ) + aoa_bias,
+ cl = aoa / VG_PIf,
+ L = windv2 * cl * power;
+
+ v3f lift_dir;
+ v3_normalize( wind );
+ v3_cross( wind, axis, lift_dir );
+
+ /* this is where induced drag (from the flappy things) would go */
+
+ v3_muls( lift_dir, L, out_force );
+}
+
+static void calculate_drag( v3f vl, f32 cd, v3f out_force ){
+ f32 v2 = v3_length2( vl );
+ v3f dir;
+ v3_copy( vl, dir );
+ v3_normalize( dir );
+ v3_muls( vl, -cd*v2, out_force );
+}
+
+/*
+ * Returns true if the bottom sphere is hit
+ */
+bool glider_physics( v2f steer )
+{
+ rigidbody *rb = &player_glide.rb;
+
+ /* lift */
+ v3f vl, wl;
+ m3x3_mulv( rb->to_local, rb->v, vl );
+ m3x3_mulv( rb->to_local, rb->w, wl );
+
+ v3f F, Flift, Fslip, Fdrag, FslipW, FliftW;
+
+ calculate_lift( vl, steer[1]*k_glide_steer,
+ (v3f){1,0,0},
+ (v3f){0,sinf(k_glide_wing_orient),cosf(k_glide_wing_orient)},
+ k_glide_cl, Flift );
+ v3_copy( Flift, player_glide.info_lift );
+ v3_cross( (v3f){0,0,0}, Flift, FliftW );
+
+ calculate_lift( vl, 0.0f,
+ (v3f){0,1,0},(v3f){0,0,1},
+ k_glide_cs, Fslip );
+ v3_copy( Fslip, player_glide.info_slip );
+ v3_cross( (v3f){0,k_glide_lift_pitch,k_glide_slip_yaw}, Fslip, FslipW );
+
+ calculate_drag( vl, k_glide_drag, Fdrag );
+ v3_copy( Fdrag, player_glide.info_drag );
+
+ v3f balance = {0.0f,-k_glide_balance,0.0f};
+ m3x3_mulv( rb->to_local, balance, balance );
+
+ v3f Fw = {
+ steer[1]*k_glide_steer - balance[2],
+ 0.0f,
+ -steer[0]*k_glide_steer + balance[0],
+ };
+
+ if( player_glide.ticker ){
+ player_glide.ticker --;
+ return 0;
+ }
+ player_glide.ticker += k_glide_pause;
+
+ /* apply forces */
+ v3_add( Flift, Fslip, F );
+ v3_add( F, Fdrag, F );
+
+ m3x3_mulv( rb->to_world, F, F );
+ v3_muladds( rb->v, F, vg.time_fixed_delta, rb->v );
+
+ v3_add( Fw, FslipW, Fw );
+ v3_add( Fw, FliftW, Fw );
+ m3x3_mulv( rb->to_world, Fw, Fw );
+ v3_muladds( rb->w, Fw, vg.time_fixed_delta, rb->w );
+
+
+ /*
+ * collisions & constraints
+ */
+ world_instance *world = world_current_instance();
+ rb_solver_reset();
+
+ bool bottom_hit = 0;
+
+ rigidbody _null = {0};
+ _null.inv_mass = 0.0f;
+ m3x3_zero( _null.iI );
+ for( u32 i=0; i < VG_ARRAY_LEN(player_glide.parts); i ++ ){
+ m4x3f mmdl;
+ m4x3_mul( rb->to_world, player_glide.parts[i].mdl, mmdl );
+
+ if( player_glide.parts[i].shape == k_rb_shape_capsule ){
+ vg_line_capsule( mmdl,
+ player_glide.parts[i].inf.r,
+ player_glide.parts[i].inf.h,
+ VG__BLACK );
+ }
+ else if( player_glide.parts[i].shape == k_rb_shape_sphere ){
+ vg_line_sphere( mmdl, player_glide.parts[i].r, 0 );
+ }
+
+ if( rb_global_has_space() ){
+ rb_ct *buf = rb_global_buffer();
+
+ u32 l = 0;
+
+ if( player_glide.parts[i].shape == k_rb_shape_capsule ){
+ l = rb_capsule__scene( mmdl, &player_glide.parts[i].inf,
+ NULL, world->geo_bh, buf,
+ k_material_flag_ghosts );
+ }
+ else if( player_glide.parts[i].shape == k_rb_shape_sphere ){
+ l = rb_sphere__scene( mmdl, player_glide.parts[i].r,
+ NULL, world->geo_bh, buf,
+ k_material_flag_ghosts );
+ }
+
+ if( player_glide.parts[i].is_damage && l ){
+ bottom_hit = 1;
+ }
+
+ for( u32 j=0; j<l; j ++ ){
+ buf[j].rba = rb;
+ buf[j].rbb = &_null;
+ }
+
+ rb_contact_count += l;
+ }
+ }
+
+ rb_presolve_contacts( rb_contact_buffer,
+ vg.time_fixed_delta, rb_contact_count );
+ for( u32 i=0; i<10; i ++ )
+ rb_solve_contacts( rb_contact_buffer, rb_contact_count );
+
+ rb_iter( rb );
+ rb_update_matrices( rb );
+
+ return bottom_hit;
+}
+
+void player_glide_update(void)
+{
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ if( glider_physics( steer ) )
+ {
+ vg_info( "player fell off due to glider hitting ground\n" );
+ player__dead_transition( k_player_die_type_generic );
+ localplayer.glider_orphan = 1;
+ }
+
+ if( !world_water_player_safe( world_current_instance(), 1.0f ) )
+ return;
+}
+
+void player_glide_post_update(void)
+{
+ v3_copy( player_glide.rb.co, localplayer.rb.co );
+ v4_copy( player_glide.rb.q, localplayer.rb.q );
+ v3_copy( player_glide.rb.v, localplayer.rb.v );
+ v3_copy( player_glide.rb.w, localplayer.rb.w );
+ rb_update_matrices( &localplayer.rb );
+}
+
+void player_glide_animate(void)
+{
+ struct player_glide *g = &player_glide;
+ struct player_glide_animator *animator = &g->animator;
+ rb_extrapolate( &localplayer.rb, animator->root_co, animator->root_q );
+}
+
+void player_glide_pose( void *_animator, player_pose *pose )
+{
+ struct skeleton *sk = &localplayer.skeleton;
+ struct player_glide_animator *animator = _animator;
+ pose->type = k_player_pose_type_ik;
+ pose->board.lean = 0.0f;
+
+ skeleton_sample_anim( sk, player_glide.anim_glide, 0.0f, pose->keyframes );
+
+ v3f temp;
+ q_mulv( animator->root_q, (v3f){0,-0.5f,0}, temp );
+ v3_add( animator->root_co, temp, pose->root_co );
+
+ v4_copy( animator->root_q, pose->root_q );
+}
+
+void player_glide_post_animate(void)
+{
+ if( localplayer.cam_control.camera_mode == k_cam_firstperson )
+ localplayer.cam_velocity_influence = 0.0f;
+ else
+ localplayer.cam_velocity_influence = 0.0f;
+
+ v3f fwd;
+ v3_muls( localplayer.rb.to_world[2], -1.0f, fwd );
+ v3_angles( fwd, localplayer.angles );
+
+ localplayer.cam_dist = 2.0f + v3_length( localplayer.rb.v )*0.2f;
+}
+
+void player_glide_animator_exchange( bitpack_ctx *ctx, void *data )
+{
+ struct player_glide_animator *animator = data;
+
+ bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
+ bitpack_qquat( ctx, animator->root_q );
+}
+
+void player_glide_remote_animator_exchange( bitpack_ctx *ctx, void *data )
+{
+ struct remote_glider_animator *animator = data;
+
+ bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->s );
+ bitpack_qquat( ctx, animator->root_q );
+}
+
+void player_glide_im_gui( ui_context *ctx )
+{
+ player__debugtext( ctx, 1, " lift: %.2f %.2f %.2f",
+ player_glide.info_lift[0],
+ player_glide.info_lift[1],
+ player_glide.info_lift[2] );
+ player__debugtext( ctx, 1, " slip: %.2f %.2f %.2f",
+ player_glide.info_slip[0],
+ player_glide.info_slip[1],
+ player_glide.info_slip[2] );
+ player__debugtext( ctx, 1, " drag: %.2f %.2f %.2f",
+ player_glide.info_drag[0],
+ player_glide.info_drag[1],
+ player_glide.info_drag[2] );
+}
+
+void player_glide_equip_glider(void)
+{
+ if( !localplayer.have_glider ){
+ localplayer.have_glider = 1;
+ localplayer.glider_orphan = 0;
+ player_glide.t = -1.0f;
+ }
+}
+
+static int ccmd_player_glider_spawn( int argc, const char *argv[] ){
+ if( vg_console.cheats ){
+ player_glide_equip_glider();
+ }
+ else {
+ vg_error( "Can't spawn without cheats enabled.\n" );
+ }
+ return 0;
+}
+
+void player_glide_bind(void)
+{
+ u32 mask = VG_VAR_CHEAT|VG_VAR_PERSISTENT;
+ VG_VAR_F32( k_glide_steer, flags=mask );
+ VG_VAR_F32( k_glide_cl, flags=mask );
+ VG_VAR_F32( k_glide_cs, flags=mask );
+ VG_VAR_F32( k_glide_drag, flags=mask );
+ VG_VAR_F32( k_glide_slip_yaw, flags=mask );
+ VG_VAR_F32( k_glide_lift_pitch, flags=mask );
+ VG_VAR_I32( k_glide_pause, flags=mask );
+ VG_VAR_F32( k_glide_balance, flags=mask );
+ VG_VAR_F32( k_glide_wing_orient, flags=mask );
+
+ vg_console_reg_cmd( "spawn_glider", ccmd_player_glider_spawn, NULL );
+
+ f32 mass = 0.0f,k_density = 8.0f;
+ m3x3f I;
+ m3x3_zero( I );
+
+ for( u32 i=0; i<VG_ARRAY_LEN(player_glide.parts); i ++ ){
+ /* create part transform matrix */
+ v4f qp, qy, qr, q;
+ q_axis_angle( qp, (v3f){1,0,0}, player_glide.parts[i].euler[0] );
+ q_axis_angle( qy, (v3f){0,1,0}, player_glide.parts[i].euler[1] );
+ q_axis_angle( qr, (v3f){0,0,1}, player_glide.parts[i].euler[2] );
+
+ q_mul( qr, qy, q );
+ q_mul( q, qp, q );
+
+ q_m3x3( q, player_glide.parts[i].mdl );
+ v3_copy( player_glide.parts[i].co, player_glide.parts[i].mdl[3] );
+
+ /* add it to inertia model */
+
+ if( player_glide.parts[i].shape == k_rb_shape_capsule ){
+ f32 r = player_glide.parts[i].inf.r,
+ h = player_glide.parts[i].inf.h,
+ pv = vg_capsule_volume( r, h ),
+ pm = pv * k_density;
+
+ mass += pm;
+
+ m3x3f pI;
+ vg_capsule_inertia( r, h, pm, pI );
+ vg_rotate_inertia( pI, player_glide.parts[i].mdl );
+ vg_translate_inertia( pI, pm, player_glide.parts[i].co );
+ m3x3_add( I, pI, I );
+ }
+ else if( player_glide.parts[i].shape == k_rb_shape_sphere ){
+ f32 r = player_glide.parts[i].r,
+ pv = vg_sphere_volume( r ),
+ pm = pv * k_density;
+
+ mass += pm;
+ m3x3f pI;
+ vg_sphere_inertia( r, pm, pI );
+ vg_translate_inertia( pI, pm, player_glide.parts[i].co );
+ m3x3_add( I, pI, I );
+ }
+ }
+
+ /* set inverses */
+ m3x3_inv( I, player_glide.rb.iI );
+ player_glide.rb.inv_mass = 1.0f / mass;
+
+ /* resources */
+ struct skeleton *sk = &localplayer.skeleton;
+ player_glide.anim_glide = skeleton_get_anim( sk, "glide_pose" );
+
+ void *alloc = vg_mem.rtmemory;
+ mdl_context *mdl = &player_glide.glider;
+
+ mdl_open( mdl, "models/glider.mdl", alloc );
+ mdl_load_metadata_block( mdl, alloc );
+ mdl_async_full_load_std( mdl );
+
+ /* load trail positions */
+ mdl_array_ptr markers;
+ MDL_LOAD_ARRAY( mdl, &markers, ent_marker, vg_mem.scratch );
+ mdl_close( mdl );
+
+ for( u32 i=0; i<mdl_arrcount( &markers ); i ++ )
+ {
+ ent_marker *marker = mdl_arritm( &markers, i );
+ v3_copy( marker->transform.co,
+ player_glide.trail_positions[ player_glide.trail_count ++ ] );
+
+ if( player_glide.trail_count == VG_ARRAY_LEN(trails_glider) )
+ break;
+ }
+
+ /* allocate effects */
+ for( u32 i=0; i<VG_ARRAY_LEN(trails_glider); i ++ )
+ {
+ trail_alloc( &trails_glider[i], 200 );
+ }
+}
+
+void player_glide_transition(void)
+{
+ localplayer.subsystem = k_player_subsystem_glide;
+ localplayer.have_glider = 0;
+ world_static.challenge_target = NULL;
+ world_static.challenge_timer = 0.0f;
+ world_static.focused_entity = 0;
+ world_static.last_use = 0.0;
+ for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ ){
+ world_instance *instance = &world_static.instances[i];
+ if( instance->status == k_world_status_loaded ){
+ world_routes_clear( instance );
+ }
+ }
+
+ v3_copy( localplayer.rb.co, player_glide.rb.co );
+
+ f32 dir = v3_dot( localplayer.rb.v, localplayer.rb.to_world[2] );
+
+ if( dir > 0.0f ){
+ v4f qyaw;
+ q_axis_angle( qyaw, (v3f){0,1,0}, VG_TAUf*0.5f );
+ q_mul( qyaw, localplayer.rb.q, player_glide.rb.q );
+ q_normalize( player_glide.rb.q );
+ }
+ else
+ v4_copy( localplayer.rb.q, player_glide.rb.q );
+
+ v3_copy( localplayer.rb.v, player_glide.rb.v );
+ v3_copy( localplayer.rb.w, player_glide.rb.w );
+ rb_update_matrices( &player_glide.rb );
+
+ player__begin_holdout( (v3f){0,0,0} );
+}
+
+void render_glider_model( vg_camera *cam, world_instance *world,
+ m4x3f mmdl, enum board_shader shader )
+{
+ u32 current_mat = 0xffffffff;
+ glActiveTexture( GL_TEXTURE0 );
+
+ mdl_context *mdl = &player_glide.glider;
+ mesh_bind( &player_glide.glider.mesh );
+
+ for( u32 i=0; i<mdl_arrcount(&mdl->meshs); i ++ )
+ {
+ mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
+
+ m4x3f mmmdl;
+ mdl_transform_m4x3( &mesh->transform, mmmdl );
+ m4x3_mul( mmdl, mmmdl, mmmdl );
+
+ if( shader == k_board_shader_player )
+ shader_model_board_view_uMdl( mmmdl );
+ else if( shader == k_board_shader_entity )
+ {
+ m4x4f m4mmmdl;
+ m4x3_expand( mmmdl, m4mmmdl );
+ m4x4_mul( cam->mtx_prev.pv, m4mmmdl, m4mmmdl );
+
+ shader_model_entity_uMdl( mmmdl );
+ shader_model_entity_uPvmPrev( m4mmmdl );
+ }
+
+ for( u32 j=0; j<mesh->submesh_count; j ++ )
+ {
+ mdl_submesh *sm = mdl_arritm( &mdl->submeshs, mesh->submesh_start+j );
+ if( !sm->material_id )
+ {
+ vg_error( "Invalid material ID 0\n" );
+ continue;
+ }
+
+ if( sm->material_id != current_mat )
+ {
+ mdl_material *mat = mdl_arritm( &mdl->materials,sm->material_id-1 );
+ GLuint tex = vg.tex_missing;
+
+ if( mat->shader == k_shader_standard )
+ {
+ struct shader_props_standard *props = mat->props.compiled;
+
+ u32 index = props->tex_diffuse-1;
+ mdl_texture *ptex = mdl_arritm( &mdl->textures, index );
+ tex = ptex->glname;
+ }
+
+ glBindTexture( GL_TEXTURE_2D, tex );
+ current_mat = sm->material_id;
+ }
+
+ mdl_draw_submesh( sm );
+ }
+ }
+}
+
+/*
+ * TODO: more flexible way to call
+ * - this depends on the current state, but we need to pass a struct in
+ * that can hold that information instead so we can save it into
+ * the replay
+ */
+void player_glide_render( vg_camera *cam, world_instance *world,
+ player_pose *pose )
+{
+ if( !((localplayer.subsystem == k_player_subsystem_glide) ||
+ (localplayer.observing_system == k_player_subsystem_glide) ||
+ localplayer.have_glider ||
+ localplayer.glider_orphan) )
+ return;
+
+ shader_model_board_view_use();
+ shader_model_board_view_uTexMain( 0 );
+ shader_model_board_view_uCamera( cam->transform[3] );
+ shader_model_board_view_uPv( cam->mtx.pv );
+ shader_model_board_view_uDepthMode(1);
+ depth_compare_bind(
+ shader_model_board_view_uTexSceneDepth,
+ shader_model_board_view_uInverseRatioDepth,
+ shader_model_board_view_uInverseRatioMain,
+ cam );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_board_view );
+
+ mdl_keyframe kf_res;
+ if( localplayer.glider_orphan ){
+ rb_extrapolate( &player_glide.rb, kf_res.co, kf_res.q );
+ v3_fill( kf_res.s, 1.0f );
+
+ v3f temp;
+ q_mulv( kf_res.q, (v3f){0,-0.5f,0}, temp );
+ v3_add( temp, kf_res.co, kf_res.co );
+ }
+ else {
+ f32 target;
+ if( localplayer.subsystem == k_player_subsystem_glide ) target = 1.0f;
+ else target = 0.0f;
+
+ /* TODO: TEMP */
+ if( skaterift.activity != k_skaterift_replay )
+ vg_slewf( &player_glide.t, target, vg.time_frame_delta * 4.0f );
+
+ mdl_keyframe kf_backpack;
+
+ struct skeleton *sk = &localplayer.skeleton;
+ m4x3_mulv( localplayer.final_mtx[localplayer.id_chest ],
+ sk->bones[localplayer.id_chest].co,
+ kf_backpack.co );
+
+ v4f qyaw, qpitch, qchest, q;
+ q_axis_angle( qyaw, (v3f){0,1,0}, VG_TAUf*0.25f );
+ q_axis_angle( qpitch, (v3f){1,0,0}, VG_TAUf*0.25f );
+ m3x3_q( localplayer.final_mtx[ localplayer.id_chest ], qchest );
+
+ q_mul( qyaw, qpitch, q );
+ q_mul( qchest, q, kf_backpack.q );
+ q_normalize( kf_backpack.q );
+
+ f32 scale;
+ if( player_glide.t <= 0.0f ){
+ f32 st = player_glide.t + 1.0f,
+ sst = vg_smoothstepf(st),
+ isst= 1.0f - sst;
+ scale = vg_lerpf( 0.0f, 0.2f, sst );
+
+ v4f qspin;
+ q_axis_angle( qspin, (v3f){0,0,1}, VG_TAUf * isst * 0.5f );
+ q_mul( kf_backpack.q, qspin, kf_backpack.q );
+ kf_backpack.co[1] += isst * 1.0f;
+ v3_muladds( kf_backpack.co,
+ localplayer.final_mtx[ localplayer.id_chest ][0],
+ isst * 0.25f,
+ kf_backpack.co );
+ }
+ else{
+ scale = vg_lerpf( 0.2f, 1.0f, vg_smoothstepf(player_glide.t) );
+ }
+
+
+ v3_fill( kf_backpack.s, scale );
+
+ v3_copy( pose->root_co, kf_res.co );
+ v4_copy( pose->root_q, kf_res.q );
+ v3_fill( kf_res.s, scale );
+
+ f32 blend = vg_smoothstepf( vg_maxf( 0, player_glide.t ) );
+ keyframe_lerp( &kf_backpack, &kf_res, blend, &kf_res );
+ }
+
+ m4x3f mmdl;
+ q_m3x3( kf_res.q, mmdl );
+ m3x3_scale( mmdl, kf_res.s );
+ v3_copy( kf_res.co, mmdl[3] );
+
+ render_glider_model( cam, world, mmdl, k_board_shader_player );
+
+ /* totally FUCKED */
+ v4_copy( kf_res.q, player_glide.remote_animator.root_q );
+ v3_copy( kf_res.co, player_glide.remote_animator.root_co );
+ player_glide.remote_animator.s = kf_res.s[0];
+}
+
+void player_glide_render_effects( vg_camera *cam )
+{
+ v3f co, temp;
+ v4f q;
+ rb_extrapolate( &player_glide.rb, co, q );
+ q_mulv( q, (v3f){0,-0.5f,0}, temp );
+ v3_add( temp, co, co );
+
+ f32 alpha = vg_maxf( (fabsf(player_glide.info_lift[2])-1.0f), 0.0f ) /18.0f;
+
+ for( u32 i=0; i<player_glide.trail_count; i ++ ){
+ v3f vvert;
+ q_mulv( q, player_glide.trail_positions[i], vvert );
+ v3_add( co, vvert, vvert );
+
+ trail_system_update( &trails_glider[i], vg.time_delta, vvert,
+ localplayer.rb.to_world[1], alpha );
+
+ trail_system_prerender( &trails_glider[i] );
+ trail_system_render( &trails_glider[i], &g_render.cam );
+ }
+}
--- /dev/null
+#pragma once
+#include "player.h"
+#include "player_render.h"
+#include "trail.h"
+
+struct player_glide
+{
+ struct skeleton_anim *anim_glide;
+
+ struct player_glide_animator
+ {
+ v3f root_co;
+ v4f root_q;
+ }
+ animator;
+
+ /* this sucks */
+ struct remote_glider_animator
+ {
+ v3f root_co;
+ v4f root_q;
+ f32 s;
+ }
+ remote_animator;
+
+ v3f info_lift,
+ info_slip,
+ info_drag;
+
+ u32 ticker;
+
+ rigidbody rb;
+
+ f32 t;
+
+ struct {
+ v3f co, euler;
+ m4x3f mdl;
+
+ union {
+ rb_capsule inf;
+ f32 r;
+ };
+
+ enum rb_shape shape;
+ bool is_damage;
+ }
+ parts[4];
+
+ u32 trail_count;
+ v3f trail_positions[2];
+
+ mdl_context glider;
+}
+extern player_glide;
+extern struct player_subsystem_interface player_subsystem_glide;
+
+void player_glide_pre_update(void);
+void player_glide_update(void);
+void player_glide_post_update(void);
+void player_glide_animate(void);
+void player_glide_pose( void *animator, player_pose *pose );
+
+void player_glide_post_animate(void);
+void player_glide_im_gui( ui_context *ctx );
+void player_glide_bind(void);
+void player_glide_transition(void);
+bool glider_physics( v2f steer );
+void player_glide_animator_exchange( bitpack_ctx *ctx, void *data );
+void player_glide_render( vg_camera *cam, world_instance *world,
+ player_pose *pose );
+void render_glider_model( vg_camera *cam, world_instance *world,
+ m4x3f mmdl, enum board_shader shader );
+void player_glide_remote_animator_exchange( bitpack_ctx *ctx, void *data );
+void player_glide_equip_glider(void);
+void player_glide_render_effects( vg_camera *cam );
+
--- /dev/null
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+#include "model.h"
+#include "skeleton.h"
+#include "player_ragdoll.h"
+
+#include "shaders/model_character_view.h"
--- /dev/null
+#pragma once
+#include "vg/vg_rigidbody.h"
+#include "vg/vg_rigidbody_collision.h"
+#include "vg/vg_rigidbody_constraints.h"
+#include "scene_rigidbody.h"
+
+#include "player.h"
+#include "player_dead.h"
+#include "audio.h"
+
+static float k_ragdoll_floatyiness = 20.0f,
+ k_ragdoll_floatydrag = 1.0f,
+ k_ragdoll_limit_scale = 1.0f,
+ k_ragdoll_spring = 127.0f,
+ k_ragdoll_dampening = 15.0f,
+ k_ragdoll_correction = 0.5f,
+ k_ragdoll_angular_drag = 0.08f,
+ k_ragdoll_active_threshold = 5.0f;
+
+static int k_ragdoll_div = 1,
+ ragdoll_frame = 0,
+ k_ragdoll_debug_collider = 1,
+ k_ragdoll_debug_constraints = 0;
+
+static int dev_ragdoll_saveload(int argc, const char *argv[]){
+ if( argc != 2 ){
+ vg_info( "Usage: ragdoll load/save filepath\n" );
+ return 1;
+ }
+
+ if( !strcmp(argv[0],"save") ){
+ FILE *fp = fopen( argv[1], "wb" );
+ if( !fp ){
+ vg_error( "Failed to open file\n" );
+ return 1;
+ }
+ fwrite( &localplayer.ragdoll.parts,
+ sizeof(localplayer.ragdoll.parts), 1, fp );
+ fclose( fp );
+ }
+ else if( !strcmp(argv[0],"load") ){
+ FILE *fp = fopen( argv[1], "rb" );
+ if( !fp ){
+ vg_error( "Failed to open file\n" );
+ return 1;
+ }
+
+ fread( &localplayer.ragdoll.parts,
+ sizeof(localplayer.ragdoll.parts), 1, fp );
+ fclose( fp );
+ }
+ else {
+ vg_error( "Unknown command: %s (options are: save,load)\n", argv[0] );
+ return 1;
+ }
+
+ return 0;
+}
+
+void player_ragdoll_init(void)
+{
+ VG_VAR_F32( k_ragdoll_active_threshold );
+ VG_VAR_F32( k_ragdoll_angular_drag );
+ VG_VAR_F32( k_ragdoll_correction );
+ VG_VAR_F32( k_ragdoll_limit_scale );
+ VG_VAR_F32( k_ragdoll_spring );
+ VG_VAR_F32( k_ragdoll_dampening );
+ VG_VAR_I32( k_ragdoll_div );
+ VG_VAR_I32( k_ragdoll_debug_collider );
+ VG_VAR_I32( k_ragdoll_debug_constraints );
+ vg_console_reg_cmd( "ragdoll", dev_ragdoll_saveload, NULL );
+}
+
+void player_init_ragdoll_bone_collider( struct skeleton_bone *bone,
+ struct ragdoll_part *rp )
+{
+ f32 k_density = 8.0f,
+ k_inertia_scale = 2.0f;
+
+ m4x3_identity( rp->collider_mtx );
+
+ rp->type = bone->collider;
+ if( bone->collider == k_bone_collider_box ){
+ v3f delta;
+ v3_sub( bone->hitbox[1], bone->hitbox[0], delta );
+ v3_muls( delta, 0.5f, delta );
+ v3_add( bone->hitbox[0], delta, rp->collider_mtx[3] );
+
+ v3_muls( delta, -1.0f, rp->inf.box[0] );
+ v3_copy( delta, rp->inf.box[1] );
+
+ rp->colour = 0xffcccccc;
+
+ rb_setbody_box( &rp->rb, rp->inf.box, k_density, k_inertia_scale );
+ }
+ else if( bone->collider == k_bone_collider_capsule ){
+ v3f v0, v1, tx, ty;
+ v3_sub( bone->hitbox[1], bone->hitbox[0], v0 );
+
+ int major_axis = 0;
+ float largest = -1.0f;
+
+ for( int i=0; i<3; i ++ ){
+ if( fabsf( v0[i] ) > largest ){
+ largest = fabsf( v0[i] );
+ major_axis = i;
+ }
+ }
+
+ v3_zero( v1 );
+ v1[ major_axis ] = 1.0f;
+ v3_tangent_basis( v1, tx, ty );
+
+ rp->inf.capsule.r = (fabsf(v3_dot(tx,v0)) + fabsf(v3_dot(ty,v0))) * 0.25f;
+ rp->inf.capsule.h = fabsf(v0[ major_axis ]);
+
+ /* orientation */
+ v3_muls( tx, -1.0f, rp->collider_mtx[0] );
+ v3_muls( v1, -1.0f, rp->collider_mtx[1] );
+ v3_muls( ty, -1.0f, rp->collider_mtx[2] );
+ v3_add( bone->hitbox[0], bone->hitbox[1], rp->collider_mtx[3] );
+ v3_muls( rp->collider_mtx[3], 0.5f, rp->collider_mtx[3] );
+
+ rp->colour = 0xff000000 | (0xff << (major_axis*8));
+
+ rb_setbody_capsule( &rp->rb, rp->inf.capsule.r, rp->inf.capsule.h,
+ k_density, k_inertia_scale );
+ }
+ else{
+ vg_warn( "type: %u\n", bone->collider );
+ vg_fatal_error( "Invalid bone collider type" );
+ }
+
+ m4x3_invert_affine( rp->collider_mtx, rp->inv_collider_mtx );
+
+ /* Position collider into rest */
+ m3x3_q( rp->collider_mtx, rp->rb.q );
+ v3_add( rp->collider_mtx[3], bone->co, rp->rb.co );
+ v3_zero( rp->rb.v );
+ v3_zero( rp->rb.w );
+ rb_update_matrices( &rp->rb );
+}
+
+/*
+ * Get parent index in the ragdoll
+ */
+u32 ragdoll_bone_parent( struct player_ragdoll *rd, u32 bone_id )
+{
+ for( u32 j=0; j<rd->part_count; j++ )
+ if( rd->parts[ j ].bone_id == bone_id )
+ return j;
+
+ vg_fatal_error( "Referenced parent bone does not have a rigidbody" );
+ return 0;
+}
+
+/*
+ * Setup ragdoll colliders from skeleton
+ */
+void setup_ragdoll_from_skeleton( struct skeleton *sk,
+ struct player_ragdoll *rd )
+{
+ rd->part_count = 0;
+
+ if( !sk->collider_count )
+ return;
+
+ rd->position_constraints_count = 0;
+ rd->cone_constraints_count = 0;
+
+ for( u32 i=1; i<sk->bone_count; i ++ ){
+ struct skeleton_bone *bone = &sk->bones[i];
+
+ /*
+ * Bones with colliders
+ */
+ if( !(bone->collider) )
+ continue;
+
+ if( rd->part_count > VG_ARRAY_LEN(rd->parts) )
+ vg_fatal_error( "Playermodel has too many colliders" );
+
+ u32 part_id = rd->part_count;
+ rd->part_count ++;
+
+ struct ragdoll_part *rp = &rd->parts[ part_id ];
+ rp->bone_id = i;
+ rp->parent = 0xffffffff;
+
+ player_init_ragdoll_bone_collider( bone, rp );
+
+ /*
+ * Bones with collider and parent
+ */
+ if( !bone->parent )
+ continue;
+
+ rp->parent = ragdoll_bone_parent( rd, bone->parent );
+
+ if( bone->orig_bone->flags & k_bone_flag_cone_constraint ){
+ u32 conid = rd->position_constraints_count;
+ rd->position_constraints_count ++;
+
+ struct rb_constr_pos *c = &rd->position_constraints[ conid ];
+
+ struct skeleton_bone *bj = &sk->bones[rp->bone_id];
+ struct ragdoll_part *pp = &rd->parts[rp->parent];
+ struct skeleton_bone *bp = &sk->bones[pp->bone_id];
+
+ rd->constraint_associations[conid][0] = rp->parent;
+ rd->constraint_associations[conid][1] = part_id;
+
+ /* Convention: rba -- parent, rbb -- child */
+ c->rba = &pp->rb;
+ c->rbb = &rp->rb;
+
+ v3f delta;
+ v3_sub( bj->co, bp->co, delta );
+ m4x3_mulv( rp->inv_collider_mtx, (v3f){0.0f,0.0f,0.0f}, c->lcb );
+ m4x3_mulv( pp->inv_collider_mtx, delta, c->lca );
+
+
+ mdl_bone *inf = bone->orig_bone;
+
+ struct rb_constr_swingtwist *a =
+ &rd->cone_constraints[ rd->cone_constraints_count ++ ];
+
+ a->rba = &pp->rb;
+ a->rbb = &rp->rb;
+ a->conet = cosf( inf->conet )-0.0001f;
+
+ /* Store constraint in local space vectors */
+ m3x3_mulv( c->rba->to_local, inf->conevx, a->conevx );
+ m3x3_mulv( c->rba->to_local, inf->conevy, a->conevy );
+ m3x3_mulv( c->rbb->to_local, inf->coneva, a->coneva );
+ v3_copy( c->lca, a->view_offset );
+
+ v3_cross( inf->coneva, inf->conevy, a->conevxb );
+ m3x3_mulv( c->rbb->to_local, a->conevxb, a->conevxb );
+
+ v3_normalize( a->conevxb );
+ v3_normalize( a->conevx );
+ v3_normalize( a->conevy );
+ v3_normalize( a->coneva );
+
+ a->conevx[3] = v3_length( inf->conevx );
+ a->conevy[3] = v3_length( inf->conevy );
+
+ rp->use_limits = 1;
+ }
+ }
+}
+
+/*
+ * Make avatar copy the ragdoll
+ */
+void copy_ragdoll_pose_to_localplayer( struct player_ragdoll *rd )
+{
+ for( int i=0; i<rd->part_count; i++ ){
+ struct ragdoll_part *part = &rd->parts[i];
+
+ m4x3f mtx;
+
+ v4f q_int;
+ v3f co_int;
+
+ float substep = vg.time_fixed_extrapolate;
+ v3_lerp( part->prev_co, part->rb.co, substep, co_int );
+ q_nlerp( part->prev_q, part->rb.q, substep, q_int );
+
+ q_m3x3( q_int, mtx );
+ v3_copy( co_int, mtx[3] );
+
+ m4x3_mul( mtx, part->inv_collider_mtx,
+ localplayer.final_mtx[part->bone_id] );
+ }
+
+ for( u32 i=1; i<localplayer.skeleton.bone_count; i++ ){
+ struct skeleton_bone *sb = &localplayer.skeleton.bones[i];
+
+ if( sb->parent && !sb->collider ){
+ v3f delta;
+ v3_sub( localplayer.skeleton.bones[i].co,
+ localplayer.skeleton.bones[sb->parent].co, delta );
+
+ m4x3f posemtx;
+ m3x3_identity( posemtx );
+ v3_copy( delta, posemtx[3] );
+
+ /* final matrix */
+ m4x3_mul( localplayer.final_mtx[sb->parent], posemtx,
+ localplayer.final_mtx[i] );
+ }
+ }
+
+ skeleton_apply_inverses( &localplayer.skeleton, localplayer.final_mtx );
+}
+
+/*
+ * Make the ragdoll copy the player model
+ */
+void copy_localplayer_to_ragdoll( struct player_ragdoll *rd,
+ enum player_die_type type )
+{
+ v3f centroid;
+
+ v3f *bone_mtx = localplayer.final_mtx[localplayer.id_hip];
+ m4x3_mulv( bone_mtx,
+ localplayer.skeleton.bones[localplayer.id_hip].co, centroid );
+
+ for( int i=0; i<rd->part_count; i++ ){
+ struct ragdoll_part *part = &rd->parts[i];
+
+ v3f pos, offset;
+ u32 bone = part->bone_id;
+
+ v3f *bone_mtx = localplayer.final_mtx[bone];
+
+ m4x3_mulv( bone_mtx, localplayer.skeleton.bones[bone].co, pos );
+ m3x3_mulv( bone_mtx, part->collider_mtx[3], offset );
+ v3_add( pos, offset, part->rb.co );
+
+ m3x3f r;
+ m3x3_mul( bone_mtx, part->collider_mtx, r );
+ m3x3_q( r, part->rb.q );
+
+ v3f ra, v;
+ v3_sub( part->rb.co, centroid, ra );
+ v3_cross( localplayer.rb.w, ra, v );
+ v3_add( localplayer.rb.v, v, part->rb.v );
+
+ if( type == k_player_die_type_feet ){
+ if( (bone == localplayer.id_foot_l) ||
+ (bone == localplayer.id_foot_r) ){
+ v3_zero( part->rb.v );
+ }
+ }
+
+ v3_copy( localplayer.rb.w, part->rb.w );
+
+ v3_copy( part->rb.co, part->prev_co );
+ v4_copy( part->rb.q, part->prev_q );
+
+ rb_update_matrices( &part->rb );
+ }
+}
+
+/*
+ * Ragdoll physics step
+ */
+void player_ragdoll_iter( struct player_ragdoll *rd )
+{
+ world_instance *world = world_current_instance();
+
+ int run_sim = 0;
+ ragdoll_frame ++;
+
+ if( ragdoll_frame >= k_ragdoll_div ){
+ ragdoll_frame = 0;
+ run_sim = 1;
+ }
+
+ rb_solver_reset();
+
+ float contact_velocities[256];
+
+ rigidbody _null = {0};
+ _null.inv_mass = 0.0f;
+ m3x3_zero( _null.iI );
+
+ for( int i=0; i<rd->part_count; i ++ ){
+ v4_copy( rd->parts[i].rb.q, rd->parts[i].prev_q );
+ v3_copy( rd->parts[i].rb.co, rd->parts[i].prev_co );
+
+ if( rb_global_has_space() ){
+ rb_ct *buf = rb_global_buffer();
+
+ int l;
+
+ if( rd->parts[i].type == k_bone_collider_capsule ){
+ l = rb_capsule__scene( rd->parts[i].rb.to_world,
+ &rd->parts[i].inf.capsule,
+ NULL, world->geo_bh, buf,
+ k_material_flag_ghosts );
+ }
+ else if( rd->parts[i].type == k_bone_collider_box ){
+ l = rb_box__scene( rd->parts[i].rb.to_world,
+ rd->parts[i].inf.box,
+ NULL, world->geo_bh, buf,
+ k_material_flag_ghosts );
+ }
+ else continue;
+
+ for( int j=0; j<l; j++ ){
+ buf[j].rba = &rd->parts[i].rb;
+ buf[j].rbb = &_null;
+ }
+
+ rb_contact_count += l;
+ }
+ }
+
+ /*
+ * self-collision
+ */
+ for( int i=0; i<rd->part_count-1; i ++ ){
+ for( int j=i+1; j<rd->part_count; j ++ ){
+ if( rd->parts[j].parent != i ){
+ if( !rb_global_has_space() )
+ break;
+
+ if( rd->parts[j].type != k_bone_collider_capsule )
+ continue;
+
+ if( rd->parts[i].type != k_bone_collider_capsule )
+ continue;
+
+ rb_ct *buf = rb_global_buffer();
+
+ int l = rb_capsule__capsule( rd->parts[i].rb.to_world,
+ &rd->parts[i].inf.capsule,
+ rd->parts[j].rb.to_world,
+ &rd->parts[j].inf.capsule,
+ buf );
+
+ for( int k=0; k<l; k++ ){
+ buf[k].rba = &rd->parts[i].rb;
+ buf[k].rbb = &rd->parts[j].rb;
+ }
+
+ rb_contact_count += l;
+ }
+ }
+ }
+
+ if( localplayer.drowned )
+ {
+ for( int j=0; j<rd->part_count; j++ )
+ {
+ struct ragdoll_part *pj = &rd->parts[j];
+
+ if( run_sim )
+ {
+ rb_effect_simple_bouyency( &pj->rb, world->water.plane,
+ k_ragdoll_floatyiness,
+ k_ragdoll_floatydrag );
+ }
+ }
+ }
+
+ /*
+ * PRESOLVE
+ */
+ for( u32 i=0; i<rb_contact_count; i++ ){
+ rb_ct *ct = &rb_contact_buffer[i];
+
+ v3f rv, ra, rb;
+ v3_sub( ct->co, ct->rba->co, ra );
+ v3_sub( ct->co, ct->rbb->co, rb );
+ rb_rcv( ct->rba, ct->rbb, ra, rb, rv );
+ float vn = v3_dot( rv, ct->n );
+
+ contact_velocities[i] = vn;
+ }
+
+ rb_presolve_contacts( rb_contact_buffer, vg.time_fixed_delta,
+ rb_contact_count );
+ rb_presolve_swingtwist_constraints( rd->cone_constraints,
+ rd->cone_constraints_count );
+
+ /*
+ * DEBUG
+ */
+ if( k_ragdoll_debug_collider ){
+ for( u32 i=0; i<rd->part_count; i ++ ){
+ struct ragdoll_part *rp = &rd->parts[i];
+
+ if( rp->type == k_bone_collider_capsule ){
+ vg_line_capsule( rp->rb.to_world,
+ rp->inf.capsule.r, rp->inf.capsule.h, rp->colour );
+ }
+ else if( rp->type == k_bone_collider_box ){
+ vg_line_boxf_transformed( rp->rb.to_world,
+ rp->inf.box, rp->colour );
+ }
+ }
+ }
+
+ if( k_ragdoll_debug_constraints ){
+ rb_debug_position_constraints( rd->position_constraints,
+ rd->position_constraints_count );
+
+ rb_debug_swingtwist_constraints( rd->cone_constraints,
+ rd->cone_constraints_count );
+ }
+
+ /*
+ * SOLVE CONSTRAINTS & Integrate
+ */
+ if( run_sim ){
+ /* the solver is not very quickly converging so... */
+ for( int i=0; i<40; i++ ){
+ if( i<20 ){
+ rb_solve_contacts( rb_contact_buffer, rb_contact_count );
+ rb_solve_swingtwist_constraints( rd->cone_constraints,
+ rd->cone_constraints_count );
+ rb_postsolve_swingtwist_constraints( rd->cone_constraints,
+ rd->cone_constraints_count );
+ }
+ rb_solve_position_constraints( rd->position_constraints,
+ rd->position_constraints_count );
+ }
+
+ rb_correct_position_constraints( rd->position_constraints,
+ rd->position_constraints_count,
+ k_ragdoll_correction * 0.5f );
+ rb_correct_swingtwist_constraints( rd->cone_constraints,
+ rd->cone_constraints_count,
+ k_ragdoll_correction * 0.25f );
+
+ for( int i=0; i<rd->part_count; i++ ){
+ rb_iter( &rd->parts[i].rb );
+
+ v3f w;
+ v3_copy( rd->parts[i].rb.w, w );
+ if( v3_length2( w ) > 0.00001f ){
+ v3_normalize( w );
+ v3_muladds( rd->parts[i].rb.w, w, -k_ragdoll_angular_drag,
+ rd->parts[i].rb.w );
+ }
+ }
+
+ for( int i=0; i<rd->part_count; i++ )
+ rb_update_matrices( &rd->parts[i].rb );
+ }
+
+ rb_ct *stress = NULL;
+ float max_stress = 1.0f;
+
+ for( u32 i=0; i<rb_contact_count; i++ ){
+ rb_ct *ct = &rb_contact_buffer[i];
+
+ v3f rv, ra, rb;
+ v3_sub( ct->co, ct->rba->co, ra );
+ v3_sub( ct->co, ct->rbb->co, rb );
+ rb_rcv( ct->rba, ct->rbb, ra, rb, rv );
+ float vn = v3_dot( rv, ct->n );
+
+ float s = fabsf(vn - contact_velocities[i]);
+ if( s > max_stress ){
+ stress = ct;
+ max_stress = s;
+ }
+ }
+
+ static u32 temp_filter = 0;
+
+ /*
+ * motorized joints
+ */
+ if( run_sim &&
+ (v3_length2(player_dead.v_lpf)>(k_ragdoll_active_threshold*
+ k_ragdoll_active_threshold)) ){
+ mdl_keyframe anim[32];
+ skeleton_sample_anim( &localplayer.skeleton, player_dead.anim_bail,
+ 0.0f, anim );
+
+ for( u32 i=0; i<rd->cone_constraints_count; i ++ ){
+ rb_constr_swingtwist *st = &rd->cone_constraints[i];
+ rb_constr_pos *pc = &rd->position_constraints[i];
+
+ v3f va, vap;
+
+ m3x3_mulv( st->rbb->to_world, st->coneva, va );
+
+ /* calculate va as seen in rest position, from the perspective of the
+ * parent object, mapped to pose world space using the parents
+ * transform. thats our target */
+
+ u32 id_p = rd->constraint_associations[i][0],
+ id_a = rd->constraint_associations[i][1];
+
+ struct ragdoll_part *pa = &rd->parts[ id_a ],
+ *pp = &rd->parts[ id_p ];
+
+ mdl_keyframe *kf = &anim[ pa->bone_id-1 ];
+ m3x3_mulv( pa->collider_mtx, st->coneva, vap );
+ q_mulv( kf->q, vap, vap );
+
+ /* This could be a transfer function */
+ m3x3_mulv( pp->inv_collider_mtx, vap, vap );
+ m3x3_mulv( st->rba->to_world, vap, vap );
+
+ f32 d = v3_dot( vap, va ),
+ a = acosf( vg_clampf( d, -1.0f, 1.0f ) );
+
+ v3f axis;
+ v3_cross( vap, va, axis );
+
+ f32 Fs = -a * k_ragdoll_spring,
+ Fd = -v3_dot( st->rbb->w, axis ) * k_ragdoll_dampening,
+ F = Fs+Fd;
+
+ v3f torque;
+ v3_muls( axis, F, torque );
+ v3_muladds( st->rbb->w, torque, vg.time_fixed_delta, st->rbb->w );
+
+ /* apply a adjustment to keep velocity at joint 0 */
+#if 0
+ v3f wcb, vcb;
+ m3x3_mulv( st->rbb->to_world, pc->lcb, wcb );
+ v3_cross( torque, wcb, vcb );
+ v3_muladds( st->rbb->v, vcb, vg.time_fixed_delta, st->rbb->v );
+#endif
+ }
+ }
+
+ if( temp_filter ){
+ temp_filter --;
+ return;
+ }
+
+ if( stress ){
+ temp_filter = 20;
+ audio_lock();
+ audio_oneshot_3d( &audio_hits[vg_randu32(&vg.rand)%5],
+ stress->co, 20.0f, 1.0f );
+ audio_unlock();
+ }
+}
--- /dev/null
+#pragma once
+
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved
+ *
+ * Ragdoll system
+ */
+
+#include "player_api.h"
+#include "skeleton.h"
+#include "vg/vg_rigidbody.h"
+#include "vg/vg_rigidbody_constraints.h"
+
+struct player_ragdoll{
+ struct ragdoll_part{
+ u32 bone_id;
+
+ /* Collider transform relative to bone */
+ m4x3f collider_mtx,
+ inv_collider_mtx;
+
+ v4f prev_q;
+ v3f prev_co;
+
+ u32 use_limits;
+ v3f limits[2];
+
+ u32 parent;
+ u32 colour;
+
+ rigidbody rb;
+ enum bone_collider type;
+
+ union {
+ rb_capsule capsule;
+ boxf box;
+ }
+ inf;
+ }
+ parts[32];
+ u32 part_count;
+
+ rb_constr_pos position_constraints[32];
+ u32 position_constraints_count;
+
+ rb_constr_swingtwist cone_constraints[32];
+ u32 cone_constraints_count;
+
+ /* TODO: Fix duplicated data */
+ u32 constraint_associations[32][2];
+ int shoes[2];
+};
+
+enum player_die_type {
+ k_player_die_type_generic,
+ k_player_die_type_head,
+ k_player_die_type_feet
+};
+
+void player_ragdoll_init(void);
+void player_init_ragdoll_bone_collider( struct skeleton_bone *bone,
+ struct ragdoll_part *rp );
+u32 ragdoll_bone_parent( struct player_ragdoll *rd, u32 bone_id );
+void setup_ragdoll_from_skeleton( struct skeleton *sk,
+ struct player_ragdoll *rd );
+void copy_ragdoll_pose_to_localplayer( struct player_ragdoll *rd );
+void copy_localplayer_to_ragdoll( struct player_ragdoll *rd,
+ enum player_die_type type );
+
+void player_debug_ragdoll(void);
+void player_ragdoll_iter( struct player_ragdoll *rd );
--- /dev/null
+#include "player_remote.h"
+#include "skeleton.h"
+#include "player_render.h"
+#include "player_api.h"
+#include "network_common.h"
+#include "addon.h"
+#include "font.h"
+#include "gui.h"
+#include "ent_miniworld.h"
+#include "ent_region.h"
+#include "shaders/model_entity.h"
+#include "vg/vg_steam_friends.h"
+
+struct global_netplayers netplayers;
+
+static i32 k_show_own_name = 0;
+
+static void player_remote_clear( struct network_player *player )
+{
+ addon_cache_unwatch( k_addon_type_player, player->playermodel_view_slot );
+ addon_cache_unwatch( k_addon_type_board, player->board_view_slot );
+
+ memset( player, 0, sizeof(*player) );
+ strcpy( player->username, "unknown" );
+ player->subsystem = k_player_subsystem_invalid;
+}
+
+/*
+ * re-attatches addon_reg pointers on the remote client if we have the same
+ * world loaded.
+ */
+static void relink_remote_player_worlds( u32 client_id ){
+ struct network_player *player = &netplayers.list[client_id];
+
+ addon_alias q[2];
+ addon_uid_to_alias( player->items[k_netmsg_playeritem_world0], &q[0] );
+ addon_uid_to_alias( player->items[k_netmsg_playeritem_world1], &q[1] );
+
+ /*
+ * currently in 10.23, the hub world will always be the same.
+ * this might but probably wont change in the future
+ */
+ for( u32 i=0; i<k_world_max; i++ ){
+ addon_reg *reg = world_static.instance_addons[ i ];
+
+ player->world_match[i] = 0;
+
+ if( reg ){
+ if( addon_alias_eq( &q[i], &world_static.instance_addons[i]->alias ) )
+ player->world_match[i] = 1;
+ }
+ }
+}
+
+/*
+ * re-attatches addon_reg pointers on the remote client if we have the mod
+ * installed locally.
+ *
+ * Run if local worlds change
+ */
+void relink_all_remote_player_worlds(void)
+{
+ for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ ){
+ struct network_player *player = &netplayers.list[i];
+ if( player->active )
+ relink_remote_player_worlds(i);
+ }
+}
+
+void player_remote_update_friendflags( struct network_player *remote )
+{
+ ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
+ remote->isfriend = SteamAPI_ISteamFriends_HasFriend( hSteamFriends,
+ remote->steamid, k_EFriendFlagImmediate );
+ remote->isblocked = SteamAPI_ISteamFriends_HasFriend( hSteamFriends,
+ remote->steamid, k_EFriendFlagBlocked );
+}
+
+void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
+{
+ netmsg_blank *tmp = msg->m_pData;
+
+ if( tmp->inetmsg_id == k_inetmsg_playerjoin ){
+ netmsg_playerjoin *playerjoin = msg->m_pData;
+ if( !packet_minsize( msg, sizeof(*playerjoin) )) return;
+
+ if( playerjoin->index < VG_ARRAY_LEN(netplayers.list) ){
+ struct network_player *player = &netplayers.list[ playerjoin->index ];
+ player_remote_clear( player );
+ player->active = 1;
+ player->steamid = playerjoin->steamid;
+ player_remote_update_friendflags( player );
+
+ /* TODO: interpret the uids */
+ player->board_view_slot = 0;
+ player->playermodel_view_slot = 0;
+
+ struct interp_buffer *buf = &netplayers.interp_data[playerjoin->index];
+ buf->t = -99999999.9;
+ for( u32 i=0; i<VG_ARRAY_LEN(buf->frames); i ++ ){
+ buf->frames[i].active = 0;
+ }
+
+ vg_info( "#%u joined friend: %d, blocked: %d\n",
+ playerjoin->index, player->isfriend, player->isblocked );
+ }
+ else {
+ vg_error( "inetmsg_playerjoin: player index out of range\n" );
+ }
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_playerleave ){
+ netmsg_playerleave *playerleave = msg->m_pData;
+ if( !packet_minsize( msg, sizeof(*playerleave) )) return;
+
+ if( playerleave->index < VG_ARRAY_LEN(netplayers.list) ){
+ struct network_player *player = &netplayers.list[ playerleave->index ];
+ player_remote_clear( player );
+ player->active = 0;
+ vg_info( "player leave (%d)\n", playerleave->index );
+ }
+ else {
+ vg_error( "inetmsg_playerleave: player index out of range\n" );
+ }
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_playerusername ){
+ netmsg_playerusername *update = msg->m_pData;
+ if( !packet_minsize( msg, sizeof(*update) )) return;
+
+ if( update->index < VG_ARRAY_LEN(netplayers.list) ){
+ struct network_player *player = &netplayers.list[ update->index ];
+
+ network_msgstring( update->name, msg->m_cbSize, sizeof(*update),
+ player->username, sizeof(player->username) );
+
+ vg_info( "#%u changed username to: %s\n",
+ update->index, player->username );
+ }
+ else {
+ vg_error( "inetmsg_playerleave: player index out of range\n" );
+ }
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_playerframe ){
+ u32 datasize = msg->m_cbSize - sizeof(netmsg_playerframe);
+
+ if( datasize > sizeof(union interp_animdata) ){
+ vg_error( "Player frame data exceeds animdata size\n" );
+ return;
+ }
+
+ netmsg_playerframe *frame = msg->m_pData;
+
+ if( frame->client >= VG_ARRAY_LEN(netplayers.list) ){
+ vg_error( "inetmsg_playerframe: player index out of range\n" );
+ return;
+ }
+
+ if( frame->subsystem >= k_player_subsystem_max ){
+ vg_error( "inetmsg_playerframe: subsystem out of range\n" );
+ return;
+ }
+
+ struct interp_buffer *ib = &netplayers.interp_data[ frame->client ];
+ struct interp_frame *dest = NULL;
+
+ f64 min_time = INFINITY;
+ for( u32 i=0; i<VG_ARRAY_LEN(ib->frames); i++ ){
+ struct interp_frame *ifr = &ib->frames[i];
+
+ if( !ifr->active ){
+ dest = ifr;
+ break;
+ }
+
+ if( ifr->timestamp < min_time ){
+ min_time = ifr->timestamp;
+ dest = ifr;
+ }
+ }
+
+ dest->active = 1;
+ dest->subsystem = frame->subsystem;
+ dest->flags = frame->flags;
+
+ bitpack_ctx ctx = {
+ .mode = k_bitpack_decompress,
+ .buffer = frame->animdata,
+ .buffer_len = datasize,
+ .bytes = 0,
+ };
+
+ /* animation
+ * -------------------------------------------------------------*/
+
+ dest->timestamp = frame->timestamp;
+ dest->boundary_hash = frame->boundary_hash;
+
+ struct network_player *player = &netplayers.list[ frame->client ];
+ struct player_subsystem_interface *sys =
+ player_subsystems[ frame->subsystem ];
+
+ memset( &dest->data, 0, sys->animator_size );
+ if( sys->network_animator_exchange )
+ sys->network_animator_exchange( &ctx, &dest->data );
+ else
+ bitpack_bytes( &ctx, sys->animator_size, sys->animator_data );
+
+ /* sfx
+ * -------------------------------------------------------------*/
+
+ for( u32 i=0; i<frame->sound_effects; i ++ ){
+ struct net_sfx sfx;
+ net_sfx_exchange( &ctx, &sfx );
+
+ f64 t = (frame->timestamp - NETWORK_FRAMERATE) +
+ (sfx.subframe*NETWORK_FRAMERATE);
+
+ f32 remaining = t - ib->t;
+
+ if( remaining <= 0.0f )
+ net_sfx_play( &sfx );
+ else{
+ struct net_sfx *dst = NULL;
+
+ for( u32 j=0; j<NETWORK_SFX_QUEUE_LENGTH; j ++ ){
+ struct net_sfx *sj = &netplayers.sfx_queue[j];
+ if( sj->system == k_player_subsystem_invalid ){
+ dst = sj;
+ break;
+ }
+
+ if( sj->priority < sfx.priority )
+ dst = sj;
+ }
+
+ *dst = sfx;
+ dst->subframe = remaining;
+ }
+ }
+
+ /* glider
+ * -------------------------------------------------------------*/
+
+ memset( &dest->data_glider, 0, sizeof(struct remote_glider_animator) );
+ if( dest->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|
+ NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){
+ player_glide_remote_animator_exchange( &ctx, &dest->data_glider );
+ }
+
+ player->subsystem = frame->subsystem;
+ player->down_bytes += msg->m_cbSize;
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_playeritem ){
+ netmsg_playeritem *item = msg->m_pData;
+ if( !packet_minsize( msg, sizeof(*item)+1 )) return;
+
+ if( item->client >= VG_ARRAY_LEN(netplayers.list) ){
+ vg_error( "inetmsg_playerframe: player index out of range\n" );
+ return;
+ }
+
+ if( item->type_index >= k_netmsg_playeritem_max ){
+ vg_warn( "Client #%d invalid equip type %u\n",
+ (i32)item->client, (u32)item->type_index );
+ return;
+ }
+
+ vg_info( "Client #%d equiped: [%s] %s\n",
+ item->client,
+ (const char *[]){[k_netmsg_playeritem_board]="board",
+ [k_netmsg_playeritem_player]="player",
+ [k_netmsg_playeritem_world0]="world0",
+ [k_netmsg_playeritem_world1]="world1"
+ }[item->type_index], item->uid );
+
+ struct network_player *player = &netplayers.list[ item->client ];
+ char *uid = player->items[ item->type_index ];
+
+ network_msgstring( item->uid, msg->m_cbSize, sizeof(*item),
+ uid, ADDON_UID_MAX );
+
+ if( item->type_index == k_netmsg_playeritem_board ){
+ addon_cache_unwatch( k_addon_type_board, player->board_view_slot );
+ player->board_view_slot =
+ addon_cache_create_viewer_from_uid( k_addon_type_board, uid );
+ }
+ else if( item->type_index == k_netmsg_playeritem_player ){
+ addon_cache_unwatch( k_addon_type_player,
+ player->playermodel_view_slot );
+ player->playermodel_view_slot =
+ addon_cache_create_viewer_from_uid( k_addon_type_player, uid );
+ }
+ else if( (item->type_index == k_netmsg_playeritem_world0) ||
+ (item->type_index == k_netmsg_playeritem_world1) ){
+ relink_remote_player_worlds( item->client );
+ }
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_chat ){
+ netmsg_chat *chat = msg->m_pData;
+
+ struct network_player *player = &netplayers.list[ chat->client ];
+ network_msgstring( chat->msg, msg->m_cbSize, sizeof(netmsg_chat),
+ player->chat, NETWORK_MAX_CHAT );
+ player->chat_time = vg.time_real;
+ vg_info( "[%d]: %s\n", chat->client, player->chat );
+ }
+ else if( tmp->inetmsg_id == k_inetmsg_region ){
+ netmsg_region *region = msg->m_pData;
+ struct network_player *player = &netplayers.list[ region->client ];
+
+ u32 l = network_msgstring(
+ region->loc, msg->m_cbSize, sizeof(netmsg_region),
+ player->region, NETWORK_REGION_MAX );
+ player->region_flags = region->flags;
+
+ if( l )
+ player->region_flags |= k_ent_region_flag_hasname;
+
+ player->effect_data.spark.colour = region_spark_colour(region->flags);
+ }
+}
+
+/*
+ * Write localplayer pose to network
+ */
+void remote_player_send_playerframe(void)
+{
+ u8 sysid = localplayer.subsystem;
+ if( sysid >= k_player_subsystem_max ) return;
+
+ struct player_subsystem_interface *sys = player_subsystems[sysid];
+
+ if( sys->animator_size ){
+ u32 max_buf_size = sys->animator_size + sizeof(localplayer.sfx_buffer),
+ base_size = sizeof(struct netmsg_playerframe),
+ max_packet = base_size + max_buf_size;
+
+ netmsg_playerframe *frame = alloca( max_packet );
+ frame->inetmsg_id = k_inetmsg_playerframe;
+ frame->client = 0xff;
+ frame->subsystem = localplayer.subsystem;
+ frame->flags = world_static.active_instance;
+
+ bitpack_ctx ctx = {
+ .mode = k_bitpack_compress,
+ .buffer = frame->animdata,
+ .buffer_len = max_buf_size,
+ .bytes = 0
+ };
+
+ /* animation
+ * -----------------------------------------------*/
+
+ frame->timestamp = vg.time_real;
+ frame->boundary_hash = localplayer.boundary_hash;
+ if( sys->network_animator_exchange )
+ sys->network_animator_exchange( &ctx, sys->animator_data );
+ else
+ bitpack_bytes( &ctx, sys->animator_size, sys->animator_data );
+
+ /* sfx
+ * ---------------------------------------------*/
+
+ frame->sound_effects = localplayer.sfx_buffer_count;
+ for( u32 i=0; i<localplayer.sfx_buffer_count; i ++ )
+ net_sfx_exchange( &ctx, &localplayer.sfx_buffer[i] );
+
+ /* glider
+ * -------------------------------------------------------------*/
+
+ if( localplayer.have_glider ||
+ (localplayer.subsystem == k_player_subsystem_glide) ) {
+ frame->flags |= NETMSG_PLAYERFRAME_HAVE_GLIDER;
+ }
+
+ if( localplayer.glider_orphan )
+ frame->flags |= NETMSG_PLAYERFRAME_GLIDER_ORPHAN;
+
+ if( frame->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|
+ NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){
+ player_glide_remote_animator_exchange( &ctx,
+ &player_glide.remote_animator );
+ }
+
+ /* ------- */
+
+ u32 wire_size = base_size + ctx.bytes;
+ netplayers.up_bytes += wire_size;
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, network_client.remote,
+ frame, wire_size,
+ k_nSteamNetworkingSend_Unreliable, NULL );
+ }
+}
+
+/*
+ * Updates network traffic stats
+ */
+void remote_player_debug_update(void)
+{
+ if( (vg.time_real - netplayers.last_data_measurement) > 1.0 ){
+ netplayers.last_data_measurement = vg.time_real;
+ u32 total_down = 0;
+
+ for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ ){
+ struct network_player *player = &netplayers.list[i];
+ if( player->active ){
+ total_down += player->down_bytes;
+ player->down_kbs = ((f32)player->down_bytes)/1024.0f;
+ player->down_bytes = 0;
+ }
+ }
+
+ netplayers.down_kbs = ((f32)total_down)/1024.0f;
+ netplayers.up_kbs = ((f32)netplayers.up_bytes)/1024.0f;
+ netplayers.up_bytes = 0;
+ }
+}
+
+/*
+ * Debugging information
+ */
+void remote_player_network_imgui( ui_context *ctx, m4x4f pv )
+{
+ if( network_client.user_intent == k_server_intent_online )
+ {
+ if( !(steam_ready &&
+ (network_client.state == k_ESteamNetworkingConnectionState_Connected)))
+ {
+ char buf[128];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+ u32 fg = 0;
+ network_status_string( &str, &fg );
+ ui_text( ctx, (ui_rect){ vg.window_x - 200, 0, 200, 48 }, buf, 1,
+ k_ui_align_middle_center, fg );
+ }
+ }
+
+ if( !network_client.network_info )
+ return;
+
+ ui_rect panel = { (vg.window_x / 2) - 200, 0, 400, 600 };
+ ui_fill( ctx, panel, (ui_colour(ctx, k_ui_bg)&0x00ffffff)|0x50000000 );
+
+ ctx->font = &vgf_default_title;
+ ui_info( ctx, panel, "Network" );
+ ctx->font = &vgf_default_large;
+ ui_info( ctx, panel, "Status" );
+ ctx->font = &vgf_default_small;
+
+ char buf[512];
+ const char *netstatus = "PROGRAMMING ERROR";
+
+ struct { enum ESteamNetworkingConnectionState state; const char *str; }
+ states[] = {
+ { k_ESteamNetworkingConnectionState_None, "None" },
+ { k_ESteamNetworkingConnectionState_Connecting,
+ (const char *[]){"Connecting -",
+ "Connecting /",
+ "Connecting |",
+ "Connecting \\",
+ }[(u32)(vg.time_real/0.25) & 0x3 ] },
+ { k_ESteamNetworkingConnectionState_FindingRoute, "Finding Route" },
+ { k_ESteamNetworkingConnectionState_Connected, "Connected" },
+ { k_ESteamNetworkingConnectionState_ClosedByPeer, "Closed by peer" },
+ { k_ESteamNetworkingConnectionState_ProblemDetectedLocally,
+ "Problem Detected Locally" },
+ { k_ESteamNetworkingConnectionState_FinWait, "Fin Wait" },
+ { k_ESteamNetworkingConnectionState_Linger, "Linger" },
+ { k_ESteamNetworkingConnectionState_Dead, "Dead" }
+ };
+ for( u32 i=0; i<VG_ARRAY_LEN(states); i ++ )
+ {
+ if( states[i].state == network_client.state )
+ {
+ netstatus = states[i].str;
+ break;
+ }
+ }
+ snprintf( buf, 512, "Network: %s", netstatus );
+ ui_info( ctx, panel, buf );
+ ui_info( ctx, panel, "---------------------" );
+
+ if( network_client.state == k_ESteamNetworkingConnectionState_Connected )
+ {
+ ui_info( ctx, panel, "#-1: localplayer" );
+
+ snprintf( buf, 512, "U%.3f/D%.3fkbs",
+ netplayers.up_kbs, netplayers.down_kbs );
+ ui_info( ctx, panel, buf );
+
+ for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ )
+ {
+ struct network_player *player = &netplayers.list[i];
+ if( player->active )
+ {
+ const char *sysname = "invalid";
+
+ if( player->subsystem < k_player_subsystem_max )
+ {
+ sysname = player_subsystems[ player->subsystem ]->name;
+ }
+ snprintf( buf, 512, "#%u: %s [%s] D%.1fkbs",
+ i, player->username, sysname, player->down_kbs );
+ ui_info( ctx, panel, buf );
+ }
+ }
+ }
+ else
+ {
+ ui_info( ctx, panel, "offline" );
+ }
+}
+
+static void remote_player_effect( struct network_player *player,
+ player_pose *final_pose ){
+ /* effects */
+}
+
+/*
+ * write the remote players final_mtx
+ */
+static void pose_remote_player( u32 index,
+ struct interp_frame *f0,
+ struct interp_frame *f1 ){
+
+ struct network_player *player = &netplayers.list[ index ];
+
+ struct interp_buffer *buf = &netplayers.interp_data[ index ];
+ struct skeleton *sk = &localplayer.skeleton;
+ m4x3f *final_mtx = &netplayers.final_mtx[ sk->bone_count*index ];
+ struct player_board_pose *board_pose = &netplayers.board_poses[index];
+
+ struct player_subsystem_interface *sys0 = player_subsystems[f0->subsystem],
+ *sys1 = NULL;
+
+ struct player_board *board =
+ addon_cache_item_if_loaded( k_addon_type_board, player->board_view_slot );
+
+ player_pose pose0, pose1, posed;
+ sys0->pose( &f0->data, &pose0 );
+
+ u8 instance_id = 0;
+
+ f32 t = 0.0f;
+
+ if( f1 ){
+ t = (buf->t - f0->timestamp) / (f1->timestamp - f0->timestamp);
+ t = vg_clampf( t, 0.0f, 1.0f );
+
+ sys1 = player_subsystems[f1->subsystem];
+ sys1->pose( &f1->data, &pose1 );
+
+ u16 bounds = f0->boundary_hash^f1->boundary_hash;
+
+ if( bounds & NETMSG_BOUNDARY_BIT )
+ t = 1.0f;
+
+ if( bounds & NETMSG_GATE_BOUNDARY_BIT ){
+ /* TODO: Extra work retransforming the root_co, instance_id.. etc */
+ t = 1.0f;
+ }
+
+ instance_id = f1->flags & NETMSG_PLAYERFRAME_INSTANCE_ID;
+ lerp_player_pose( &pose0, &pose1, t, &posed );
+ effect_blink_apply( &player->effect_data.blink, &posed, vg.time_delta );
+
+ apply_full_skeleton_pose( sk, &posed, final_mtx );
+
+ if( t < 0.5f ){
+ if( sys0->effects )
+ sys0->effects( &f0->data, final_mtx, board, &player->effect_data );
+ }
+ else{
+ if( sys1->effects )
+ sys1->effects( &f1->data, final_mtx, board, &player->effect_data );
+ }
+
+ memcpy( board_pose, &posed.board, sizeof(*board_pose) );
+ }
+ else {
+ instance_id = f0->flags & NETMSG_PLAYERFRAME_INSTANCE_ID;
+ effect_blink_apply( &player->effect_data.blink, &pose0, vg.time_delta );
+ apply_full_skeleton_pose( sk, &pose0, final_mtx );
+ if( sys0->effects )
+ sys0->effects( &f0->data, final_mtx, board, &player->effect_data );
+ memcpy( board_pose, &pose0.board, sizeof(*board_pose) );
+ }
+
+ if( f0->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|
+ NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){
+ player->render_glider = 1;
+
+ v3f co;
+ v4f q;
+ f32 s;
+
+ if( f1 ){
+ v3_lerp( f0->data_glider.root_co, f1->data_glider.root_co, t, co );
+ q_nlerp( f0->data_glider.root_q, f1->data_glider.root_q, t, q );
+ s = vg_lerpf( f0->data_glider.s, f1->data_glider.s, t );
+ }
+ else {
+ v3_copy( f0->data_glider.root_co, co );
+ v4_copy( f0->data_glider.root_q, q );
+ s = f0->data_glider.s;
+ }
+
+ v3f *mtx = netplayers.glider_mtx[ index ];
+ q_m3x3( q, mtx );
+ m3x3_scalef( mtx, s );
+ v3_copy( co, mtx[3] );
+ }
+ else
+ player->render_glider = 0;
+
+ if( player->world_match[ instance_id ] )
+ player->active_world = &world_static.instances[ instance_id ];
+}
+
+/*
+ * animate remote player and store in final_mtx
+ */
+void animate_remote_player( u32 index )
+{
+ /*
+ * Trys to keep the cursor inside the buffer
+ */
+ f64 min_time = -999999999.9,
+ max_time = 999999999.9,
+ abs_max_time = -999999999.9;
+
+ struct interp_frame *minframe = NULL,
+ *maxframe = NULL,
+ *abs_max_frame = NULL;
+
+ struct interp_buffer *buf = &netplayers.interp_data[index];
+ for( u32 i=0; i<VG_ARRAY_LEN(buf->frames); i ++ ){
+ struct interp_frame *ifr = &buf->frames[i];
+
+ if( ifr->active ){
+ if( (ifr->timestamp > min_time) && (ifr->timestamp < buf->t) ){
+ min_time = ifr->timestamp;
+ minframe = ifr;
+ }
+
+ if( (ifr->timestamp < max_time) && (ifr->timestamp > buf->t) ){
+ max_time = ifr->timestamp;
+ maxframe = ifr;
+ }
+
+ if( ifr->timestamp > abs_max_time ){
+ abs_max_time = ifr->timestamp;
+ abs_max_frame = ifr;
+ }
+ }
+ }
+
+ struct network_player *player = &netplayers.list[ index ];
+ if( player->active != 2 )
+ player->active_world = NULL;
+
+ if( minframe && maxframe ){
+ pose_remote_player( index, minframe, maxframe );
+ buf->t += vg.time_frame_delta;
+ }
+ else {
+ buf->t = abs_max_time - 0.25;
+
+ if( abs_max_frame )
+ pose_remote_player( index, abs_max_frame, NULL );
+ else
+ return;
+ }
+}
+
+/*
+ * Update full final_mtx for all remote players
+ */
+void animate_remote_players(void)
+{
+ for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i ++ ){
+ struct network_player *player = &netplayers.list[i];
+ if( !player->active ) continue;
+
+ animate_remote_player( i );
+ }
+}
+
+/*
+ * Draw remote players
+ */
+void render_remote_players( world_instance *world, vg_camera *cam )
+{
+ u32 draw_list[ NETWORK_MAX_PLAYERS ],
+ draw_list_count = 0,
+ gliders = 0;
+
+ for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
+ struct network_player *player = &netplayers.list[i];
+ if( !player->active || player->isblocked ) continue;
+ if( player->active_world != world ) continue;
+
+#if 0
+ if( !player->isfriend &&
+ (world-world_static.instances == k_world_purpose_hub)) continue;
+#endif
+
+ draw_list[draw_list_count ++] = i;
+
+ if( player->render_glider )
+ gliders ++;
+ }
+
+ struct skeleton *sk = &localplayer.skeleton;
+
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+
+ for( u32 j=0; j<draw_list_count; j ++ ){
+ u32 index = draw_list[j];
+
+ struct network_player *player = &netplayers.list[index];
+ m4x3f *final_mtx = &netplayers.final_mtx[ sk->bone_count*index ];
+
+ struct player_model *model =
+ addon_cache_item_if_loaded( k_addon_type_player,
+ player->playermodel_view_slot );
+
+ if( !model ) model = &localplayer.fallback_model;
+ render_playermodel( cam, world, 0, model, sk, final_mtx );
+
+ struct player_board *board =
+ addon_cache_item_if_loaded( k_addon_type_board,
+ player->board_view_slot );
+ render_board( cam, world, board, final_mtx[localplayer.id_board],
+ &netplayers.board_poses[ index ], k_board_shader_player );
+ }
+
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+
+ if( !gliders )
+ return;
+
+ /* TODO: we really, really only need to do all this once. at some point
+ * PLEASE figure out a good place to do this once per frame!
+ */
+
+ shader_model_entity_use();
+ shader_model_entity_uTexMain( 0 );
+ shader_model_entity_uCamera( cam->transform[3] );
+ shader_model_entity_uPv( cam->mtx.pv );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_entity );
+
+ for( u32 j=0; j<draw_list_count; j ++ ){
+ u32 index = draw_list[j];
+
+ struct network_player *player = &netplayers.list[index];
+ if( !player->render_glider ) continue;
+
+ if( player->render_glider ){
+ v3f *glider_mtx = netplayers.glider_mtx[ index ];
+ render_glider_model( cam, world, glider_mtx, k_board_shader_entity );
+ }
+ }
+}
+
+static int remote_players_randomize( int argc, const char *argv[] ){
+ for( int i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
+ struct network_player *player = &netplayers.list[i];
+
+ player->active = (vg_randu32(&vg.rand) & 0x1)? 2: 0;
+ player->isfriend = vg_randu32(&vg.rand) & vg_randu32(&vg.rand) & 0x1;
+ player->isblocked = vg_randu32(&vg.rand) &
+ vg_randu32(&vg.rand) &
+ vg_randu32(&vg.rand) & 0x1;
+ player->world_match[ 0 ] = vg_randu32(&vg.rand) & 0x1;
+ player->world_match[ 1 ] = 0;
+
+ if( player->world_match[0] )
+ player->active_world = &world_static.instances[0];
+ else
+ player->active_world = NULL;
+
+ for( int i=0; i<sizeof(player->username)-1; i ++ ){
+ player->username[i] = 'a' + (vg_randu32(&vg.rand) % 30);
+ player->username[i+1] = '\0';
+
+ if( (vg_randu32(&vg.rand) % 8) == 3 )
+ break;
+ }
+
+ for( int i=0; i<3; i ++ ){
+ player->medals[i] = vg_randu32(&vg.rand) % 3;
+ }
+
+ v3f pos;
+
+ vg_rand_sphere( &vg.rand, pos );
+ v3_muladds( localplayer.rb.co, pos, 100.0f,
+ netplayers.final_mtx[ i*localplayer.skeleton.bone_count][3] );
+ }
+
+ return 0;
+}
+
+void remote_players_init(void)
+{
+ vg_console_reg_cmd( "add_test_players", remote_players_randomize, NULL );
+ vg_console_reg_var( "k_show_own_name", &k_show_own_name,
+ k_var_dtype_i32, 0 );
+ for( u32 i=0; i<NETWORK_SFX_QUEUE_LENGTH; i ++ ){
+ netplayers.sfx_queue[i].system = k_player_subsystem_invalid;
+ }
+}
+
+void remote_sfx_pre_update(void)
+{
+ for( u32 i=0; i<NETWORK_SFX_QUEUE_LENGTH; i ++ ){
+ struct net_sfx *si = &netplayers.sfx_queue[i];
+
+ if( si->system != k_player_subsystem_invalid ){
+ si->subframe -= vg.time_frame_delta;
+ if( si->subframe <= 0.0f ){
+ net_sfx_play( si );
+ si->system = k_player_subsystem_invalid;
+ }
+ }
+ }
+}
+
+/*
+ * animator->root_co of remote player
+ */
+static void remote_player_position( int id, v3f out_co ){
+ struct skeleton *sk = &localplayer.skeleton;
+ m4x3f *final_mtx = &netplayers.final_mtx[ sk->bone_count*id ];
+ v3_copy( final_mtx[0][3], out_co );
+}
+
+enum remote_player_gui_type {
+ k_remote_player_gui_type_stranger,
+ k_remote_player_gui_type_friend,
+ k_remote_player_gui_type_you,
+};
+
+/*
+ * Given players' root_co, get the screen point where we should draw tag info.
+ */
+static int player_tag_position( m4x4f pv, v3f root_co, ui_point out_point ){
+ v4f wpos;
+ v3_copy( root_co, wpos );
+ wpos[1] += 2.0f;
+ wpos[3] = 1.0f;
+
+ m4x4_mulv( pv, wpos, wpos );
+
+ if( wpos[3] > 0.0f ){
+ v2_muls( wpos, (1.0f/wpos[3]) * 0.5f, wpos );
+ v2_add( wpos, (v2f){ 0.5f, 0.5f }, wpos );
+
+ float k_max = 32000.0f;
+
+ out_point[0] = vg_clampf(wpos[0] * vg.window_x, -k_max, k_max );
+ out_point[1] = vg_clampf((1.0f-wpos[1]) * vg.window_y, -k_max, k_max );
+ return 1;
+ }
+ else
+ return 0;
+}
+
+/*
+ * Draw chat box relative to the root tag position on the screen
+ */
+static void chat_box( ui_context *ctx,
+ ui_point tag_root, f64 time, const char *message )
+{
+ if( (vg.time_real - time) > 15.0 )
+ return;
+
+ ui_rect wr;
+ wr[2] = ui_text_line_width( ctx, message ) + 8;
+ wr[3] = ctx->font->ch + 2;
+ wr[0] = tag_root[0]-(wr[2]/2);
+ wr[1] = tag_root[1] - wr[3] - 8;
+
+ ui_fill( ctx, wr, ui_opacity( ui_colour(ctx, k_ui_bg), 0.23f ) );
+ ui_text( ctx, wr, message, 1, k_ui_align_middle_center, 0 );
+}
+
+/*
+ * Draw full imgui for remote player
+ */
+static void remote_player_nametag( ui_context *ctx, ui_point tag_root,
+ struct network_player *player )
+{
+ ctx->font = &vgf_default_large;
+
+ ui_rect wr;
+ wr[2] = VG_MAX( ui_text_line_width( ctx, player->username ), 140 ) + 8;
+ wr[3] = 32;
+ wr[0] = tag_root[0]-(wr[2]/2);
+ wr[1] = tag_root[1]-(wr[3]/2);
+
+ ui_fill( ctx, wr, ui_opacity( ui_colour(ctx, k_ui_bg), 0.23f ) );
+ ui_text( ctx, wr, player->username, 1, k_ui_align_middle_center, 0 );
+ ctx->font = &vgf_default_small;
+
+ /* medals */
+ int cols = 0;
+ for( int i=0; i<3; i ++ )
+ if( player->medals[i] )
+ cols ++;
+
+ char buf[32];
+ vg_str str;
+
+ if( cols )
+ {
+ f32 w = (f32)wr[2] / (f32)cols;
+ cols = 0;
+
+ for( int i=0; i<3; i ++ )
+ {
+ if( player->medals[i] )
+ {
+ ui_rect col = { wr[0] + (f32)cols*w, wr[1] + wr[3],
+ w, ctx->font->ch };
+
+ vg_strnull( &str, buf, 32 );
+#if 0
+ vg_strcatch( &str, (char)k_SRglyph_vg_circle );
+#endif
+ vg_strcati32( &str, player->medals[i] );
+
+ ui_text( ctx, col, buf, 1, k_ui_align_middle_center,
+ ui_colour( ctx, (enum ui_scheme_colour[]){
+ k_ui_yellow, k_ui_gray, k_ui_orange }[i] ) );
+
+ cols ++;
+ }
+ }
+ }
+}
+
+static void remote_player_world_gui( ui_context *ctx, m4x4f pv, v3f root_co,
+ struct network_player *player ){
+ ui_point tag_root;
+ if( !player_tag_position( pv, root_co, tag_root ) )
+ return;
+
+ if( player )
+ {
+ remote_player_nametag( ctx, tag_root, player );
+ chat_box( ctx, tag_root, player->chat_time, player->chat );
+ }
+ else
+ {
+ if( netplayers.chatting )
+ chat_box( ctx, tag_root, vg.time_real, netplayers.chat_buffer );
+ else
+ {
+ chat_box( ctx, tag_root,
+ netplayers.chat_time, netplayers.chat_message );
+ }
+ }
+}
+
+static void remote_player_gui_info( ui_context *ctx, ui_rect box,
+ const char *username,
+ const char *activity,
+ enum remote_player_gui_type type,
+ int in_world ){
+
+ f32 opacity = in_world? 0.6f: 0.3f;
+
+ if( type == k_remote_player_gui_type_you )
+ ui_fill( ctx, box, ui_opacity( 0xff555555, opacity ) );
+ else
+ ui_fill( ctx, box, ui_opacity( 0xff000000, opacity ) );
+
+ if( type == k_remote_player_gui_type_friend )
+ ui_outline( ctx, box, -1, ui_opacity( 0xff00c4f0, opacity ), 0 );
+
+ ui_rect top, bottom;
+ ui_split_ratio( box, k_ui_axis_h, 0.6666f, 1, top, bottom );
+
+ u32 fg;
+
+ if( type == k_remote_player_gui_type_friend )
+ fg = ui_colour( ctx, k_ui_yellow + (in_world? k_ui_brighter: 0) );
+ else
+ fg = ui_colour( ctx, in_world? k_ui_fg: k_ui_fg+4 );
+
+ ctx->font = &vgf_default_large;
+ ui_text( ctx, top, username, 1, k_ui_align_middle_center, fg );
+ ctx->font = &vgf_default_small;
+ ui_text( ctx, bottom, activity, 1, k_ui_align_middle_center, fg );
+}
+
+void remote_players_imgui_lobby( ui_context *ctx )
+{
+ if( network_client.user_intent == k_server_intent_online ){
+ if( !(steam_ready &&
+ (network_client.state == k_ESteamNetworkingConnectionState_Connected)))
+ {
+ return;
+ }
+ }
+
+ ui_px y = 50, width = 200, height = 42, gap = 2,
+ x = vg.window_x - width;
+
+ ctx->font = &vgf_default_large;
+ ui_text( ctx, (ui_rect){ x, 0, width, height },
+ "In World", 1, k_ui_align_middle_center, 0 );
+ ctx->font = &vgf_default_small;
+
+
+ ui_rect us = { x, y, width, height };
+ /* FIXME: your location */
+ remote_player_gui_info( ctx, us, steam_username_at_startup, "you",
+ k_remote_player_gui_type_you, 1 );
+ y += height + gap;
+
+ for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ )
+ {
+ struct network_player *player = &netplayers.list[i];
+ if( !player->active || player->isblocked ) continue;
+
+ int in_same_world = player->active_world == world_current_instance();
+ if( !player->isfriend && !in_same_world )
+ continue;
+
+ const char *location = in_same_world? "": "another world";
+ if( player->region_flags & k_ent_region_flag_hasname ){
+ location = player->region;
+ }
+
+ ui_rect box = { x, y, width, height };
+ remote_player_gui_info( ctx, box, player->username, location,
+ player->isfriend, in_same_world );
+ y += height + gap;
+ }
+}
+
+void remote_players_imgui_world( ui_context *ctx,
+ world_instance *world, m4x4f pv,
+ f32 max_dist, int geo_cull )
+{
+ ui_flush( ctx, k_ui_shader_colour, NULL );
+
+ for( u32 i=0; i<NETWORK_MAX_PLAYERS; i++ )
+ {
+ struct network_player *player = &netplayers.list[i];
+ if( player->active )
+ {
+ v3f co;
+ remote_player_position( i, co );
+
+ if( !player->active_world )
+ continue;
+ if( !player->isfriend &&
+ (world-world_static.instances == k_world_purpose_hub)) continue;
+
+ /* their in our active subworld */
+ if( player->active_world != world )
+ {
+ m4x3_mulv( global_miniworld.mmdl, co, co );
+ co[1] -= 2.0f; /* HACK lol */
+ }
+
+ f32 d2 = v3_dist2( co, localplayer.rb.co );
+
+ if( d2 > (max_dist*max_dist) )
+ continue;
+
+ f32 dist = sqrtf(d2);
+ f32 opacity = 0.95f * sqrtf(((max_dist-dist)/max_dist));
+
+ if( geo_cull ){
+ ray_hit hit;
+ hit.dist = dist;
+
+ v3f dir;
+ v3_sub( co, g_render.cam.pos, dir );
+ v3_normalize( dir );
+
+ if( ray_world( world, g_render.cam.pos, dir, &hit,
+ k_material_flag_ghosts ) ){
+ opacity *= 0.5f;
+ }
+ }
+
+ player->opacity = vg_lerpf( player->opacity, opacity,
+ vg.time_frame_delta * 2.0f );
+
+ remote_player_world_gui( ctx, pv, co, player );
+
+ vg_ui.colour[3] = player->opacity;
+ ui_flush( ctx, k_ui_shader_colour, NULL );
+ }
+ }
+
+ vg_ui.colour[3] = 1.0f;
+ remote_player_world_gui( ctx, pv, localplayer.rb.co, NULL );
+ ui_flush( ctx, k_ui_shader_colour, NULL );
+}
+
+static void chat_escape( ui_context *ctx )
+{
+ netplayers.chatting = -1;
+}
+
+static void chat_enter( ui_context *ctx, char *buf, u32 len ){
+ vg_strncpy( buf, netplayers.chat_message, NETWORK_MAX_CHAT,
+ k_strncpy_always_add_null );
+ netplayers.chatting = -1;
+ netplayers.chat_time = vg.time_real;
+ chat_send_message( buf );
+}
+
+void remote_players_chat_imgui( ui_context *ctx )
+{
+ if( netplayers.chatting == 1 )
+ {
+ ui_rect box = { 0, 0, 400, 40 },
+ window = { 0, 0, vg.window_x, vg.window_y };
+ ui_rect_center( window, box );
+
+ struct ui_textbox_callbacks callbacks =
+ {
+ .enter = chat_enter,
+ .escape = chat_escape
+ };
+
+ ui_textbox( ctx, box, NULL,
+ netplayers.chat_buffer, NETWORK_MAX_CHAT, 1,
+ UI_TEXTBOX_AUTOFOCUS, &callbacks );
+ }
+ else
+ {
+ if( netplayers.chatting == -1 )
+ {
+ netplayers.chatting = 0;
+ srinput.state = k_input_state_resume;
+ }
+ else
+ {
+ if( (skaterift.activity == k_skaterift_default) &&
+ button_down( k_srbind_chat ) ){
+ netplayers.chatting = 1;
+ netplayers.chat_buffer[0] = '\0';
+ srinput.state = k_input_state_pause;
+ }
+ }
+ }
+}
--- /dev/null
+#pragma once
+#include "player.h"
+#include "network.h"
+#include "network_common.h"
+#include "player_render.h"
+#include "player_effects.h"
+#include "player_api.h"
+
+#include "player_skate.h"
+#include "player_walk.h"
+#include "player_dead.h"
+#include "player_basic_info.h"
+#include "player_glide.h"
+
+#define NETWORK_SFX_QUEUE_LENGTH 12
+
+struct global_netplayers
+{
+ struct network_player {
+ int active, isfriend, isblocked;
+ u64 steamid;
+ u16 board_view_slot, playermodel_view_slot;
+ enum player_subsystem subsystem;
+
+ /* this is set IF they exist in a world that we have loaded */
+ world_instance *active_world;
+ int world_match[ k_world_max ];
+ u32 location_pstr; /* TODO: valid if active_world set. */
+
+ char username[ NETWORK_USERNAME_MAX ];
+ char items[k_netmsg_playeritem_max][ADDON_UID_MAX];
+ char chat[ NETWORK_MAX_CHAT ];
+ char region[ NETWORK_REGION_MAX ];
+ u32 region_flags;
+ f64 chat_time;
+
+ /* ui */
+ u32 medals[3];
+ f32 opacity;
+
+ u32 down_bytes;
+ f32 down_kbs;
+
+ struct player_effects_data effect_data;
+ bool render_glider;
+ }
+ list[ NETWORK_MAX_PLAYERS ];
+
+ struct interp_buffer {
+ /* collect the most recent 6 frames of animation data */
+ struct interp_frame {
+ int active;
+ f64 timestamp;
+ enum player_subsystem subsystem;
+
+ u8 flags;
+ u16 boundary_hash;
+
+ union interp_animdata {
+ /* these aren't accessed directly, just used to take the
+ * max(sizeof) all systems */
+ struct player_skate_animator __skate;
+ struct player_walk_animator __walk;
+ struct player_dead_animator __dead;
+ struct player_basic_info_animator __basic;
+ }
+ data;
+
+ struct remote_glider_animator data_glider;
+ }
+ frames[ NETWORK_BUFFERFRAMES ];
+
+ f64 t;
+ }
+ interp_data[ NETWORK_MAX_PLAYERS ];
+
+ struct net_sfx sfx_queue[ NETWORK_SFX_QUEUE_LENGTH ];
+
+ m4x3f *final_mtx,
+ *glider_mtx;
+ struct player_board_pose board_poses[ NETWORK_MAX_PLAYERS ];
+
+ u32 up_bytes;
+ f32 up_kbs, down_kbs;
+ f64 last_data_measurement;
+
+ int chatting;
+ char chat_buffer[ NETWORK_MAX_CHAT ], chat_message[ NETWORK_MAX_CHAT ];
+ f64 chat_time;
+}
+extern netplayers;
+
+void player_remote_rx_200_300( SteamNetworkingMessage_t *msg );
+void remote_player_debug_update(void);
+void remote_player_send_playerframe(void);
+void animate_remote_player( u32 index );
+void animate_remote_players(void);
+void render_remote_players( world_instance *world, vg_camera *cam );
+void relink_all_remote_player_worlds(void);
+void player_remote_update_friendflags( struct network_player *remote );
+void remote_players_init(void);
+void remote_sfx_pre_update(void);
+void remote_player_network_imgui( ui_context *ctx, m4x4f pv );
+void remote_players_imgui_world( ui_context *ctx, world_instance *world,
+ m4x4f pv, f32 max_dist, int geo_cull );
+void remote_players_imgui_lobby( ui_context *ctx );
+void remote_players_chat_imgui( ui_context *ctx );
--- /dev/null
+#include "player.h"
+#include "player_render.h"
+#include "vg/vg_camera.h"
+#include "player_model.h"
+#include "ent_skateshop.h"
+#include "audio.h"
+#include "input.h"
+
+#include "shaders/model_character_view.h"
+#include "shaders/model_board_view.h"
+#include "shaders/model_entity.h"
+#include "shaders/model_board_view.h"
+#include "depth_compare.h"
+
+#include "network.h"
+#include "player_remote.h"
+#include "player_glide.h"
+
+void player_load_animation_reference( const char *path )
+{
+ mdl_context *meta = &localplayer.skeleton_meta;
+ mdl_open( meta, path, vg_mem.rtmemory );
+ mdl_load_metadata_block( meta, vg_mem.rtmemory );
+ mdl_load_animation_block( meta, vg_mem.rtmemory );
+ mdl_close( meta );
+
+ struct skeleton *sk = &localplayer.skeleton;
+ skeleton_setup( sk, vg_mem.rtmemory, meta );
+
+ localplayer.id_world = skeleton_bone_id( sk, "world" );
+ localplayer.id_hip = skeleton_bone_id( sk, "hips" );
+ localplayer.id_chest = skeleton_bone_id( sk, "chest" );
+ localplayer.id_ik_hand_l = skeleton_bone_id( sk, "hand.IK.L" );
+ localplayer.id_ik_hand_r = skeleton_bone_id( sk, "hand.IK.R" );
+ localplayer.id_ik_elbow_l = skeleton_bone_id( sk, "elbow.L" );
+ localplayer.id_ik_elbow_r = skeleton_bone_id( sk, "elbow.R" );
+ localplayer.id_head = skeleton_bone_id( sk, "head" );
+ localplayer.id_foot_l = skeleton_bone_id( sk, "foot.L" );
+ localplayer.id_foot_r = skeleton_bone_id( sk, "foot.R" );
+ localplayer.id_ik_foot_l = skeleton_bone_id( sk, "foot.IK.L" );
+ localplayer.id_ik_foot_r = skeleton_bone_id( sk, "foot.IK.R" );
+ localplayer.id_board = skeleton_bone_id( sk, "board" );
+ localplayer.id_wheel_l = skeleton_bone_id( sk, "wheel.L" );
+ localplayer.id_wheel_r = skeleton_bone_id( sk, "wheel.R" );
+ localplayer.id_ik_knee_l = skeleton_bone_id( sk, "knee.L" );
+ localplayer.id_ik_knee_r = skeleton_bone_id( sk, "knee.R" );
+ localplayer.id_eyes = skeleton_bone_id( sk, "eyes" );
+
+ for( i32 i=0; i<sk->bone_count; i ++ ){
+ localplayer.skeleton_mirror[i] = 0;
+ }
+
+ for( i32 i=1; i<sk->bone_count-1; i ++ ){
+ struct skeleton_bone *si = &sk->bones[i];
+
+ char tmp[64];
+ vg_str str;
+ vg_strnull( &str, tmp, 64 );
+ vg_strcat( &str, si->name );
+
+ char *L = vg_strch( &str, 'L' );
+ if( !L ) continue;
+ u32 len = L-tmp;
+
+ for( i32 j=i+1; j<sk->bone_count; j ++ ){
+ struct skeleton_bone *sj = &sk->bones[j];
+
+ if( !strncmp( si->name, sj->name, len ) ){
+ if( sj->name[len] == 'R' ){
+ localplayer.skeleton_mirror[i] = j;
+ localplayer.skeleton_mirror[j] = i;
+ break;
+ }
+ }
+ }
+ }
+
+ setup_ragdoll_from_skeleton( sk, &localplayer.ragdoll );
+
+ /* allocate matrix buffers for localplayer and remote players */
+ u32 mtx_size = sizeof(m4x3f)*sk->bone_count;
+ localplayer.final_mtx = vg_linear_alloc( vg_mem.rtmemory, mtx_size );
+ netplayers.final_mtx = vg_linear_alloc( vg_mem.rtmemory,
+ mtx_size*NETWORK_MAX_PLAYERS );
+ netplayers.glider_mtx = vg_linear_alloc( vg_mem.rtmemory,
+ sizeof(m4x3f)*NETWORK_MAX_PLAYERS );
+}
+
+/* TODO: Standard model load */
+
+void dynamic_model_load( mdl_context *ctx,
+ struct dynamic_model_1texture *mdl,
+ const char *path, u32 *fixup_table )
+{
+ if( !mdl_arrcount( &ctx->textures ) )
+ vg_fatal_error( "No texture in model" );
+
+ mdl_texture *tex0 = mdl_arritm( &ctx->textures, 0 );
+ void *data = vg_linear_alloc( vg_mem.scratch, tex0->file.pack_size );
+ mdl_fread_pack_file( ctx, &tex0->file, data );
+
+ vg_tex2d_load_qoi_async( data, tex0->file.pack_size,
+ VG_TEX2D_NEAREST|VG_TEX2D_CLAMP,
+ &mdl->texture );
+
+ mdl_async_load_glmesh( ctx, &mdl->mesh, fixup_table );
+}
+
+void dynamic_model_unload( struct dynamic_model_1texture *mdl )
+{
+ mesh_free( &mdl->mesh );
+ glDeleteTextures( 1, &mdl->texture );
+}
+
+/* TODO: allow error handling */
+void player_board_load( struct player_board *board, const char *path )
+{
+ vg_linear_clear( vg_mem.scratch );
+
+ mdl_context ctx;
+ mdl_open( &ctx, path, vg_mem.scratch );
+ mdl_load_metadata_block( &ctx, vg_mem.scratch );
+
+ dynamic_model_load( &ctx, &board->mdl, path, NULL );
+
+ mdl_array_ptr markers;
+ MDL_LOAD_ARRAY( &ctx, &markers, ent_marker, vg_mem.scratch );
+
+ /* TODO: you get put into a new section, the above is standard mdl loads. */
+ for( int i=0; i<4; i++ )
+ board->wheels[i].indice_count = 0;
+ for( int i=0; i<2; i++ )
+ board->trucks[i].indice_count = 0;
+ board->board.indice_count = 0;
+
+ for( u32 i=0; i<mdl_arrcount(&ctx.meshs); i++ ){
+ mdl_mesh *mesh = mdl_arritm( &ctx.meshs, i );
+
+ if( mdl_entity_id_type( mesh->entity_id ) != k_ent_marker )
+ continue;
+
+ u32 index = mdl_entity_id_id( mesh->entity_id );
+ ent_marker *marker = mdl_arritm( &markers, index );
+
+ mdl_submesh *sm0 = mdl_arritm( &ctx.submeshs, mesh->submesh_start );
+
+ const char *alias = mdl_pstr( &ctx, marker->pstr_alias );
+ u32 lr = marker->transform.co[0] > 0.0f? 1: 0,
+ fb = marker->transform.co[2] > 0.0f? 0: 1;
+
+ if( !strcmp( alias, "wheel" ) ){
+ u32 id = fb<<1 | lr;
+ board->wheels[ id ] = *sm0;
+ v3_copy( marker->transform.co, board->wheel_positions[ id ] );
+ }
+ else if( !strcmp( alias, "board" ) ){
+ board->board = *sm0;
+ v3_copy( marker->transform.co, board->board_position );
+ }
+ else if( !strcmp( alias, "truck" ) ){
+ board->trucks[ fb ] = *sm0;
+ v3_copy( marker->transform.co, board->truck_positions[ fb ] );
+ }
+ }
+
+ mdl_close( &ctx );
+}
+
+void player_board_unload( struct player_board *board )
+{
+ dynamic_model_unload( &board->mdl );
+}
+
+void player_model_load( struct player_model *board, const char *path)
+{
+ vg_linear_clear( vg_mem.scratch );
+
+ mdl_context ctx;
+ mdl_open( &ctx, path, vg_mem.scratch );
+ mdl_load_metadata_block( &ctx, vg_mem.scratch );
+
+ if( !ctx.armatures.count )
+ vg_fatal_error( "No armature in playermodel\n" );
+
+ mdl_armature *armature = mdl_arritm( &ctx.armatures, 0 );
+
+ u32 fixup_table[ armature->bone_count+1 ];
+ for( u32 i=0; i<armature->bone_count+1; i ++ )
+ fixup_table[i] = 0;
+
+ for( u32 i=1; i<localplayer.skeleton.bone_count; i ++ ){
+ struct skeleton_bone *sb = &localplayer.skeleton.bones[i];
+ u32 hash = vg_strdjb2( sb->name );
+
+ for( u32 j=1; j<armature->bone_count; j ++ ){
+ mdl_bone *bone = mdl_arritm( &ctx.bones, armature->bone_start+j );
+
+ if( mdl_pstreq( &ctx, bone->pstr_name, sb->name, hash ) ){
+ fixup_table[j+1] = i;
+ break;
+ }
+ }
+ }
+
+ dynamic_model_load( &ctx, &board->mdl, path, fixup_table );
+ mdl_close( &ctx );
+}
+
+void player_model_unload( struct player_model *board )
+{
+ dynamic_model_unload( &board->mdl );
+}
+
+void apply_full_skeleton_pose( struct skeleton *sk, player_pose *pose,
+ m4x3f *final_mtx ){
+ m4x3f transform;
+ q_m3x3( pose->root_q, transform );
+ v3_copy( pose->root_co, transform[3] );
+
+ if( pose->type == k_player_pose_type_ik ){
+ skeleton_apply_pose( sk, pose->keyframes,
+ k_anim_apply_defer_ik, final_mtx );
+ skeleton_apply_ik_pass( sk, final_mtx );
+ skeleton_apply_pose( sk, pose->keyframes,
+ k_anim_apply_deffered_only, final_mtx );
+ skeleton_apply_inverses( sk, final_mtx );
+ skeleton_apply_transform( sk, transform, final_mtx );
+ }
+ else if( pose->type == k_player_pose_type_fk_2 ){
+ skeleton_apply_pose( sk, pose->keyframes,
+ k_anim_apply_always, final_mtx );
+ skeleton_apply_inverses( sk, final_mtx );
+ skeleton_apply_transform( sk, transform, final_mtx );
+ }
+}
+
+void player__animate(void)
+{
+ struct player_subsystem_interface *sys =
+ player_subsystems[localplayer.subsystem];
+
+ struct player_board *board =
+ addon_cache_item_if_loaded( k_addon_type_board,
+ localplayer.board_view_slot );
+
+ sys->animate();
+
+ player_pose *pose = &localplayer.pose;
+ sys->pose( sys->animator_data, pose );
+
+ struct skeleton *sk = &localplayer.skeleton;
+
+ if( localplayer.holdout_time > 0.0f ){
+ skeleton_lerp_pose( sk,
+ pose->keyframes,localplayer.holdout_pose.keyframes,
+ localplayer.holdout_time, pose->keyframes );
+
+ v3_muladds( pose->root_co, localplayer.holdout_pose.root_co,
+ localplayer.holdout_time, pose->root_co );
+ q_nlerp( pose->root_q, localplayer.holdout_pose.root_q,
+ localplayer.holdout_time, pose->root_q );
+
+ localplayer.holdout_time -= vg.time_frame_delta / 0.25f;
+ }
+
+ effect_blink_apply( &localplayer.effect_data.blink,
+ &localplayer.pose, vg.time_delta );
+ apply_full_skeleton_pose( sk, &localplayer.pose, localplayer.final_mtx );
+
+ if( sys->effects ){
+ sys->effects( sys->animator_data, localplayer.final_mtx, board,
+ &localplayer.effect_data );
+ }
+
+ skeleton_debug( sk, localplayer.final_mtx );
+
+ if( sys->post_animate )
+ sys->post_animate();
+
+ player__observe_system( localplayer.subsystem );
+ if( sys->sfx_comp )
+ sys->sfx_comp( sys->animator_data );
+
+ player__cam_iterate();
+}
+
+static void player_copy_frame_animator( replay_frame *frame ){
+ struct player_subsystem_interface *sys =
+ player_subsystems[localplayer.subsystem];
+
+ if( sys->animator_size ){
+ void *src = replay_frame_data( frame, k_replay_framedata_animator );
+ memcpy( sys->animator_data, src, sys->animator_size );
+ }
+}
+
+void lerp_player_pose( player_pose *pose0, player_pose *pose1, f32 t,
+ player_pose *posed ){
+ struct skeleton *sk = &localplayer.skeleton;
+
+ v3_lerp( pose0->root_co, pose1->root_co, t, posed->root_co );
+ q_nlerp( pose0->root_q, pose1->root_q, t, posed->root_q );
+ posed->type = pose0->type;
+ posed->board.lean = vg_lerpf( pose0->board.lean, pose1->board.lean, t );
+
+ if( pose0->type != pose1->type ){
+ /* it would be nice to apply IK pass in-keyframes. TOO BAD! */
+ skeleton_copy_pose( sk, pose0->keyframes, posed->keyframes );
+ }
+ else {
+ skeleton_lerp_pose( sk, pose0->keyframes, pose1->keyframes, t,
+ posed->keyframes );
+ }
+}
+
+void player__observe_system( enum player_subsystem id )
+{
+ if( id != localplayer.observing_system ){
+ struct player_subsystem_interface *sysm1 =
+ player_subsystems[ localplayer.observing_system ];
+
+ if( sysm1->sfx_kill ) sysm1->sfx_kill();
+ localplayer.observing_system = id;
+ }
+}
+
+void player__animate_from_replay( replay_buffer *replay )
+{
+ replay_frame *frame = replay->cursor_frame,
+ *next = NULL;
+ if( frame ){
+ next = frame->r;
+
+ struct player_subsystem_interface
+ *sys0 = player_subsystems[frame->system];
+ void *a0 = replay_frame_data( frame, k_replay_framedata_animator );
+
+ struct replay_glider_data
+ *g0 = replay_frame_data( frame, k_replay_framedata_glider ),
+ *g1;
+
+ f32 t = 0.0f;
+
+ if( next ){
+ t = replay_subframe_time( replay );
+
+ player_pose pose0, pose1;
+
+ struct player_subsystem_interface
+ *sys1 = player_subsystems[next->system];
+ void *a1 = replay_frame_data( next, k_replay_framedata_animator );
+
+ sys0->pose( a0, &pose0 );
+ sys1->pose( a1, &pose1 );
+
+ lerp_player_pose( &pose0, &pose1, t, &localplayer.pose );
+ g1 = replay_frame_data( next, k_replay_framedata_glider );
+ }
+ else{
+ sys0->pose( a0, &localplayer.pose );
+ g1 = NULL;
+ }
+
+ player__observe_system( frame->system );
+ if( sys0->sfx_comp )
+ sys0->sfx_comp( a0 );
+
+ if( g0 ){
+ if( g0->glider_orphan ){
+ if( g1 ){
+ v3_lerp( g0->co, g1->co, t, player_glide.rb.co );
+ q_nlerp( g0->q, g1->q, t, player_glide.rb.q );
+ }
+ else {
+ v3_copy( g0->co, player_glide.rb.co );
+ v4_copy( g0->q, player_glide.rb.q );
+ }
+
+ rb_update_matrices( &player_glide.rb );
+ }
+
+ if( g1 )
+ player_glide.t = vg_lerpf( g0->t, g1->t, t );
+ else
+ player_glide.t = g0->t;
+
+ localplayer.have_glider = g0->have_glider;
+ localplayer.glider_orphan = g0->glider_orphan;
+ }
+ else /* no glider data in g1, or edge case we dont care about */ {
+ localplayer.have_glider = 0;
+ localplayer.glider_orphan = 0;
+ player_glide.t = 0.0f;
+ }
+ }
+ else return;
+
+ apply_full_skeleton_pose( &localplayer.skeleton, &localplayer.pose,
+ localplayer.final_mtx );
+}
+
+void player__pre_render(void)
+{
+ /* shadowing/ao info */
+ struct player_board *board =
+ addon_cache_item_if_loaded( k_addon_type_board,
+ localplayer.board_view_slot );
+ v3f vp0, vp1;
+ if( board ){
+ v3_copy((v3f){0.0f,0.1f, board->truck_positions[0][2]}, vp0 );
+ v3_copy((v3f){0.0f,0.1f, board->truck_positions[1][2]}, vp1 );
+ }
+ else{
+ v3_zero( vp0 );
+ v3_zero( vp1 );
+ }
+
+ struct ub_world_lighting *ubo = &world_current_instance()->ub_lighting;
+ v3f *board_mtx = localplayer.final_mtx[ localplayer.id_board ];
+ m4x3_mulv( board_mtx, vp0, ubo->g_board_0 );
+ m4x3_mulv( board_mtx, vp1, ubo->g_board_1 );
+}
+
+void render_board( vg_camera *cam, world_instance *world,
+ struct player_board *board, m4x3f root,
+ struct player_board_pose *pose,
+ enum board_shader shader )
+{
+ if( !board )
+ board = &localplayer.fallback_board;
+
+ /* TODO:
+ * adding depth compare to this shader
+ */
+
+ v3f inverse;
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, board->mdl.texture );
+
+ if( shader == k_board_shader_player )
+ {
+ shader_model_board_view_use();
+ shader_model_board_view_uTexMain( 0 );
+ shader_model_board_view_uCamera( cam->transform[3] );
+ shader_model_board_view_uPv( cam->mtx.pv );
+
+ shader_model_board_view_uDepthMode(1);
+ depth_compare_bind(
+ shader_model_board_view_uTexSceneDepth,
+ shader_model_board_view_uInverseRatioDepth,
+ shader_model_board_view_uInverseRatioMain,
+ cam );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_board_view );
+ }
+ else if( shader == k_board_shader_entity )
+ {
+ shader_model_entity_use();
+ shader_model_entity_uTexMain( 0 );
+ shader_model_entity_uCamera( cam->transform[3] );
+ shader_model_entity_uPv( cam->mtx.pv );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_entity );
+ }
+
+ mesh_bind( &board->mdl.mesh );
+
+ m4x4f m4mdl;
+
+ if( board->board.indice_count ){
+ m4x3f mlocal;
+ m3x3_identity( mlocal );
+
+ mdl_keyframe kf;
+ v3_zero( kf.co );
+ q_identity( kf.q );
+ v3_zero( kf.s );
+
+ v4f qroll;
+ q_axis_angle( qroll, (v3f){0.0f,0.0f,1.0f}, pose->lean * 0.6f );
+ keyframe_rotate_around( &kf, (v3f){0.0f,0.11f,0.0f},
+ (v3f){0.0f,0.0f,0.0f}, qroll );
+
+ v3_add( board->board_position, kf.co, mlocal[3] );
+ q_m3x3( kf.q, mlocal );
+
+ m4x3_mul( root, mlocal, mlocal );
+
+ if( shader == k_board_shader_entity ){
+ /* TODO: provide a way to supply previous mdl mtx? */
+ m4x3_expand( mlocal, m4mdl );
+ m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
+ shader_model_entity_uPvmPrev( m4mdl );
+ shader_model_entity_uMdl( mlocal );
+ }
+ else
+ shader_model_board_view_uMdl( mlocal );
+
+ mdl_draw_submesh( &board->board );
+ }
+
+ for( int i=0; i<2; i++ ){
+ if( !board->trucks[i].indice_count )
+ continue;
+
+ m4x3f mlocal;
+ m3x3_identity( mlocal );
+ v3_copy( board->truck_positions[i], mlocal[3] );
+ m4x3_mul( root, mlocal, mlocal );
+
+ if( shader == k_board_shader_entity ){
+ m4x3_expand( mlocal, m4mdl );
+ m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
+ shader_model_entity_uPvmPrev( m4mdl );
+ shader_model_entity_uMdl( mlocal );
+ }
+ else
+ shader_model_board_view_uMdl( mlocal );
+
+ mdl_draw_submesh( &board->trucks[i] );
+ }
+
+ for( int i=0; i<4; i++ ){
+ if( !board->wheels[i].indice_count )
+ continue;
+
+ m4x3f mlocal;
+ m3x3_identity( mlocal );
+ v3_copy( board->wheel_positions[i], mlocal[3] );
+ m4x3_mul( root, mlocal, mlocal );
+
+ if( shader == k_board_shader_entity ){
+ m4x3_expand( mlocal, m4mdl );
+ m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
+ shader_model_entity_uPvmPrev( m4mdl );
+ shader_model_entity_uMdl( mlocal );
+ }
+ else
+ shader_model_board_view_uMdl( mlocal );
+
+ mdl_draw_submesh( &board->wheels[i] );
+ }
+}
+
+void render_playermodel( vg_camera *cam, world_instance *world,
+ int depth_compare,
+ struct player_model *model,
+ struct skeleton *skeleton,
+ m4x3f *final_mtx )
+{
+ if( !model ) return;
+
+ shader_model_character_view_use();
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, model->mdl.texture );
+ shader_model_character_view_uTexMain( 0 );
+ shader_model_character_view_uCamera( cam->transform[3] );
+ shader_model_character_view_uPv( cam->mtx.pv );
+ shader_model_character_view_uDepthMode( depth_compare );
+ if( depth_compare )
+ {
+ depth_compare_bind(
+ shader_model_character_view_uTexSceneDepth,
+ shader_model_character_view_uInverseRatioDepth,
+ shader_model_character_view_uInverseRatioMain,
+ cam );
+ }
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_character_view );
+
+ glUniformMatrix4x3fv( _uniform_model_character_view_uTransforms,
+ skeleton->bone_count,
+ 0,
+ (const GLfloat *)final_mtx );
+
+ mesh_bind( &model->mdl.mesh );
+ mesh_draw( &model->mdl.mesh );
+}
+
+void player__render( vg_camera *cam )
+{
+ world_instance *world = world_current_instance();
+ SDL_AtomicLock( &addon_system.sl_cache_using_resources );
+
+ struct player_model *model =
+ addon_cache_item_if_loaded( k_addon_type_player,
+ localplayer.playermodel_view_slot );
+
+ if( !model ) model = &localplayer.fallback_model;
+ render_playermodel( cam, world, 1, model, &localplayer.skeleton,
+ localplayer.final_mtx );
+
+ struct player_board *board =
+ addon_cache_item_if_loaded( k_addon_type_board,
+ localplayer.board_view_slot );
+
+ render_board( cam, world, board, localplayer.final_mtx[localplayer.id_board],
+ &localplayer.pose.board, k_board_shader_player );
+
+ SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
+
+ glEnable( GL_CULL_FACE );
+ player_glide_render( cam, world, &localplayer.pose );
+ glDisable( GL_CULL_FACE );
+}
+
+void player_mirror_pose( mdl_keyframe pose[32], mdl_keyframe mirrored[32] )
+{
+ mdl_keyframe temp[32];
+
+ struct skeleton *sk = &localplayer.skeleton;
+ for( u32 i=1; i<sk->bone_count; i ++ ){
+ mdl_keyframe *dest = &temp[i-1];
+ u8 mapping = localplayer.skeleton_mirror[i];
+
+ if( mapping ) *dest = pose[mapping-1]; /* R */
+ else *dest = pose[i-1]; /* L */
+
+ dest->co[2] *= -1.0f;
+ dest->q[0] *= -1.0f;
+ dest->q[1] *= -1.0f;
+ }
+
+ for( u32 i=0; i<sk->bone_count-1; i ++ ){
+ mirrored[i] = temp[i];
+ }
+}
--- /dev/null
+#pragma once
+#include "model.h"
+#include "skeleton.h"
+#include "vg/vg_camera.h"
+#include "world.h"
+#include "player_render.h"
+#include "player_api.h"
+#include "player_replay.h"
+
+enum eboard_truck{
+ k_board_truck_back = 0,
+ k_board_truck_front = 1
+};
+
+enum eboard_wheel{
+ k_board_wheel_fl = 0,
+ k_board_wheel_fr = 1,
+ k_board_wheel_bl = 2,
+ k_board_wheel_br = 3,
+};
+
+/* TODO: Fully featured dynamic models
+ * This is FAR from the final system we want at all, but it will do for now */
+struct dynamic_model_1texture{
+ glmesh mesh;
+ GLuint texture;
+};
+
+struct player_board{
+ struct dynamic_model_1texture mdl;
+
+ v4f wheel_positions[4],
+ truck_positions[2],
+ board_position;
+
+ mdl_submesh wheels[4],
+ trucks[2],
+ board;
+};
+
+struct player_model{
+ struct dynamic_model_1texture mdl;
+};
+
+enum board_shader{
+ k_board_shader_player,
+ k_board_shader_entity
+};
+
+void dynamic_model_load( mdl_context *ctx,
+ struct dynamic_model_1texture *mdl,
+ const char *path, u32 *fixup_table );
+void dynamic_model_unload( struct dynamic_model_1texture *mdl );
+
+void player_board_load( struct player_board *mdl, const char *path );
+void player_board_unload( struct player_board *mdl );
+
+void player_model_load( struct player_model *board, const char *path);
+void player_model_unload( struct player_model *board );
+
+void render_board( vg_camera *cam, world_instance *world,
+ struct player_board *board, m4x3f root,
+ struct player_board_pose *pose,
+ enum board_shader shader );
+
+void render_playermodel( vg_camera *cam, world_instance *world,
+ int depth_compare,
+ struct player_model *model,
+ struct skeleton *skeleton,
+ m4x3f *final_mtx );
+void apply_full_skeleton_pose( struct skeleton *sk, player_pose *pose,
+ m4x3f *final_mtx );
+void lerp_player_pose( player_pose *pose0, player_pose *pose1, f32 t,
+ player_pose *posed );
+void player_mirror_pose( mdl_keyframe pose[32],
+ mdl_keyframe mirrored[32] );
+void player__observe_system( enum player_subsystem id );
+void player_load_animation_reference( const char *path );
+void player__render( vg_camera *cam );
+void player__animate_from_replay( replay_buffer *replay );
+void player__animate(void);
+void player__pre_render(void);
--- /dev/null
+#include "skaterift.h"
+#include "player.h"
+#include "player_replay.h"
+#include "input.h"
+#include "gui.h"
+#include "freecam.h"
+
+#include "player_walk.h"
+#include "player_skate.h"
+#include "player_dead.h"
+#include "player_glide.h"
+
+struct replay_globals player_replay =
+{
+ .active_keyframe = -1,
+ .show_ui = 1,
+ .editor_mode = 0
+};
+
+void replay_clear( replay_buffer *replay )
+{
+ replay->head = NULL;
+ replay->tail = NULL;
+ replay->cursor_frame = NULL;
+ replay->statehead = NULL;
+ replay->cursor = -99999.9;
+}
+
+void *replay_frame_data( replay_frame *frame, enum replay_framedata type )
+{
+ if( frame->data_table[type][1] == 0 )
+ return NULL;
+
+ void *baseptr = frame;
+ return baseptr + frame->data_table[type][0];
+}
+
+static u16 replay_frame_calculate_data_offsets(
+ u16 data_table[k_replay_framedata_rows][2] ){
+
+ u32 total = vg_align8( sizeof(replay_frame) );
+ for( u32 i=0; i<k_replay_framedata_rows; i++ ){
+ data_table[i][0] = total;
+ total += vg_align8(data_table[i][1]);
+
+ if( total > 0xffff )
+ vg_fatal_error( "Exceeded frame storage capacity\n" );
+ }
+ return total;
+}
+
+static void replay_tailpop( replay_buffer *replay ){
+ if( replay->cursor_frame == replay->tail )
+ replay->cursor_frame = NULL;
+ if( replay->statehead == replay->tail )
+ replay->statehead = NULL;
+
+ replay->tail = replay->tail->r;
+
+ if( replay->tail )
+ replay->tail->l = NULL;
+ else
+ replay->head = NULL;
+}
+
+static replay_frame *replay_newframe( replay_buffer *replay,
+ u16 animator_size,
+ u16 gamestate_size,
+ u16 sfx_count,
+ bool save_glider ){
+ u16 data_table[ k_replay_framedata_rows ][2];
+ data_table[ k_replay_framedata_animator ][1] = animator_size;
+ data_table[ k_replay_framedata_gamestate ][1] = gamestate_size;
+ data_table[ k_replay_framedata_sfx ][1] = sfx_count*sizeof(struct net_sfx);
+ data_table[ k_replay_framedata_internal_gamestate ][1] = 0;
+ if( gamestate_size )
+ {
+ data_table[ k_replay_framedata_internal_gamestate ][1] =
+ sizeof( replay_gamestate );
+ }
+
+ data_table[ k_replay_framedata_glider ][1] = 0;
+ if( save_glider )
+ {
+ data_table[ k_replay_framedata_glider ][1] =
+ sizeof(struct replay_glider_data);
+ }
+
+ u32 nextsize = replay_frame_calculate_data_offsets( data_table );
+
+ replay_frame *frame = NULL;
+ if( replay->head )
+ {
+ u32 headsize = replay->head->total_size,
+ nextpos = ((void *)replay->head - replay->data) + headsize;
+
+ if( nextpos + nextsize > replay->size )
+ {
+ nextpos = 0;
+
+ /* maintain contiguity */
+ while( replay->tail )
+ {
+ if( (void *)replay->tail - replay->data )
+ replay_tailpop( replay );
+ else break;
+ }
+ }
+
+check_again:;
+ u32 tailpos = (void *)replay->tail - replay->data;
+
+ if( tailpos >= nextpos )
+ {
+ if( nextpos + nextsize > tailpos )
+ {
+ replay_tailpop( replay );
+
+ if( replay->tail )
+ goto check_again;
+ }
+ }
+
+ frame = replay->data + nextpos;
+
+ if( replay->head )
+ replay->head->r = frame;
+ }
+ else
+ frame = replay->data;
+
+ for( u32 i=0; i<k_replay_framedata_rows; i++ )
+ {
+ frame->data_table[i][0] = data_table[i][0];
+ frame->data_table[i][1] = data_table[i][1];
+ }
+
+ frame->total_size = nextsize;
+ frame->l = replay->head;
+ frame->r = NULL;
+ replay->head = frame;
+ if( !replay->tail ) replay->tail = frame;
+ if( gamestate_size ) replay->statehead = frame;
+
+ return frame;
+}
+
+static void replay_emit_frame_sounds( replay_frame *frame ){
+ void *baseptr = frame;
+ u16 *inf = frame->data_table[k_replay_framedata_sfx];
+ struct net_sfx *buffer = baseptr + inf[0];
+ u32 count = inf[1] / sizeof(struct net_sfx);
+
+ for( u32 i=0; i<count; i ++ ){
+ net_sfx_play( buffer + i );
+ }
+}
+
+int replay_seek( replay_buffer *replay, f64 t )
+{
+ if( !replay->head ) return 0;
+
+ if( t < replay->tail->time ) t = replay->tail->time;
+ if( t > replay->head->time ) t = replay->head->time;
+
+ if( !replay->cursor_frame ) {
+ replay->cursor = replay->head->time;
+ replay->cursor_frame = replay->head;
+
+ if( fabs(replay->head->time-t) > fabs(replay->tail->time-t) ){
+ replay->cursor = replay->tail->time;
+ replay->cursor_frame = replay->tail;
+ }
+ }
+
+ f64 dir = t - replay->cursor;
+ if( dir == 0.0 ) return 0;
+ dir = vg_signf( dir );
+
+
+ u32 i=4096;
+ while( i --> 0 ){
+ if( dir < 0.0 ){
+ if( t > replay->cursor_frame->time ) {
+ replay->cursor = t;
+ return 1;
+ }
+ }
+
+ replay_frame *next;
+ if( dir > 0.0 ) next = replay->cursor_frame->r;
+ else next = replay->cursor_frame->l;
+
+ if( !next ) break;
+
+ if( dir > 0.0 ){
+ if( t < next->time ){
+ replay->cursor = t;
+ return 1;
+ }
+ }
+
+ replay_emit_frame_sounds( next );
+
+ replay->cursor_frame = next;
+ replay->cursor = next->time;
+
+ if( !i ) return 1;
+ }
+
+ replay->cursor = t;
+ return 0;
+}
+
+replay_frame *replay_find_recent_stateframe( replay_buffer *replay )
+{
+ replay_frame *frame = replay->cursor_frame;
+ u32 i=4096;
+ while( i --> 0 ){
+ if( !frame ) return frame;
+ if( frame->data_table[ k_replay_framedata_gamestate ][1] ) return frame;
+ frame = frame->l;
+ }
+
+ return NULL;
+}
+
+f32 replay_subframe_time( replay_buffer *replay )
+{
+ replay_frame *frame = replay->cursor_frame;
+ if( !frame ) return 0.0f;
+ replay_frame *next = frame->r;
+ if( next )
+ {
+ f64 l = next->time - frame->time,
+ t = (l <= (1.0/128.0))? 0.0: (replay->cursor - frame->time) / l;
+ return vg_clampf( t, 0.0f, 1.0f );
+ }
+ else
+ return 0.0f;
+}
+
+void replay_get_frame_camera( replay_frame *frame, vg_camera *cam )
+{
+ cam->fov = frame->cam.fov;
+ v3_copy( frame->cam.pos, cam->pos );
+ v3_copy( frame->cam.angles, cam->angles );
+}
+
+void replay_get_camera( replay_buffer *replay, vg_camera *cam )
+{
+ cam->nearz = 0.1f;
+ cam->farz = 100.0f;
+ if( replay->cursor_frame )
+ {
+ replay_frame *next = replay->cursor_frame->r;
+
+ if( next )
+ {
+ vg_camera temp;
+
+ replay_get_frame_camera( replay->cursor_frame, cam );
+ replay_get_frame_camera( next, &temp );
+ vg_camera_lerp( cam, &temp, replay_subframe_time( replay ), cam );
+ }
+ else
+ {
+ replay_get_frame_camera( replay->cursor_frame, cam );
+ }
+ }
+ else
+ {
+ v3_zero( cam->pos );
+ v3_zero( cam->angles );
+ cam->fov = 90.0f;
+ }
+}
+
+void skaterift_get_replay_cam( vg_camera *cam )
+{
+ replay_buffer *replay = &player_replay.local;
+
+ if( player_replay.active_keyframe != -1 )
+ {
+ replay_keyframe *kf =
+ &player_replay.keyframes[player_replay.active_keyframe];
+
+ v3_copy( kf->cam.pos, cam->pos );
+ v3_copy( kf->cam.angles, cam->angles );
+ cam->fov = kf->cam.fov;
+ return;
+ }
+
+ if( player_replay.keyframe_count >= 2 )
+ {
+ for( u32 i=0; i<player_replay.keyframe_count-1; i ++ )
+ {
+ replay_keyframe *kf = &player_replay.keyframes[i];
+
+ if( (kf[0].time<=replay->cursor) && (kf[1].time>replay->cursor) )
+ {
+ f64 l = kf[1].time - kf[0].time,
+ t = (l <= (1.0/128.0))? 0.0: (replay->cursor-kf[0].time) / l;
+
+ if( player_replay.keyframe_count >= 3 )
+ {
+ f32 m_start = 0.5f, m_end = 0.5f;
+
+ if( i > 0 )
+ {
+ if( (t < 0.5f) || (i==player_replay.keyframe_count-2) )
+ {
+ kf --;
+ }
+ }
+
+ u32 last = player_replay.keyframe_count-1;
+ if( kf+0 == player_replay.keyframes ) m_start = 1.0f;
+ if( kf+2 == player_replay.keyframes+last ) m_end = 1.0f;
+
+ f32 ts = vg_lerpf( kf[0].time, kf[1].time, 1.0f-m_start ),
+ te = vg_lerpf( kf[1].time, kf[2].time, m_end );
+
+ l = te-ts;
+ t = (replay->cursor-ts)/l;
+
+ /*
+ * Adjust t, so that its derivative matches at the endpoints.
+ * Since t needs to go from 0 to 1, it will naturally change at
+ * different rates between keyframes. So this smooths it out.
+ *
+ * Newton method, going through standard direct quadratic eq has
+ * precision / other problems. Also we only care about 0>t>1.
+ */
+ f32 b = (kf[1].time-ts)/l,
+ x0 = 1.0-t;
+ for( u32 i=0; i<4; i ++ )
+ {
+ f32 ix0 = 1.0f-x0,
+ fx_x0 = 2.0f*b*x0*ix0 + ix0*ix0 - t,
+ fxd_x0 = 2.0f*(-2.0f*b*x0 + b + x0 - 1.0f);
+ x0 = x0 - (fx_x0/fxd_x0);
+ }
+ t = 1.0-x0;
+
+ f32 t0 = t*m_start+(1.0f-m_start),
+ t1 = t*m_end;
+
+ v3f ps, pe, as, ae;
+ f32 fs, fe;
+
+ /* first order */
+ v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t0, ps );
+ vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles,
+ t0, as );
+ fs = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t0 );
+
+ v3_lerp( kf[1].cam.pos, kf[2].cam.pos, t1, pe );
+ vg_camera_lerp_angles( kf[1].cam.angles, kf[2].cam.angles,
+ t1, ae );
+ fe = vg_lerpf( kf[1].cam.fov, kf[2].cam.fov, t1 );
+
+ /* second order */
+ v3_lerp( ps, pe, t, cam->pos );
+ vg_camera_lerp_angles( as, ae, t, cam->angles );
+ cam->fov = vg_lerpf( fs, fe, t );
+ }
+ else
+ {
+ v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t, cam->pos );
+ vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles,
+ t, cam->angles );
+ cam->fov = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t );
+ }
+ return;
+ }
+ }
+ }
+
+ replay_get_camera( replay, cam );
+}
+
+struct replay_rb
+{
+ v3f co, v, w;
+ v4f q;
+};
+
+void skaterift_record_frame( replay_buffer *replay, int force_gamestate )
+{
+ f64 delta = 9999999.9,
+ statedelta = 9999999.9;
+
+ if( replay->head )
+ delta = vg.time - replay->head->time;
+
+ if( replay->statehead )
+ statedelta = vg.time - replay->statehead->time;
+
+ const f64 k_replay_rate = 1.0/30.0,
+ k_gamestate_rate = 0.5;
+
+ int save_frame = 0,
+ save_state = 0,
+ save_glider = 0;
+
+ if( force_gamestate ) save_state = 1;
+ if( statedelta > k_gamestate_rate ) save_state = 1;
+ if( delta > k_replay_rate ) save_frame = 1;
+ if( save_state ) save_frame = 1;
+
+ if( localplayer.have_glider || localplayer.glider_orphan ||
+ localplayer.subsystem == k_player_subsystem_glide ){
+ save_glider = 1;
+ }
+
+ if( !save_frame ) return;
+
+ u16 gamestate_size = 0;
+ if( save_state ){
+ /* TODO: have as part of system struct */
+ gamestate_size = (u32 []){
+ [k_player_subsystem_walk ] = sizeof(struct player_walk_state),
+ [k_player_subsystem_drive] = 0,
+ [k_player_subsystem_skate] = sizeof(struct player_skate_state),
+ [k_player_subsystem_dead ] = localplayer.ragdoll.part_count *
+ sizeof(struct replay_rb),
+ [k_player_subsystem_glide] = sizeof(struct replay_rb),
+ }[ localplayer.subsystem ];
+ }
+
+ u16 animator_size = player_subsystems[localplayer.subsystem]->animator_size;
+
+ replay_frame *frame = replay_newframe( replay,
+ animator_size, gamestate_size,
+ localplayer.local_sfx_buffer_count,
+ save_glider );
+ frame->system = localplayer.subsystem;
+
+ if( save_state ){
+ replay_gamestate *gs =
+ replay_frame_data( frame, k_replay_framedata_internal_gamestate );
+
+ gs->current_run_version = world_static.current_run_version;
+ gs->drowned = localplayer.drowned;
+
+ /* permanent block */
+ memcpy( &gs->rb, &localplayer.rb, sizeof(rigidbody) );
+ memcpy( &gs->glider_rb, &player_glide.rb, sizeof(rigidbody) );
+ memcpy( &gs->cam_control, &localplayer.cam_control,
+ sizeof(struct player_cam_controller) );
+ v3_copy( localplayer.angles, gs->angles );
+
+ void *dst = replay_frame_data( frame, k_replay_framedata_gamestate );
+
+ /* subsytem/dynamic block */
+ if( localplayer.subsystem == k_player_subsystem_walk )
+ memcpy( dst, &player_walk.state, gamestate_size );
+ else if( localplayer.subsystem == k_player_subsystem_skate )
+ memcpy( dst, &player_skate.state, gamestate_size );
+ else if( localplayer.subsystem == k_player_subsystem_dead ){
+ struct replay_rb *arr = dst;
+ for( u32 i=0; i<localplayer.ragdoll.part_count; i ++ ){
+ rigidbody *rb = &localplayer.ragdoll.parts[i].rb;
+ v3_copy( rb->co, arr[i].co );
+ v3_copy( rb->w, arr[i].w );
+ v3_copy( rb->v, arr[i].v );
+ v4_copy( rb->q, arr[i].q );
+ }
+ }
+ else if( localplayer.subsystem == k_player_subsystem_glide ){
+ struct replay_rb *arr = dst;
+ rigidbody *rb = &player_glide.rb;
+ v3_copy( rb->co, arr[0].co );
+ v3_copy( rb->w, arr[0].w );
+ v3_copy( rb->v, arr[0].v );
+ v4_copy( rb->q, arr[0].q );
+ }
+ }
+
+ if( save_glider ){
+ struct replay_glider_data *inf =
+ replay_frame_data( frame, k_replay_framedata_glider );
+
+ inf->have_glider = localplayer.have_glider;
+ inf->glider_orphan = localplayer.glider_orphan;
+ inf->t = player_glide.t;
+ v3_copy( player_glide.rb.co, inf->co );
+ v4_copy( player_glide.rb.q, inf->q );
+ }
+
+ replay->cursor = vg.time;
+ replay->cursor_frame = frame;
+ frame->time = vg.time;
+
+ /* camera */
+ v3_copy( localplayer.cam.pos, frame->cam.pos );
+ if( localplayer.gate_waiting ){
+ m4x3_mulv( localplayer.gate_waiting->transport,
+ frame->cam.pos, frame->cam.pos );
+
+ v3f v0;
+ v3_angles_vector( localplayer.cam.angles, v0 );
+ m3x3_mulv( localplayer.gate_waiting->transport, v0, v0 );
+ v3_angles( v0, frame->cam.angles );
+ }
+ else
+ v3_copy( localplayer.cam.angles, frame->cam.angles );
+
+ frame->cam.fov = localplayer.cam.fov;
+
+ /* animator */
+ void *dst = replay_frame_data( frame, k_replay_framedata_animator ),
+ *src = player_subsystems[localplayer.subsystem]->animator_data;
+ memcpy( dst, src, animator_size );
+
+ /* sound effects */
+ memcpy( replay_frame_data( frame, k_replay_framedata_sfx ),
+ localplayer.local_sfx_buffer,
+ sizeof(struct net_sfx)*localplayer.local_sfx_buffer_count );
+
+ localplayer.local_sfx_buffer_count = 0;
+}
+
+static void skaterift_restore_frame( replay_frame *frame )
+{
+ replay_gamestate *gs =
+ replay_frame_data( frame, k_replay_framedata_internal_gamestate );
+ void *src = replay_frame_data( frame, k_replay_framedata_gamestate );
+ u16 src_size = frame->data_table[ k_replay_framedata_gamestate ][1];
+ world_static.current_run_version = gs->current_run_version;
+ localplayer.drowned = gs->drowned;
+
+ if(frame->system == k_player_subsystem_walk ){
+ memcpy( &player_walk.state, src, src_size );
+ }
+ else if( frame->system == k_player_subsystem_skate ){
+ memcpy( &player_skate.state, src, src_size );
+ }
+ else if( frame->system == k_player_subsystem_dead ){
+ player__dead_transition(0);
+ struct replay_rb *arr = src;
+
+ for( u32 i=0; i<localplayer.ragdoll.part_count; i ++ ){
+ struct ragdoll_part *part = &localplayer.ragdoll.parts[i];
+ rigidbody *rb = &part->rb;
+
+ v3_copy( arr[i].co, rb->co );
+ v3_copy( arr[i].w, rb->w );
+ v3_copy( arr[i].v, rb->v );
+ v4_copy( arr[i].q, rb->q );
+
+ v3_copy( arr[i].co, part->prev_co );
+ v4_copy( arr[i].q, part->prev_q );
+ rb_update_matrices( rb );
+ }
+ }
+ else if( frame->system == k_player_subsystem_glide ){
+ struct replay_rb *arr = src;
+ rigidbody *rb = &player_glide.rb;
+ v3_copy( arr[0].co, rb->co );
+ v3_copy( arr[0].w, rb->w );
+ v3_copy( arr[0].v, rb->v );
+ v4_copy( arr[0].q, rb->q );
+ rb_update_matrices( rb );
+ }
+
+ localplayer.subsystem = frame->system;
+
+ /* restore the seperated glider data if we have it */
+ if( frame->data_table[ k_replay_framedata_glider ][1] ){
+ struct replay_glider_data *inf =
+ replay_frame_data( frame, k_replay_framedata_glider );
+
+ localplayer.have_glider = inf->have_glider;
+ localplayer.glider_orphan = inf->glider_orphan;
+ player_glide.t = inf->t;
+ }
+ else {
+ localplayer.have_glider = 0;
+ localplayer.glider_orphan = 0;
+ player_glide.t = 0.0f;
+ }
+
+ memcpy( &localplayer.rb, &gs->rb, sizeof(rigidbody) );
+ memcpy( &player_glide.rb, &gs->glider_rb, sizeof(rigidbody) );
+ v3_copy( gs->angles, localplayer.angles );
+
+ v3_copy( frame->cam.pos, localplayer.cam.pos );
+ v3_copy( frame->cam.angles, localplayer.cam.angles );
+ localplayer.cam.fov = frame->cam.fov;
+
+ memcpy( &localplayer.cam_control, &gs->cam_control,
+ sizeof(struct player_cam_controller) );
+
+ /* chop end off replay */
+ frame->r = NULL;
+ player_replay.local.statehead = frame;
+ player_replay.local.head = frame;
+ player_replay.local.cursor_frame = frame;
+ player_replay.local.cursor = frame->time;
+ player_replay.replay_control = k_replay_control_scrub;
+ skaterift.activity = k_skaterift_default;
+ vg.time = frame->time;
+}
+
+static void skaterift_replay_resume(void){
+ replay_frame *prev = replay_find_recent_stateframe(&player_replay.local);
+
+ if( prev ){
+ player_replay.replay_control = k_replay_control_resume;
+ player_replay.resume_target = prev;
+ player_replay.resume_begin = player_replay.local.cursor;
+ player_replay.resume_transition = 0.0f;
+ }
+
+ gui_helper_clear();
+}
+
+static void skaterift_replay_update_helpers(void);
+
+void skaterift_replay_pre_update(void)
+{
+ if( skaterift.activity != k_skaterift_replay ) return;
+
+ bool input = player_replay.editor_mode^0x1;
+
+ if( player_replay.replay_control == k_replay_control_resume )
+ {
+ if( player_replay.local.cursor_frame == player_replay.resume_target ||
+ player_replay.local.cursor_frame == NULL )
+ {
+ skaterift_restore_frame( player_replay.resume_target );
+ }
+ else
+ {
+ vg_slewf( &player_replay.resume_transition, 1.0f,
+ vg.time_frame_delta * (1.0f/1.0f) );
+
+ if( player_replay.resume_transition >= 1.0f )
+ skaterift_restore_frame( player_replay.resume_target );
+ else {
+ f64 target = vg_lerp( player_replay.resume_begin,
+ player_replay.resume_target->time,
+ vg_smoothstepf( player_replay.resume_transition ) );
+ if( replay_seek( &player_replay.local, target ) )
+ player_replay.track_velocity = 1.0f;
+ else
+ player_replay.track_velocity = 0.0f;
+ }
+ }
+ }
+ else
+ {
+ if( input && button_down( k_srbind_replay_play ) )
+ player_replay.replay_control = k_replay_control_play;
+ if( input && button_down( k_srbind_replay_freecam ) )
+ {
+ player_replay.use_freecam ^= 0x1;
+
+ if( player_replay.use_freecam )
+ {
+ replay_get_camera( &player_replay.local,
+ &player_replay.replay_freecam );
+ }
+ skaterift_replay_update_helpers();
+ }
+
+ f32 target_speed = 0.0f;
+ if( input )
+ target_speed = axis_state( k_sraxis_replay_h ) * 5.0;
+
+ if( input && button_press( k_srbind_reset ) )
+ target_speed += -2.0;
+
+ if( fabsf(target_speed) > 0.01f )
+ player_replay.replay_control = k_replay_control_scrub;
+
+ if( player_replay.replay_control == k_replay_control_play )
+ target_speed = 1.0;
+
+ vg_slewf( &player_replay.track_velocity, target_speed,
+ 18.0f*vg.time_frame_delta );
+
+ if( fabsf( player_replay.track_velocity ) > 0.0001f )
+ {
+ f64 target = player_replay.local.cursor;
+ target += player_replay.track_velocity * vg.time_frame_delta;
+
+ if( !replay_seek( &player_replay.local, target ) )
+ player_replay.track_velocity = 0.0f;
+ }
+
+ if( input && button_down( k_srbind_mback ) )
+ {
+ if( player_replay.local.statehead )
+ skaterift_restore_frame( player_replay.local.statehead );
+ else
+ skaterift.activity = k_skaterift_default;
+ srinput.state = k_input_state_resume;
+ gui_helper_clear();
+ }
+
+ if( input )
+ {
+ if( player_replay.use_freecam )
+ {
+ freecam_preupdate();
+ }
+ else
+ {
+ if( button_down( k_srbind_replay_resume ) )
+ {
+ skaterift_replay_resume();
+ }
+ }
+ }
+ }
+}
+
+static void skaterift_replay_update_helpers(void)
+{
+ player_replay.helper_resume->greyed = player_replay.use_freecam;
+
+ vg_str freecam_text;
+ vg_strnull( &freecam_text, player_replay.helper_freecam->text,
+ GUI_HELPER_TEXT_LENGTH );
+ vg_strcat( &freecam_text,
+ player_replay.use_freecam? "Exit freecam": "Freecam" );
+}
+
+static void replay_show_helpers(void)
+{
+ gui_helper_clear();
+ vg_str text;
+
+ if( gui_new_helper( input_axis_list[k_sraxis_replay_h], &text ) )
+ vg_strcat( &text, "Scrub" );
+
+ if( (player_replay.helper_resume = gui_new_helper(
+ input_button_list[k_srbind_replay_resume], &text )) )
+ vg_strcat( &text, "Resume" );
+
+ if( gui_new_helper( input_button_list[k_srbind_replay_play], &text ))
+ vg_strcat( &text, "Playback" );
+
+ player_replay.helper_freecam = gui_new_helper(
+ input_button_list[k_srbind_replay_freecam], &text );
+
+ skaterift_replay_update_helpers();
+}
+
+void skaterift_replay_post_render(void)
+{
+#ifndef SR_ALLOW_REWIND_HUB
+ if( world_static.active_instance != k_world_purpose_client )
+ return;
+#endif
+
+ /* capture the current resume frame at the very last point */
+ if( button_down( k_srbind_reset ) )
+ {
+ if( skaterift.activity == k_skaterift_default )
+ {
+ localplayer.rewinded_since_last_gate = 1;
+ skaterift.activity = k_skaterift_replay;
+ skaterift_record_frame( &player_replay.local, 1 );
+ if( player_replay.local.head )
+ {
+ player_replay.local.cursor = player_replay.local.head->time;
+ player_replay.local.cursor_frame = player_replay.local.head;
+ }
+ player_replay.replay_control = k_replay_control_scrub;
+ replay_show_helpers();
+ }
+ }
+}
+
+void skaterift_replay_init(void)
+{
+ u32 bytes = 1024*1024*10;
+ player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, bytes );
+ player_replay.local.size = bytes;
+ replay_clear( &player_replay.local );
+}
+
+void skaterift_replay_debug_info( ui_context *ctx )
+{
+ player__debugtext( ctx, 2, "replay info" );
+ replay_buffer *replay = &player_replay.local;
+
+ u32 head = 0,
+ tail = 0;
+ if( replay->tail ) tail = (void *)replay->tail - replay->data;
+ if( replay->head ) head = (void *)replay->head - replay->data;
+
+ player__debugtext( ctx, 1, "head @%u | tail @%u\n", head, tail );
+
+ if( replay->statehead )
+ {
+ for( u32 i=0; i<k_replay_framedata_rows; i++ )
+ {
+ player__debugtext( ctx, 1, "[%u]: [%hu, %hu]\n", i,
+ replay->statehead->data_table[i][0],
+ replay->statehead->data_table[i][1] );
+ }
+ u32 state = (void *)replay->statehead - replay->data;
+ player__debugtext( ctx, 1, "gs @%u\n", state );
+ player__debugtext( ctx, 1, "gamestate_size: %hu\n",
+ replay->statehead->data_table[k_replay_framedata_gamestate][1] );
+ }
+ else
+ player__debugtext( ctx, 1, "gs @NULL\n" );
+
+ f64 start = replay->cursor,
+ end = replay->cursor;
+ if( replay->tail ) start = replay->tail->time;
+ if( replay->head ) end = replay->head->time;
+
+ f64 cur = replay->cursor - start,
+ len = end - start;
+
+ player__debugtext( ctx, 1, "cursor: %.2fs / %.2fs\n", cur, len );
+}
+
+static int _keyframe_cmp( const void *p1, const void *p2 )
+{
+ const replay_keyframe *kf1 = p1, *kf2 = p2;
+ return kf1->time > kf2->time;
+}
+
+static void replay_keyframe_sort(void)
+{
+ qsort( player_replay.keyframes, player_replay.keyframe_count,
+ sizeof(replay_keyframe), _keyframe_cmp );
+}
+
+static void replay_fly_edit_keyframe( ui_context *ctx, replay_keyframe *kf )
+{
+ if( ui_click_down( ctx, UI_MOUSE_LEFT ) )
+ {
+ /* init freecam */
+ v3_copy( kf->cam.pos, player_replay.replay_freecam.pos );
+ v3_copy( kf->cam.angles, player_replay.replay_freecam.angles );
+ v3_zero( player_replay.freecam_v );
+ v3_zero( player_replay.freecam_w );
+ player_replay.replay_freecam.fov = kf->cam.fov;
+ }
+
+ /* move freecam */
+ ui_capture_mouse( ctx, 0 );
+ freecam_preupdate();
+
+ if( vg_getkey(SDLK_q) )
+ player_replay.freecam_v[1] -= vg.time_frame_delta*6.0f*20.0f;
+ if( vg_getkey(SDLK_e) )
+ player_replay.freecam_v[1] += vg.time_frame_delta*6.0f*20.0f;
+
+ v3_copy( player_replay.replay_freecam.pos, g_render.cam.pos );
+ v3_copy( player_replay.replay_freecam.angles, g_render.cam.angles);
+ g_render.cam.fov = player_replay.replay_freecam.fov;
+
+ v3_copy( g_render.cam.pos, kf->cam.pos );
+ v3_copy( g_render.cam.angles, kf->cam.angles );
+ kf->cam.fov = g_render.cam.fov;
+}
+
+void skaterift_replay_imgui( ui_context *ctx )
+{
+ if( skaterift.activity != k_skaterift_replay ) return;
+
+ /* extra keys for entering editor */
+ static u8 f1_key = 0;
+ u8 f1_now = vg_getkey(SDLK_F1);
+ if( f1_now && !f1_key && player_replay.show_ui )
+ {
+ player_replay.editor_mode ^= 0x1;
+
+ if( player_replay.editor_mode )
+ gui_helper_clear();
+ else
+ replay_show_helpers();
+ }
+ f1_key = f1_now;
+
+ static u8 f2_key = 0;
+ u8 f2_now = vg_getkey(SDLK_F2);
+ if( f2_now && !f2_key )
+ {
+ player_replay.show_ui ^= 0x1;
+ }
+ f2_key = f2_now;
+
+ if( player_replay.editor_mode )
+ {
+ static u8 space_key = 0;
+ u8 space_now = vg_getkey(SDLK_SPACE);
+ if( space_now & !space_key )
+ {
+ player_replay.replay_control ^= k_replay_control_play;
+ }
+ space_key = space_now;
+ }
+
+ if( !player_replay.show_ui ) return;
+
+ if( player_replay.editor_mode )
+ {
+ u32 colour = ui_opacity( ui_colour(ctx,k_ui_fg), 0.3333f );
+ ui_rect cx = { vg.window_x/2, 0, 1, vg.window_y },
+ cy = { 0, vg.window_y/2, vg.window_x, 1 };
+ ui_fill( ctx, cx, colour );
+ ui_fill( ctx, cy, colour );
+ }
+
+ replay_buffer *replay = &player_replay.local;
+ f64 start = replay->cursor,
+ end = replay->cursor;
+ if( replay->tail ) start = replay->tail->time;
+ if( replay->head ) end = replay->head->time;
+ f64 len = end - start,
+ cur = (replay->cursor - start) / len;
+
+ char buffer[ 128 ];
+
+ /* mainbar */
+ ui_px height = 32,
+ cwidth = 2;
+ ui_rect timeline = { 0, 0, vg.window_x, height };
+ ui_fill( ctx, timeline, ui_colour( ctx, k_ui_bg ) );
+
+ /* cursor frame block */
+ if( replay->cursor_frame )
+ {
+ if( replay->cursor_frame->r )
+ {
+ f64 l = (replay->cursor_frame->r->time-replay->cursor_frame->time)/len,
+ s = (replay->cursor_frame->time - start) / len;
+ ui_rect box = { s*(f64)vg.window_x, 0,
+ VG_MAX(4,(ui_px)(l*vg.window_x)), timeline[3]+2 };
+ ui_fill( ctx, box, ui_colour( ctx, k_ui_bg+4 ) );
+ }
+ }
+
+ /* cursor */
+ ui_rect cusor = { cur * (f64)vg.window_x - (cwidth/2), 0,
+ cwidth, (player_replay.editor_mode? 0: 16) + timeline[3] };
+ ui_fill( ctx, cusor, ui_colour( ctx, k_ui_bg+7 ) );
+
+ /* latest state marker */
+ if( replay->statehead )
+ {
+ f64 t = (replay->statehead->time - start) / len;
+ ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 };
+ ui_fill( ctx, tag, ui_colour( ctx, k_ui_green+k_ui_brighter ) );
+ }
+
+ /* previous state marker */
+ replay_frame *prev = replay_find_recent_stateframe( replay );
+ if( prev )
+ {
+ f64 t = (prev->time - start) / len;
+ ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 };
+ ui_fill( ctx, tag, ui_colour( ctx, k_ui_yellow+k_ui_brighter ) );
+ }
+
+ snprintf( buffer, 128, "-%.2fs (F1: Edit replay)", (end-replay->cursor) );
+ ui_text( ctx, timeline, buffer, 1, k_ui_align_middle_left, 0 );
+ ui_text( ctx, timeline, "0s", 1, k_ui_align_middle_right, 0 );
+
+ if( !player_replay.editor_mode ) return;
+ ui_capture_mouse( ctx, 1 );
+
+ ui_rect panel = { 0, timeline[3] + 20, 200, 400 };
+ ui_fill( ctx, panel, ui_opacity( ui_colour( ctx, k_ui_bg ), 0.5f ) );
+ ui_rect_pad( panel, (ui_px[2]){4,4} );
+
+ if( ui_button( ctx, panel,
+ (player_replay.replay_control == k_replay_control_play)?
+ "Pause (space)": "Play (space)" ) == k_ui_button_click )
+ {
+ player_replay.replay_control ^= k_replay_control_play;
+ }
+
+ /* script bar */
+ ui_rect script = { 0, height + 2, vg.window_x, 16 };
+ ui_fill( ctx, script, ui_colour( ctx, k_ui_bg ) );
+ f64 mouse_t = start + ((f64)ctx->mouse[0] / (f64)vg.window_x)*len;
+
+ /* keyframe draw and select */
+ bool absorb_by_keyframe = 0;
+ ui_px lx = 0;
+ for( u32 i=0; i<player_replay.keyframe_count; i ++ )
+ {
+ replay_keyframe *kf = &player_replay.keyframes[i];
+ f64 t = (kf->time-start)/len;
+
+ ui_px x = t*(f64)vg.window_x-8;
+
+ /* draw connections between keyframes */
+ if( i )
+ {
+ ui_rect con = { lx, script[1]+7, x-lx, 1 };
+ ui_fill( ctx, con, ui_colour( ctx, k_ui_blue ) );
+ vg_line( kf->cam.pos, player_replay.keyframes[i-1].cam.pos, VG__BLUE );
+ }
+
+ /* keyframe selection */
+ ui_rect tag = { x, script[1], 16, 16 };
+
+ if( ui_inside_rect( tag, ctx->mouse ) )
+ {
+ absorb_by_keyframe = 1;
+
+ if( ui_click_down( ctx, UI_MOUSE_LEFT ) )
+ {
+ if( player_replay.active_keyframe != i )
+ {
+ player_replay.active_keyframe = i;
+ replay_seek( &player_replay.local, kf->time );
+ }
+ }
+ else
+ {
+ ui_outline( ctx, tag, 1, ui_colour(ctx, k_ui_fg), 0 );
+ }
+ }
+
+ /* edit controls */
+ u32 drag_colour = ui_opacity( ui_colour(ctx, k_ui_bg+2), 0.5f );
+ if( i == player_replay.active_keyframe )
+ {
+ ui_outline( ctx, tag, 2, ui_colour(ctx, k_ui_fg), 0 );
+
+ ui_rect tray = { tag[0]+8-32, tag[1]+16+2, 64, 16 };
+ ui_rect dragbar = { tray[0]+16, tray[1], 32, 16 };
+
+ bool pos_correct = 0;
+
+ if( ui_inside_rect( dragbar, ctx->mouse_click ) )
+ {
+ if( ui_clicking( ctx, UI_MOUSE_LEFT ) )
+ {
+ drag_colour = ui_opacity( ui_colour(ctx,k_ui_fg), 0.5f );
+ pos_correct = 1;
+ replay_seek( &player_replay.local, mouse_t );
+ }
+ else if( ui_click_up( ctx, UI_MOUSE_LEFT ) )
+ {
+ pos_correct = 1;
+ kf->time = mouse_t;
+ replay_keyframe_sort();
+
+ for( u32 j=0; j<player_replay.keyframe_count; j ++ )
+ {
+ if( player_replay.keyframes[j].time == mouse_t )
+ {
+ player_replay.active_keyframe = j;
+ break;
+ }
+ }
+ }
+
+ if( pos_correct )
+ {
+ tag[0] = ctx->mouse[0]-8;
+ tray[0] = tag[0]+8-32;
+ dragbar[0] = tray[0]+16;
+ }
+ }
+
+ if( ui_inside_rect( dragbar, ctx->mouse ) )
+ {
+ ctx->cursor = k_ui_cursor_hand;
+ }
+
+ if( !pos_correct )
+ {
+ ui_fill( ctx, tray,
+ ui_opacity( ui_colour( ctx, k_ui_bg+2 ), 0.5f ) );
+ }
+
+ ui_fill( ctx, dragbar, drag_colour );
+ ui_text( ctx, dragbar, ":::", 1, k_ui_align_middle_center, 0 );
+
+ if( !pos_correct )
+ {
+ ui_rect btn = { tray[0], tray[1], 16, 16 };
+ if( ui_button_text( ctx, btn, "X", 1 ) == k_ui_button_click )
+ {
+ for( u32 j=i; j<player_replay.keyframe_count-1; j ++ )
+ player_replay.keyframes[j] = player_replay.keyframes[j+1];
+
+ player_replay.keyframe_count --;
+ player_replay.active_keyframe = -1;
+ }
+
+ ui_rect btn1 = { tray[0]+48, tray[1], 16, 16 };
+
+ enum ui_button_state mask_using =
+ k_ui_button_holding_inside |
+ k_ui_button_holding_outside |
+ k_ui_button_click;
+
+ if( ui_button_text( ctx, btn1, "E", 1 ) & mask_using )
+ {
+ replay_fly_edit_keyframe( ctx, kf );
+ vg_ui_set_mouse_pos( btn1[0]+8, btn1[1]+8 );
+ }
+ }
+ }
+
+ ui_fill( ctx, tag, ui_colour( ctx, k_ui_blue ) );
+ lx = x;
+ }
+
+ /* adding keyframes */
+ if( ui_inside_rect( script, ctx->mouse ) )
+ {
+ ctx->cursor = k_ui_cursor_hand;
+
+ ui_rect cursor = { ctx->mouse[0], script[1], 4, 16 };
+ ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) );
+
+ if( !absorb_by_keyframe && ui_click_down( ctx, UI_MOUSE_LEFT ) )
+ {
+ u32 max = VG_ARRAY_LEN( player_replay.keyframes );
+ if( player_replay.keyframe_count == max )
+ {
+ ui_start_modal( ctx, "Maximum keyframes reached", UI_MODAL_BAD );
+ }
+ else
+ {
+ replay_keyframe *kf =
+ &player_replay.keyframes[player_replay.keyframe_count++];
+
+ kf->time = mouse_t;
+ v3_copy( g_render.cam.pos, kf->cam.pos );
+ v3_copy( g_render.cam.angles, kf->cam.angles );
+ kf->cam.fov = g_render.cam.fov;
+
+ replay_keyframe_sort();
+ }
+ }
+ }
+
+ /* timeline scrub */
+ bool start_in_timeline =
+ ui_clicking(ctx, UI_MOUSE_LEFT) &&
+ ui_inside_rect(timeline, ctx->mouse_click);
+ if( (ui_inside_rect( timeline, ctx->mouse )) || start_in_timeline )
+ {
+ ui_rect cursor = { ctx->mouse[0], timeline[1], 4, timeline[3] };
+ ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) );
+ ctx->cursor = k_ui_cursor_ibeam;
+
+ if( ui_clicking( ctx, UI_MOUSE_LEFT ) && start_in_timeline )
+ {
+ replay_seek( &player_replay.local, mouse_t );
+ player_replay.active_keyframe = -1;
+ }
+ }
+
+ if( ui_button( ctx, panel, "Clear keyframes" ) == k_ui_button_click )
+ {
+ player_replay.keyframe_count = 0;
+ }
+
+ if( (ui_button( ctx, panel, "Hide UI (F2)" ) == k_ui_button_click) )
+ {
+ player_replay.show_ui ^= 0x1;
+ }
+
+ if( player_replay.active_keyframe != -1 )
+ {
+ replay_keyframe *kf =
+ &player_replay.keyframes[ player_replay.active_keyframe ];
+
+ enum ui_button_state mask_using =
+ k_ui_button_holding_inside |
+ k_ui_button_holding_outside |
+ k_ui_button_click;
+
+ if( ui_button( ctx, panel, "Edit cam" ) & mask_using )
+ {
+ replay_fly_edit_keyframe( ctx, kf );
+ }
+ }
+
+ ui_info( ctx, panel, "World settings" );
+ f32 new_time = world_current_instance()->time;
+ if( ui_slider( ctx, panel, "Time of day", 0, 1, &new_time ) )
+ {
+ world_current_instance()->time = new_time;
+ }
+
+ ui_info( ctx, panel, "" );
+ if( ui_button( ctx, panel, "Exit editor (F1)" ) == k_ui_button_click )
+ {
+ player_replay.editor_mode = 0;
+ replay_show_helpers();
+ }
+
+ /* TODO: Add Q/E scrub here too.
+ * Add replay trimming
+ */
+}
--- /dev/null
+#pragma once
+#include "player_render.h"
+#include "vg/vg_rigidbody.h"
+
+typedef struct replay_buffer replay_buffer;
+typedef struct replay_frame replay_frame;
+typedef struct replay_keyframe replay_keyframe;
+
+typedef struct replay_gamestate replay_gamestate;
+typedef struct replay_sfx replay_sfx;
+
+struct replay_buffer {
+ void *data;
+ u32 size; /* bytes */
+
+ replay_frame *head, *tail, *cursor_frame,
+ *statehead;
+ f64 cursor;
+};
+
+enum replay_framedata{
+ k_replay_framedata_animator,
+ k_replay_framedata_gamestate,
+ k_replay_framedata_internal_gamestate,
+ k_replay_framedata_sfx,
+ k_replay_framedata_glider,
+ k_replay_framedata_rows
+};
+
+struct replay_cam
+{
+ v3f pos, angles;
+ f32 fov;
+};
+
+struct replay_frame
+{
+ struct replay_cam cam;
+ f64 time;
+
+ replay_frame *l, *r;
+
+ enum player_subsystem system;
+ u16 total_size;
+ u16 data_table[k_replay_framedata_rows][2];
+};
+
+/* player-defined replay frames */
+struct replay_keyframe
+{
+ struct replay_cam cam;
+ f64 time;
+};
+
+struct replay_gamestate
+{
+ rigidbody rb, glider_rb; /* TODO: these don't need to be saved with their
+ full matrices */
+ v3f angles;
+ struct player_cam_controller cam_control;
+ u32 current_run_version;
+ bool drowned;
+};
+
+/* we save this per-anim-frame. if there glider is existing in any state */
+struct replay_glider_data
+{
+ bool have_glider, glider_orphan;
+ f32 t;
+ v3f co;
+ v4f q;
+};
+
+struct replay_sfx {
+ u32 none;
+};
+
+struct replay_globals
+{
+ replay_buffer local;
+ replay_frame *resume_target;
+ f64 resume_begin;
+ f32 resume_transition;
+
+ enum replay_control {
+ k_replay_control_scrub = 0x00,
+ k_replay_control_play = 0x01,
+ k_replay_control_resume= 0x02
+ }
+ replay_control;
+ f32 track_velocity;
+ struct gui_helper *helper_resume, *helper_freecam;
+
+ vg_camera replay_freecam;
+
+ bool use_freecam;
+ bool show_ui;
+ v3f freecam_v, freecam_w;
+
+ i32 editor_mode;
+
+ replay_keyframe keyframes[32];
+ u32 keyframe_count;
+ i32 active_keyframe;
+}
+extern player_replay;
+
+int replay_seek( replay_buffer *replay, f64 t );
+
+replay_frame *replay_find_recent_stateframe( replay_buffer *replay );
+void replay_get_camera( replay_buffer *replay, vg_camera *cam );
+void replay_get_frame_camera( replay_frame *frame, vg_camera *cam );
+f32 replay_subframe_time( replay_buffer *replay );
+void replay_clear( replay_buffer *replay );
+void *
+replay_frame_data( replay_frame *frame, enum replay_framedata type );
+
+void skaterift_replay_pre_update(void);
+void skaterift_replay_imgui( ui_context *ctx );
+void skaterift_replay_debug_info( ui_context *ctx );
+void skaterift_record_frame( replay_buffer *replay,
+ int force_gamestate );
+void skaterift_replay_post_render(void);
+void skaterift_replay_init(void);
+void skaterift_get_replay_cam( vg_camera *cam );
--- /dev/null
+#include "player_skate.h"
+#include "player.h"
+#include "audio.h"
+#include "vg/vg_perlin.h"
+#include "vg/vg_lines.h"
+#include "menu.h"
+#include "ent_skateshop.h"
+#include "addon.h"
+#include "input.h"
+#include "ent_tornado.h"
+
+#include "vg/vg_rigidbody.h"
+#include "scene_rigidbody.h"
+#include "player_glide.h"
+#include "player_dead.h"
+#include "player_walk.h"
+#include <string.h>
+
+struct player_skate player_skate;
+struct player_subsystem_interface player_subsystem_skate =
+{
+ .system_register = player__skate_register,
+ .bind = player__skate_bind,
+ .pre_update = player__skate_pre_update,
+ .update = player__skate_update,
+ .post_update = player__skate_post_update,
+ .im_gui = player__skate_im_gui,
+ .animate = player__skate_animate,
+ .pose = player__skate_pose,
+ .effects = player__skate_effects,
+ .post_animate = player__skate_post_animate,
+ .network_animator_exchange = player__skate_animator_exchange,
+ .sfx_oneshot = player__skate_sfx_oneshot,
+ .sfx_comp = player__skate_comp_audio,
+ .sfx_kill = player__skate_kill_audio,
+
+ .animator_data = &player_skate.animator,
+ .animator_size = sizeof(player_skate.animator),
+ .name = "Skate"
+};
+
+void player__skate_bind(void){
+ struct skeleton *sk = &localplayer.skeleton;
+ rb_update_matrices( &localplayer.rb );
+
+ struct { struct skeleton_anim **anim; const char *name; }
+ bindings[] = {
+ { &player_skate.anim_grind, "pose_grind" },
+ { &player_skate.anim_grind_jump, "pose_grind_jump" },
+ { &player_skate.anim_stand, "pose_stand" },
+ { &player_skate.anim_highg, "pose_highg" },
+ { &player_skate.anim_air, "pose_air" },
+ { &player_skate.anim_slide, "pose_slide" },
+ { &player_skate.anim_push, "push" },
+ { &player_skate.anim_push_reverse, "push_reverse" },
+ { &player_skate.anim_ollie, "ollie" },
+ { &player_skate.anim_ollie_reverse,"ollie_reverse" },
+ { &player_skate.anim_grabs, "grabs" },
+ { &player_skate.anim_handplant, "handplant" },
+ };
+
+ for( u32 i=0; i<VG_ARRAY_LEN(bindings); i++ )
+ *bindings[i].anim = skeleton_get_anim( sk, bindings[i].name );
+}
+
+void player__skate_kill_audio(void){
+ audio_lock();
+ if( player_skate.aud_main ){
+ player_skate.aud_main =
+ audio_channel_fadeout( player_skate.aud_main, 0.1f );
+ }
+ if( player_skate.aud_air ){
+ player_skate.aud_air =
+ audio_channel_fadeout( player_skate.aud_air, 0.1f );
+ }
+ if( player_skate.aud_slide ){
+ player_skate.aud_slide =
+ audio_channel_fadeout( player_skate.aud_slide, 0.1f );
+ }
+ audio_unlock();
+}
+
+/*
+ * Collision detection routines
+ *
+ *
+ */
+
+/*
+ * Does collision detection on a sphere vs world, and applies some smoothing
+ * filters to the manifold afterwards
+ */
+static int skate_collide_smooth( m4x3f mtx, f32 r, rb_ct *man ){
+ world_instance *world = world_current_instance();
+
+ int len = 0;
+ len = rb_sphere__scene( mtx, r, NULL, world->geo_bh, man,
+ k_material_flag_walking );
+
+ for( int i=0; i<len; i++ ){
+ man[i].rba = &localplayer.rb;
+ man[i].rbb = NULL;
+ }
+
+ rb_manifold_filter_coplanar( man, len, 0.03f );
+
+ if( len > 1 ){
+ rb_manifold_filter_backface( man, len );
+ rb_manifold_filter_joint_edges( man, len, 0.03f );
+ rb_manifold_filter_pairs( man, len, 0.03f );
+ }
+ int new_len = rb_manifold_apply_filtered( man, len );
+ if( len && !new_len )
+ len = 1;
+ else
+ len = new_len;
+
+ return len;
+}
+
+struct grind_info
+{
+ v3f co, dir, n;
+};
+
+static int skate_grind_scansq( v3f pos, v3f dir, float r,
+ struct grind_info *inf ){
+ world_instance *world = world_current_instance();
+
+ v4f plane;
+ v3_copy( dir, plane );
+ v3_normalize( plane );
+ plane[3] = v3_dot( plane, pos );
+
+ boxf box;
+ v3_add( pos, (v3f){ r, r, r }, box[1] );
+ v3_sub( pos, (v3f){ r, r, r }, box[0] );
+
+ struct grind_sample{
+ v2f co;
+ v2f normal;
+ v3f normal3,
+ centroid;
+ }
+ samples[48];
+ int sample_count = 0;
+
+ v2f support_min,
+ support_max;
+
+ v3f support_axis;
+ v3_cross( plane, (v3f){0,1,0}, support_axis );
+ v3_normalize( support_axis );
+
+ bh_iter it;
+ bh_iter_init_box( 0, &it, box );
+ i32 idx;
+
+ while( bh_next( world->geo_bh, &it, &idx ) ){
+ u32 *ptri = &world->scene_geo.arrindices[ idx*3 ];
+ v3f tri[3];
+
+ struct world_surface *surf = world_tri_index_surface(world,ptri[0]);
+ if( !(surf->info.flags & k_material_flag_grindable) )
+ continue;
+
+ for( int j=0; j<3; j++ )
+ v3_copy( world->scene_geo.arrvertices[ptri[j]].co, tri[j] );
+
+ for( int j=0; j<3; j++ ){
+ int i0 = j,
+ i1 = (j+1) % 3;
+
+ struct grind_sample *sample = &samples[ sample_count ];
+ v3f co;
+
+ if( plane_segment( plane, tri[i0], tri[i1], co ) ){
+ v3f d;
+ v3_sub( co, pos, d );
+ if( v3_length2( d ) > r*r )
+ continue;
+
+ v3f va, vb, normal;
+ v3_sub( tri[1], tri[0], va );
+ v3_sub( tri[2], tri[0], vb );
+ v3_cross( va, vb, normal );
+
+ sample->normal[0] = v3_dot( support_axis, normal );
+ sample->normal[1] = normal[1];
+ sample->co[0] = v3_dot( support_axis, d );
+ sample->co[1] = d[1];
+
+ v3_copy( normal, sample->normal3 ); /* normalize later
+ if we want to us it */
+
+ v3_muls( tri[0], 1.0f/3.0f, sample->centroid );
+ v3_muladds( sample->centroid, tri[1], 1.0f/3.0f, sample->centroid );
+ v3_muladds( sample->centroid, tri[2], 1.0f/3.0f, sample->centroid );
+
+ v2_normalize( sample->normal );
+ sample_count ++;
+
+ if( sample_count == VG_ARRAY_LEN( samples ) )
+ goto too_many_samples;
+ }
+ }
+ }
+
+too_many_samples:
+
+ if( sample_count < 2 )
+ return 0;
+
+ v3f average_direction,
+ average_normal;
+
+ v2f min_co, max_co;
+ v2_fill( min_co, INFINITY );
+ v2_fill( max_co, -INFINITY );
+
+ v3_zero( average_direction );
+ v3_zero( average_normal );
+
+ int passed_samples = 0;
+
+ for( int i=0; i<sample_count-1; i++ ){
+ struct grind_sample *si, *sj;
+
+ si = &samples[i];
+
+ for( int j=i+1; j<sample_count; j++ ){
+ if( i == j )
+ continue;
+
+ sj = &samples[j];
+
+ /* non overlapping */
+ if( v2_dist2( si->co, sj->co ) >= (0.01f*0.01f) )
+ continue;
+
+ /* not sharp angle */
+ if( v2_dot( si->normal, sj->normal ) >= 0.7f )
+ continue;
+
+ /* not convex */
+ v3f v0;
+ v3_sub( sj->centroid, si->centroid, v0 );
+ if( v3_dot( v0, si->normal3 ) >= 0.0f ||
+ v3_dot( v0, sj->normal3 ) <= 0.0f )
+ continue;
+
+ v2_minv( sj->co, min_co, min_co );
+ v2_maxv( sj->co, max_co, max_co );
+
+ v3f n0, n1, dir;
+ v3_copy( si->normal3, n0 );
+ v3_copy( sj->normal3, n1 );
+ v3_cross( n0, n1, dir );
+
+ if( v3_length2( dir ) <= 0.000001f )
+ continue;
+
+ v3_normalize( dir );
+
+ /* make sure the directions all face a common hemisphere */
+ v3_muls( dir, vg_signf(v3_dot(dir,plane)), dir );
+ v3_add( average_direction, dir, average_direction );
+
+ float yi = si->normal3[1],
+ yj = sj->normal3[1];
+
+ if( yi > yj ) v3_add( si->normal3, average_normal, average_normal );
+ else v3_add( sj->normal3, average_normal, average_normal );
+
+ passed_samples ++;
+ }
+ }
+
+ if( !passed_samples )
+ return 0;
+
+ if( (v3_length2( average_direction ) <= 0.001f) ||
+ (v3_length2( average_normal ) <= 0.001f ) )
+ return 0;
+
+ float div = 1.0f/(float)passed_samples;
+ v3_normalize( average_direction );
+ v3_normalize( average_normal );
+
+ v2f average_coord;
+ v2_add( min_co, max_co, average_coord );
+ v2_muls( average_coord, 0.5f, average_coord );
+
+ v3_muls( support_axis, average_coord[0], inf->co );
+ inf->co[1] += average_coord[1];
+ v3_add( pos, inf->co, inf->co );
+ v3_copy( average_normal, inf->n );
+ v3_copy( average_direction, inf->dir );
+
+ vg_line_point( inf->co, 0.02f, VG__GREEN );
+ vg_line_arrow( inf->co, average_direction, 0.3f, VG__GREEN );
+ vg_line_arrow( inf->co, inf->n, 0.2f, VG__CYAN );
+
+ return passed_samples;
+}
+
+static void reset_jump_info( jump_info *inf ){
+ inf->log_length = 0;
+ inf->land_dist = 0.0f;
+ inf->score = 0.0f;
+ inf->type = k_prediction_unset;
+ v3_zero( inf->apex );
+}
+
+static int create_jumps_to_hit_target( jump_info *jumps,
+ v3f target, float max_angle_delta,
+ float gravity ){
+ /* calculate the exact 2 solutions to jump onto that grind spot */
+
+ v3f v0;
+ v3_sub( target, localplayer.rb.co, v0 );
+
+ v3f ax;
+ v3_copy( v0, ax );
+ ax[1] = 0.0f;
+ v3_normalize( ax );
+
+ v2f d = { v3_dot( ax, v0 ), v0[1] },
+ v = { v3_dot( ax, localplayer.rb.v ), localplayer.rb.v[1] };
+
+ float a = atan2f( v[1], v[0] ),
+ m = v2_length( v ),
+ root = m*m*m*m - gravity*(gravity*d[0]*d[0] + 2.0f*d[1]*m*m);
+
+ int valid_count = 0;
+
+ if( root > 0.0f ){
+ root = sqrtf( root );
+ float a0 = atanf( (m*m + root) / (gravity * d[0]) ),
+ a1 = atanf( (m*m - root) / (gravity * d[0]) );
+
+ if( fabsf(a0-a) < max_angle_delta ){
+ jump_info *inf = &jumps[ valid_count ++ ];
+ reset_jump_info( inf );
+
+ v3_muls( ax, cosf( a0 ) * m, inf->v );
+ inf->v[1] += sinf( a0 ) * m;
+ inf->land_dist = d[0] / (cosf(a0)*m);
+ inf->gravity = gravity;
+
+ v3_copy( target, inf->log[inf->log_length ++] );
+ }
+
+ if( fabsf(a1-a) < max_angle_delta ){
+ jump_info *inf = &jumps[ valid_count ++ ];
+ reset_jump_info( inf );
+
+ v3_muls( ax, cosf( a1 ) * m, inf->v );
+ inf->v[1] += sinf( a1 ) * m;
+ inf->land_dist = d[0] / (cosf(a1)*m);
+ inf->gravity = gravity;
+
+ v3_copy( target, inf->log[inf->log_length ++] );
+ }
+ }
+
+ return valid_count;
+}
+
+void player__approximate_best_trajectory(void)
+{
+ world_instance *world0 = world_current_instance();
+
+ float k_trace_delta = vg.time_fixed_delta * 10.0f;
+ struct player_skate_state *state = &player_skate.state;
+
+ state->air_start = vg.time;
+ v3_copy( localplayer.rb.v, state->air_init_v );
+ v3_copy( localplayer.rb.co, state->air_init_co );
+
+ player_skate.possible_jump_count = 0;
+
+ v3f axis;
+ v3_cross( localplayer.rb.v, localplayer.rb.to_world[1], axis );
+ v3_normalize( axis );
+
+ /* at high slopes, Y component is low */
+ float upness = localplayer.rb.to_world[1][1],
+ angle_begin = -(1.0f-fabsf( upness )),
+ angle_end = 1.0f;
+
+ struct grind_info grind;
+ int grind_located = 0;
+ float grind_located_gravity = k_gravity;
+
+
+ v3f launch_v_bounds[2];
+
+ for( int i=0; i<2; i++ ){
+ v3_copy( localplayer.rb.v, launch_v_bounds[i] );
+ float ang = (float[]){ angle_begin, angle_end }[ i ];
+ ang *= 0.15f;
+
+ v4f qbias;
+ q_axis_angle( qbias, axis, ang );
+ q_mulv( qbias, launch_v_bounds[i], launch_v_bounds[i] );
+ }
+
+ for( int m=0;m<=30; m++ ){
+ jump_info *inf =
+ &player_skate.possible_jumps[ player_skate.possible_jump_count ++ ];
+ reset_jump_info( inf );
+
+ v3f launch_co, launch_v, co0, co1;
+ v3_copy( localplayer.rb.co, launch_co );
+ v3_copy( localplayer.rb.v, launch_v );
+ v3_copy( launch_co, co0 );
+ world_instance *trace_world = world0;
+
+ float vt = (float)m * (1.0f/30.0f),
+ ang = vg_lerpf( angle_begin, angle_end, vt ) * 0.15f;
+
+ v4f qbias;
+ q_axis_angle( qbias, axis, ang );
+ q_mulv( qbias, launch_v, launch_v );
+
+ float yaw_sketch = 1.0f-fabsf(upness);
+
+ float yaw_bias = ((float)(m%3) - 1.0f) * 0.08f * yaw_sketch;
+ q_axis_angle( qbias, localplayer.rb.to_world[1], yaw_bias );
+ q_mulv( qbias, launch_v, launch_v );
+
+ float gravity_bias = vg_lerpf( 0.85f, 1.4f, vt ),
+ gravity = k_gravity * gravity_bias;
+ inf->gravity = gravity;
+ v3_copy( launch_v, inf->v );
+
+ /* initial conditions */
+ v3f v;
+ v3_copy( launch_v, v );
+ v3_copy( launch_co, co1 );
+
+ for( int i=1; i<=50; i++ ){
+ f32 t = (f32)i * k_trace_delta;
+
+ /* integrate forces */
+ v3f a;
+ ent_tornado_forces( co1, v, a );
+ a[1] -= gravity;
+
+ /* position */
+ v3_muladds( co1, v, k_trace_delta, co1 );
+ v3_muladds( co1, a, 0.5f*k_trace_delta*k_trace_delta, co1 );
+
+ /* velocity */
+ v3_muladds( v, a, k_trace_delta, v );
+
+ int search_for_grind = 1;
+ if( grind_located ) search_for_grind = 0;
+ if( v[1] > 0.0f ) search_for_grind = 0;
+
+ /* REFACTOR */
+
+ v3f closest={0.0f,0.0f,0.0f};
+ if( search_for_grind ){
+ if( bh_closest_point(trace_world->geo_bh,co1,closest,1.0f) != -1 ){
+ float min_dist = 0.75f;
+ min_dist *= min_dist;
+
+ if( v3_dist2( closest, launch_co ) < min_dist )
+ search_for_grind = 0;
+
+ v3f bound[2];
+
+ for( int j=0; j<2; j++ ){
+ v3_muls( launch_v_bounds[j], t, bound[j] );
+ bound[j][1] += -0.5f*gravity*t*t;
+ v3_add( launch_co, bound[j], bound[j] );
+ }
+
+ float limh = vg_minf( 2.0f, t ),
+ minh = vg_minf( bound[0][1], bound[1][1] )-limh,
+ maxh = vg_maxf( bound[0][1], bound[1][1] )+limh;
+
+ if( (closest[1] < minh) || (closest[1] > maxh) ){
+ search_for_grind = 0;
+ }
+ }
+ else
+ search_for_grind = 0;
+ }
+
+ if( search_for_grind ){
+ if( skate_grind_scansq( closest, v, 0.5f, &grind ) ){
+ /* check alignment */
+ v2f v0 = { v[0], v[2] },
+ v1 = { grind.dir[0], grind.dir[2] };
+
+ v2_normalize( v0 );
+ v2_normalize( v1 );
+
+ float a = v2_dot( v0, v1 );
+
+ float a_min = cosf( VG_PIf * 0.185f );
+ if( state->grind_cooldown )
+ a_min = cosf( VG_PIf * 0.05f );
+
+ /* check speed */
+ if( (fabsf(v3_dot( v, grind.dir ))>=k_grind_axel_min_vel) &&
+ (a >= a_min) &&
+ (fabsf(grind.dir[1]) < 0.70710678118654752f))
+ {
+ grind_located = 1;
+ grind_located_gravity = inf->gravity;
+ }
+ }
+ }
+
+ if( trace_world->rendering_gate ){
+ ent_gate *gate = trace_world->rendering_gate;
+ if( gate_intersect( gate, co1, co0 ) ){
+ m4x3_mulv( gate->transport, co0, co0 );
+ m4x3_mulv( gate->transport, co1, co1 );
+ m3x3_mulv( gate->transport, launch_v, launch_v);
+ m4x3_mulv( gate->transport, launch_co, launch_co );
+
+ if( gate->flags & k_ent_gate_nonlocal )
+ trace_world = &world_static.instances[ gate->target ];
+ }
+ }
+
+ float t1;
+ v3f n;
+
+ float scan_radius = k_board_radius;
+ scan_radius *= vg_clampf( t, 0.02f, 1.0f );
+
+ int idx = spherecast_world( trace_world, co0, co1, scan_radius, &t1, n,
+ k_material_flag_walking );
+ if( idx != -1 ){
+ v3f co;
+ v3_lerp( co0, co1, t1, co );
+ v3_copy( co, inf->log[ inf->log_length ++ ] );
+
+ v3_copy( n, inf->n );
+ u32 *tri = &trace_world->scene_geo.arrindices[ idx*3 ];
+ struct world_surface *surf =
+ world_tri_index_surface( trace_world, tri[0] );
+
+ inf->type = k_prediction_land;
+ inf->score = -v3_dot( v, inf->n );
+ inf->land_dist = t + k_trace_delta * t1;
+
+ /* Bias prediction towords ramps */
+ if( !(surf->info.flags & k_material_flag_skate_target) )
+ inf->score *= 10.0f;
+
+ if( surf->info.flags & k_material_flag_boundary )
+ player_skate.possible_jump_count --;
+
+ break;
+ }
+
+ if( i % 3 == 0 )
+ v3_copy( co1, inf->log[ inf->log_length ++ ] );
+ v3_copy( co1, co0 );
+ }
+
+ if( inf->type == k_prediction_unset )
+ player_skate.possible_jump_count --;
+ }
+
+ if( grind_located ){
+ jump_info grind_jumps[2];
+
+ int valid_count =
+ create_jumps_to_hit_target( grind_jumps, grind.co,
+ 0.175f*VG_PIf, grind_located_gravity );
+
+ /* knock out original landing points in the 1m area */
+ for( u32 j=0; j<player_skate.possible_jump_count; j++ ){
+ jump_info *jump = &player_skate.possible_jumps[ j ];
+ float dist = v3_dist2( jump->log[jump->log_length-1], grind.co );
+ float descale = 1.0f-vg_minf(1.0f,dist);
+ jump->score += descale*3.0f;
+ }
+
+ for( int i=0; i<valid_count; i++ ){
+ jump_info *jump = &grind_jumps[i];
+ jump->type = k_prediction_grind;
+
+ v3f launch_v, launch_co, co0, co1;
+
+ v3_copy( jump->v, launch_v );
+ v3_copy( localplayer.rb.co, launch_co );
+
+ float t = 0.05f * jump->land_dist;
+ v3_muls( launch_v, t, co0 );
+ co0[1] += -0.5f * jump->gravity * t*t;
+ v3_add( launch_co, co0, co0 );
+
+ /* rough scan to make sure we dont collide with anything */
+ for( int j=1; j<=16; j++ ){
+ t = (float)j*(1.0f/16.0f);
+ t *= 0.9f;
+ t += 0.05f;
+ t *= jump->land_dist;
+
+ v3_muls( launch_v, t, co1 );
+ co1[1] += -0.5f * jump->gravity * t*t;
+ v3_add( launch_co, co1, co1 );
+
+ float t1;
+ v3f n;
+
+ int idx = spherecast_world( world0, co0,co1,
+ k_board_radius*0.1f, &t1, n,
+ k_material_flag_walking );
+ if( idx != -1 ){
+ goto invalidated_grind;
+ }
+
+ v3_copy( co1, co0 );
+ }
+
+ v3_copy( grind.n, jump->n );
+
+ /* determine score */
+ v3f ve;
+ v3_copy( jump->v, ve );
+ ve[1] += -jump->gravity*jump->land_dist;
+ jump->score = -v3_dot( ve, grind.n ) * 0.9f;
+
+ player_skate.possible_jumps[ player_skate.possible_jump_count ++ ] =
+ *jump;
+
+ continue;
+invalidated_grind:;
+ }
+ }
+
+
+ float score_min = INFINITY,
+ score_max = -INFINITY;
+
+ jump_info *best = NULL;
+
+ for( int i=0; i<player_skate.possible_jump_count; i ++ ){
+ jump_info *jump = &player_skate.possible_jumps[i];
+
+ if( jump->score < score_min )
+ best = jump;
+
+ score_min = vg_minf( score_min, jump->score );
+ score_max = vg_maxf( score_max, jump->score );
+ }
+
+ for( int i=0; i<player_skate.possible_jump_count; i ++ ){
+ jump_info *jump = &player_skate.possible_jumps[i];
+ float s = jump->score;
+
+ s -= score_min;
+ s /= (score_max-score_min);
+ s = 1.0f - s;
+
+ jump->score = s;
+ jump->colour = s * 255.0f;
+
+ if( jump == best )
+ jump->colour <<= 16;
+ else if( jump->type == k_prediction_land )
+ jump->colour <<= 8;
+
+ jump->colour |= 0xff000000;
+ }
+
+ if( best ){
+ v3_copy( best->n, state->land_normal );
+ v3_copy( best->v, localplayer.rb.v );
+ state->land_dist = best->land_dist;
+ state->gravity_bias = best->gravity;
+
+ if( best->type == k_prediction_grind ){
+ state->activity = k_skate_activity_air_to_grind;
+ }
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+ v2_normalize_clamp( steer );
+
+ if( (fabsf(steer[1]) > 0.5f) && (state->land_dist >= 1.5f) ){
+ state->flip_rate = (1.0f/state->land_dist) * vg_signf(steer[1]) *
+ state->reverse ;
+ state->flip_time = 0.0f;
+ v3_copy( localplayer.rb.to_world[0], state->flip_axis );
+ }
+ else{
+ state->flip_rate = 0.0f;
+ v3_zero( state->flip_axis );
+ }
+ }
+ else
+ v3_copy( (v3f){0,1,0}, state->land_normal );
+}
+
+/*
+ *
+ * Varius physics models
+ * ------------------------------------------------
+ */
+
+/*
+ * Air control, no real physics
+ */
+static void skate_apply_air_model(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ if( state->activity_prev > k_skate_activity_air_to_grind )
+ player__approximate_best_trajectory();
+
+ float angle = v3_dot( localplayer.rb.to_world[1], state->land_normal );
+ angle = vg_clampf( angle, -1.0f, 1.0f );
+ v3f axis;
+ v3_cross( localplayer.rb.to_world[1], state->land_normal, axis );
+
+ v4f correction;
+ q_axis_angle( correction, axis,
+ acosf(angle)*2.0f*VG_TIMESTEP_FIXED );
+ q_mul( correction, localplayer.rb.q, localplayer.rb.q );
+}
+
+static enum trick_type player_skate_trick_input(void);
+static void skate_apply_trick_model(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ v3f Fd, Fs, F;
+ v3f strength = { 3.7f, 3.6f, 8.0f };
+
+ v3_muls( state->trick_residualv, -4.0f , Fd );
+ v3_muls( state->trick_residuald, -10.0f, Fs );
+ v3_add( Fd, Fs, F );
+ v3_mul( strength, F, F );
+
+ v3_muladds( state->trick_residualv, F, vg.time_fixed_delta,
+ state->trick_residualv );
+ v3_muladds( state->trick_residuald, state->trick_residualv,
+ vg.time_fixed_delta, state->trick_residuald );
+
+ if( state->activity <= k_skate_activity_air_to_grind ){
+ if( v3_length2( state->trick_vel ) < 0.0001f )
+ return;
+
+ int carry_on = state->trick_type == player_skate_trick_input();
+
+ /* we assume velocities share a common divisor, in which case the
+ * interval is the minimum value (if not zero) */
+
+ float min_rate = 99999.0f;
+
+ for( int i=0; i<3; i++ ){
+ float v = state->trick_vel[i];
+ if( (v > 0.0f) && (v < min_rate) )
+ min_rate = v;
+ }
+
+ float interval = 1.0f / min_rate,
+ current = floorf( state->trick_time ),
+ next_end = current+1.0f;
+
+
+ /* integrate trick velocities */
+ v3_muladds( state->trick_euler, state->trick_vel, vg.time_fixed_delta,
+ state->trick_euler );
+
+ if( !carry_on && (state->trick_time + vg.time_fixed_delta/interval >= next_end) ){
+ state->trick_time = 0.0f;
+ state->trick_euler[0] = roundf( state->trick_euler[0] );
+ state->trick_euler[1] = roundf( state->trick_euler[1] );
+ state->trick_euler[2] = roundf( state->trick_euler[2] );
+ v3_copy( state->trick_vel, state->trick_residualv );
+ v3_zero( state->trick_vel );
+
+ audio_lock();
+ audio_oneshot_3d( &audio_flips[vg_randu32(&vg.rand)%4],
+ localplayer.rb.co, 40.0f, 1.0f );
+ audio_unlock();
+ }
+ else
+ state->trick_time += vg.time_fixed_delta / interval;
+ }
+ else{
+ if( (v3_length2(state->trick_vel) >= 0.0001f ) &&
+ state->trick_time > 0.2f)
+ {
+ vg_info( "player fell off due to lack of skill\n" );
+ player__dead_transition( k_player_die_type_feet );
+ }
+
+ state->trick_euler[0] = roundf( state->trick_euler[0] );
+ state->trick_euler[1] = roundf( state->trick_euler[1] );
+ state->trick_euler[2] = roundf( state->trick_euler[2] );
+ state->trick_time = 0.0f;
+ v3_zero( state->trick_vel );
+ }
+}
+
+static void skate_apply_grab_model(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ float grabt = axis_state( k_sraxis_grab );
+
+ if( grabt > 0.5f ){
+ v2_muladds( state->grab_mouse_delta, vg.mouse_delta, 0.02f,
+ state->grab_mouse_delta );
+
+ v2_normalize_clamp( state->grab_mouse_delta );
+ }
+ else
+ v2_zero( state->grab_mouse_delta );
+
+ state->grabbing = vg_lerpf( state->grabbing, grabt, 8.4f*vg.time_fixed_delta );
+}
+
+static void skate_apply_steering_model(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ v2f jsteer;
+ joystick_state( k_srjoystick_steer, jsteer );
+
+ /* Steering */
+ float steer = jsteer[0],
+ grab = axis_state( k_sraxis_grab );
+
+ steer = vg_signf( steer ) * steer*steer * k_steer_ground;
+
+ v3f steer_axis;
+ v3_muls( localplayer.rb.to_world[1], -vg_signf( steer ), steer_axis );
+
+ float rate = 26.0f,
+ top = 1.0f;
+
+ f32 skid_target = 0.0f;
+
+ if( state->activity <= k_skate_activity_air_to_grind ){
+ rate = 6.0f * fabsf(steer);
+ top = 1.5f;
+ }
+ else{
+ /* rotate slower when grabbing on ground */
+ steer *= (1.0f-(state->jump_charge+grab)*0.4f);
+
+ if( state->activity == k_skate_activity_grind_5050 ){
+ rate = 0.0f;
+ top = 0.0f;
+ }
+
+ else if( state->activity >= k_skate_activity_grind_any ){
+ rate *= fabsf(steer);
+
+ float a = 0.8f * -steer * vg.time_fixed_delta;
+
+ v4f q;
+ q_axis_angle( q, localplayer.rb.to_world[1], a );
+ q_mulv( q, player_skate.grind_vec, player_skate.grind_vec );
+
+ v3_normalize( player_skate.grind_vec );
+ }
+
+ else if( state->manual_direction ){
+ rate = 35.0f;
+ top = 1.5f;
+ }
+ else {
+ f32 skid = axis_state(k_sraxis_skid);
+
+ /* skids on keyboard lock to the first direction pressed */
+ if( vg_input.display_input_method == k_input_method_kbm ){
+ if( button_press(k_srbind_skid) && (fabsf(state->skid)<0.01f) &&
+ (fabsf(steer) > 0.4f) ){
+ state->skid = vg_signf( steer ) * 0.02f;
+ }
+
+ if( button_press(k_srbind_skid) && (fabsf(state->skid)>0.01f) ){
+ skid_target = vg_signf( state->skid );
+ }
+ }
+ else {
+ if( fabsf(skid) > 0.1f ){
+ skid_target = skid;
+ }
+ }
+ }
+
+ if( grab < 0.5f ){
+ top *= 1.0f+v3_length( state->throw_v )*k_mmthrow_steer;
+ }
+ }
+
+ vg_slewf( &state->skid, skid_target, vg.time_fixed_delta*(1.0f/0.1f) );
+ steer = vg_lerpf( steer, state->skid*k_steer_ground*0.5f,
+ fabsf(state->skid*0.8f) );
+
+ float current = v3_dot( localplayer.rb.to_world[1], localplayer.rb.w ),
+ addspeed = (steer * -top) - current,
+ maxaccel = rate * vg.time_fixed_delta,
+ accel = vg_clampf( addspeed, -maxaccel, maxaccel );
+
+ v3_muladds( localplayer.rb.w, localplayer.rb.to_world[1],
+ accel, localplayer.rb.w );
+}
+
+/*
+ * Computes friction and surface interface model
+ */
+static void skate_apply_friction_model(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ /*
+ * Computing localized friction forces for controlling the character
+ * Friction across X is significantly more than Z
+ */
+
+ v3f vel;
+ m3x3_mulv( localplayer.rb.to_local, localplayer.rb.v, vel );
+ float slip = 0.0f;
+
+ if( fabsf(vel[2]) > 0.01f )
+ slip = fabsf(-vel[0] / vel[2]) * vg_signf(vel[0]);
+
+ if( fabsf( slip ) > 1.2f )
+ slip = vg_signf( slip ) * 1.2f;
+
+ state->slip = slip;
+ state->reverse = -vg_signf(vel[2]);
+
+ f32 lat = k_friction_lat;
+
+ if( fabsf(axis_state(k_sraxis_skid)) > 0.1f ){
+ if( (player_skate.surface == k_surface_prop_snow) ||
+ (player_skate.surface == k_surface_prop_sand) ){
+ lat *= 8.0f;
+ }
+ else
+ lat *= 1.5f;
+ }
+
+ if( player_skate.surface == k_surface_prop_snow )
+ lat *= 0.5f;
+ else if( player_skate.surface == k_surface_prop_sand )
+ lat *= 0.6f;
+
+ vel[0] += vg_cfrictf( vel[0], lat * vg.time_fixed_delta );
+ vel[2] += vg_cfrictf( vel[2], k_friction_resistance * vg.time_fixed_delta );
+
+ /* Pushing additive force */
+
+ if( !button_press( k_srbind_jump ) && (fabsf(state->skid)<0.1f) ){
+ if( button_press( k_srbind_push ) || (vg.time-state->start_push<0.75) ){
+ if( (vg.time - state->cur_push) > 0.25 )
+ state->start_push = vg.time;
+
+ state->cur_push = vg.time;
+
+ double push_time = vg.time - state->start_push;
+
+ float cycle_time = push_time*k_push_cycle_rate,
+ accel = k_push_accel * (sinf(cycle_time)*0.5f+0.5f),
+ amt = accel * VG_TIMESTEP_FIXED,
+ current = v3_length( vel ),
+ new_vel = vg_minf( current + amt, k_max_push_speed ),
+ delta = new_vel - vg_minf( current, k_max_push_speed );
+
+ vel[2] += delta * -state->reverse;
+ }
+ }
+
+ /* Send back to velocity */
+ m3x3_mulv( localplayer.rb.to_world, vel, localplayer.rb.v );
+}
+
+static void skate_apply_jump_model(void){
+ struct player_skate_state *state = &player_skate.state;
+ int charging_jump_prev = state->charging_jump;
+ state->charging_jump = button_press( k_srbind_jump );
+
+ /* Cannot charge this in air */
+ if( state->activity <= k_skate_activity_air_to_grind ){
+ state->charging_jump = 0;
+ return;
+ }
+
+ if( state->charging_jump ){
+ state->jump_charge += vg.time_fixed_delta * k_jump_charge_speed;
+
+ if( !charging_jump_prev )
+ state->jump_dir = state->reverse>0.0f? 1: 0;
+ }
+ else{
+ state->jump_charge -= k_jump_charge_speed * vg.time_fixed_delta;
+ }
+
+ state->jump_charge = vg_clampf( state->jump_charge, 0.0f, 1.0f );
+
+ /* player let go after charging past 0.2: trigger jump */
+ if( (!state->charging_jump) && (state->jump_charge > 0.2f) ){
+ v3f jumpdir;
+
+ /* Launch more up if alignment is up else improve velocity */
+ float aup = localplayer.rb.to_world[1][1],
+ mod = 0.5f,
+ dir = mod + fabsf(aup)*(1.0f-mod);
+
+ if( state->activity == k_skate_activity_ground ){
+ v3_copy( localplayer.rb.v, jumpdir );
+ v3_normalize( jumpdir );
+ v3_muls( jumpdir, 1.0f-dir, jumpdir );
+ v3_muladds( jumpdir, localplayer.rb.to_world[1], dir, jumpdir );
+ v3_normalize( jumpdir );
+ }else{
+ v3_copy( state->up_dir, jumpdir );
+ state->grind_cooldown = 30;
+ state->activity = k_skate_activity_ground;
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ float tilt = steer[0] * 0.3f;
+ tilt *= vg_signf(v3_dot( localplayer.rb.v,
+ player_skate.grind_dir ));
+
+ v4f qtilt;
+ q_axis_angle( qtilt, player_skate.grind_dir, tilt );
+ q_mulv( qtilt, jumpdir, jumpdir );
+ }
+ state->surface_cooldown = 10;
+ state->trick_input_collect = 0.0f;
+
+ float force = k_jump_force*state->jump_charge;
+ v3_muladds( localplayer.rb.v, jumpdir, force, localplayer.rb.v );
+ state->jump_charge = 0.0f;
+ state->jump_time = vg.time;
+ player__networked_sfx( k_player_subsystem_skate, 32,
+ k_player_skate_soundeffect_jump,
+ localplayer.rb.co, 1.0f );
+ }
+}
+
+static void skate_apply_handplant_model(void){
+ struct player_skate_state *state = &player_skate.state;
+ if( localplayer.rb.to_world[1][1] < -0.1f ) return;
+ if( localplayer.rb.to_world[1][1] > 0.6f ) return;
+ if( !( button_press(k_srbind_skid) || (fabsf(state->skid)>0.1f)) ) return;
+
+ v3f lco = { 0.0f, -0.2f, -state->reverse },
+ co, dir;
+ m4x3_mulv( localplayer.rb.to_world, lco, co );
+ v3_muls( localplayer.rb.to_world[2], state->reverse, dir );
+ vg_line_arrow( co, dir, 0.13f, 0xff000000 );
+
+ ray_hit hit = { .dist = 2.0f };
+ if( ray_world( world_current_instance(), co, dir,
+ &hit, k_material_flag_ghosts )) {
+ vg_line( co, hit.pos, 0xff000000 );
+ vg_line_point( hit.pos, 0.1f, 0xff000000 );
+
+ if( hit.normal[1] < 0.7f ) return;
+ if( hit.dist < 0.95f ) return;
+
+ state->activity = k_skate_activity_handplant;
+ state->handplant_t = 0.0f;
+ v3_copy( localplayer.rb.co, state->store_co );
+ v3_copy( localplayer.rb.v, state->air_init_v );
+ v4_copy( localplayer.rb.q, state->store_q );
+ v3_copy( state->cog, state->store_cog );
+ v3_copy( state->cog_v, state->store_cog_v );
+ v4_copy( state->smoothed_rotation, state->store_smoothed );
+ }
+}
+
+static void skate_apply_pump_model(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ if( state->activity != k_skate_activity_ground ){
+ v3_zero( state->throw_v );
+ return;
+ }
+
+ /* Throw / collect routine
+ */
+ if( axis_state( k_sraxis_grab ) > 0.5f ){
+ if( state->activity == k_skate_activity_ground ){
+ /* Throw */
+ v3_muls( localplayer.rb.to_world[1], k_mmthrow_scale, state->throw_v );
+ }
+ }
+ else{
+ /* Collect */
+ f32 doty = v3_dot( localplayer.rb.to_world[1], state->throw_v );
+
+ v3f Fl, Fv;
+ v3_muladds( state->throw_v, localplayer.rb.to_world[1], -doty, Fl);
+ player_skate.collect_feedback = v3_length(Fl) * 4.0f;
+
+ if( state->activity == k_skate_activity_ground ){
+ if( v3_length2(localplayer.rb.v)<(20.0f*20.0f) ){
+ v3_muladds( localplayer.rb.v, Fl,
+ k_mmcollect_lat, localplayer.rb.v );
+ }
+ v3_muladds( state->throw_v, Fl, -k_mmcollect_lat, state->throw_v );
+ }
+
+ v3_muls( localplayer.rb.to_world[1], -doty, Fv );
+ v3_muladds( localplayer.rb.v, Fv, k_mmcollect_vert, localplayer.rb.v );
+ v3_muladds( state->throw_v, Fv, k_mmcollect_vert, state->throw_v );
+ }
+
+ /* Decay */
+ if( v3_length2( state->throw_v ) > 0.0001f ){
+ v3f dir;
+ v3_copy( state->throw_v, dir );
+ v3_normalize( dir );
+
+ float max = v3_dot( dir, state->throw_v ),
+ amt = vg_minf( k_mmdecay * vg.time_fixed_delta, max );
+ v3_muladds( state->throw_v, dir, -amt, state->throw_v );
+ }
+}
+
+static void skate_apply_cog_model(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ v3f ideal_cog, ideal_diff, ideal_dir;
+ v3_copy( state->up_dir, ideal_dir );
+ v3_normalize( ideal_dir );
+
+ float grab = axis_state( k_sraxis_grab );
+ v3_muladds( localplayer.rb.co, ideal_dir, 1.0f-grab, ideal_cog );
+ v3_sub( ideal_cog, state->cog, ideal_diff );
+
+ /* Apply velocities */
+ v3f rv;
+ v3_sub( localplayer.rb.v, state->cog_v, rv );
+
+ v3f F;
+ v3_muls( ideal_diff, -k_cog_spring * 60.0f, F );
+ v3_muladds( F, rv, -k_cog_damp * 60.0f, F );
+
+ float ra = k_cog_mass_ratio,
+ rb = 1.0f-k_cog_mass_ratio;
+
+ /* Apply forces & intergrate */
+ v3_muladds( state->cog_v, F, -rb, state->cog_v );
+ state->cog_v[1] += -9.8f * vg.time_fixed_delta;
+ v3_muladds( state->cog, state->cog_v, vg.time_fixed_delta, state->cog );
+}
+
+static void skate_integrate(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ float rate_x = 1.0f - (vg.time_fixed_delta * 3.0f),
+ rate_z = rate_x,
+ rate_y = 1.0f;
+
+ if( state->activity >= k_skate_activity_grind_any ){
+ rate_x = 1.0f-(16.0f*vg.time_fixed_delta);
+ rate_y = 1.0f-(10.0f*vg.time_fixed_delta);
+ rate_z = 1.0f-(40.0f*vg.time_fixed_delta);
+ }
+
+ float wx = v3_dot( localplayer.rb.w, localplayer.rb.to_world[0] ) * rate_x,
+ wy = v3_dot( localplayer.rb.w, localplayer.rb.to_world[1] ) * rate_y,
+ wz = v3_dot( localplayer.rb.w, localplayer.rb.to_world[2] ) * rate_z;
+
+ v3_muls( localplayer.rb.to_world[0], wx, localplayer.rb.w );
+ v3_muladds( localplayer.rb.w, localplayer.rb.to_world[1], wy,
+ localplayer.rb.w );
+ v3_muladds( localplayer.rb.w, localplayer.rb.to_world[2], wz,
+ localplayer.rb.w );
+
+ state->flip_time += state->flip_rate * vg.time_fixed_delta;
+ rb_update_matrices( &localplayer.rb );
+}
+
+static enum trick_type player_skate_trick_input(void){
+ return (button_press( k_srbind_trick0 ) ) |
+ (button_press( k_srbind_trick1 ) << 1) |
+ (button_press( k_srbind_trick2 ) << 1) |
+ (button_press( k_srbind_trick2 ) );
+}
+
+void player__skate_pre_update(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ if( state->activity == k_skate_activity_handplant ){
+ state->handplant_t += vg.time_delta;
+ mdl_keyframe hpose[32];
+
+ struct skeleton_anim *anim = player_skate.anim_handplant;
+
+ int end = !skeleton_sample_anim_clamped(
+ &localplayer.skeleton, anim,
+ state->handplant_t, hpose );
+
+ if( state->reverse < 0.0f )
+ player_mirror_pose( hpose, hpose );
+
+ mdl_keyframe *kf_world = &hpose[ localplayer.id_world -1 ];
+ m4x3f world, mmdl, world_view;
+ q_m3x3( kf_world->q, world );
+ v3_copy( kf_world->co, world[3] );
+
+ /* original mtx */
+ q_m3x3( state->store_q, mmdl );
+ v3_copy( state->store_co, mmdl[3] );
+ m4x3_mul( mmdl, world, world_view );
+
+ vg_line_arrow( world_view[3], world_view[0], 1.0f, 0xff0000ff );
+ vg_line_arrow( world_view[3], world_view[1], 1.0f, 0xff00ff00 );
+ vg_line_arrow( world_view[3], world_view[2], 1.0f, 0xffff0000 );
+
+ m4x3f invworld;
+ m4x3_invert_affine( world, invworld );
+ m4x3_mul( mmdl, invworld, world_view );
+
+ v3_copy( world_view[3], localplayer.rb.co );
+ m3x3_q( world_view, localplayer.rb.q );
+
+ /* new * old^-1 = transfer function */
+ m4x3f transfer;
+ m4x3_invert_affine( mmdl, transfer );
+ m4x3_mul( world_view, transfer, transfer );
+
+ m3x3_mulv( transfer, state->air_init_v, localplayer.rb.v );
+ m3x3_mulv( transfer, state->store_cog_v, state->cog_v );
+
+ m4x3_mulv( transfer, state->store_cog, state->cog );
+ v3_muladds( state->cog, localplayer.rb.to_world[1],
+ -state->handplant_t*0.5f, state->cog );
+
+ v4f qtransfer;
+ m3x3_q( transfer, qtransfer );
+ q_mul( qtransfer, state->store_smoothed, state->smoothed_rotation );
+ q_normalize( state->smoothed_rotation );
+ rb_update_matrices( &localplayer.rb );
+
+ if( end ){
+ state->activity = k_skate_activity_air;
+ }
+ else return;
+ }
+
+ if( button_down(k_srbind_use) && (v3_length2(state->trick_vel) < 0.01f) ){
+ localplayer.subsystem = k_player_subsystem_walk;
+
+ if( (state->activity <= k_skate_activity_air_to_grind) &&
+ localplayer.have_glider ){
+ player_glide_transition();
+ return;
+ }
+
+ v3f angles;
+ v3_copy( localplayer.cam.angles, localplayer.angles );
+ localplayer.angles[2] = 0.0f;
+
+ v3f newpos, offset;
+ m4x3_mulv( localplayer.rb.to_world, (v3f){0.0f,1.0f,0.0f}, newpos );
+ v3_add( newpos, (v3f){0.0f,-1.0f,0.0f}, newpos );
+ v3_sub( localplayer.rb.co, newpos, offset );
+ v3_copy( newpos, localplayer.rb.co );
+ v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], -0.1f,
+ localplayer.rb.co );
+
+ player__begin_holdout( offset );
+ player__walk_transition( state->activity <= k_skate_activity_air_to_grind?
+ 0: 1, state->trick_euler[0] );
+
+ return;
+ }
+
+ enum trick_type trick = player_skate_trick_input();
+ if( trick )
+ state->trick_input_collect += vg.time_frame_delta;
+ else
+ state->trick_input_collect = 0.0f;
+
+ if( state->activity <= k_skate_activity_air_to_grind ){
+ if( trick && (state->trick_input_collect < 0.1f) ){
+ if( state->trick_time == 0.0f ){
+ audio_lock();
+ audio_oneshot_3d( &audio_flips[vg_randu32(&vg.rand)%4],
+ localplayer.rb.co, 40.0f, 1.0f );
+ audio_unlock();
+ }
+
+ if( state->trick_time < 0.1f ){
+ v3_zero( state->trick_vel );
+
+ if( trick == k_trick_type_kickflip ){
+ state->trick_vel[0] = 3.0f;
+ }
+ else if( trick == k_trick_type_shuvit ){
+ state->trick_vel[2] = 3.0f;
+ }
+ else if( trick == k_trick_type_treflip ){
+ state->trick_vel[0] = 2.0f;
+ state->trick_vel[2] = 2.0f;
+ }
+ state->trick_type = trick;
+ }
+ }
+ }
+ else
+ state->trick_type = k_trick_type_none;
+}
+
+void player__skate_comp_audio( void *_animator ){
+ struct player_skate_animator *animator = _animator;
+ audio_lock();
+
+ f32 air = ((animator->activity <= k_skate_activity_air_to_grind) ||
+ (animator->activity == k_skate_activity_handplant))? 1.0f: 0.0f,
+ speed = v3_length( animator->root_v ),
+ attn = vg_minf( 1.0f, speed*0.1f ),
+ slide = animator->slide;
+
+ if( animator->activity >= k_skate_activity_grind_any )
+ slide = 0.0f;
+
+ f32 gate = skaterift.time_rate;
+
+ if( skaterift.activity == k_skaterift_replay ){
+ gate = vg_minf( 1.0f, fabsf(player_replay.track_velocity) );
+ }
+
+ f32
+ vol_main = sqrtf( (1.0f-air)*attn*(1.0f-slide) * 0.4f ) * gate,
+ vol_air = sqrtf( air *attn * 0.5f ) * gate,
+ vol_slide = sqrtf( (1.0f-air)*attn*slide * 0.25f ) * gate;
+
+ const u32 flags = AUDIO_FLAG_SPACIAL_3D|AUDIO_FLAG_LOOP;
+
+ if( !player_skate.aud_air ){
+ player_skate.aud_air = audio_get_first_idle_channel();
+ if( player_skate.aud_air )
+ audio_channel_init( player_skate.aud_air, &audio_board[1], flags );
+ }
+
+ if( !player_skate.aud_slide ){
+ player_skate.aud_slide = audio_get_first_idle_channel();
+ if( player_skate.aud_slide )
+ audio_channel_init( player_skate.aud_slide, &audio_board[2], flags );
+ }
+
+
+ /* brrrrrrrrrrrt sound for tiles and stuff
+ * --------------------------------------------------------*/
+ float sidechain_amt = 0.0f,
+ hz = vg_maxf( speed * 2.0f, 2.0f );
+
+ if( (animator->surface == k_surface_prop_tiles) &&
+ (animator->activity < k_skate_activity_grind_any) )
+ sidechain_amt = 1.0f;
+ else
+ sidechain_amt = 0.0f;
+
+ audio_set_lfo_frequency( 0, hz );
+ audio_set_lfo_wave( 0, k_lfo_polynomial_bipolar,
+ vg_lerpf( 250.0f, 80.0f, attn ) );
+
+ if( player_skate.sample_change_cooldown > 0.0f ){
+ player_skate.sample_change_cooldown -= vg.time_frame_delta;
+ }
+ else{
+ int sample_type = k_skate_sample_concrete;
+
+ if( animator->activity == k_skate_activity_grind_5050 ){
+ if( animator->surface == k_surface_prop_metal )
+ sample_type = k_skate_sample_metal_scrape_generic;
+ else
+ sample_type = k_skate_sample_concrete_scrape_metal;
+ }
+ else if( (animator->activity == k_skate_activity_grind_back50) ||
+ (animator->activity == k_skate_activity_grind_front50) )
+ {
+ if( animator->surface == k_surface_prop_metal ){
+ sample_type = k_skate_sample_metal_scrape_generic;
+ }
+ else{
+#if 0
+ float a = v3_dot( localplayer.rb.to_world[2],
+ player_skate.grind_dir );
+ if( fabsf(a) > 0.70710678118654752f )
+ sample_type = k_skate_sample_concrete_scrape_wood;
+ else
+ sample_type = k_skate_sample_concrete_scrape_metal;
+#endif
+
+ sample_type = k_skate_sample_concrete_scrape_wood;
+ }
+ }
+ else if( animator->activity == k_skate_activity_grind_boardslide ){
+ if( animator->surface == k_surface_prop_metal )
+ sample_type = k_skate_sample_metal_scrape_generic;
+ else
+ sample_type = k_skate_sample_concrete_scrape_wood;
+ }
+
+ audio_clip *relevant_samples[] = {
+ &audio_board[0],
+ &audio_board[0],
+ &audio_board[7],
+ &audio_board[6],
+ &audio_board[5]
+ };
+
+ if( (player_skate.main_sample_type != sample_type) ||
+ (!player_skate.aud_main) ){
+
+ player_skate.aud_main =
+ audio_channel_crossfade( player_skate.aud_main,
+ relevant_samples[sample_type],
+ 0.06f, flags );
+ player_skate.sample_change_cooldown = 0.1f;
+ player_skate.main_sample_type = sample_type;
+ }
+ }
+
+ if( player_skate.aud_main ){
+ player_skate.aud_main->colour = 0x00103efe;
+ audio_channel_set_spacial( player_skate.aud_main,
+ animator->root_co, 40.0f );
+ //audio_channel_slope_volume( player_skate.aud_main, 0.05f, vol_main );
+ audio_channel_edit_volume( player_skate.aud_main, vol_main, 1 );
+ audio_channel_sidechain_lfo( player_skate.aud_main, 0, sidechain_amt );
+
+ float rate = 1.0f + (attn-0.5f)*0.2f;
+ audio_channel_set_sampling_rate( player_skate.aud_main, rate );
+ }
+
+ if( player_skate.aud_slide ){
+ player_skate.aud_slide->colour = 0x00103efe;
+ audio_channel_set_spacial( player_skate.aud_slide,
+ animator->root_co, 40.0f );
+ //audio_channel_slope_volume( player_skate.aud_slide, 0.05f, vol_slide );
+ audio_channel_edit_volume( player_skate.aud_slide, vol_slide, 1 );
+ audio_channel_sidechain_lfo( player_skate.aud_slide, 0, sidechain_amt );
+ }
+
+ if( player_skate.aud_air ){
+ player_skate.aud_air->colour = 0x00103efe;
+ audio_channel_set_spacial( player_skate.aud_air,
+ animator->root_co, 40.0f );
+ //audio_channel_slope_volume( player_skate.aud_air, 0.05f, vol_air );
+ audio_channel_edit_volume( player_skate.aud_air, vol_air, 1 );
+ }
+
+ audio_unlock();
+}
+
+void player__skate_post_update(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ for( int i=0; i<player_skate.possible_jump_count; i++ ){
+ jump_info *jump = &player_skate.possible_jumps[i];
+
+ if( jump->log_length == 0 ){
+ vg_fatal_error( "assert: jump->log_length == 0\n" );
+ }
+
+ for( int j=0; j<jump->log_length - 1; j ++ ){
+ float brightness = jump->score*jump->score*jump->score;
+ v3f p1;
+ v3_lerp( jump->log[j], jump->log[j+1], brightness, p1 );
+ vg_line( jump->log[j], p1, jump->colour );
+ }
+
+ vg_line_cross( jump->log[jump->log_length-1], jump->colour, 0.25f );
+
+ v3f p1;
+ v3_add( jump->log[jump->log_length-1], jump->n, p1 );
+ vg_line( jump->log[jump->log_length-1], p1, 0xffffffff );
+
+ vg_line_point( jump->apex, 0.02f, 0xffffffff );
+ }
+}
+
+/*
+ * truck alignment model at ra(local)
+ * returns 1 if valid surface:
+ * surface_normal will be filled out with an averaged normal vector
+ * axel_dir will be the direction from left to right wheels
+ *
+ * returns 0 if no good surface found
+ */
+static
+int skate_compute_surface_alignment( v3f ra, u32 colour,
+ v3f surface_normal, v3f axel_dir ){
+ world_instance *world = world_current_instance();
+
+ v3f truck, left, right;
+ m4x3_mulv( localplayer.rb.to_world, ra, truck );
+
+ v3_muladds( truck, localplayer.rb.to_world[0], -k_board_width, left );
+ v3_muladds( truck, localplayer.rb.to_world[0], k_board_width, right );
+ vg_line( left, right, colour );
+
+ float k_max_truck_flex = VG_PIf * 0.25f;
+
+ ray_hit ray_l, ray_r;
+
+ v3f dir;
+ v3_muls( localplayer.rb.to_world[1], -1.0f, dir );
+
+ int res_l = 0, res_r = 0;
+
+ for( int i=0; i<8; i++ ){
+ float t = 1.0f - (float)i * (1.0f/8.0f);
+ v3_muladds( truck, localplayer.rb.to_world[0], -k_board_radius*t, left );
+ v3_muladds( left, localplayer.rb.to_world[1], k_board_radius, left );
+ ray_l.dist = 2.1f * k_board_radius;
+
+ res_l = ray_world( world, left, dir, &ray_l, k_material_flag_walking );
+
+ if( res_l )
+ break;
+ }
+
+ for( int i=0; i<8; i++ ){
+ float t = 1.0f - (float)i * (1.0f/8.0f);
+ v3_muladds( truck, localplayer.rb.to_world[0], k_board_radius*t, right );
+ v3_muladds( right, localplayer.rb.to_world[1], k_board_radius, right );
+ ray_r.dist = 2.1f * k_board_radius;
+
+ res_r = ray_world( world, right, dir, &ray_r, k_material_flag_walking );
+
+ if( res_r )
+ break;
+ }
+
+ v3f v0;
+ v3f midpoint;
+ v3f tangent_average;
+ v3_muladds( truck, localplayer.rb.to_world[1], -k_board_radius, midpoint );
+ v3_zero( tangent_average );
+
+ if( res_l || res_r ){
+ v3f p0, p1, t;
+ v3_copy( midpoint, p0 );
+ v3_copy( midpoint, p1 );
+
+ if( res_l ){
+ v3_copy( ray_l.pos, p0 );
+ v3_cross( ray_l.normal, localplayer.rb.to_world[0], t );
+ v3_add( t, tangent_average, tangent_average );
+ }
+ if( res_r ){
+ v3_copy( ray_r.pos, p1 );
+ v3_cross( ray_r.normal, localplayer.rb.to_world[0], t );
+ v3_add( t, tangent_average, tangent_average );
+ }
+
+ v3_sub( p1, p0, v0 );
+ v3_normalize( v0 );
+ }
+ else{
+ /* fallback: use the closes point to the trucks */
+ v3f closest;
+ int idx = bh_closest_point( world->geo_bh, midpoint, closest, 0.1f );
+
+ if( idx != -1 ){
+ u32 *tri = &world->scene_geo.arrindices[ idx * 3 ];
+ v3f verts[3];
+
+ for( int j=0; j<3; j++ )
+ v3_copy( world->scene_geo.arrvertices[ tri[j] ].co, verts[j] );
+
+ v3f vert0, vert1, n;
+ v3_sub( verts[1], verts[0], vert0 );
+ v3_sub( verts[2], verts[0], vert1 );
+ v3_cross( vert0, vert1, n );
+ v3_normalize( n );
+
+ if( v3_dot( n, localplayer.rb.to_world[1] ) < 0.3f )
+ return 0;
+
+ v3_cross( n, localplayer.rb.to_world[2], v0 );
+ v3_muladds( v0, localplayer.rb.to_world[2],
+ -v3_dot( localplayer.rb.to_world[2], v0 ), v0 );
+ v3_normalize( v0 );
+
+ v3f t;
+ v3_cross( n, localplayer.rb.to_world[0], t );
+ v3_add( t, tangent_average, tangent_average );
+ }
+ else
+ return 0;
+ }
+
+ v3_muladds( truck, v0, k_board_width, right );
+ v3_muladds( truck, v0, -k_board_width, left );
+
+ vg_line( left, right, VG__WHITE );
+
+ v3_normalize( tangent_average );
+ v3_cross( v0, tangent_average, surface_normal );
+ v3_copy( v0, axel_dir );
+
+ return 1;
+}
+
+static void skate_weight_distribute(void){
+ struct player_skate_state *state = &player_skate.state;
+ v3_zero( player_skate.weight_distribution );
+
+ int reverse_dir = v3_dot( localplayer.rb.to_world[2],
+ localplayer.rb.v ) < 0.0f?1:-1;
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ if( state->manual_direction == 0 ){
+ if( (steer[1] > 0.7f) && (state->activity == k_skate_activity_ground) &&
+ (state->jump_charge <= 0.01f) )
+ state->manual_direction = reverse_dir;
+ }
+ else{
+ if( steer[1] < 0.1f ){
+ state->manual_direction = 0;
+ }
+ else{
+ if( reverse_dir != state->manual_direction ){
+ return;
+ }
+ }
+ }
+
+ if( state->manual_direction ){
+ float amt = vg_minf( steer[1] * 8.0f, 1.0f );
+ player_skate.weight_distribution[2] = k_board_length * amt *
+ (float)state->manual_direction;
+ }
+
+ if( state->manual_direction ){
+ v3f plane_z;
+
+ m3x3_mulv( localplayer.rb.to_world, player_skate.weight_distribution,
+ plane_z );
+ v3_negate( plane_z, plane_z );
+
+ v3_muladds( plane_z, player_skate.surface_picture,
+ -v3_dot( plane_z, player_skate.surface_picture ), plane_z );
+ v3_normalize( plane_z );
+
+ v3_muladds( plane_z, player_skate.surface_picture, 0.3f, plane_z );
+ v3_normalize( plane_z );
+
+ v3f p1;
+ v3_muladds( localplayer.rb.co, plane_z, 1.5f, p1 );
+ vg_line( localplayer.rb.co, p1, VG__GREEN );
+
+ v3f refdir;
+ v3_muls( localplayer.rb.to_world[2], -(float)state->manual_direction,
+ refdir );
+
+ rb_effect_spring_target_vector( &localplayer.rb, refdir, plane_z,
+ k_manul_spring, k_manul_dampener,
+ player_skate.substep_delta );
+ }
+}
+
+static void skate_adjust_up_direction(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ if( state->activity == k_skate_activity_ground ){
+ v3f target;
+ v3_copy( player_skate.surface_picture, target );
+
+ target[1] += 2.0f * player_skate.surface_picture[1];
+ v3_normalize( target );
+
+ v3_lerp( state->up_dir, target,
+ 8.0f * player_skate.substep_delta, state->up_dir );
+ }
+ else if( state->activity <= k_skate_activity_air_to_grind ){
+ v3_lerp( state->up_dir, localplayer.rb.to_world[1],
+ 8.0f * player_skate.substep_delta, state->up_dir );
+ }
+ else{
+ v3f avg;
+ v3_add( localplayer.rb.to_world[1], (v3f){0,1,0}, avg );
+ v3_normalize( avg );
+
+ v3_lerp( state->up_dir, avg,
+ 6.0f * player_skate.substep_delta, state->up_dir );
+ }
+}
+
+static int skate_point_visible( v3f origin, v3f target ){
+ v3f dir;
+ v3_sub( target, origin, dir );
+
+ ray_hit ray;
+ ray.dist = v3_length( dir );
+ v3_muls( dir, 1.0f/ray.dist, dir );
+ ray.dist -= 0.025f;
+
+ if( ray_world( world_current_instance(), origin, dir, &ray,
+ k_material_flag_walking ) )
+ return 0;
+
+ return 1;
+}
+
+static void skate_grind_orient( struct grind_info *inf, m3x3f mtx ){
+ v3_copy( inf->dir, mtx[0] );
+ v3_copy( inf->n, mtx[1] );
+ v3_cross( mtx[0], mtx[1], mtx[2] );
+}
+
+static void skate_grind_friction( struct grind_info *inf, float strength ){
+ v3f v2;
+ v3_muladds( localplayer.rb.to_world[2], inf->n,
+ -v3_dot( localplayer.rb.to_world[2], inf->n ), v2 );
+
+ float a = 1.0f-fabsf( v3_dot( v2, inf->dir ) ),
+ dir = vg_signf( v3_dot( localplayer.rb.v, inf->dir ) ),
+ F = a * -dir * k_grind_max_friction;
+
+ v3_muladds( localplayer.rb.v, inf->dir, F*vg.time_fixed_delta*strength,
+ localplayer.rb.v );
+}
+
+static void skate_grind_decay( struct grind_info *inf, float strength ){
+ m3x3f mtx, mtx_inv;
+ skate_grind_orient( inf, mtx );
+ m3x3_transpose( mtx, mtx_inv );
+
+ v3f v_grind;
+ m3x3_mulv( mtx_inv, localplayer.rb.v, v_grind );
+
+ float decay = 1.0f - ( vg.time_fixed_delta * k_grind_decayxy * strength );
+ v3_mul( v_grind, (v3f){ 1.0f, decay, decay }, v_grind );
+ m3x3_mulv( mtx, v_grind, localplayer.rb.v );
+}
+
+static void skate_grind_truck_apply( float sign, struct grind_info *inf,
+ float strength ){
+ struct player_skate_state *state = &player_skate.state;
+ /* REFACTOR */
+ v3f ra = { 0.0f, -k_board_radius, sign * k_board_length };
+ v3f raw, wsp;
+ m3x3_mulv( localplayer.rb.to_world, ra, raw );
+ v3_add( localplayer.rb.co, raw, wsp );
+
+ v3_copy( ra, player_skate.weight_distribution );
+
+ v3f delta;
+ v3_sub( inf->co, wsp, delta );
+
+ /* spring force */
+ v3_muladds( localplayer.rb.v, delta, k_spring_force*strength*vg.time_fixed_delta,
+ localplayer.rb.v );
+
+ skate_grind_decay( inf, strength );
+ skate_grind_friction( inf, strength );
+
+ /* yeah yeah yeah yeah */
+ v3f raw_nplane, axis;
+ v3_muladds( raw, inf->n, -v3_dot( inf->n, raw ), raw_nplane );
+ v3_cross( raw_nplane, inf->n, axis );
+ v3_normalize( axis );
+
+ /* orientation */
+ m3x3f mtx;
+ skate_grind_orient( inf, mtx );
+ v3f target_fwd, fwd, up, target_up;
+ m3x3_mulv( mtx, player_skate.grind_vec, target_fwd );
+ v3_copy( raw_nplane, fwd );
+ v3_copy( localplayer.rb.to_world[1], up );
+ v3_copy( inf->n, target_up );
+
+ v3_muladds( target_fwd, inf->n, -v3_dot(inf->n,target_fwd), target_fwd );
+ v3_muladds( fwd, inf->n, -v3_dot(inf->n,fwd), fwd );
+
+ v3_normalize( target_fwd );
+ v3_normalize( fwd );
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ float way = steer[1] * vg_signf( v3_dot( raw_nplane, localplayer.rb.v ) );
+
+ v4f q;
+ q_axis_angle( q, axis, VG_PIf*0.125f * way );
+ q_mulv( q, target_up, target_up );
+ q_mulv( q, target_fwd, target_fwd );
+
+ rb_effect_spring_target_vector( &localplayer.rb, up, target_up,
+ k_grind_spring,
+ k_grind_dampener,
+ vg.time_fixed_delta );
+
+ rb_effect_spring_target_vector( &localplayer.rb, fwd, target_fwd,
+ k_grind_spring*strength,
+ k_grind_dampener*strength,
+ vg.time_fixed_delta );
+
+ vg_line_arrow( localplayer.rb.co, target_up, 1.0f, VG__GREEN );
+ vg_line_arrow( localplayer.rb.co, fwd, 0.8f, VG__RED );
+ vg_line_arrow( localplayer.rb.co, target_fwd, 1.0f, VG__YELOW );
+
+ player_skate.grind_strength = strength;
+
+ /* Fake contact */
+ struct grind_limit *limit =
+ &player_skate.limits[ player_skate.limit_count ++ ];
+ m4x3_mulv( localplayer.rb.to_local, wsp, limit->ra );
+ m3x3_mulv( localplayer.rb.to_local, inf->n, limit->n );
+ limit->p = 0.0f;
+
+ v3_copy( inf->dir, player_skate.grind_dir );
+}
+
+static void skate_5050_apply( struct grind_info *inf_front,
+ struct grind_info *inf_back ){
+ struct player_skate_state *state = &player_skate.state;
+ struct grind_info inf_avg;
+
+ v3_sub( inf_front->co, inf_back->co, inf_avg.dir );
+ v3_muladds( inf_back->co, inf_avg.dir, 0.5f, inf_avg.co );
+ v3_normalize( inf_avg.dir );
+
+ /* dont ask */
+ v3_muls( inf_avg.dir, vg_signf(v3_dot(inf_avg.dir,localplayer.rb.v)),
+ inf_avg.dir );
+
+ v3f axis_front, axis_back, axis;
+ v3_cross( inf_front->dir, inf_front->n, axis_front );
+ v3_cross( inf_back->dir, inf_back->n, axis_back );
+ v3_add( axis_front, axis_back, axis );
+ v3_normalize( axis );
+
+ v3_cross( axis, inf_avg.dir, inf_avg.n );
+ skate_grind_decay( &inf_avg, 1.0f );
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ float way = steer[1] * vg_signf( v3_dot( localplayer.rb.to_world[2],
+ localplayer.rb.v ) );
+ v4f q;
+ v3f up, target_up;
+ v3_copy( localplayer.rb.to_world[1], up );
+ v3_copy( inf_avg.n, target_up );
+ q_axis_angle( q, localplayer.rb.to_world[0], VG_PIf*0.25f * -way );
+ q_mulv( q, target_up, target_up );
+
+ v3_zero( player_skate.weight_distribution );
+ player_skate.weight_distribution[2] = k_board_length * -way;
+
+ rb_effect_spring_target_vector( &localplayer.rb, up, target_up,
+ k_grind_spring,
+ k_grind_dampener,
+ vg.time_fixed_delta );
+ vg_line_arrow( localplayer.rb.co, up, 1.0f, VG__GREEN );
+ vg_line_arrow( localplayer.rb.co, target_up, 1.0f, VG__GREEN );
+
+ v3f fwd_nplane, dir_nplane;
+ v3_muladds( localplayer.rb.to_world[2], inf_avg.n,
+ -v3_dot( localplayer.rb.to_world[2], inf_avg.n ), fwd_nplane );
+
+ v3f dir;
+ v3_muls( inf_avg.dir, v3_dot( fwd_nplane, inf_avg.dir ), dir );
+ v3_muladds( dir, inf_avg.n, -v3_dot( dir, inf_avg.n ), dir_nplane );
+
+ v3_normalize( fwd_nplane );
+ v3_normalize( dir_nplane );
+
+ rb_effect_spring_target_vector( &localplayer.rb, fwd_nplane, dir_nplane,
+ 1000.0f,
+ k_grind_dampener,
+ vg.time_fixed_delta );
+ vg_line_arrow( localplayer.rb.co, fwd_nplane, 0.8f, VG__RED );
+ vg_line_arrow( localplayer.rb.co, dir_nplane, 0.8f, VG__RED );
+
+ v3f pos_front = { 0.0f, -k_board_radius, -1.0f * k_board_length },
+ pos_back = { 0.0f, -k_board_radius, 1.0f * k_board_length },
+ delta_front, delta_back, delta_total;
+
+ m4x3_mulv( localplayer.rb.to_world, pos_front, pos_front );
+ m4x3_mulv( localplayer.rb.to_world, pos_back, pos_back );
+
+ v3_sub( inf_front->co, pos_front, delta_front );
+ v3_sub( inf_back->co, pos_back, delta_back );
+ v3_add( delta_front, delta_back, delta_total );
+
+ v3_muladds( localplayer.rb.v, delta_total, 50.0f * vg.time_fixed_delta,
+ localplayer.rb.v );
+
+ /* Fake contact */
+ struct grind_limit *limit =
+ &player_skate.limits[ player_skate.limit_count ++ ];
+ v3_zero( limit->ra );
+ m3x3_mulv( localplayer.rb.to_local, inf_avg.n, limit->n );
+ limit->p = 0.0f;
+
+ v3_copy( inf_avg.dir, player_skate.grind_dir );
+}
+
+static int skate_grind_truck_renew( f32 sign, struct grind_info *inf ){
+ struct player_skate_state *state = &player_skate.state;
+
+ v3f wheel_co = { 0.0f, 0.0f, sign * k_board_length },
+ grind_co = { 0.0f, -k_board_radius, sign * k_board_length };
+
+ m4x3_mulv( localplayer.rb.to_world, wheel_co, wheel_co );
+ m4x3_mulv( localplayer.rb.to_world, grind_co, grind_co );
+
+ /* Exit condition: lost grind tracking */
+ if( !skate_grind_scansq( grind_co, localplayer.rb.v, 0.3f, inf ) )
+ return 0;
+
+ /* Exit condition: cant see grind target directly */
+ if( !skate_point_visible( wheel_co, inf->co ) )
+ return 0;
+
+ /* Exit condition: minimum velocity not reached, but allow a bit of error */
+ float dv = fabsf(v3_dot( localplayer.rb.v, inf->dir )),
+ minv = k_grind_axel_min_vel*0.8f;
+
+ if( dv < minv )
+ return 0;
+
+ if( fabsf(v3_dot( inf->dir, player_skate.grind_dir )) < k_grind_max_edge_angle )
+ return 0;
+
+ v3_copy( inf->dir, player_skate.grind_dir );
+ return 1;
+}
+
+static int skate_grind_truck_entry( f32 sign, struct grind_info *inf ){
+ struct player_skate_state *state = &player_skate.state;
+
+ /* REFACTOR */
+ v3f ra = { 0.0f, -k_board_radius, sign * k_board_length };
+
+ v3f raw, wsp;
+ m3x3_mulv( localplayer.rb.to_world, ra, raw );
+ v3_add( localplayer.rb.co, raw, wsp );
+
+ if( skate_grind_scansq( wsp, localplayer.rb.v, 0.3, inf ) ){
+ if( fabsf(v3_dot( localplayer.rb.v, inf->dir )) < k_grind_axel_min_vel )
+ return 0;
+
+ /* velocity should be at least 60% aligned */
+ v3f pv, axis;
+ v3_cross( inf->n, inf->dir, axis );
+ v3_muladds( localplayer.rb.v, inf->n,
+ -v3_dot( localplayer.rb.v, inf->n ), pv );
+
+ if( v3_length2( pv ) < 0.0001f )
+ return 0;
+ v3_normalize( pv );
+
+ if( fabsf(v3_dot( pv, inf->dir )) < k_grind_axel_max_angle )
+ return 0;
+
+ if( v3_dot( localplayer.rb.v, inf->n ) > 0.5f )
+ return 0;
+
+ v3f local_co, local_dir, local_n;
+ m4x3_mulv( localplayer.rb.to_local, inf->co, local_co );
+ m3x3_mulv( localplayer.rb.to_local, inf->dir, local_dir );
+ m3x3_mulv( localplayer.rb.to_local, inf->n, local_n );
+
+ v2f delta = { local_co[0], local_co[2] - k_board_length*sign };
+
+ float truck_height = -(k_board_radius+0.03f);
+
+ v3f rv;
+ v3_cross( localplayer.rb.w, raw, rv );
+ v3_add( localplayer.rb.v, rv, rv );
+
+ if( (local_co[1] >= truck_height) &&
+ (v2_length2( delta ) <= k_board_radius*k_board_radius) )
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void skate_boardslide_apply( struct grind_info *inf ){
+ struct player_skate_state *state = &player_skate.state;
+
+ v3f local_co, local_dir, local_n;
+ m4x3_mulv( localplayer.rb.to_local, inf->co, local_co );
+ m3x3_mulv( localplayer.rb.to_local, inf->dir, local_dir );
+ m3x3_mulv( localplayer.rb.to_local, inf->n, local_n );
+
+ v3f intersection;
+ v3_muladds( local_co, local_dir, local_co[0]/-local_dir[0],
+ intersection );
+ v3_copy( intersection, player_skate.weight_distribution );
+
+ skate_grind_decay( inf, 0.0125f );
+ skate_grind_friction( inf, 0.25f );
+
+ /* direction alignment */
+ v3f dir, perp;
+ v3_cross( local_dir, local_n, perp );
+ v3_muls( local_dir, vg_signf(local_dir[0]), dir );
+ v3_muls( perp, vg_signf(perp[2]), perp );
+
+ m3x3_mulv( localplayer.rb.to_world, dir, dir );
+ m3x3_mulv( localplayer.rb.to_world, perp, perp );
+
+ v4f qbalance;
+ q_axis_angle( qbalance, dir, local_co[0]*k_grind_balance );
+ q_mulv( qbalance, perp, perp );
+
+ rb_effect_spring_target_vector( &localplayer.rb, localplayer.rb.to_world[0],
+ dir,
+ k_grind_spring, k_grind_dampener,
+ vg.time_fixed_delta );
+
+ rb_effect_spring_target_vector( &localplayer.rb, localplayer.rb.to_world[2],
+ perp,
+ k_grind_spring, k_grind_dampener,
+ vg.time_fixed_delta );
+
+ vg_line_arrow( localplayer.rb.co, dir, 0.5f, VG__GREEN );
+ vg_line_arrow( localplayer.rb.co, perp, 0.5f, VG__BLUE );
+
+ v3_copy( inf->dir, player_skate.grind_dir );
+}
+
+static int skate_boardslide_entry( struct grind_info *inf ){
+ struct player_skate_state *state = &player_skate.state;
+
+ if( skate_grind_scansq( localplayer.rb.co,
+ localplayer.rb.to_world[0], k_board_length,
+ inf ) )
+ {
+ v3f local_co, local_dir;
+ m4x3_mulv( localplayer.rb.to_local, inf->co, local_co );
+ m3x3_mulv( localplayer.rb.to_local, inf->dir, local_dir );
+
+ if( (fabsf(local_co[2]) <= k_board_length) && /* within wood area */
+ (local_co[1] >= 0.0f) && /* at deck level */
+ (fabsf(local_dir[0]) >= 0.25f) ) /* perpendicular to us */
+ {
+ if( fabsf(v3_dot( localplayer.rb.v, inf->dir )) < k_grind_axel_min_vel )
+ return 0;
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int skate_boardslide_renew( struct grind_info *inf ){
+ struct player_skate_state *state = &player_skate.state;
+
+ if( !skate_grind_scansq( localplayer.rb.co,
+ localplayer.rb.to_world[0], k_board_length,
+ inf ) )
+ return 0;
+
+ /* Exit condition: cant see grind target directly */
+ v3f vis;
+ v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], 0.2f, vis );
+ if( !skate_point_visible( vis, inf->co ) )
+ return 0;
+
+ /* Exit condition: minimum velocity not reached, but allow a bit of error */
+ float dv = fabsf(v3_dot( localplayer.rb.v, inf->dir )),
+ minv = k_grind_axel_min_vel*0.8f;
+
+ if( dv < minv )
+ return 0;
+
+ if( fabsf(v3_dot( inf->dir, player_skate.grind_dir )) < k_grind_max_edge_angle )
+ return 0;
+
+ return 1;
+}
+
+static void skate_store_grind_vec( struct grind_info *inf ){
+ struct player_skate_state *state = &player_skate.state;
+
+ m3x3f mtx;
+ skate_grind_orient( inf, mtx );
+ m3x3_transpose( mtx, mtx );
+
+ v3f raw;
+ v3_sub( inf->co, localplayer.rb.co, raw );
+
+ m3x3_mulv( mtx, raw, player_skate.grind_vec );
+ v3_normalize( player_skate.grind_vec );
+ v3_copy( inf->dir, player_skate.grind_dir );
+}
+
+static enum skate_activity skate_availible_grind(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ if( state->grind_cooldown > 100 ){
+ vg_fatal_error( "wth!\n" );
+ }
+
+ /* debounces this state manager a little bit */
+ if( state->grind_cooldown ){
+ state->grind_cooldown --;
+ return k_skate_activity_undefined;
+ }
+
+ struct grind_info inf_back50,
+ inf_front50,
+ inf_slide;
+
+ int res_back50 = 0,
+ res_front50 = 0,
+ res_slide = 0;
+
+ int allow_back = 1,
+ allow_front = 1;
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ if( state->activity == k_skate_activity_grind_5050 ||
+ state->activity == k_skate_activity_grind_back50 ||
+ state->activity == k_skate_activity_grind_front50 )
+ {
+ float tilt = steer[1];
+
+ if( fabsf(tilt) >= 0.25f ){
+ v3f raw = {0.0f,0.0f,tilt};
+ m3x3_mulv( localplayer.rb.to_world, raw, raw );
+
+ float way = tilt * vg_signf( v3_dot( raw, localplayer.rb.v ) );
+
+ if( way < 0.0f ) allow_front = 0;
+ else allow_back = 0;
+ }
+ }
+
+ if( state->activity == k_skate_activity_grind_boardslide ){
+ res_slide = skate_boardslide_renew( &inf_slide );
+ }
+ else if( state->activity == k_skate_activity_grind_back50 ){
+ res_back50 = skate_grind_truck_renew( 1.0f, &inf_back50 );
+
+ if( allow_front )
+ res_front50 = skate_grind_truck_entry( -1.0f, &inf_front50 );
+ }
+ else if( state->activity == k_skate_activity_grind_front50 ){
+ res_front50 = skate_grind_truck_renew( -1.0f, &inf_front50 );
+
+ if( allow_back )
+ res_back50 = skate_grind_truck_entry( 1.0f, &inf_back50 );
+ }
+ else if( state->activity == k_skate_activity_grind_5050 ){
+ if( allow_front )
+ res_front50 = skate_grind_truck_renew( -1.0f, &inf_front50 );
+ if( allow_back )
+ res_back50 = skate_grind_truck_renew( 1.0f, &inf_back50 );
+ }
+ else{
+ res_slide = skate_boardslide_entry( &inf_slide );
+
+ if( allow_back )
+ res_back50 = skate_grind_truck_entry( 1.0f, &inf_back50 );
+
+ if( allow_front )
+ res_front50 = skate_grind_truck_entry( -1.0f, &inf_front50 );
+
+ if( res_back50 != res_front50 ){
+ int wants_to_do_that = fabsf(steer[1]) >= 0.25f;
+
+ res_back50 &= wants_to_do_that;
+ res_front50 &= wants_to_do_that;
+ }
+ }
+
+ const enum skate_activity table[] =
+ { /* slide | back | front */
+ k_skate_activity_undefined, /* 0 0 0 */
+ k_skate_activity_grind_front50, /* 0 0 1 */
+ k_skate_activity_grind_back50, /* 0 1 0 */
+ k_skate_activity_grind_5050, /* 0 1 1 */
+
+ /* slide has priority always */
+ k_skate_activity_grind_boardslide, /* 1 0 0 */
+ k_skate_activity_grind_boardslide, /* 1 0 1 */
+ k_skate_activity_grind_boardslide, /* 1 1 0 */
+ k_skate_activity_grind_boardslide, /* 1 1 1 */
+ }
+ , new_activity = table[ res_slide << 2 | res_back50 << 1 | res_front50 ];
+
+ if( new_activity == k_skate_activity_undefined ){
+ if( state->activity >= k_skate_activity_grind_any ){
+ state->grind_cooldown = 15;
+ state->surface_cooldown = 10;
+ }
+ }
+ else if( new_activity == k_skate_activity_grind_boardslide ){
+ skate_boardslide_apply( &inf_slide );
+ }
+ else if( new_activity == k_skate_activity_grind_back50 ){
+ if( state->activity != k_skate_activity_grind_back50 )
+ skate_store_grind_vec( &inf_back50 );
+
+ skate_grind_truck_apply( 1.0f, &inf_back50, 1.0f );
+ }
+ else if( new_activity == k_skate_activity_grind_front50 ){
+ if( state->activity != k_skate_activity_grind_front50 )
+ skate_store_grind_vec( &inf_front50 );
+
+ skate_grind_truck_apply( -1.0f, &inf_front50, 1.0f );
+ }
+ else if( new_activity == k_skate_activity_grind_5050 )
+ skate_5050_apply( &inf_front50, &inf_back50 );
+
+ return new_activity;
+}
+
+void player__skate_update(void){
+ struct player_skate_state *state = &player_skate.state;
+ world_instance *world = world_current_instance();
+
+ if( state->activity == k_skate_activity_handplant )
+ return;
+
+ if( !world_water_player_safe( world, 0.25f ) ) return;
+
+ v3_copy( localplayer.rb.co, state->prev_pos );
+ state->activity_prev = state->activity;
+ v3f normal_total;
+ v3_zero( normal_total );
+
+ struct board_collider
+ {
+ v3f pos;
+ float radius;
+
+ u32 colour;
+
+ enum board_collider_state
+ {
+ k_collider_state_default,
+ k_collider_state_disabled,
+ k_collider_state_colliding
+ }
+ state;
+ }
+ wheels[] =
+ {
+ {
+ { 0.0f, 0.0f, -k_board_length },
+ .radius = k_board_radius,
+ .colour = VG__RED
+ },
+ {
+ { 0.0f, 0.0f, k_board_length },
+ .radius = k_board_radius,
+ .colour = VG__GREEN
+ }
+ };
+
+ float slap = 0.0f;
+
+ if( state->activity <= k_skate_activity_air_to_grind ){
+ float min_dist = 0.6f;
+ for( int i=0; i<2; i++ ){
+ v3f wpos, closest;
+ m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, wpos );
+
+ if( bh_closest_point( world->geo_bh, wpos, closest, min_dist ) != -1 ){
+ min_dist = vg_minf( min_dist, v3_dist( closest, wpos ) );
+ }
+ }
+ min_dist -= 0.2f;
+ float vy = vg_maxf( 0.0f, localplayer.rb.v[1] );
+ slap = vg_clampf( (min_dist/0.5f) + vy, 0.0f, 1.0f )*0.3f;
+ }
+ state->slap = vg_lerpf( state->slap, slap, 10.0f*vg.time_fixed_delta );
+
+ wheels[0].pos[1] = state->slap;
+ wheels[1].pos[1] = state->slap;
+
+
+ const int k_wheel_count = 2;
+
+ player_skate.substep = vg.time_fixed_delta;
+ player_skate.substep_delta = player_skate.substep;
+ player_skate.limit_count = 0;
+
+ int substep_count = 0;
+
+ v3_zero( player_skate.surface_picture );
+
+ int prev_contacts[2];
+
+ for( int i=0; i<k_wheel_count; i++ ){
+ wheels[i].state = k_collider_state_default;
+ prev_contacts[i] = player_skate.wheel_contacts[i];
+ }
+
+ /* check if we can enter or continue grind */
+ enum skate_activity grindable_activity = skate_availible_grind();
+ if( grindable_activity != k_skate_activity_undefined ){
+ state->activity = grindable_activity;
+ goto grinding;
+ }
+
+ int contact_count = 0;
+ for( int i=0; i<2; i++ ){
+ v3f normal, axel;
+ v3_copy( localplayer.rb.to_world[0], axel );
+
+ if( skate_compute_surface_alignment( wheels[i].pos,
+ wheels[i].colour, normal, axel ) )
+ {
+ rb_effect_spring_target_vector( &localplayer.rb,
+ localplayer.rb.to_world[0],
+ axel,
+ k_surface_spring, k_surface_dampener,
+ player_skate.substep_delta );
+
+ v3_add( normal, player_skate.surface_picture,
+ player_skate.surface_picture );
+ contact_count ++;
+ player_skate.wheel_contacts[i] = 1;
+ }
+ else{
+ player_skate.wheel_contacts[i] = 0;
+ }
+
+ m3x3_mulv( localplayer.rb.to_local, axel, player_skate.truckv0[i] );
+ }
+
+ if( state->surface_cooldown ){
+ state->surface_cooldown --;
+ contact_count = 0;
+ }
+
+ if( (prev_contacts[0]+prev_contacts[1] == 1) && (contact_count == 2) ){
+ for( int i=0; i<2; i++ ){
+ if( !prev_contacts[i] ){
+ v3f co;
+ m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, co );
+ player__networked_sfx( k_player_subsystem_skate, 32,
+ k_player_skate_soundeffect_tap,
+ localplayer.rb.co, 0.75f );
+ }
+ }
+ }
+
+ if( contact_count ){
+ state->activity = k_skate_activity_ground;
+ state->gravity_bias = k_gravity;
+ v3_normalize( player_skate.surface_picture );
+
+ skate_apply_friction_model();
+ skate_weight_distribute();
+ }
+ else{
+ if( state->activity > k_skate_activity_air_to_grind )
+ state->activity = k_skate_activity_air;
+
+ v3_zero( player_skate.weight_distribution );
+ skate_apply_air_model();
+ }
+
+grinding:;
+
+ if( state->activity == k_skate_activity_grind_back50 )
+ wheels[1].state = k_collider_state_disabled;
+ if( state->activity == k_skate_activity_grind_front50 )
+ wheels[0].state = k_collider_state_disabled;
+ if( state->activity == k_skate_activity_grind_5050 ){
+ wheels[0].state = k_collider_state_disabled;
+ wheels[1].state = k_collider_state_disabled;
+ }
+
+ /* all activities */
+ skate_apply_steering_model();
+ skate_adjust_up_direction();
+ skate_apply_cog_model();
+ skate_apply_jump_model();
+ skate_apply_handplant_model();
+ skate_apply_grab_model();
+ skate_apply_trick_model();
+ skate_apply_pump_model();
+
+ ent_tornado_debug();
+ v3f a;
+ ent_tornado_forces( localplayer.rb.co, localplayer.rb.v, a );
+ v3_muladds( localplayer.rb.v, a, vg.time_fixed_delta, localplayer.rb.v );
+
+begin_collision:;
+
+ /*
+ * Phase 0: Continous collision detection
+ * --------------------------------------------------------------------------
+ */
+
+ v3f head_wp0, head_wp1, start_co;
+ m4x3_mulv( localplayer.rb.to_world, state->head_position, head_wp0 );
+ v3_copy( localplayer.rb.co, start_co );
+
+ /* calculate transform one step into future */
+ v3f future_co;
+ v4f future_q;
+ v3_muladds( localplayer.rb.co, localplayer.rb.v, player_skate.substep,
+ future_co );
+
+ if( v3_length2( localplayer.rb.w ) > 0.0f ){
+ v4f rotation;
+ v3f axis;
+ v3_copy( localplayer.rb.w, axis );
+
+ float mag = v3_length( axis );
+ v3_divs( axis, mag, axis );
+ q_axis_angle( rotation, axis, mag*player_skate.substep );
+ q_mul( rotation, localplayer.rb.q, future_q );
+ q_normalize( future_q );
+ }
+ else
+ v4_copy( localplayer.rb.q, future_q );
+
+ v3f future_cg, current_cg, cg_offset;
+ q_mulv( localplayer.rb.q, player_skate.weight_distribution, current_cg );
+ q_mulv( future_q, player_skate.weight_distribution, future_cg );
+ v3_sub( future_cg, current_cg, cg_offset );
+
+ /* calculate the minimum time we can move */
+ float max_time = player_skate.substep;
+
+ for( int i=0; i<k_wheel_count; i++ ){
+ if( wheels[i].state == k_collider_state_disabled )
+ continue;
+
+ v3f current, future, r_cg;
+
+ q_mulv( future_q, wheels[i].pos, future );
+ v3_add( future, future_co, future );
+ v3_add( cg_offset, future, future );
+
+ q_mulv( localplayer.rb.q, wheels[i].pos, current );
+ v3_add( current, localplayer.rb.co, current );
+
+ float t;
+ v3f n;
+
+ float cast_radius = wheels[i].radius - k_penetration_slop * 2.0f;
+ if( spherecast_world( world, current, future, cast_radius, &t, n,
+ k_material_flag_walking ) != -1)
+ max_time = vg_minf( max_time, t * player_skate.substep );
+ }
+
+ /* clamp to a fraction of delta, to prevent locking */
+ float rate_lock = substep_count;
+ rate_lock *= vg.time_fixed_delta * 0.1f;
+ rate_lock *= rate_lock;
+
+ max_time = vg_maxf( max_time, rate_lock );
+ player_skate.substep_delta = max_time;
+
+ /* integrate */
+ v3_muladds( localplayer.rb.co, localplayer.rb.v,
+ player_skate.substep_delta, localplayer.rb.co );
+ if( v3_length2( localplayer.rb.w ) > 0.0f ){
+ v4f rotation;
+ v3f axis;
+ v3_copy( localplayer.rb.w, axis );
+
+ float mag = v3_length( axis );
+ v3_divs( axis, mag, axis );
+ q_axis_angle( rotation, axis, mag*player_skate.substep_delta );
+ q_mul( rotation, localplayer.rb.q, localplayer.rb.q );
+ q_normalize( localplayer.rb.q );
+
+ q_mulv( localplayer.rb.q, player_skate.weight_distribution, future_cg );
+ v3_sub( current_cg, future_cg, cg_offset );
+ v3_add( localplayer.rb.co, cg_offset, localplayer.rb.co );
+ }
+
+ rb_update_matrices( &localplayer.rb );
+ localplayer.rb.v[1] += -state->gravity_bias * player_skate.substep_delta;
+
+ player_skate.substep -= player_skate.substep_delta;
+
+ rb_ct manifold[128];
+ int manifold_len = 0;
+ /*
+ * Phase -1: head detection
+ * --------------------------------------------------------------------------
+ */
+ m4x3_mulv( localplayer.rb.to_world, state->head_position, head_wp1 );
+
+ float t;
+ v3f n;
+ if( (v3_dist2( head_wp0, head_wp1 ) > 0.001f) &&
+ (spherecast_world( world, head_wp0, head_wp1, 0.2f, &t, n,
+ k_material_flag_walking ) != -1) )
+ {
+ v3_lerp( start_co, localplayer.rb.co, t, localplayer.rb.co );
+ rb_update_matrices( &localplayer.rb );
+
+ vg_info( "player fell of due to hitting head\n" );
+ player__dead_transition( k_player_die_type_head );
+ return;
+ }
+
+ /*
+ * Phase 1: Regular collision detection
+ * --------------------------------------------------------------------------
+ */
+
+ for( int i=0; i<k_wheel_count; i++ ){
+ if( wheels[i].state == k_collider_state_disabled )
+ continue;
+
+ m4x3f mtx;
+ m3x3_identity( mtx );
+ m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, mtx[3] );
+
+ rb_ct *man = &manifold[ manifold_len ];
+
+ int l = skate_collide_smooth( mtx, wheels[i].radius, man );
+ if( l )
+ wheels[i].state = k_collider_state_colliding;
+
+ manifold_len += l;
+ }
+
+ float grind_radius = k_board_radius * 0.75f;
+ rb_capsule capsule = { .h = (k_board_length+0.2f)*2.0f,
+ .r = grind_radius };
+ m4x3f mtx;
+ v3_muls( localplayer.rb.to_world[0], 1.0f, mtx[0] );
+ v3_muls( localplayer.rb.to_world[2], -1.0f, mtx[1] );
+ v3_muls( localplayer.rb.to_world[1], 1.0f, mtx[2] );
+ v3_muladds( localplayer.rb.to_world[3], localplayer.rb.to_world[1],
+ grind_radius + k_board_radius*0.25f+state->slap, mtx[3] );
+
+ rb_ct *cman = &manifold[manifold_len];
+
+ int l = rb_capsule__scene( mtx, &capsule, NULL, world->geo_bh,
+ cman, k_material_flag_walking );
+
+ /* weld joints */
+ for( int i=0; i<l; i ++ )
+ cman[l].type = k_contact_type_edge;
+ rb_manifold_filter_joint_edges( cman, l, 0.03f );
+ l = rb_manifold_apply_filtered( cman, l );
+
+ manifold_len += l;
+ vg_line_capsule( mtx, capsule.r, capsule.h, VG__WHITE );
+
+ /* add limits */
+ if( state->activity >= k_skate_activity_grind_any ){
+ for( int i=0; i<player_skate.limit_count; i++ ){
+ struct grind_limit *limit = &player_skate.limits[i];
+ rb_ct *ct = &manifold[ manifold_len ++ ];
+ m4x3_mulv( localplayer.rb.to_world, limit->ra, ct->co );
+ m3x3_mulv( localplayer.rb.to_world, limit->n, ct->n );
+ ct->p = limit->p;
+ ct->type = k_contact_type_default;
+ }
+ }
+
+ /*
+ * Phase 3: Dynamics
+ * --------------------------------------------------------------------------
+ */
+
+
+ v3f world_cog;
+ m4x3_mulv( localplayer.rb.to_world,
+ player_skate.weight_distribution, world_cog );
+ vg_line_point( world_cog, 0.02f, VG__BLACK );
+
+ for( int i=0; i<manifold_len; i ++ ){
+ rb_prepare_contact( &manifold[i], player_skate.substep_delta );
+ rb_debug_contact( &manifold[i] );
+ }
+
+ /* yes, we are currently rebuilding mass matrices every frame. too bad! */
+ v3f extent = { k_board_width*10.0f, 0.1f, k_board_length };
+ float ex2 = k_board_interia*extent[0]*extent[0],
+ ey2 = k_board_interia*extent[1]*extent[1],
+ ez2 = k_board_interia*extent[2]*extent[2];
+
+ float mass = 2.0f * (extent[0]*extent[1]*extent[2]);
+ float inv_mass = 1.0f/mass;
+
+ v3f I;
+ I[0] = ((1.0f/12.0f) * mass * (ey2+ez2));
+ I[1] = ((1.0f/12.0f) * mass * (ex2+ez2));
+ I[2] = ((1.0f/12.0f) * mass * (ex2+ey2));
+
+ m3x3f iI;
+ m3x3_identity( iI );
+ iI[0][0] = I[0];
+ iI[1][1] = I[1];
+ iI[2][2] = I[2];
+ m3x3_inv( iI, iI );
+
+ m3x3f iIw;
+ m3x3_mul( iI, localplayer.rb.to_local, iIw );
+ m3x3_mul( localplayer.rb.to_world, iIw, iIw );
+
+ for( int j=0; j<10; j++ ){
+ for( int i=0; i<manifold_len; i++ ){
+ /*
+ * regular dance; calculate velocity & total mass, apply impulse.
+ */
+
+ rb_ct *ct = &manifold[i];
+
+ v3f rv, delta;
+ v3_sub( ct->co, world_cog, delta );
+ v3_cross( localplayer.rb.w, delta, rv );
+ v3_add( localplayer.rb.v, rv, rv );
+
+ v3f raCn;
+ v3_cross( delta, ct->n, raCn );
+
+ v3f raCnI, rbCnI;
+ m3x3_mulv( iIw, raCn, raCnI );
+
+ float normal_mass = 1.0f / (inv_mass + v3_dot(raCn,raCnI)),
+ vn = v3_dot( rv, ct->n ),
+ lambda = normal_mass * ( -vn );
+
+ float temp = ct->norm_impulse;
+ ct->norm_impulse = vg_maxf( temp + lambda, 0.0f );
+ lambda = ct->norm_impulse - temp;
+
+ v3f impulse;
+ v3_muls( ct->n, lambda, impulse );
+
+ v3_muladds( normal_total, impulse, inv_mass, normal_total );
+ v3_muladds( localplayer.rb.v, impulse, inv_mass, localplayer.rb.v );
+ v3_cross( delta, impulse, impulse );
+ m3x3_mulv( iIw, impulse, impulse );
+ v3_add( impulse, localplayer.rb.w, localplayer.rb.w );
+
+ v3_cross( localplayer.rb.w, delta, rv );
+ v3_add( localplayer.rb.v, rv, rv );
+ vn = v3_dot( rv, ct->n );
+ }
+ }
+
+ v3f dt;
+ rb_depenetrate( manifold, manifold_len, dt );
+ v3_add( dt, localplayer.rb.co, localplayer.rb.co );
+ rb_update_matrices( &localplayer.rb );
+
+ substep_count ++;
+
+ if( player_skate.substep >= 0.0001f )
+ goto begin_collision; /* again! */
+
+ /*
+ * End of collision and dynamics routine
+ * --------------------------------------------------------------------------
+ */
+
+ f32 nforce = v3_length(normal_total);
+ if( nforce > 4.0f ){
+ if( nforce > 17.6f ){
+ vg_info( "player fell off due to hitting ground too hard\n" );
+ v3_muladds( localplayer.rb.v, normal_total, -1.0f, localplayer.rb.v );
+ player__dead_transition( k_player_die_type_feet );
+ return;
+ }
+
+ f32 amt = k_cam_punch;
+ if( localplayer.cam_control.camera_mode == k_cam_firstperson ){
+ amt *= 0.25f;
+ }
+
+ v3_muladds( localplayer.cam_land_punch_v, normal_total, amt,
+ localplayer.cam_land_punch_v );
+ }
+
+ player_skate.surface = k_surface_prop_concrete;
+
+ for( int i=0; i<manifold_len; i++ ){
+ rb_ct *ct = &manifold[i];
+ struct world_surface *surf = world_contact_surface( world, ct );
+
+ if( surf->info.surface_prop > player_skate.surface )
+ player_skate.surface = surf->info.surface_prop;
+ }
+
+ for( int i=0; i<k_wheel_count; i++ ){
+ m4x3f mtx;
+ m3x3_copy( localplayer.rb.to_world, mtx );
+ m4x3_mulv( localplayer.rb.to_world, wheels[i].pos, mtx[3] );
+ vg_line_sphere( mtx, wheels[i].radius,
+ (u32[]){ VG__WHITE, VG__BLACK,
+ wheels[i].colour }[ wheels[i].state ]);
+ }
+
+ skate_integrate();
+ vg_line_point( state->cog, 0.02f, VG__WHITE );
+
+ u32 id = world_intersect_gates( world, localplayer.rb.co, state->prev_pos );
+
+ if( id ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, mdl_entity_id_id(id) );
+
+ m4x3_mulv( gate->transport, localplayer.rb.co, localplayer.rb.co );
+ m3x3_mulv( gate->transport, localplayer.rb.v, localplayer.rb.v );
+ m4x3_mulv( gate->transport, state->cog, state->cog );
+ m3x3_mulv( gate->transport, state->cog_v, state->cog_v );
+ m3x3_mulv( gate->transport, state->throw_v, state->throw_v );
+ m3x3_mulv( gate->transport, state->head_position,
+ state->head_position );
+ m3x3_mulv( gate->transport, state->up_dir, state->up_dir );
+
+ v4f transport_rotation;
+ m3x3_q( gate->transport, transport_rotation );
+ q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
+ q_mul( transport_rotation, state->smoothed_rotation,
+ state->smoothed_rotation );
+ q_normalize( localplayer.rb.q );
+ q_normalize( state->smoothed_rotation );
+ rb_update_matrices( &localplayer.rb );
+ player__pass_gate( id );
+ }
+
+ /* FIXME: Rate limit */
+ static int stick_frames = 0;
+
+ if( state->activity >= k_skate_activity_ground )
+ stick_frames ++;
+ else
+ stick_frames = 0;
+
+ if( stick_frames > 5 ) stick_frames = 5;
+
+ if( stick_frames == 4 ){
+ if( state->activity == k_skate_activity_ground ){
+ if( (fabsf(state->slip) > 0.75f) ){
+ player__networked_sfx( k_player_subsystem_skate, 128,
+ k_player_skate_soundeffect_land_bad,
+ localplayer.rb.co, 0.6f );
+ }
+ else{
+ player__networked_sfx( k_player_subsystem_skate, 128,
+ k_player_skate_soundeffect_land_good,
+ localplayer.rb.co, 1.0f );
+ }
+ }
+ else if( player_skate.surface == k_surface_prop_metal ){
+ player__networked_sfx( k_player_subsystem_skate, 128,
+ k_player_skate_soundeffect_grind_metal,
+ localplayer.rb.co, 1.0f );
+ }
+ else{
+ player__networked_sfx( k_player_subsystem_skate, 128,
+ k_player_skate_soundeffect_grind_wood,
+ localplayer.rb.co, 1.0f );
+ }
+ } else if( stick_frames == 0 ){
+ /* TODO: EXIT SOUNDS */
+ }
+
+ if( (state->activity_prev < k_skate_activity_grind_any) &&
+ (state->activity >= k_skate_activity_grind_any) ){
+ state->velocity_limit = v3_length( localplayer.rb.v )*1.0f;
+ state->grind_y_start = localplayer.rb.co[1];
+ }
+
+ if( state->activity >= k_skate_activity_grind_any ){
+ f32 dy = localplayer.rb.co[1] - state->grind_y_start;
+ if( dy < 0.0f ){
+ state->velocity_limit += -dy*0.2f;
+ }
+ state->grind_y_start = localplayer.rb.co[1];
+
+
+ f32 speed_end = v3_length( localplayer.rb.v );
+ if( speed_end > state->velocity_limit ){
+ v3_muls( localplayer.rb.v, state->velocity_limit/speed_end,
+ localplayer.rb.v );
+ }
+ }
+}
+
+void player__skate_im_gui( ui_context *ctx )
+{
+ struct player_skate_state *state = &player_skate.state;
+ player__debugtext( ctx, 1, "V: %5.2f %5.2f %5.2f",localplayer.rb.v[0],
+ localplayer.rb.v[1],
+ localplayer.rb.v[2] );
+ player__debugtext( ctx, 1, "CO: %5.2f %5.2f %5.2f",localplayer.rb.co[0],
+ localplayer.rb.co[1],
+ localplayer.rb.co[2] );
+ player__debugtext( ctx, 1, "W: %5.2f %5.2f %5.2f",localplayer.rb.w[0],
+ localplayer.rb.w[1],
+ localplayer.rb.w[2] );
+
+ const char *activity_txt[] = {
+ "air",
+ "air_to_grind",
+ "ground",
+ "handplant",
+ "undefined (INVALID)",
+ "grind_any (INVALID)",
+ "grind_boardslide",
+ "grind_metallic (INVALID)",
+ "grind_back50",
+ "grind_front50",
+ "grind_5050"
+ };
+
+ player__debugtext( ctx, 1, "activity: %s", activity_txt[state->activity] );
+ player__debugtext( ctx, 1, "flip: %.4f %.4f", state->flip_rate,
+ state->flip_time );
+ player__debugtext( ctx, 1, "trickv: %.2f %.2f %.2f",
+ state->trick_vel[0],
+ state->trick_vel[1],
+ state->trick_vel[2] );
+ player__debugtext( ctx, 1, "tricke: %.2fs %.2f %.2f %.2f",
+ state->trick_time,
+ state->trick_euler[0],
+ state->trick_euler[1],
+ state->trick_euler[2] );
+}
+
+void player__skate_animate(void){
+ struct player_skate_state *state = &player_skate.state;
+ struct player_skate_animator *animator = &player_skate.animator;
+
+ /* Head */
+ float kheight = 2.0f,
+ kleg = 0.6f;
+
+ v3_zero( animator->offset );
+
+ v3f cog_local, cog_ideal;
+ m4x3_mulv( localplayer.rb.to_local, state->cog, cog_local );
+
+ v3_copy( state->up_dir, cog_ideal );
+ v3_normalize( cog_ideal );
+ m3x3_mulv( localplayer.rb.to_local, cog_ideal, cog_ideal );
+
+ v3_sub( cog_ideal, cog_local, animator->offset );
+
+ v3_muls( animator->offset, 4.0f, animator->offset );
+ animator->offset[1] *= -1.0f;
+
+ float curspeed = v3_length( localplayer.rb.v ),
+ kickspeed = vg_clampf( curspeed*(1.0f/40.0f), 0.0f, 1.0f ),
+ kicks = (vg_randf64(&vg.rand)-0.5f)*2.0f*kickspeed,
+ sign = vg_signf( kicks );
+
+ animator->wobble[0] = vg_lerpf( animator->wobble[0], kicks*kicks*sign,
+ 6.0f*vg.time_delta);
+ animator->wobble[1] = vg_lerpf( animator->wobble[1], animator->wobble[0],
+ 2.4f*vg.time_delta);
+
+ animator->offset[0] *= 0.26f;
+ animator->offset[0] += animator->wobble[1]*3.0f;
+
+ animator->offset[1] *= -0.3f;
+ animator->offset[2] *= 0.01f;
+
+ animator->offset[0]=vg_clampf(animator->offset[0],-0.8f,0.8f)*
+ (1.0f-fabsf(animator->slide)*0.9f);
+ animator->offset[1]=vg_clampf(animator->offset[1],-0.5f,0.0f);
+
+ v3f cam_offset;
+ v3_mul( animator->offset, (v3f){1.0f,0.3f,1.0f}, cam_offset );
+
+ /* localized vectors */
+ m4x3_mulv( localplayer.rb.to_local, state->cog, animator->local_cog );
+
+ /*
+ * Animation blending
+ * ===========================================
+ */
+
+ /* sliding */
+ {
+ float desired = 0.0f;
+ if( state->activity == k_skate_activity_ground )
+ desired = vg_clampf( vg_maxf(fabsf( state->slip ),
+ fabsf( state->skid ) ), 0.0f, 1.0f );
+
+ animator->slide = vg_lerpf( animator->slide, desired, 2.4f*vg.time_delta);
+
+ f32 dirx = 0.0f;
+ if( fabsf(state->slip) > fabsf(dirx) ) dirx = state->slip;
+ if( fabsf(state->skid) > fabsf(dirx) ) dirx = state->skid;
+ if( fabsf( dirx ) > 0.025f ) dirx = vg_signf( dirx );
+ dirx = vg_signf( state->slip );
+ vg_slewf( &animator->x, dirx, 2.6f*vg.time_delta );
+ }
+
+ cam_offset[0] += animator->slide * -animator->x;
+ v3_copy( cam_offset, localplayer.cam_control.tpv_offset_extra );
+
+ /* movement information */
+ int iair = state->activity <= k_skate_activity_air_to_grind;
+
+ float dirz = state->reverse > 0.0f? 0.0f: 1.0f,
+ fly = iair? 1.0f: 0.0f,
+ wdist= player_skate.weight_distribution[2] / k_board_length;
+
+ if( state->activity >= k_skate_activity_grind_any )
+ wdist = 0.0f;
+
+ animator->z = vg_lerpf( animator->z, dirz, 2.4f*vg.time_delta );
+ animator->skid = state->skid;
+ animator->fly = vg_lerpf( animator->fly, fly, 3.4f*vg.time_delta );
+ animator->weight = vg_lerpf( animator->weight, wdist, 9.0f*vg.time_delta );
+
+ float stand = 1.0f - vg_clampf( curspeed * 0.03f, 0.0f, 1.0f );
+ animator->stand = vg_lerpf( animator->stand, stand, 6.0f*vg.time_delta );
+ animator->reverse = state->reverse;
+
+ if( fabsf(state->slip) > 0.3f ){
+ f32 slide_x = v3_dot(localplayer.rb.v, localplayer.rb.to_world[0]);
+ state->delayed_slip_dir = vg_signf(slide_x);
+ }
+
+ /* grinding */
+ f32 grind=state->activity >= k_skate_activity_grind_any? 1.0f: 0.0f;
+ animator->grind = vg_lerpf( animator->grind, grind, 5.0f*vg.time_delta );
+
+ f32 grind_frame = 0.5f;
+
+ if( state->activity == k_skate_activity_grind_front50 )
+ grind_frame = 0.0f;
+ else if( state->activity == k_skate_activity_grind_back50 )
+ grind_frame = 1.0f;
+
+ animator->grind_balance = vg_lerpf( animator->grind_balance, grind_frame,
+ 5.0f*vg.time_delta );
+ animator->activity = state->activity;
+ animator->surface = player_skate.surface;
+
+ /* pushing */
+ animator->push_time = vg.time - state->start_push;
+ animator->push = vg_lerpf( animator->push,
+ (vg.time - state->cur_push) < 0.125,
+ 6.0f*vg.time_delta );
+
+ /* jumping */
+ animator->jump_charge = state->jump_charge;
+ animator->jump = vg_lerpf( animator->jump, animator->jump_charge,
+ 8.4f*vg.time_delta );
+
+ /* trick setup */
+ animator->jump_dir = state->jump_dir;
+ f32 jump_start_frame = 14.0f/30.0f;
+ animator->jump_time = animator->jump_charge * jump_start_frame;
+ f32 jump_frame = (vg.time - state->jump_time) + jump_start_frame;
+ if( jump_frame >= jump_start_frame && jump_frame <= (40.0f/30.0f) )
+ animator->jump_time = jump_frame;
+
+ /* trick */
+ float jump_t = vg.time-state->jump_time;
+ float k=17.0f;
+ float h = k*jump_t;
+ float extra = h*exp(1.0-h) * (state->jump_dir?1.0f:-1.0f);
+ extra *= state->slap * 4.0f;
+
+ v3_add( state->trick_euler, state->trick_residuald,
+ animator->board_euler );
+ v3_muls( animator->board_euler, VG_TAUf, animator->board_euler );
+
+ animator->board_euler[0] *= 0.5f;
+ animator->board_euler[1] += extra;
+ animator->trick_type = state->trick_type;
+
+ /* board lean */
+ f32 lean1, lean2 = animator->steer[0] * animator->reverse * -0.36f,
+ lean;
+
+ lean1 = animator->slide * animator->delayed_slip_dir;
+ if( fabsf(lean1)>fabsf(lean2) ) lean = lean1;
+ else lean = lean2;
+
+ if( ((int)roundf(animator->board_euler[0]/VG_PIf)) % 2 ) lean = -lean;
+ lean = vg_clampf( lean, -1.0f, 1.0f );
+ animator->board_lean =
+ vg_lerpf(animator->board_lean, lean, vg.time_delta*18.0f);
+
+ /* feet placement */
+ struct player_board *board =
+ addon_cache_item_if_loaded( k_addon_type_board,
+ localplayer.board_view_slot );
+ if( board ){
+ if( animator->weight > 0.0f ){
+ animator->foot_offset[0] =
+ board->truck_positions[k_board_truck_back][2]+0.3f;
+ }
+ else{
+ animator->foot_offset[1] =
+ board->truck_positions[k_board_truck_front][2]-0.3f;
+ }
+ }
+
+ f32 slapm = vg_maxf( 1.0f-v3_length2( state->trick_vel ), 0.0f );
+ animator->slap = state->slap;
+ animator->subslap = vg_lerpf( animator->subslap, slapm,
+ vg.time_delta*10.0f );
+
+#if 0
+ f32 l = ((state->activity < k_skate_activity_ground) &&
+ v3_length2(state->trick_vel) > 0.1f )? 1: 0;
+ animator->trick_foot = vg_lerpf( animator->trick_foot, l,
+ 8.4f*vg.time_delta );
+#endif
+
+ animator->trick_foot = vg_exp_impulse( state->trick_time, 5.0f );
+
+ /* grab */
+ v2f grab_input;
+ joystick_state( k_srjoystick_grab, grab_input );
+ v2_add( state->grab_mouse_delta, grab_input, grab_input );
+
+ if( v2_length2( grab_input ) <= 0.001f ) grab_input[0] = -1.0f;
+ else v2_normalize_clamp( grab_input );
+ v2_lerp( animator->grab, grab_input, 2.4f*vg.time_delta, animator->grab );
+ animator->grabbing = state->grabbing;
+
+ /* steer */
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+ animator->airdir = vg_lerpf( animator->airdir,
+ -steer[0], 2.4f*vg.time_delta );
+
+ animator->steer[0] = steer[0];
+ animator->steer[1] = vg_lerpf( animator->steer[1],
+ steer[0], 4.0f*vg.time_delta );
+
+
+ /* flip angle */
+ if( (state->activity <= k_skate_activity_air_to_grind) &&
+ (fabsf(state->flip_rate) > 0.01f) ){
+ float substep = vg.time_fixed_extrapolate;
+ float t = state->flip_time+state->flip_rate*substep*vg.time_fixed_delta;
+ sign = vg_signf( t );
+
+ t = 1.0f - vg_minf( 1.0f, fabsf( t * 1.1f ) );
+ t = sign * (1.0f-t*t);
+
+ f32 angle = vg_clampf( t, -1.0f, 1.0f ) * VG_TAUf,
+ distm = state->land_dist * fabsf(state->flip_rate) * 3.0f,
+ blend = vg_clampf( 1.0f-distm, 0.0f, 1.0f );
+ angle = vg_lerpf( angle, vg_signf(state->flip_rate)*VG_TAUf, blend );
+ q_axis_angle( animator->qflip, state->flip_axis, angle );
+ }
+ else
+ q_identity( animator->qflip );
+
+ /* counter-rotation */
+ if( v3_length2( state->up_dir ) > 0.001f ){
+ v4_lerp( state->smoothed_rotation, localplayer.rb.q,
+ 2.0f*vg.time_frame_delta,
+ state->smoothed_rotation );
+ q_normalize( state->smoothed_rotation );
+
+ v3f yaw_smooth = {1.0f,0.0f,0.0f};
+ q_mulv( state->smoothed_rotation, yaw_smooth, yaw_smooth );
+ m3x3_mulv( localplayer.rb.to_local, yaw_smooth, yaw_smooth );
+ yaw_smooth[1] = 0.0f;
+ v3_normalize( yaw_smooth );
+
+ f32 yaw_counter_rotate = yaw_smooth[0];
+ yaw_counter_rotate = vg_maxf( 0.7f, yaw_counter_rotate );
+ yaw_counter_rotate = acosf( yaw_counter_rotate );
+ yaw_counter_rotate *= 1.0f-animator->fly;
+
+ v3f ndir;
+ m3x3_mulv( localplayer.rb.to_local, state->up_dir, ndir );
+ v3_normalize( ndir );
+
+ v3f up = { 0.0f, 1.0f, 0.0f };
+ float a = v3_dot( ndir, up );
+ a = acosf( vg_clampf( a, -1.0f, 1.0f ) );
+
+ v3f axis;
+ v4f qcounteryaw, qfixup;
+
+ v3_cross( up, ndir, axis );
+ q_axis_angle( qfixup, axis, a*2.0f );
+
+ v3_cross( (v3f){1.0f,0.0f,0.0f}, yaw_smooth, axis );
+ q_axis_angle( qcounteryaw, axis, yaw_counter_rotate );
+
+ q_mul( qcounteryaw, qfixup, animator->qfixuptotal );
+ q_normalize( animator->qfixuptotal );
+
+ v3f p1, p2;
+ m3x3_mulv( localplayer.rb.to_world, up, p1 );
+ m3x3_mulv( localplayer.rb.to_world, ndir, p2 );
+
+ vg_line_arrow( localplayer.rb.co, p1, 0.5f, VG__PINK );
+ vg_line_arrow( localplayer.rb.co, p2, 0.5f, VG__PINK );
+ }
+ else q_identity( animator->qfixuptotal );
+
+ if( state->activity == k_skate_activity_handplant ){
+ v3_copy( state->store_co, animator->root_co );
+ v4_copy( state->store_q, animator->root_q );
+ v3_zero( animator->root_v );
+ }
+ else {
+ rb_extrapolate( &localplayer.rb, animator->root_co, animator->root_q );
+ v3_copy( localplayer.rb.v, animator->root_v );
+ }
+
+ animator->handplant_t = state->handplant_t;
+}
+
+void player__skate_pose( void *_animator, player_pose *pose ){
+ struct skeleton *sk = &localplayer.skeleton;
+ struct player_skate_animator *animator = _animator;
+
+ pose->type = k_player_pose_type_ik;
+ v3_copy( animator->root_co, pose->root_co );
+ v4_copy( animator->root_q, pose->root_q );
+
+ /* transform */
+ v3f ext_up,ext_co;
+ q_mulv( pose->root_q, (v3f){0.0f,1.0f,0.0f}, ext_up );
+ v3_copy( pose->root_co, ext_co );
+ v3_muladds( pose->root_co, ext_up, -0.1f, pose->root_co );
+
+ /* apply flip rotation at midpoint */
+ q_mul( animator->qflip, pose->root_q, pose->root_q );
+ q_normalize( pose->root_q );
+
+ v3f rotation_point, rco;
+ v3_muladds( ext_co, ext_up, 0.5f, rotation_point );
+ v3_sub( pose->root_co, rotation_point, rco );
+
+ q_mulv( animator->qflip, rco, rco );
+ v3_add( rco, rotation_point, pose->root_co );
+
+ /* ANIMATIONS
+ * ---------------------------------------------------------------------- */
+
+ mdl_keyframe apose[32], bpose[32];
+ mdl_keyframe ground_pose[32];
+ {
+ /* stand/crouch */
+ f32 dir_frame = animator->z * (15.0f/30.0f),
+ stand_blend = animator->offset[1]*-2.0f;
+
+ pose->board.lean = animator->board_lean;
+
+ stand_blend = vg_clampf( 1.0f-animator->local_cog[1], 0, 1 );
+
+ skeleton_sample_anim( sk, player_skate.anim_stand, dir_frame, apose );
+ skeleton_sample_anim( sk, player_skate.anim_highg, dir_frame, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, stand_blend, apose );
+
+ /* sliding */
+ f32 slide_frame = animator->x * 0.25f + 0.25f;
+ skeleton_sample_anim( sk, player_skate.anim_slide, slide_frame, bpose );
+
+ mdl_keyframe mirrored[32];
+ player_mirror_pose( bpose, mirrored );
+ skeleton_lerp_pose( sk, bpose, mirrored, animator->z, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, animator->slide, apose );
+
+ if( animator->reverse > 0.0f ){
+ skeleton_sample_anim( sk, player_skate.anim_push, animator->push_time,
+ bpose );
+ }
+ else{
+ skeleton_sample_anim( sk, player_skate.anim_push_reverse,
+ animator->push_time, bpose );
+ }
+ skeleton_lerp_pose( sk, apose, bpose, animator->push, apose );
+
+ struct skeleton_anim *jump_anim = animator->jump_dir?
+ player_skate.anim_ollie:
+ player_skate.anim_ollie_reverse;
+
+ f32 setup_blend = vg_minf( animator->jump, 1.0f );
+ skeleton_sample_anim_clamped( sk, jump_anim, animator->jump_time, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, setup_blend, ground_pose );
+ }
+
+ mdl_keyframe air_pose[32];
+ {
+ float air_frame = (animator->airdir*0.5f+0.5f) * (15.0f/30.0f);
+ skeleton_sample_anim( sk, player_skate.anim_air, air_frame, apose );
+
+ float ang = atan2f( animator->grab[0], animator->grab[1] ),
+ ang_unit = (ang+VG_PIf) * (1.0f/VG_TAUf),
+ grab_frame = ang_unit * (15.0f/30.0f);
+
+ skeleton_sample_anim( sk, player_skate.anim_grabs, grab_frame, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, animator->grabbing, air_pose );
+ }
+
+ skeleton_lerp_pose( sk, ground_pose, air_pose, animator->fly,
+ pose->keyframes );
+
+ mdl_keyframe *kf_board = &pose->keyframes[localplayer.id_board-1],
+ *kf_foot_l = &pose->keyframes[localplayer.id_ik_foot_l-1],
+ *kf_foot_r = &pose->keyframes[localplayer.id_ik_foot_r-1],
+ *kf_knee_l = &pose->keyframes[localplayer.id_ik_knee_l-1],
+ *kf_knee_r = &pose->keyframes[localplayer.id_ik_knee_r-1],
+ *kf_hip = &pose->keyframes[localplayer.id_hip-1],
+ *kf_wheels[] = { &pose->keyframes[localplayer.id_wheel_r-1],
+ &pose->keyframes[localplayer.id_wheel_l-1] };
+
+
+ mdl_keyframe grind_pose[32];
+ {
+ f32 frame = animator->grind_balance * 0.5f;
+
+ skeleton_sample_anim( sk, player_skate.anim_grind, frame, apose );
+ skeleton_sample_anim( sk, player_skate.anim_grind_jump, frame, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, animator->jump, grind_pose );
+ }
+ skeleton_lerp_pose( sk, pose->keyframes, grind_pose,
+ animator->grind, pose->keyframes );
+ float add_grab_mod = 1.0f - animator->fly;
+
+ /* additive effects */
+ u32 apply_to[] = { localplayer.id_hip,
+ localplayer.id_ik_hand_l,
+ localplayer.id_ik_hand_r,
+ localplayer.id_ik_elbow_l,
+ localplayer.id_ik_elbow_r };
+
+ float apply_rates[] = { 1.0f,
+ 0.75f,
+ 0.75f,
+ 0.75f,
+ 0.75f };
+
+ for( int i=0; i<VG_ARRAY_LEN(apply_to); i ++ ){
+ pose->keyframes[apply_to[i]-1].co[0] += animator->offset[0]*add_grab_mod;
+ pose->keyframes[apply_to[i]-1].co[2] += animator->offset[2]*add_grab_mod;
+ }
+
+#if 1
+ /* angle 'correction' */
+ v3f origin;
+ v3_add( sk->bones[localplayer.id_hip].co, kf_hip->co, origin );
+
+ for( int i=0; i<VG_ARRAY_LEN(apply_to); i ++ ){
+ mdl_keyframe *kf = &pose->keyframes[apply_to[i]-1];
+ keyframe_rotate_around( kf, origin, sk->bones[apply_to[i]].co,
+ animator->qfixuptotal );
+ }
+#endif
+
+
+ if( animator->activity == k_skate_activity_handplant ){
+ struct skeleton_anim *anim = player_skate.anim_handplant;
+
+ mdl_keyframe hpose[32];
+ skeleton_sample_anim_clamped( sk, anim, animator->handplant_t, hpose );
+ if( animator->reverse < 0.0f )
+ player_mirror_pose( hpose, hpose );
+
+ mdl_keyframe *kf_world = &hpose[ localplayer.id_world -1 ];
+ m4x3f world, mmdl, world_view;
+ q_m3x3( kf_world->q, world );
+ v3_copy( kf_world->co, world[3] );
+
+ q_m3x3( pose->root_q, mmdl );
+ v3_copy( pose->root_co, mmdl[3] );
+
+ m4x3_mul( mmdl, world, world_view );
+
+ vg_line_arrow( world_view[3], world_view[0], 1.0f, 0xff0000ff );
+ vg_line_arrow( world_view[3], world_view[1], 1.0f, 0xff00ff00 );
+ vg_line_arrow( world_view[3], world_view[2], 1.0f, 0xffff0000 );
+
+ m4x3f invworld;
+ m4x3_invert_affine( world, invworld );
+ m4x3_mul( mmdl, invworld, world_view );
+
+ m3x3_q( world_view, pose->root_q );
+ v3_copy( world_view[3], pose->root_co );
+
+ f32 t = animator->handplant_t,
+ frames = anim->length-1,
+ length = animator->activity == k_skate_activity_handplant?
+ frames / anim->rate:
+ 999999,
+ end_dist = vg_minf( t, length - t )/k_anim_transition,
+ blend = vg_smoothstepf( vg_minf(1,end_dist) );
+
+ skeleton_lerp_pose( sk, pose->keyframes, hpose, blend, pose->keyframes );
+ }
+
+
+ /* trick rotation */
+ v4f qtrick, qyaw, qpitch, qroll;
+ q_axis_angle( qyaw, (v3f){0.0f,1.0f,0.0f}, animator->board_euler[0] );
+ q_axis_angle( qpitch, (v3f){1.0f,0.0f,0.0f}, animator->board_euler[1] );
+ q_axis_angle( qroll, (v3f){0.0f,0.0f,1.0f}, animator->board_euler[2] );
+
+ q_mul( qyaw, qroll, qtrick );
+ q_mul( qpitch, qtrick, qtrick );
+ q_mul( kf_board->q, qtrick, kf_board->q );
+ q_normalize( kf_board->q );
+
+ kf_foot_l->co[2] = vg_lerpf( kf_foot_l->co[2], animator->foot_offset[0],
+ 0.5f * animator->weight );
+ kf_foot_r->co[2] = vg_lerpf( kf_foot_r->co[2], animator->foot_offset[1],
+ -0.5f * animator->weight );
+
+ kf_foot_l->co[1] += animator->slap;
+ kf_foot_r->co[1] += animator->slap;
+ kf_knee_l->co[1] += animator->slap;
+ kf_knee_r->co[1] += animator->slap;
+ kf_board->co[1] += animator->slap * animator->subslap;
+ kf_hip->co[1] += animator->slap * 0.25f;
+
+ /* kickflip and shuvit are in the wrong order for some reason */
+ if( animator->trick_type == k_trick_type_kickflip ){
+ kf_foot_l->co[0] += animator->trick_foot * 0.15f;
+ kf_foot_r->co[0] -= animator->trick_foot * 0.15f;
+ kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
+ kf_foot_r->co[1] -= animator->trick_foot * 0.18f;
+ }
+ else if( animator->trick_type == k_trick_type_shuvit ){
+ kf_foot_l->co[0] += animator->trick_foot * 0.2f;
+ kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
+ kf_foot_r->co[0] -= animator->trick_foot * 0.1f;
+ kf_foot_r->co[1] += animator->trick_foot * 0.09f;
+ }
+ else if( animator->trick_type == k_trick_type_treflip ){
+ kf_foot_l->co[0] += animator->trick_foot * 0.2f;
+ kf_foot_r->co[0] -= animator->trick_foot * 0.15f;
+ kf_foot_l->co[1] -= animator->trick_foot * 0.18f;
+ kf_foot_r->co[1] -= animator->trick_foot * 0.18f;
+ }
+
+ /*
+ * animation wishlist:
+ * boardslide/grind jump animations
+ * when tricking the slap should not appply or less apply
+ * not animations however DONT target grinds that are vertically down.
+ */
+
+ /* truck rotation */
+ for( int i=0; i<2; i++ ){
+ float a = vg_minf( player_skate.truckv0[i][0], 1.0f );
+ a = -acosf( a ) * vg_signf( player_skate.truckv0[i][1] );
+
+ v4f q;
+ q_axis_angle( q, (v3f){0.0f,0.0f,1.0f}, a );
+ q_mul( q, kf_wheels[i]->q, kf_wheels[i]->q );
+ q_normalize( kf_wheels[i]->q );
+ }
+
+#if 1
+ {
+ mdl_keyframe
+ *kf_head = &pose->keyframes[localplayer.id_head-1],
+ *kf_elbow_l = &pose->keyframes[localplayer.id_ik_elbow_l-1],
+ *kf_elbow_r = &pose->keyframes[localplayer.id_ik_elbow_r-1],
+ *kf_hand_l = &pose->keyframes[localplayer.id_ik_hand_l-1],
+ *kf_hand_r = &pose->keyframes[localplayer.id_ik_hand_r-1],
+ *kf_hip = &pose->keyframes[localplayer.id_hip-1];
+
+ float warble = vg_perlin_fract_1d( vg.time, 2.0f, 2, 300 );
+ warble *= vg_maxf(animator->grind, fabsf(animator->weight)) * 0.3f;
+
+ v4f qrot;
+ q_axis_angle( qrot, (v3f){0.8f,0.7f,0.6f}, warble );
+
+ v3f origin = {0.0f,0.2f,0.0f};
+ keyframe_rotate_around( kf_hand_l, origin,
+ sk->bones[localplayer.id_ik_hand_l].co, qrot );
+ keyframe_rotate_around( kf_hand_r, origin,
+ sk->bones[localplayer.id_ik_hand_r].co, qrot );
+ keyframe_rotate_around( kf_hip, origin,
+ sk->bones[localplayer.id_hip].co, qrot );
+ keyframe_rotate_around( kf_elbow_r, origin,
+ sk->bones[localplayer.id_ik_elbow_r].co, qrot );
+ keyframe_rotate_around( kf_elbow_l, origin,
+ sk->bones[localplayer.id_ik_elbow_l].co, qrot );
+
+ q_inv( qrot, qrot );
+ q_mul( qrot, kf_head->q, kf_head->q );
+ q_normalize( kf_head->q );
+
+
+ /* hand placement */
+
+ u32 hand_id = animator->z < 0.5f?
+ localplayer.id_ik_hand_l: localplayer.id_ik_hand_r;
+
+ v3f sample_co;
+ m4x3f mmdl;
+ q_m3x3( pose->root_q, mmdl );
+ q_mulv( pose->root_q, pose->keyframes[hand_id-1].co, mmdl[3] );
+ v3_add( mmdl[3], pose->root_co, mmdl[3] );
+ m4x3_mulv( mmdl, sk->bones[hand_id].co, sample_co );
+
+ v3_muladds( sample_co, mmdl[1], 0.3f, sample_co );
+ vg_line_point( sample_co, 0.04f, 0xff0000ff );
+
+ v3f dir;
+ v3_muls( mmdl[1], -1.0f, dir );
+ ray_hit hit = { .dist = 1.5f };
+ if(ray_world( world_current_instance(), sample_co, dir, &hit, 0 )){
+ vg_line_cross( hit.pos, 0xff0000ff, 0.05f );
+ vg_line( sample_co, hit.pos, 0xffffffff );
+
+ f32 amt = vg_maxf( 0.0f, animator->slide-0.5f ) *
+ 2.0f * fabsf(animator->z*2.0f-1.0f);
+
+ f32 d = (hit.dist - 0.3f) * amt;
+ pose->keyframes[hand_id-1].co[1] -= d;
+ kf_hip->co[1] -= d*0.4f;
+ }
+
+ /* skid */
+ f32 amt = vg_maxf(0.0f, (animator->slide - 0.5f) * 2.0f);
+ u8 skidders[] = { localplayer.id_ik_foot_l,
+ localplayer.id_ik_foot_r,
+ localplayer.id_board };
+ v4f qskid;
+ q_axis_angle( qskid, (v3f){0,1,0}, -animator->steer[1]*0.2f );
+
+ for( u32 i=0; i<VG_ARRAY_LEN(skidders); i ++ ){
+ mdl_keyframe *kf = &pose->keyframes[ skidders[i]-1 ];
+ keyframe_rotate_around( kf,
+ (v3f){0,0,0.4f*(animator->z*2.0f-1.0f)*amt},
+ sk->bones[skidders[i]].co, qskid );
+ }
+ }
+#endif
+}
+
+void player__skate_effects( void *_animator, m4x3f *final_mtx,
+ struct player_board *board,
+ struct player_effects_data *effect_data ){
+ struct skeleton *sk = &localplayer.skeleton;
+ struct player_skate_animator *animator = _animator;
+
+ v3f vp0, vp1, vpc;
+ if( board ){
+ v3_copy((v3f){0.0f,0.02f, board->truck_positions[0][2]}, vp1 );
+ v3_copy((v3f){0.0f,0.02f, board->truck_positions[1][2]}, vp0 );
+ }
+ else{
+ v3_zero( vp0 );
+ v3_zero( vp1 );
+ }
+
+ v3f *board_mtx = final_mtx[ localplayer.id_board ];
+ m4x3_mulv( board_mtx, vp0, vp0 );
+ m4x3_mulv( board_mtx, vp1, vp1 );
+ v3_add( vp0, vp1, vpc );
+ v3_muls( vpc, 0.5f, vpc );
+
+ if( animator->surface == k_surface_prop_sand ){
+ if( (animator->slide>0.4f) && (v3_length2(animator->root_v)>4.0f*4.0f) ){
+ v3f v, co;
+ v3_muls( animator->root_v, 0.5f, v );
+ v3_lerp( vp0, vp1, vg_randf64(&vg.rand), co );
+
+ effect_data->sand.colour = 0xff8ec4e6;
+ effect_spark_apply( &effect_data->sand, co, v, vg.time_delta * 8.0 );
+ }
+ }
+
+ if( animator->grind > 0.5f ){
+ int back = 0, front = 0, mid = 0;
+
+ if( animator->activity == k_skate_activity_grind_5050 ){
+ back = 1;
+ front = 1;
+ }
+ else if( animator->activity == k_skate_activity_grind_back50 ){
+ back = 1;
+ }
+ else if( animator->activity == k_skate_activity_grind_front50 ){
+ front = 1;
+ }
+ else if( animator->activity == k_skate_activity_grind_boardslide ){
+ mid = 1;
+ }
+
+ if( back ){
+ effect_spark_apply( &effect_data->spark, vp0,
+ animator->root_v, vg.time_delta );
+ }
+
+ if( front ){
+ effect_spark_apply( &effect_data->spark, vp1,
+ animator->root_v, vg.time_delta );
+ }
+
+ if( mid ){
+ effect_spark_apply( &effect_data->spark, vpc,
+ animator->root_v, vg.time_delta );
+ }
+ }
+}
+
+void player__skate_post_animate(void){
+ struct player_skate_state *state = &player_skate.state;
+ localplayer.cam_velocity_influence = 1.0f;
+ localplayer.cam_dist = 1.8f;
+
+ v3f head = { 0.0f, 1.8f, 0.0f };
+ m4x3_mulv( localplayer.final_mtx[ localplayer.id_head ],
+ head, state->head_position );
+ m4x3_mulv( localplayer.rb.to_local,
+ state->head_position, state->head_position );
+}
+
+void player__skate_reset_animator(void){
+ struct player_skate_state *state = &player_skate.state;
+
+ memset( &player_skate.animator, 0, sizeof(player_skate.animator) );
+
+ if( state->activity <= k_skate_activity_air_to_grind )
+ player_skate.animator.fly = 1.0f;
+ else
+ player_skate.animator.fly = 0.0f;
+}
+
+void player__skate_clear_mechanics(void)
+{
+ struct player_skate_state *state = &player_skate.state;
+ state->jump_charge = 0.0f;
+ state->charging_jump = 0;
+ state->jump_dir = 0;
+ v3_zero( state->flip_axis );
+ state->flip_time = 0.0f;
+ state->flip_rate = 0.0f;
+ state->reverse = 0.0f;
+ state->slip = 0.0f;
+ state->grabbing = 0.0f;
+ v2_zero( state->grab_mouse_delta );
+ state->slap = 0.0f;
+ state->jump_time = 0.0;
+ state->start_push = 0.0;
+ state->cur_push = 0.0;
+ state->air_start = 0.0;
+
+ v3_zero( state->air_init_v );
+ v3_zero( state->air_init_co );
+
+ state->gravity_bias = k_gravity;
+ v3_copy( localplayer.rb.co, state->prev_pos );
+ v4_copy( localplayer.rb.q, state->smoothed_rotation );
+ v3_zero( state->throw_v );
+ v3_zero( state->trick_vel );
+ v3_zero( state->trick_euler );
+ v3_zero( state->cog_v );
+ state->grind_cooldown = 0;
+ state->surface_cooldown = 0;
+ v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], 1.0f, state->cog );
+ v3_copy( localplayer.rb.to_world[1], state->up_dir );
+ v3_copy( localplayer.rb.to_world[1], player_skate.surface_picture );
+ v3_copy( localplayer.rb.co, state->prev_pos );
+ v3_zero( player_skate.weight_distribution );
+
+ v3f head = { 0.0f, 1.8f, 0.0f };
+ m4x3_mulv( localplayer.rb.to_world, head, state->head_position );
+}
+
+#include "network_compression.h"
+
+void player__skate_animator_exchange( bitpack_ctx *ctx, void *data ){
+ struct player_skate_animator *animator = data;
+
+ bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
+ bitpack_qquat( ctx, animator->root_q );
+
+ bitpack_qv3f( ctx, 8, -1.0f, 1.0f, animator->offset );
+ bitpack_qv3f( ctx, 8, -1.0f, 1.0f, animator->local_cog );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->slide );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->z );
+ bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->x );
+
+ /* these could likely be pressed down into single bits if needed */
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->fly );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->grind );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->stand );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->push );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->jump ); /*??*/
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->jump_charge ); /*??*/
+
+ /* just the sign bit? */
+ bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->reverse );
+ bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->delayed_slip_dir );
+ bitpack_bytes( ctx, 1, &animator->jump_dir );
+ bitpack_bytes( ctx, 1, &animator->trick_type );
+
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->grind_balance );
+ bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->airdir );
+ bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->weight );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->trick_foot );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->slap );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->subslap );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->grabbing );
+
+ /* animator->wobble is ommited */
+
+ bitpack_qv2f( ctx, 8, -1.0f, 1.0f, animator->foot_offset );
+ bitpack_qquat( ctx, animator->qfixuptotal );
+ bitpack_qquat( ctx, animator->qflip );
+
+ bitpack_qv3f( ctx, 16, -100.0f, 100.0f, animator->board_euler );
+ bitpack_qf32( ctx, 8, -1.0f, 1.0f, &animator->board_lean );
+ bitpack_qv2f( ctx, 8, -1.0f, 1.0f, animator->steer );
+ bitpack_qv2f( ctx, 8, -1.0f, 1.0f, animator->grab );
+
+ bitpack_qf32( ctx, 16, 0.0f, 120.0f, &animator->push_time );
+ bitpack_qf32( ctx, 16, 0.0f, 120.0f, &animator->jump_time );
+ bitpack_qf32( ctx, 16, 0.0f, 4.0f, &animator->handplant_t );
+ bitpack_qv3f( ctx, 16, -100.0f, 100.0f, animator->root_v );
+ bitpack_bytes( ctx, 1, &animator->activity );
+}
+
+void player__skate_sfx_oneshot( u8 id, v3f pos, f32 volume ){
+ audio_lock();
+
+ if( id == k_player_skate_soundeffect_jump ){
+ audio_oneshot_3d( &audio_jumps[vg_randu32(&vg.rand)%2],
+ pos, 40.0f, volume );
+ }
+ else if( id == k_player_skate_soundeffect_tap ){
+ audio_oneshot_3d( &audio_taps[vg_randu32(&vg.rand)%4],
+ pos, 40.0f, volume );
+ }
+ else if( id == k_player_skate_soundeffect_land_good ){
+ audio_oneshot_3d( &audio_lands[vg_randu32(&vg.rand)%3],
+ pos, 40.0f, volume );
+ }
+ else if( id == k_player_skate_soundeffect_land_bad ){
+ audio_oneshot_3d( &audio_lands[vg_randu32(&vg.rand)%2+3],
+ pos, 40.0f, volume );
+ }
+ else if( id == k_player_skate_soundeffect_grind_metal ){
+ audio_oneshot_3d( &audio_board[3], pos, 40.0f, volume );
+ }
+ else if( id == k_player_skate_soundeffect_grind_wood ){
+ audio_oneshot_3d( &audio_board[8], pos, 40.0f, volume );
+ }
+
+ audio_unlock();
+}
--- /dev/null
+#pragma once
+#include "vg/vg_audio.h"
+#include "player.h"
+#include "player_api.h"
+
+typedef struct jump_info jump_info;
+
+struct player_skate{
+ struct player_skate_state{
+ enum skate_activity{
+ k_skate_activity_air,
+ k_skate_activity_air_to_grind,
+ k_skate_activity_ground,
+ k_skate_activity_handplant,
+ k_skate_activity_undefined,
+ k_skate_activity_grind_any,
+ k_skate_activity_grind_boardslide,
+ k_skate_activity_grind_metallic,
+ k_skate_activity_grind_back50,
+ k_skate_activity_grind_front50,
+ k_skate_activity_grind_5050
+ }
+ activity,
+ activity_prev;
+
+ u32 grind_cooldown,
+ surface_cooldown;
+
+ f32 reverse, slip, delayed_slip_dir;
+ int manual_direction;
+
+ /* tricks */
+ v3f flip_axis;
+ float flip_time,
+ flip_rate;
+
+ v3f trick_vel, /* measured in units of TAU/s */
+ trick_euler; /* measured in units of TAU */
+ v3f trick_residualv, /* spring */
+ trick_residuald;
+
+ float trick_time;
+ enum trick_type{
+ k_trick_type_none,
+ k_trick_type_kickflip,
+ k_trick_type_shuvit,
+ k_trick_type_treflip,
+ }
+ trick_type;
+ float gravity_bias;
+
+ f32 trick_input_collect;
+
+ v3f up_dir;
+ v3f head_position;
+
+ v3f throw_v;
+ v3f cog_v, cog;
+
+ float grabbing;
+ v2f grab_mouse_delta;
+
+ int charging_jump, jump_dir;
+ float jump_charge,
+ slap;
+
+ double jump_time;
+ double start_push,
+ cur_push;
+
+ v3f prev_pos;
+
+ /* initial launch conditions */
+ double air_start;
+ v3f air_init_v,
+ air_init_co;
+
+ float land_dist;
+ v3f land_normal;
+ v4f smoothed_rotation;
+
+ f32 velocity_limit, grind_y_start, skid;
+ f32 handplant_t;
+
+ v3f store_cog_v, store_cog, store_co;
+ v4f store_smoothed, store_q;
+ }
+ state;
+
+ struct player_skate_animator {
+ v3f root_co;
+ v4f root_q;
+ v3f root_v;
+
+ v3f offset,
+ local_cog;
+
+ f32 slide,
+ skid,
+ z,
+ x,
+ fly,
+ grind,
+ grind_balance,
+ stand,
+ push,
+ jump,
+ airdir,
+ weight,
+ trick_foot,
+ slap,
+ subslap,
+ reverse,
+ delayed_slip_dir,
+ grabbing;
+
+ v2f wobble;
+ f32 foot_offset[2];
+
+ v4f qfixuptotal;
+ v4f qflip;
+
+ v3f board_euler;
+ f32 board_lean;
+ v2f steer, grab;
+
+ f32 jump_charge;
+
+ /* linear anims. TODO: we can union a bunch of variables here depending
+ * on activity. */
+ f32 push_time, jump_time, handplant_t;
+ u8 jump_dir;
+ u8 trick_type; /* todo: should encode grind type */
+ u8 activity, surface;
+ }
+ animator;
+
+ f32 collect_feedback;
+
+ /* animation /audio
+ * --------------------------------------------------------------*/
+ struct skeleton_anim *anim_stand, *anim_highg, *anim_slide,
+ *anim_air, *anim_grind, *anim_grind_jump,
+ *anim_push, *anim_push_reverse,
+ *anim_ollie, *anim_ollie_reverse,
+ *anim_grabs, *anim_stop,
+ *anim_handplant;
+
+ /* vectors representing the direction of the axels in localspace */
+ v3f truckv0[2];
+
+ audio_channel *aud_main, *aud_slide, *aud_air;
+ enum mdl_surface_prop surface, audio_surface;
+
+ int wheel_contacts[2];
+ float sample_change_cooldown;
+
+ enum {
+ k_skate_sample_concrete,
+ k_skate_sample_wood,
+ k_skate_sample_concrete_scrape_metal,
+ k_skate_sample_concrete_scrape_wood,
+ k_skate_sample_metal_scrape_generic
+ }
+ main_sample_type;
+
+ /*
+ * Physics
+ * ----------------------------------------------------
+ */
+
+ float substep, substep_delta;
+
+ struct jump_info{
+ v3f log[50];
+ v3f n;
+ v3f apex;
+ v3f v;
+
+ float gravity;
+
+ int log_length;
+ float score,
+ land_dist;
+
+ enum prediction_type{
+ k_prediction_none,
+ k_prediction_unset,
+ k_prediction_land,
+ k_prediction_grind
+ }
+ type;
+
+ u32 colour;
+ }
+ possible_jumps[36];
+ u32 possible_jump_count;
+
+ v3f surface_picture,
+ weight_distribution,
+ grind_vec,
+ grind_dir;
+
+ float grind_strength;
+ struct grind_limit{
+ v3f ra, n;
+ float p;
+ }
+ limits[3];
+ u32 limit_count;
+}
+extern player_skate;
+extern struct player_subsystem_interface player_subsystem_skate;
+
+enum player_skate_soundeffect {
+ k_player_skate_soundeffect_jump,
+ k_player_skate_soundeffect_tap,
+ k_player_skate_soundeffect_land_good,
+ k_player_skate_soundeffect_land_bad,
+ k_player_skate_soundeffect_grind_metal,
+ k_player_skate_soundeffect_grind_wood,
+};
+
+static float
+ k_friction_lat = 12.0f,
+ k_friction_resistance = 0.01f,
+
+ k_max_push_speed = 16.0f,
+ k_push_accel = 10.0f,
+ k_push_cycle_rate = 8.0f,
+
+ k_steer_ground = 2.5f,
+ k_steer_air = 3.6f,
+
+ k_jump_charge_speed = (1.0f/0.4f),
+ k_jump_force = 5.0f,
+
+ k_cog_spring = 0.2f,
+ k_cog_damp = 0.02f,
+ k_cog_mass_ratio = 0.9f,
+
+ k_mmthrow_steer = 1.0f,
+ k_mmthrow_scale = 6.0f,
+ k_mmcollect_lat = 2.0f,
+ k_mmcollect_vert = 0.0f,
+ k_mmdecay = 12.0f,
+ k_spring_angular = 1.0f,
+
+ k_spring_force = 300.0f,
+ k_spring_dampener = 5.0f,
+
+ k_grind_spring = 50.0f,
+ k_grind_aligment = 10.0f,
+ k_grind_dampener = 5.0f,
+
+ k_surface_spring = 100.0f,
+ k_surface_dampener = 40.0f,
+ k_manul_spring = 200.0f,
+ k_manul_dampener = 30.0f,
+ k_board_interia = 8.0f,
+
+ k_grind_decayxy = 30.0f,
+ k_grind_axel_min_vel = 1.0f,
+ k_grind_axel_max_angle = 0.95f, /* cosine(|a|) */
+ k_grind_axel_max_vangle = 0.4f,
+ k_grind_max_friction = 3.0f,
+ k_grind_max_edge_angle = 0.97f,
+
+ k_board_length = 0.45f,
+ k_board_width = 0.13f,
+ k_board_end_radius = 0.1f,
+ k_board_radius = 0.14f, /* 0.07 */
+
+ k_grind_balance = -40.0f,
+ k_anim_transition = 0.12f;
+
+static void player__skate_register(void)
+{
+ VG_VAR_F32( k_grind_dampener, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_grind_spring, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_grind_aligment, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_surface_spring, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_surface_dampener, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_board_interia, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_grind_decayxy, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_grind_axel_min_vel, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_grind_axel_max_angle, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_grind_max_friction, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_grind_balance, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_friction_lat, flags=VG_VAR_CHEAT );
+
+ VG_VAR_F32( k_cog_spring, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_cog_damp, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_cog_mass_ratio, flags=VG_VAR_CHEAT );
+
+ VG_VAR_F32( k_spring_force, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_spring_dampener, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_spring_angular, flags=VG_VAR_CHEAT );
+
+ VG_VAR_F32( k_mmthrow_scale, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_mmcollect_lat, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_mmcollect_vert, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_mmdecay, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_mmthrow_steer, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_anim_transition, flags=VG_VAR_CHEAT );
+}
+
+void player__skate_bind (void);
+void player__skate_pre_update (void);
+void player__skate_update (void);
+void player__skate_post_update (void);
+void player__skate_im_gui ( ui_context *ctx );
+void player__skate_animate (void);
+void player__skate_pose (void *animator, player_pose *pose);
+void player__skate_effects( void *_animator, m4x3f *final_mtx,
+ struct player_board *board,
+ struct player_effects_data *effect_data );
+void player__skate_post_animate (void);
+void player__skate_animator_exchange( bitpack_ctx *ctx, void *data );
+void player__skate_sfx_oneshot ( u8 id, v3f pos, f32 volume );
+
+void player__skate_clear_mechanics(void);
+void player__skate_reset_animator(void);
+void player__approximate_best_trajectory(void);
+void player__skate_comp_audio( void *animator );
+void player__skate_kill_audio(void);
--- /dev/null
+#include "vg/vg_rigidbody_collision.h"
+
+#include "skaterift.h"
+#include "player_walk.h"
+#include "player_skate.h"
+#include "player_dead.h"
+#include "player.h"
+#include "input.h"
+#include "audio.h"
+#include "scene_rigidbody.h"
+
+struct player_walk player_walk;
+struct player_subsystem_interface player_subsystem_walk =
+{
+ .system_register = player__walk_register,
+ .bind = player__walk_bind,
+ .pre_update = player__walk_pre_update,
+ .update = player__walk_update,
+ .post_update = player__walk_post_update,
+ .im_gui = player__walk_im_gui,
+ .animate = player__walk_animate,
+ .post_animate = player__walk_post_animate,
+ .pose = player__walk_pose,
+ .network_animator_exchange = player__walk_animator_exchange,
+ .sfx_oneshot = player__walk_sfx_oneshot,
+
+ .animator_data = &player_walk.animator,
+ .animator_size = sizeof(player_walk.animator),
+ .name = "Walk"
+};
+
+
+static void player_walk_drop_in_vector( v3f vec ){
+ v3f axis, init_dir;
+ v3_cross( (v3f){0.0f,1.0f,0.0f}, player_walk.state.drop_in_normal, axis );
+ v3_cross( axis, player_walk.state.drop_in_normal, init_dir );
+ v3_normalize( init_dir );
+ v3_muls( init_dir, 4.25f, vec );
+}
+
+static float player_xyspeed2(void){
+ return v3_length2( (v3f){localplayer.rb.v[0], 0.0f, localplayer.rb.v[2]} );
+}
+
+static void player_walk_generic_to_skate( enum skate_activity init, f32 yaw ){
+ localplayer.subsystem = k_player_subsystem_skate;
+
+ v3f v;
+
+ if( player_xyspeed2() < 0.1f * 0.1f )
+ q_mulv( localplayer.rb.q, (v3f){0.0f,0.0f,1.6f}, v );
+ else
+ v3_copy( localplayer.rb.v, v );
+
+ player_skate.state.activity_prev = k_skate_activity_ground;
+ player_skate.state.activity = init;
+
+ v3f dir;
+ v3_copy( v, dir );
+ v3_normalize( dir );
+
+ q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
+ atan2f(-dir[0],-dir[2]) );
+ q_normalize( localplayer.rb.q );
+
+ q_mulv( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f}, player_skate.state.cog );
+ v3_add( player_skate.state.cog, localplayer.rb.co, player_skate.state.cog );
+
+ v3_copy( v, player_skate.state.cog_v );
+ v3_copy( v, localplayer.rb.v );
+
+ player__begin_holdout( (v3f){0.0f,0.0f,0.0f} );
+ player__skate_reset_animator();
+ player__skate_clear_mechanics();
+ rb_update_matrices( &localplayer.rb );
+ v3_copy( (v3f){yaw,0.0f,0.0f}, player_skate.state.trick_euler );
+
+ if( init == k_skate_activity_air )
+ player__approximate_best_trajectory();
+}
+
+static void player_walk_drop_in_to_skate(void){
+ localplayer.immobile = 0;
+ localplayer.subsystem = k_player_subsystem_skate;
+
+ player_skate.state.activity_prev = k_skate_activity_ground;
+ player_skate.state.activity = k_skate_activity_ground;
+
+ player__begin_holdout( (v3f){0,0,0} );
+ player__skate_clear_mechanics();
+ player__skate_reset_animator();
+
+ v3f init_velocity;
+ player_walk_drop_in_vector( init_velocity );
+
+ rb_update_matrices( &localplayer.rb );
+ v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], 1.0f,
+ player_skate.state.cog );
+ v3_copy( init_velocity, player_skate.state.cog_v );
+ v3_copy( init_velocity, localplayer.rb.v );
+ v3_copy( init_velocity, localplayer.cam_control.cam_velocity_smooth );
+ v3_copy( (v3f){player_walk.animator.board_yaw+1.0f,0,0},
+ player_skate.state.trick_euler );
+}
+
+static void player_walk_drop_in_overhang_transform( f32 t, v3f co, v4f q ){
+ v3f axis;
+ v3_cross( (v3f){0,1,0}, player_walk.state.drop_in_normal, axis );
+ v3_normalize( axis );
+
+ float a = acosf( player_walk.state.drop_in_normal[1] ) * t;
+ q_axis_angle( q, axis, a );
+
+ float l = t * 0.5f,
+ heading_angle = player_walk.state.drop_in_angle;
+
+ v3f overhang;
+ overhang[0] = sinf( heading_angle ) * l;
+ overhang[1] = 0.28f * l;
+ overhang[2] = cosf( heading_angle ) * l;
+
+ q_mulv( q, overhang, overhang );
+ v3_add( player_walk.state.drop_in_target, overhang, co );
+}
+
+static int player_walk_scan_for_drop_in(void){
+ world_instance *world = world_current_instance();
+
+ v3f dir, center;
+ q_mulv( localplayer.rb.q, (v3f){0.0f,0.0f,1.0f}, dir );
+ v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1], -1.0f, center );
+
+ ray_hit samples[20];
+ int sample_count = 0;
+
+ for( int i=0; i<20; i ++ ){
+ float t = (float)i * (1.0f/19.0f),
+ s = sinf( t * VG_PIf * 0.25f ),
+ c = cosf( t * VG_PIf * 0.25f );
+
+ v3f ray_dir, pos;
+ v3_muls ( localplayer.rb.to_world[1], -c, ray_dir );
+ v3_muladds( ray_dir, dir, -s, ray_dir );
+ v3_muladds( center, ray_dir, -2.0f, pos );
+
+ ray_hit *ray = &samples[ sample_count ];
+ ray->dist = 2.0f;
+
+ if( ray_world( world, pos, ray_dir, ray, 0 ) ){
+ vg_line( pos, ray->pos, VG__RED );
+ vg_line_point( ray->pos, 0.025f, VG__BLACK );
+
+ sample_count ++;
+ }
+ }
+
+ float min_a = 0.70710678118654752f;
+ ray_hit *candidate = NULL;
+
+ if( sample_count >= 2 ){
+ for( int i=0; i<sample_count-1; i++ ){
+ ray_hit *s0 = &samples[i],
+ *s1 = &samples[i+1];
+
+ float a = v3_dot( s0->normal, s1->normal );
+
+ if( (a < min_a) && (a >= -0.1f) && (s0->normal[1]>s1->normal[1]) ){
+ min_a = a;
+ candidate = s0;
+ }
+ }
+ }
+
+ if( candidate ){
+ v4f pa, pb, pc;
+
+ ray_hit *s0 = candidate,
+ *s1 = candidate+1;
+
+ vg_line( s0->pos, s1->pos, VG__WHITE );
+
+ v3_copy( s0->normal, pa );
+ v3_copy( s1->normal, pb );
+ v3_cross( localplayer.rb.to_world[1], dir, pc );
+ v3_normalize( pc );
+
+ pa[3] = v3_dot( pa, s0->pos );
+ pb[3] = v3_dot( pb, s1->pos );
+ pc[3] = v3_dot( pc, localplayer.rb.co );
+
+ v3f edge;
+ if( plane_intersect3( pa, pb, pc, edge ) ){
+ v3_copy( edge, player_walk.state.drop_in_target );
+ v3_copy( s1->normal, player_walk.state.drop_in_normal );
+ v3_copy( localplayer.rb.co, player_walk.state.drop_in_start );
+
+ player_walk.state.drop_in_start_angle = player_get_heading_yaw();
+ player_walk.state.drop_in_angle =
+ atan2f( player_walk.state.drop_in_normal[0],
+ player_walk.state.drop_in_normal[2] );
+
+ /* TODO: scan multiple of these? */
+ v3f oco;
+ v4f oq;
+ player_walk_drop_in_overhang_transform( 1.0f, oco, oq );
+
+ v3f va = {0.0f,0.0f,-k_board_length - 0.3f},
+ vb = {0.0f,0.0f, k_board_length + 0.3f};
+
+ q_mulv( oq, va, va );
+ q_mulv( oq, vb, vb );
+ v3_add( oco, va, va );
+ v3_add( oco, vb, vb );
+
+ v3f v0;
+ v3_sub( vb, va, v0 );
+ v3_normalize( v0 );
+
+ ray_hit ray;
+ ray.dist = k_board_length*2.0f + 0.6f;
+
+ if( ray_world( world, va, v0, &ray, 0 ) ){
+ vg_line( va, vb, VG__RED );
+ vg_line_point( ray.pos, 0.1f, VG__RED );
+ vg_error( "invalidated\n" );
+ return 0;
+ }
+
+ v3_muls( v0, -1.0f, v0 );
+ if( ray_world( world, vb, v0, &ray, 0 ) ){
+ vg_line( va, vb, VG__RED );
+ vg_line_point( ray.pos, 0.1f, VG__RED );
+ vg_error( "invalidated\n" );
+ return 0;
+ }
+
+ player_walk_drop_in_vector( localplayer.rb.v );
+ return 1;
+ }
+ else{
+ vg_error( "failed to find intersection of drop in\n" );
+ }
+ }
+
+ return 0;
+}
+
+static bool player__preupdate_anim( struct skeleton_anim *anim, f32 *t,
+ f32 speed ){
+ f32 length = (f32)(anim->length-1) / anim->rate;
+ *t += (vg.time_delta * speed) / length;
+
+ if( *t >= 1.0f ) return 1;
+ else return 0;
+}
+
+static void player_walk_pre_sit(void){
+ struct player_walk *w = &player_walk;
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+
+ vg_slewf( &w->state.transition_t, 1.0f, vg.time_delta );
+
+ if( button_down(k_srbind_sit) || (v2_length2(steer)>0.2f) ||
+ button_down(k_srbind_jump) ){
+ w->state.activity = k_walk_activity_sit_up;
+ }
+ return;
+}
+
+static void player_walk_pre_sit_up(void){
+ struct player_walk *w = &player_walk;
+
+ if( w->state.transition_t > 0.0f )
+ vg_slewf( &w->state.transition_t, 0.0f, vg.time_delta );
+ else
+ w->state.activity = k_walk_activity_ground;
+
+ if( button_down(k_srbind_sit) )
+ w->state.activity = k_walk_activity_sit;
+
+ return;
+}
+
+static void player_walk_pre_ground(void){
+ struct player_walk *w = &player_walk;
+
+ if( button_down(k_srbind_sit) ){
+ v3_zero( localplayer.rb.v );
+ w->state.activity = k_walk_activity_sit;
+ w->state.transition_t = 0.0f;
+ return;
+ }
+
+ if( button_down( k_srbind_use ) ){
+ if( player_walk_scan_for_drop_in() ){
+ w->state.activity = k_walk_activity_odrop_in;
+ }
+ else{
+ w->state.activity = k_walk_activity_oregular;
+ }
+
+ w->state.transition_t = 0.0f;
+ }
+
+ if( button_down( k_srbind_jump ) ){
+ w->state.jump_queued = 1;
+ w->state.jump_input_time = vg.time;
+ }
+}
+
+static void player_walk_pre_air(void){
+ struct player_walk *w = &player_walk;
+ if( button_down( k_srbind_use ) ){
+ w->state.activity = k_walk_activity_oair;
+ w->state.transition_t = 0.0f;
+ }
+
+ if( button_down( k_srbind_jump ) ){
+ w->state.jump_queued = 1;
+ w->state.jump_input_time = vg.time;
+ }
+}
+
+static void player_walk_pre_drop_in(void){
+ struct player_walk *w = &player_walk;
+ bool finished = player__preupdate_anim( w->anim_drop_in,
+ &w->state.transition_t, 1.0f );
+ if( finished )
+ player_walk_drop_in_to_skate();
+}
+
+static void player_walk_pre_caveman(void){
+ struct player_walk *w = &player_walk;
+ bool finished = player__preupdate_anim( w->anim_jump_to_air,
+ &w->state.transition_t, 1.0f );
+ if( finished ){
+ player_walk_generic_to_skate( k_skate_activity_air,
+ player_walk.animator.board_yaw );
+ }
+}
+
+static void player_walk_pre_running_start(void){
+ struct player_walk *w = &player_walk;
+ bool finished = player__preupdate_anim( w->anim_intro,
+ &w->state.transition_t, 1.0f );
+ if( finished ){
+ /* TODO: get the derivative of the last keyframes to calculate new
+ * velocity for player */
+ player_walk_generic_to_skate( k_skate_activity_ground,
+ player_walk.animator.board_yaw+1.0f );
+ }
+}
+
+static void player_walk_pre_popoff(void){
+ struct player_walk *w = &player_walk;
+ bool finished = player__preupdate_anim( w->anim_popoff,
+ &w->state.transition_t, 1.0f );
+
+ if( finished ){
+ w->state.activity = k_walk_activity_ground;
+ w->animator.board_yaw += 1.0f;
+ }
+}
+
+void player__walk_pre_update(void){
+ struct player_walk *w = &player_walk;
+
+ if( localplayer.immobile ) return;
+ else player_look( localplayer.angles, skaterift.time_rate );
+
+ enum walk_activity a = w->state.activity;
+
+ if ( a == k_walk_activity_sit ) player_walk_pre_sit();
+ else if( a == k_walk_activity_sit_up ) player_walk_pre_sit_up();
+ else if( a == k_walk_activity_ground ) player_walk_pre_ground();
+ else if( a == k_walk_activity_air ) player_walk_pre_air();
+ else if( a == k_walk_activity_odrop_in ) player_walk_pre_drop_in();
+ else if( a == k_walk_activity_oair ) player_walk_pre_caveman();
+ else if( a == k_walk_activity_oregular ) player_walk_pre_running_start();
+ else if( a == k_walk_activity_ipopoff ) player_walk_pre_popoff();
+}
+
+static int player_walk_normal_standable( v3f n ){
+ return n[1] > 0.70710678118f;
+}
+
+static void player_accelerate( v3f v, v3f movedir, f32 speed, f32 accel ){
+ float currentspeed = v3_dot( v, movedir ),
+ addspeed = speed - currentspeed;
+
+ if( addspeed <= 0 )
+ return;
+
+ float accelspeed = accel * vg.time_fixed_delta * speed;
+
+ if( accelspeed > addspeed )
+ accelspeed = addspeed;
+
+ v3_muladds( v, movedir, accelspeed, v );
+}
+
+static void player_friction( v3f v, f32 friction ){
+ float speed = v3_length( v ),
+ drop = 0.0f,
+ control = vg_maxf( speed, k_stopspeed );
+
+ if( speed < 0.04f )
+ return;
+
+ drop += control * friction * vg.time_fixed_delta;
+
+ float newspeed = vg_maxf( 0.0f, speed - drop );
+ newspeed /= speed;
+
+ v3_muls( v, newspeed, v );
+}
+
+static void player_walk_custom_filter( world_instance *world,
+ rb_ct *man, int len, f32 w ){
+ for( int i=0; i<len; i++ ){
+ rb_ct *ci = &man[i];
+ if( ci->type == k_contact_type_disabled ||
+ ci->type == k_contact_type_edge )
+ continue;
+
+
+ float d1 = v3_dot( ci->co, ci->n );
+
+ for( int j=0; j<len; j++ ){
+ if( j == i )
+ continue;
+
+ rb_ct *cj = &man[j];
+ if( cj->type == k_contact_type_disabled )
+ continue;
+
+ struct world_surface *si = world_contact_surface( world, ci ),
+ *sj = world_contact_surface( world, cj );
+
+ if( (sj->info.flags & k_material_flag_walking) &&
+ !(si->info.flags & k_material_flag_walking)){
+ continue;
+ }
+
+ float d2 = v3_dot( cj->co, ci->n ),
+ d = d2-d1;
+
+ if( fabsf( d ) <= w ){
+ cj->type = k_contact_type_disabled;
+ }
+ }
+ }
+}
+
+static void player_walk_update_generic(void){
+ struct player_walk *w = &player_walk;
+
+ if( (w->state.activity != k_walk_activity_oregular) &&
+ (w->state.activity != k_walk_activity_oair) ){
+ joystick_state( k_srjoystick_steer, w->state.steer );
+ w->state.steer[2] = button_press(k_srbind_run)? k_runspeed: k_walkspeed;
+ if( v2_length2(w->state.steer)>1.0f )
+ v2_normalize(w->state.steer);
+ }
+
+ v3_copy( localplayer.rb.co, w->state.prev_pos );
+ v3_zero( localplayer.rb.w );
+
+ world_instance *world = world_current_instance();
+ if( !world_water_player_safe( world, 0.4f ) ) return;
+
+ enum walk_activity prev_state = w->state.activity;
+
+ w->collider.h = 2.0f;
+ w->collider.r = 0.3f;
+
+ m4x3f mtx;
+ m3x3_copy( localplayer.rb.to_world, mtx );
+ v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
+
+ vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__WHITE );
+
+ rb_ct manifold[64];
+ int len;
+
+ float yaw = localplayer.angles[0];
+
+ v3f forward_dir = { -sinf(yaw), 0.0f, cosf(yaw) };
+ v3f right_dir = { forward_dir[2], 0.0f, -forward_dir[0] };
+
+ /*
+ * Collision detection
+ */
+
+ len = rb_capsule__scene( mtx, &w->collider, NULL,
+ world->geo_bh, manifold, 0 );
+ player_walk_custom_filter( world, manifold, len, 0.01f );
+ len = rb_manifold_apply_filtered( manifold, len );
+
+ v3f surface_avg = { 0.0f, 0.0f, 0.0f };
+
+ w->state.activity = k_walk_activity_air;
+ w->surface = k_surface_prop_concrete;
+
+ for( int i=0; i<len; i++ ){
+ rb_ct *ct = &manifold[i];
+ rb_debug_contact( ct );
+
+ if( player_walk_normal_standable( ct->n ) ){
+ w->state.activity = k_walk_activity_ground;
+
+ v3_add( surface_avg, ct->n, surface_avg );
+
+ struct world_surface *surf = world_contact_surface( world, ct );
+ if( surf->info.surface_prop > w->surface )
+ w->surface = surf->info.surface_prop;
+ }
+
+ rb_prepare_contact( ct, vg.time_fixed_delta );
+ }
+
+ /*
+ * Move & Friction
+ */
+ float accel_speed = 0.0f, nominal_speed = 0.0f;
+ v3f movedir;
+
+ v3_muls( right_dir, w->state.steer[0], movedir );
+ v3_muladds( movedir, forward_dir, w->state.steer[1], movedir );
+
+ if( w->state.activity == k_walk_activity_ground ){
+ v3_normalize( surface_avg );
+
+ v3f tx, ty;
+ v3_tangent_basis( surface_avg, tx, ty );
+
+ if( v2_length2(w->state.steer) > 0.001f ){
+ /* clip movement to the surface */
+ float d = v3_dot(surface_avg,movedir);
+ v3_muladds( movedir, surface_avg, -d, movedir );
+ }
+
+ accel_speed = k_walk_accel;
+ nominal_speed = w->state.steer[2];
+
+ /* jump */
+ if( w->state.jump_queued ){
+ w->state.jump_queued = 0;
+
+ f32 t = vg.time - w->state.jump_input_time;
+ if( t < PLAYER_JUMP_EPSILON ){
+ localplayer.rb.v[1] = 5.0f;
+ w->state.activity = k_walk_activity_air;
+ prev_state = k_walk_activity_air;
+ accel_speed = k_walk_air_accel;
+ nominal_speed = k_airspeed;
+ }
+ }
+ else{
+ player_friction( localplayer.rb.v, k_walk_friction );
+ }
+ }
+ else{
+ accel_speed = k_walk_air_accel;
+ nominal_speed = k_airspeed;
+ }
+
+ if( v2_length2( w->state.steer ) > 0.001f ){
+ player_accelerate( localplayer.rb.v, movedir,
+ nominal_speed, accel_speed );
+ v3_normalize( movedir );
+ }
+
+ /*
+ * Resolve velocity constraints
+ */
+ for( int j=0; j<5; j++ ){
+ for( int i=0; i<len; i++ ){
+ rb_ct *ct = &manifold[i];
+
+ /*normal */
+ float vn = -v3_dot( localplayer.rb.v, ct->n );
+
+ float temp = ct->norm_impulse;
+ ct->norm_impulse = vg_maxf( temp + vn, 0.0f );
+ vn = ct->norm_impulse - temp;
+
+ v3_muladds( localplayer.rb.v, ct->n, vn, localplayer.rb.v );
+ }
+ }
+
+ /* stepping */
+ if( w->state.activity == k_walk_activity_ground||
+ prev_state == k_walk_activity_ground ){
+ float max_dist = 0.4f;
+
+ v3f pa, pb;
+ v3_copy( localplayer.rb.co, pa );
+ pa[1] += w->collider.r + max_dist;
+ v3_add( pa, (v3f){0, -max_dist * 2.0f, 0}, pb );
+ vg_line( pa, pb, 0xff000000 );
+
+ v3f n;
+ float t;
+ if( spherecast_world( world, pa, pb,
+ w->collider.r, &t, n, 0 ) != -1 ){
+ if( player_walk_normal_standable(n) ){
+ v3_lerp( pa, pb, t, localplayer.rb.co );
+ localplayer.rb.co[1] += -w->collider.r - k_penetration_slop;
+ w->state.activity = k_walk_activity_ground;
+
+ float d = -v3_dot(n,localplayer.rb.v);
+ v3_muladds( localplayer.rb.v, n, d, localplayer.rb.v );
+ localplayer.rb.v[1] += -k_gravity * vg.time_fixed_delta;
+ }
+ }
+ }
+
+ /*
+ * Depenetrate
+ */
+ v3f dt;
+ rb_depenetrate( manifold, len, dt );
+ v3_add( dt, localplayer.rb.co, localplayer.rb.co );
+
+ /* integrate */
+ if( w->state.activity == k_walk_activity_air ){
+ localplayer.rb.v[1] += -k_gravity*vg.time_fixed_delta;
+ }
+
+ if( localplayer.immobile ){
+ localplayer.rb.v[0] = 0.0f;
+ localplayer.rb.v[2] = 0.0f;
+ }
+
+ v3_muladds( localplayer.rb.co, localplayer.rb.v, vg.time_fixed_delta,
+ localplayer.rb.co );
+ v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
+ vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__GREEN );
+
+ /*
+ * CCD routine
+ * ---------------------------------------------------
+ *
+ */
+ v3f lwr_prev,
+ lwr_now,
+ lwr_offs = { 0.0f, w->collider.r, 0.0f };
+
+ v3_add( lwr_offs, w->state.prev_pos, lwr_prev );
+ v3_add( lwr_offs, localplayer.rb.co, lwr_now );
+
+ v3f movedelta;
+ v3_sub( localplayer.rb.co, w->state.prev_pos, movedelta );
+
+ float movedist = v3_length( movedelta );
+
+ if( movedist > 0.3f ){
+ float t, sr = w->collider.r-0.04f;
+ v3f n;
+
+ if( spherecast_world( world, lwr_prev, lwr_now, sr, &t, n, 0 ) != -1 ){
+ v3_lerp( lwr_prev, lwr_now, vg_maxf(0.01f,t), localplayer.rb.co );
+ localplayer.rb.co[1] -= w->collider.r;
+ rb_update_matrices( &localplayer.rb );
+ v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
+ vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__RED);
+ }
+ }
+
+ u32 id = world_intersect_gates(world, localplayer.rb.co, w->state.prev_pos);
+ if( id ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, mdl_entity_id_id(id) );
+ m4x3_mulv( gate->transport, localplayer.rb.co, localplayer.rb.co );
+ m3x3_mulv( gate->transport, localplayer.rb.v, localplayer.rb.v );
+
+ v4f transport_rotation;
+ m3x3_q( gate->transport, transport_rotation );
+ q_mul( transport_rotation, localplayer.rb.q, localplayer.rb.q );
+ q_normalize( localplayer.rb.q );
+ rb_update_matrices( &localplayer.rb );
+ player__pass_gate( id );
+ }
+ rb_update_matrices( &localplayer.rb );
+
+ if( (prev_state == k_walk_activity_oregular) ||
+ (prev_state == k_walk_activity_oair) ||
+ (prev_state == k_walk_activity_ipopoff) ){
+ w->state.activity = prev_state;
+ }
+
+ w->move_speed = vg_minf( v2_length( (v2f){ localplayer.rb.v[0],
+ localplayer.rb.v[2] } ),
+ k_runspeed );
+}
+
+void player__walk_post_update(void){
+ struct player_walk *w = &player_walk;
+
+ m4x3f mtx;
+ m3x3_copy( localplayer.rb.to_world, mtx );
+ v3_add( localplayer.rb.co, (v3f){0,1,0}, mtx[3] );
+
+ float substep = vg.time_fixed_extrapolate;
+ v3_muladds( mtx[3], localplayer.rb.v, vg.time_fixed_delta*substep, mtx[3] );
+ vg_line_capsule( mtx, w->collider.r, w->collider.h, VG__YELOW );
+
+ /* Calculate header */
+ v3f v;
+ if( (player_xyspeed2() > 0.1f*0.1f) ){
+ f32 r = 0.3f;
+ if( (w->state.activity == k_walk_activity_ground) ||
+ (w->state.activity == k_walk_activity_ipopoff) ||
+ (w->state.activity == k_walk_activity_oregular) ){
+ r = 0.07f;
+ }
+
+ f32 ta = atan2f( localplayer.rb.v[0], localplayer.rb.v[2] );
+ v4f qt;
+ q_axis_angle( qt, (v3f){0,1,0}, ta );
+ q_nlerp( localplayer.rb.q, qt, vg.time_delta/r, localplayer.rb.q );
+ }
+
+ vg_line_point( w->state.drop_in_target, 0.1f, VG__GREEN );
+ v3f p1;
+ v3_muladds( w->state.drop_in_target, w->state.drop_in_normal, 0.3f, p1 );
+ vg_line( w->state.drop_in_target, p1, VG__GREEN );
+ v3_muladds( w->state.drop_in_target, localplayer.rb.to_world[1], 0.3f, p1 );
+ vg_line( w->state.drop_in_target, p1, VG__GREEN );
+
+ float a = player_get_heading_yaw();
+ p1[0] = sinf( a );
+ p1[1] = 0.0f;
+ p1[2] = cosf( a );
+
+ v3_add( localplayer.rb.co, p1, p1 );
+ vg_line( localplayer.rb.co, p1, VG__PINK );
+
+ int walk_phase = 0;
+ if( vg_fractf(w->state.walk_timer) > 0.5f )
+ walk_phase = 1;
+ else
+ walk_phase = 0;
+
+ if( (w->state.step_phase != walk_phase) &&
+ (w->state.activity == k_walk_activity_ground ) )
+ {
+ audio_lock();
+ if( w->surface == k_surface_prop_concrete ){
+ audio_oneshot_3d(
+ &audio_footsteps[vg_randu32(&vg.rand) % 4],
+ localplayer.rb.co, 40.0f, 1.0f
+ );
+ }
+ else if( w->surface == k_surface_prop_grass ){
+ audio_oneshot_3d(
+ &audio_footsteps_grass[ vg_randu32(&vg.rand) % 6 ],
+ localplayer.rb.co, 40.0f, 1.0f
+ );
+ }
+ else if( w->surface == k_surface_prop_wood ){
+ audio_oneshot_3d(
+ &audio_footsteps_wood[ vg_randu32(&vg.rand) % 6 ],
+ localplayer.rb.co, 40.0f, 1.0f
+ );
+ }
+ audio_unlock();
+ }
+
+ w->state.step_phase = walk_phase;
+}
+
+void player__walk_update(void){
+ struct player_walk *w = &player_walk;
+
+ if( (w->state.activity == k_walk_activity_air) ||
+ (w->state.activity == k_walk_activity_ground) ||
+ (w->state.activity == k_walk_activity_oair) ||
+ (w->state.activity == k_walk_activity_oregular) ||
+ (w->state.activity == k_walk_activity_ipopoff) ){
+ player_walk_update_generic();
+ }
+}
+
+static void player_walk_animate_drop_in(void){
+ struct player_walk *w = &player_walk;
+ struct player_walk_animator *animator = &w->animator;
+ struct skeleton_anim *anim = w->anim_drop_in;
+
+ f32 length = (f32)(anim->length-1) / anim->rate,
+ time = w->state.transition_t;
+
+ f32 walk_yaw = vg_alerpf( w->state.drop_in_start_angle,
+ w->state.drop_in_angle, animator->transition_t );
+ v3_lerp( w->state.drop_in_start, w->state.drop_in_target,
+ animator->transition_t, localplayer.rb.co );
+
+ q_axis_angle( localplayer.rb.q, (v3f){0,1,0}, walk_yaw + VG_PIf );
+
+ /* the drop in bit */
+ v3f final_co;
+ v4f final_q;
+ player_walk_drop_in_overhang_transform( animator->transition_t,
+ final_co, final_q );
+
+ q_mul( final_q, localplayer.rb.q, localplayer.rb.q );
+ v3_lerp( localplayer.rb.co, final_co, animator->transition_t,
+ localplayer.rb.co );
+
+ rb_update_matrices( &localplayer.rb );
+
+ v3_muladds( localplayer.rb.co, localplayer.rb.to_world[1],
+ -0.1f*animator->transition_t, localplayer.rb.co );
+
+ v3_copy( localplayer.rb.co, animator->root_co );
+ v4_copy( localplayer.rb.q, animator->root_q );
+
+ /* for the camera purposes only */
+ v3f init_velocity;
+ player_walk_drop_in_vector( init_velocity );
+ v3_muls( init_velocity, animator->transition_t, localplayer.rb.v );
+ v3_copy( localplayer.rb.v,
+ localplayer.cam_control.cam_velocity_smooth );
+}
+
+static void player_walk_animate_generic(void){
+ struct player_walk *w = &player_walk;
+ struct player_walk_animator *animator = &w->animator;
+
+ v4f _null;
+ rb_extrapolate( &localplayer.rb, animator->root_co, _null );
+
+ f32 walk_yaw = player_get_heading_yaw(),
+ head_yaw = localplayer.angles[0] + VG_PIf,
+ y = vg_angle_diff( head_yaw, -walk_yaw ),
+ p = vg_clampf( localplayer.angles[1],
+ -k_sit_pitch_limit, k_sit_pitch_limit );
+
+ if( fabsf(y) > k_sit_yaw_limit ){
+ y = 0.0f;
+ p = 0.0f;
+ }
+
+ animator->yaw = vg_lerpf( animator->yaw, y, vg.time_delta*2.0f );
+ animator->pitch = vg_lerpf( animator->pitch, p, vg.time_delta*2.8f );
+ q_axis_angle( animator->root_q, (v3f){0,1,0}, walk_yaw + VG_PIf );
+
+ v4f qrev;
+ q_axis_angle( qrev, (v3f){0,1,0}, VG_TAUf*0.5f );
+ q_mul( localplayer.rb.q, qrev, animator->root_q );
+}
+
+void player__walk_animate(void){
+ struct player_walk *w = &player_walk;
+ player_pose *pose = &localplayer.pose;
+ struct player_walk_animator *animator = &w->animator;
+
+ animator->activity = w->state.activity;
+ animator->transition_t = w->state.transition_t;
+
+ {
+ f32 fly = (w->state.activity == k_walk_activity_air)? 1.0f: 0.0f,
+ rate;
+
+ if( w->state.activity == k_walk_activity_air ) rate = 2.4f;
+ else rate = 9.0f;
+
+ animator->fly = vg_lerpf( animator->fly, fly, rate*vg.time_delta );
+ animator->run = vg_lerpf( animator->run, w->move_speed,
+ 8.0f*vg.time_delta);
+ }
+
+ if( animator->run > 0.025f ){
+ f32 walk_norm = 30.0f/(float)w->anim_walk->length,
+ run_norm = 30.0f/(float)w->anim_run->length,
+ l;
+
+ if( animator->run <= k_walkspeed )
+ l = (animator->run / k_walkspeed) * walk_norm;
+ else {
+ l = vg_lerpf( walk_norm, run_norm,
+ (animator->run-k_walkspeed) / (k_runspeed-k_walkspeed) );
+ }
+ w->state.walk_timer += l * vg.time_delta;
+ }
+ else
+ w->state.walk_timer = 0.0f;
+
+ animator->walk_timer = w->state.walk_timer;
+
+ player_walk_animate_generic();
+ if( w->state.activity == k_walk_activity_odrop_in ){
+ player_walk_animate_drop_in();
+ }
+
+ if( (w->state.activity == k_walk_activity_odrop_in) ||
+ (w->state.activity == k_walk_activity_oregular) ||
+ (w->state.activity == k_walk_activity_oair) ){
+ localplayer.cam_velocity_influence = w->animator.transition_t;
+ }
+ else if( w->state.activity == k_walk_activity_ipopoff ){
+ localplayer.cam_velocity_influence = 1.0f-w->animator.transition_t;
+ }
+ else
+ localplayer.cam_velocity_influence = 0.0f;
+
+ if( w->state.activity == k_walk_activity_sit ){
+ localplayer.cam_dist = 3.8f;
+ }
+ else {
+ localplayer.cam_dist = 1.8f;
+ }
+}
+
+static void player_walk_pose_sit( struct player_walk_animator *animator,
+ player_pose *pose )
+{
+ mdl_keyframe bpose[32];
+
+ struct player_walk *w = &player_walk;
+ struct skeleton *sk = &localplayer.skeleton;
+
+ f32 t = animator->transition_t,
+ st = t * ((f32)(w->anim_sit->length-1)/30.0f);
+ skeleton_sample_anim( sk, w->anim_sit, st, bpose );
+
+ v4f qy,qp;
+ f32 *qh = bpose[localplayer.id_head-1].q;
+ q_axis_angle( qy, (v3f){0,1,0}, animator->yaw*0.5f*t );
+ q_axis_angle( qp, (v3f){0,0,1}, animator->pitch*t );
+ q_mul( qy, qh, qh );
+ q_mul( qh, qp, qh );
+ q_normalize( qh );
+
+ qh = bpose[localplayer.id_chest-1].q;
+ q_axis_angle( qy, (v3f){0,1,0}, animator->yaw*0.5f*t );
+ q_mul( qy, qh, qh );
+ q_normalize( qh );
+
+ skeleton_lerp_pose( sk, pose->keyframes, bpose,
+ vg_minf(1.0f,t*10.0f), pose->keyframes );
+}
+
+enum walk_transition_type {
+ k_walk_transition_in,
+ k_walk_transition_out,
+ k_walk_transition_outin,
+};
+
+static void player_walk_pose_transition(
+ struct player_walk_animator *animator, struct skeleton_anim *anim,
+ enum walk_transition_type type,
+ mdl_keyframe apose[32], f32 *mask, player_pose *pose ){
+
+ mdl_keyframe bpose[32];
+
+ struct player_walk *w = &player_walk;
+ struct skeleton *sk = &localplayer.skeleton;
+
+ f32 length = (f32)(anim->length-1) / anim->rate,
+ t = animator->transition_t * length,
+ blend = 1.0f;
+
+ if( type == k_walk_transition_in || type == k_walk_transition_outin )
+ blend = vg_minf( blend, length-t );
+
+ if( type == k_walk_transition_out || type == k_walk_transition_outin )
+ blend = vg_minf( blend, t );
+
+ blend = vg_smoothstepf( vg_minf(1,blend/k_anim_transition) );
+
+ skeleton_sample_anim_clamped( sk, anim, t, bpose );
+
+ mdl_keyframe *kf_board = &bpose[localplayer.id_board-1];
+ f32 yaw = animator->board_yaw * VG_TAUf * 0.5f;
+
+ v4f qyaw;
+ q_axis_angle( qyaw, (v3f){0,1,0}, yaw );
+ q_mul( kf_board->q, qyaw, kf_board->q );
+ q_normalize( kf_board->q );
+
+ if( mask ){
+ for( i32 i=0; i<sk->bone_count-1; i ++ )
+ keyframe_lerp( apose+i, bpose+i, blend*mask[i], pose->keyframes+i );
+ }
+ else
+ skeleton_lerp_pose( sk, apose, bpose, blend, pose->keyframes );
+}
+
+void player__walk_pose( void *_animator, player_pose *pose ){
+ struct player_walk *w = &player_walk;
+ struct player_walk_animator *animator = _animator;
+ struct skeleton *sk = &localplayer.skeleton;
+
+ v3_copy( animator->root_co, pose->root_co );
+ v4_copy( animator->root_q, pose->root_q );
+ pose->board.lean = 0.0f;
+ pose->type = k_player_pose_type_ik;
+
+ float walk_norm = (float)w->anim_walk->length/30.0f,
+ run_norm = (float)w->anim_run->length/30.0f,
+ t = animator->walk_timer;
+
+ /* walk/run */
+ mdl_keyframe apose[32], bpose[32];
+ if( animator->run <= k_walkspeed ){
+ /* walk / idle */
+ f32 l = vg_minf( 1, (animator->run/k_walkspeed)*6.0f );
+ skeleton_sample_anim( sk, w->anim_idle, vg.time*0.1f, apose );
+ skeleton_sample_anim( sk, w->anim_walk, t*walk_norm, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, l, apose );
+ }
+ else {
+ /* walk / run */
+ f32 l = (animator->run-k_walkspeed) / (k_runspeed-k_walkspeed);
+ skeleton_sample_anim( sk, w->anim_walk, t*walk_norm, apose );
+ skeleton_sample_anim( sk, w->anim_run, t*run_norm, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, l, apose );
+ }
+
+ /* air */
+ skeleton_sample_anim( sk, w->anim_jump, vg.time*0.6f, bpose );
+ skeleton_lerp_pose( sk, apose, bpose, animator->fly, apose );
+
+ mdl_keyframe *kf_board = &apose[localplayer.id_board-1];
+ f32 yaw = animator->board_yaw;
+
+ if( animator->activity == k_walk_activity_ipopoff )
+ if( animator->transition_t > 0.5f )
+ yaw += 1.0f;
+
+ v4f qyaw;
+ q_axis_angle( qyaw, (v3f){0,1,0}, yaw * VG_TAUf * 0.5f );
+ q_mul( kf_board->q, qyaw, kf_board->q );
+ q_normalize( kf_board->q );
+
+ /* sit */
+ if( (animator->activity == k_walk_activity_sit) ||
+ (animator->activity == k_walk_activity_sit_up) )
+ {
+ skeleton_copy_pose( sk, apose, pose->keyframes );
+ player_walk_pose_sit( animator, pose );
+ }
+ else if( animator->activity == k_walk_activity_odrop_in ){
+ player_walk_pose_transition(
+ animator, w->anim_drop_in, k_walk_transition_out, apose,
+ NULL, pose );
+ }
+ else if( animator->activity == k_walk_activity_oair ){
+ player_walk_pose_transition(
+ animator, w->anim_jump_to_air, k_walk_transition_out, apose,
+ NULL, pose );
+ }
+ else if( animator->activity == k_walk_activity_oregular ){
+ player_walk_pose_transition(
+ animator, w->anim_intro, k_walk_transition_out, apose,
+ NULL, pose );
+ }
+ else if( animator->activity == k_walk_activity_ipopoff ){
+ if( animator->run > 0.2f ){
+ f32 t = 1.0f-vg_minf( animator->run-0.2f, 1.0f ),
+ mask[ 32 ];
+
+ for( u32 i=0; i<32; i ++ )
+ mask[i] = 1.0f;
+
+ mask[ localplayer.id_ik_foot_l-1 ] = t;
+ mask[ localplayer.id_ik_foot_r-1 ] = t;
+ mask[ localplayer.id_ik_knee_l-1 ] = t;
+ mask[ localplayer.id_ik_knee_r-1 ] = t;
+ mask[ localplayer.id_hip-1 ] = t;
+ player_walk_pose_transition(
+ animator, w->anim_popoff, k_walk_transition_in, apose,
+ mask, pose );
+ }
+ else{
+ player_walk_pose_transition(
+ animator, w->anim_popoff, k_walk_transition_in, apose,
+ NULL, pose );
+ }
+ }
+ else {
+ skeleton_copy_pose( sk, apose, pose->keyframes );
+ }
+}
+
+void player__walk_post_animate(void){
+ /*
+ * Camera
+ */
+ struct player_walk *w = &player_walk;
+
+}
+
+void player__walk_im_gui( ui_context *ctx )
+{
+ struct player_walk *w = &player_walk;
+ player__debugtext( ctx, 1, "V: %5.2f %5.2f %5.2f (%5.2fm/s)",
+ localplayer.rb.v[0], localplayer.rb.v[1], localplayer.rb.v[2],
+ v3_length(localplayer.rb.v) );
+ player__debugtext( ctx,
+ 1, "CO: %5.2f %5.2f %5.2f",localplayer.rb.co[0],
+ localplayer.rb.co[1],
+ localplayer.rb.co[2] );
+ player__debugtext( ctx, 1, "transition: %5.2f ", w->state.transition_t );
+ player__debugtext( ctx, 1, "activity: %s\n",
+ (const char *[]){ "air",
+ "ground",
+ "sit",
+ "sit_up",
+ "inone",
+ "ipopoff",
+ "oair",
+ "odrop_in",
+ "oregular" }
+ [w->state.activity] );
+ player__debugtext( ctx, 1, "surface: %s\n",
+ (const char *[]){ "concrete",
+ "wood",
+ "grass",
+ "tiles",
+ "metal",
+ "snow",
+ "sand" }
+ [w->surface] );
+}
+
+void player__walk_bind(void){
+ struct player_walk *w = &player_walk;
+ struct skeleton *sk = &localplayer.skeleton;
+
+ w->anim_idle = skeleton_get_anim( sk, "idle_cycle+y" );
+ w->anim_walk = skeleton_get_anim( sk, "walk+y" );
+ w->anim_run = skeleton_get_anim( sk, "run+y" );
+ w->anim_jump = skeleton_get_anim( sk, "jump+y" );
+ w->anim_jump_to_air = skeleton_get_anim( sk, "jump_to_air" );
+ w->anim_drop_in = skeleton_get_anim( sk, "drop_in" );
+ w->anim_intro = skeleton_get_anim( sk, "into_skate" );
+ w->anim_sit = skeleton_get_anim( sk, "sit" );
+ w->anim_popoff = skeleton_get_anim( sk, "pop_off_short" );
+}
+
+void player__walk_transition( bool grounded, f32 board_yaw ){
+ struct player_walk *w = &player_walk;
+ w->state.activity = k_walk_activity_air;
+
+ if( grounded ){
+ w->state.activity = k_walk_activity_ipopoff;
+ }
+
+ w->state.transition_t = 0.0f;
+ w->state.jump_queued = 0;
+ w->state.jump_input_time = 0.0;
+ w->state.walk_timer = 0.0f;
+ w->state.step_phase = 0;
+ w->animator.board_yaw = fmodf( board_yaw, 2.0f );
+ rb_update_matrices( &localplayer.rb );
+}
+
+void player__walk_reset(void)
+{
+ struct player_walk *w = &player_walk;
+ w->state.activity = k_walk_activity_air;
+ w->state.transition_t = 0.0f;
+
+ v3f fwd = { 0.0f, 0.0f, 1.0f };
+ q_mulv( localplayer.rb.q, fwd, fwd );
+ q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
+ atan2f(fwd[0], fwd[2]) );
+
+ rb_update_matrices( &localplayer.rb );
+}
+
+void player__walk_animator_exchange( bitpack_ctx *ctx, void *data ){
+ struct player_walk_animator *animator = data;
+
+ bitpack_qv3f( ctx, 24, -1024.0f, 1024.0f, animator->root_co );
+ bitpack_qquat( ctx, animator->root_q );
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->fly );
+ bitpack_qf32( ctx, 8, 0.0f, k_runspeed, &animator->run );
+ bitpack_qf32( ctx, 16, 0.0f, 120.0f, &animator->walk_timer );
+
+ for( int i=0; i<1; i++ ){ /* without this you get a warning from gcc. lol */
+ bitpack_bytes( ctx, 8, &animator->activity );
+ }
+
+ bitpack_qf32( ctx, 8, 0.0f, 1.0f, &animator->transition_t );
+
+ if( (animator->activity == k_walk_activity_sit) ||
+ (animator->activity == k_walk_activity_sit_up) ){
+ bitpack_qf32( ctx, 8, -k_sit_yaw_limit, k_sit_yaw_limit, &animator->yaw );
+ bitpack_qf32( ctx, 8, -k_sit_pitch_limit, k_sit_pitch_limit,
+ &animator->pitch );
+ }
+
+ bitpack_qf32( ctx, 16, -100.0f, 100.0f, &animator->board_yaw );
+}
+
+void player__walk_sfx_oneshot( u8 id, v3f pos, f32 volume )
+{
+ audio_lock();
+
+ if( id == k_player_walk_soundeffect_splash ){
+ audio_oneshot_3d( &audio_splash, pos, 40.0f, 1.0f );
+ }
+
+ audio_unlock();
+}
--- /dev/null
+#pragma once
+#include "player.h"
+#include "player_api.h"
+#include "vg/vg_rigidbody.h"
+
+#define PLAYER_JUMP_EPSILON 0.1 /* 100ms jump allowance */
+
+struct player_walk
+{
+ rb_capsule collider;
+
+ struct player_walk_state{
+ v3f prev_pos;
+ v3f drop_in_target,
+ drop_in_start,
+ drop_in_normal;
+
+ float drop_in_start_angle,
+ drop_in_angle;
+
+ enum walk_activity{
+ k_walk_activity_air,
+ k_walk_activity_ground,
+ k_walk_activity_sit,
+ k_walk_activity_sit_up,
+
+ /* transitions */
+ k_walk_activity_inone,
+ k_walk_activity_ipopoff,
+ k_walk_activity_oair,
+ k_walk_activity_odrop_in,
+ k_walk_activity_oregular,
+
+ k_walk_activity_max,
+ }
+ activity;
+
+ f32 transition_t;
+
+ int jump_queued;
+ f64 jump_input_time;
+
+ f32 walk_timer;
+ int step_phase;
+ v3f steer;
+ }
+ state;
+
+ f32 move_speed;
+
+ enum mdl_surface_prop surface;
+ struct skeleton_anim *anim_walk, *anim_run, *anim_idle, *anim_jump,
+ *anim_jump_to_air, *anim_drop_in, *anim_intro,
+ *anim_sit, *anim_popoff;
+
+ struct player_walk_animator {
+ v3f root_co;
+ v4f root_q;
+ f32 fly,
+ run,
+ walk;
+
+ f32 walk_timer, yaw, pitch, board_yaw;
+
+ enum walk_activity activity;
+ f32 transition_t;
+ }
+ animator;
+}
+extern player_walk;
+extern struct player_subsystem_interface player_subsystem_walk;
+
+enum player_walk_soundeffect {
+ k_player_walk_soundeffect_splash
+};
+
+static f32
+ k_walkspeed = 4.4f,
+ k_runspeed = 10.0f,
+ k_airspeed = 1.2f,
+ k_stopspeed = 4.0f,
+ k_walk_accel = 10.0f,
+ k_walk_air_accel = 7.0f,
+ k_walk_friction = 6.0f,
+ k_walk_step_height = 0.2f,
+
+ k_sit_yaw_limit = VG_PIf/1.7f,
+ k_sit_pitch_limit = VG_PIf/4.0f;
+
+static void player__walk_register(void)
+{
+ VG_VAR_F32( k_walkspeed, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_runspeed, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_stopspeed, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_airspeed, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_walk_friction, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_walk_air_accel, flags=VG_VAR_CHEAT );
+ VG_VAR_F32( k_walk_accel, flags=VG_VAR_CHEAT );
+}
+
+void player__walk_pre_update (void);
+void player__walk_update (void);
+void player__walk_post_update (void);
+void player__walk_animate (void);
+void player__walk_pose (void *animator, player_pose *pose);
+void player__walk_post_animate(void);
+void player__walk_im_gui ( ui_context *ctx );
+void player__walk_bind (void);
+void player__walk_reset (void);
+void player__walk_restore (void);
+void player__walk_animator_exchange( bitpack_ctx *ctx, void *data );
+void player__walk_transition( bool grounded, f32 board_yaw );
+void player__walk_sfx_oneshot( u8 id, v3f pos, f32 volume );
--- /dev/null
+#include "render.h"
+#include "vg/vg_engine.h"
+#include "vg/vg_platform.h"
+#include "vg/vg_framebuffer.h"
+
+static void async_render_init( void *payload, u32 size )
+{
+ f32 rh = 0x1p-4f, ih = 0.3f;
+
+ float quad[] = {
+ 0.00f,0.00f, 1.00f,1.00f, 0.00f,1.00f, /* fsquad */
+ 0.00f,0.00f, 1.00f,0.00f, 1.00f,1.00f,
+
+ 0.00f,0.00f, 1.00f,rh, 0.00f,rh, /* fsquad1 */
+ 0.00f,0.00f, 1.00f,0.00f, 1.00f,rh,
+ 0.00f,1.00f, 0.00f,1.0f-rh,1.00f,1.0f-rh,
+ 0.00f,1.00f, 1.00f,1.0f-rh,1.00f,1.0f,
+
+ /* 9x9 debug grid */
+ /* row0 */
+ 0.00f,0.00f, 0.30f,0.30f, 0.00f,0.30f,
+ 0.00f,0.00f, 0.30f,0.00f, 0.30f,0.30f,
+ 0.30f,0.00f, 0.60f,0.30f, 0.30f,0.30f,
+ 0.30f,0.00f, 0.60f,0.00f, 0.60f,0.30f,
+ 0.60f,0.00f, 0.90f,0.30f, 0.60f,0.30f,
+ 0.60f,0.00f, 0.90f,0.00f, 0.90f,0.30f,
+ /* row1 */
+ 0.00f,0.30f, 0.30f,0.60f, 0.00f,0.60f,
+ 0.00f,0.30f, 0.30f,0.30f, 0.30f,0.60f,
+ 0.30f,0.30f, 0.60f,0.60f, 0.30f,0.60f,
+ 0.30f,0.30f, 0.60f,0.30f, 0.60f,0.60f,
+ 0.60f,0.30f, 0.90f,0.60f, 0.60f,0.60f,
+ 0.60f,0.30f, 0.90f,0.30f, 0.90f,0.60f,
+ /* row2 */
+ 0.00f,0.60f, 0.30f,0.90f, 0.00f,0.90f,
+ 0.00f,0.60f, 0.30f,0.60f, 0.30f,0.90f,
+ 0.30f,0.60f, 0.60f,0.90f, 0.30f,0.90f,
+ 0.30f,0.60f, 0.60f,0.60f, 0.60f,0.90f,
+ 0.60f,0.60f, 0.90f,0.90f, 0.60f,0.90f,
+ 0.60f,0.60f, 0.90f,0.60f, 0.90f,0.90f,
+
+ 0.00f,ih, 1.00f,ih+rh, 0.00f,ih+rh, /* fsquad2 */
+ 0.00f,ih, 1.00f,ih, 1.00f,ih+rh,
+ };
+
+ glGenVertexArrays( 1, &g_render.fsquad.vao );
+ glGenBuffers( 1, &g_render.fsquad.vbo );
+ glBindVertexArray( g_render.fsquad.vao );
+ glBindBuffer( GL_ARRAY_BUFFER, g_render.fsquad.vbo );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW );
+ glBindVertexArray( g_render.fsquad.vao );
+ glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE,
+ sizeof(float)*2, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ g_render.ready = 1;
+}
+
+void render_init(void)
+{
+ vg_console_reg_var( "blur_strength", &k_blur_strength, k_var_dtype_f32, 0 );
+ vg_console_reg_var( "render_scale", &k_render_scale,
+ k_var_dtype_f32, VG_VAR_PERSISTENT );
+ vg_console_reg_var( "fov", &k_fov, k_var_dtype_f32, VG_VAR_PERSISTENT );
+ vg_console_reg_var( "cam_height", &k_cam_height,
+ k_var_dtype_f32, VG_VAR_PERSISTENT );
+ vg_console_reg_var( "blur_effect", &k_blur_effect,
+ k_var_dtype_i32, VG_VAR_PERSISTENT );
+
+ void *alloc = vg_mem.rtmemory;
+
+ /*
+ * Main framebuffer
+ */
+ g_render.fb_main = vg_framebuffer_allocate( alloc, 3, 1 );
+ g_render.fb_main->display_name = "main";
+ g_render.fb_main->resolution_div = 1;
+ g_render.fb_main->attachments[0] = (vg_framebuffer_attachment)
+ {
+ "colour", k_framebuffer_attachment_type_texture,
+
+ .internalformat = GL_RGB,
+ .format = GL_RGB,
+ .type = GL_UNSIGNED_BYTE,
+ .attachment = GL_COLOR_ATTACHMENT0
+ };
+ g_render.fb_main->attachments[1] = (vg_framebuffer_attachment)
+ {
+ "motion", k_framebuffer_attachment_type_texture,
+
+ .quality = k_framebuffer_quality_high_only,
+ .internalformat = GL_RG16F,
+ .format = GL_RG,
+ .type = GL_FLOAT,
+ .attachment = GL_COLOR_ATTACHMENT1
+ };
+ g_render.fb_main->attachments[2] = (vg_framebuffer_attachment)
+ {
+ "depth_stencil", k_framebuffer_attachment_type_texture_depth,
+ .internalformat = GL_DEPTH24_STENCIL8,
+ .format = GL_DEPTH_STENCIL,
+ .type = GL_UNSIGNED_INT_24_8,
+ .attachment = GL_DEPTH_STENCIL_ATTACHMENT
+ };
+ vg_framebuffer_create( g_render.fb_main );
+
+ /*
+ * Water reflection
+ */
+ g_render.fb_water_reflection = vg_framebuffer_allocate( alloc, 2, 1 );
+ g_render.fb_water_reflection->display_name = "water_reflection";
+ g_render.fb_water_reflection->resolution_div = 2;
+ g_render.fb_water_reflection->attachments[0] = (vg_framebuffer_attachment)
+ {
+ "colour", k_framebuffer_attachment_type_texture,
+ .internalformat = GL_RGB,
+ .format = GL_RGB,
+ .type = GL_UNSIGNED_BYTE,
+ .attachment = GL_COLOR_ATTACHMENT0
+ };
+ g_render.fb_water_reflection->attachments[1] = (vg_framebuffer_attachment)
+ {
+ "depth_stencil", k_framebuffer_attachment_type_renderbuffer,
+ .internalformat = GL_DEPTH24_STENCIL8,
+ .attachment = GL_DEPTH_STENCIL_ATTACHMENT
+ };
+ vg_framebuffer_create( g_render.fb_water_reflection );
+
+ /*
+ * Thid rendered view from the perspective of the camera, but just
+ * captures stuff thats under the water
+ */
+ g_render.fb_water_beneath = vg_framebuffer_allocate( alloc, 2, 1 );
+ g_render.fb_water_beneath->display_name = "water_beneath";
+ g_render.fb_water_beneath->resolution_div = 2;
+ g_render.fb_water_beneath->attachments[0] = (vg_framebuffer_attachment)
+ {
+ "colour", k_framebuffer_attachment_type_texture,
+ .internalformat = GL_RED,
+ .format = GL_RED,
+ .type = GL_UNSIGNED_BYTE,
+ .attachment = GL_COLOR_ATTACHMENT0
+ };
+ g_render.fb_water_beneath->attachments[1] = (vg_framebuffer_attachment)
+ {
+ "depth_stencil", k_framebuffer_attachment_type_renderbuffer,
+ .internalformat = GL_DEPTH24_STENCIL8,
+ .attachment = GL_DEPTH_STENCIL_ATTACHMENT
+ };
+ vg_framebuffer_create( g_render.fb_water_beneath );
+
+ /*
+ * Workshop preview
+ */
+ g_render.fb_workshop_preview = vg_framebuffer_allocate( alloc, 2, 1 );
+ g_render.fb_workshop_preview->display_name = "workshop_preview";
+ g_render.fb_workshop_preview->resolution_div = 0;
+ g_render.fb_workshop_preview->fixed_w = WORKSHOP_PREVIEW_WIDTH;
+ g_render.fb_workshop_preview->fixed_h = WORKSHOP_PREVIEW_HEIGHT;
+ g_render.fb_workshop_preview->attachments[0] = (vg_framebuffer_attachment)
+ {
+ "colour", k_framebuffer_attachment_type_texture,
+ .internalformat = GL_RGB,
+ .format = GL_RGB,
+ .type = GL_UNSIGNED_BYTE,
+ .attachment = GL_COLOR_ATTACHMENT0
+ };
+ g_render.fb_workshop_preview->attachments[1] = (vg_framebuffer_attachment)
+ {
+ "depth_stencil", k_framebuffer_attachment_type_renderbuffer,
+ .internalformat = GL_DEPTH24_STENCIL8,
+ .attachment = GL_DEPTH_STENCIL_ATTACHMENT
+ };
+ vg_framebuffer_create( g_render.fb_workshop_preview );
+
+ /*
+ * Network status
+ */
+ g_render.fb_network_status = vg_framebuffer_allocate( alloc, 1, 1 );
+ g_render.fb_network_status->display_name = "network_status_ui";
+ g_render.fb_network_status->resolution_div = 0;
+ g_render.fb_network_status->fixed_w = 128;
+ g_render.fb_network_status->fixed_h = 48;
+ g_render.fb_network_status->attachments[0] = (vg_framebuffer_attachment)
+ {
+ "colour", k_framebuffer_attachment_type_texture,
+ .internalformat = GL_RGB,
+ .format = GL_RGB,
+ .type = GL_UNSIGNED_BYTE,
+ .attachment = GL_COLOR_ATTACHMENT0
+ };
+ vg_framebuffer_create( g_render.fb_network_status );
+
+ vg_async_call( async_render_init, NULL, 0 );
+}
+
+/*
+ * Utility
+ */
+void render_fsquad(void)
+{
+ glBindVertexArray( g_render.fsquad.vao );
+ glDrawArrays( GL_TRIANGLES, 0, 6 );
+}
+
+void render_fsquad1(void)
+{
+ glBindVertexArray( g_render.fsquad.vao );
+ glDrawArrays( GL_TRIANGLES, 6, 6+6 );
+}
+
+void render_fsquad2(void)
+{
+ glBindVertexArray( g_render.fsquad.vao );
+ glDrawArrays( GL_TRIANGLES, 66+6,6 );
+}
+
+void postprocess_to_screen( vg_framebuffer *fb )
+{
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ glViewport( 0,0, vg.window_x, vg.window_y );
+
+ glEnable(GL_BLEND);
+ glDisable(GL_DEPTH_TEST);
+ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
+ glBlendEquation(GL_FUNC_ADD);
+
+ v2f inverse;
+ vg_framebuffer_inverse_ratio( fb, inverse );
+
+ if( k_blur_effect )
+ {
+ shader_blitblur_use();
+ shader_blitblur_uTexMain( 0 );
+ shader_blitblur_uTexMotion( 1 );
+ shader_blitblur_uBlurStrength( k_blur_strength /
+ (vg.time_frame_delta*60.0) );
+ shader_blitblur_uInverseRatio( inverse );
+
+ inverse[0] -= 0.0001f;
+ inverse[1] -= 0.0001f;
+ shader_blitblur_uClampUv( inverse );
+ shader_blitblur_uOverrideDir( g_render.blur_override );
+
+ vg_framebuffer_bind_texture( fb, 0, 0 );
+ vg_framebuffer_bind_texture( fb, 1, 1 );
+ }
+ else
+ {
+ shader_blit_use();
+ shader_blit_uTexMain( 0 );
+ shader_blit_uInverseRatio( inverse );
+ vg_framebuffer_bind_texture( fb, 0, 0 );
+ }
+
+ render_fsquad();
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+#pragma once
+#include "common.h"
+#include "model.h"
+#include "shader_props.h"
+#include "vg/vg_framebuffer.h"
+#include "vg/vg_camera.h"
+
+#include "shaders/blit.h"
+#include "shaders/blitblur.h"
+#include "shaders/blitcolour.h"
+#include "shaders/blit_transition.h"
+
+#define WORKSHOP_PREVIEW_WIDTH 504
+#define WORKSHOP_PREVIEW_HEIGHT 336
+
+static f32 k_render_scale = 1.0f;
+static i32 k_blur_effect = 1;
+static f32 k_blur_strength = 0.3f;
+static f32 k_fov = 0.86f;
+static f32 k_cam_height = 0.8f;
+
+/*
+ * All standard buffers used in rendering
+ */
+struct pipeline
+{
+ glmesh fsquad;
+
+ vg_framebuffer *fb_main,
+ *fb_water_reflection,
+ *fb_water_beneath,
+ *fb_workshop_preview,
+ *fb_network_status;
+ int ready;
+
+ v2f blur_override;
+ vg_camera cam;
+}
+static g_render;
+
+void render_init(void);
+void render_fsquad(void);
+void render_fsquad1(void);
+void render_fsquad2(void);
+void postprocess_to_screen( vg_framebuffer *fb );
--- /dev/null
+#include "skaterift.h"
+#include "save.h"
+#include "addon.h"
+#include "vg/vg_msg.h"
+#include "vg/vg_log.h"
+#include "vg/vg_loader.h"
+#include "world.h"
+#include "player.h"
+
+static const char *str_skaterift_main_save = "save.bkv";
+static f64 last_autosave;
+
+void savedata_file_write( savedata_file *file )
+{
+ savedata_file *sav = file;
+ FILE *fp = fopen( sav->path, "wb" );
+ if( fp ){
+ fwrite( sav->buf, sav->len, 1, fp );
+ fclose( fp );
+ vg_success( "savedata written to '%s'\n", sav->path );
+ }
+ else {
+ vg_error( "Error writing savedata (%s)\n", sav->path );
+ }
+}
+
+void savedata_group_write( savedata_group *group )
+{
+ for( u32 i=0; i<group->file_count; i++ ){
+ savedata_file_write( &group->files[i] );
+ }
+}
+
+void savedata_file_read( savedata_file *file )
+{
+ FILE *fp = fopen( file->path, "rb" );
+ if( fp ){
+ file->len = fread( file->buf, 1, sizeof(file->buf), fp );
+ fclose( fp );
+ }
+ else{
+ file->len = 0;
+ vg_warn( "Error reading savedata (%s)\n", file->path );
+ }
+}
+
+static void skaterift_write_addon_alias( vg_msg *msg, const char *key,
+ addon_alias *alias ){
+ if( alias->workshop_id )
+ vg_msg_wkvnum( msg, key, k_vg_msg_u64, 1, &alias->workshop_id );
+ else
+ vg_msg_wkvstr( msg, key, alias->foldername );
+}
+
+static void skaterift_write_viewslot( vg_msg *msg, const char *key,
+ enum addon_type type, u16 cache_id ){
+ if( !cache_id ) return;
+
+ struct addon_cache *cache = &addon_system.cache[type];
+ addon_cache_entry *entry = vg_pool_item( &cache->pool, cache_id );
+ addon_reg *reg = entry->reg_ptr;
+
+ if( reg )
+ skaterift_write_addon_alias( msg, key, ®->alias );
+}
+
+void skaterift_read_addon_alias( vg_msg *msg, const char *key,
+ enum addon_type type,
+ addon_alias *alias )
+{
+ alias->foldername[0] = '\0';
+ alias->workshop_id = 0;
+ alias->type = type;
+
+ vg_msg_cmd kv;
+ if( vg_msg_getkvcmd( msg, key, &kv ) ){
+ if( kv.code == k_vg_msg_kvstring ){
+ vg_strncpy( kv.value, alias->foldername, sizeof(alias->foldername),
+ k_strncpy_allow_cutoff );
+ }
+ else
+ vg_msg_cast( kv.value, kv.code, &alias->workshop_id, k_vg_msg_u64 );
+ }
+}
+
+static void skaterift_populate_world_savedata( savedata_file *file,
+ enum world_purpose which ){
+ file->path[0] = '\0';
+ file->len = 0;
+ addon_reg *reg = world_static.instance_addons[ which ];
+
+ if( !reg ){
+ vg_error( "Tried to save unspecified world (reg was null)\n" );
+ return;
+ }
+
+ skaterift_world_get_save_path( which, file->path );
+
+ vg_msg sav;
+ vg_msg_init( &sav, file->buf, sizeof(file->buf) );
+
+ world_instance *instance = &world_static.instances[which];
+ world_entity_serialize( instance, &sav );
+
+ vg_msg_frame( &sav, "player" );
+ {
+ vg_msg_wkvnum( &sav, "position", k_vg_msg_float|k_vg_msg_32b, 3,
+ (which == world_static.active_instance)?
+ localplayer.rb.co:
+ instance->player_co );
+ }
+ vg_msg_end_frame( &sav );
+
+ file->len = sav.cur.co;
+}
+
+static void skaterift_populate_main_savedata( savedata_file *file )
+{
+ strcpy( file->path, str_skaterift_main_save );
+
+ vg_msg sav;
+ vg_msg_init( &sav, file->buf, sizeof(file->buf) );
+ vg_msg_wkvnum( &sav, "ach", k_vg_msg_u32, 1, &skaterift.achievements );
+
+ vg_msg_frame( &sav, "player" );
+ {
+ skaterift_write_viewslot( &sav, "board", k_addon_type_board,
+ localplayer.board_view_slot );
+ skaterift_write_viewslot( &sav, "playermodel", k_addon_type_player,
+ localplayer.playermodel_view_slot );
+ }
+ vg_msg_end_frame( &sav );
+
+ file->len = sav.cur.co;
+}
+
+void skaterift_read_main_savedata( savedata_file *file )
+{
+ strcpy( file->path, str_skaterift_main_save );
+ savedata_file_read( file );
+}
+
+int skaterift_autosave( int async )
+{
+ if( async )
+ if( !vg_loader_availible() ) return 0;
+
+ u32 save_files = 2;
+ if( world_static.instances[k_world_purpose_client].status
+ == k_world_status_loaded ){
+ save_files ++;
+ }
+
+ vg_linear_clear( vg_async.buffer );
+ u32 size = sizeof(savedata_group) + sizeof(savedata_file) * save_files;
+
+ savedata_group *group;
+ if( async ){
+ size = vg_align8( size );
+ group = vg_linear_alloc( vg_async.buffer, size );
+ }
+ else
+ group = alloca( size );
+
+ group->file_count = save_files;
+ skaterift_populate_main_savedata( &group->files[0] );
+ skaterift_populate_world_savedata( &group->files[1], k_world_purpose_hub );
+
+ if( world_static.instances[ k_world_purpose_client ].status
+ == k_world_status_loaded ){
+ skaterift_populate_world_savedata( &group->files[2],
+ k_world_purpose_client );
+ }
+
+ if( async )
+ vg_loader_start( (void *)savedata_group_write, group );
+ else
+ savedata_group_write( group );
+
+ return 1;
+}
+
+void skaterift_autosave_synchronous(void)
+{
+ skaterift_autosave(0);
+}
+
+void skaterift_autosave_update(void)
+{
+ if( vg.time - last_autosave > 20.0 ){
+ if( skaterift_autosave(1) ){
+ last_autosave = vg.time;
+ }
+ }
+}
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+#include "vg/vg_msg.h"
+#include "addon.h"
+
+typedef struct savedata_file savedata_file;
+typedef struct savedata_group savedata_group;
+
+struct savedata_group {
+ u32 file_count;
+ struct savedata_file {
+ char path[128];
+ u8 buf[2048];
+ u32 len;
+ }
+ files[];
+};
+
+void savedata_file_read( savedata_file *file );
+void savedata_file_write( savedata_file *file );
+void savedata_group_write( savedata_group *group );
+int skaterift_autosave(int async);
+void skaterift_autosave_synchronous(void);
+void skaterift_autosave_update(void);
+void skaterift_read_addon_alias( vg_msg *msg, const char *key,
+ enum addon_type type,
+ addon_alias *alias );
+
+void skaterift_read_main_savedata( savedata_file *file );
--- /dev/null
+#include "scene.h"
+
+u32 scene_mem_required( scene_context *ctx )
+{
+ u32 vertex_length = vg_align8(ctx->max_vertices * sizeof(scene_vert)),
+ index_length = vg_align8(ctx->max_indices * sizeof(u32));
+
+ return vertex_length + index_length;
+}
+
+void scene_init( scene_context *ctx, u32 max_vertices, u32 max_indices )
+{
+ ctx->vertex_count = 0;
+ ctx->indice_count = 0;
+ ctx->max_vertices = max_vertices;
+ ctx->max_indices = max_indices;
+ ctx->arrindices = NULL; /* must be filled out by user */
+ ctx->arrvertices = NULL;
+
+ memset( &ctx->submesh, 0, sizeof(mdl_submesh) );
+
+ v3_fill( ctx->bbx[0], 999999.9f );
+ v3_fill( ctx->bbx[1], -999999.9f );
+}
+
+void scene_supply_buffer( scene_context *ctx, void *buffer )
+{
+ u32 vertex_length = vg_align8( ctx->max_vertices * sizeof(scene_vert) );
+
+ ctx->arrvertices = buffer;
+ ctx->arrindices = (u32*)(((u8*)buffer) + vertex_length);
+}
+
+void scene_vert_pack_norm( scene_vert *vert, v3f norm, f32 blend )
+{
+ v3f n;
+ v3_muls( norm, 127.0f, n );
+ v3_minv( n, (v3f){ 127.0f, 127.0f, 127.0f }, n );
+ v3_maxv( n, (v3f){ -127.0f, -127.0f, -127.0f }, n );
+ vert->norm[0] = n[0];
+ vert->norm[1] = n[1];
+ vert->norm[2] = n[2];
+ vert->norm[3] = blend * 127.0f;
+}
+
+/*
+ * Append a model into the scene with a given transform
+ */
+void scene_add_mdl_submesh( scene_context *ctx, mdl_context *mdl,
+ mdl_submesh *sm, m4x3f transform )
+{
+ if( ctx->vertex_count + sm->vertex_count > ctx->max_vertices ){
+ vg_fatal_error( "Scene vertex buffer overflow (%u exceeds %u)\n",
+ ctx->vertex_count + sm->vertex_count,
+ ctx->max_vertices );
+ }
+
+ if( ctx->indice_count + sm->indice_count > ctx->max_indices ){
+ vg_fatal_error( "Scene index buffer overflow (%u exceeds %u)\n",
+ ctx->indice_count + sm->indice_count,
+ ctx->max_indices );
+ }
+
+ mdl_vert *src_verts = mdl_arritm( &mdl->verts, sm->vertex_start );
+ scene_vert *dst_verts = &ctx->arrvertices[ ctx->vertex_count ];
+
+ u32 *src_indices = mdl_arritm( &mdl->indices, sm->indice_start ),
+ *dst_indices = &ctx->arrindices[ ctx->indice_count ];
+
+ /* Transform and place vertices */
+ boxf bbxnew;
+ box_init_inf( bbxnew );
+ m4x3_expand_aabb_aabb( transform, bbxnew, sm->bbx );
+ box_concat( ctx->bbx, bbxnew );
+
+ m3x3f normal_matrix;
+ m3x3_copy( transform, normal_matrix );
+ v3_normalize( normal_matrix[0] );
+ v3_normalize( normal_matrix[1] );
+ v3_normalize( normal_matrix[2] );
+
+ for( u32 i=0; i<sm->vertex_count; i++ ){
+ mdl_vert *src = &src_verts[i];
+ scene_vert *pvert = &dst_verts[i];
+
+ m4x3_mulv( transform, src->co, pvert->co );
+
+ v3f normal;
+ m3x3_mulv( normal_matrix, src->norm, normal );
+ scene_vert_pack_norm( pvert, normal, src->colour[0]*(1.0f/255.0f) );
+
+ v2_copy( src->uv, pvert->uv );
+ }
+
+ u32 real_indices = 0;
+ for( u32 i=0; i<sm->indice_count/3; i++ ){
+ u32 *src = &src_indices[i*3],
+ *dst = &dst_indices[real_indices];
+
+ v3f ab, ac, tn;
+ v3_sub( src_verts[src[2]].co, src_verts[src[0]].co, ab );
+ v3_sub( src_verts[src[1]].co, src_verts[src[0]].co, ac );
+ v3_cross( ac, ab, tn );
+
+#if 0
+ if( v3_length2( tn ) <= 0.00001f )
+ continue;
+#endif
+
+ dst[0] = src[0] + ctx->vertex_count;
+ dst[1] = src[1] + ctx->vertex_count;
+ dst[2] = src[2] + ctx->vertex_count;
+
+ real_indices += 3;
+ }
+
+ if( real_indices != sm->indice_count )
+ vg_warn( "Zero area triangles in model\n" );
+
+ ctx->vertex_count += sm->vertex_count;
+ ctx->indice_count += real_indices;
+}
+
+/*
+ * One by one adders for simplified access (mostly procedural stuff)
+ */
+void scene_push_tri( scene_context *ctx, u32 tri[3] )
+{
+ if( ctx->indice_count + 3 > ctx->max_indices )
+ vg_fatal_error( "Scene indice buffer overflow (%u exceeds %u)\n",
+ ctx->indice_count+3, ctx->max_indices );
+
+ u32 *dst = &ctx->arrindices[ ctx->indice_count ];
+
+ dst[0] = tri[0];
+ dst[1] = tri[1];
+ dst[2] = tri[2];
+
+ ctx->indice_count += 3;
+}
+
+void scene_push_vert( scene_context *ctx, scene_vert *v )
+{
+ if( ctx->vertex_count + 1 > ctx->max_vertices )
+ vg_fatal_error( "Scene vertex buffer overflow (%u exceeds %u)\n",
+ ctx->vertex_count+1, ctx->max_vertices );
+
+ scene_vert *dst = &ctx->arrvertices[ ctx->vertex_count ];
+ *dst = *v;
+
+ ctx->vertex_count ++;
+}
+
+void scene_copy_slice( scene_context *ctx, mdl_submesh *sm )
+{
+ sm->indice_start = ctx->submesh.indice_start;
+ sm->indice_count = ctx->indice_count - sm->indice_start;
+
+ sm->vertex_start = ctx->submesh.vertex_start;
+ sm->vertex_count = ctx->vertex_count - sm->vertex_start;
+
+ ctx->submesh.indice_start = ctx->indice_count;
+ ctx->submesh.vertex_start = ctx->vertex_count;
+}
+
+void scene_set_vertex_flags( scene_context *ctx,
+ u32 start, u32 count, u16 flags )
+{
+ for( u32 i=0; i<count; i++ )
+ ctx->arrvertices[ start + i ].flags = flags;
+}
+
+struct scene_upload_info{
+ scene_context *ctx;
+ glmesh *mesh;
+};
+
+void async_scene_upload( void *payload, u32 size )
+{
+ struct scene_upload_info *info = payload;
+
+ //assert( mesh->loaded == 0 );
+
+ glmesh *mesh = info->mesh;
+ scene_context *ctx = info->ctx;
+
+ glGenVertexArrays( 1, &mesh->vao );
+ glGenBuffers( 1, &mesh->vbo );
+ glGenBuffers( 1, &mesh->ebo );
+ glBindVertexArray( mesh->vao );
+
+ size_t stride = sizeof(scene_vert);
+
+ glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo );
+ glBufferData( GL_ARRAY_BUFFER, ctx->vertex_count*stride,
+ ctx->arrvertices, GL_STATIC_DRAW );
+
+ glBindVertexArray( mesh->vao );
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER, ctx->indice_count*sizeof(u32),
+ ctx->arrindices, GL_STATIC_DRAW );
+
+ /* 0: coordinates */
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ /* 1: normal */
+ glVertexAttribPointer( 1, 4, GL_BYTE, GL_TRUE,
+ stride, (void *)offsetof(scene_vert, norm) );
+ glEnableVertexAttribArray( 1 );
+
+ /* 2: uv */
+ glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE,
+ stride, (void *)offsetof(scene_vert, uv) );
+ glEnableVertexAttribArray( 2 );
+
+ mesh->indice_count = ctx->indice_count;
+ mesh->loaded = 1;
+
+ vg_info( "Scene upload ( XYZ_f32 UV_f32 XYZW_i8 )[ u32 ]\n" );
+ vg_info( " indices:%u\n", ctx->indice_count );
+ vg_info( " verts:%u\n", ctx->vertex_count );
+}
+
+void scene_upload_async( scene_context *ctx, glmesh *mesh )
+{
+ vg_async_item *call = vg_async_alloc( sizeof(struct scene_upload_info) );
+
+ struct scene_upload_info *info = call->payload;
+ info->mesh = mesh;
+ info->ctx = ctx;
+
+ vg_async_dispatch( call, async_scene_upload );
+}
+
+vg_async_item *scene_alloc_async( scene_context *scene, glmesh *mesh,
+ u32 max_vertices, u32 max_indices )
+{
+ scene_init( scene, max_vertices, max_indices );
+ u32 buf_size = scene_mem_required( scene );
+
+ u32 hdr_size = vg_align8(sizeof(struct scene_upload_info));
+ vg_async_item *call = vg_async_alloc( hdr_size + buf_size );
+
+ struct scene_upload_info *info = call->payload;
+
+ info->mesh = mesh;
+ info->ctx = scene;
+
+ void *buffer = ((u8*)call->payload)+hdr_size;
+ scene_supply_buffer( scene, buffer );
+
+ return call;
+}
+
+
+/*
+ * BVH implementation
+ */
+
+static void scene_bh_expand_bound( void *user, boxf bound, u32 item_index )
+{
+ scene_context *s = user;
+ scene_vert *pa = &s->arrvertices[ s->arrindices[item_index*3+0] ],
+ *pb = &s->arrvertices[ s->arrindices[item_index*3+1] ],
+ *pc = &s->arrvertices[ s->arrindices[item_index*3+2] ];
+
+ box_addpt( bound, pa->co );
+ box_addpt( bound, pb->co );
+ box_addpt( bound, pc->co );
+}
+
+static float scene_bh_centroid( void *user, u32 item_index, int axis )
+{
+ scene_context *s = user;
+ scene_vert *pa = &s->arrvertices[ s->arrindices[item_index*3+0] ],
+ *pb = &s->arrvertices[ s->arrindices[item_index*3+1] ],
+ *pc = &s->arrvertices[ s->arrindices[item_index*3+2] ];
+
+ #if 0
+
+ float min, max;
+
+ min = vg_minf( pa->co[axis], pb->co[axis] );
+ max = vg_maxf( pa->co[axis], pb->co[axis] );
+ min = vg_minf( min, pc->co[axis] );
+ max = vg_maxf( max, pc->co[axis] );
+
+ return (min+max) * 0.5f;
+
+ #else
+ return (pa->co[axis] + pb->co[axis] + pc->co[axis]) * (1.0f/3.0f);
+ #endif
+}
+
+static void scene_bh_swap( void *user, u32 ia, u32 ib )
+{
+ scene_context *s = user;
+
+ u32 *ti = &s->arrindices[ia*3];
+ u32 *tj = &s->arrindices[ib*3];
+
+ u32 temp[3];
+ temp[0] = ti[0];
+ temp[1] = ti[1];
+ temp[2] = ti[2];
+
+ ti[0] = tj[0];
+ ti[1] = tj[1];
+ ti[2] = tj[2];
+
+ tj[0] = temp[0];
+ tj[1] = temp[1];
+ tj[2] = temp[2];
+}
+
+static void scene_bh_debug( void *user, u32 item_index )
+{
+ scene_context *s = user;
+ u32 idx = item_index*3;
+ scene_vert *pa = &s->arrvertices[ s->arrindices[ idx+0 ] ],
+ *pb = &s->arrvertices[ s->arrindices[ idx+1 ] ],
+ *pc = &s->arrvertices[ s->arrindices[ idx+2 ] ];
+
+ vg_line( pa->co, pb->co, 0xff0000ff );
+ vg_line( pb->co, pc->co, 0xff0000ff );
+ vg_line( pc->co, pa->co, 0xff0000ff );
+}
+
+static void scene_bh_closest( void *user, u32 index, v3f point, v3f closest )
+{
+ scene_context *s = user;
+
+ v3f positions[3];
+ u32 *tri = &s->arrindices[ index*3 ];
+ for( int i=0; i<3; i++ )
+ v3_copy( s->arrvertices[tri[i]].co, positions[i] );
+
+ closest_on_triangle_1( point, positions, closest );
+}
+
+bh_system bh_system_scene =
+{
+ .expand_bound = scene_bh_expand_bound,
+ .item_centroid = scene_bh_centroid,
+ .item_closest = scene_bh_closest,
+ .item_swap = scene_bh_swap,
+ .item_debug = scene_bh_debug,
+};
+
+/*
+ * An extra step is added onto the end to calculate the hit normal
+ */
+int scene_raycast( scene_context *s, bh_tree *bh,
+ v3f co, v3f dir, ray_hit *hit, u16 ignore )
+{
+ hit->tri = NULL;
+
+ bh_iter it;
+ bh_iter_init_ray( 0, &it, co, dir, hit->dist );
+ i32 idx;
+
+ while( bh_next( bh, &it, &idx ) ){
+ u32 *tri = &s->arrindices[ idx*3 ];
+
+ if( s->arrvertices[tri[0]].flags & ignore ) continue;
+
+ v3f vs[3];
+ for( u32 i=0; i<3; i++ )
+ v3_copy( s->arrvertices[tri[i]].co, vs[i] );
+
+ f32 t;
+ if( ray_tri( vs, co, dir, &t, 0 ) ){
+ if( t < hit->dist ){
+ hit->dist = t;
+ hit->tri = tri;
+ }
+ }
+ }
+
+ if( hit->tri ){
+ v3f v0, v1;
+
+ float *pa = s->arrvertices[hit->tri[0]].co,
+ *pb = s->arrvertices[hit->tri[1]].co,
+ *pc = s->arrvertices[hit->tri[2]].co;
+
+ v3_sub( pa, pb, v0 );
+ v3_sub( pc, pb, v1 );
+ v3_cross( v1, v0, hit->normal );
+ v3_normalize( hit->normal );
+ v3_muladds( co, dir, hit->dist, hit->pos );
+ }
+
+ return hit->tri?1:0;
+}
+
+bh_tree *scene_bh_create( void *lin_alloc, scene_context *s )
+{
+ u32 triangle_count = s->indice_count / 3;
+ return bh_create( lin_alloc, &bh_system_scene, s, triangle_count, 2 );
+}
--- /dev/null
+#pragma once
+#include "vg/vg_bvh.h"
+#include "vg/vg_async.h"
+#include "common.h"
+#include "model.h"
+
+typedef struct scene_context scene_context;
+typedef struct scene_vert scene_vert;
+
+#pragma pack(push,1)
+
+/* 32 byte vertexs, we don't care about the normals too much,
+ * maybe possible to bring down uv to i16s too */
+struct scene_vert
+{
+ v3f co; /* 3*32 */
+ v2f uv; /* 2*32 */
+ i8 norm[4]; /* 4*8 */
+ u16 flags; /* only for the cpu. its junk on the gpu */
+ u16 unused[3];
+};
+
+#pragma pack(pop)
+
+/*
+ * 1. this should probably be a CONTEXT based approach unlike this mess.
+ * take a bit of the mdl_context ideas and redo this header. its messed up
+ * pretty bad right now.
+ */
+
+struct scene_context
+{
+ scene_vert *arrvertices;
+ u32 *arrindices;
+
+ u32 vertex_count, indice_count,
+ max_vertices, max_indices;
+
+ boxf bbx;
+ mdl_submesh submesh;
+};
+
+extern bh_system bh_system_scene;
+bh_tree *scene_bh_create( void *lin_alloc, scene_context *s );
+int scene_raycast( scene_context *s, bh_tree *bh,
+ v3f co, v3f dir, ray_hit *hit, u16 ignore );
+vg_async_item *scene_alloc_async( scene_context *scene, glmesh *mesh,
+ u32 max_vertices, u32 max_indices );
+void scene_copy_slice( scene_context *ctx, mdl_submesh *sm );
+void scene_push_vert( scene_context *ctx, scene_vert *v );
+void scene_vert_pack_norm( scene_vert *vert, v3f norm, f32 blend );
+void scene_push_tri( scene_context *ctx, u32 tri[3] );
+void scene_add_mdl_submesh( scene_context *ctx, mdl_context *mdl,
+ mdl_submesh *sm, m4x3f transform );
+void scene_set_vertex_flags( scene_context *ctx,
+ u32 start, u32 count, u16 flags );
+void scene_supply_buffer( scene_context *ctx, void *buffer );
+void scene_init( scene_context *ctx, u32 max_vertices, u32 max_indices );
+u32 scene_mem_required( scene_context *ctx );
+void async_scene_upload( void *payload, u32 size );
+void scene_upload_async( scene_context *ctx, glmesh *mesh );
--- /dev/null
+#pragma once
+
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved
+ *
+ * Describes intereactions between vg rigidbody objects and skaterift's scene
+ * description
+ */
+
+#include "scene.h"
+#include "vg/vg_rigidbody.h"
+#include "vg/vg_rigidbody_collision.h"
+
+static int rb_sphere__scene( m4x3f mtxA, f32 r,
+ m4x3f mtxB, bh_tree *scene_bh, rb_ct *buf,
+ u16 ignore ){
+ scene_context *sc = scene_bh->user;
+
+ int count = 0;
+
+ boxf box;
+ v3_sub( mtxA[3], (v3f){ r,r,r }, box[0] );
+ v3_add( mtxA[3], (v3f){ r,r,r }, box[1] );
+
+ bh_iter it;
+ i32 idx;
+ bh_iter_init_box( 0, &it, box );
+
+ while( bh_next( scene_bh, &it, &idx ) ){
+ u32 *ptri = &sc->arrindices[ idx*3 ];
+ v3f tri[3];
+
+ if( sc->arrvertices[ptri[0]].flags & ignore ) continue;
+
+ for( int j=0; j<3; j++ )
+ v3_copy( sc->arrvertices[ptri[j]].co, tri[j] );
+
+ buf[ count ].element_id = ptri[0];
+
+ vg_line( tri[0],tri[1],0x70ff6000 );
+ vg_line( tri[1],tri[2],0x70ff6000 );
+ vg_line( tri[2],tri[0],0x70ff6000 );
+
+ int contact = rb_sphere__triangle( mtxA, r, tri, &buf[count] );
+ count += contact;
+
+ if( count == 16 ){
+ vg_warn( "Exceeding sphere_vs_scene capacity. Geometry too dense!\n" );
+ return count;
+ }
+ }
+
+ return count;
+}
+
+static int rb_box__scene( m4x3f mtxA, boxf bbx,
+ m4x3f mtxB, bh_tree *scene_bh,
+ rb_ct *buf, u16 ignore ){
+ scene_context *sc = scene_bh->user;
+ v3f tri[3];
+
+ v3f extent, center;
+ v3_sub( bbx[1], bbx[0], extent );
+ v3_muls( extent, 0.5f, extent );
+ v3_add( bbx[0], extent, center );
+
+ f32 r = v3_length(extent);
+ boxf world_bbx;
+ v3_fill( world_bbx[0], -r );
+ v3_fill( world_bbx[1], r );
+ for( int i=0; i<2; i++ ){
+ v3_add( center, world_bbx[i], world_bbx[i] );
+ v3_add( mtxA[3], world_bbx[i], world_bbx[i] );
+ }
+
+ m4x3f to_local;
+ m4x3_invert_affine( mtxA, to_local );
+
+ bh_iter it;
+ bh_iter_init_box( 0, &it, world_bbx );
+ int idx;
+ int count = 0;
+
+ vg_line_boxf( world_bbx, VG__RED );
+
+ while( bh_next( scene_bh, &it, &idx ) ){
+ u32 *ptri = &sc->arrindices[ idx*3 ];
+ if( sc->arrvertices[ptri[0]].flags & ignore ) continue;
+
+ for( int j=0; j<3; j++ )
+ v3_copy( sc->arrvertices[ptri[j]].co, tri[j] );
+
+ if( rb_box_triangle_sat( extent, center, to_local, tri ) ){
+ vg_line(tri[0],tri[1],0xff50ff00 );
+ vg_line(tri[1],tri[2],0xff50ff00 );
+ vg_line(tri[2],tri[0],0xff50ff00 );
+ }
+ else{
+ vg_line(tri[0],tri[1],0xff0000ff );
+ vg_line(tri[1],tri[2],0xff0000ff );
+ vg_line(tri[2],tri[0],0xff0000ff );
+ continue;
+ }
+
+ v3f v0,v1,n;
+ v3_sub( tri[1], tri[0], v0 );
+ v3_sub( tri[2], tri[0], v1 );
+ v3_cross( v0, v1, n );
+
+ if( v3_length2( n ) <= 0.00001f ){
+#ifdef RIGIDBODY_CRY_ABOUT_EVERYTHING
+ vg_error( "Zero area triangle!\n" );
+#endif
+ return 0;
+ }
+
+ v3_normalize( n );
+
+ /* find best feature */
+ f32 best = v3_dot( mtxA[0], n );
+ int axis = 0;
+
+ for( int i=1; i<3; i++ ){
+ f32 c = v3_dot( mtxA[i], n );
+
+ if( fabsf(c) > fabsf(best) ){
+ best = c;
+ axis = i;
+ }
+ }
+
+ v3f manifold[4];
+
+ if( axis == 0 ){
+ f32 px = best > 0.0f? bbx[0][0]: bbx[1][0];
+ manifold[0][0] = px;
+ manifold[0][1] = bbx[0][1];
+ manifold[0][2] = bbx[0][2];
+ manifold[1][0] = px;
+ manifold[1][1] = bbx[1][1];
+ manifold[1][2] = bbx[0][2];
+ manifold[2][0] = px;
+ manifold[2][1] = bbx[1][1];
+ manifold[2][2] = bbx[1][2];
+ manifold[3][0] = px;
+ manifold[3][1] = bbx[0][1];
+ manifold[3][2] = bbx[1][2];
+ }
+ else if( axis == 1 ){
+ f32 py = best > 0.0f? bbx[0][1]: bbx[1][1];
+ manifold[0][0] = bbx[0][0];
+ manifold[0][1] = py;
+ manifold[0][2] = bbx[0][2];
+ manifold[1][0] = bbx[1][0];
+ manifold[1][1] = py;
+ manifold[1][2] = bbx[0][2];
+ manifold[2][0] = bbx[1][0];
+ manifold[2][1] = py;
+ manifold[2][2] = bbx[1][2];
+ manifold[3][0] = bbx[0][0];
+ manifold[3][1] = py;
+ manifold[3][2] = bbx[1][2];
+ }
+ else{
+ f32 pz = best > 0.0f? bbx[0][2]: bbx[1][2];
+ manifold[0][0] = bbx[0][0];
+ manifold[0][1] = bbx[0][1];
+ manifold[0][2] = pz;
+ manifold[1][0] = bbx[1][0];
+ manifold[1][1] = bbx[0][1];
+ manifold[1][2] = pz;
+ manifold[2][0] = bbx[1][0];
+ manifold[2][1] = bbx[1][1];
+ manifold[2][2] = pz;
+ manifold[3][0] = bbx[0][0];
+ manifold[3][1] = bbx[1][1];
+ manifold[3][2] = pz;
+ }
+
+ for( int j=0; j<4; j++ )
+ m4x3_mulv( mtxA, manifold[j], manifold[j] );
+
+ vg_line( manifold[0], manifold[1], 0xffffffff );
+ vg_line( manifold[1], manifold[2], 0xffffffff );
+ vg_line( manifold[2], manifold[3], 0xffffffff );
+ vg_line( manifold[3], manifold[0], 0xffffffff );
+
+ for( int j=0; j<4; j++ ){
+ rb_ct *ct = buf+count;
+
+ v3_copy( manifold[j], ct->co );
+ v3_copy( n, ct->n );
+
+ f32 l0 = v3_dot( tri[0], n ),
+ l1 = v3_dot( manifold[j], n );
+
+ ct->p = (l0-l1)*0.5f;
+ if( ct->p < 0.0f )
+ continue;
+
+ ct->type = k_contact_type_default;
+ count ++;
+
+ if( count >= 12 )
+ return count;
+ }
+ }
+ return count;
+}
+
+/* mtxB is defined only for tradition; it is not used currently */
+static int rb_capsule__scene( m4x3f mtxA, rb_capsule *c,
+ m4x3f mtxB, bh_tree *scene_bh,
+ rb_ct *buf, u16 ignore ){
+ int count = 0;
+
+ boxf bbx;
+ v3_sub( mtxA[3], (v3f){ c->h, c->h, c->h }, bbx[0] );
+ v3_add( mtxA[3], (v3f){ c->h, c->h, c->h }, bbx[1] );
+
+ scene_context *sc = scene_bh->user;
+
+ bh_iter it;
+ bh_iter_init_box( 0, &it, bbx );
+ i32 idx;
+ while( bh_next( scene_bh, &it, &idx ) ){
+ u32 *ptri = &sc->arrindices[ idx*3 ];
+ if( sc->arrvertices[ptri[0]].flags & ignore ) continue;
+
+ v3f tri[3];
+ for( int j=0; j<3; j++ )
+ v3_copy( sc->arrvertices[ptri[j]].co, tri[j] );
+
+ buf[ count ].element_id = ptri[0];
+
+ int contact = rb_capsule__triangle( mtxA, c, tri, &buf[count] );
+ count += contact;
+
+ if( count >= 16 ){
+ vg_warn("Exceeding capsule_vs_scene capacity. Geometry too dense!\n");
+ return count;
+ }
+ }
+
+ return count;
+}
+
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+
+struct shader_props_standard
+{
+ u32 tex_diffuse;
+};
+
+struct shader_props_terrain
+{
+ u32 tex_diffuse;
+ v2f blend_offset;
+ v4f sand_colour;
+};
+
+struct shader_props_vertex_blend
+{
+ u32 tex_diffuse;
+ v2f blend_offset;
+};
+
+struct shader_props_water
+{
+ v4f shore_colour;
+ v4f deep_colour;
+ f32 fog_scale;
+ f32 fresnel;
+ f32 water_sale;
+ v4f wave_speed;
+};
+
+struct shader_props_cubemapped
+{
+ u32 tex_diffuse;
+ u32 cubemap_entity;
+ v4f tint;
+};
--- /dev/null
+/*
+ * =============================================================================
+ *
+ * Copyright . . . -----, ,----- ,---. .---.
+ * 2021-2024 |\ /| | / | | | | /|
+ * | \ / | +-- / +----- +---' | / |
+ * | \ / | | / | | \ | / |
+ * | \/ | | / | | \ | / |
+ * ' ' '--' [] '----- '----- ' ' '---' SOFTWARE
+ *
+ * =============================================================================
+ */
+
+#define SR_ALLOW_REWIND_HUB
+
+#ifdef _WIN32
+ #include <winsock2.h>
+#endif
+
+/*
+ * system headers
+ * --------------------- */
+
+#include "vg/vg_opt.h"
+#include "vg/vg_loader.h"
+#include "vg/vg_io.h"
+
+#include "skaterift.h"
+#include "steam.h"
+#include "render.h"
+#include "world.h"
+#include "font.h"
+#include "player.h"
+#include "network.h"
+#include "menu.h"
+#include "vehicle.h"
+#include "save.h"
+#include "player_remote.h"
+#include "particle.h"
+#include "trail.h"
+#include "freecam.h"
+#include "ent_tornado.h"
+#include "ent_miniworld.h"
+#include "ent_skateshop.h"
+#include "ent_npc.h"
+#include "ent_camera.h"
+#include "world_map.h"
+#include "gui.h"
+#include "workshop.h"
+#include "audio.h"
+#include "player_render.h"
+#include "control_overlay.h"
+#include "client.h"
+
+struct skaterift_globals skaterift =
+{
+ .time_rate = 1.0f,
+ .hub_world = "maps/dev_hub",
+};
+
+void game_launch_opt(void)
+{
+ const char *arg;
+ if( (arg = vg_long_opt_arg( "world" )) )
+ skaterift.hub_world = arg;
+}
+
+static void async_skaterift_player_start( void *payload, u32 size ){
+ world_switch_instance(0);
+}
+
+static void skaterift_restore_state(void)
+{
+ savedata_file sav;
+ skaterift_read_main_savedata( &sav );
+
+ vg_msg kvsav;
+ vg_msg_init( &kvsav, sav.buf, sizeof(sav.buf) );
+
+ u32 ach;
+ vg_msg_getkvintg( &kvsav, "ach", k_vg_msg_u32, &ach, NULL );
+ skaterift.achievements |= ach;
+
+ u32 board_reg_id = time(NULL) % addon_count( k_addon_type_board, 0 ),
+ player_reg_id = (time(NULL)+44) % addon_count( k_addon_type_player, 0 );
+
+ vg_msg_cursor orig = kvsav.cur;
+ if( vg_msg_seekframe( &kvsav, "player" ) ){
+ addon_alias q;
+
+ /* board */
+ skaterift_read_addon_alias( &kvsav, "board", k_addon_type_board, &q );
+ u32 reg_id = addon_match( &q );
+ if( reg_id != 0xffffffff )
+ board_reg_id = reg_id;
+
+ /* playermodel */
+ skaterift_read_addon_alias( &kvsav, "playermodel",
+ k_addon_type_player, &q );
+ reg_id = addon_match( &q );
+ if( reg_id != 0xffffffff )
+ player_reg_id = reg_id;
+ }
+
+ localplayer.board_view_slot =
+ addon_cache_create_viewer( k_addon_type_board, board_reg_id );
+ localplayer.playermodel_view_slot =
+ addon_cache_create_viewer( k_addon_type_player, player_reg_id );
+
+ kvsav.cur = orig;
+}
+
+static addon_reg *skaterift_mount_world_unloadable( const char *path, u32 ext ){
+ addon_reg *reg = addon_mount_local_addon( path, k_addon_type_world, ".mdl" );
+ if( !reg ) vg_fatal_error( "world not found\n" );
+ reg->flags |= (ADDON_REG_HIDDEN | ext);
+ return reg;
+}
+
+static void skaterift_load_world_content(void){
+ /* hub world */
+ addon_reg *hub = skaterift_mount_world_unloadable( skaterift.hub_world, 0 );
+ skaterift_mount_world_unloadable( "maps/mp_spawn",
+ ADDON_REG_CITY|ADDON_REG_PREMIUM );
+ skaterift_mount_world_unloadable( "maps/mp_mtzero",
+ ADDON_REG_MTZERO|ADDON_REG_PREMIUM );
+ skaterift_mount_world_unloadable( "maps/dev_tutorial", 0 );
+ skaterift_mount_world_unloadable( "maps/dev_flatworld", 0 );
+ skaterift_mount_world_unloadable( "maps/mp_line1", ADDON_REG_PREMIUM );
+
+ world_static.load_state = k_world_loader_load;
+
+ struct world_load_args args = {
+ .purpose = k_world_purpose_hub,
+ .reg = hub
+ };
+ skaterift_world_load_thread( &args );
+}
+
+static void skaterift_load_player_content(void)
+{
+ particle_alloc( &particles_grind, 300 );
+ particle_alloc( &particles_env, 200 );
+
+ player_load_animation_reference( "models/ch_none.mdl" );
+ player_model_load( &localplayer.fallback_model, "models/ch_none.mdl" );
+ player__bind();
+ player_board_load( &localplayer.fallback_board, "models/board_none.mdl" );
+}
+
+void game_load(void)
+{
+ vg_console_reg_cmd( "load_world", skaterift_load_world_command, NULL );
+ vg_console_reg_var( "immobile", &localplayer.immobile, k_var_dtype_i32, 0 );
+ vg_loader_step( menu_init, NULL );
+
+ vg_loader_step( control_overlay_init, NULL );
+ vg_loader_step( world_init, NULL );
+ vg_loader_step( vehicle_init, NULL );
+ vg_loader_step( gui_init, NULL );
+
+ vg_loader_step( player_init, NULL );
+ vg_loader_step( player_ragdoll_init, NULL );
+ vg_loader_step( npc_init, NULL );
+
+ /* content stuff */
+ vg_loader_step( addon_system_init, NULL );
+ vg_loader_step( workshop_init, NULL );
+ vg_loader_step( skateshop_init, NULL );
+ vg_loader_step( ent_tornado_init, NULL );
+ vg_loader_step( skaterift_replay_init, NULL );
+ vg_loader_step( skaterift_load_player_content, NULL );
+
+ vg_bake_shaders();
+ vg_loader_step( audio_init, NULL );
+
+ vg_loader_step( skaterift_load_world_content, NULL );
+ vg_async_call( async_skaterift_player_start, NULL, 0 );
+ vg_async_stall();
+
+ vg_console_load_autos();
+
+ addon_mount_content_folder( k_addon_type_player,
+ "playermodels", ".mdl" );
+ addon_mount_content_folder( k_addon_type_board, "boards", ".mdl" );
+ addon_mount_content_folder( k_addon_type_world, "maps", ".mdl" );
+ addon_mount_workshop_items();
+ vg_async_call( async_addon_reg_update, NULL, 0 );
+ vg_async_stall();
+
+ skaterift_restore_state();
+ update_ach_models();
+
+ vg_loader_step( NULL, skaterift_autosave_synchronous );
+}
+
+static void draw_origin_axis(void)
+{
+ vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 1.0f, 0.0f, 0.0f }, 0xffff0000 );
+ vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 1.0f, 0.0f }, 0xff00ff00 );
+ vg_line( (v3f){ 0.0f, 0.0f, 0.0f }, (v3f){ 0.0f, 0.0f, 1.0f }, 0xff0000ff );
+}
+void skaterift_change_client_world_preupdate(void);
+
+/*
+ * UPDATE LOOP
+ * ---------------------------------------------------------------------------*/
+
+void vg_pre_update(void)
+{
+ skaterift_preupdate_inputs();
+
+ steam_update();
+ skaterift_change_client_world_preupdate();
+
+ if( !g_client.loaded ) return;
+
+ draw_origin_axis();
+ addon_system_pre_update();
+ skateshop_world_preview_preupdate();
+ network_update();
+
+ /* time rate */
+ f32 target = 1;
+ if( skaterift.activity & k_skaterift_replay )
+ target = 0;
+
+ v3f listen_co;
+ v3_copy( localplayer.rb.co, listen_co );
+
+ if( skaterift.activity & k_skaterift_menu )
+ {
+ if( menu.bg_cam )
+ {
+ v3_copy( menu.bg_cam->transform.co, listen_co );
+ }
+ else target = 0;
+ }
+
+ vg_slewf( &skaterift.time_rate, target, vg.time_frame_delta * (1.0f/0.3f) );
+ vg.time_rate = vg_smoothstepf( skaterift.time_rate );
+
+ /* TODO: how can we compress this? */
+ ent_miniworld_preupdate();
+ world_entity_focus_preupdate();
+
+ if( skaterift.activity != k_skaterift_menu )
+ {
+ player__pre_update();
+ }
+
+ skaterift_replay_pre_update();
+ remote_sfx_pre_update();
+ skateshop_world_preupdate( world_current_instance() );
+
+ world_update( world_current_instance(), localplayer.rb.co );
+ audio_ambient_sprites_update( world_current_instance(), listen_co );
+ world_map_pre_update();
+}
+
+void vg_fixed_update(void)
+{
+ if( !g_client.loaded ) return;
+
+ world_routes_fixedupdate( world_current_instance() );
+ player__update();
+ vehicle_update_fixed();
+}
+
+void vg_post_update(void)
+{
+ if( !g_client.loaded ) return;
+
+ player__post_update();
+
+ float dist;
+ int sample_index;
+ world_audio_sample_distances( localplayer.rb.co, &sample_index, &dist );
+
+ audio_lock();
+ vg_dsp.echo_distances[sample_index] = dist;
+
+ v3f ears = { 1.0f,0.0f,0.0f };
+ m3x3_mulv( g_render.cam.transform, ears, ears );
+ v3_copy( ears, vg_audio.external_listener_ears );
+ v3_copy( g_render.cam.transform[3], vg_audio.external_listener_pos );
+
+ if( localplayer.gate_waiting ){
+ m4x3_mulv( localplayer.gate_waiting->transport,
+ vg_audio.external_listener_pos,
+ vg_audio.external_listener_pos );
+ }
+
+ v3_copy( localplayer.rb.v, vg_audio.external_lister_velocity );
+ audio_unlock();
+
+ vehicle_update_post();
+ skaterift_autosave_update();
+}
+
+/*
+ * RENDERING
+ * ---------------------------------------------------------------------------*/
+
+static void render_player_transparent(void)
+{
+ if( (skaterift.activity == k_skaterift_menu) &&
+ (menu.page == k_menu_page_main) &&
+ (menu.main_index == k_menu_main_guide) )
+ {
+ return;
+ }
+
+ static vg_camera small_cam; /* DOES NOT NEED TO BE STATIC BUT MINGW
+ SAIS OTHERWISE */
+
+ m4x3_copy( g_render.cam.transform, small_cam.transform );
+
+ small_cam.fov = g_render.cam.fov;
+ small_cam.nearz = 0.05f;
+ small_cam.farz = 60.0f;
+
+ vg_camera_update_view( &small_cam );
+ vg_camera_update_projection( &small_cam );
+ vg_camera_finalize( &small_cam );
+
+ /* Draw player to window buffer and blend background ontop */
+ player__render( &small_cam );
+}
+
+static world_instance *get_view_world(void)
+{
+ if( (skaterift.activity & k_skaterift_menu) &&
+ (menu.page == k_menu_page_main) &&
+ (menu.main_index == k_menu_main_guide) )
+ {
+ return &world_static.instances[0];
+ }
+
+ world_instance *view_world = world_current_instance();
+ if( localplayer.gate_waiting &&
+ (localplayer.gate_waiting->flags & k_ent_gate_nonlocal) ){
+ view_world = &world_static.instances[world_static.active_instance ^ 0x1];
+ }
+
+ return view_world;
+}
+
+static void render_scene(void)
+{
+ /* Draw world */
+ glEnable( GL_DEPTH_TEST );
+
+ for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ )
+ {
+ if( world_static.instances[i].status == k_world_status_loaded )
+ {
+ world_prerender( &world_static.instances[i] );
+ }
+ }
+
+ if( menu_viewing_map() )
+ {
+ world_instance *world = world_current_instance();
+ glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
+
+ v3f bg;
+ v3_muls( world->ub_lighting.g_daysky_colour,
+ world->ub_lighting.g_day_phase -
+ world->ub_lighting.g_sunset_phase*0.1f, bg );
+
+ v3_muladds( bg, world->ub_lighting.g_sunset_colour,
+ (1.0f-0.5f)*world->ub_lighting.g_sunset_phase, bg );
+
+ v3_muladds( bg, world->ub_lighting.g_nightsky_colour,
+ (1.0f-world->ub_lighting.g_day_phase), bg );
+
+ glClearColor( bg[0], bg[1], bg[2], 0.0f );
+ glClear( GL_COLOR_BUFFER_BIT );
+ glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0,
+ GL_COLOR_ATTACHMENT1 } );
+
+ m4x3f identity;
+ m4x3_identity( identity );
+ render_world_override( world, world, identity, &g_render.cam,
+ world_map.close_spawn,
+ (v4f){world->tar_min, world->tar_max, 1.0f, 0.0f});
+ render_world_routes( world, world, identity, &g_render.cam, 0, 1 );
+ return;
+ }
+
+ world_instance *view_world = get_view_world();
+ render_world( view_world, &g_render.cam, 0, 0, 1, 1 );
+
+ particle_system_update( &particles_grind, vg.time_delta );
+ //particle_system_debug( &particles_grind );
+ particle_system_prerender( &particles_grind );
+ particle_system_render( &particles_grind, &g_render.cam );
+
+ ent_tornado_pre_update();
+ particle_system_update( &particles_env, vg.time_delta );
+ particle_system_prerender( &particles_env );
+ particle_system_render( &particles_env, &g_render.cam );
+
+ player_glide_render_effects( &g_render.cam );
+
+ /*
+ * render transition
+ */
+ if( global_miniworld.transition == 0 )
+ return;
+
+ world_instance *holdout_world = NULL;
+ f32 t = 0.0f;
+
+ if( global_miniworld.transition == 1 ){
+ holdout_world = &world_static.instances[ k_world_purpose_hub ];
+ t = global_miniworld.t;
+ }
+ else{
+ holdout_world = &world_static.instances[ k_world_purpose_client ];
+ t = 1.0f-global_miniworld.t;
+ }
+
+ if( holdout_world->status != k_world_status_loaded )
+ return;
+
+ t = vg_smoothstepf( t );
+
+ glEnable( GL_STENCIL_TEST );
+ glDisable( GL_DEPTH_TEST );
+ glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
+ glStencilFunc( GL_ALWAYS, 1, 0xFF );
+ glStencilMask( 0xFF );
+
+ shader_blit_transition_use();
+ shader_blit_transition_uInverseRatio( (v2f){1.0f,1.0f} );
+ shader_blit_transition_uT( -(sqrtf(2)+0.5f) * t );
+
+ render_fsquad();
+ render_world( holdout_world, &global_miniworld.cam, 1, 0, 1, 1 );
+}
+
+static void skaterift_composite_maincamera(void)
+{
+ vg_camera_lerp( &localplayer.cam, &world_static.focus_cam,
+ vg_smoothstepf(world_static.focus_strength), &g_render.cam );
+
+ if( skaterift.activity == k_skaterift_replay )
+ {
+ if( player_replay.use_freecam )
+ {
+ freecam_preupdate();
+ v3_copy( player_replay.replay_freecam.pos, g_render.cam.pos );
+ v3_copy( player_replay.replay_freecam.angles, g_render.cam.angles );
+ g_render.cam.fov = player_replay.replay_freecam.fov;
+ }
+ else
+ {
+ skaterift_get_replay_cam( &g_render.cam );
+ }
+ }
+
+ g_render.cam.nearz = 0.1f;
+ g_render.cam.farz = 2100.0f;
+
+ if( (skaterift.activity == k_skaterift_menu) && menu.bg_cam )
+ {
+ ent_camera_unpack( menu.bg_cam, &g_render.cam );
+ }
+
+ if( menu_viewing_map() )
+ {
+ vg_camera_copy( &world_map.cam, &g_render.cam );
+ g_render.cam.nearz = 4.0f;
+ g_render.cam.farz = 3100.0f;
+ }
+
+ if( global_miniworld.transition ){
+ f32 dt = vg.time_frame_delta / 2.0f,
+ s = vg_signf( global_miniworld.transition );
+ global_miniworld.t += s * dt;
+
+ if( (global_miniworld.t > 1.0f) || (global_miniworld.t < 0.0f) ){
+ global_miniworld.t = vg_clampf( global_miniworld.t, 0.0f, 1.0f );
+ global_miniworld.transition = 0;
+ }
+ }
+
+ vg_camera_update_transform( &g_render.cam );
+ vg_camera_update_view( &g_render.cam );
+ vg_camera_update_projection( &g_render.cam );
+ vg_camera_finalize( &g_render.cam );
+}
+
+static void render_main_game(void)
+{
+ if( skaterift.activity == k_skaterift_replay )
+ {
+ player__animate_from_replay( &player_replay.local );
+ }
+ else{
+ player__animate();
+ skaterift_record_frame( &player_replay.local,
+ localplayer.deferred_frame_record );
+ localplayer.deferred_frame_record = 0;
+ }
+ animate_remote_players();
+ player__pre_render();
+
+ skaterift_composite_maincamera();
+
+ /* --------------------------------------------------------------------- */
+ if( !menu_viewing_map() )
+ {
+ world_instance *world = world_current_instance();
+ render_world_cubemaps( world );
+
+ ent_gate *nlg = world->rendering_gate;
+ if( nlg && (nlg->flags & k_ent_gate_nonlocal) )
+ render_world_cubemaps( &world_static.instances[nlg->target] );
+ }
+
+ /* variable res target */
+ vg_framebuffer_bind( g_render.fb_main, k_render_scale );
+ glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT );
+
+ render_scene();
+ glEnable( GL_DEPTH_TEST );
+
+ /* full res target */
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ glViewport( 0,0, vg.window_x, vg.window_y );
+
+ render_player_transparent(); /* needs to read the depth buffer before we fuck
+ it up with the oblique rendering inside the
+ portals */
+
+ /* continue with variable rate */
+ if( !global_miniworld.transition && !menu_viewing_map() )
+ {
+ vg_framebuffer_bind( g_render.fb_main, k_render_scale );
+ render_world_gates( get_view_world(), &g_render.cam );
+ }
+
+ /* composite */
+
+ if( (skaterift.activity == k_skaterift_menu) && menu.bg_blur )
+ v2_muls( (v2f){ 0.04f, 0.001f }, 1.0f-skaterift.time_rate,
+ g_render.blur_override );
+ else
+ v2_zero( g_render.blur_override );
+ postprocess_to_screen( g_render.fb_main );
+
+ skaterift_replay_post_render();
+ control_overlay_render();
+}
+
+void vg_render(void)
+{
+ if( !g_client.loaded )
+ {
+ vg_loader_render();
+ return;
+ }
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+
+ glViewport( 0,0, vg.window_x, vg.window_y );
+ glDisable( GL_DEPTH_TEST );
+ glDisable( GL_BLEND );
+
+ glClearColor( 1.0f, 0.0f, 0.0f, 0.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+
+ render_main_game();
+ m4x4_copy( g_render.cam.mtx.pv, vg.pv );
+
+ /* Other shite */
+ glDisable(GL_BLEND);
+ glDisable(GL_DEPTH_TEST);
+ vg_lines_drawall();
+ glViewport( 0,0, vg.window_x, vg.window_y );
+
+ gui_render_icons();
+}
+
+void vg_gui( ui_context *ctx )
+{
+ if( !g_client.loaded ) return;
+
+ gui_draw( ctx );
+
+ if( k_light_editor )
+ imgui_world_light_edit( ctx, world_current_instance() );
+
+ vg_ui.tex_bg = g_render.fb_main->attachments[0].id;
+ vg_framebuffer_inverse_ratio( g_render.fb_main, vg_ui.bg_inverse_ratio );
+
+ menu_gui( ctx );
+ player__im_gui( ctx );
+ world_instance *world = world_current_instance();
+
+ world_routes_imgui( ctx, world );
+ skaterift_replay_imgui( ctx );
+ workshop_form_gui( ctx );
+ remote_player_network_imgui( ctx, vg.pv );
+
+ if( menu_viewing_map() )
+ {
+ remote_players_imgui_world( ctx, world_current_instance(),
+ vg.pv, 2000.0f, 0 );
+ remote_players_imgui_lobby( ctx );
+ }
+ else
+ {
+ remote_players_chat_imgui( ctx ); /* TODO: conditional */
+ remote_players_imgui_world( ctx, world_current_instance(),
+ vg.pv, 100.0f, 1 );
+ }
+}
+
+#include "addon.c"
+#include "addon_types.c"
+#include "audio.c"
+#include "ent_challenge.c"
+#include "ent_glider.c"
+#include "entity.c"
+#include "ent_miniworld.c"
+#include "ent_objective.c"
+#include "ent_region.c"
+#include "ent_relay.c"
+#include "ent_route.c"
+#include "ent_skateshop.c"
+#include "ent_tornado.c"
+#include "ent_traffic.c"
+#include "freecam.c"
+#include "menu.c"
+#include "network.c"
+#include "particle.c"
+#include "player_basic_info.c"
+#include "player.c"
+#include "player_common.c"
+#include "player_dead.c"
+#include "player_drive.c"
+#include "player_effects.c"
+#include "player_glide.c"
+#include "player_ragdoll.c"
+#include "player_remote.c"
+#include "player_render.c"
+#include "player_replay.c"
+#include "player_skate.c"
+#include "player_walk.c"
+#include "render.c"
+#include "save.c"
+#include "scene.c"
+#include "steam.c"
+#include "trail.c"
+#include "vehicle.c"
+#include "workshop.c"
+#include "world_audio.c"
+#include "world.c"
+#include "world_entity.c"
+#include "world_gate.c"
+#include "world_gen.c"
+#include "world_load.c"
+#include "world_map.c"
+#include "world_physics.c"
+#include "world_render.c"
+#include "world_routes.c"
+#include "world_routes_ui.c"
+#include "world_sfd.c"
+#include "world_volumes.c"
+#include "world_water.c"
+#include "ent_npc.c"
+#include "model.c"
+#include "control_overlay.c"
+#include "ent_camera.c"
--- /dev/null
+#pragma once
+#define SKATERIFT
+#define SKATERIFT_APPID 2103940
+
+#include "vg/vg_engine.h"
+#include "vg/vg_camera.h"
+
+enum skaterift_rt
+{
+ k_skaterift_rt_workshop_preview,
+ k_skaterift_rt_server_status,
+ k_skaterift_rt_max
+};
+
+struct skaterift_globals
+{
+ f32 time_rate;
+
+ enum skaterift_activity {
+ k_skaterift_default = 0x00,
+ k_skaterift_replay = 0x01,
+ k_skaterift_ent_focus = 0x02,
+ k_skaterift_menu = 0x04,
+ }
+ activity;
+ GLuint rt_textures[k_skaterift_rt_max];
+
+ u32 achievements;
+ int demo_mode;
+
+ const char *hub_world;
+}
+extern skaterift;
--- /dev/null
+#define QOI_IMPLEMENTATION
+#include "vg/submodules/qoi/qoi.h"
+#include "vg/vg_platform.h"
+#include "vg/vg_m.h"
+
+u8 *qoi_encode_rgbaf32( f32 *data, u32 width, u32 height, int *length )
+{
+ u8 *buf = (u8 *)data;
+ for( u32 i=0; i<width*height*4; i ++ )
+ {
+ buf[i] = vg_clampf( data[i] * 255.0f, 0.0f, 255.0f );
+ }
+
+ qoi_desc desc =
+ {
+ .channels=4,
+ .colorspace=0,
+ .width=width,
+ .height=height
+ };
+
+ return qoi_encode( buf, &desc, length );
+}
+
+void qoi_free( u8 *ptr )
+{
+ free( ptr );
+}
+
+#include "vg/vg_tool.h"
+#include "vg/vg_tool.c"
--- /dev/null
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+#include "vg/vg_lines.h"
+#include "model.h"
+
+struct skeleton
+{
+ struct skeleton_bone
+ {
+ v3f co, end;
+ u32 parent;
+
+ u32 flags;
+ int defer;
+
+ mdl_keyframe kf;
+ mdl_bone *orig_bone;
+
+ u32 collider;
+ boxf hitbox;
+ const char *name;
+ }
+ *bones;
+ u32 bone_count;
+
+ struct skeleton_anim
+ {
+ const char *name;
+ u32 length;
+
+ float rate;
+ mdl_keyframe *anim_data;
+ }
+ *anims;
+ u32 anim_count;
+
+#if 0
+ m4x3f *final_mtx;
+#endif
+
+ struct skeleton_ik
+ {
+ u32 lower, upper, target, pole;
+ m3x3f ia, ib;
+ }
+ *ik;
+ u32 ik_count;
+
+ u32
+ collider_count,
+ bindable_count;
+};
+
+static u32 skeleton_bone_id( struct skeleton *skele, const char *name )
+{
+ for( u32 i=1; i<skele->bone_count; i++ ){
+ if( !strcmp( skele->bones[i].name, name ))
+ return i;
+ }
+
+ vg_error( "skeleton_bone_id( *, \"%s\" );\n", name );
+ vg_fatal_error( "Bone does not exist\n" );
+
+ return 0;
+}
+
+static void keyframe_copy_pose( mdl_keyframe *kfa, mdl_keyframe *kfb,
+ int num )
+{
+ for( int i=0; i<num; i++ )
+ kfb[i] = kfa[i];
+}
+
+
+/* apply a rotation from the perspective of root */
+static void keyframe_rotate_around( mdl_keyframe *kf,
+ v3f origin, v3f offset, v4f q )
+{
+ v3f v0, co;
+ v3_add( kf->co, offset, co );
+ v3_sub( co, origin, v0 );
+ q_mulv( q, v0, v0 );
+ v3_add( v0, origin, co );
+ v3_sub( co, offset, kf->co );
+
+ q_mul( q, kf->q, kf->q );
+ q_normalize( kf->q );
+}
+
+static void keyframe_lerp( mdl_keyframe *kfa, mdl_keyframe *kfb, f32 t,
+ mdl_keyframe *kfd ){
+ v3_lerp( kfa->co, kfb->co, t, kfd->co );
+ q_nlerp( kfa->q, kfb->q, t, kfd->q );
+ v3_lerp( kfa->s, kfb->s, t, kfd->s );
+}
+
+/*
+ * Lerp between two sets of keyframes and store in dest. Rotations use Nlerp.
+ */
+static void keyframe_lerp_pose( mdl_keyframe *kfa, mdl_keyframe *kfb,
+ float t, mdl_keyframe *kfd, int count ){
+ if( t <= 0.0001f ){
+ keyframe_copy_pose( kfa, kfd, count );
+ return;
+ }
+ else if( t >= 0.9999f ){
+ keyframe_copy_pose( kfb, kfd, count );
+ return;
+ }
+
+ for( int i=0; i<count; i++ )
+ keyframe_lerp( kfa+i, kfb+i, t, kfd+i );
+}
+
+static
+void skeleton_lerp_pose( struct skeleton *skele,
+ mdl_keyframe *kfa, mdl_keyframe *kfb, float t,
+ mdl_keyframe *kfd )
+{
+ keyframe_lerp_pose( kfa, kfb, t, kfd, skele->bone_count-1 );
+}
+
+static void skeleton_copy_pose( struct skeleton *skele,
+ mdl_keyframe *kfa, mdl_keyframe *kfd )
+{
+ keyframe_copy_pose( kfa, kfd, skele->bone_count-1 );
+}
+
+/*
+ * Sample animation between 2 closest frames using time value. Output is a
+ * keyframe buffer that is allocated with an appropriate size
+ */
+static void skeleton_sample_anim( struct skeleton *skele,
+ struct skeleton_anim *anim,
+ float time,
+ mdl_keyframe *output )
+{
+ f32 animtime = fmodf( time*anim->rate, anim->length ),
+ animframe = floorf( animtime ),
+ t = animtime - animframe;
+
+ u32 frame = (u32)animframe % anim->length,
+ next = (frame+1) % anim->length;
+
+ mdl_keyframe *base = anim->anim_data + (skele->bone_count-1)*frame,
+ *nbase = anim->anim_data + (skele->bone_count-1)*next;
+
+ skeleton_lerp_pose( skele, base, nbase, t, output );
+}
+
+static int skeleton_sample_anim_clamped( struct skeleton *skele,
+ struct skeleton_anim *anim,
+ float time,
+ mdl_keyframe *output )
+{
+ float end = (float)(anim->length-1) / anim->rate;
+ skeleton_sample_anim( skele, anim, vg_minf( end, time ), output );
+
+ if( time > end )
+ return 0;
+ else
+ return 1;
+}
+
+typedef enum anim_apply
+{
+ k_anim_apply_always,
+ k_anim_apply_defer_ik,
+ k_anim_apply_deffered_only,
+ k_anim_apply_absolute
+}
+anim_apply;
+
+static
+int should_apply_bone( struct skeleton *skele, u32 id, anim_apply type )
+{
+ struct skeleton_bone *sb = &skele->bones[ id ],
+ *sp = &skele->bones[ sb->parent ];
+
+ if( type == k_anim_apply_defer_ik ){
+ if( ((sp->flags & k_bone_flag_ik) && !(sb->flags & k_bone_flag_ik))
+ || sp->defer )
+ {
+ sb->defer = 1;
+ return 0;
+ }
+ else{
+ sb->defer = 0;
+ return 1;
+ }
+ }
+ else if( type == k_anim_apply_deffered_only ){
+ if( sb->defer )
+ return 1;
+ else
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Apply block of keyframes to skeletons final pose
+ */
+static void skeleton_apply_pose( struct skeleton *skele, mdl_keyframe *pose,
+ anim_apply passtype, m4x3f *final_mtx ){
+ if( passtype == k_anim_apply_absolute ){
+ for( u32 i=1; i<skele->bone_count; i++ ){
+ mdl_keyframe *kf = &pose[i-1];
+
+ v3f *posemtx = final_mtx[i];
+
+ q_m3x3( kf->q, posemtx );
+ m3x3_scale( posemtx, kf->s );
+ v3_copy( kf->co, posemtx[3] );
+ }
+ return;
+ }
+
+ m4x3_identity( final_mtx[0] );
+ skele->bones[0].defer = 0;
+ skele->bones[0].flags &= ~k_bone_flag_ik;
+
+ for( u32 i=1; i<skele->bone_count; i++ ){
+ struct skeleton_bone *sb = &skele->bones[i],
+ *sp = &skele->bones[sb->parent];
+
+ if( !should_apply_bone( skele, i, passtype ) )
+ continue;
+
+ sb->defer = 0;
+
+ /* process pose */
+ m4x3f posemtx;
+
+ v3f temp_delta;
+ v3_sub( skele->bones[i].co, skele->bones[sb->parent].co, temp_delta );
+
+ /* pose matrix */
+ mdl_keyframe *kf = &pose[i-1];
+ q_m3x3( kf->q, posemtx );
+ m3x3_scale( posemtx, kf->s );
+ v3_copy( kf->co, posemtx[3] );
+ v3_add( temp_delta, posemtx[3], posemtx[3] );
+
+ /* final matrix */
+ m4x3_mul( final_mtx[ sb->parent ], posemtx, final_mtx[i] );
+ }
+}
+
+/*
+ * Take the final matrices and decompose it into an absolute positioned anim
+ */
+static void skeleton_decompose_mtx_absolute( struct skeleton *skele,
+ mdl_keyframe *anim,
+ m4x3f *final_mtx ){
+ for( u32 i=1; i<skele->bone_count; i++ ){
+ struct skeleton_bone *sb = &skele->bones[i];
+ mdl_keyframe *kf = &anim[i-1];
+ m4x3_decompose( final_mtx[i], kf->co, kf->q, kf->s );
+ }
+}
+
+/*
+ * creates the reference inverse matrix for an IK bone, as it has an initial
+ * intrisic rotation based on the direction that the IK is setup..
+ */
+static void skeleton_inverse_for_ik( struct skeleton *skele,
+ v3f ivaxis,
+ u32 id, m3x3f inverse )
+{
+ v3_copy( ivaxis, inverse[0] );
+ v3_copy( skele->bones[id].end, inverse[1] );
+ v3_normalize( inverse[1] );
+ v3_cross( inverse[0], inverse[1], inverse[2] );
+ m3x3_transpose( inverse, inverse );
+}
+
+/*
+ * Creates inverse rotation matrices which the IK system uses.
+ */
+static void skeleton_create_inverses( struct skeleton *skele )
+{
+ /* IK: inverse 'plane-bone space' axis '(^axis,^bone,...)[base] */
+ for( u32 i=0; i<skele->ik_count; i++ ){
+ struct skeleton_ik *ik = &skele->ik[i];
+
+ m4x3f inverse;
+ v3f iv0, iv1, ivaxis;
+ v3_sub( skele->bones[ik->target].co, skele->bones[ik->lower].co, iv0 );
+ v3_sub( skele->bones[ik->pole].co, skele->bones[ik->lower].co, iv1 );
+ v3_cross( iv0, iv1, ivaxis );
+ v3_normalize( ivaxis );
+
+ skeleton_inverse_for_ik( skele, ivaxis, ik->lower, ik->ia );
+ skeleton_inverse_for_ik( skele, ivaxis, ik->upper, ik->ib );
+ }
+}
+
+/*
+ * Apply a model matrix to all bones, should be done last
+ */
+static
+void skeleton_apply_transform( struct skeleton *skele, m4x3f transform,
+ m4x3f *final_mtx )
+{
+ for( u32 i=0; i<skele->bone_count; i++ ){
+ struct skeleton_bone *sb = &skele->bones[i];
+ m4x3_mul( transform, final_mtx[i], final_mtx[i] );
+ }
+}
+
+/*
+ * Apply an inverse matrix to all bones which maps vertices from bind space into
+ * bone relative positions
+ */
+static void skeleton_apply_inverses( struct skeleton *skele, m4x3f *final_mtx ){
+ for( u32 i=0; i<skele->bone_count; i++ ){
+ struct skeleton_bone *sb = &skele->bones[i];
+ m4x3f inverse;
+ m3x3_identity( inverse );
+ v3_negate( sb->co, inverse[3] );
+
+ m4x3_mul( final_mtx[i], inverse, final_mtx[i] );
+ }
+}
+
+/*
+ * Apply all IK modifiers (2 bone ik reference from blender is supported)
+ */
+static void skeleton_apply_ik_pass( struct skeleton *skele, m4x3f *final_mtx ){
+ for( u32 i=0; i<skele->ik_count; i++ ){
+ struct skeleton_ik *ik = &skele->ik[i];
+
+ v3f v0, /* base -> target */
+ v1, /* base -> pole */
+ vaxis;
+
+ v3f co_base,
+ co_target,
+ co_pole;
+
+ v3_copy( final_mtx[ik->lower][3], co_base );
+ v3_copy( final_mtx[ik->target][3], co_target );
+ v3_copy( final_mtx[ik->pole][3], co_pole );
+
+ v3_sub( co_target, co_base, v0 );
+ v3_sub( co_pole, co_base, v1 );
+ v3_cross( v0, v1, vaxis );
+ v3_normalize( vaxis );
+ v3_normalize( v0 );
+ v3_cross( vaxis, v0, v1 );
+
+ /* localize problem into [x:v0,y:v1] 2d plane */
+ v2f base = { v3_dot( v0, co_base ), v3_dot( v1, co_base ) },
+ end = { v3_dot( v0, co_target ), v3_dot( v1, co_target ) },
+ knee;
+
+ /* Compute angles (basic trig)*/
+ v2f delta;
+ v2_sub( end, base, delta );
+
+ float
+ l1 = v3_length( skele->bones[ik->lower].end ),
+ l2 = v3_length( skele->bones[ik->upper].end ),
+ d = vg_clampf( v2_length(delta), fabsf(l1 - l2), l1+l2-0.00001f ),
+ c = acosf( (l1*l1 + d*d - l2*l2) / (2.0f*l1*d) ),
+ rot = atan2f( delta[1], delta[0] ) + c - VG_PIf/2.0f;
+
+ knee[0] = sinf(-rot) * l1;
+ knee[1] = cosf(-rot) * l1;
+
+ m4x3_identity( final_mtx[ik->lower] );
+ m4x3_identity( final_mtx[ik->upper] );
+
+ /* create rotation matrix */
+ v3f co_knee;
+ v3_muladds( co_base, v0, knee[0], co_knee );
+ v3_muladds( co_knee, v1, knee[1], co_knee );
+ vg_line( co_base, co_knee, 0xff00ff00 );
+
+ m4x3f transform;
+ v3_copy( vaxis, transform[0] );
+ v3_muls( v0, knee[0], transform[1] );
+ v3_muladds( transform[1], v1, knee[1], transform[1] );
+ v3_normalize( transform[1] );
+ v3_cross( transform[0], transform[1], transform[2] );
+ v3_copy( co_base, transform[3] );
+
+ m3x3_mul( transform, ik->ia, transform );
+ m4x3_copy( transform, final_mtx[ik->lower] );
+
+ /* upper/knee bone */
+ v3_copy( vaxis, transform[0] );
+ v3_sub( co_target, co_knee, transform[1] );
+ v3_normalize( transform[1] );
+ v3_cross( transform[0], transform[1], transform[2] );
+ v3_copy( co_knee, transform[3] );
+
+ m3x3_mul( transform, ik->ib, transform );
+ m4x3_copy( transform, final_mtx[ik->upper] );
+ }
+}
+
+/*
+ * Applies the typical operations that you want for an IK rig:
+ * Pose, IK, Pose(deferred), Inverses, Transform
+ */
+static void skeleton_apply_standard( struct skeleton *skele, mdl_keyframe *pose,
+ m4x3f transform, m4x3f *final_mtx ){
+ skeleton_apply_pose( skele, pose, k_anim_apply_defer_ik, final_mtx );
+ skeleton_apply_ik_pass( skele, final_mtx );
+ skeleton_apply_pose( skele, pose, k_anim_apply_deffered_only, final_mtx );
+ skeleton_apply_inverses( skele, final_mtx );
+ skeleton_apply_transform( skele, transform, final_mtx );
+}
+
+/*
+ * Get an animation by name
+ */
+static struct skeleton_anim *skeleton_get_anim( struct skeleton *skele,
+ const char *name ){
+ for( u32 i=0; i<skele->anim_count; i++ ){
+ struct skeleton_anim *anim = &skele->anims[i];
+
+ if( !strcmp( anim->name, name ) )
+ return anim;
+ }
+
+ vg_error( "skeleton_get_anim( *, \"%s\" )\n", name );
+ vg_fatal_error( "Invalid animation name\n" );
+
+ return NULL;
+}
+
+static void skeleton_alloc_from( struct skeleton *skele,
+ void *lin_alloc,
+ mdl_context *mdl,
+ mdl_armature *armature ){
+ skele->bone_count = armature->bone_count+1;
+ skele->anim_count = armature->anim_count;
+ skele->ik_count = 0;
+ skele->collider_count = 0;
+
+ for( u32 i=0; i<armature->bone_count; i++ ){
+ mdl_bone *bone = mdl_arritm( &mdl->bones, armature->bone_start+i );
+
+ if( bone->flags & k_bone_flag_ik )
+ skele->ik_count ++;
+
+ if( bone->collider )
+ skele->collider_count ++;
+ }
+
+ u32 bone_size = sizeof(struct skeleton_bone) * skele->bone_count,
+ ik_size = sizeof(struct skeleton_ik) * skele->ik_count,
+ mtx_size = sizeof(m4x3f) * skele->bone_count,
+ anim_size = sizeof(struct skeleton_anim) * skele->anim_count;
+
+ skele->bones = vg_linear_alloc( lin_alloc, bone_size );
+ skele->ik = vg_linear_alloc( lin_alloc, ik_size );
+ //skele->final_mtx = vg_linear_alloc( lin_alloc, mtx_size );
+ skele->anims = vg_linear_alloc( lin_alloc, anim_size );
+
+ memset( skele->bones, 0, bone_size );
+ memset( skele->ik, 0, ik_size );
+ //memset( skele->final_mtx, 0, mtx_size );
+ memset( skele->anims, 0, anim_size );
+}
+
+static void skeleton_fatal_err(void){
+ vg_fatal_error( "Skeleton setup failed" );
+}
+
+/* Setup an animated skeleton from model. mdl's metadata should stick around */
+static void skeleton_setup( struct skeleton *skele,
+ void *lin_alloc, mdl_context *mdl ){
+ u32 ik_count = 0, collider_count = 0;
+ skele->bone_count = 0;
+ skele->bones = NULL;
+ //skele->final_mtx = NULL;
+ skele->anims = NULL;
+
+ if( !mdl->armatures.count ){
+ vg_error( "No skeleton in model\n" );
+ skeleton_fatal_err();
+ }
+
+ mdl_armature *armature = mdl_arritm( &mdl->armatures, 0 );
+ skeleton_alloc_from( skele, lin_alloc, mdl, armature );
+
+ for( u32 i=0; i<armature->bone_count; i++ ){
+ mdl_bone *bone = mdl_arritm( &mdl->bones, armature->bone_start+i );
+ struct skeleton_bone *sb = &skele->bones[i+1];
+
+ v3_copy( bone->co, sb->co );
+ v3_copy( bone->end, sb->end );
+
+ sb->parent = bone->parent;
+ sb->name = mdl_pstr( mdl, bone->pstr_name );
+ sb->flags = bone->flags;
+ sb->collider = bone->collider;
+ sb->orig_bone = bone;
+
+ if( sb->flags & k_bone_flag_ik ){
+ skele->bones[ sb->parent ].flags |= k_bone_flag_ik;
+
+ if( ik_count == skele->ik_count ){
+ vg_error( "Too many ik bones, corrupt model file\n" );
+ skeleton_fatal_err();
+ }
+
+ struct skeleton_ik *ik = &skele->ik[ ik_count ++ ];
+ ik->upper = i+1;
+ ik->lower = bone->parent;
+ ik->target = bone->ik_target;
+ ik->pole = bone->ik_pole;
+ }
+
+ box_copy( bone->hitbox, sb->hitbox );
+
+ if( bone->collider ){
+ if( collider_count == skele->collider_count ){
+ vg_error( "Too many collider bones\n" );
+ skeleton_fatal_err();
+ }
+
+ collider_count ++;
+ }
+ }
+
+ /* fill in implicit root bone */
+ v3_zero( skele->bones[0].co );
+ v3_copy( (v3f){0.0f,1.0f,0.0f}, skele->bones[0].end );
+ skele->bones[0].parent = 0xffffffff;
+ skele->bones[0].flags = 0;
+ skele->bones[0].name = "[root]";
+
+ /* process animation quick refs */
+ for( u32 i=0; i<skele->anim_count; i++ ){
+ mdl_animation *anim =
+ mdl_arritm( &mdl->animations, armature->anim_start+i );
+
+ skele->anims[i].rate = anim->rate;
+ skele->anims[i].length = anim->length;
+ skele->anims[i].name = mdl_pstr(mdl, anim->pstr_name);
+ skele->anims[i].anim_data =
+ mdl_arritm( &mdl->keyframes, anim->offset );
+
+ vg_info( "animation[ %f, %u ] '%s'\n", anim->rate,
+ anim->length,
+ skele->anims[i].name );
+ }
+
+ skeleton_create_inverses( skele );
+ vg_success( "Loaded skeleton with %u bones\n", skele->bone_count );
+ vg_success( " %u colliders\n", skele->collider_count );
+}
+
+static void skeleton_debug( struct skeleton *skele, m4x3f *final_mtx ){
+ for( u32 i=1; i<skele->bone_count; i ++ ){
+ struct skeleton_bone *sb = &skele->bones[i];
+
+ v3f p0, p1;
+ v3_copy( sb->co, p0 );
+ v3_add( p0, sb->end, p1 );
+
+ m4x3_mulv( final_mtx[i], p0, p0 );
+ m4x3_mulv( final_mtx[i], p1, p1 );
+
+ if( sb->flags & k_bone_flag_deform ){
+ if( sb->flags & k_bone_flag_ik ){
+ vg_line( p0, p1, 0xff0000ff );
+ }
+ else{
+ vg_line( p0, p1, 0xffcccccc );
+ }
+ }
+ else
+ vg_line( p0, p1, 0xff00ffff );
+ }
+}
--- /dev/null
+#include "vg/vg_steam.h"
+#include "vg/vg_steam_utils.h"
+#include "vg/vg_steam_networking.h"
+#include "vg/vg_steam_auth.h"
+#include "vg/vg_steam_http.h"
+#include "vg/vg_steam_friends.h"
+#include "vg/vg_steam_user_stats.h"
+#include "submodules/anyascii/impl/c/anyascii.c"
+#include "skaterift.h"
+#include <string.h>
+
+/*
+ * We only want to use steamworks if building for the networked version,
+ * theres not much point otherwise. We mainly want steamworks for setting
+ * achievements etc.. so that includes our own server too.
+ *
+ * This file also wraps the functions and interfaces that we want to use to
+ * make them a bit easier to read, since they are the flat API they have very
+ * long names. in non-networked builds they will return default errors or do
+ * nothing.
+ */
+
+char steam_username_at_startup[128] = "Unassigned";
+
+static void recv_steam_warning( int severity, const char *msg )
+{
+ if( severity == 0 )
+ vg_low( "%s\n", msg );
+ else
+ vg_info( "%s\n", msg );
+}
+
+int steam_ready = 0,
+ steam_stats_ready = 0;
+
+void *hSteamNetworkingSockets, *hSteamUser, *hSteamUserStats;
+static HSteamPipe hSteamClientPipe;
+
+static const char *steam_achievement_names[] =
+{
+ "ALBERT", "MARC", "JANET", "BERNADETTA",
+ "ROUTE_MPY", "ROUTE_MPG", "ROUTE_MPB", "ROUTE_MPR",
+ "ROUTE_TO", "ROUTE_TC", "CITY_COMPLETE", "MTZERO_SILVER", "MTZERO_GOLD",
+ "80FT"
+};
+
+void steam_store_achievements(void)
+{
+ if( steam_ready && steam_stats_ready ){
+ SteamAPI_ISteamUserStats_StoreStats( hSteamUserStats );
+ }
+}
+
+void update_ach_models(void);
+void steam_set_achievement( const char *name )
+{
+ if( skaterift.demo_mode )
+ return;
+
+ /* hack lol */
+ if( !strcmp(name,"MARC") ) skaterift.achievements |= 0x1;
+ if( !strcmp(name,"ALBERT") ) skaterift.achievements |= 0x2;
+ if( !strcmp(name,"JANET") ) skaterift.achievements |= 0x4;
+ if( !strcmp(name,"BERNADETTA") ) skaterift.achievements |= 0x8;
+ update_ach_models();
+
+ if( steam_ready && steam_stats_ready ){
+ if( SteamAPI_ISteamUserStats_SetAchievement( hSteamUserStats, name ) ){
+ vg_success( "Achievement set! '%s'\n", name );
+
+ }
+ else{
+ vg_warn( "Failed to set achievement: %s\n", name );
+ }
+ }
+ else{
+ vg_warn( "Failed to set achievement (steam not ready): %s\n", name );
+ }
+}
+
+void steam_clear_achievement( const char *name )
+{
+ if( steam_ready && steam_stats_ready ){
+ if( SteamAPI_ISteamUserStats_ClearAchievement( hSteamUserStats, name ) ){
+ vg_info( "Achievement cleared: '%s'\n", name );
+ }
+ else{
+ vg_warn( "Failed to clear achievement: %s\n", name );
+ }
+ }
+ else{
+ vg_warn( "Failed to clear achievement (steam not ready): %s\n", name );
+ }
+}
+
+
+void steam_print_all_achievements(void)
+{
+ vg_info( "Achievements: \n" );
+
+ if( steam_ready && steam_stats_ready ){
+ for( int i=0; i<VG_ARRAY_LEN(steam_achievement_names); i++ ){
+ steamapi_bool set = 0;
+ const char *name = steam_achievement_names[i];
+
+ if( SteamAPI_ISteamUserStats_GetAchievement(
+ hSteamUserStats, name, &set ) )
+ {
+ vg_info( " %s %s\n", (set? "[YES]": "[ ]"), name );
+ }
+ else{
+ vg_warn( " Error while fetching achievement status '%s'\n", name );
+ }
+ }
+ }
+ else{
+ vg_warn( " Steam is not initialized, no results\n" );
+ }
+}
+
+int steam_achievement_ccmd( int argc, char const *argv[] )
+{
+ if( !(steam_ready && steam_stats_ready) ) return 1;
+
+ if( argc == 1 ){
+ if( !strcmp( argv[0], "list" ) ){
+ steam_print_all_achievements();
+ return 0;
+ }
+ else if( !strcmp( argv[0], "clearall" )){
+ for( int i=0; i<VG_ARRAY_LEN(steam_achievement_names); i++ )
+ steam_clear_achievement( steam_achievement_names[i] );
+
+ steam_store_achievements();
+ }
+ }
+
+ if( argc == 2 ){
+ if( !strcmp( argv[0], "set" ) ){
+ steam_set_achievement( argv[1] );
+ steam_store_achievements();
+ return 0;
+ }
+ else if( strcmp( argv[0], "clear" ) ){
+ steam_clear_achievement( argv[1] );
+ steam_store_achievements();
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static void steam_on_recieve_current_stats( CallbackMsg_t *msg )
+{
+ UserStatsReceived_t *rec = (UserStatsReceived_t *)msg->m_pubParam;
+
+ if( rec->m_eResult == k_EResultOK ){
+ vg_info( "Recieved stats for: %lu (user: %lu)\n", rec->m_nGameID,
+ rec->m_steamIDUser );
+ steam_stats_ready = 1;
+
+ steamapi_bool set = 0;
+ if( SteamAPI_ISteamUserStats_GetAchievement(
+ hSteamUserStats, "MARC", &set ) ){
+ if( set ) skaterift.achievements |= 0x1;
+ }
+ if( SteamAPI_ISteamUserStats_GetAchievement(
+ hSteamUserStats, "ALBERT", &set ) ){
+ if( set ) skaterift.achievements |= 0x2;
+ }
+ if( SteamAPI_ISteamUserStats_GetAchievement(
+ hSteamUserStats, "JANET", &set ) ){
+ if( set ) skaterift.achievements |= 0x4;
+ }
+ if( SteamAPI_ISteamUserStats_GetAchievement(
+ hSteamUserStats, "BERNADETTA", &set ) ){
+ if( set ) skaterift.achievements |= 0x8;
+ }
+ update_ach_models();
+ }
+ else{
+ vg_error( "Error recieveing stats for user (%u)\n", rec->m_eResult );
+ }
+}
+
+static u32 utf8_byte0_byte_count( u8 char0 )
+{
+ for( u32 k=2; k<4; k++ ){
+ if( !(char0 & (0x80 >> k)) )
+ return k;
+ }
+
+ return 0;
+}
+
+u32 str_utf8_collapse( const char *str, char *buf, u32 length )
+{
+ u8 *ustr = (u8 *)str;
+ u32 utf32_code = 0x00000000;
+ u32 i=0, j=0, utf32_byte_ct=0;
+
+ for(;j < length-1;){
+ if( ustr[i] == 0x00 )
+ break;
+
+ if( ustr[i] & 0x80 ){
+ if( utf32_byte_ct ){
+ utf32_byte_ct --;
+ utf32_code |= (ustr[i] & 0x3F) << (utf32_byte_ct*6);
+
+ if( !utf32_byte_ct ){
+ const char *match;
+ size_t chars = anyascii( utf32_code, &match );
+
+ for( u32 k=0; k<VG_MIN(chars, length-1-j); k++ ){
+ buf[ j++ ] = (u8)match[k];
+ }
+ }
+ }
+ else{
+ utf32_byte_ct = utf8_byte0_byte_count( ustr[i] )-1;
+ utf32_code = ustr[i] & (0x3F >> utf32_byte_ct);
+ utf32_code <<= utf32_byte_ct*6;
+ }
+ }
+ else{
+ utf32_byte_ct = 0x00;
+ buf[j ++] = str[i];
+ }
+
+ i++;
+ }
+
+ buf[j] = 0x00;
+ return j;
+}
+
+int steam_init(void)
+{
+ const char *username = "offline player";
+
+ vg_info( "Initializing steamworks\n" );
+
+ if( !SteamAPI_Init() ){
+ printf("\n");
+ vg_error( "Steamworks failed to initialize\n" );
+ return 1;
+ }
+
+ steam_ready = 1;
+
+ SteamAPI_ManualDispatch_Init();
+
+ /* Connect interfaces */
+ hSteamClientPipe = SteamAPI_GetHSteamPipe();
+ hSteamNetworkingSockets = SteamAPI_SteamNetworkingSockets_SteamAPI();
+ hSteamUser = SteamAPI_SteamUser();
+
+ ISteamUtils *utils = SteamAPI_SteamUtils();
+ SteamAPI_ISteamUtils_SetWarningMessageHook( utils, recv_steam_warning );
+
+ printf("\n");
+ vg_success( "\nSteamworks API running\n" );
+
+ ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
+ username = SteamAPI_ISteamFriends_GetPersonaName( hSteamFriends );
+
+ /*
+ * Request stats
+ * --------------------------------------------------------
+ */
+ hSteamUserStats = SteamAPI_SteamUserStats();
+ steam_register_callback( k_iUserStatsReceived,
+ steam_on_recieve_current_stats );
+
+ if( !SteamAPI_ISteamUserStats_RequestCurrentStats( hSteamUserStats ) )
+ vg_warn( "No Steam Logon: Cannot request stats\n" );
+
+
+ vg_console_reg_cmd( "ach", steam_achievement_ccmd, NULL );
+
+ /* TODO: On username update callback */
+ str_utf8_collapse( username, steam_username_at_startup,
+ VG_ARRAY_LEN(steam_username_at_startup) );
+
+ return 1;
+}
+
+void steam_update(void)
+{
+ if( steam_ready ){
+ steamworks_event_loop( hSteamClientPipe );
+ }
+}
+
+void steam_end(void)
+{
+ if( steam_ready ){
+ vg_info( "Shutting down\n..." );
+ SteamAPI_Shutdown();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * All trademarks are property of their respective owners
+ */
+#pragma once
+
+extern int steam_ready, steam_stats_ready;
+extern void *hSteamNetworkingSockets, *hSteamUser, *hSteamUserStats;
+extern char steam_username_at_startup[128];
+
+int steam_init(void);
+void steam_update(void);
+void steam_end(void);
+u32 str_utf8_collapse( const char *str, char *buf, u32 length );
+int steam_achievement_ccmd( int argc, char const *argv[] );
+void steam_print_all_achievements(void);
+void steam_clear_achievement( const char *name );
+void steam_set_achievement( const char *name );
+void steam_store_achievements(void);
--- /dev/null
+#ifndef TRAFFIC_H
+#define TRAFFIC_H
+
+#include "common.h"
+#include "model.h"
+#include "rigidbody.h"
+#include "world.h"
+
+typedef struct traffic_node traffic_node;
+typedef struct traffic_driver traffic_driver;
+
+struct traffic_node
+{
+ v3f co, h;
+
+ union
+ {
+ struct{ traffic_node *next, *next1; };
+ struct{ mdl_node *mn_next, *mn_next1; };
+ };
+};
+
+struct traffic_driver
+{
+ m4x3f transform;
+
+ traffic_node *current;
+ int option;
+ float t, speed;
+};
+
+static float eval_bezier_length( v3f p0, v3f p1, v3f h0, v3f h1, int res )
+{
+ float length = 0.0f, m = 1.0f/(float)res;
+ v3f l, p;
+ v3_copy( p0, l );
+
+ for( int i=0; i<res; i++ )
+ {
+ float t = (float)(i+1)*m;
+ eval_bezier_time(p0,p1,h0,h1,t,p);
+ length += v3_dist( p,l );
+ v3_copy( p, l );
+ }
+
+ return length;
+}
+
+static void traffic_finalize( traffic_node *system, int count )
+{
+ for( int i=0; i<count; i++ )
+ {
+ traffic_node *tn = &system[i];
+
+ if( tn->mn_next )
+ tn->next = &system[ tn->mn_next->sub_uid ];
+ if( tn->mn_next1 )
+ tn->next1 = &system[ tn->mn_next1->sub_uid ];
+ }
+}
+
+static void traffic_visualize_link( traffic_node *ta, traffic_node *tb )
+{
+ v3f p0, p1, h0, h1, p, l;
+
+ if( !tb ) return;
+
+ v3_copy( ta->co, p0 );
+ v3_muladds( ta->co, ta->h, 1.0f, h0 );
+ v3_copy( tb->co, p1 );
+ v3_muladds( tb->co, tb->h, -1.0f, h1 );
+ v3_copy( p0, l );
+
+ vg_line_pt3( h0, 0.2f, 0xff00ff00 );
+ vg_line_pt3( h1, 0.2f, 0xffff00ff );
+ vg_line( p0, h0, 0xff000000 );
+ vg_line( p1, h1, 0xff000000 );
+
+ for( int i=0; i<5; i++ )
+ {
+ float t = (float)(i+1)/5.0f;
+ eval_bezier_time( p0, p1, h0, h1, t, p );
+
+ vg_line( p, l, 0xffffffff );
+ v3_copy( p, l );
+ }
+}
+
+static void sample_wheel_floor( v3f pos )
+{
+ v3f ground;
+ v3_copy( pos, ground );
+ ground[1] += 4.0f;
+
+ ray_hit hit;
+ hit.dist = 8.0f;
+
+ if( ray_world( ground, (v3f){0.0f,-1.0f,0.0f}, &hit ))
+ {
+ v3_copy( hit.pos, pos );
+ }
+}
+
+static void traffic_drive( traffic_driver *driver )
+{
+ traffic_node *next, *current = driver->current;
+
+ if( !current ) return;
+ next = driver->option==0? current->next: current->next1;
+
+ if( driver->t > 1.0f )
+ {
+ driver->t = driver->t - floorf( driver->t );
+ driver->current = driver->option==0? current->next: current->next1;
+ driver->option = 0;
+
+ current = driver->current;
+ if( !current )
+ return;
+
+ if( current->next && current->next1 )
+ if( vg_randf() > 0.5f )
+ driver->option = 1;
+ }
+
+ traffic_visualize_link( current, next );
+
+ /*
+ * Calculate the speed of the curve at the current point. On the reference
+ * curve the rate should come out to be exactly 1 ktimestep traveled.
+ * Dividing this distance by ktimestep gives us the modifier to use.
+ */
+ v3f p0,p1,h0,h1,pc,pn;
+
+ v3_copy( current->co, p0 );
+ v3_muladds( current->co, current->h, 1.0f, h0 );
+ v3_copy( next->co, p1 );
+ v3_muladds( next->co, next->h, -1.0f, h1 );
+
+ eval_bezier_time( p0,p1,h0,h1, driver->t, pc );
+ eval_bezier_time( p0,p1,h0,h1, driver->t + vg.time_delta, pn );
+
+ float mod = vg.time_delta / v3_dist( pc, pn );
+ v3f dir,side,up;
+ v3_sub( pn, pc, dir );
+ v3_normalize(dir);
+
+ /*
+ * Stick the car on the ground by casting rays where the wheels are
+ */
+ side[0] = -dir[2];
+ side[1] = 0.0f;
+ side[2] = dir[0];
+ v3_normalize(side);
+
+ v3f fl, fr, bc;
+ v3_muladds( pc, dir, 2.0f, fr );
+ v3_muladds( pc, dir, 2.0f, fl );
+ v3_muladds( pc, dir, -2.0f, bc );
+ v3_muladds( fr, side, 1.0f, fr );
+ v3_muladds( fl, side, -1.0f, fl );
+
+ sample_wheel_floor( fl );
+ sample_wheel_floor( fr );
+ sample_wheel_floor( bc );
+
+ vg_line( fl, fr, 0xff00ffff );
+ vg_line( fr, bc, 0xff00ffff );
+ vg_line( bc, fl, 0xff00ffff );
+
+ v3f norm;
+ v3f v0, v1;
+ v3_sub( fr, fl, v0 );
+ v3_sub( bc, fl, v1 );
+ v3_cross( v1, v0, norm );
+ v3_normalize( norm );
+
+ /*
+ * Jesus take the wheel
+ */
+ float steer_penalty = 1.0f-v3_dot( dir, driver->transform[0] );
+ steer_penalty /= vg.time_delta;
+ steer_penalty *= 30.0f;
+
+ float target_speed = vg_maxf( 16.0f * (1.0f-steer_penalty), 0.1f ),
+ accel = target_speed - driver->speed;
+ driver->speed = stable_force( driver->speed, accel*vg.time_delta*2.0f );
+ driver->t += driver->speed*mod*vg.time_delta;
+
+ /*
+ * Update transform
+ */
+ v3_cross( dir, norm, side );
+ v3_copy( dir, driver->transform[0] );
+ v3_copy( norm, driver->transform[1] );
+ v3_copy( side, driver->transform[2] );
+
+ v3_add( fl, fr, pc );
+ v3_add( bc, pc, pc );
+ v3_muls( pc, 1.0f/3.0f, pc );
+ v3_copy( pc, driver->transform[3] );
+}
+
+static void traffic_visualize( traffic_node *system, int count )
+{
+ for( int i=0; i<count; i++ )
+ {
+ traffic_node *tn = &system[i];
+
+ traffic_visualize_link( tn, tn->next );
+ traffic_visualize_link( tn, tn->next1 );
+ }
+}
+
+static void traffic_visualize_car( traffic_driver *driver )
+{
+ vg_line_boxf_transformed( driver->transform,
+ (boxf){{-1.0f,0.0f,-0.5f},
+ { 1.0f,0.0f, 0.5f}}, 0xff00ff00 );
+}
+
+#endif /* TRAFFIC_H */
--- /dev/null
+#pragma once
+#include "vg/vg_engine.h"
+#include "vg/vg_platform.h"
+#include "vg/vg_m.h"
+#include "vg/vg_lines.h"
+#include "vg/vg_async.h"
+#include "vg/vg_camera.h"
+#include "trail.h"
+#include "shaders/particle.h"
+#include "shaders/trail.h"
+
+static void trail_increment( trail_system *sys ){
+ sys->head ++;
+
+ if( sys->head == sys->max )
+ sys->head = 0;
+
+ /* undesirable effect: will remove active points if out of space! */
+ if( sys->count < sys->max )
+ sys->count ++;
+}
+
+void trail_system_update( trail_system *sys, f32 dt,
+ v3f co, v3f normal, f32 alpha )
+{
+ /* update existing points and clip dead ones */
+ bool clip_allowed = 1;
+ for( i32 i=0; i<sys->count; i ++ ){
+ i32 i0 = sys->head - sys->count + i;
+ if( i0 < 0 ) i0 += sys->max;
+
+ trail_point *p0 = &sys->array[i0];
+ p0->alpha -= dt/sys->lifetime;
+
+ if( clip_allowed ){
+ if( p0->alpha <= 0.0f )
+ sys->count --;
+ else
+ clip_allowed = 0;
+ }
+ }
+
+ i32 icur = sys->head -1,
+ iprev = sys->head -2,
+ ihead = sys->head;
+
+ if( icur < 0 ) icur += sys->max;
+ if( iprev < 0 ) iprev += sys->max;
+
+ trail_point *pcur = &sys->array[ icur ],
+ *pprev = &sys->array[ iprev ],
+ *phead = &sys->array[ ihead ],
+ *pdest = NULL;
+ v3f dir;
+
+ f32 k_min = 0.001f;
+
+ if( sys->count == 0 ){
+ trail_increment( sys );
+ v3_copy( (v3f){0,0,-1}, dir );
+ pdest = phead;
+ }
+ else if( sys->count == 1 ){
+ if( v3_dist2( pcur->co, co ) < k_min*k_min )
+ return;
+
+ trail_increment( sys );
+ pdest = phead;
+ v3_sub( co, pcur->co, dir );
+ }
+ else {
+ if( v3_dist2( pprev->co, co ) < k_min*k_min )
+ return;
+
+ if( v3_dist2( pprev->co, co ) > sys->min_dist*sys->min_dist ){
+ trail_increment( sys );
+ pdest = phead;
+ }
+ else
+ pdest = pcur;
+
+ v3_sub( co, pprev->co, dir );
+ }
+
+ v3_cross( dir, normal, pdest->right );
+ v3_normalize( pdest->right );
+ v3_copy( co, pdest->co );
+ v3_copy( normal, pdest->normal );
+ pdest->alpha = alpha;
+}
+
+void trail_system_debug( trail_system *sys )
+{
+ for( i32 i=0; i<sys->count; i ++ ){
+ i32 i0 = sys->head - sys->count + i;
+ if( i0 < 0 ) i0 += sys->max;
+
+ trail_point *p0 = &sys->array[i0];
+ vg_line_point( p0->co, 0.04f, 0xff000000 | (u32)(p0->alpha*255.0f) );
+ vg_line_arrow( p0->co, p0->right, 0.3f, VG__GREEN );
+
+ if( i == sys->count-1 ) break;
+
+ i32 i1 = i0+1;
+ if( i1 == sys->max ) i1 = 0;
+
+ trail_point *p1 = &sys->array[i1];
+ vg_line( p0->co, p1->co, VG__RED );
+ }
+}
+
+struct trail_init_args {
+ trail_system *sys;
+};
+
+void async_trail_init( void *payload, u32 size )
+{
+ struct trail_init_args *args = payload;
+ trail_system *sys = args->sys;
+
+ glGenVertexArrays( 1, &sys->vao );
+ glGenBuffers( 1, &sys->vbo );
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(trail_vert);
+
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferData( GL_ARRAY_BUFFER, sys->max*stride*2, NULL, GL_DYNAMIC_DRAW );
+
+ /* 0: coordinates */
+ glVertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, stride, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+}
+
+void trail_alloc( trail_system *sys, u32 max )
+{
+ size_t stride = sizeof(trail_vert);
+ sys->max = max;
+ sys->array = vg_linear_alloc( vg_mem.rtmemory, max*sizeof(trail_point) );
+ sys->vertices = vg_linear_alloc( vg_mem.rtmemory, max*stride*2 );
+
+ vg_async_item *call = vg_async_alloc( sizeof(struct trail_init_args) );
+
+ struct trail_init_args *init = call->payload;
+ init->sys = sys;
+ vg_async_dispatch( call, async_trail_init );
+}
+
+void trail_system_prerender( trail_system *sys )
+{
+ if( sys->count < 2 ) return;
+
+ for( i32 i=0; i<sys->count; i ++ ){
+ i32 i0 = sys->head - sys->count + i;
+ if( i0 < 0 ) i0 += sys->max;
+
+ trail_point *p0 = &sys->array[i0];
+ trail_vert *v0 = &sys->vertices[i*2+0],
+ *v1 = &sys->vertices[i*2+1];
+
+ v3_muladds( p0->co, p0->right, -sys->width, v0->co );
+ v3_muladds( p0->co, p0->right, sys->width, v1->co );
+ v0->co[3] = p0->alpha;
+ v1->co[3] = p0->alpha;
+ }
+
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(trail_vert);
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferSubData( GL_ARRAY_BUFFER, 0, sys->count*stride*2, sys->vertices );
+}
+
+void trail_system_render( trail_system *sys, vg_camera *cam )
+{
+ if( sys->count < 2 ) return;
+ glDisable( GL_CULL_FACE );
+ glEnable( GL_DEPTH_TEST );
+
+ shader_trail_use();
+ shader_trail_uPv( cam->mtx.pv );
+ shader_trail_uPvPrev( cam->mtx_prev.pv );
+ shader_trail_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
+
+ glBindVertexArray( sys->vao );
+ glDrawArrays( GL_TRIANGLE_STRIP, 0, sys->count*2 );
+}
--- /dev/null
+#pragma once
+
+typedef struct trail_system trail_system;
+typedef struct trail_point trail_point;
+typedef struct trail_vert trail_vert;
+
+struct trail_system {
+ struct trail_point {
+ v3f co, normal, right;
+ f32 alpha;
+ }
+ *array;
+
+#pragma pack(push,1)
+ struct trail_vert {
+ v4f co; /* xyz: position, w: alpha */
+ }
+ *vertices;
+#pragma pack(pop)
+
+ i32 head, count, max;
+ GLuint vao, vbo;
+
+ /* render settings */
+ f32 width, lifetime, min_dist;
+};
+
+void trail_alloc( trail_system *sys, u32 max );
+void trail_system_update( trail_system *sys, f32 dt, v3f co,
+ v3f normal, f32 alpha );
+void trail_system_debug( trail_system *sys );
+void trail_system_prerender( trail_system *sys );
+void trail_system_render( trail_system *sys, vg_camera *cam );
--- /dev/null
+#include "skaterift.h"
+#include "vehicle.h"
+#include "scene_rigidbody.h"
+
+struct drivable_vehicle gzoomer =
+{
+ .rb.co = {-2000,-2000,-2000}
+};
+
+int spawn_car( int argc, const char *argv[] )
+{
+ v3f ra, rb, rx;
+ v3_copy( g_render.cam.pos, ra );
+ v3_muladds( ra, g_render.cam.transform[2], -10.0f, rb );
+
+ float t;
+ if( spherecast_world( world_current_instance(),
+ ra, rb, 1.0f, &t, rx, 0 ) != -1 )
+ {
+ v3_lerp( ra, rb, t, gzoomer.rb.co );
+ gzoomer.rb.co[1] += 4.0f;
+ q_axis_angle( gzoomer.rb.q, (v3f){1.0f,0.0f,0.0f}, 0.001f );
+ v3_zero( gzoomer.rb.v );
+ v3_zero( gzoomer.rb.w );
+
+ rb_update_matrices( &gzoomer.rb );
+ gzoomer.alive = 1;
+
+ vg_success( "Spawned car\n" );
+ }
+ else{
+ vg_error( "Can't spawn here\n" );
+ }
+
+ return 0;
+}
+
+void vehicle_init(void)
+{
+ q_identity( gzoomer.rb.q );
+ v3_zero( gzoomer.rb.w );
+ v3_zero( gzoomer.rb.v );
+ v3_zero( gzoomer.rb.co );
+ rb_setbody_sphere( &gzoomer.rb, 1.0f, 8.0f, 1.0f );
+
+ VG_VAR_F32( k_car_spring, flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_spring_damp, flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_spring_length, flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_wheel_radius, flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_friction_lat, flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_friction_roll, flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_drive_force, flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_air_resistance,flags=VG_VAR_PERSISTENT );
+ VG_VAR_F32( k_car_downforce, flags=VG_VAR_PERSISTENT );
+
+ VG_VAR_I32( gzoomer.inside );
+
+ vg_console_reg_cmd( "spawn_car", spawn_car, NULL );
+
+ v3_copy((v3f){ -1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[0] );
+ v3_copy((v3f){ 1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[1] );
+ v3_copy((v3f){ -1.0f, -0.25f, 1.5f }, gzoomer.wheels_local[2] );
+ v3_copy((v3f){ 1.0f, -0.25f, 1.5f }, gzoomer.wheels_local[3] );
+}
+
+void vehicle_wheel_force( int index )
+{
+ v3f pa, pb, n;
+ m4x3_mulv( gzoomer.rb.to_world, gzoomer.wheels_local[index], pa );
+ v3_muladds( pa, gzoomer.rb.to_world[1], -k_car_spring_length, pb );
+
+
+#if 1
+ float t;
+ if( spherecast_world( world_current_instance(), pa, pb,
+ k_car_wheel_radius, &t, n, 0 ) == -1 )
+ { t = 1.0f;
+ }
+
+#else
+
+ v3f dir;
+ v3_muls( gzoomer.rb.up, -1.0f, dir );
+
+ ray_hit hit;
+ hit.dist = k_car_spring_length;
+ ray_world( pa, dir, &hit );
+
+ float t = hit.dist / k_car_spring_length;
+
+#endif
+
+ v3f pc;
+ v3_lerp( pa, pb, t, pc );
+
+ m4x3f mtx;
+ m3x3_copy( gzoomer.rb.to_world, mtx );
+ v3_copy( pc, mtx[3] );
+ vg_line_sphere( mtx, k_car_wheel_radius, VG__BLACK );
+ vg_line( pa, pc, VG__WHITE );
+ v3_copy( pc, gzoomer.wheels[index] );
+
+ if( t < 1.0f ){
+ /* spring force */
+ float Fv = (1.0f-t) * k_car_spring*vg.time_fixed_delta;
+
+ v3f delta;
+ v3_sub( pa, gzoomer.rb.co, delta );
+
+ v3f rv;
+ v3_cross( gzoomer.rb.w, delta, rv );
+ v3_add( gzoomer.rb.v, rv, rv );
+
+ Fv += v3_dot(rv, gzoomer.rb.to_world[1])
+ * -k_car_spring_damp*vg.time_fixed_delta;
+
+ /* scale by normal incident */
+ Fv *= v3_dot( n, gzoomer.rb.to_world[1] );
+
+ v3f F;
+ v3_muls( gzoomer.rb.to_world[1], Fv, F );
+ rb_linear_impulse( &gzoomer.rb, delta, F );
+
+ /* friction vectors
+ * -------------------------------------------------------------*/
+ v3f tx, ty;
+
+ if( index <= 1 )
+ v3_cross( gzoomer.steerv, n, tx );
+ else
+ v3_cross( n, gzoomer.rb.to_world[2], tx );
+ v3_cross( tx, n, ty );
+
+ v3_copy( tx, gzoomer.tangent_vectors[ index ][0] );
+ v3_copy( ty, gzoomer.tangent_vectors[ index ][1] );
+
+ gzoomer.normal_forces[ index ] = Fv;
+ gzoomer.tangent_forces[ index ][0] = 0.0f;
+ gzoomer.tangent_forces[ index ][1] = 0.0f;
+
+ /* orient inverse inertia tensors */
+ v3f raW;
+ m3x3_mulv( gzoomer.rb.to_world, gzoomer.wheels_local[index], raW );
+
+ v3f raCtx, raCtxI, raCty, raCtyI;
+ v3_cross( tx, raW, raCtx );
+ v3_cross( ty, raW, raCty );
+ m3x3_mulv( gzoomer.rb.iIw, raCtx, raCtxI );
+ m3x3_mulv( gzoomer.rb.iIw, raCty, raCtyI );
+
+ gzoomer.tangent_mass[index][0] = gzoomer.rb.inv_mass;
+ gzoomer.tangent_mass[index][0] += v3_dot( raCtx, raCtxI );
+ gzoomer.tangent_mass[index][0] = 1.0f/gzoomer.tangent_mass[index][0];
+
+ gzoomer.tangent_mass[index][1] = gzoomer.rb.inv_mass;
+ gzoomer.tangent_mass[index][1] += v3_dot( raCty, raCtyI );
+ gzoomer.tangent_mass[index][1] = 1.0f/gzoomer.tangent_mass[index][1];
+
+ /* apply drive force */
+ if( index >= 2 ){
+ v3_muls( ty, -gzoomer.drive * k_car_drive_force
+ * vg.time_fixed_delta, F );
+ rb_linear_impulse( &gzoomer.rb, raW, F );
+ }
+ }
+ else{
+ gzoomer.normal_forces[ index ] = 0.0f;
+ gzoomer.tangent_forces[ index ][0] = 0.0f;
+ gzoomer.tangent_forces[ index ][1] = 0.0f;
+ }
+}
+
+void vehicle_solve_friction(void)
+{
+ rigidbody *rb = &gzoomer.rb;
+ for( int i=0; i<4; i++ ){
+ v3f raW;
+ m3x3_mulv( rb->to_world, gzoomer.wheels_local[i], raW );
+
+ v3f rv;
+ v3_cross( rb->w, raW, rv );
+ v3_add( rb->v, rv, rv );
+
+ float fx = k_car_friction_lat * gzoomer.normal_forces[i],
+ fy = k_car_friction_roll * gzoomer.normal_forces[i],
+ vtx = v3_dot( rv, gzoomer.tangent_vectors[i][0] ),
+ vty = v3_dot( rv, gzoomer.tangent_vectors[i][1] ),
+ lambdax = gzoomer.tangent_mass[i][0] * -vtx,
+ lambday = gzoomer.tangent_mass[i][1] * -vty;
+
+ float tempx = gzoomer.tangent_forces[i][0],
+ tempy = gzoomer.tangent_forces[i][1];
+ gzoomer.tangent_forces[i][0] = vg_clampf( tempx + lambdax, -fx, fx );
+ gzoomer.tangent_forces[i][1] = vg_clampf( tempy + lambday, -fy, fy );
+ lambdax = gzoomer.tangent_forces[i][0] - tempx;
+ lambday = gzoomer.tangent_forces[i][1] - tempy;
+
+ v3f impulsex, impulsey;
+ v3_muls( gzoomer.tangent_vectors[i][0], lambdax, impulsex );
+ v3_muls( gzoomer.tangent_vectors[i][1], lambday, impulsey );
+ rb_linear_impulse( rb, raW, impulsex );
+ rb_linear_impulse( rb, raW, impulsey );
+ }
+}
+
+void vehicle_update_fixed(void)
+{
+ if( !gzoomer.alive )
+ return;
+
+ rigidbody *rb = &gzoomer.rb;
+
+ v3_muls( rb->to_world[2], -cosf(gzoomer.steer), gzoomer.steerv );
+ v3_muladds( gzoomer.steerv, rb->to_world[0],
+ sinf(gzoomer.steer), gzoomer.steerv );
+
+ /* apply air resistance */
+ v3f Fair, Fdown;
+
+ v3_muls( rb->v, -k_car_air_resistance, Fair );
+ v3_muls( rb->to_world[1], -fabsf(v3_dot( rb->v, rb->to_world[2] )) *
+ k_car_downforce, Fdown );
+
+ v3_muladds( rb->v, Fair, vg.time_fixed_delta, rb->v );
+ v3_muladds( rb->v, Fdown, vg.time_fixed_delta, rb->v );
+
+ for( int i=0; i<4; i++ )
+ vehicle_wheel_force( i );
+
+ rigidbody _null = {0};
+ _null.inv_mass = 0.0f;
+ m3x3_zero( _null.iI );
+
+ rb_ct manifold[64];
+ int len = rb_sphere__scene( rb->to_world, 1.0f, NULL,
+ world_current_instance()->geo_bh,
+ manifold, 0 );
+ for( int j=0; j<len; j++ ){
+ manifold[j].rba = rb;
+ manifold[j].rbb = &_null;
+ }
+ rb_manifold_filter_coplanar( manifold, len, 0.05f );
+
+ if( len > 1 ){
+ rb_manifold_filter_backface( manifold, len );
+ rb_manifold_filter_joint_edges( manifold, len, 0.05f );
+ rb_manifold_filter_pairs( manifold, len, 0.05f );
+ }
+ len = rb_manifold_apply_filtered( manifold, len );
+
+ rb_presolve_contacts( manifold, vg.time_fixed_delta, len );
+ for( int i=0; i<8; i++ ){
+ rb_solve_contacts( manifold, len );
+ vehicle_solve_friction();
+ }
+
+ rb_iter( rb );
+ rb_update_matrices( rb );
+}
+
+void vehicle_update_post(void)
+{
+ if( !gzoomer.alive )
+ return;
+
+ vg_line_sphere( gzoomer.rb.to_world, 1.0f, VG__WHITE );
+
+ /* draw friction vectors */
+ v3f p0, px, py;
+
+ for( int i=0; i<4; i++ ){
+ v3_copy( gzoomer.wheels[i], p0 );
+ v3_muladds( p0, gzoomer.tangent_vectors[i][0], 0.5f, px );
+ v3_muladds( p0, gzoomer.tangent_vectors[i][1], 0.5f, py );
+
+ vg_line( p0, px, VG__RED );
+ vg_line( p0, py, VG__GREEN );
+ }
+}
--- /dev/null
+#pragma once
+#include "vg/vg_rigidbody.h"
+#include "player.h"
+#include "world.h"
+#include "world_physics.h"
+
+static float k_car_spring = 1.0f,
+ k_car_spring_damp = 0.001f,
+ k_car_spring_length = 0.5f,
+ k_car_wheel_radius = 0.2f,
+ k_car_friction_lat = 0.6f,
+ k_car_friction_roll = 0.01f,
+ k_car_drive_force = 1.0f,
+ k_car_air_resistance = 0.1f,
+ k_car_downforce = 0.5f;
+
+typedef struct drivable_vehicle drivable_vehicle;
+struct drivable_vehicle
+{
+ int alive, inside;
+ rigidbody rb;
+
+ v3f wheels[4];
+
+ float tangent_mass[4][2],
+ normal_forces[4],
+ tangent_forces[4][2];
+
+ float steer, drive;
+ v3f steerv;
+
+ v3f tangent_vectors[4][2];
+ v3f wheels_local[4];
+}
+extern gzoomer;
+
+int spawn_car( int argc, const char *argv[] );
+void vehicle_init(void);
+void vehicle_wheel_force( int index );
+void vehicle_solve_friction(void);
+void vehicle_update_fixed(void);
+void vehicle_update_post(void);
--- /dev/null
+#include "vg/vg_engine.h"
+#include "vg/vg_tex.h"
+#include "vg/vg_image.h"
+#include "vg/vg_msg.h"
+#include "vg/vg_binstr.h"
+#include "vg/vg_loader.h"
+#include "vg/vg_io.h"
+#include "ent_skateshop.h"
+
+#include "vg/vg_steam_auth.h"
+#include "vg/vg_steam_ugc.h"
+#include "vg/vg_steam_friends.h"
+#include "steam.h"
+#include "workshop.h"
+
+struct workshop_form workshop_form;
+
+static struct ui_enum_opt workshop_form_visibility_opts[] = {
+ { k_ERemoteStoragePublishedFileVisibilityPublic, "Public" },
+ { k_ERemoteStoragePublishedFileVisibilityUnlisted, "Unlisted" },
+ { k_ERemoteStoragePublishedFileVisibilityFriendsOnly, "Friends Only" },
+ { k_ERemoteStoragePublishedFileVisibilityPrivate, "Private" },
+};
+
+static struct ui_enum_opt workshop_form_type_opts[] = {
+ { k_addon_type_none, "None" },
+ { k_addon_type_board, "Board" },
+ { k_addon_type_world, "World" },
+ { k_addon_type_player, "Player" },
+};
+
+/*
+ * Close the form and discard UGC query result
+ */
+static void workshop_quit_form(void){
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+
+ if( workshop_form.ugc_query.result == k_EResultOK ){
+ workshop_form.ugc_query.result = k_EResultNone;
+
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(
+ hSteamUGC, workshop_form.ugc_query.handle );
+ }
+
+ workshop_form.page = k_workshop_form_hidden;
+ workshop_form.op = k_workshop_op_none;
+}
+
+/*
+ * Delete all information about the submission
+ */
+static void workshop_reset_submission_data(void)
+{
+ workshop_form.submission.file_id = 0; /* assuming id of 0 is none/invalid */
+ workshop_form.submission.description[0] = '\0';
+ workshop_form.submission.title[0] = '\0';
+ workshop_form.submission.author[0] = '\0';
+ workshop_form.submission.submission_type_selection =
+ k_addon_type_none;
+ workshop_form.submission.type = k_addon_type_none;
+
+ workshop_form.submission.visibility =
+ k_ERemoteStoragePublishedFileVisibilityPublic;
+
+ workshop_form.addon_folder[0] = '\0';
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+}
+
+
+/*
+ * Mostly copies of what it sais on the Steam API documentation
+ */
+static const char *workshop_EResult_user_string( EResult result )
+{
+ switch( result ){
+ case k_EResultInsufficientPrivilege:
+ return "Your account is currently restricted from uploading content "
+ "due to a hub ban, account lock, or community ban. You need to "
+ "contact Steam Support to resolve the issue.";
+ case k_EResultBanned:
+ return "You do not have permission to upload content to this hub "
+ "because you have an active VAC or Game ban.";
+ case k_EResultTimeout:
+ return "The operation took longer than expected, so it was discarded. "
+ "Please try again.";
+ case k_EResultNotLoggedOn:
+ return "You are currently not logged into Steam.";
+ case k_EResultServiceUnavailable:
+ return "The workshop server is having issues or is unavailable, "
+ "please try again.";
+ case k_EResultInvalidParam:
+ return "One of the submission fields contains something not being "
+ "accepted by that field.";
+ case k_EResultAccessDenied:
+ return "There was a problem trying to save the title and description. "
+ "Access was denied.";
+ case k_EResultLimitExceeded:
+ return "You have exceeded your Steam Cloud quota. If you wish to "
+ "upload this file, you must remove some published items.";
+ case k_EResultFileNotFound:
+ return "The uploaded file could not be found.";
+ case k_EResultDuplicateRequest:
+ return "The file was already successfully uploaded.";
+ case k_EResultDuplicateName:
+ return "You already have a Steam Workshop item with that name.";
+ case k_EResultServiceReadOnly:
+ return "Due to a recent password or email change, you are not allowed "
+ "to upload new content. Usually this restriction will expire in"
+ " 5 days, but can last up to 30 days if the account has been "
+ "inactive recently.";
+ default:
+ return "Operation failed for an error which has not been accounted for "
+ "by the programmer. Try again, sorry :)";
+ }
+}
+
+/*
+ * op: k_workshop_form_op_publishing_update
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * The endpoint of this operation
+ */
+static void on_workshop_update_result( void *data, void *user )
+{
+ vg_info( "Recieved workshop update result\n" );
+ SubmitItemUpdateResult_t *result = data;
+
+ /* this seems to be set here, but my account definitely has accepted it */
+ if( result->m_bUserNeedsToAcceptWorkshopLegalAgreement ){
+ vg_warn( "Workshop agreement currently not accepted\n" );
+ }
+
+ if( result->m_eResult == k_EResultOK ){
+ workshop_form.page = k_workshop_form_closing_good;
+ workshop_form.failure_or_success_string = "Uploaded workshop file!";
+ vg_success( "file uploaded\n" );
+ }
+ else{
+ workshop_form.page = k_workshop_form_closing_bad;
+ workshop_form.failure_or_success_string =
+ workshop_EResult_user_string( result->m_eResult );
+
+ vg_error( "Error with the submitted file (%d)\n", result->m_eResult );
+ }
+ workshop_form.op = k_workshop_op_none;
+}
+
+static const char *workshop_filetype_folder(void){
+ enum addon_type type = workshop_form.submission.type;
+ if ( type == k_addon_type_board ) return "boards/";
+ else if( type == k_addon_type_player ) return "playermodels/";
+ else if( type == k_addon_type_world ) return "maps/";
+
+ return "unknown_addon_type/";
+}
+
+/*
+ * reciever on completion of packaging the files, it will then start the item
+ * update with Steam API
+ */
+static void workshop_form_upload_submission( PublishedFileId_t file_id,
+ char *metadata )
+{
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ UGCUpdateHandle_t handle
+ = SteamAPI_ISteamUGC_StartItemUpdate( hSteamUGC, SKATERIFT_APPID,
+ file_id );
+
+ /* TODO: Handle failure cases for these */
+
+ SteamAPI_ISteamUGC_SetItemMetadata( hSteamUGC, handle, metadata );
+
+ if( workshop_form.submission.submit_title ){
+ vg_info( "Setting title\n" );
+ SteamAPI_ISteamUGC_SetItemTitle( hSteamUGC, handle,
+ workshop_form.submission.title );
+ }
+
+ if( workshop_form.submission.submit_description ){
+ vg_info( "Setting description\n" );
+ SteamAPI_ISteamUGC_SetItemDescription( hSteamUGC, handle,
+ workshop_form.submission.description);
+ }
+
+ if( workshop_form.submission.submit_file_and_image ){
+ char path_buf[4096];
+ vg_str folder;
+ vg_strnull( &folder, path_buf, 4096 );
+ vg_strcat( &folder, vg.base_path );
+
+ vg_strcat( &folder, workshop_filetype_folder() );
+ vg_strcat( &folder, workshop_form.addon_folder );
+
+ vg_info( "Setting item content\n" );
+ SteamAPI_ISteamUGC_SetItemContent( hSteamUGC, handle, folder.buffer );
+
+ vg_str preview = folder;
+ vg_strcat( &preview, "/preview.jpg" );
+
+ vg_info( "Setting preview image\n" );
+ SteamAPI_ISteamUGC_SetItemPreview( hSteamUGC, handle, preview.buffer );
+ }
+
+ vg_info( "Setting visibility\n" );
+ SteamAPI_ISteamUGC_SetItemVisibility( hSteamUGC, handle,
+ workshop_form.submission.visibility );
+
+ vg_info( "Submitting updates\n" );
+ vg_steam_async_call *call = vg_alloc_async_steam_api_call();
+ call->userdata = NULL;
+ call->p_handler = on_workshop_update_result;
+ call->id = SteamAPI_ISteamUGC_SubmitItemUpdate( hSteamUGC, handle, "" );
+}
+
+/*
+ * Steam API call result for when we've created a new item on their network, or
+ * not, if it has failed
+ */
+static void on_workshop_createitem( void *data, void *user )
+{
+ CreateItemResult_t *result = data;
+
+ if( result->m_eResult == k_EResultOK ){
+ vg_info( "Created workshop file with id: %lu\n",
+ result->m_nPublishedFileId );
+
+ if( result->m_bUserNeedsToAcceptWorkshopLegalAgreement ){
+ vg_warn( "Workshop agreement currently not accepted\n" );
+ }
+
+ workshop_form_upload_submission( result->m_nPublishedFileId, user );
+ }
+ else{
+ const char *errstr = workshop_EResult_user_string( result->m_eResult );
+
+ if( errstr ){
+ vg_error( "ISteamUGC_CreateItem() failed(%d): '%s' \n",
+ result->m_eResult, errstr );
+ }
+
+ workshop_form.page = k_workshop_form_closing_bad;
+ workshop_form.failure_or_success_string = errstr;
+ }
+}
+
+/*
+ * Starts the workshop upload process through Steam API
+ */
+static void workshop_form_async_submit_begin( void *payload, u32 size )
+{
+
+ /* use existing file */
+ if( workshop_form.submission.file_id ){
+ workshop_form_upload_submission( workshop_form.submission.file_id,
+ payload );
+ }
+ else{
+ vg_steam_async_call *call = vg_alloc_async_steam_api_call();
+ call->userdata = payload;
+ call->p_handler = on_workshop_createitem;
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ call->id = SteamAPI_ISteamUGC_CreateItem( hSteamUGC, SKATERIFT_APPID,
+ k_EWorkshopFileTypeCommunity );
+ }
+}
+
+/*
+ * Downloads the framebuffer into scratch memory
+ */
+static void workshop_form_async_download_image( void *payload, u32 size )
+{
+ int w, h;
+ vg_framebuffer_get_res( g_render.fb_workshop_preview, &w, &h );
+ vg_linear_clear( vg_mem.scratch );
+ workshop_form.img_buffer = vg_linear_alloc( vg_mem.scratch, w*h*3 );
+
+ vg_info( "read framebuffer: glReadPixels( %dx%d )\n", w,h );
+
+ glBindFramebuffer( GL_READ_FRAMEBUFFER, g_render.fb_workshop_preview->id );
+ glReadBuffer( GL_COLOR_ATTACHMENT0 );
+ glReadPixels( 0,0, w,h, GL_RGB, GL_UNSIGNED_BYTE, workshop_form.img_buffer );
+
+ workshop_form.img_w = w;
+ workshop_form.img_h = h;
+}
+
+/*
+ * Thread which kicks off the upload process
+ */
+static void _workshop_form_submit_thread( void *data )
+{
+ vg_async_call( workshop_form_async_download_image, NULL, 0 );
+ vg_async_stall();
+
+ char path_buf[4096];
+ vg_str folder;
+ vg_strnull( &folder, path_buf, 4096 );
+
+ vg_strcat( &folder, workshop_filetype_folder() );
+ vg_strcat( &folder, workshop_form.addon_folder );
+
+ if( !vg_strgood(&folder) ){
+ vg_error( "addon folder path too long\n" );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ /*
+ * Create the metadata file
+ * -----------------------------------------------------------------------*/
+ u8 descriptor_buf[ 512 ];
+ vg_msg descriptor;
+ vg_msg_init( &descriptor, descriptor_buf, sizeof(descriptor_buf) );
+ vg_linear_clear( vg_mem.scratch );
+
+ /* short description */
+ vg_msg_frame( &descriptor, "workshop" );
+ vg_msg_wkvstr( &descriptor, "title", workshop_form.submission.title );
+ //vg_msg_wkvstr( &descriptor, "author", "unknown" );
+ vg_msg_wkvnum( &descriptor, "type", k_vg_msg_u32, 1,
+ &workshop_form.submission.type );
+ vg_msg_wkvstr( &descriptor, "folder", workshop_form.addon_folder );
+ vg_msg_end_frame( &descriptor );
+ //vg_msg_wkvstr( &descriptor, "location", "USA" );
+
+ char *short_descriptor_str =
+ vg_linear_alloc( vg_mem.scratch, vg_align8(descriptor.cur.co*2+1));
+ vg_bin_str( descriptor_buf, short_descriptor_str, descriptor.cur.co );
+ short_descriptor_str[descriptor.cur.co*2] = '\0';
+ vg_info( "binstr: %s\n", short_descriptor_str );
+
+ vg_dir dir;
+ if( !vg_dir_open( &dir, folder.buffer ) )
+ {
+ vg_error( "could not open addon folder '%s'\n", folder.buffer );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ while( vg_dir_next_entry(&dir) )
+ {
+ if( vg_dir_entry_type(&dir) == k_vg_entry_type_file )
+ {
+ const char *d_name = vg_dir_entry_name(&dir);
+ if( d_name[0] == '.' ) continue;
+
+ vg_str file = folder;
+ vg_strcat( &file, "/" );
+ vg_strcat( &file, d_name );
+ if( !vg_strgood( &file ) ) continue;
+
+ char *ext = vg_strch( &file, '.' );
+ if( !ext ) continue;
+ if( strcmp(ext,".mdl") ) continue;
+
+ vg_msg_wkvstr( &descriptor, "content", d_name );
+ break;
+ }
+ }
+ vg_dir_close(&dir);
+
+ vg_str descriptor_file = folder;
+ vg_strcat( &descriptor_file, "/addon.inf" );
+ if( !vg_strgood(&descriptor_file) ){
+ vg_error( "Addon info path too long\n" );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ FILE *fp = fopen( descriptor_file.buffer, "wb" );
+ if( !fp ){
+ vg_error( "Could not open addon info file '%s'\n",
+ descriptor_file.buffer );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+ fwrite( descriptor_buf, descriptor.cur.co, 1, fp );
+ fclose( fp );
+
+ /* Save the preview
+ * -----------------------------------------------------------------------*/
+ vg_str preview = folder;
+ vg_strcat( &preview, "/preview.jpg" );
+
+ if( !vg_strgood(&preview) ){
+ vg_error( "preview image path too long\n" );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ int w = workshop_form.img_w,
+ h = workshop_form.img_h;
+
+ vg_info( "writing: %s (%dx%d @90%%)\n", preview.buffer, w,h );
+ stbi_flip_vertically_on_write(1);
+ stbi_write_jpg( preview.buffer, w,h, 3, workshop_form.img_buffer, 90 );
+
+ vg_async_call( workshop_form_async_submit_begin, short_descriptor_str, 0 );
+}
+
+/*
+ * Entry point for the publishing submission operation
+ */
+static void workshop_op_submit( ui_context *ctx )
+{
+ /* TODO: Show these errors to the user */
+ if( workshop_form.submission.submit_title )
+ {
+ if( !workshop_form.submission.title[0] )
+ {
+ ui_start_modal( ctx, "Cannot submit because a title is required\n",
+ UI_MODAL_WARN );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+ }
+
+ if( workshop_form.submission.submit_description )
+ {
+ if( !workshop_form.submission.description[0] )
+ {
+ ui_start_modal( ctx,
+ "Cannot submit because a description is required\n",
+ UI_MODAL_WARN );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+ }
+
+ if( workshop_form.submission.submit_file_and_image )
+ {
+ if( workshop_form.file_intent == k_workshop_form_file_intent_none )
+ {
+ ui_start_modal( ctx, "Cannot submit because the file is "
+ "empty or unspecified\n", UI_MODAL_WARN );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+ }
+
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+ workshop_form.op = k_workshop_op_publishing_update;
+
+ vg_loader_start( _workshop_form_submit_thread, NULL );
+}
+
+/*
+ * op: k_workshop_form_op_loading_model
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * Reciever for completion of the model file load
+ */
+static void workshop_form_loadmodel_async_complete( void *payload, u32 size )
+{
+ v2_zero( workshop_form.view_angles );
+ v3_zero( workshop_form.view_offset );
+ workshop_form.view_dist = 1.0f;
+ workshop_form.view_changed = 1;
+ workshop_form.file_intent = k_workshop_form_file_intent_new;
+
+ vg_success( "workshop async load complete\n" );
+ workshop_form.op = k_workshop_op_none;
+}
+
+/*
+ * Reciever for failure to load
+ */
+static void workshop_form_loadmodel_async_error( void *payload, u32 size ){
+}
+
+/*
+ * Thread which loads the model from the disk
+ */
+static void _workshop_form_load_thread( void *data )
+{
+ char path_buf[4096];
+ vg_str folder;
+ vg_strnull( &folder, path_buf, 4096 );
+
+ vg_strcat( &folder, workshop_filetype_folder() );
+ vg_strcat( &folder, workshop_form.addon_folder );
+
+ if( !vg_strgood(&folder) ){
+ vg_error( "workshop async load failed: path too long\n" );
+ vg_async_call( workshop_form_loadmodel_async_error, NULL, 0 );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ vg_dir dir;
+ if( !vg_dir_open( &dir, folder.buffer ) ){
+ vg_error( "workshop async load failed: could not open folder\n" );
+ vg_async_call( workshop_form_loadmodel_async_error, NULL, 0 );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ vg_info( "Searching %s for model files\n", folder.buffer );
+
+ int found_mdl = 0;
+ while( vg_dir_next_entry(&dir) ){
+ if( vg_dir_entry_type(&dir) == k_vg_entry_type_file ){
+ const char *d_name = vg_dir_entry_name(&dir);
+ if( d_name[0] == '.' ) continue;
+
+ vg_str file = folder;
+ vg_strcat( &file, "/" );
+ vg_strcat( &file, d_name );
+ if( !vg_strgood( &file ) ) continue;
+
+ char *ext = vg_strch( &file, '.' );
+ if( !ext ) continue;
+ if( strcmp(ext,".mdl") ) continue;
+ found_mdl = 1;
+ break;
+ }
+ }
+ vg_dir_close(&dir);
+
+ if( !found_mdl ){
+ vg_error( "workshop async load failed: no model files found\n" );
+ vg_async_call( workshop_form_loadmodel_async_error, NULL, 0 );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ if( workshop_form.submission.type == k_addon_type_board )
+ player_board_load( &workshop_form.board_model, path_buf );
+ else if( workshop_form.submission.type == k_addon_type_player )
+ player_model_load( &workshop_form.player_model, path_buf );
+
+ vg_async_call( workshop_form_loadmodel_async_complete, NULL, 0 );
+}
+
+/*
+ * Entry point for load model operation
+ */
+static void workshop_op_load_model( ui_context *ctx )
+{
+ world_instance *world = world_current_instance();
+ workshop_form.view_world = world;
+
+ if( workshop_form.submission.type == k_addon_type_board )
+ {
+ if( mdl_arrcount( &world->ent_swspreview ) )
+ {
+ workshop_form.ptr_ent = mdl_arritm( &world->ent_swspreview, 0 );
+ }
+ else
+ {
+ ui_start_modal( ctx, "There is no ent_swspreview in the level. \n"
+ "Cannot publish here\n", UI_MODAL_BAD );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+ }
+ else if( workshop_form.submission.type == k_addon_type_player ){}
+ else
+ {
+ ui_start_modal( ctx, "Don't know how to prepare for this item type. \n"
+ "Please contact the developers.\n", UI_MODAL_BAD );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ workshop_form.op = k_workshop_op_loading_model;
+ vg_loader_start( _workshop_form_load_thread, NULL );
+}
+
+/*
+ * op: k_workshop_form_op_downloading_submission
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * The image has been decoded and is ready to slap into the framebuffer
+ */
+static void workshop_form_async_imageload( void *data, u32 len )
+{
+ if( data )
+ {
+ vg_framebuffer_attachment *a =
+ &g_render.fb_workshop_preview->attachments[0];
+
+ glBindTexture( GL_TEXTURE_2D, a->id );
+ glTexSubImage2D( GL_TEXTURE_2D, 0,0,0,
+ WORKSHOP_PREVIEW_WIDTH, WORKSHOP_PREVIEW_HEIGHT,
+ a->format, a->type, data );
+ stbi_image_free( data );
+ vg_success( "Loaded workshop preview image\n" );
+ }
+ else
+ {
+ snprintf( workshop_form.error_msg, sizeof(workshop_form.error_msg),
+ "Preview image could not be loaded. Reason: %s\n",
+ stbi_failure_reason() );
+ ui_start_modal( &vg_ui.ctx, workshop_form.error_msg, UI_MODAL_BAD );
+ }
+ workshop_form.op = k_workshop_op_none;
+}
+
+/*
+ * Load the image located at ./workshop_preview.jpg into our framebuffer
+ */
+static void _workshop_load_preview_thread( void *data ){
+ char path_buf[ 4096 ];
+ vg_str path;
+ vg_strnull( &path, path_buf, 4096 );
+ vg_strcat( &path, workshop_filetype_folder() );
+ vg_strcat( &path, workshop_form.addon_folder );
+ vg_strcat( &path, "/preview.jpg" );
+
+ if( vg_strgood( &path ) )
+ {
+ stbi_set_flip_vertically_on_load(1);
+ int x, y, nc;
+ u8 *rgb = stbi_load( path.buffer, &x, &y, &nc, 3 );
+
+ if( rgb )
+ {
+ if( (x == WORKSHOP_PREVIEW_WIDTH) && (y == WORKSHOP_PREVIEW_HEIGHT) )
+ {
+ vg_async_call( workshop_form_async_imageload, rgb, x*y*3 );
+ }
+ else
+ {
+ vg_error( "Resolution does not match framebuffer, so we can't"
+ " show it\n" );
+ stbi_image_free( rgb );
+ vg_async_call( workshop_form_async_imageload, NULL, 0 );
+ }
+ }
+ else
+ {
+ vg_async_call( workshop_form_async_imageload, NULL, 0 );
+ }
+ }
+ else
+ {
+ vg_async_call( workshop_form_async_imageload, NULL, 0 );
+ }
+}
+
+/*
+ * Entry point to view operation
+ */
+static void workshop_op_download_and_view_submission( int result_index )
+{
+ workshop_form.op = k_workshop_op_downloading_submission;
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ ISteamRemoteStorage *hSteamRemoteStorage = SteamAPI_SteamRemoteStorage();
+ SteamUGCDetails_t details;
+ if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC,
+ workshop_form.ugc_query.handle,
+ result_index,
+ &details ) )
+ {
+ workshop_reset_submission_data();
+ workshop_form.submission.submit_description = 0;
+ workshop_form.submission.submit_file_and_image = 0;
+ workshop_form.submission.submit_title = 0;
+
+ u8 metadata_buf[512];
+ char metadata_str[1024+1];
+ int have_meta = SteamAPI_ISteamUGC_GetQueryUGCMetadata( hSteamUGC,
+ workshop_form.ugc_query.handle,
+ result_index, metadata_str,
+ 1024+1 );
+
+ vg_strncpy( details.m_rgchDescription,
+ workshop_form.submission.description,
+ VG_ARRAY_LEN( workshop_form.submission.description ),
+ k_strncpy_always_add_null );
+
+ vg_strncpy( details.m_rgchTitle,
+ workshop_form.submission.title,
+ VG_ARRAY_LEN( workshop_form.submission.title ),
+ k_strncpy_always_add_null );
+
+ snprintf( workshop_form.addon_folder,
+ VG_ARRAY_LEN( workshop_form.addon_folder ),
+ "Steam Cloud ("PRINTF_U64")", details.m_nPublishedFileId );
+
+ workshop_form.submission.file_id = details.m_nPublishedFileId;
+ workshop_form.file_intent = k_workshop_form_file_intent_keep_old;
+ workshop_form.page = k_workshop_form_edit;
+ workshop_form.submission.visibility = details.m_eVisibility;
+ workshop_form.submission.type = k_addon_type_none;
+ workshop_form.submission.submission_type_selection = k_addon_type_none;
+
+ if( have_meta )
+ {
+ u32 len = strlen(metadata_str);
+ vg_info( "Metadata: %s\n", metadata_str );
+ vg_str_bin( metadata_str, metadata_buf, len );
+ vg_msg msg;
+ vg_msg_init( &msg, metadata_buf, len/2 );
+
+ if( vg_msg_seekframe( &msg, "workshop" ))
+ {
+ u32 type;
+ vg_msg_getkvintg( &msg, "type", k_vg_msg_u32, &type, NULL );
+ workshop_form.submission.type = type;
+ workshop_form.submission.submission_type_selection = type;
+
+ const char *kv_folder = vg_msg_getkvstr( &msg, "folder" );
+ if( kv_folder )
+ {
+ vg_strncpy( kv_folder, workshop_form.addon_folder,
+ sizeof(workshop_form.addon_folder),
+ k_strncpy_always_add_null );
+ }
+ }
+ }
+ else
+ {
+ vg_error( "No metadata was returned with this item.\n" );
+ }
+
+ vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
+ glClearColor( 0.2f, 0.0f, 0.0f, 1.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ glViewport( 0,0, vg.window_x, vg.window_y );
+
+ vg_loader_start( _workshop_load_preview_thread, NULL );
+ }
+ else
+ {
+ vg_error( "GetQueryUGCResult: Index out of range\n" );
+ workshop_form.op = k_workshop_op_none;
+ }
+}
+
+/*
+ * Regular stuff
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * View a page of results on the sidebar
+ */
+static void workshop_view_page( int req )
+{
+ if( workshop_form.ugc_query.result != k_EResultOK )
+ {
+ vg_error( "Tried to change page without complete data\n" );
+ workshop_form.op = k_workshop_op_none;
+ return;
+ }
+
+ int page = VG_MAX(VG_MIN(req, workshop_form.view_published_page_count-1),0),
+ start = page * WORKSHOP_VIEW_PER_PAGE,
+ end = VG_MIN( (page+1) * WORKSHOP_VIEW_PER_PAGE,
+ workshop_form.ugc_query.returned_item_count ),
+ count = end-start;
+
+ vg_info( "View page %d\n", page );
+
+ workshop_form.view_published_page_id = page;
+ workshop_form.published_files_list_length = count;
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+
+ for( int i=0; i<count; i ++ )
+ {
+ struct published_file *pfile = &workshop_form.published_files_list[i];
+
+ SteamUGCDetails_t details;
+ if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC,
+ workshop_form.ugc_query.handle,
+ start+i,
+ &details ) )
+ {
+ if( details.m_eResult != k_EResultOK )
+ {
+ snprintf( pfile->title, 80, "Error (%d)", details.m_eResult );
+ }
+ else
+ {
+ vg_strncpy( details.m_rgchTitle, pfile->title, 80,
+ k_strncpy_always_add_null );
+ }
+
+ pfile->result = details.m_eResult;
+ pfile->result_index = start+i;
+ }
+ else
+ {
+ pfile->result = k_EResultValueOutOfRange;
+ pfile->result_index = -1;
+ snprintf( pfile->title, 80, "Error (invalid index)" );
+ }
+ }
+}
+
+/*
+ * Steam API result for when we recieve submitted UGC information about the user
+ */
+static void on_workshop_UGCQueryComplete( void *data, void *userdata )
+{
+ SteamUGCQueryCompleted_t *query = data;
+ workshop_form.ugc_query.result = query->m_eResult;
+
+ if( query->m_eResult == k_EResultOK )
+ {
+ if( query->m_unTotalMatchingResults > 50 )
+ {
+ vg_warn( "You have %d items submitted, "
+ "we can only view the last 50\n" );
+ }
+
+ workshop_form.ugc_query.all_item_count = query->m_unTotalMatchingResults;
+ workshop_form.ugc_query.returned_item_count =
+ query->m_unNumResultsReturned;
+
+ workshop_form.ugc_query.handle = query->m_handle;
+ workshop_form.view_published_page_count =
+ (query->m_unNumResultsReturned+WORKSHOP_VIEW_PER_PAGE-1)/
+ WORKSHOP_VIEW_PER_PAGE;
+ workshop_form.view_published_page_id = 0;
+ workshop_form.published_files_list_length = 0;
+
+ workshop_view_page( 0 );
+ }
+ else
+ {
+ vg_error( "Steam UGCQuery failed (%d)\n", query->m_eResult );
+ workshop_form.view_published_page_count = 0;
+ workshop_form.view_published_page_id = 0;
+ workshop_form.published_files_list_length = 0;
+
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ SteamAPI_ISteamUGC_ReleaseQueryUGCRequest( hSteamUGC, query->m_handle );
+ }
+}
+
+/*
+ * Console command to open the workshop publisher
+ */
+int workshop_submit_command( int argc, const char *argv[] )
+{
+ if( !steam_ready )
+ {
+ ui_start_modal( &vg_ui.ctx,
+ "Steam API is not initialized\n", UI_MODAL_BAD );
+ return 0;
+ }
+
+ workshop_form.page = k_workshop_form_open;
+ workshop_form.view_published_page_count = 0;
+ workshop_form.view_published_page_id = 0;
+ workshop_form.published_files_list_length = 0;
+ workshop_form.ugc_query.result = k_EResultNone;
+
+ vg_steam_async_call *call = vg_alloc_async_steam_api_call();
+
+ ISteamUser *hSteamUser = SteamAPI_SteamUser();
+ CSteamID steamid;
+ steamid.m_unAll64Bits = SteamAPI_ISteamUser_GetSteamID( hSteamUser );
+
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ call->p_handler = on_workshop_UGCQueryComplete;
+ call->userdata = NULL;
+ UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryUserUGCRequest
+ (
+ hSteamUGC,
+ steamid.m_comp.m_unAccountID,
+ k_EUserUGCList_Published,
+ k_EUGCMatchingUGCType_Items,
+ k_EUserUGCListSortOrder_CreationOrderDesc,
+ SKATERIFT_APPID, SKATERIFT_APPID,
+ 1 );
+ SteamAPI_ISteamUGC_SetReturnMetadata( hSteamUGC, handle, 1 );
+ call->id = SteamAPI_ISteamUGC_SendQueryUGCRequest( hSteamUGC, handle );
+ return 0;
+}
+
+void workshop_init(void)
+{
+ vg_console_reg_cmd( "workshop_submit", workshop_submit_command, NULL );
+}
+
+static void workshop_render_world_preview(void)
+{
+ vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
+
+ glClearColor( 0.0f, 0.0f, 0.3f, 1.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+ glEnable( GL_DEPTH_TEST );
+ glDisable( GL_BLEND );
+
+ render_world( world_current_instance(), &g_render.cam, 0, 0, 1, 1 );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ glViewport( 0,0, vg.window_x, vg.window_y );
+}
+
+/*
+ * Redraw the playermodel into the workshop framebuffer
+ */
+static void workshop_render_player_preview(void)
+{
+ vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
+ glClearColor( 0.16f, 0.15f, 0.15f, 1.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+ glEnable( GL_DEPTH_TEST );
+ glDisable( GL_BLEND );
+
+ struct skeleton *sk = &localplayer.skeleton;
+
+ player_pose res;
+ res.type = k_player_pose_type_ik;
+
+ struct skeleton_anim *anim = skeleton_get_anim( sk, "idle_cycle+y" );
+ skeleton_sample_anim( sk, anim, vg.time*0.1f, res.keyframes );
+ q_axis_angle( res.root_q, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
+ v3_zero( res.root_co );
+ res.root_co[1] = 200.0f;
+
+ m4x3f transform;
+ q_m3x3( res.root_q, transform );
+ v3_copy( res.root_co, transform[3] );
+
+ /* TODO: Function. */
+ skeleton_apply_pose( sk, res.keyframes, k_anim_apply_defer_ik,
+ localplayer.final_mtx );
+ skeleton_apply_ik_pass( sk, localplayer.final_mtx );
+ skeleton_apply_pose( sk, res.keyframes, k_anim_apply_deffered_only,
+ localplayer.final_mtx );
+ skeleton_apply_inverses( sk, localplayer.final_mtx );
+ skeleton_apply_transform( sk, transform, localplayer.final_mtx );
+
+ vg_camera cam;
+ v3_copy( (v3f){ 0.0f, 201.7f, 1.2f }, cam.pos );
+
+ cam.nearz = 0.01f;
+ cam.farz = 100.0f;
+ cam.fov = 57.0f;
+ v3_zero( cam.angles );
+
+ vg_camera_update_transform( &cam );
+ vg_camera_update_view( &cam );
+ vg_camera_update_projection( &cam );
+ vg_camera_finalize( &cam );
+
+ render_playermodel( &cam, world_current_instance(), 0,
+ &workshop_form.player_model, sk, localplayer.final_mtx );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ glViewport( 0,0, vg.window_x, vg.window_y );
+}
+
+/*
+ * Redraw the model file into the workshop framebuffer
+ */
+static void workshop_render_board_preview(void)
+{
+ if( !workshop_form.ptr_ent )
+ {
+ return;
+ }
+
+ vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
+
+ glClearColor( 0.0f, 0.0f, 0.3f, 1.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+ glEnable( GL_DEPTH_TEST );
+ glDisable( GL_BLEND );
+
+ ent_swspreview *swsprev = workshop_form.ptr_ent;
+ world_instance *world = workshop_form.view_world;
+
+ ent_camera *ref = mdl_arritm( &world->ent_camera,
+ mdl_entity_id_id(swsprev->id_camera) );
+ ent_marker *display = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(swsprev->id_display) ),
+ *display1= mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(swsprev->id_display1) );
+
+ v3f baseco;
+ v3_add( display->transform.co, display1->transform.co, baseco );
+ v3_muls( baseco, 0.5f, baseco );
+
+ vg_camera cam;
+ v3f basevector;
+ v3_sub( display->transform.co, ref->transform.co, basevector );
+ float dist = v3_length( basevector );
+
+ v3f baseangles;
+ v3_angles( basevector, baseangles );
+
+ v2_add( workshop_form.view_angles, baseangles, cam.angles );
+ cam.angles[2] = 0.0f;
+
+ float sX = sinf( cam.angles[0] ),
+ cX = cosf( cam.angles[0] ),
+ sY = sinf( cam.angles[1] ),
+ cY = cosf( cam.angles[1] );
+
+ v3f offset = { -sX * cY, sY, cX * cY };
+
+ v3_muladds( display->transform.co, offset,
+ dist*workshop_form.view_dist, cam.pos );
+
+ cam.pos[0] += -sX*workshop_form.view_offset[2];
+ cam.pos[2] += cX*workshop_form.view_offset[2];
+ cam.pos[0] += cX*workshop_form.view_offset[0];
+ cam.pos[2] += sX*workshop_form.view_offset[0];
+ cam.pos[1] += workshop_form.view_offset[1];
+
+ cam.nearz = 0.01f;
+ cam.farz = 100.0f;
+ cam.fov = ref->fov;
+
+ vg_camera_update_transform( &cam );
+ vg_camera_update_view( &cam );
+ vg_camera_update_projection( &cam );
+ vg_camera_finalize( &cam );
+
+ m4x3f mmdl, mmdl1;
+ mdl_transform_m4x3( &display->transform, mmdl );
+ mdl_transform_m4x3( &display1->transform, mmdl1 );
+
+ /* force update this for nice shadows. its usually set in the world
+ * pre-render step, but that includes timer stuff
+ */
+ struct player_board *board = &workshop_form.board_model;
+ struct ub_world_lighting *ubo = &world->ub_lighting;
+ v3f vp0, vp1;
+ v3_copy((v3f){0.0f,0.1f, board->truck_positions[0][2]}, vp0 );
+ v3_copy((v3f){0.0f,0.1f, board->truck_positions[1][2]}, vp1 );
+ m4x3_mulv( mmdl1, vp0, ubo->g_board_0 );
+ m4x3_mulv( mmdl1, vp1, ubo->g_board_1 );
+ glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+ glBufferSubData( GL_UNIFORM_BUFFER, 0,
+ sizeof(struct ub_world_lighting), &world->ub_lighting );
+
+ render_world( world, &cam, 0, 0, 0, 0 );
+ struct player_board_pose pose = {0};
+ render_board( &cam, world, board, mmdl, &pose, k_board_shader_entity );
+ render_board( &cam, world, board, mmdl1, &pose, k_board_shader_entity );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ glViewport( 0,0, vg.window_x, vg.window_y );
+}
+
+/*
+ * ImGUI section for workshop form
+ * -----------------------------------------------------------------------------
+ */
+
+static void workshop_changed_model_path( ui_context *ctx, char *buf, u32 len )
+{
+ workshop_form.submission.submit_file_and_image = 1;
+}
+
+static void workshop_changed_title( ui_context *ctx, char *buf, u32 len )
+{
+ workshop_form.submission.submit_title = 1;
+}
+
+static void workshop_changed_description( ui_context *ctx, char *buf, u32 len )
+{
+ workshop_form.submission.submit_description = 1;
+}
+
+static void workshop_form_gui_page_undecided( ui_context *ctx, ui_rect content )
+{
+ ui_rect box;
+ rect_copy( content, box );
+ box[3] = 128;
+ box[2] = (box[2]*2)/3;
+ ui_rect_center( content, box );
+
+ ui_rect row;
+ ui_split( box, k_ui_axis_h, 28, 0, row, box );
+ ui_text( ctx, row,
+ "Select the type of item\n", 1, k_ui_align_middle_center,0);
+ ui_split( box, k_ui_axis_h, 28, 0, row, box );
+ ui_enum( ctx, row, "Type:", workshop_form_type_opts,
+ 4, &workshop_form.submission.submission_type_selection );
+ ui_split( box, k_ui_axis_h, 8, 0, row, box );
+ ui_split( box, k_ui_axis_h, 28, 0, row, box );
+
+ ui_rect button_l, button_r;
+ rect_copy( row, button_l );
+ button_l[2] = 128*2;
+ ui_rect_center( row, button_l );
+ ui_split_ratio( button_l, k_ui_axis_v, 0.5f, 2, button_l, button_r );
+
+ enum addon_type type = workshop_form.submission.submission_type_selection;
+ if( type != k_addon_type_none)
+ {
+ if( ui_button_text( ctx, button_l, "OK", 1 ) == 1 )
+ {
+ workshop_form.submission.type = type;
+
+ if( type == k_addon_type_world ){
+ workshop_form.view_changed = 1;
+ workshop_form.file_intent = k_workshop_form_file_intent_new;
+ }
+ }
+ }
+ else
+ {
+ ui_fill( ctx, button_l, ui_colour(ctx,k_ui_bg) );
+ ui_text( ctx, button_l, "OK", 1, k_ui_align_middle_center,
+ ui_colour(ctx, k_ui_bg+4) );
+ }
+
+ if( ui_button_text( ctx, button_r, "Cancel", 1 ) == 1 )
+ {
+ workshop_form.page = k_workshop_form_open;
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+ }
+}
+
+static void workshop_form_gui_draw_preview( ui_context *ctx, ui_rect img_box )
+{
+ enum addon_type type = workshop_form.submission.type;
+ if( workshop_form.file_intent == k_workshop_form_file_intent_keep_old )
+ {
+ ui_image( ctx,
+ img_box, &g_render.fb_workshop_preview->attachments[0].id );
+ }
+ else if( workshop_form.file_intent == k_workshop_form_file_intent_new )
+ {
+ ui_image( ctx,
+ img_box, &g_render.fb_workshop_preview->attachments[0].id );
+
+ if( type == k_addon_type_world )
+ {
+ return;
+ }
+
+ int hover = ui_inside_rect( img_box, ctx->mouse ),
+ target = ui_inside_rect( img_box, ctx->mouse_click );
+
+ if( ui_click_down(ctx,UI_MOUSE_MIDDLE) && target )
+ {
+ v3_copy( workshop_form.view_offset,
+ workshop_form.view_offset_begin );
+ }
+ else if( ui_click_down(ctx,UI_MOUSE_LEFT) && target )
+ {
+ v2_copy( workshop_form.view_angles,
+ workshop_form.view_angles_begin );
+ }
+
+ if( ui_clicking(ctx,UI_MOUSE_MIDDLE) && target )
+ {
+ v2f delta = { ctx->mouse[0]-ctx->mouse_click[0],
+ ctx->mouse[1]-ctx->mouse_click[1] };
+
+ float *begin = workshop_form.view_offset_begin,
+ *offset = workshop_form.view_offset;
+ offset[0] = vg_clampf( begin[0]-delta[0]*0.002f, -1.0f, 1.0f );
+ offset[2] = vg_clampf( begin[2]-delta[1]*0.002f, -1.0f, 1.0f );
+ workshop_form.view_changed = 1;
+ }
+ else if( ui_clicking(ctx,UI_MOUSE_LEFT) && target )
+ {
+ v2f delta = { ctx->mouse[0]-ctx->mouse_click[0],
+ ctx->mouse[1]-ctx->mouse_click[1] };
+
+ v2f angles;
+ v2_muladds( workshop_form.view_angles_begin, delta, 0.002f, angles);
+
+ float limit = VG_PIf*0.2f;
+
+ angles[0] = vg_clampf( angles[0], -limit, limit );
+ angles[1] = vg_clampf( angles[1], -limit, limit );
+
+ v2_copy( angles, workshop_form.view_angles );
+ workshop_form.view_changed = 1;
+ }
+
+ if( !ui_clicking(ctx,UI_MOUSE_LEFT) && hover )
+ {
+ float zoom = workshop_form.view_dist;
+ zoom += vg.mouse_wheel[1] * -0.07f;
+ zoom = vg_clampf( zoom, 0.4f, 2.0f );
+
+ if( zoom != workshop_form.view_dist )
+ {
+ workshop_form.view_changed = 1;
+ workshop_form.view_dist = zoom;
+ }
+ }
+ }
+ else
+ {
+ ui_text( ctx, img_box, "No image", 1, k_ui_align_middle_center,
+ ui_colour( ctx, k_ui_orange ) );
+ }
+}
+
+static void workshop_form_gui_edit_page( ui_context *ctx, ui_rect content )
+{
+ enum addon_type type = workshop_form.submission.type;
+
+ if( type == k_addon_type_none )
+ {
+ workshop_form_gui_page_undecided( ctx, content );
+ return;
+ }
+
+ ui_rect image_plane;
+ ui_split( content, k_ui_axis_h, 300, 0, image_plane, content );
+ ui_fill( ctx, image_plane, ui_colour( ctx, k_ui_bg+0 ) );
+
+ ui_rect img_box;
+ ui_fit_item( image_plane, (ui_px[2]){ 3, 2 }, img_box );
+ workshop_form_gui_draw_preview( ctx, img_box );
+
+ /* file path */
+ ui_rect file_button, file_label;
+
+ char buf[128];
+ snprintf( buf, 128,
+ "Addon folder: skaterift/%s", workshop_filetype_folder() );
+
+ if( type == k_addon_type_world )
+ {
+ struct ui_textbox_callbacks callbacks =
+ {
+ .change = workshop_changed_model_path
+ };
+ ui_textbox( ctx, content, buf, workshop_form.addon_folder,
+ VG_ARRAY_LEN(workshop_form.addon_folder), 1, 0, &callbacks );
+ }
+ else
+ {
+ ui_rect file_entry;
+ ui_standard_widget( ctx, content, file_entry, 1 );
+ ui_split( file_entry, k_ui_axis_v, -128, 0, file_entry, file_button );
+
+ if( workshop_form.file_intent != k_workshop_form_file_intent_none )
+ {
+ ui_text( ctx, file_entry, workshop_form.addon_folder, 1,
+ k_ui_align_middle_left, ui_colour( ctx, k_ui_fg+4 ) );
+
+ if( ui_button_text( ctx, file_button, "Remove", 1 ) == 1 )
+ {
+ if( type == k_addon_type_board )
+ player_board_unload( &workshop_form.board_model );
+ else if( type == k_addon_type_player )
+ player_model_unload( &workshop_form.player_model );
+
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+ workshop_form.addon_folder[0] = '\0';
+ }
+ }
+ else
+ {
+ struct ui_textbox_callbacks callbacks =
+ {
+ .change = workshop_changed_model_path
+ };
+
+ ui_textbox( ctx, file_entry, buf, workshop_form.addon_folder,
+ VG_ARRAY_LEN(workshop_form.addon_folder), 1,
+ 0, &callbacks );
+
+ if( ui_button_text( ctx, file_button, "Load", 1 ) == 1 )
+ {
+ workshop_op_load_model( ctx );
+ }
+ }
+ }
+
+ const char *str_title = "Title:", *str_desc = "Description:";
+
+ /* title box */
+ {
+ struct ui_textbox_callbacks callbacks = {
+ .change = workshop_changed_title
+ };
+ ui_textbox( ctx, content, str_title, workshop_form.submission.title,
+ VG_ARRAY_LEN(workshop_form.submission.title), 1,
+ 0, &callbacks );
+ }
+
+ /* visibility option */
+ {
+ ui_enum( ctx, content, "Visibility:", workshop_form_visibility_opts,
+ 4, &workshop_form.submission.visibility );
+ }
+
+ /* description box */
+ {
+ struct ui_textbox_callbacks callbacks =
+ {
+ .change = workshop_changed_description
+ };
+ ui_textbox( ctx, content, str_desc, workshop_form.submission.description,
+ VG_ARRAY_LEN(workshop_form.submission.description), 4,
+ UI_TEXTBOX_MULTILINE|UI_TEXTBOX_WRAP, &callbacks );
+ }
+
+ /* submissionable */
+ ui_rect final_row;
+ ui_split( content, k_ui_axis_h, content[3]-32-8, 0, content, final_row );
+
+ ui_rect submission_center;
+ rect_copy( final_row, submission_center );
+ submission_center[2] = 256;
+ ui_rect_center( final_row, submission_center );
+
+ ui_rect btn_left, btn_right;
+ ui_split_ratio( submission_center, k_ui_axis_v, 0.5f, 8,
+ btn_left, btn_right );
+
+ if( ui_button_text( ctx, btn_left, "Publish", 1 ) == 1 )
+ {
+ workshop_op_submit( ctx );
+ }
+ if( ui_button_text( ctx, btn_right, "Cancel", 1 ) == 1 )
+ {
+ workshop_form.page = k_workshop_form_open;
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+ }
+
+ /* disclaimer */
+ const char *disclaimer_text =
+ "By submitting this item, you agree to the workshop terms of service";
+
+ ui_rect disclaimer_row, inner, link;
+ ui_split( content, k_ui_axis_h, content[3]-32, 0, content, disclaimer_row );
+
+ ui_px btn_width = 32;
+
+ rect_copy( disclaimer_row, inner );
+ inner[2] = ui_text_line_width( ctx, disclaimer_text ) + btn_width+8;
+
+ ui_rect label;
+ ui_rect_center( disclaimer_row, inner );
+ ui_split( inner, k_ui_axis_v, inner[2]-btn_width, 0, label, btn_right);
+ ui_rect_pad( btn_right, (ui_px[2]){2,2} );
+
+ if( ui_button_text( ctx, btn_right, "\xb2", 2 ) == 1 )
+ {
+ ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
+ SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends,
+ "https://steamcommunity.com/sharedfiles/workshoplegalagreement",
+ k_EActivateGameOverlayToWebPageMode_Default );
+ }
+
+ ui_text( ctx, label, disclaimer_text, 1, k_ui_align_middle_left,
+ ui_colour( ctx, k_ui_fg+4 ) );
+}
+
+static void workshop_form_gui_sidebar( ui_context *ctx, ui_rect sidebar )
+{
+ ui_fill( ctx, sidebar, ui_colour( ctx, k_ui_bg+2 ) );
+
+ ui_rect title;
+ ui_split( sidebar, k_ui_axis_h, 28, 0, title, sidebar );
+
+ if( workshop_form.page == k_workshop_form_edit )
+ {
+ ui_text( ctx, title, "Editing", 1, k_ui_align_middle_center, 0 );
+ ui_split( sidebar, k_ui_axis_h, 28, 0, title, sidebar );
+
+ if( workshop_form.submission.type != k_addon_type_none )
+ {
+ char buf[512];
+ vg_str str;
+ vg_strnull( &str, buf, 512 );
+
+ if( workshop_form.submission.file_id )
+ vg_strcat( &str, "Editing an existing " );
+ else
+ vg_strcat( &str, "Creating a new " );
+
+ if( workshop_form.submission.type == k_addon_type_board )
+ vg_strcat( &str, "skateboard." );
+ else if( workshop_form.submission.type == k_addon_type_world )
+ vg_strcat( &str, "world." );
+ else if( workshop_form.submission.type == k_addon_type_player )
+ vg_strcat( &str, "playermodel." );
+ else
+ vg_strcat( &str, "???." );
+
+ ui_text( ctx, title, buf, 1, k_ui_align_middle_center,
+ ui_colour(ctx, k_ui_fg+4) );
+ }
+ return;
+ }
+
+ /*
+ * sidebar existing entries panel
+ */
+ ui_text( ctx, title, "Your submissions", 1, k_ui_align_middle_center, 0 );
+
+ ui_rect controls, btn_create_new;
+ ui_split( sidebar, k_ui_axis_h, 32, 0, controls, sidebar );
+ ui_split( sidebar, k_ui_axis_h, -32, 0, sidebar, btn_create_new );
+ ui_fill( ctx, controls, ui_colour( ctx, k_ui_bg+1 ) );
+
+ char buf[32];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_strcat( &str, "page " );
+ vg_strcati32( &str, workshop_form.view_published_page_id+1 );
+ vg_strcatch( &str, '/' );
+ vg_strcati32( &str, workshop_form.view_published_page_count );
+
+ ui_rect_pad( controls, (ui_px[2]){0,4} );
+ ui_rect info;
+ ui_split_ratio( controls, k_ui_axis_v, 0.25f, 0, info, controls );
+ ui_text( ctx, info, buf, 1, k_ui_align_middle_center, 0 );
+
+ ui_rect btn_left, btn_right;
+ ui_split_ratio( controls, k_ui_axis_v, 0.5f, 2, btn_left, btn_right );
+
+ if( ui_button_text( ctx, btn_left, "newer", 1 ) == 1 )
+ {
+ workshop_view_page( workshop_form.view_published_page_id-1 );
+ }
+
+ if( ui_button_text( ctx, btn_right, "older", 1 ) == 1 )
+ {
+ workshop_view_page( workshop_form.view_published_page_count+1 );
+ }
+
+ if( ui_button_text( ctx, btn_create_new, "Create New Item", 1 ) == 1 )
+ {
+ workshop_reset_submission_data();
+ workshop_form.submission.submit_title = 1;
+ workshop_form.submission.submit_description = 1;
+ workshop_form.submission.submit_file_and_image = 1;
+ workshop_form.page = k_workshop_form_edit;
+ }
+
+ for( int i=0; i<workshop_form.published_files_list_length; i++ )
+ {
+ ui_rect item;
+ ui_split( sidebar, k_ui_axis_h, 28, 0, item, sidebar );
+ ui_rect_pad( item, (ui_px[2]){4,4} );
+
+ struct published_file *pfile = &workshop_form.published_files_list[i];
+ if( ui_button_text( ctx, item, pfile->title, 1 ) == 1 )
+ {
+ if( pfile->result == k_EResultOK )
+ {
+ vg_info( "Select index: %d\n", pfile->result_index );
+ workshop_op_download_and_view_submission( pfile->result_index );
+ }
+ else
+ {
+ vg_warn( "Cannot select that item, result not OK\n" );
+ }
+ }
+ }
+}
+
+void workshop_form_gui( ui_context *ctx )
+{
+ enum workshop_form_page stable_page = workshop_form.page;
+ if( stable_page == k_workshop_form_hidden ) return;
+
+ ui_rect null;
+ ui_rect screen = { 0, 0, vg.window_x, vg.window_y };
+ ui_rect window = { 0, 0, 1000, 700 };
+ ui_rect_center( screen, window );
+ ctx->wants_mouse = 1;
+
+ ui_fill( ctx, window, ui_colour( ctx, k_ui_bg+1 ) );
+ ui_outline( ctx, window, 1, ui_colour( ctx, k_ui_bg+7 ), 0 );
+
+ ui_rect title, panel;
+ ui_split( window, k_ui_axis_h, 28, 0, title, panel );
+ ui_fill( ctx, title, ui_colour( ctx, k_ui_bg+7 ) );
+ ui_text( ctx, title, "Workshop tool", 1, k_ui_align_middle_center,
+ ui_colourcont( ctx, k_ui_bg+7 ) );
+
+ ui_rect quit_button;
+ ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button );
+
+ if( vg_loader_availible() )
+ {
+ if( ui_button_text( ctx, quit_button, "X", 1 ) == 1 )
+ {
+ workshop_quit_form();
+ return;
+ }
+ }
+
+ /*
+ * temporary operation blinders, we don't yet have a nice way to show the
+ * user that we're doing something uninterruptable, so the code just
+ * escapes here and we show them a basic string
+ */
+
+ if( workshop_form.op != k_workshop_op_none )
+ {
+ const char *op_string = "The programmer has not bothered to describe "
+ "the current operation that is running.";
+
+ switch( workshop_form.op )
+ {
+ case k_workshop_op_loading_model:
+ op_string = "Operation in progress: Loading model file.";
+ break;
+ case k_workshop_op_publishing_update:
+ op_string = "Operation in progress: publishing submission update "
+ "to steam.";
+ break;
+ case k_workshop_op_downloading_submission:
+ op_string = "Operation in progress: downloading existing submission"
+ " from Steam services.";
+ break;
+ default: break;
+ }
+
+ ui_text( ctx, panel, op_string, 1, k_ui_align_middle_center, 0 );
+ return;
+ }
+
+ /* re draw board preview if need to */
+ if( (stable_page == k_workshop_form_edit) &&
+ workshop_form.view_changed &&
+ workshop_form.file_intent == k_workshop_form_file_intent_new )
+ {
+ enum addon_type type = workshop_form.submission.type;
+ if( type == k_addon_type_board ){
+ workshop_render_board_preview();
+ }
+ else if( type == k_addon_type_world ){
+ vg_success( "Renders world preview\n" );
+ workshop_render_world_preview();
+ }
+ else if( type == k_addon_type_player ){
+ workshop_render_player_preview();
+ }
+
+ workshop_form.view_changed = 0;
+ }
+
+ struct workshop_form *form = &workshop_form;
+
+ ui_rect sidebar, content;
+ ui_split_ratio( panel, k_ui_axis_v, 0.3f, 1, sidebar, content );
+
+ /* content page */
+ ui_rect_pad( content, (ui_px[2]){8,8} );
+
+ if( stable_page == k_workshop_form_edit )
+ {
+ workshop_form_gui_edit_page( ctx, content );
+ }
+ else if( stable_page == k_workshop_form_open )
+ {
+ ui_text( ctx, content, "Nothing selected.", 1, k_ui_align_middle_center,
+ ui_colour( ctx, k_ui_fg+4 ) );
+ }
+ else if( stable_page >= k_workshop_form_cclosing )
+ {
+ ui_rect submission_row;
+ ui_split( content, k_ui_axis_h, content[3]-32-8, 0, content,
+ submission_row );
+
+ u32 colour;
+
+ if( stable_page == k_workshop_form_closing_bad )
+ colour = ui_colour( ctx, k_ui_red+k_ui_brighter );
+ else
+ colour = ui_colour( ctx, k_ui_green+k_ui_brighter );
+
+ ui_text( ctx, content, workshop_form.failure_or_success_string, 1,
+ k_ui_align_middle_center, colour );
+
+ ui_rect submission_center;
+ rect_copy( submission_row, submission_center );
+ submission_center[2] = 128;
+ ui_rect_center( submission_row, submission_center );
+ ui_rect_pad( submission_center, (ui_px[2]){8,8} );
+
+ if( ui_button_text( ctx, submission_center, "OK", 1 ) == 1 )
+ {
+ workshop_form.page = k_workshop_form_open;
+ }
+ }
+
+ workshop_form_gui_sidebar( ctx, sidebar );
+}
+
+/*
+ * Some async api stuff
+ * -----------------------------------------------------------------------------
+ */
+
+void async_workshop_get_filepath( void *data, u32 len )
+{
+ struct async_workshop_filepath_info *info = data;
+
+ u64 _size;
+ u32 _ts;
+
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ if( !SteamAPI_ISteamUGC_GetItemInstallInfo( hSteamUGC, info->id, &_size,
+ info->buf, info->len, &_ts ))
+ {
+ vg_error( "GetItemInstallInfo failed\n" );
+ info->buf[0] = '\0';
+ }
+}
+
+void async_workshop_get_installed_files( void *data, u32 len )
+{
+ struct async_workshop_installed_files_info *info = data;
+
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ u32 count = SteamAPI_ISteamUGC_GetSubscribedItems( hSteamUGC, info->buffer,
+ *info->len );
+
+ vg_info( "Found %u subscribed items\n", count );
+
+ u32 j=0;
+ for( u32 i=0; i<count; i++ ){
+ u32 state = SteamAPI_ISteamUGC_GetItemState( hSteamUGC, info->buffer[i] );
+ if( state & k_EItemStateInstalled ){
+ info->buffer[j ++] = info->buffer[i];
+ }
+ }
+
+ *info->len = j;
+}
--- /dev/null
+#pragma once
+#include "addon_types.h"
+#include "vg/vg_steam_remote_storage.h"
+#include "skaterift.h"
+#include "vg/vg_steam_auth.h"
+#include "vg/vg_steam_ugc.h"
+#include "vg/vg_steam_friends.h"
+#include "steam.h"
+#include "ent_skateshop.h"
+
+struct async_workshop_filepath_info{
+ PublishedFileId_t id;
+ char *buf;
+ u32 len;
+};
+
+struct async_workshop_installed_files_info{
+ PublishedFileId_t *buffer;
+ u32 *len; /* inout */
+};
+
+struct async_workshop_metadata_info{
+ struct workshop_file_info *info;
+ const char *path;
+};
+
+
+#define WORKSHOP_VIEW_PER_PAGE 15
+
+struct workshop_form{
+ enum workshop_op {
+ k_workshop_op_none,
+ k_workshop_op_downloading_submission,
+ k_workshop_op_publishing_update,
+ k_workshop_op_loading_model
+ }
+ op;
+
+ struct {
+ char title[80];
+ char description[512];
+ char author[32];
+ i32 submission_type_selection;
+ enum addon_type type;
+
+ PublishedFileId_t file_id; /* 0 if not published yet */
+
+ i32 visibility;
+ int submit_title, /* set if the respective controls are touched */
+ submit_description,
+ submit_file_and_image;
+ }
+ submission;
+
+ enum workshop_form_page{
+ k_workshop_form_hidden,
+ k_workshop_form_open, /* open but not looking at anything */
+ k_workshop_form_edit, /* editing a submission */
+ k_workshop_form_cclosing,
+ k_workshop_form_closing_good, /* post upload screen */
+ k_workshop_form_closing_bad,
+ }
+ page;
+
+ /* model viewer
+ * -----------------------------
+ */
+
+ char addon_folder[128];
+ struct player_board board_model;
+ struct player_model player_model;
+
+ /* what does the user want to do with the image preview? */
+ enum workshop_form_file_intent{
+ k_workshop_form_file_intent_none, /* loading probably */
+ k_workshop_form_file_intent_new, /* board_model is valid */
+ k_workshop_form_file_intent_keep_old /* just browsing */
+ }
+ file_intent;
+
+ world_instance *view_world;
+ ent_swspreview *ptr_ent;
+ v2f view_angles,
+ view_angles_begin;
+ v3f view_offset,
+ view_offset_begin;
+
+ float view_dist;
+ int view_changed;
+
+ /*
+ * published UGC request
+ * ------------------------------
+ */
+
+ struct {
+ UGCQueryHandle_t handle;
+ EResult result;
+
+ int all_item_count,
+ returned_item_count;
+ }
+ ugc_query;
+
+ /*
+ * UI information
+ * ------------------------------------------
+ */
+
+ const char *failure_or_success_string;
+ char error_msg[256];
+
+ int img_w, img_h;
+ u8 *img_buffer;
+
+ int view_published_page_count,
+ view_published_page_id;
+
+ struct published_file{
+ EResult result;
+ int result_index;
+ char title[80];
+ }
+ published_files_list[WORKSHOP_VIEW_PER_PAGE];
+ int published_files_list_length;
+}
+extern workshop_form;
+
+void workshop_init(void);
+int workshop_submit_command( int argc, const char *argv[] );
+void async_workshop_get_filepath( void *data, u32 len );
+void async_workshop_get_installed_files( void *data, u32 len );
+void workshop_load_metadata( const char *path,struct workshop_file_info *info );
+void workshop_form_gui( ui_context *ctx );
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#include "skaterift.h"
+#include "world.h"
+#include "network.h"
+#include "vg/vg_loader.h"
+#include "vg/vg_mem.h"
+#include "save.h"
+#include "player.h"
+#include "ent_traffic.h"
+
+struct world_static world_static;
+
+world_instance *world_current_instance(void)
+{
+ return &world_static.instances[ world_static.active_instance ];
+}
+
+static int skaterift_switch_instance_cmd( int argc, const char *argv[] );
+
+void world_init(void)
+{
+ vg_loader_step( world_render_init, NULL );
+ vg_loader_step( world_sfd_init, NULL );
+ vg_loader_step( world_water_init, NULL );
+ vg_loader_step( world_gates_init, NULL );
+ vg_loader_step( world_routes_init, NULL );
+
+ /* Allocate dynamic world memory arena */
+ u32 max_size = 76*1024*1024;
+ world_static.heap = vg_create_linear_allocator( vg_mem.rtmemory, max_size,
+ VG_MEMORY_SYSTEM );
+
+ vg_console_reg_cmd( "switch_active_instance",
+ skaterift_switch_instance_cmd, NULL );
+}
+
+void world_switch_instance( u32 index )
+{
+ localplayer.subsystem = k_player_subsystem_walk;
+
+ if( index >= VG_ARRAY_LEN(world_static.instances) ){
+ vg_error( "Instance ID out of range (%u)\n", index );
+ return;
+ }
+
+ world_instance *new = &world_static.instances[ index ];
+
+ if( new->status != k_world_status_loaded ){
+ vg_error( "Instance is not loaded (%u)\n", index );
+ return;
+ }
+
+ if( skaterift.demo_mode ){
+ if( world_static.instance_addons[index]->flags & ADDON_REG_PREMIUM ){
+ vg_error( "Can't switch to a premium world in the demo version\n" );
+ return;
+ }
+ }
+
+ world_instance *current =
+ &world_static.instances[ world_static.active_instance ];
+
+ if( index != world_static.active_instance ){
+ v3_copy( localplayer.rb.co, current->player_co );
+ skaterift_autosave(1);
+ }
+
+ v3_copy( new->player_co, localplayer.rb.co );
+
+ world_static.active_instance = index;
+ player__reset();
+}
+
+static int skaterift_switch_instance_cmd( int argc, const char *argv[] )
+{
+ if( argc )
+ world_switch_instance( atoi(argv[0]) );
+ else
+ vg_info( "switch_active_instance <id>\n" );
+ return 0;
+}
+
+void skaterift_world_get_save_path( enum world_purpose which, char buf[128] )
+{
+ addon_reg *reg = world_static.instance_addons[ which ];
+
+ if( !reg )
+ vg_fatal_error( "Looking up addon for world without one\n" );
+
+ char id[76];
+ addon_alias_uid( ®->alias, id );
+ snprintf( buf, 128, "savedata/%s.bkv", id );
+}
+
+void world_update( world_instance *world, v3f pos )
+{
+ world_render.sky_time += world_render.sky_rate * vg.time_delta;
+ world_render.sky_rate = vg_lerp( world_render.sky_rate,
+ world_render.sky_target_rate,
+ vg.time_delta * 5.0 );
+
+ world_routes_update_timer_texts( world );
+ world_routes_update( world );
+ ent_traffic_update( world, pos );
+ world_sfd_update( world, pos );
+ world_volumes_update( world, pos );
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+#include "render.h"
+#include "network_msg.h"
+#include "addon.h"
+#include "scene.h"
+
+/* types
+ */
+
+enum world_geo_type{
+ k_world_geo_type_solid = 0,
+ k_world_geo_type_nonsolid = 1,
+ k_world_geo_type_water = 2
+};
+
+enum world_purpose{
+ k_world_purpose_invalid = -1,
+ k_world_purpose_hub = 0,
+ k_world_purpose_client = 1,
+ k_world_max
+};
+
+struct leaderboard_cache {
+ enum request_status status;
+ f64 cache_time;
+ u8 *data;
+ u32 data_len;
+};
+
+typedef struct world_instance world_instance;
+
+void skaterift_world_get_save_path( enum world_purpose which, char buf[128] );
+
+/* submodule headers */
+#include "world_entity.h"
+#include "world_gate.h"
+#include "world_gen.h"
+#include "world_info.h"
+#include "world_physics.h"
+#include "world_render.h"
+#include "world_sfd.h"
+#include "world_volumes.h"
+#include "world_water.h"
+#include "world_audio.h"
+#include "world_routes.h"
+#include "world_routes_ui.h"
+
+/* console variables */
+
+static f32 k_day_length = 30.0f; /* minutes */
+static i32 k_debug_light_indices = 0,
+ k_debug_light_complexity= 0,
+ k_light_preview = 0,
+ k_light_editor = 0;
+
+#define WORLD_SURFACE_HAS_TRAFFIC 0x1
+#define WORLD_SURFACE_HAS_PROPS 0x2
+
+struct world_instance {
+ /* Fixed items
+ * -------------------------------------------------------
+ */
+
+ v4f player_co;
+
+ void *heap;
+ enum world_status{
+ k_world_status_unloaded = 0,
+ k_world_status_loading = 1,
+ k_world_status_loaded = 2,
+ k_world_status_unloading = 3 /* dont spawn sounds and stuff */
+ }
+ status;
+
+ struct{
+ boxf depthbounds;
+ int depth_computed;
+
+ float height;
+ int enabled;
+ v4f plane;
+ }
+ water;
+
+ f64 time;
+ f32 tar_min, tar_max;
+
+ /* STD140 */
+ struct ub_world_lighting{
+ v4f g_cube_min,
+ g_cube_inv_range;
+
+ v4f g_water_plane,
+ g_depth_bounds;
+
+ v4f g_daysky_colour;
+ v4f g_nightsky_colour;
+ v4f g_sunset_colour;
+ v4f g_ambient_colour;
+ v4f g_sunset_ambient;
+ v4f g_sun_colour;
+ v4f g_sun_dir;
+ v4f g_board_0;
+ v4f g_board_1;
+
+ float g_water_fog;
+ float g_time;
+ float g_realtime;
+ float g_shadow_length;
+ float g_shadow_spread;
+
+ float g_time_of_day;
+ float g_day_phase;
+ float g_sunset_phase;
+
+ int g_light_preview;
+ int g_shadow_samples;
+
+ int g_debug_indices;
+ int g_debug_complexity;
+ }
+ ub_lighting;
+ GLuint ubo_lighting;
+ int ubo_bind_point;
+
+ GLuint tbo_light_entities,
+ tex_light_entities,
+ tex_light_cubes;
+
+ float probabilities[3];
+
+ v3i light_cubes;
+ vg_framebuffer *heightmap;
+
+ /*
+ * Dynamically allocated when world_load is called.
+ *
+ * the following arrays index somewhere into this linear
+ * allocator
+ * --------------------------------------------------------------------------
+ */
+
+ /*
+ * Main world .mdl
+ */
+ mdl_context meta;
+
+ GLuint *textures;
+ u32 texture_count;
+
+ struct world_surface{
+ mdl_material info;
+ mdl_submesh sm_geo,
+ sm_no_collide;
+ u32 flags;
+ u32 alpha_tex;
+ }
+ * surfaces;
+ u32 surface_count;
+
+ ent_worldinfo info;
+ mdl_array_ptr ent_spawn,
+ ent_gate,
+ ent_light,
+ ent_route_node,
+ ent_path_index,
+ ent_checkpoint,
+ ent_route,
+ ent_water,
+
+ ent_audio_clip,
+ ent_audio,
+ ent_volume,
+ ent_traffic,
+ ent_skateshop,
+ ent_marker,
+ ent_camera,
+ ent_swspreview,
+ ent_ccmd,
+ ent_objective,
+ ent_challenge,
+ ent_relay,
+ ent_cubemap,
+ ent_miniworld,
+ ent_prop,
+ ent_region,
+ ent_glider,
+ ent_npc;
+
+ enum skybox {
+ k_skybox_default,
+ k_skybox_space
+ } skybox;
+
+ ent_gate *rendering_gate;
+
+ /* logic
+ * ----------------------------------------------------
+ */
+
+ /* world geometry */
+ scene_context scene_geo,
+ scene_no_collide,
+ scene_lines;
+
+ /* spacial mappings */
+ bh_tree *geo_bh,
+ *entity_bh;
+ u32 *entity_list;
+
+ /* graphics */
+ glmesh mesh_route_lines;
+ glmesh mesh_geo,
+ mesh_no_collide;
+ u32 cubemap_cooldown, cubemap_side;
+
+ /* leaderboards */
+ struct leaderboard_cache *leaderboard_cache;
+
+ /* ui */
+ struct route_ui *routes_ui;
+};
+
+struct world_static {
+ /*
+ * Allocated as system memory
+ * --------------------------------------------------------------------------
+ */
+ void *heap;
+
+ u32 current_run_version;
+ double time, rewind_from, rewind_to, last_use;
+
+ u32 active_trigger_volumes[8];
+ u32 active_trigger_volume_count;
+
+ addon_reg *instance_addons[ k_world_max ];
+ world_instance instances[ k_world_max ];
+
+ enum world_purpose active_instance;
+ u32 focused_entity; /* like skateshop, challenge.. */
+ f32 focus_strength;
+ vg_camera focus_cam;
+
+ /* challenges */
+ ent_objective *challenge_target;
+ f32 challenge_timer;
+
+ enum world_loader_state{
+ k_world_loader_none,
+ k_world_loader_preload,
+ k_world_loader_load
+ }
+ load_state;
+
+ bool clear_async_op_when_done;
+}
+extern world_static;
+
+struct world_load_args
+{
+ enum world_purpose purpose;
+ addon_reg *reg;
+};
+
+void world_init(void);
+world_instance *world_current_instance(void);
+void world_switch_instance( u32 index );
+void skaterift_world_load_thread( void *_args );
+void world_update( world_instance *world, v3f pos );
--- /dev/null
+#include "audio.h"
+#include "world_audio.h"
+
+/* finds any active playing in world and fades them out, we can only do this
+ * while unloading */
+void world_fadeout_audio( world_instance *world )
+{
+ if( world->status != k_world_status_unloading ){
+ vg_fatal_error( "World status must be set to 'unloading', to fadeout"
+ " audio.\n" );
+ }
+
+ u8 world_id = (world - world_static.instances) + 1;
+
+ audio_lock();
+ for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
+ audio_channel *ch = &vg_audio.channels[i];
+
+ if( ch->allocated && (ch->world_id == world_id) ){
+ ch = audio_channel_fadeout( ch, 1.0f );
+ }
+ }
+ audio_unlock();
+}
+
+/*
+ * Trace out a random point, near the player to try and determine water areas
+ */
+enum audio_sprite_type world_audio_sample_sprite_random(v3f origin, v3f output)
+{
+ v3f chance = { (vg_randf64(&vg.rand)-0.5f) * 30.0f,
+ 8,
+ (vg_randf64(&vg.rand)-0.5f) * 30.0f };
+
+ v3f pos;
+ v3_add( chance, origin, pos );
+
+ ray_hit contact;
+ contact.dist = vg_minf( 16.0f, pos[1] );
+
+ world_instance *world = world_current_instance();
+
+ if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &contact,
+ k_material_flag_ghosts ) ){
+ struct world_surface *mat = ray_hit_surface( world, &contact );
+
+ if( mat->info.surface_prop == k_surface_prop_grass){
+ v3_copy( contact.pos, output );
+ return k_audio_sprite_type_grass;
+ }
+ else{
+ return k_audio_sprite_type_none;
+ }
+ }
+
+ output[0] = pos[0];
+ output[1] = 0.0f;
+ output[2] = pos[2];
+
+ float dist = fabsf(output[1] - origin[1]);
+
+ if( world->water.enabled && dist<=40.0f && !(world->info.flags&0x2) )
+ return k_audio_sprite_type_water;
+ else
+ return k_audio_sprite_type_none;
+}
+
+void world_audio_sample_distances( v3f co, int *index, float *value )
+{
+ float inr3 = 0.57735027,
+ inr2 = 0.70710678118;
+
+ v3f sample_directions[] = {
+ { -1.0f, 0.0f, 0.0f },
+ { 1.0f, 0.0f, 0.0f },
+ { 0.0f, 0.0f, 1.0f },
+ { 0.0f, 0.0f, -1.0f },
+ { 0.0f, 1.0f, 0.0f },
+ { 0.0f, -1.0f, 0.0f },
+ { -inr3, inr3, inr3 },
+ { inr3, inr3, inr3 },
+ { -inr3, inr3, -inr3 },
+ { inr3, inr3, -inr3 },
+ { -inr2, 0.0f, inr2 },
+ { inr2, 0.0f, inr2 },
+ { -inr2, 0.0f, -inr2 },
+ { inr2, 0.0f, -inr2 },
+ };
+
+ static int si = 0;
+ static float distances[16];
+
+ ray_hit ray;
+ ray.dist = 5.0f;
+
+ v3f rc, rd, ro;
+ v3_copy( sample_directions[ si ], rd );
+ v3_add( co, (v3f){0.0f,1.5f,0.0f}, ro );
+ v3_copy( ro, rc );
+
+ float dist = 200.0f;
+
+ for( int i=0; i<10; i++ ){
+ if( ray_world( world_current_instance(), rc, rd, &ray,
+ k_material_flag_ghosts ) ){
+ dist = (float)i*5.0f + ray.dist;
+ break;
+ }
+ else{
+ v3_muladds( rc, rd, ray.dist, rc );
+ }
+ }
+
+ distances[si] = dist;
+
+ if( vg_audio.debug_ui && vg_lines.enabled ){
+ for( int i=0; i<14; i++ ){
+ if( distances[i] != 200.0f ){
+ u32 colours[] = { VG__RED, VG__BLUE, VG__GREEN,
+ VG__CYAN, VG__YELOW, VG__PINK,
+ VG__WHITE };
+
+ u32 colour = colours[i%7];
+
+ v3f p1;
+ v3_muladds( ro, sample_directions[i], distances[i], p1 );
+ vg_line( ro, p1, colour );
+ vg_line_point( p1, 0.1f, colour );
+ }
+ }
+ }
+
+ *index = si;
+ *value = dist;
+
+ si ++;
+ if( si >= 14 )
+ si = 0;
+}
--- /dev/null
+#pragma once
+#include "world.h"
+
+void world_fadeout_audio( world_instance *world );
+void world_audio_sample_distances( v3f co, int *index, float *value );
+enum audio_sprite_type
+world_audio_sample_sprite_random( v3f origin, v3f output );
--- /dev/null
+#include "vg/vg_steam.h"
+#include "vg/vg_steam_user_stats.h"
+#include "model.h"
+#include "entity.h"
+#include "world.h"
+#include "world_load.h"
+#include "save.h"
+#include "vg/vg_msg.h"
+#include "menu.h"
+#include "ent_challenge.h"
+#include "ent_skateshop.h"
+#include "ent_route.h"
+#include "ent_traffic.h"
+#include "ent_glider.h"
+#include "ent_region.h"
+#include "ent_npc.h"
+#include "ent_camera.h"
+#include "input.h"
+#include "player_walk.h"
+
+bh_system bh_system_entity_list =
+{
+ .expand_bound = entity_bh_expand_bound,
+ .item_centroid = entity_bh_centroid,
+ .item_closest = entity_bh_closest,
+ .item_swap = entity_bh_swap,
+ .item_debug = entity_bh_debug,
+ .cast_ray = NULL
+};
+
+void world_entity_set_focus( u32 entity_id )
+{
+ if( world_static.focused_entity )
+ {
+ vg_warn( "Entity %u#%u tried to take focus from %u#%u\n",
+ mdl_entity_id_type( entity_id ),
+ mdl_entity_id_id( entity_id ),
+ mdl_entity_id_type( world_static.focused_entity ),
+ mdl_entity_id_id( world_static.focused_entity ) );
+ return;
+ }
+
+ world_static.focused_entity = entity_id;
+}
+
+void world_entity_focus_modal(void)
+{
+ localplayer.immobile = 1;
+ menu.disable_open = 1;
+ srinput.state = k_input_state_resume;
+
+ v3_zero( localplayer.rb.v );
+ v3_zero( localplayer.rb.w );
+ player_walk.move_speed = 0.0f;
+ skaterift.activity = k_skaterift_ent_focus;
+}
+
+void world_entity_exit_modal(void)
+{
+ if( skaterift.activity != k_skaterift_ent_focus )
+ {
+ vg_warn( "Entity %u#%u tried to exit modal when we weren't in one\n",
+ mdl_entity_id_type( world_static.focused_entity ),
+ mdl_entity_id_id( world_static.focused_entity ) );
+ return;
+ }
+
+ localplayer.immobile = 0;
+ menu.disable_open = 0;
+ srinput.state = k_input_state_resume;
+ skaterift.activity = k_skaterift_default;
+}
+
+void world_entity_clear_focus(void)
+{
+ if( skaterift.activity == k_skaterift_ent_focus )
+ {
+ vg_warn( "Entity %u#%u tried to clear focus before exiting modal\n",
+ mdl_entity_id_type( world_static.focused_entity ),
+ mdl_entity_id_id( world_static.focused_entity ) );
+ return;
+ }
+
+ world_static.focused_entity = 0;
+}
+
+void world_entity_focus_camera( world_instance *world, u32 uid )
+{
+ if( mdl_entity_id_type( uid ) == k_ent_camera )
+ {
+ u32 index = mdl_entity_id_id( uid );
+ ent_camera *cam = mdl_arritm( &world->ent_camera, index );
+ ent_camera_unpack( cam, &world_static.focus_cam );
+ }
+ else
+ {
+ vg_camera_copy( &localplayer.cam, &world_static.focus_cam );
+
+ /* TODO ? */
+ world_static.focus_cam.nearz = localplayer.cam.nearz;
+ world_static.focus_cam.farz = localplayer.cam.farz;
+ }
+}
+
+/* logic preupdate */
+void world_entity_focus_preupdate(void)
+{
+ f32 rate = vg_minf( 1.0f, vg.time_frame_delta * 2.0f );
+ int active = 0;
+ if( skaterift.activity == k_skaterift_ent_focus )
+ active = 1;
+
+ vg_slewf( &world_static.focus_strength, active,
+ vg.time_frame_delta * (1.0f/0.5f) );
+
+ if( world_static.focused_entity == 0 )
+ return;
+
+ u32 type = mdl_entity_id_type( world_static.focused_entity ),
+ index = mdl_entity_id_id( world_static.focused_entity );
+
+ world_instance *world = world_current_instance();
+
+ static void (*table[])( ent_focus_context *ctx ) =
+ {
+ [ k_ent_skateshop ] = ent_skateshop_preupdate,
+ [ k_ent_challenge ] = ent_challenge_preupdate,
+ [ k_ent_route ] = ent_route_preupdate,
+ [ k_ent_npc ] = ent_npc_preupdate,
+ };
+
+ if( (type > VG_ARRAY_LEN(table)) || (table[type] == NULL) )
+ {
+ vg_fatal_error( "No pre-update method set for entity (%u#%u)\n",
+ type, index );
+ }
+
+ table[type]( &(ent_focus_context){
+ .world = world,
+ .index = index,
+ .active = active } );
+}
+
+/* additional renderings like text etc.. */
+void world_entity_focus_render(void)
+{
+ world_instance *world = world_current_instance();
+ if( skaterift.activity != k_skaterift_ent_focus ){
+ skateshop_render_nonfocused( world, &g_render.cam );
+ return;
+ }
+
+ u32 type = mdl_entity_id_type( world_static.focused_entity ),
+ index = mdl_entity_id_id( world_static.focused_entity );
+
+ if( type == k_ent_skateshop ){
+ ent_skateshop *skateshop = mdl_arritm( &world->ent_skateshop, index );
+ skateshop_render( skateshop );
+ }
+ else if( type == k_ent_challenge ){}
+ else if( type == k_ent_route ){}
+ else if( type == k_ent_miniworld ){}
+ else if( type == k_ent_npc ){}
+ else {
+ vg_fatal_error( "Programming error\n" );
+ }
+}
+
+void world_gen_entities_init( world_instance *world )
+{
+ /* lights */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
+ ent_light *light = mdl_arritm( &world->ent_light, j );
+
+ m4x3f to_world;
+ q_m3x3( light->transform.q, to_world );
+ v3_copy( light->transform.co, to_world[3] );
+ m4x3_invert_affine( to_world, light->inverse_world );
+
+ light->angle_sin_cos[0] = sinf( light->angle * 0.5f );
+ light->angle_sin_cos[1] = cosf( light->angle * 0.5f );
+ }
+
+ vg_async_call( world_link_gates_async, world, 0 );
+ vg_async_stall();
+
+ /* water */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_water); j++ ){
+ ent_water *water = mdl_arritm( &world->ent_water, j );
+ if( world->water.enabled ){
+ vg_warn( "Multiple water surfaces in level!\n" );
+ break;
+ }
+
+ world->water.enabled = 1;
+ water_set_surface( world, water->transform.co[1] );
+ }
+
+ /* volumes */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_volume); j++ ){
+ ent_volume *volume = mdl_arritm( &world->ent_volume, j );
+ mdl_transform_m4x3( &volume->transform, volume->to_world );
+ m4x3_invert_full( volume->to_world, volume->to_local );
+ }
+
+ /* audio packs */
+ for( u32 j=0; j<mdl_arrcount(&world->ent_audio); j++ ){
+ ent_audio *audio = mdl_arritm( &world->ent_audio, j );
+
+ for( u32 k=0; k<audio->clip_count; k++ ){
+ ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
+ audio->clip_start+k );
+
+ if( clip->_.file.pack_size ){
+ u32 size = clip->_.file.pack_size,
+ offset = clip->_.file.pack_offset;
+
+ /* embedded files are fine to clear the scratch buffer, only
+ * external audio uses it */
+
+ vg_linear_clear( vg_mem.scratch );
+ void *data = vg_linear_alloc( vg_mem.scratch,
+ clip->_.file.pack_size );
+
+ mdl_fread_pack_file( &world->meta, &clip->_.file, data );
+
+ clip->_.clip.path = NULL;
+ clip->_.clip.flags = audio->flags;
+ clip->_.clip.data = data;
+ clip->_.clip.size = size;
+ }
+ else{
+ clip->_.clip.path = mdl_pstr(&world->meta,clip->_.file.pstr_path);
+ clip->_.clip.flags = audio->flags;
+ clip->_.clip.data = NULL;
+ clip->_.clip.size = 0;
+ }
+
+ audio_clip_load( &clip->_.clip, world->heap );
+ }
+ }
+
+ /* create generic entity hierachy for those who need it */
+ u32 indexed_count = 0;
+ struct {
+ u32 type;
+ mdl_array_ptr *array;
+ }
+ indexables[] = {
+ { k_ent_gate, &world->ent_gate },
+ { k_ent_objective, &world->ent_objective },
+ { k_ent_volume, &world->ent_volume },
+ { k_ent_challenge, &world->ent_challenge },
+ { k_ent_glider, &world->ent_glider },
+ { k_ent_npc, &world->ent_npc }
+ };
+
+ for( u32 i=0; i<VG_ARRAY_LEN(indexables); i++ )
+ indexed_count += mdl_arrcount( indexables[i].array );
+ vg_info( "indexing %u entities\n", indexed_count );
+
+ world->entity_list = vg_linear_alloc( world->heap,
+ vg_align8(indexed_count*sizeof(u32)));
+
+ u32 index=0;
+ for( u32 i=0; i<VG_ARRAY_LEN(indexables); i++ ){
+ u32 type = indexables[i].type,
+ count = mdl_arrcount( indexables[i].array );
+
+ for( u32 j=0; j<count; j ++ )
+ world->entity_list[index ++] = mdl_entity_id( type, j );
+ }
+
+ world->entity_bh = bh_create( world->heap, &bh_system_entity_list, world,
+ indexed_count, 2 );
+
+ world->tar_min = world->entity_bh->nodes[0].bbx[0][1];
+ world->tar_max = world->entity_bh->nodes[0].bbx[1][1] + 20.0f;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i++ ){
+ ent_marker *marker = mdl_arritm( &world->ent_marker, i );
+
+ if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_min" ) )
+ world->tar_min = marker->transform.co[1];
+
+ if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_max" ) )
+ world->tar_max = marker->transform.co[1];
+ }
+}
+
+ent_spawn *world_find_closest_spawn( world_instance *world, v3f position )
+{
+ ent_spawn *rp = NULL, *r;
+ float min_dist = INFINITY;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
+ r = mdl_arritm( &world->ent_spawn, i );
+ float d = v3_dist2( r->transform.co, position );
+
+ if( d < min_dist ){
+ min_dist = d;
+ rp = r;
+ }
+ }
+
+ if( !rp ){
+ if( mdl_arrcount(&world->ent_spawn) ){
+ vg_warn( "Invalid distances to spawns.. defaulting to first one.\n" );
+ return mdl_arritm( &world->ent_spawn, 0 );
+ }
+ else{
+ vg_error( "There are no spawns in the level!\n" );
+ }
+ }
+
+ return rp;
+}
+
+ent_spawn *world_find_spawn_by_name( world_instance *world, const char *name )
+{
+ ent_spawn *rp = NULL, *r;
+ for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
+ r = mdl_arritm( &world->ent_spawn, i );
+ if( !strcmp( mdl_pstr(&world->meta, r->pstr_name), name ) ){
+ rp = r;
+ break;
+ }
+ }
+
+ if( !rp )
+ vg_warn( "No spawn named '%s'\n", name );
+
+ return rp;
+}
+
+void world_default_spawn_pos( world_instance *world, v3f pos )
+{
+ ent_spawn *rp = world_find_spawn_by_name( world, "start" );
+ if( !rp ) rp = world_find_closest_spawn( world, (v3f){0,0,0} );
+ if( rp )
+ v3_copy( rp->transform.co, pos );
+ else
+ {
+ vg_error( "There are no valid spawns in the world\n" );
+ v3_zero( pos );
+ }
+}
+
+entity_call_result ent_volume_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_volume *volume = mdl_arritm( &world->ent_volume, index );
+
+ if( !volume->target )
+ return k_entity_call_result_OK;
+
+ if( call->function == k_ent_function_trigger )
+ {
+ call->id = volume->target;
+
+ if( volume->flags & k_ent_volume_flag_particles )
+ {
+ float *co = alloca( sizeof(float)*3 );
+ co[0] = vg_randf64(&vg.rand)*2.0f-1.0f;
+ co[1] = vg_randf64(&vg.rand)*2.0f-1.0f;
+ co[2] = vg_randf64(&vg.rand)*2.0f-1.0f;
+ m4x3_mulv( volume->to_world, co, co );
+
+ call->function = k_ent_function_particle_spawn;
+ call->data = co;
+ entity_call( world, call );
+ }
+ else
+ {
+ call->function = volume->trigger.event;
+ entity_call( world, call );
+ }
+
+ return k_entity_call_result_OK;
+ }
+ else if( call->function == k_ent_function_trigger_leave )
+ {
+ call->id = volume->target;
+
+ if( volume->flags & k_ent_volume_flag_particles )
+ {
+ vg_warn( "Invalid condition; calling leave on particle volume.\n" );
+ }
+ else
+ {
+ call->function = volume->trigger.event_leave;
+ entity_call( world, call );
+ }
+
+ return k_entity_call_result_OK;
+ }
+
+ return k_entity_call_result_unhandled;
+}
+
+entity_call_result ent_audio_call( world_instance *world, ent_call *call )
+{
+ if( world->status == k_world_status_unloading )
+ {
+ vg_warn( "cannot modify audio while unloading world\n" );
+ return k_entity_call_result_invalid;
+ }
+
+ u8 world_id = (world - world_static.instances) + 1;
+ u32 index = mdl_entity_id_id( call->id );
+ ent_audio *audio = mdl_arritm( &world->ent_audio, index );
+
+ v3f sound_co;
+
+ if( call->function == k_ent_function_particle_spawn )
+ {
+ v3_copy( call->data, sound_co );
+ }
+ else if( call->function == k_ent_function_trigger )
+ {
+ v3_copy( audio->transform.co, sound_co );
+ }
+ else
+ return k_entity_call_result_unhandled;
+
+ float chance = vg_randf64(&vg.rand)*100.0f,
+ bar = 0.0f;
+
+ for( u32 i=0; i<audio->clip_count; i++ ){
+ ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
+ audio->clip_start+i );
+
+ float mod = world->probabilities[ audio->probability_curve ],
+ p = clip->probability * mod;
+
+ bar += p;
+ if( chance < bar )
+ {
+ audio_lock();
+
+ if( audio->behaviour == k_channel_behaviour_unlimited )
+ {
+ audio_oneshot_3d( &clip->_.clip, sound_co,
+ audio->transform.s[0],
+ audio->volume );
+ }
+ else if( audio->behaviour == k_channel_behaviour_discard_if_full )
+ {
+ audio_channel *ch =
+ audio_get_group_idle_channel( audio->group,
+ audio->max_channels );
+
+ if( ch )
+ {
+ audio_channel_init( ch, &clip->_.clip, audio->flags );
+ audio_channel_group( ch, audio->group );
+ audio_channel_world( ch, world_id );
+ audio_channel_set_spacial( ch, sound_co, audio->transform.s[0] );
+ audio_channel_edit_volume( ch, audio->volume, 1 );
+ ch = audio_relinquish_channel( ch );
+ }
+ }
+ else if( audio->behaviour == k_channel_behaviour_crossfade_if_full)
+ {
+ audio_channel *ch =
+ audio_get_group_idle_channel( audio->group,
+ audio->max_channels );
+
+ /* group is full */
+ if( !ch ){
+ audio_channel *existing =
+ audio_get_group_first_active_channel( audio->group );
+
+ if( existing ){
+ if( existing->source == &clip->_.clip ){
+ audio_unlock();
+ return k_entity_call_result_OK;
+ }
+
+ existing->group = 0;
+ existing = audio_channel_fadeout(existing, audio->crossfade);
+ }
+
+ ch = audio_get_first_idle_channel();
+ }
+
+ if( ch )
+ {
+ audio_channel_init( ch, &clip->_.clip, audio->flags );
+ audio_channel_group( ch, audio->group );
+ audio_channel_world( ch, world_id );
+ audio_channel_fadein( ch, audio->crossfade );
+ ch = audio_relinquish_channel( ch );
+ }
+ }
+
+ audio_unlock();
+ return k_entity_call_result_OK;
+ }
+ }
+ return k_entity_call_result_OK;
+}
+
+
+entity_call_result ent_ccmd_call( world_instance *world, ent_call *call )
+{
+ if( call->function == k_ent_function_trigger )
+ {
+ u32 index = mdl_entity_id_id( call->id );
+ ent_ccmd *ccmd = mdl_arritm( &world->ent_ccmd, index );
+ vg_execute_console_input( mdl_pstr(&world->meta, ccmd->pstr_command), 0 );
+ return k_entity_call_result_OK;
+ }
+ else
+ return k_entity_call_result_unhandled;
+}
+
+/*
+ * BVH implementation
+ * ----------------------------------------------------------------------------
+ */
+
+void entity_bh_expand_bound( void *user, boxf bound, u32 item_index )
+{
+ world_instance *world = user;
+
+ u32 id = world->entity_list[ item_index ],
+ type = mdl_entity_id_type( id ),
+ index = mdl_entity_id_id( id );
+
+ if( type == k_ent_gate ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, index );
+ boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
+ { gate->dimensions[0], gate->dimensions[1], 0.1f }};
+
+ m4x3_expand_aabb_aabb( gate->to_world, bound, box );
+ }
+ else if( type == k_ent_objective ){
+ ent_objective *objective = mdl_arritm( &world->ent_objective, index );
+
+ /* TODO: This might be more work than necessary. could maybe just get
+ * away with representing them as points */
+
+ boxf box;
+ box_init_inf( box );
+
+ for( u32 i=0; i<objective->submesh_count; i++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ objective->submesh_start+i );
+ box_concat( box, sm->bbx );
+ }
+
+ m4x3f transform;
+ mdl_transform_m4x3( &objective->transform, transform );
+ m4x3_expand_aabb_aabb( transform, bound, box );
+ }
+ else if( type == k_ent_volume ){
+ ent_volume *volume = mdl_arritm( &world->ent_volume, index );
+ m4x3_expand_aabb_aabb( volume->to_world, bound,
+ (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
+ }
+ else if( type == k_ent_challenge ){
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
+
+ boxf box = {{-1.2f*0.5f,-0.72f*0.5f,-0.01f*0.5f},
+ { 1.2f*0.5f, 0.72f*0.5f, 0.01f*0.5f}};
+ m4x3f transform;
+ mdl_transform_m4x3( &challenge->transform, transform );
+ m4x3_expand_aabb_aabb( transform, bound, box );
+ }
+ else if( type == k_ent_glider ){
+ ent_glider *glider = mdl_arritm( &world->ent_glider, index );
+ m4x3f transform;
+ mdl_transform_m4x3( &glider->transform, transform );
+ m4x3_expand_aabb_aabb( transform, bound,
+ (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
+ }
+ else if( type == k_ent_npc )
+ {
+ ent_npc *npc = mdl_arritm( &world->ent_npc, index );
+ box_addpt( bound, npc->transform.co );
+ }
+ else{
+ vg_fatal_error( "Programming error\n" );
+ }
+}
+
+float entity_bh_centroid( void *user, u32 item_index, int axis )
+{
+ world_instance *world = user;
+
+ u32 id = world->entity_list[ item_index ],
+ type = mdl_entity_id_type( id ),
+ index = mdl_entity_id_id( id );
+
+ if( type == k_ent_gate ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, index );
+ return gate->to_world[3][axis];
+ }
+ else if( type == k_ent_objective ){
+ ent_objective *objective = mdl_arritm( &world->ent_objective, index );
+ return objective->transform.co[axis];
+ }
+ else if( type == k_ent_volume ){
+ ent_volume *volume = mdl_arritm( &world->ent_volume, index );
+ return volume->transform.co[axis];
+ }
+ else if( type == k_ent_challenge )
+ {
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
+ return challenge->transform.co[axis];
+ }
+ else if( type == k_ent_glider )
+ {
+ ent_glider *glider = mdl_arritm( &world->ent_glider, index );
+ return glider->transform.co[axis];
+ }
+ else if( type == k_ent_npc )
+ {
+ ent_npc *npc = mdl_arritm( &world->ent_npc, index );
+ return npc->transform.co[axis];
+ }
+ else
+ {
+ vg_fatal_error( "Programming error\n" );
+ return INFINITY;
+ }
+}
+
+void entity_bh_swap( void *user, u32 ia, u32 ib )
+{
+ world_instance *world = user;
+
+ u32 a = world->entity_list[ ia ],
+ b = world->entity_list[ ib ];
+
+ world->entity_list[ ia ] = b;
+ world->entity_list[ ib ] = a;
+}
+
+void entity_bh_debug( void *user, u32 item_index ){
+ world_instance *world = user;
+
+ u32 id = world->entity_list[ item_index ],
+ type = mdl_entity_id_type( id ),
+ index = mdl_entity_id_id( id );
+
+ if( type == k_ent_gate ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, index );
+ boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
+ { gate->dimensions[0], gate->dimensions[1], 0.1f }};
+ vg_line_boxf_transformed( gate->to_world, box, 0xf000ff00 );
+ }
+ else if( type == k_ent_objective ){
+ ent_objective *objective = mdl_arritm( &world->ent_objective, index );
+ boxf box;
+ box_init_inf( box );
+
+ for( u32 i=0; i<objective->submesh_count; i++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ objective->submesh_start+i );
+ box_concat( box, sm->bbx );
+ }
+
+ m4x3f transform;
+ mdl_transform_m4x3( &objective->transform, transform );
+ vg_line_boxf_transformed( transform, box, 0xf000ff00 );
+ }
+ else if( type == k_ent_volume ){
+ ent_volume *volume = mdl_arritm( &world->ent_volume, index );
+ vg_line_boxf_transformed( volume->to_world,
+ (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}},
+ 0xf000ff00 );
+ }
+ else if( type == k_ent_challenge ){
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
+
+ boxf box = {{-1.2f*0.5f,-0.72f*0.5f,-0.01f*0.5f},
+ { 1.2f*0.5f, 0.72f*0.5f, 0.01f*0.5f}};
+ m4x3f transform;
+ mdl_transform_m4x3( &challenge->transform, transform );
+ vg_line_boxf_transformed( transform, box, 0xf0ff0000 );
+ }
+ else{
+ vg_fatal_error( "Programming error\n" );
+ }
+}
+
+void update_ach_models(void)
+{
+ world_instance *hub = &world_static.instances[k_world_purpose_hub];
+ if( hub->status != k_world_status_loaded ) return;
+
+ for( u32 i=0; i<mdl_arrcount( &hub->ent_prop ); i ++ ){
+ ent_prop *prop = mdl_arritm( &hub->ent_prop, i );
+ if( prop->flags & 0x2 ){
+ if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "MARC" ) )
+ if( skaterift.achievements & 0x1 )
+ prop->flags &= ~0x1;
+ if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "ALBERT" ) )
+ if( skaterift.achievements & 0x2 )
+ prop->flags &= ~0x1;
+ if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "JANET" ) )
+ if( skaterift.achievements & 0x4 )
+ prop->flags &= ~0x1;
+ if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "BERNADETTA" ) )
+ if( skaterift.achievements & 0x8 )
+ prop->flags &= ~0x1;
+ }
+ }
+}
+
+void entity_bh_closest( void *user, u32 item_index, v3f point, v3f closest )
+{
+ world_instance *world = user;
+
+ u32 id = world->entity_list[ item_index ],
+ type = mdl_entity_id_type( id ),
+ index = mdl_entity_id_id( id );
+
+ if( type == k_ent_gate ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, index );
+ v3_copy( gate->to_world[3], closest );
+ }
+ else if( type == k_ent_objective ){
+ ent_objective *challenge = mdl_arritm( &world->ent_objective, index );
+ v3_copy( challenge->transform.co, closest );
+ }
+ else if( type == k_ent_volume ){
+ ent_volume *volume = mdl_arritm( &world->ent_volume, index );
+ v3_copy( volume->to_world[3], closest );
+ }
+ else if( type == k_ent_challenge ){
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
+ v3_copy( challenge->transform.co, closest );
+ }
+ else{
+ vg_fatal_error( "Programming error\n" );
+ }
+}
+
+void world_entity_start( world_instance *world, vg_msg *sav )
+{
+ vg_info( "Start instance %p\n", world );
+
+ world->probabilities[ k_probability_curve_constant ] = 1.0f;
+ for( u32 i=0; i<mdl_arrcount(&world->ent_audio); i++ )
+ {
+ ent_audio *audio = mdl_arritm(&world->ent_audio,i);
+ if( audio->flags & AUDIO_FLAG_AUTO_START )
+ {
+ ent_call call;
+ call.data = NULL;
+ call.function = k_ent_function_trigger;
+ call.id = mdl_entity_id( k_ent_audio, i );
+ entity_call( world, &call );
+ }
+ }
+
+ /* read savedata
+ * ----------------------------------------------------------------------- */
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
+ const char *alias = mdl_pstr( &world->meta, challenge->pstr_alias );
+
+ u32 result;
+ vg_msg_getkvintg( sav, alias, k_vg_msg_u32, &result, NULL );
+
+ if( result ){
+ ent_call call;
+ call.data = NULL;
+ call.function = 0;
+ call.id = mdl_entity_id( k_ent_challenge, i );
+ entity_call( world, &call );
+ }
+ }
+
+ vg_msg routes_block = *sav;
+ if( vg_msg_seekframe( &routes_block, "routes" ) ){
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ vg_msg route_info = routes_block;
+ if( vg_msg_seekframe( &route_info,
+ mdl_pstr(&world->meta,route->pstr_name) ) ){
+
+ u32 flags;
+ vg_msg_getkvintg( &route_info, "flags", k_vg_msg_u32,
+ &flags, NULL );
+ route->flags |= flags;
+
+ vg_msg_getkvintg( &route_info, "best_laptime", k_vg_msg_f64,
+ &route->best_laptime, NULL );
+
+ f32 sections[ route->checkpoints_count ];
+ vg_msg_cmd cmd;
+ if( vg_msg_getkvcmd( &route_info, "sections", &cmd ) ){
+ vg_msg_cast( cmd.value, cmd.code, sections,
+ k_vg_msg_f32 |
+ vg_msg_count_bits(route->checkpoints_count) );
+ }
+ else{
+ for( u32 j=0; j<route->checkpoints_count; j ++ )
+ sections[j] = 0.0f;
+ }
+
+ for( u32 j=0; j<route->checkpoints_count; j ++ ){
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
+ route->checkpoints_start + j );
+
+ cp->best_time = sections[j];
+ }
+
+ /* LEGACY: check if steam achievements can give us a medal */
+ if( steam_ready && steam_stats_ready ){
+ for( u32 j=0; j<VG_ARRAY_LEN(track_infos); j ++ ){
+ struct track_info *inf = &track_infos[j];
+ if( !strcmp(inf->name,
+ mdl_pstr(&world->meta,route->pstr_name))){
+
+ steamapi_bool set = 0;
+ if( SteamAPI_ISteamUserStats_GetAchievement(
+ hSteamUserStats, inf->achievement_id, &set ) )
+ {
+ if( set ){
+ route->flags |= k_ent_route_flag_achieve_silver;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ent_region_re_eval( world );
+}
+
+void world_entity_serialize( world_instance *world, vg_msg *sav )
+{
+ for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
+ ent_challenge *challenge = mdl_arritm(&world->ent_challenge,i);
+
+ const char *alias = mdl_pstr(&world->meta,challenge->pstr_alias);
+ vg_msg_wkvnum( sav, alias, k_vg_msg_u32, 1, &challenge->status );
+ }
+
+ if( mdl_arrcount(&world->ent_route) ){
+ vg_msg_frame( sav, "routes" );
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ vg_msg_frame( sav, mdl_pstr( &world->meta, route->pstr_name ) );
+ {
+ vg_msg_wkvnum( sav, "flags", k_vg_msg_u32, 1, &route->flags );
+ vg_msg_wkvnum( sav, "best_laptime",
+ k_vg_msg_f64, 1, &route->best_laptime );
+
+ f32 sections[ route->checkpoints_count ];
+
+ for( u32 j=0; j<route->checkpoints_count; j ++ ){
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
+ route->checkpoints_start + j );
+
+ sections[j] = cp->best_time;
+ }
+
+ vg_msg_wkvnum( sav, "sections", k_vg_msg_f32,
+ route->checkpoints_count, sections );
+ }
+ vg_msg_end_frame( sav );
+ }
+ vg_msg_end_frame( sav );
+ }
+}
--- /dev/null
+#pragma once
+#include "world.h"
+#include "entity.h"
+#include "vg/vg_bvh.h"
+#include "vg/vg_msg.h"
+
+typedef struct ent_focus_context ent_focus_context;
+struct ent_focus_context
+{
+ world_instance *world;
+ u32 index; /* Array index of the focused entity */
+ bool active;
+};
+
+void world_gen_entities_init( world_instance *world );
+ent_spawn *world_find_spawn_by_name( world_instance *world,
+ const char *name );
+ent_spawn *world_find_closest_spawn( world_instance *world,
+ v3f position );
+void world_default_spawn_pos( world_instance *world, v3f pos );
+void world_entity_start( world_instance *world, vg_msg *sav );
+void world_entity_serialize( world_instance *world, vg_msg *sav );
+
+entity_call_result ent_volume_call( world_instance *world, ent_call *call );
+entity_call_result ent_audio_call( world_instance *world, ent_call *call );
+entity_call_result ent_ccmd_call( world_instance *world, ent_call *call );
+
+void entity_bh_expand_bound( void *user, boxf bound, u32 item_index );
+float entity_bh_centroid( void *user, u32 item_index, int axis );
+void entity_bh_swap( void *user, u32 ia, u32 ib );
+void entity_bh_debug( void *user, u32 item_index );
+void entity_bh_closest( void *user, u32 item_index, v3f point,
+ v3f closest );
+
+void world_entity_set_focus( u32 entity_id );
+void world_entity_focus_modal(void);
+
+void world_entity_exit_modal(void);
+void world_entity_clear_focus(void);
+
+void world_entity_focus_preupdate(void);
+void world_entity_focus_render(void);
+void world_entity_focus_camera( world_instance *world, u32 uid );
+void update_ach_models(void);
+
+extern bh_system bh_system_entity_list;
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef WORLD_GATE_C
+#define WORLD_GATE_C
+
+#include "world.h"
+#include "world_gate.h"
+
+#include "skaterift.h"
+#include "common.h"
+#include "model.h"
+#include "entity.h"
+#include "render.h"
+
+#include "world_water.h"
+#include "player_remote.h"
+#include "shaders/model_gate_unlinked.h"
+#include <string.h>
+
+struct world_gates world_gates;
+
+/*
+ * Update the transform matrices for gate
+ */
+void gate_transform_update( ent_gate *gate )
+{
+ if( gate->flags & k_ent_gate_flip )
+ {
+ v4f qflip;
+ q_axis_angle( qflip, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
+ q_mul( gate->q[1], qflip, gate->q[1] );
+ q_normalize( gate->q[1] );
+ }
+
+ m4x3f to_local, recv_to_world;
+
+ q_m3x3( gate->q[0], gate->to_world );
+ v3_copy( gate->co[0], gate->to_world[3] );
+ m4x3_invert_affine( gate->to_world, to_local );
+
+ q_m3x3( gate->q[1], recv_to_world );
+ v3_copy( gate->co[1], recv_to_world[3] );
+
+ m4x3_mul( recv_to_world, to_local, gate->transport );
+}
+
+void world_gates_init(void)
+{
+ vg_info( "world_gates_init\n" );
+ vg_linear_clear( vg_mem.scratch );
+
+ mdl_context mgate;
+ mdl_open( &mgate, "models/rs_gate.mdl", vg_mem.scratch );
+ mdl_load_metadata_block( &mgate, vg_mem.scratch );
+
+ mdl_mesh *surface = mdl_find_mesh( &mgate, "rs_gate" );
+ mdl_submesh *sm = mdl_arritm(&mgate.submeshs,surface->submesh_start);
+ world_gates.sm_surface = *sm;
+
+ const char *names[] = { "rs_gate_marker", "rs_gate_marker.001",
+ "rs_gate_marker.002", "rs_gate_marker.003" };
+
+ for( int i=0; i<4; i++ ){
+ mdl_mesh *marker = mdl_find_mesh( &mgate, names[i] );
+ sm = mdl_arritm( &mgate.submeshs, marker->submesh_start );
+ world_gates.sm_marker[i] = *sm;
+ }
+
+ mdl_async_load_glmesh( &mgate, &world_gates.mesh, NULL );
+ mdl_close( &mgate );
+}
+
+void ent_gate_get_mdl_mtx( ent_gate *gate, m4x3f mmdl )
+{
+ m4x3_copy( gate->to_world, mmdl );
+
+ if( !(gate->flags & k_ent_gate_custom_mesh) ){
+ m3x3_scale( mmdl, (v3f){ gate->dimensions[0],
+ gate->dimensions[1], 1.0f } );
+ }
+}
+
+static void render_gate_mesh( world_instance *world, ent_gate *gate )
+{
+ if( gate->flags & k_ent_gate_custom_mesh ){
+ mesh_bind( &world->mesh_no_collide );
+ for( u32 i=0; i<gate->submesh_count; i++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ gate->submesh_start+i );
+ mdl_draw_submesh( sm );
+ }
+ }
+ else {
+ mesh_bind( &world_gates.mesh );
+ mdl_draw_submesh( &world_gates.sm_surface );
+ }
+}
+
+/*
+ * Render the view through a gate
+ */
+int render_gate( world_instance *world, world_instance *world_inside,
+ ent_gate *gate, vg_camera *cam )
+{
+ v3f viewdir, gatedir;
+ m3x3_mulv( cam->transform, (v3f){0.0f,0.0f,-1.0f}, viewdir );
+ q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, gatedir );
+
+ v3f v0;
+ v3_sub( cam->pos, gate->co[0], v0 );
+
+ float dist = v3_dot(v0, gatedir);
+
+ /* Hard cutoff */
+ if( dist > 3.0f )
+ return 0;
+
+ if( v3_dist( cam->pos, gate->co[0] ) > 100.0f )
+ return 0;
+
+ {
+ f32 w = gate->dimensions[0],
+ h = gate->dimensions[1];
+
+ v3f a,b,c,d;
+ m4x3_mulv( gate->to_world, (v3f){-w,-h,0.0f}, a );
+ m4x3_mulv( gate->to_world, (v3f){ w,-h,0.0f}, b );
+ m4x3_mulv( gate->to_world, (v3f){ w, h,0.0f}, c );
+ m4x3_mulv( gate->to_world, (v3f){-w, h,0.0f}, d );
+
+ vg_line( a,b, 0xffffa000 );
+ vg_line( b,c, 0xffffa000 );
+ vg_line( c,d, 0xffffa000 );
+ vg_line( d,a, 0xffffa000 );
+ vg_line( gate->co[0], gate->co[1], 0xff0000ff );
+ }
+
+ /* update gate camera */
+ world_gates.cam.fov = cam->fov;
+ world_gates.cam.nearz = 0.1f;
+ world_gates.cam.farz = 2000.0f;
+
+ m4x3_mul( gate->transport, cam->transform, world_gates.cam.transform );
+ vg_camera_update_view( &world_gates.cam );
+ vg_camera_update_projection( &world_gates.cam );
+
+ /* Add special clipping plane to projection */
+ v4f surface;
+ q_mulv( gate->q[1], (v3f){0.0f,0.0f,-1.0f}, surface );
+ surface[3] = v3_dot( surface, gate->co[1] );
+
+ m4x3_mulp( world_gates.cam.transform_inverse, surface, surface );
+ surface[3] = -fabsf(surface[3]);
+
+ if( dist < -0.5f )
+ m4x4_clip_projection( world_gates.cam.mtx.p, surface );
+
+ /* Ready to draw with new camrea */
+ vg_camera_finalize( &world_gates.cam );
+
+ vg_line_point( world_gates.cam.transform[3], 0.3f, 0xff00ff00 );
+
+ shader_model_gate_use();
+ shader_model_gate_uPv( cam->mtx.pv );
+ shader_model_gate_uCam( cam->pos );
+ shader_model_gate_uColour( (v4f){0.0f,1.0f,0.0f,0.0f} );
+ shader_model_gate_uTime( vg.time*0.25f );
+ shader_model_gate_uInvRes( (v2f){
+ 1.0f / (float)vg.window_x,
+ 1.0f / (float)vg.window_y });
+
+ glEnable( GL_STENCIL_TEST );
+ glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
+ glStencilFunc( GL_ALWAYS, 1, 0xFF );
+ glStencilMask( 0xFF );
+ glEnable( GL_CULL_FACE );
+
+ m4x3f mmdl;
+ ent_gate_get_mdl_mtx( gate, mmdl );
+ shader_model_gate_uMdl( mmdl );
+ render_gate_mesh( world, gate );
+
+ render_world( world_inside, &world_gates.cam,
+ 1, !localplayer.gate_waiting, 1, 1 );
+
+ return 1;
+}
+
+void render_gate_unlinked( world_instance *world,
+ ent_gate *gate, vg_camera *cam )
+{
+ m4x3f mmdl; m4x4f m4mdl;
+ ent_gate_get_mdl_mtx( gate, mmdl );
+ m4x3_expand( mmdl, m4mdl );
+ m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
+
+ shader_model_gate_unlinked_use();
+ shader_model_gate_unlinked_uPv( cam->mtx.pv );
+ shader_model_gate_unlinked_uPvmPrev( m4mdl );
+ shader_model_gate_unlinked_uCam( cam->pos );
+ shader_model_gate_unlinked_uColour( (v4f){0.0f,1.0f,0.0f,0.0f} );
+ shader_model_gate_unlinked_uTime( vg.time*0.25f );
+ shader_model_gate_unlinked_uMdl( mmdl );
+
+ vg_line_point( gate->co[0], 0.1f, 0xffffff00 );
+
+ render_gate_mesh( world, gate );
+}
+
+/*
+ * Intersect the plane of a gate with a line segment, plane coordinate result
+ * stored in 'where'
+ */
+static int gate_intersect_plane( ent_gate *gate,
+ v3f pos, v3f last, v2f where )
+{
+ v4f surface;
+ q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, surface );
+ surface[3] = v3_dot( surface, gate->co[0] );
+
+ v3f v0, c, delta, p0;
+ v3_sub( pos, last, v0 );
+ float l = v3_length( v0 );
+
+ if( l == 0.0f )
+ return 0;
+
+ v3_divs( v0, l, v0 );
+
+ v3_muls( surface, surface[3], c );
+ v3_sub( c, last, delta );
+
+ float d = v3_dot( surface, v0 );
+
+ if( d > 0.00001f ){
+ float t = v3_dot(delta, surface) / d;
+ if( t >= 0.0f && t <= l ){
+ v3f local, rel;
+ v3_muladds( last, v0, t, local );
+ v3_sub( gate->co[0], local, rel );
+
+ where[0] = v3_dot( rel, gate->to_world[0] );
+ where[1] = v3_dot( rel, gate->to_world[1] );
+
+ where[0] /= v3_dot( gate->to_world[0], gate->to_world[0] );
+ where[1] /= v3_dot( gate->to_world[1], gate->to_world[1] );
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Intersect specific gate
+ */
+int gate_intersect( ent_gate *gate, v3f pos, v3f last )
+{
+ v2f xy;
+
+ if( gate_intersect_plane( gate, pos, last, xy ) ){
+ if( (fabsf(xy[0]) <= gate->dimensions[0]) &&
+ (fabsf(xy[1]) <= gate->dimensions[1]) ){
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Intersect all gates in the world
+ */
+u32 world_intersect_gates( world_instance *world, v3f pos, v3f last )
+{
+ for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+
+ if( !(gate->flags & k_ent_gate_linked) ) continue;
+ if( gate->flags & k_ent_gate_locked ) continue;
+
+ if( gate->flags & k_ent_gate_nonlocal ){
+ if( world_static.instances[gate->target].status
+ != k_world_status_loaded )
+ continue;
+ }
+
+ if( gate_intersect( gate, pos, last ) )
+ return mdl_entity_id( k_ent_gate, i );
+ }
+
+ return 0;
+}
+
+entity_call_result ent_gate_call( world_instance *world, ent_call *call )
+{
+ u32 index = mdl_entity_id_id( call->id );
+ ent_gate *gate = mdl_arritm( &world->ent_gate, index );
+
+ if( call->function == 0 ) /* unlock() */
+ {
+ gate->flags &= ~k_ent_gate_locked;
+ return k_entity_call_result_OK;
+ }
+ else
+ {
+ return k_entity_call_result_unhandled;
+ }
+}
+
+
+/*
+ * detatches any nonlocal gates
+ */
+void world_unlink_nonlocal( world_instance *world )
+{
+ for( u32 j=0; j<mdl_arrcount(&world->ent_gate); j ++ )
+ {
+ ent_gate *gate = mdl_arritm( &world->ent_gate, j );
+
+ if( gate->flags & k_ent_gate_nonlocal )
+ {
+ gate->flags &= ~k_ent_gate_linked;
+ }
+ }
+}
+
+/*
+ * This has to be synchronous because main thread looks at gate data for
+ * rendering, and we modify gates that the main thread has ownership of.
+ */
+void world_link_gates_async( void *payload, u32 size )
+{
+ VG_ASSERT( vg_thread_purpose() == k_thread_purpose_main );
+
+ world_instance *world = payload;
+ u32 world_id = world - world_static.instances;
+
+ for( u32 j=0; j<mdl_arrcount(&world->ent_gate); j ++ )
+ {
+ ent_gate *gate = mdl_arritm( &world->ent_gate, j );
+ gate_transform_update( gate );
+
+ if( skaterift.demo_mode )
+ if( world_static.instance_addons[world_id]->flags & ADDON_REG_PREMIUM )
+ continue;
+
+ if( !(gate->flags & k_ent_gate_nonlocal) ) continue;
+ if( gate->flags & k_ent_gate_linked ) continue;
+
+ const char *key = mdl_pstr( &world->meta, gate->key );
+ vg_info( "key: %s\n", key );
+
+ for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ ){
+ world_instance *other = &world_static.instances[i];
+ if( other == world ) continue;
+ if( other->status != k_world_status_loaded ) continue;
+ vg_info( "Checking world %u for key matches\n", i );
+
+ for( u32 k=0; k<mdl_arrcount( &other->ent_gate ); k++ ){
+ ent_gate *gate2 = mdl_arritm( &other->ent_gate, k );
+
+ if( !(gate2->flags & k_ent_gate_nonlocal) ) continue;
+ if( gate2->flags & k_ent_gate_linked ) continue;
+
+ const char *key2 = mdl_pstr( &other->meta, gate2->key );
+ vg_info( " key2: %s\n", key2 );
+
+ if( strcmp( key, key2 ) ) continue;
+
+ vg_success( "Non-local matching pair '%s' found. (%u:%u)\n",
+ key, world_id, i );
+
+ gate->flags |= k_ent_gate_linked;
+ gate2->flags |= k_ent_gate_linked;
+ gate->target = i;
+ gate2->target = world_id;
+
+ v3_copy( gate->co[0], gate2->co[1] );
+ v3_copy( gate2->co[0], gate->co[1] );
+ v4_copy( gate->q[0], gate2->q[1] );
+ v4_copy( gate2->q[0], gate->q[1] );
+
+ if( world->meta.info.version < 102 ){
+ /* LEGACY BEHAVIOUR: v101
+ * this would flip both the client worlds portal's entrance and
+ * exit. effectively the clients portal would be the opposite
+ * to the hub worlds one. new behaviour is to just flip the
+ * destinations so the rules are consistent in each world.
+ */
+ v4f qflip;
+ q_axis_angle( qflip, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
+ q_mul( gate->q[0], qflip, gate->q[0] );
+ q_mul( gate->q[1], qflip, gate->q[1] );
+ q_mul( gate2->q[1], qflip, gate2->q[1] );
+ }
+
+ gate_transform_update( gate );
+ gate_transform_update( gate2 );
+
+ goto matched;
+ }
+ } matched:;
+ }
+}
+
+#endif /* WORLD_GATE_C */
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+
+#include "vg/vg_camera.h"
+#include "world.h"
+#include "shaders/model_gate.h"
+#include "entity.h"
+
+struct world_gates
+{
+ glmesh mesh;
+ mdl_submesh sm_surface, sm_marker[4];
+ vg_camera cam;
+
+ v3f userportal_co;
+}
+extern world_gates;
+
+void world_gates_init(void);
+void gate_transform_update( ent_gate *gate );
+int render_gate( world_instance *world, world_instance *world_inside,
+ ent_gate *gate, vg_camera *cam );
+
+int gate_intersect( ent_gate *gate, v3f pos, v3f last );
+u32 world_intersect_gates( world_instance *world, v3f pos, v3f last );
+
+entity_call_result ent_gate_call( world_instance *world, ent_call *call );
+void ent_gate_get_mdl_mtx( ent_gate *gate, m4x3f mmdl );
+
+void world_link_gates_async( void *payload, u32 size );
+void world_unlink_nonlocal( world_instance *world );
+void render_gate_unlinked( world_instance *world,
+ ent_gate *gate, vg_camera *cam );
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ *
+ * World generation/population. Different to regular loading, since it needs to
+ * create geometry, apply procedural stuff and save that image to files etc.
+ */
+#include "world.h"
+#include "world_gen.h"
+#include "world_load.h"
+#include "world_volumes.h"
+#include "world_gate.h"
+#include <string.h>
+
+/*
+ * Add all triangles from the model, which match the material ID
+ * applies affine transform to the model
+ */
+static void world_add_all_if_material( m4x3f transform, scene_context *scene,
+ mdl_context *mdl, u32 id )
+{
+ for( u32 i=0; i<mdl_arrcount(&mdl->meshs); i++ ){
+ mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
+
+ for( u32 j=0; j<mesh->submesh_count; j++ ){
+ mdl_submesh *sm = mdl_arritm( &mdl->submeshs, mesh->submesh_start+j );
+ if( sm->material_id == id ){
+ m4x3f transform2;
+ mdl_transform_m4x3( &mesh->transform, transform2 );
+ m4x3_mul( transform, transform2, transform2 );
+
+ scene_add_mdl_submesh( scene, mdl, sm, transform2 );
+ }
+ }
+ }
+}
+
+/*
+ * Adds a small blob shape to the world at a raycast location. This is for the
+ * grass sprites
+ *
+ * /''''\
+ * / \
+ * | |
+ * |________|
+ */
+static void world_gen_add_blob( vg_rand *rand, world_instance *world,
+ scene_context *scene, ray_hit *hit )
+{
+ m4x3f transform;
+ v4f qsurface, qrandom;
+ v3f axis;
+
+ v3_cross( (v3f){0.0f,1.0f,0.0f}, hit->normal, axis );
+
+ float angle = v3_dot(hit->normal,(v3f){0.0f,1.0f,0.0f});
+ q_axis_angle( qsurface, axis, angle );
+ q_axis_angle( qrandom, (v3f){0.0f,1.0f,0.0f}, vg_randf64(rand)*VG_TAUf );
+ q_mul( qsurface, qrandom, qsurface );
+ q_m3x3( qsurface, transform );
+ v3_copy( hit->pos, transform[3] );
+
+ scene_vert verts[] =
+ {
+ { .co = { -1.00f, 0.0f, 0.0f } },
+ { .co = { 1.00f, 0.0f, 0.0f } },
+ { .co = { -1.00f, 1.2f, 0.0f } },
+ { .co = { 1.00f, 1.2f, 0.0f } },
+ { .co = { -0.25f, 2.0f, 0.0f } },
+ { .co = { 0.25f, 2.0f, 0.0f } }
+ };
+
+ const u32 indices[] = { 0,1,3, 0,3,2, 2,3,5, 2,5,4 };
+
+ if( scene->vertex_count + VG_ARRAY_LEN(verts) > scene->max_vertices )
+ vg_fatal_error( "Scene vertex buffer overflow" );
+
+ if( scene->indice_count + VG_ARRAY_LEN(indices) > scene->max_indices )
+ vg_fatal_error( "Scene index buffer overflow" );
+
+ scene_vert *dst_verts = &scene->arrvertices[ scene->vertex_count ];
+ u32 *dst_indices = &scene->arrindices [ scene->indice_count ];
+
+ scene_vert *ref = &world->scene_geo.arrvertices[ hit->tri[0] ];
+
+ for( u32 i=0; i<VG_ARRAY_LEN(verts); i++ ){
+ scene_vert *pvert = &dst_verts[ i ],
+ *src = &verts[ i ];
+
+ m4x3_mulv( transform, src->co, pvert->co );
+ scene_vert_pack_norm( pvert, transform[1], 0.0f );
+
+ v2_copy( ref->uv, pvert->uv );
+ }
+
+ for( u32 i=0; i<VG_ARRAY_LEN(indices); i++ )
+ dst_indices[i] = indices[i] + scene->vertex_count;
+
+ scene->vertex_count += VG_ARRAY_LEN(verts);
+ scene->indice_count += VG_ARRAY_LEN(indices);
+}
+
+/*
+ * Sprinkle foliage models over the map on terrain material
+ */
+static void world_apply_procedural_foliage( world_instance *world,
+ scene_context *scene,
+ struct world_surface *mat )
+{
+ if( (vg.quality_profile == k_quality_profile_low) ||
+ (vg.quality_profile == k_quality_profile_min) )
+ return;
+
+ vg_info( "Applying foliage (%u)\n", mat->info.pstr_name );
+
+ v3f volume;
+ v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], volume );
+ volume[1] = 1.0f;
+
+ int count = 0;
+
+ float area = volume[0]*volume[2];
+ u32 particles = 0.08f * area;
+
+ vg_info( "Map area: %f. Max particles: %u\n", area, particles );
+
+ u64 t0 = SDL_GetPerformanceCounter();
+#if 0
+ for( u32 i=0; i<particles; i++ ){
+ v3f pos;
+ v3_mul( volume, (v3f){ vg_randf64(), 1000.0f, vg_randf64() }, pos );
+ pos[1] = 1000.0f;
+ v3_add( pos, world->scene_geo.bbx[0], pos );
+
+ ray_hit hit;
+ hit.dist = INFINITY;
+
+ if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &hit,
+ k_material_flag_ghosts )){
+ struct world_surface *m1 = ray_hit_surface( world, &hit );
+ if((hit.normal[1] > 0.8f) && (m1 == mat) && (hit.pos[1] > 0.0f+10.0f)){
+ world_gen_add_blob( world, scene, &hit );
+ count ++;
+ }
+ }
+ }
+#else
+
+ vg_rand rand;
+ vg_rand_seed( &rand, 3030 );
+
+ const f32 tile_scale = 16.0f;
+ v2i tiles = { volume[0]/tile_scale, volume[2]/tile_scale };
+
+ u32 per_tile = particles/(tiles[0]*tiles[1]);
+
+ for( i32 x=0; x<tiles[0]; x ++ ){
+ for( i32 z=0; z<tiles[1]; z ++ ){
+ for( u32 i=0; i<per_tile; i ++ ){
+ v3f co = { (f32)x+vg_randf64(&rand), 0, (f32)z+vg_randf64(&rand) };
+ v3_muls( co, tile_scale, co );
+ co[1] = 1000.0f;
+ v3_add( co, world->scene_geo.bbx[0], co );
+
+ ray_hit hit;
+ hit.dist = INFINITY;
+
+ if( ray_world( world, co, (v3f){0.0f,-1.0f,0.0f}, &hit,
+ k_material_flag_ghosts )){
+ struct world_surface *m1 = ray_hit_surface( world, &hit );
+ if((hit.normal[1] > 0.8f) && (m1 == mat) &&
+ (hit.pos[1] > 0.0f+10.0f)){
+ world_gen_add_blob( &rand, world, scene, &hit );
+ count ++;
+ }
+ }
+
+ }
+ }
+ }
+
+#endif
+
+
+
+ u64 t1 = SDL_GetPerformanceCounter(),
+ utime_blobs = t1-t0,
+ ufreq = SDL_GetPerformanceFrequency();
+ f64 ftime_blobs = ((f64)utime_blobs / (f64)ufreq)*1000.0;
+
+ vg_info( "%d foliage models added. %f%% (%fms)\n", count,
+ 100.0*((f64)count/(f64)particles), ftime_blobs);
+}
+
+static
+void world_unpack_submesh_dynamic( world_instance *world,
+ scene_context *scene, mdl_submesh *sm ){
+ if( sm->flags & k_submesh_flag_consumed ) return;
+
+ m4x3f identity;
+ m4x3_identity( identity );
+ scene_add_mdl_submesh( scene, &world->meta, sm, identity );
+
+ scene_copy_slice( scene, sm );
+ sm->flags |= k_submesh_flag_consumed;
+}
+
+/*
+ * Create the main meshes for the world
+ */
+void world_gen_generate_meshes( world_instance *world )
+{
+ /*
+ * Compile meshes into the world scenes
+ */
+ scene_init( &world->scene_geo, 320000, 1200000 );
+ u32 buf_size = scene_mem_required( &world->scene_geo );
+ u8 *buffer = vg_linear_alloc( world->heap, buf_size );
+ scene_supply_buffer( &world->scene_geo, buffer );
+
+ m4x3f midentity;
+ m4x3_identity( midentity );
+
+ /*
+ * Generate scene: collidable geometry
+ * ----------------------------------------------------------------
+ */
+
+ vg_info( "Generating collidable geometry\n" );
+
+ for( u32 i=0; i<world->surface_count; i++ ){
+ struct world_surface *surf = &world->surfaces[ i ];
+
+ if( surf->info.flags & k_material_flag_collision )
+ world_add_all_if_material( midentity, &world->scene_geo,
+ &world->meta, i );
+
+ scene_copy_slice( &world->scene_geo, &surf->sm_geo );
+ scene_set_vertex_flags( &world->scene_geo,
+ surf->sm_geo.vertex_start,
+ surf->sm_geo.vertex_count,
+ (u16)(surf->info.flags & 0xffff) );
+ }
+
+ /* compress that bad boy */
+ u32 new_vert_max = world->scene_geo.vertex_count,
+ new_vert_size = vg_align8(new_vert_max*sizeof(scene_vert)),
+ new_indice_len = world->scene_geo.indice_count*sizeof(u32);
+
+ u32 *src_indices = world->scene_geo.arrindices,
+ *dst_indices = (u32 *)(buffer + new_vert_size);
+
+ memmove( dst_indices, src_indices, new_indice_len );
+
+ world->scene_geo.max_indices = world->scene_geo.indice_count;
+ world->scene_geo.max_vertices = world->scene_geo.vertex_count;
+ buf_size = scene_mem_required( &world->scene_geo );
+
+ buffer = vg_linear_resize( world->heap, buffer, buf_size );
+
+ world->scene_geo.arrvertices = (scene_vert *)(buffer);
+ world->scene_geo.arrindices = (u32 *)(buffer + new_vert_size);
+
+ scene_upload_async( &world->scene_geo, &world->mesh_geo );
+
+ /* need send off the memory to the gpu before we can create the bvh. */
+ vg_async_stall();
+ vg_info( "creating bvh\n" );
+ world->geo_bh = scene_bh_create( world->heap, &world->scene_geo );
+
+ /*
+ * Generate scene: non-collidable geometry
+ * ----------------------------------------------------------------
+ */
+ vg_info( "Generating non-collidable geometry\n" );
+
+ vg_async_item *call = scene_alloc_async( &world->scene_no_collide,
+ &world->mesh_no_collide,
+ 250000, 500000 );
+
+ for( u32 i=0; i<world->surface_count; i++ ){
+ struct world_surface *surf = &world->surfaces[ i ];
+
+ if( !(surf->info.flags & k_material_flag_collision) ){
+ world_add_all_if_material( midentity,
+ &world->scene_no_collide, &world->meta, i );
+ }
+
+ if( surf->info.flags & k_material_flag_grow_grass ){
+ world_apply_procedural_foliage( world, &world->scene_no_collide,
+ surf );
+ }
+
+ scene_copy_slice( &world->scene_no_collide, &surf->sm_no_collide );
+ }
+
+ /* unpack traffic models.. TODO: should we just put all these submeshes in a
+ * dynamic models list? and then the actual entitities point to the
+ * models. we only have 2 types at the moment which need dynamic models but
+ * would make sense to do this when/if we have more.
+ */
+ for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
+ ent_traffic *vehc = mdl_arritm( &world->ent_traffic, i );
+
+ for( u32 j=0; j<vehc->submesh_count; j++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ vehc->submesh_start+j );
+ world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
+ world->surfaces[ sm->material_id ].flags |= WORLD_SURFACE_HAS_TRAFFIC;
+ }
+ }
+
+ /* unpack challenge models */
+ for( u32 i=0; i<mdl_arrcount( &world->ent_objective ); i++ ){
+ ent_objective *objective = mdl_arritm( &world->ent_objective, i );
+
+ for( u32 j=0; j<objective->submesh_count; j ++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ objective->submesh_start+j );
+ world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
+ }
+ }
+
+ /* unpack region models */
+ for( u32 i=0; i<mdl_arrcount( &world->ent_region ); i++ ){
+ ent_region *region = mdl_arritm( &world->ent_region, i );
+
+ for( u32 j=0; j<region->submesh_count; j ++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ region->submesh_start+j );
+ world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
+ }
+ }
+
+ /* unpack gate models */
+ for( u32 i=0; i<mdl_arrcount( &world->ent_gate ); i++ ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+
+ if( !(gate->flags & k_ent_gate_custom_mesh) ) continue;
+
+ for( u32 j=0; j<gate->submesh_count; j ++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ gate->submesh_start+j );
+ world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
+ }
+ }
+
+ /* unpack prop models */
+ for( u32 i=0; i<mdl_arrcount( &world->ent_prop ); i++ ){
+ ent_prop *prop = mdl_arritm( &world->ent_prop, i );
+
+ for( u32 j=0; j<prop->submesh_count; j ++ ){
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ prop->submesh_start+j );
+ world->surfaces[ sm->material_id ].flags |= WORLD_SURFACE_HAS_PROPS;
+ world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
+ }
+ }
+
+ vg_async_dispatch( call, async_scene_upload );
+}
+
+/* signed distance function for cone */
+static f32 fsd_cone_infinite( v3f p, v2f c ){
+ v2f q = { v2_length( (v2f){ p[0], p[2] } ), -p[1] };
+ float s = vg_maxf( 0.0f, v2_dot( q, c ) );
+
+ v2f v0;
+ v2_muls( c, s, v0 );
+ v2_sub( q, v0, v0 );
+
+ float d = v2_length( v0 );
+ return d * ((q[0]*c[1]-q[1]*c[0]<0.0f)?-1.0f:1.0f);
+}
+
+struct light_indices_upload_info{
+ world_instance *world;
+ v3i count;
+
+ void *data;
+};
+
+/*
+ * Async reciever to buffer light index data
+ */
+static void async_upload_light_indices( void *payload, u32 size ){
+ struct light_indices_upload_info *info = payload;
+
+ glGenTextures( 1, &info->world->tex_light_cubes );
+ glBindTexture( GL_TEXTURE_3D, info->world->tex_light_cubes );
+ glTexImage3D( GL_TEXTURE_3D, 0, GL_RG32UI,
+ info->count[0], info->count[1], info->count[2],
+ 0, GL_RG_INTEGER, GL_UNSIGNED_INT, info->data );
+ glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+}
+
+/*
+ * Computes light indices for world
+ */
+void world_gen_compute_light_indices( world_instance *world )
+{
+ /* light cubes */
+ v3f cubes_min, cubes_max;
+ v3_muls( world->scene_geo.bbx[0], 1.0f/k_world_light_cube_size, cubes_min );
+ v3_muls( world->scene_geo.bbx[1], 1.0f/k_world_light_cube_size, cubes_max );
+
+ v3_sub( cubes_min, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_min );
+ v3_add( cubes_max, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_max );
+
+ v3_floor( cubes_min, cubes_min );
+ v3_floor( cubes_max, cubes_max );
+
+ v3i icubes_min, icubes_max;
+
+ for( int i=0; i<3; i++ ){
+ icubes_min[i] = cubes_min[i];
+ icubes_max[i] = cubes_max[i];
+ }
+
+ v3f cube_size;
+
+ v3i icubes_count;
+ v3i_sub( icubes_max, icubes_min, icubes_count );
+
+ for( int i=0; i<3; i++ ){
+ int clamped_count = VG_MIN( 128, icubes_count[i]+1 );
+ float clamped_max = icubes_min[i] + clamped_count,
+ max = icubes_min[i] + icubes_count[i]+1;
+
+ icubes_count[i] = clamped_count;
+ cube_size[i] = (max / clamped_max) * k_world_light_cube_size;
+ cubes_max[i] = clamped_max;
+ }
+
+ v3_mul( cubes_min, cube_size, cubes_min );
+ v3_mul( cubes_max, cube_size, cubes_max );
+
+ for( int i=0; i<3; i++ ){
+ float range = cubes_max[i]-cubes_min[i];
+ world->ub_lighting.g_cube_inv_range[i] = 1.0f / range;
+ world->ub_lighting.g_cube_inv_range[i] *= (float)icubes_count[i];
+
+ vg_info( "cubes[%d]: %d\n", i, icubes_count[i] );
+ }
+
+ int total_cubes = icubes_count[0]*icubes_count[1]*icubes_count[2];
+
+ u32 data_size = vg_align8(total_cubes*sizeof(u32)*2),
+ hdr_size = vg_align8(sizeof(struct light_indices_upload_info));
+
+ vg_async_item *call = vg_async_alloc( data_size + hdr_size );
+ struct light_indices_upload_info *info = call->payload;
+ info->data = ((u8*)call->payload) + hdr_size;
+ info->world = world;
+ u32 *cubes_index = info->data;
+
+ for( int i=0; i<3; i++ )
+ info->count[i] = icubes_count[i];
+
+ vg_info( "Computing light cubes (%d) [%f %f %f] -> [%f %f %f]\n",
+ total_cubes, cubes_min[0], -cubes_min[2], cubes_min[1],
+ cubes_max[0], -cubes_max[2], cubes_max[1] );
+ v3_copy( cubes_min, world->ub_lighting.g_cube_min );
+
+ float bound_radius = v3_length( cube_size );
+
+ for( int iz = 0; iz<icubes_count[2]; iz ++ ){
+ for( int iy = 0; iy<icubes_count[1]; iy++ ){
+ for( int ix = 0; ix<icubes_count[0]; ix++ ){
+ boxf bbx;
+ v3_div( (v3f){ ix, iy, iz }, world->ub_lighting.g_cube_inv_range,
+ bbx[0] );
+ v3_div( (v3f){ ix+1, iy+1, iz+1 },
+ world->ub_lighting.g_cube_inv_range,
+ bbx[1] );
+
+ v3_add( bbx[0], world->ub_lighting.g_cube_min, bbx[0] );
+ v3_add( bbx[1], world->ub_lighting.g_cube_min, bbx[1] );
+
+ v3f center;
+ v3_add( bbx[0], bbx[1], center );
+ v3_muls( center, 0.5f, center );
+
+ u32 indices[6] = { 0, 0, 0, 0, 0, 0 };
+ u32 count = 0;
+
+ float influences[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+ const int N = VG_ARRAY_LEN( influences );
+
+ for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
+ ent_light *light = mdl_arritm( &world->ent_light, j );
+ v3f closest;
+ closest_point_aabb( light->transform.co, bbx, closest );
+
+ f32 dist2 = v3_dist2( closest, light->transform.co );
+
+ if( dist2 > light->range*light->range )
+ continue;
+
+ f32 dist = sqrtf(dist2),
+ influence = 1.0f/(dist+1.0f);
+
+ if( light->type == k_light_type_spot){
+ v3f local;
+ m4x3_mulv( light->inverse_world, center, local );
+
+ float r = fsd_cone_infinite( local, light->angle_sin_cos );
+
+ if( r > bound_radius )
+ continue;
+ }
+
+ int best_pos = N;
+ for( int k=best_pos-1; k>=0; k -- )
+ if( influence > influences[k] )
+ best_pos = k;
+
+ if( best_pos < N ){
+ for( int k=N-1; k>best_pos; k -- ){
+ influences[k] = influences[k-1];
+ indices[k] = indices[k-1];
+ }
+
+ influences[best_pos] = influence;
+ indices[best_pos] = j;
+ }
+ }
+
+ for( int j=0; j<N; j++ )
+ if( influences[j] > 0.0f )
+ count ++;
+
+ int base_index = iz * (icubes_count[0]*icubes_count[1]) +
+ iy * (icubes_count[0]) +
+ ix;
+
+ int lower_count = VG_MIN( 3, count );
+ u32 packed_index_lower = lower_count;
+ packed_index_lower |= indices[0]<<2;
+ packed_index_lower |= indices[1]<<12;
+ packed_index_lower |= indices[2]<<22;
+
+ int upper_count = VG_MAX( 0, count - lower_count );
+ u32 packed_index_upper = upper_count;
+ packed_index_upper |= indices[3]<<2;
+ packed_index_upper |= indices[4]<<12;
+ packed_index_upper |= indices[5]<<22;
+
+ cubes_index[ base_index*2 + 0 ] = packed_index_lower;
+ cubes_index[ base_index*2 + 1 ] = packed_index_upper;
+ }
+ }
+ }
+
+ vg_async_dispatch( call, async_upload_light_indices );
+}
+
+/*
+ * Rendering pass needed to complete the world
+ */
+void async_world_postprocess( void *payload, u32 _size )
+{
+ /* create scene lighting buffer */
+ world_instance *world = payload;
+
+ u32 size = VG_MAX(mdl_arrcount(&world->ent_light),1) * sizeof(float)*12;
+ vg_info( "Upload %ubytes (lighting)\n", size );
+
+ glGenBuffers( 1, &world->tbo_light_entities );
+ glBindBuffer( GL_TEXTURE_BUFFER, world->tbo_light_entities );
+ glBufferData( GL_TEXTURE_BUFFER, size, NULL, GL_DYNAMIC_DRAW );
+
+ /* buffer layout
+ *
+ * colour position direction (spots)
+ * | . . . . | . . . . | . . . . |
+ * | Re Ge Be Night | Xco Yco Zco Range | Dx Dy Dz Da |
+ *
+ */
+
+ v4f *light_dst = glMapBuffer( GL_TEXTURE_BUFFER, GL_WRITE_ONLY );
+ for( u32 i=0; i<mdl_arrcount(&world->ent_light); i++ ){
+ ent_light *light = mdl_arritm( &world->ent_light, i );
+
+ /* colour + night */
+ v3_muls( light->colour, light->colour[3] * 2.0f, light_dst[i*3+0] );
+ light_dst[i*3+0][3] = 2.0f;
+
+ if( !light->daytime ){
+ u32 hash = (i * 29986577u) & 0xffu;
+ float switch_on = hash;
+ switch_on *= (1.0f/255.0f);
+
+ light_dst[i*3+0][3] = 0.44f + switch_on * 0.015f;
+ }
+
+ /* position + 1/range^2 */
+ v3_copy( light->transform.co, light_dst[i*3+1] );
+ light_dst[i*3+1][3] = 1.0f/(light->range*light->range);
+
+ /* direction + angle */
+ q_mulv( light->transform.q, (v3f){0.0f,-1.0f,0.0f}, light_dst[i*3+2]);
+ light_dst[i*3+2][3] = cosf( light->angle );
+ }
+
+ glUnmapBuffer( GL_TEXTURE_BUFFER );
+
+ glGenTextures( 1, &world->tex_light_entities );
+ glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
+ glTexBuffer( GL_TEXTURE_BUFFER, GL_RGBA32F, world->tbo_light_entities );
+
+ /* Upload lighting uniform buffer */
+ if( world->water.enabled )
+ v4_copy( world->water.plane, world->ub_lighting.g_water_plane );
+
+ v4f info_vec;
+ v3f *bounds = world->scene_geo.bbx;
+
+ info_vec[0] = bounds[0][0];
+ info_vec[1] = bounds[0][2];
+ info_vec[2] = 1.0f/ (bounds[1][0]-bounds[0][0]);
+ info_vec[3] = 1.0f/ (bounds[1][2]-bounds[0][2]);
+ v4_copy( info_vec, world->ub_lighting.g_depth_bounds );
+
+ /*
+ * Rendering the depth map
+ */
+ vg_camera ortho;
+
+ v3f extent;
+ v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], extent );
+
+ float fl = world->scene_geo.bbx[0][0],
+ fr = world->scene_geo.bbx[1][0],
+ fb = world->scene_geo.bbx[0][2],
+ ft = world->scene_geo.bbx[1][2],
+ rl = 1.0f / (fr-fl),
+ tb = 1.0f / (ft-fb);
+
+ m4x4_zero( ortho.mtx.p );
+ ortho.mtx.p[0][0] = 2.0f * rl;
+ ortho.mtx.p[2][1] = 2.0f * tb;
+ ortho.mtx.p[3][0] = (fr + fl) * -rl;
+ ortho.mtx.p[3][1] = (ft + fb) * -tb;
+ ortho.mtx.p[3][3] = 1.0f;
+ m4x3_identity( ortho.transform );
+ vg_camera_update_view( &ortho );
+ vg_camera_finalize( &ortho );
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+ glDisable(GL_CULL_FACE);
+ vg_framebuffer_bind( world->heightmap, 1.0f );
+ shader_blitcolour_use();
+ shader_blitcolour_uColour( (v4f){-9999.0f,-9999.0f,-9999.0f,-9999.0f} );
+ render_fsquad();
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE);
+ glBlendEquation(GL_MAX);
+
+ render_world_position( world, &ortho );
+ glDisable(GL_BLEND);
+ glEnable(GL_DEPTH_TEST);
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+
+ /* upload full buffer */
+ glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+ glBufferSubData( GL_UNIFORM_BUFFER, 0,
+ sizeof(struct ub_world_lighting), &world->ub_lighting );
+
+ /*
+ * Allocate cubemaps
+ */
+ for( u32 i=0; i<mdl_arrcount(&world->ent_cubemap); i++ ){
+ ent_cubemap *cm = mdl_arritm(&world->ent_cubemap,i);
+
+ glGenTextures( 1, &cm->texture_id );
+ glBindTexture( GL_TEXTURE_CUBE_MAP, cm->texture_id );
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+ for( u32 j=0; j<6; j ++ ) {
+ glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, GL_RGB,
+ WORLD_CUBEMAP_RES, WORLD_CUBEMAP_RES,
+ 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
+ }
+
+ glGenFramebuffers( 1, &cm->framebuffer_id );
+ glBindFramebuffer( GL_FRAMEBUFFER, cm->framebuffer_id );
+ glGenRenderbuffers(1, &cm->renderbuffer_id );
+ glBindRenderbuffer( GL_RENDERBUFFER, cm->renderbuffer_id );
+ glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
+ WORLD_CUBEMAP_RES, WORLD_CUBEMAP_RES );
+
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X, cm->texture_id, 0 );
+ glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, cm->renderbuffer_id );
+
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X, cm->texture_id, 0 );
+
+ if( glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE ){
+ vg_error( "Cubemap framebuffer incomplete.\n" );
+ }
+ }
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+}
+
+/* Loads textures from the pack file */
+void world_gen_load_surfaces( world_instance *world )
+{
+ vg_info( "Loading textures\n" );
+ world->texture_count = 0;
+
+ world->texture_count = world->meta.textures.count+1;
+ world->textures = vg_linear_alloc( world->heap,
+ vg_align8(sizeof(GLuint)*world->texture_count) );
+ world->textures[0] = vg.tex_missing;
+
+ for( u32 i=0; i<mdl_arrcount(&world->meta.textures); i++ )
+ {
+ mdl_texture *tex = mdl_arritm( &world->meta.textures, i );
+
+ if( !tex->file.pack_size )
+ {
+ vg_fatal_error( "World models must have packed textures!" );
+ }
+
+ vg_linear_clear( vg_mem.scratch );
+ void *src_data = vg_linear_alloc( vg_mem.scratch,
+ tex->file.pack_size );
+ mdl_fread_pack_file( &world->meta, &tex->file, src_data );
+
+ vg_tex2d_load_qoi_async( src_data, tex->file.pack_size,
+ VG_TEX2D_NEAREST|VG_TEX2D_REPEAT,
+ &world->textures[i+1] );
+ }
+
+ vg_info( "Loading materials\n" );
+
+ world->surface_count = world->meta.materials.count+1;
+ world->surfaces = vg_linear_alloc( world->heap,
+ vg_align8(sizeof(struct world_surface)*world->surface_count) );
+
+ /* error material */
+ struct world_surface *errmat = &world->surfaces[0];
+ memset( errmat, 0, sizeof(struct world_surface) );
+
+ for( u32 i=0; i<mdl_arrcount(&world->meta.materials); i++ )
+ {
+ struct world_surface *surf = &world->surfaces[i+1];
+ surf->info = *(mdl_material *)mdl_arritm( &world->meta.materials, i );
+ surf->flags = 0;
+
+ if( surf->info.shader == k_shader_water )
+ {
+ struct shader_props_water *props = surf->info.props.compiled;
+ world->ub_lighting.g_water_fog = props->fog_scale;
+ }
+
+ if( surf->info.shader == k_shader_standard_cutout ||
+ surf->info.shader == k_shader_foliage )
+ {
+ struct shader_props_standard *props = surf->info.props.compiled;
+ surf->alpha_tex = props->tex_diffuse;
+ }
+ else
+ surf->alpha_tex = 0;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ *
+ * World generation/population. Different to regular loading, since it needs to
+ * create geometry, apply procedural stuff and save that image to files etc.
+ */
+
+#pragma once
+#include "world.h"
+
+void world_init_blank( world_instance *world );
+void world_gen_load_surfaces( world_instance *world );
+void world_gen_generate_meshes( world_instance *world );
+void world_gen_compute_light_indices( world_instance *world );
+void async_world_postprocess( void *payload, u32 _size );
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#ifndef WORLD_INFO_H
+#define WORLD_INFO_H
+
+/* Purely an information header, shares common strings across client and
+ * server programs. */
+
+struct track_info
+{
+ const char *name,
+ *achievement_id;
+}
+static track_infos[] =
+{
+ {
+ .name = "Megapark Green",
+ .achievement_id = "ROUTE_MPG",
+ },
+ {
+ .name = "Megapark Blue",
+ .achievement_id = "ROUTE_MPB",
+ },
+ {
+ .name = "Megapark Yellow",
+ .achievement_id = "ROUTE_MPY",
+ },
+ {
+ .name = "Megapark Red",
+ .achievement_id = "ROUTE_MPR",
+ },
+ {
+ .name = "Coastal Run",
+ .achievement_id = "ROUTE_TC",
+ },
+ {
+ .name = "Docks Jumps",
+ .achievement_id = "ROUTE_TO",
+ }
+};
+
+#endif
--- /dev/null
+#include "world_load.h"
+#include "world_routes.h"
+#include "world_gate.h"
+#include "ent_skateshop.h"
+#include "addon.h"
+#include "save.h"
+#include "vg/vg_msg.h"
+#include "network.h"
+#include "player_remote.h"
+#include "vg/vg_loader.h"
+#include "vg/vg_io.h"
+#include <string.h>
+
+/*
+ * load the .mdl file located in path as a world instance
+ */
+static void world_instance_load_mdl( u32 instance_id, const char *path ){
+ world_instance *world = &world_static.instances[ instance_id ];
+ world_init_blank( world );
+ world->status = k_world_status_loading;
+
+ vg_info( "Loading instance[%u]: %s\n", instance_id, path );
+
+ void *allocator = NULL;
+ if( instance_id == 0 ) allocator = world_static.heap;
+ else allocator = world_static.instances[instance_id-1].heap;
+
+ u32 heap_availible = vg_linear_remaining( allocator );
+ u32 min_overhead = sizeof(vg_linear_allocator);
+
+ if( heap_availible < (min_overhead+1024) ){
+ vg_fatal_error( "out of memory" );
+ }
+
+ u32 size = heap_availible - min_overhead;
+ void *heap = vg_create_linear_allocator( allocator, size, VG_MEMORY_SYSTEM );
+
+ world->heap = heap;
+ mdl_context *meta = &world->meta;
+
+ mdl_open( meta, path, world->heap );
+ mdl_load_metadata_block( meta, world->heap );
+ mdl_load_animation_block( meta, world->heap );
+ mdl_load_mesh_block( meta, world->heap );
+
+ vg_info( "%u\n", sizeof(ent_cubemap) );
+
+ MDL_LOAD_ARRAY( meta, &world->ent_gate, ent_gate, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_camera, ent_camera, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_spawn, ent_spawn, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_light, ent_light, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_route_node,ent_route_node, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_path_index,ent_path_index, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_checkpoint,ent_checkpoint, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_route, ent_route, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_water, ent_water, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_audio_clip,ent_audio_clip, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_audio, ent_audio, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_volume, ent_volume, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_traffic, ent_traffic, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_marker, ent_marker, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_skateshop, ent_skateshop, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_swspreview,ent_swspreview, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_ccmd, ent_ccmd, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_objective, ent_objective, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_challenge, ent_challenge, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_relay, ent_relay, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_cubemap, ent_cubemap, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_miniworld, ent_miniworld, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_prop, ent_prop, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_region, ent_region, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_glider, ent_glider, heap );
+ MDL_LOAD_ARRAY( meta, &world->ent_npc, ent_npc, heap );
+
+ mdl_array_ptr infos;
+ MDL_LOAD_ARRAY( meta, &infos, ent_worldinfo, vg_mem.scratch );
+
+ world->skybox = k_skybox_default;
+ if( mdl_arrcount(&infos) )
+ {
+ world->info = *((ent_worldinfo *)mdl_arritm(&infos,0));
+
+ if( world->meta.info.version >= 104 )
+ {
+ if( MDL_CONST_PSTREQ( &world->meta, world->info.pstr_skybox,"space"))
+ {
+ world->skybox = k_skybox_space;
+ }
+ }
+ }
+ else
+ {
+ world->info.pstr_author = 0;
+ world->info.pstr_desc = 0;
+ world->info.pstr_name = 0;
+ world->info.timezone = 0.0f;
+ world->info.flags = 0;
+ }
+
+ time_t seconds = time(NULL) % ((u32)vg_maxf(1.0f,k_day_length)*60);
+ world->time = ((f64)(seconds)/(k_day_length*60.0));
+ world->time += (world->info.timezone/24.0);
+
+ /* process resources from pack */
+ u64 t4 = SDL_GetPerformanceCounter();
+ world_gen_load_surfaces( world );
+ u64 t5 = SDL_GetPerformanceCounter();
+ world_gen_routes_ent_init( world );
+ world_gen_entities_init( world );
+ u64 t6 = SDL_GetPerformanceCounter();
+
+ /* main bulk */
+ u64 t0 = SDL_GetPerformanceCounter();
+ world_gen_generate_meshes( world );
+ u64 t1 = SDL_GetPerformanceCounter();
+ world_gen_routes_generate( instance_id );
+ u64 t2 = SDL_GetPerformanceCounter();
+ world_gen_compute_light_indices( world );
+ u64 t3 = SDL_GetPerformanceCounter();
+ mdl_close( meta );
+
+ u64 utime_mesh = t1-t0,
+ utime_route = t2-t1,
+ utime_indices = t3-t2,
+ utime_tex = t5-t4,
+ utime_ent = t6-t5,
+ ufreq = SDL_GetPerformanceFrequency();
+
+ f64 ftime_mesh = ((f64)utime_mesh / (f64)ufreq)*1000.0,
+ ftime_route = ((f64)utime_route / (f64)ufreq)*1000.0,
+ ftime_ind = ((f64)utime_route / (f64)ufreq)*1000.0,
+ ftime_tex = ((f64)utime_tex / (f64)ufreq)*1000.0,
+ ftime_ent = ((f64)utime_ent / (f64)ufreq)*1000.0;
+
+ vg_info( "wtime:mesh %.2fms route %.2fms ind %.2fms tex %.2fms ent %.2fms\n",
+ ftime_mesh, ftime_route, ftime_ind, ftime_tex, ftime_ent );
+
+ /* init player position.
+ * - this is overriden by the save state when(if) it loads */
+ world_default_spawn_pos( world, world->player_co );
+
+ /* allocate leaderboard buffers */
+ u32 bs = mdl_arrcount(&world->ent_route)*sizeof(struct leaderboard_cache);
+ world->leaderboard_cache = vg_linear_alloc( heap, bs );
+
+ for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i ++ )
+ {
+ struct leaderboard_cache *board = &world->leaderboard_cache[i];
+ board->data = vg_linear_alloc( heap, NETWORK_REQUEST_MAX );
+ board->status = k_request_status_client_error;
+ board->cache_time = 0.0;
+ board->data_len = 0;
+ }
+
+ world->routes_ui = vg_linear_alloc( heap,
+ sizeof(struct route_ui)*mdl_arrcount(&world->ent_route) );
+
+ vg_async_call( async_world_postprocess, world, 0 );
+ vg_async_stall();
+}
+
+struct world_load_complete_data{
+ savedata_file save;
+ enum world_purpose purpose;
+};
+
+static void skaterift_world_load_done( void *payload, u32 size )
+{
+ struct world_load_complete_data *data = payload;
+ world_instance *world = &world_static.instances[ data->purpose ];
+
+ vg_msg sav;
+ vg_msg_init( &sav, data->save.buf, data->save.len );
+
+ if( data->purpose != k_world_purpose_hub )
+ {
+ vg_msg player_frame = sav;
+ if( vg_msg_seekframe( &player_frame, "player" ) )
+ {
+ vg_msg_getkvvecf( &player_frame, "position", k_vg_msg_v3f,
+ world->player_co, NULL );
+ }
+ }
+
+ world_entity_start( world, &sav );
+ world->status = k_world_status_loaded;
+ world_static.load_state = k_world_loader_none;
+
+ if( world_static.clear_async_op_when_done )
+ {
+ g_client.loaded = 1;
+ world_static.clear_async_op_when_done = 0;
+ }
+}
+
+/*
+ * Does a complete world switch using the remaining free slots
+ */
+void skaterift_world_load_thread( void *_args )
+{
+ struct world_load_args args = *((struct world_load_args *)_args);
+
+ addon_reg *reg = args.reg;
+ world_static.instance_addons[ args.purpose ] = reg;
+
+ char uid[ADDON_UID_MAX];
+ addon_alias_uid( ®->alias, uid );
+ vg_info( "LOAD WORLD %s @%d\n", uid, args.purpose );
+
+ char path_buf[4096];
+ vg_str path;
+ vg_strnull( &path, path_buf, 4096 );
+
+ addon_get_content_folder( reg, &path, 1 );
+
+ vg_str folder = path;
+ if( !vg_strgood( &folder ) ) {
+ vg_error( "Load target too long\n" );
+ return;
+ }
+
+ char worlds[k_world_max-1][4096];
+ u32 i=0;
+
+ vg_dir dir;
+ if( !vg_dir_open(&dir, folder.buffer) ){
+ vg_error( "opendir('%s') failed\n", folder.buffer );
+ return;
+ }
+
+ while( vg_dir_next_entry(&dir) ){
+ if( vg_dir_entry_type(&dir) == k_vg_entry_type_file ){
+ const char *d_name = vg_dir_entry_name(&dir);
+ if( d_name[0] == '.' ) continue;
+
+ vg_str file = folder;
+ vg_strcat( &file, "/" );
+ vg_strcat( &file, d_name );
+ if( !vg_strgood( &file ) ) continue;
+
+ char *ext = vg_strch( &file, '.' );
+ if( !ext ) continue;
+ if( strcmp(ext,".mdl") ) continue;
+
+ if( i == k_world_max-1 ){
+ vg_warn( "There are too many .mdl files in the map folder!(3)\n" );
+ break;
+ }
+
+ strcpy( worlds[i++], file.buffer );
+ }
+ }
+ vg_dir_close(&dir);
+
+ if( i == 0 ){
+ vg_warn( "There are no .mdl files in the map folder.\n" );
+ }
+
+ u32 first_index = 0;
+ for( u32 j=0; j<i; j++ ){
+ vg_str name = { .buffer = worlds[j], .i=strlen(worlds[j]),
+ sizeof(worlds[j]) };
+ char *fname = vg_strch( &name, '/' );
+ if( fname ){
+ if( !strcmp( fname+1, "main.mdl" ) ){
+ first_index = j;
+ }
+ }
+ }
+
+ world_instance_load_mdl( args.purpose, worlds[first_index] );
+
+ vg_async_item *final_call =
+ vg_async_alloc( sizeof(struct world_load_complete_data) );
+
+ struct world_load_complete_data *data = final_call->payload;
+ data->purpose = args.purpose;
+
+ skaterift_world_get_save_path( args.purpose, data->save.path );
+ savedata_file_read( &data->save );
+
+ vg_async_dispatch( final_call, skaterift_world_load_done );
+ vg_async_stall();
+}
+
+void skaterift_change_client_world_preupdate(void)
+{
+ if( world_static.load_state != k_world_loader_preload )
+ return;
+
+ /* holding pattern before we can start loading the new world, since we might
+ * be waiting for audio to stop */
+ for( u32 i=1; i<k_world_max; i++ )
+ {
+ world_instance *inst = &world_static.instances[i];
+
+ if( inst->status == k_world_status_unloading )
+ {
+ if( world_freeable( inst ) )
+ {
+ world_free( inst );
+ }
+ return;
+ }
+ }
+
+ if( vg_loader_availible() )
+ {
+ vg_info( "worlds cleared, begining load\n" );
+ world_static.load_state = k_world_loader_load;
+
+ vg_linear_clear( vg_async.buffer );
+ struct world_load_args *args =
+ vg_linear_alloc( vg_async.buffer, sizeof(struct world_load_args) );
+ args->purpose = k_world_purpose_client;
+ args->reg = world_static.instance_addons[ k_world_purpose_client ];
+
+ /* this is replaces the already correct reg but we have to set it again
+ * TOO BAD */
+
+ /* finally can start the loader */
+ vg_loader_start( skaterift_world_load_thread, args );
+ }
+}
+
+/*
+ * places all loaded worlds into unloading state, pass NULL to reload the world
+ */
+void skaterift_change_world_start( addon_reg *reg )
+{
+ if( world_static.instance_addons[ k_world_purpose_client ] == reg )
+ {
+ vg_warn( "World is already loaded\n" );
+ return;
+ }
+
+ if( !reg )
+ {
+ if( world_static.instance_addons[ k_world_purpose_client ] )
+ {
+ reg = world_static.instance_addons[ k_world_purpose_client ];
+ world_static.clear_async_op_when_done = 1;
+ }
+ else
+ {
+ vg_warn( "No client world loaded\n" );
+ return;
+ }
+ }
+
+ world_static.load_state = k_world_loader_preload;
+
+ if( world_static.active_instance != 0 )
+ g_client.loaded = 0;
+
+ char buf[76];
+ addon_alias_uid( ®->alias, buf );
+ vg_info( "switching to: %s\n", buf );
+ skaterift_autosave(1);
+
+ vg_linear_clear( vg_mem.scratch ); /* ?? */
+ vg_info( "unloading old worlds\n" );
+
+ world_instance *client_world =
+ &world_static.instances[ k_world_purpose_client ];
+
+ if( client_world->status == k_world_status_loaded )
+ {
+ client_world->status = k_world_status_unloading;
+ world_fadeout_audio( client_world );
+ }
+
+ world_static.instance_addons[ k_world_purpose_client ] = reg;
+ network_send_item( k_netmsg_playeritem_world1 );
+ relink_all_remote_player_worlds();
+ world_unlink_nonlocal( &world_static.instances[k_world_purpose_hub] );
+}
+
+/* console command for the above function */
+int skaterift_load_world_command( int argc, const char *argv[] )
+{
+ if( !vg_loader_availible() )
+ {
+ vg_error( "Loading thread is currently unavailible\n" );
+ return 0;
+ }
+
+ if( argc == 1 )
+ {
+ if( !strcmp( argv[0], "reload" ) )
+ {
+ skaterift_change_world_start( NULL );
+ return 0;
+ }
+
+ addon_alias q;
+ addon_uid_to_alias( argv[0], &q );
+
+ u32 reg_id = addon_match( &q );
+ if( reg_id != 0xffffffff )
+ {
+ addon_reg *reg = get_addon_from_index( k_addon_type_world, reg_id, 0 );
+ skaterift_change_world_start( reg );
+ }
+ else
+ {
+ vg_error( "Addon '%s' is not installed or not found.\n", argv[0] );
+ }
+ }
+ else
+ {
+ vg_info( "worlds availible to load:\n" );
+
+ for( int i=0; i<addon_count(k_addon_type_world,0); i ++ )
+ {
+ addon_reg *w = get_addon_from_index( k_addon_type_world, i, 0);
+
+ char buf[ADDON_UID_MAX];
+ addon_alias_uid( &w->alias, buf );
+
+ if( w->flags & ADDON_REG_HIDDEN )
+ vg_info( " %s [hidden]\n", buf );
+ else
+ vg_info( " %s\n", buf );
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * checks:
+ * 1. to see if all audios owned by the world have been stopped
+ * 2. that this is the least significant world
+ */
+int world_freeable( world_instance *world )
+{
+ if( world->status != k_world_status_unloading ) return 0;
+ u8 world_id = (world - world_static.instances) + 1;
+
+ for( u32 i=world_id; i<VG_ARRAY_LEN(world_static.instances); i++ ){
+ if( world_static.instances[i].status != k_world_status_unloaded ){
+ return 0;
+ }
+ }
+
+ int freeable = 1;
+ audio_lock();
+ for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
+ audio_channel *ch = &vg_audio.channels[i];
+
+ if( ch->allocated && (ch->world_id == world_id)){
+ if( !audio_channel_finished( ch ) ){
+ freeable = 0;
+ break;
+ }
+ }
+ }
+ audio_unlock();
+ return freeable;
+}
+
+/*
+ * Free all resources for world instance
+ */
+void world_free( world_instance *world )
+{
+ vg_info( "Free world @%p\n", world );
+
+ /* free meshes */
+ mesh_free( &world->mesh_route_lines );
+ mesh_free( &world->mesh_geo );
+ mesh_free( &world->mesh_no_collide );
+
+ /* glDeleteBuffers silently ignores 0's and names that do not correspond to
+ * existing buffer objects.
+ * */
+ glDeleteBuffers( 1, &world->tbo_light_entities );
+ glDeleteTextures( 1, &world->tex_light_entities );
+ glDeleteTextures( 1, &world->tex_light_cubes );
+
+ /* delete textures and meshes */
+ glDeleteTextures( world->texture_count-1, world->textures+1 );
+
+ u32 world_index = world - world_static.instances;
+ if( world_index ){
+ vg_linear_del( world_static.instances[world_index-1].heap,
+ vg_linear_header(world->heap) );
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_cubemap); i++ ){
+ ent_cubemap *cm = mdl_arritm(&world->ent_cubemap,i);
+ glDeleteTextures( 1, &cm->texture_id );
+ glDeleteFramebuffers( 1, &cm->framebuffer_id );
+ glDeleteRenderbuffers( 1, &cm->renderbuffer_id );
+ }
+
+ world->status = k_world_status_unloaded;
+}
+
+/*
+ * reset the world structure without deallocating persistent buffers
+ * TODO: Make this a memset(0), and have persistent items live in a static loc
+ */
+void world_init_blank( world_instance *world )
+{
+ memset( &world->meta, 0, sizeof(mdl_context) );
+
+ world->textures = NULL;
+ world->texture_count = 0;
+ world->surfaces = NULL;
+ world->surface_count = 0;
+
+ world->geo_bh = NULL;
+ world->entity_bh = NULL;
+ world->entity_list = NULL;
+ world->rendering_gate = NULL;
+
+ world->water.enabled = 0;
+ world->time = 0.0;
+
+ /* default lighting conditions
+ * -------------------------------------------------------------*/
+ struct ub_world_lighting *state = &world->ub_lighting;
+
+ state->g_light_preview = 0;
+ state->g_shadow_samples = 8;
+ state->g_water_fog = 0.04f;
+
+ v4_zero( state->g_water_plane );
+ v4_zero( state->g_depth_bounds );
+
+ state->g_shadow_length = 9.50f;
+ state->g_shadow_spread = 0.65f;
+
+#if 0
+ /* 2023 style */
+ v3_copy( (v3f){0.37f, 0.54f, 0.97f}, state->g_daysky_colour );
+ v3_copy( (v3f){0.03f, 0.05f, 0.20f}, state->g_nightsky_colour );
+ v3_copy( (v3f){1.00f, 0.32f, 0.01f}, state->g_sunset_colour );
+ v3_copy( (v3f){0.13f, 0.17f, 0.35f}, state->g_ambient_colour );
+ v3_copy( (v3f){0.25f, 0.17f, 0.51f}, state->g_sunset_ambient );
+ v3_copy( (v3f){1.10f, 0.89f, 0.35f}, state->g_sun_colour );
+#else
+ /* 2024 style */
+ v3_copy( (v3f){0.308f, 0.543f, 0.904f}, state->g_daysky_colour );
+ v3_copy( (v3f){0.030f, 0.050f, 0.200f}, state->g_nightsky_colour );
+ v3_copy( (v3f){1.000f, 0.320f, 0.010f}, state->g_sunset_colour );
+ v3_copy( (v3f){0.130f, 0.170f, 0.350f}, state->g_ambient_colour );
+ v3_copy( (v3f){0.25f, 0.17f, 0.51f}, state->g_sunset_ambient );
+ v3_copy( (v3f){1.000f, 0.809f, 0.318f}, state->g_sun_colour );
+#endif
+}
--- /dev/null
+#pragma once
+#include <time.h>
+
+#include "world.h"
+#include "addon.h"
+
+void world_free( world_instance *world );
+int world_freeable( world_instance *world );
+int skaterift_load_world_command( int argc, const char *argv[] );
+void skaterift_change_world_start( addon_reg *reg );
+void skaterift_change_client_world_preupdate(void);
--- /dev/null
+#include "skaterift.h"
+#include "world_map.h"
+#include "world.h"
+#include "input.h"
+#include "gui.h"
+#include "menu.h"
+#include "scene.h"
+
+struct world_map world_map;
+
+static void world_map_get_dir( v3f dir )
+{
+ /* idk */
+ dir[0] = -sqrtf(0.5f);
+ dir[2] = sqrtf(0.5f);
+ dir[1] = 1.0f;
+ v3_normalize(dir);
+}
+
+static void world_map_get_plane( v4f plane )
+{
+ world_instance *world = &world_static.instances[ world_map.world_id ];
+ f32 h = localplayer.rb.co[1];
+ if( world_map.world_id != world_static.active_instance )
+ h = (world->scene_geo.bbx[0][1] + world->scene_geo.bbx[1][1]) * 0.5f;
+
+ v4_copy( (v4f){0.0f,1.0f,0.0f,h}, plane );
+}
+
+static void respawn_world_to_plane_pos( v3f pos, v2f plane_pos )
+{
+ v3f dir;
+ world_map_get_dir( dir );
+ v3_negate(dir,dir);
+ v4f plane;
+ world_map_get_plane( plane );
+
+ v3f co;
+ f32 t = ray_plane( plane, pos, dir );
+ v3_muladds( pos, dir, t, co );
+ plane_pos[0] = co[0];
+ plane_pos[1] = co[2];
+}
+
+static void respawn_map_draw_icon( vg_camera *cam,
+ enum gui_icon icon, v3f pos, f32 size )
+{
+ v4f v;
+ v3_copy( pos, v );
+ v[3] = 1.0f;
+ m4x4_mulv( cam->mtx.pv, v, v );
+ v2_divs( v, v[3], v );
+
+ gui_draw_icon( icon, (v2f){ v[0]*0.5f+0.5f,v[1]*0.5f+0.5f }, size );
+}
+
+static void world_map_select_close(void)
+{
+ world_map.sel_spawn = world_map.close_spawn;
+ gui_helper_clear();
+
+ vg_str text;
+ if( gui_new_helper( input_button_list[k_srbind_maccept], &text ) )
+ vg_strcat( &text, "Spawn Here" );
+ if( gui_new_helper( input_button_list[k_srbind_mback], &text ) )
+ vg_strcat( &text, "Back" );
+}
+
+void world_map_click(void)
+{
+ world_map_select_close();
+}
+
+static void world_map_help_normal(void)
+{
+ gui_helper_clear();
+
+ vg_str text;
+ if( gui_new_helper( input_joy_list[k_srjoystick_steer], &text ) )
+ vg_strcat( &text, "Move" );
+
+ if( gui_new_helper( input_button_list[k_srbind_maccept], &text ) )
+ vg_strcat( &text, "Select" );
+
+ if( gui_new_helper( input_button_list[k_srbind_mback], &text ) )
+ vg_strcat( &text, "Exit" );
+
+ if( world_static.instances[1].status == k_world_status_loaded )
+ {
+ if( gui_new_helper( input_button_list[k_srbind_mhub], &text ) )
+ vg_strcat( &text, world_static.active_instance?
+ "Go to Hub": "Go to Active World" );
+ }
+}
+
+void world_map_pre_update(void)
+{
+ if( menu_viewing_map() )
+ {
+ if( !world_map.view_ready )
+ {
+ world_map.world_id = world_static.active_instance;
+
+ world_instance *world = &world_static.instances[ world_map.world_id ];
+ v3f *bbx = world->scene_geo.bbx;
+
+ v3_copy( localplayer.rb.co, world->player_co );
+ respawn_world_to_plane_pos( localplayer.rb.co, world_map.plane_pos );
+ world_map.boom_dist = 400.0f;
+ world_map.home_select = 0;
+ world_map.view_ready = 1;
+ world_map.sel_spawn = NULL;
+ world_map.close_spawn = NULL;
+
+ world_map_help_normal();
+ }
+ }
+ else
+ {
+ if( world_map.view_ready )
+ {
+ gui_helper_clear();
+ world_map.view_ready = 0;
+ }
+
+ return;
+ }
+
+ world_instance *world = &world_static.instances[ world_map.world_id ];
+ v3f *bbx = world->scene_geo.bbx;
+ f32 *pos = world_map.plane_pos;
+
+ v2f steer;
+ joystick_state( k_srjoystick_steer, steer );
+ v2_normalize_clamp( steer );
+
+ if( !world_map.sel_spawn )
+ {
+ f32 *pos = world_map.plane_pos;
+ m2x2f rm;
+ m2x2_create_rotation( rm, -0.25f*VG_PIf );
+ m2x2_mulv( rm, steer, steer );
+ v2_muladds( pos, steer, vg.time_frame_delta * 200.0f, pos );
+ }
+
+ f32 bd_target = 400.0f,
+ interp = vg.time_frame_delta*2.0f;
+
+ if( world_map.sel_spawn )
+ {
+ v2f pp;
+ respawn_world_to_plane_pos( world_map.sel_spawn->transform.co, pp );
+ v2_lerp( pos, pp, interp, pos );
+
+ bd_target = 200.0f;
+ }
+ world_map.boom_dist = vg_lerpf( world_map.boom_dist, bd_target, interp );
+
+ v2_minv( (v2f){ bbx[1][0], bbx[1][2] }, pos, pos );
+ v2_maxv( (v2f){ bbx[0][0], bbx[0][2] }, pos, pos );
+
+ /* update camera */
+ vg_camera *cam = &world_map.cam;
+ v3f dir;
+ world_map_get_dir(dir);
+
+ v4f plane;
+ world_map_get_plane( plane );
+
+ v3f co = { pos[0], plane[3]*plane[1], pos[1] };
+ v3_muladds( co, dir, world_map.boom_dist, cam->pos );
+
+ vg_line_cross( co, VG__RED, 10.0f );
+
+ cam->angles[0] = 0.25f * VG_PIf;
+ cam->angles[1] = 0.25f * VG_PIf;
+ cam->farz = 5000.0f;
+ cam->nearz = 10.0f;
+ cam->fov = 40.0f;
+
+ vg_camera_update_transform( cam );
+ vg_camera_update_view( cam );
+ vg_camera_update_projection( cam );
+ vg_camera_finalize( cam );
+
+ /* pick spawn */
+ f32 closest2 = INFINITY;
+ v2f centroid = { 0, 0 };
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ )
+ {
+ ent_spawn *spawn = mdl_arritm(&world->ent_spawn,i);
+
+ v4f v;
+ v3_copy( spawn->transform.co, v );
+ v[3] = 1.0f;
+ m4x4_mulv( cam->mtx.pv, v, v );
+ v2_divs( v, v[3], v );
+
+ f32 d2 = v2_dist2(v, centroid);
+ if( d2 < closest2 )
+ {
+ world_map.close_spawn = spawn;
+ closest2 = d2;
+ }
+ spawn->transform.s[0] = d2;
+ }
+
+ if( button_down( k_srbind_maccept ) )
+ {
+ if( world_map.sel_spawn )
+ {
+ skaterift.activity = k_skaterift_default;
+ world_static.active_instance = world_map.world_id;
+ srinput.state = k_input_state_resume;
+ player__spawn( world_map.sel_spawn );
+ return;
+ }
+ else
+ {
+ world_map_select_close();
+ }
+ }
+
+ if( button_down( k_srbind_mback ) )
+ {
+ if( world_map.sel_spawn )
+ {
+ world_map.sel_spawn = NULL;
+ world_map_help_normal();
+ }
+ else
+ {
+ srinput.state = k_input_state_resume;
+ skaterift.activity = k_skaterift_default;
+ return;
+ }
+ }
+
+ /* icons
+ * ---------------------*/
+ for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ )
+ {
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
+
+ enum gui_icon icon = k_gui_icon_exclaim_2d;
+ if( challenge->status )
+ icon = k_gui_icon_tick_2d;
+
+ respawn_map_draw_icon( cam, icon, challenge->transform.co, 1.0f );
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i ++ )
+ {
+ ent_spawn *spawn = mdl_arritm( &world->ent_spawn, i );
+
+ if( spawn->transform.s[0] > 0.3f )
+ continue;
+
+ f32 s = 1.0f-(spawn->transform.s[0] / 0.3f);
+ respawn_map_draw_icon( cam,
+ spawn==world_map.sel_spawn?
+ k_gui_icon_spawn_select: k_gui_icon_spawn,
+ spawn->transform.co, s );
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_skateshop); i++ )
+ {
+ ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, i );
+ if( shop->type == k_skateshop_type_boardshop )
+ {
+ respawn_map_draw_icon( cam, k_gui_icon_board, shop->transform.co, 1 );
+ }
+ else if( shop->type == k_skateshop_type_worldshop )
+ {
+ respawn_map_draw_icon( cam, k_gui_icon_world, shop->transform.co, 1 );
+ }
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ )
+ {
+ ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+ if( gate->flags & k_ent_gate_nonlocal )
+ {
+ respawn_map_draw_icon( cam, k_gui_icon_rift, gate->co[0], 1 );
+ }
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ )
+ {
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ v4f colour;
+ v4_copy( route->colour, colour );
+ v3_muls( colour, 1.6666f, colour );
+ gui_icon_setcolour( colour );
+ respawn_map_draw_icon( cam, k_gui_icon_rift_run_2d,
+ route->board_transform[3], 1 );
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_glider); i ++ )
+ {
+ ent_glider *glider = mdl_arritm( &world->ent_glider, i );
+
+ v4f colour = { 1,1,1,1 };
+
+ if( !(glider->flags & 0x1) )
+ v3_muls( colour, 0.5f, colour );
+ gui_icon_setcolour( colour );
+
+ respawn_map_draw_icon( cam, k_gui_icon_glider, glider->transform.co, 1 );
+ }
+}
--- /dev/null
+#pragma once
+#include "vg/vg_platform.h"
+#include "vg/vg_camera.h"
+#include "world_entity.h"
+
+struct world_map
+{
+ v2f plane_pos;
+ f32 boom_dist;
+ u32 world_id;
+ u32 home_select;
+
+ ent_spawn *sel_spawn, *close_spawn;
+ vg_camera cam;
+
+ bool view_ready;
+}
+extern world_map;
+void world_map_pre_update(void);
--- /dev/null
+#ifndef WORLD_PHYSICS_C
+#define WORLD_PHYSICS_C
+
+#include "world.h"
+#include "world_physics.h"
+
+void ray_world_get_tri( world_instance *world, ray_hit *hit, v3f tri[3] )
+{
+ for( int i=0; i<3; i++ )
+ v3_copy( world->scene_geo.arrvertices[ hit->tri[i] ].co, tri[i] );
+}
+
+int ray_world( world_instance *world,
+ v3f pos, v3f dir, ray_hit *hit, u16 ignore )
+{
+ return scene_raycast( &world->scene_geo, world->geo_bh, pos, dir, hit,
+ ignore );
+}
+
+/*
+ * Cast a sphere from a to b and see what time it hits
+ */
+int spherecast_world( world_instance *world,
+ v3f pa, v3f pb, float r, float *t, v3f n, u16 ignore )
+{
+ boxf region;
+ box_init_inf( region );
+ box_addpt( region, pa );
+ box_addpt( region, pb );
+
+ v3_add( (v3f){ r, r, r}, region[1], region[1] );
+ v3_add( (v3f){-r,-r,-r}, region[0], region[0] );
+
+ v3f dir;
+ v3_sub( pb, pa, dir );
+
+ v3f dir_inv;
+ dir_inv[0] = 1.0f/dir[0];
+ dir_inv[1] = 1.0f/dir[1];
+ dir_inv[2] = 1.0f/dir[2];
+
+ int hit = -1;
+ float min_t = 1.0f;
+
+ bh_iter it;
+ bh_iter_init_box( 0, &it, region );
+ i32 idx;
+ while( bh_next( world->geo_bh, &it, &idx ) ){
+ u32 *ptri = &world->scene_geo.arrindices[ idx*3 ];
+ if( world->scene_geo.arrvertices[ptri[0]].flags & ignore ) continue;
+
+ v3f tri[3];
+ boxf box;
+ box_init_inf( box );
+ for( int j=0; j<3; j++ ){
+ v3_copy( world->scene_geo.arrvertices[ptri[j]].co, tri[j] );
+ box_addpt( box, tri[j] );
+ }
+
+ v3_add( (v3f){ r, r, r}, box[1], box[1] );
+ v3_add( (v3f){-r,-r,-r}, box[0], box[0] );
+
+ if( !ray_aabb1( box, pa, dir_inv, 1.0f ) )
+ continue;
+
+ float t;
+ v3f n1;
+ if( spherecast_triangle( tri, pa, dir, r, &t, n1 ) ){
+ if( t < min_t ){
+ min_t = t;
+ hit = idx;
+ v3_copy( n1, n );
+ }
+ }
+ }
+
+ *t = min_t;
+ return hit;
+}
+
+struct world_surface *world_tri_index_surface( world_instance *world,
+ u32 index )
+{
+ for( int i=1; i<world->surface_count; i++ ){
+ struct world_surface *surf = &world->surfaces[i];
+
+ if( (index >= surf->sm_geo.vertex_start) &&
+ (index < surf->sm_geo.vertex_start+surf->sm_geo.vertex_count ) )
+ {
+ return surf;
+ }
+ }
+
+ return &world->surfaces[0];
+}
+
+struct world_surface *world_contact_surface( world_instance *world, rb_ct *ct )
+{
+ return world_tri_index_surface( world, ct->element_id );
+}
+
+struct world_surface *ray_hit_surface( world_instance *world, ray_hit *hit )
+{
+ return world_tri_index_surface( world, hit->tri[0] );
+}
+
+#endif /* WORLD_PHYSICS_C */
--- /dev/null
+#pragma once
+#include "world.h"
+#include "vg/vg_rigidbody.h"
+#include "vg/vg_rigidbody_collision.h"
+#include "vg/vg_bvh.h"
+
+void ray_world_get_tri( world_instance *world,
+ ray_hit *hit, v3f tri[3] );
+
+int ray_world( world_instance *world,
+ v3f pos, v3f dir, ray_hit *hit, u16 ignore );
+
+int spherecast_world( world_instance *world,
+ v3f pa, v3f pb, float r, float *t, v3f n,
+ u16 ignore );
+
+struct world_surface *world_tri_index_surface( world_instance *world,
+ u32 index );
+
+struct world_surface *world_contact_surface( world_instance *world,
+ rb_ct *ct );
+
+struct world_surface *ray_hit_surface( world_instance *world,
+ ray_hit *hit );
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#include "world.h"
+#include "world_render.h"
+#include "font.h"
+#include "gui.h"
+#include "world_map.h"
+#include "ent_miniworld.h"
+#include "player_remote.h"
+#include "ent_skateshop.h"
+#include "ent_npc.h"
+#include "shaders/model_entity.h"
+
+struct world_render world_render;
+
+static int ccmd_set_time( int argc, const char *argv[] ){
+ world_instance *world = world_current_instance();
+ if( argc == 1 )
+ world->time = atof( argv[0] );
+ else
+ vg_error( "Usage set_time <0-1.0> (current time: %f)\n", world->time );
+ return 0;
+}
+
+static void async_world_render_init( void *payload, u32 size )
+{
+ vg_info( "Allocate uniform buffers\n" );
+ for( int i=0; i<k_world_max; i++ )
+ {
+ world_instance *world = &world_static.instances[i];
+ world->ubo_bind_point = i;
+
+ glGenBuffers( 1, &world->ubo_lighting );
+ glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+ glBufferData( GL_UNIFORM_BUFFER, sizeof(struct ub_world_lighting),
+ NULL, GL_DYNAMIC_DRAW );
+
+ glBindBufferBase( GL_UNIFORM_BUFFER, i, world->ubo_lighting );
+ }
+}
+
+void world_render_init(void)
+{
+ VG_VAR_F32( k_day_length );
+ VG_VAR_I32( k_debug_light_indices );
+ VG_VAR_I32( k_debug_light_complexity );
+ VG_VAR_I32( k_light_preview );
+ VG_VAR_I32( k_light_editor );
+ vg_console_reg_cmd( "set_time", ccmd_set_time, NULL );
+
+ world_render.sky_rate = 1.0;
+ world_render.sky_target_rate = 1.0;
+
+ vg_info( "Loading world resources\n" );
+ vg_linear_clear( vg_mem.scratch );
+
+ mdl_context msky;
+ mdl_open( &msky, "models/rs_skydome.mdl", vg_mem.scratch );
+ mdl_load_metadata_block( &msky, vg_mem.scratch );
+ mdl_async_load_glmesh( &msky, &world_render.skydome, NULL );
+ mdl_close( &msky );
+
+ vg_info( "Loading default world textures\n" );
+ vg_tex2d_load_qoi_async_file( "textures/garbage.qoi",
+ VG_TEX2D_NEAREST|VG_TEX2D_REPEAT,
+ &world_render.tex_terrain_noise );
+
+ vg_info( "Allocate frame buffers\n" );
+ for( int i=0; i<k_world_max; i++ )
+ {
+ world_instance *world = &world_static.instances[i];
+ world->heightmap = vg_framebuffer_allocate( vg_mem.rtmemory, 1, 0 );
+ world->heightmap->display_name = NULL;
+ world->heightmap->fixed_w = 1024;
+ world->heightmap->fixed_h = 1024;
+ world->heightmap->resolution_div = 0;
+ world->heightmap->attachments[0] = (vg_framebuffer_attachment)
+ {
+ NULL, k_framebuffer_attachment_type_texture,
+ .internalformat = GL_RG16F,
+ .format = GL_RG,
+ .type = GL_FLOAT,
+ .attachment = GL_COLOR_ATTACHMENT0
+ };
+ vg_framebuffer_create( world->heightmap );
+ }
+
+ vg_async_call( async_world_render_init, NULL, 0 );
+}
+
+/*
+ * standard uniform bindings
+ * ----------------------------------------------------------------------------
+ */
+void world_link_lighting_ub( world_instance *world, GLuint shader )
+{
+ GLuint idx = glGetUniformBlockIndex( shader, "ub_world_lighting" );
+ glUniformBlockBinding( shader, idx, world->ubo_bind_point );
+}
+
+void world_bind_position_texture( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot )
+{
+ vg_framebuffer_bind_texture( world->heightmap, 0, slot );
+ glUniform1i( location, slot );
+}
+
+void world_bind_light_array( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot )
+{
+ glActiveTexture( GL_TEXTURE0 + slot );
+ glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
+ glUniform1i( location, slot );
+}
+
+void world_bind_light_index( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot )
+{
+ glActiveTexture( GL_TEXTURE0 + slot );
+ glBindTexture( GL_TEXTURE_3D, world->tex_light_cubes );
+ glUniform1i( location, slot );
+}
+
+void bind_terrain_noise(void)
+{
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+}
+
+/*
+ * Get OpenGL texture name from texture ID.
+ */
+static GLuint world_get_texture( world_instance *world, u32 id ){
+ if( id & 0x80000000 ) return skaterift.rt_textures[id & ~0x80000000];
+ else return world->textures[ id ];
+}
+
+/*
+ * Passes Rendering
+ * ----------------------------------------------------------------------------
+ */
+
+struct world_pass
+{
+ vg_camera *cam;
+ enum mdl_shader shader;
+ enum world_geo_type geo_type;
+
+ void (*fn_bind)( world_instance *world, struct world_surface *mat );
+ void (*fn_set_mdl)( m4x3f mdl );
+ void (*fn_set_uPvmPrev)( m4x4f pvm );
+ void (*fn_set_uNormalMtx)( m3x3f mnorm );
+};
+
+void render_world_depth( world_instance *world, vg_camera *cam );
+
+/*
+ * Render a run of submeshes, only of those which match material_id
+ */
+static void world_render_submeshes( world_instance *world,
+ struct world_pass *pass,
+ mdl_transform *transform,
+ u32 start, u32 count, u32 material_id )
+{
+ for( u32 k=0; k<count; k++ )
+ {
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs, start+k );
+ if( sm->material_id != material_id )
+ continue;
+
+ m4x3f mmdl;
+ mdl_transform_m4x3( transform, mmdl );
+
+ m4x4f m4mdl;
+ m4x3_expand( mmdl, m4mdl );
+ m4x4_mul( pass->cam->mtx_prev.pv, m4mdl, m4mdl );
+
+ pass->fn_set_mdl( mmdl );
+ pass->fn_set_uPvmPrev( m4mdl );
+
+ mdl_draw_submesh( sm );
+ }
+}
+
+/*
+ * Render props attached to this material
+ */
+static void world_render_props( world_instance *world, u32 material_id,
+ struct world_pass *pass )
+{
+ struct world_surface *mat = &world->surfaces[ material_id ];
+ if( !(mat->flags & WORLD_SURFACE_HAS_PROPS) ) return;
+
+ pass->fn_bind( world, mat );
+
+ for( u32 j=0; j<mdl_arrcount( &world->ent_prop ); j++ ){
+ ent_prop *prop = mdl_arritm( &world->ent_prop, j );
+ if( prop->flags & 0x1 ) continue;
+
+ world_render_submeshes( world, pass, &prop->transform,
+ prop->submesh_start, prop->submesh_count, material_id );
+ }
+}
+
+/*
+ * Render traffic models attactched to this material
+ */
+static void world_render_traffic( world_instance *world, u32 material_id,
+ struct world_pass *pass )
+{
+ struct world_surface *mat = &world->surfaces[ material_id ];
+ if( !(mat->flags & WORLD_SURFACE_HAS_TRAFFIC) ) return;
+
+ pass->fn_bind( world, mat );
+
+ for( u32 j=0; j<mdl_arrcount( &world->ent_traffic ); j++ ){
+ ent_traffic *traffic = mdl_arritm( &world->ent_traffic, j );
+
+ world_render_submeshes( world, pass, &traffic->transform,
+ traffic->submesh_start, traffic->submesh_count,
+ material_id );
+ }
+}
+
+/*
+ * Iterate and render all materials which match the passes shader and geometry
+ * type. Includes props/traffic.
+ */
+static void world_render_pass( world_instance *world, struct world_pass *pass )
+{
+ for( int i=0; i<world->surface_count; i++ )
+ {
+ struct world_surface *mat = &world->surfaces[i];
+
+ if( mat->info.shader == pass->shader )
+ {
+ mdl_submesh *sm;
+
+ if( pass->geo_type == k_world_geo_type_solid )
+ {
+ sm = &mat->sm_geo;
+ }
+ else
+ {
+ world_render_traffic( world, i, pass );
+ world_render_props( world, i, pass );
+ sm = &mat->sm_no_collide;
+ }
+
+ if( !sm->indice_count )
+ continue;
+
+ m4x3f mmdl;
+ m4x3_identity( mmdl );
+ pass->fn_set_mdl( mmdl );
+ pass->fn_set_uPvmPrev( pass->cam->mtx_prev.pv );
+ pass->fn_bind( world, mat );
+ mdl_draw_submesh( sm );
+ }
+ }
+}
+
+/*
+ * Specific shader instructions
+ * ----------------------------------------------------------------------------
+ */
+
+static void world_render_both_stages( world_instance *world,
+ struct world_pass *pass )
+{
+ mesh_bind( &world->mesh_geo );
+ pass->geo_type = k_world_geo_type_solid;
+ world_render_pass( world, pass );
+
+ glDisable( GL_CULL_FACE );
+ mesh_bind( &world->mesh_no_collide );
+ pass->geo_type = k_world_geo_type_nonsolid;
+ world_render_pass( world, pass );
+ glEnable( GL_CULL_FACE );
+}
+
+static void bindpoint_world_vb( world_instance *world,
+ struct world_surface *mat )
+{
+ struct shader_props_vertex_blend *props = mat->info.props.compiled;
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
+
+#if 0
+ shader_scene_vertex_blend_uOffset( props->blend_offset );
+#endif
+}
+
+static void render_world_vb( world_instance *world, vg_camera *cam )
+{
+ shader_scene_vertex_blend_use();
+ shader_scene_vertex_blend_uTexGarbage(0);
+ shader_scene_vertex_blend_uTexGradients(1);
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_vertex_blend );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+
+ shader_scene_vertex_blend_uPv( cam->mtx.pv );
+ shader_scene_vertex_blend_uCamera( cam->transform[3] );
+
+ struct world_pass pass =
+ {
+ .shader = k_shader_standard_vertex_blend,
+ .cam = cam,
+ .fn_bind = bindpoint_world_vb,
+ .fn_set_mdl = shader_scene_vertex_blend_uMdl,
+ .fn_set_uPvmPrev = shader_scene_vertex_blend_uPvmPrev,
+ };
+
+ world_render_both_stages( world, &pass );
+}
+
+static void world_shader_standard_bind( world_instance *world, vg_camera *cam )
+{
+ shader_scene_standard_use();
+ shader_scene_standard_uTexGarbage(0);
+ shader_scene_standard_uTexMain(1);
+ shader_scene_standard_uPv( cam->mtx.pv );
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_standard );
+
+ bind_terrain_noise();
+ shader_scene_standard_uCamera( cam->transform[3] );
+}
+
+static void bindpoint_standard( world_instance *world,
+ struct world_surface *mat )
+{
+ struct shader_props_standard *props = mat->info.props.compiled;
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
+}
+
+static void render_world_standard( world_instance *world, vg_camera *cam )
+{
+ world_shader_standard_bind( world, cam );
+ struct world_pass pass =
+ {
+ .shader = k_shader_standard,
+ .cam = cam,
+ .fn_bind = bindpoint_standard,
+ .fn_set_mdl = shader_scene_standard_uMdl,
+ .fn_set_uPvmPrev = shader_scene_standard_uPvmPrev,
+ };
+
+ world_render_both_stages( world, &pass );
+}
+
+static void bindpoint_world_cubemapped( world_instance *world,
+ struct world_surface *mat )
+{
+ struct shader_props_cubemapped *props = mat->info.props.compiled;
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D,
+ world_get_texture( world,props->tex_diffuse ) );
+
+ u32 cubemap_id = props->cubemap_entity,
+ cubemap_index = 0;
+
+ if( mdl_entity_id_type( cubemap_id ) == k_ent_cubemap )
+ {
+ cubemap_index = mdl_entity_id_id( cubemap_id );
+ }
+
+ ent_cubemap *cm = mdl_arritm( &world->ent_cubemap, cubemap_index );
+ glActiveTexture( GL_TEXTURE10 );
+ glBindTexture( GL_TEXTURE_CUBE_MAP, cm->texture_id );
+
+ shader_scene_cubemapped_uColour( props->tint );
+}
+
+static void bindpoint_world_cubemapped_disabled( world_instance *world,
+ struct world_surface *mat )
+{
+ struct shader_props_cubemapped *props = mat->info.props.compiled;
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D,
+ world_get_texture( world, props->tex_diffuse ) );
+}
+
+static void render_world_cubemapped( world_instance *world, vg_camera *cam,
+ int enabled )
+{
+ if( !mdl_arrcount( &world->ent_cubemap ) )
+ return;
+
+ if( !enabled )
+ {
+ world_shader_standard_bind( world, cam );
+
+ struct world_pass pass =
+ {
+ .shader = k_shader_cubemap,
+ .cam = cam,
+ .fn_bind = bindpoint_world_cubemapped_disabled,
+ .fn_set_mdl = shader_scene_standard_uMdl,
+ .fn_set_uPvmPrev = shader_scene_standard_uPvmPrev,
+ };
+
+ world_render_both_stages( world, &pass );
+ }
+ else
+ {
+ shader_scene_cubemapped_use();
+ shader_scene_cubemapped_uTexGarbage(0);
+ shader_scene_cubemapped_uTexMain(1);
+ shader_scene_cubemapped_uTexCubemap(10);
+ shader_scene_cubemapped_uPv( cam->mtx.pv );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_cubemapped );
+
+ bind_terrain_noise();
+ shader_scene_cubemapped_uCamera( cam->transform[3] );
+
+ struct world_pass pass =
+ {
+ .shader = k_shader_cubemap,
+ .cam = cam,
+ .fn_bind = bindpoint_world_cubemapped,
+ .fn_set_mdl = shader_scene_cubemapped_uMdl,
+ .fn_set_uPvmPrev = shader_scene_cubemapped_uPvmPrev,
+ };
+
+ world_render_both_stages( world, &pass );
+ }
+}
+
+static void render_world_alphatest( world_instance *world, vg_camera *cam )
+{
+ shader_scene_standard_alphatest_use();
+ shader_scene_standard_alphatest_uTexGarbage(0);
+ shader_scene_standard_alphatest_uTexMain(1);
+ shader_scene_standard_alphatest_uPv( cam->mtx.pv );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_standard_alphatest );
+
+ bind_terrain_noise();
+ shader_scene_standard_alphatest_uCamera( cam->transform[3] );
+ glDisable(GL_CULL_FACE);
+
+ struct world_pass pass =
+ {
+ .shader = k_shader_standard_cutout,
+ .cam = cam,
+ .fn_bind = bindpoint_standard,
+ .fn_set_mdl = shader_scene_standard_alphatest_uMdl,
+ .fn_set_uPvmPrev = shader_scene_standard_alphatest_uPvmPrev,
+ };
+
+ world_render_both_stages( world, &pass );
+ glEnable(GL_CULL_FACE);
+}
+
+static void render_world_foliage( world_instance *world, vg_camera *cam )
+{
+ shader_scene_foliage_use();
+ shader_scene_foliage_uTexGarbage(0);
+ shader_scene_foliage_uTexMain(1);
+ shader_scene_foliage_uPv( cam->mtx.pv );
+ shader_scene_foliage_uTime( vg.time );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_foliage );
+ bind_terrain_noise();
+
+ shader_scene_foliage_uCamera( cam->transform[3] );
+ glDisable(GL_CULL_FACE);
+ struct world_pass pass =
+ {
+ .shader = k_shader_foliage,
+ .cam = cam,
+ .fn_bind = bindpoint_standard,
+ .fn_set_mdl = shader_scene_foliage_uMdl,
+ .fn_set_uPvmPrev = shader_scene_foliage_uPvmPrev,
+ };
+ world_render_both_stages( world, &pass );
+ glEnable(GL_CULL_FACE);
+}
+
+static void world_render_challenges( world_instance *world,
+ struct world_pass *pass, v3f pos )
+{
+ if( !world ) return;
+ if( skaterift.activity == k_skaterift_replay ) return;
+ if( world != world_current_instance() ) return;
+
+ /* sort lists */
+ f32 radius = 40.0f;
+
+ u32 objective_list[ 32 ],
+ challenge_list[ 16 ];
+
+ v2f objective_uv_offsets[ 32 ];
+
+ u32 objective_count = 0,
+ challenge_count = 0;
+
+ ent_challenge *active_challenge = NULL;
+ int running = 0;
+ if( mdl_entity_id_type( world_static.focused_entity ) == k_ent_challenge ){
+ if( (skaterift.activity == k_skaterift_default) &&
+ world_static.challenge_target ){
+ running = 1;
+ }
+
+ if( !((skaterift.activity != k_skaterift_ent_focus) &&
+ !world_static.challenge_target) ){
+ world_instance *challenge_world = world_current_instance();
+ u32 index = mdl_entity_id_id( world_static.focused_entity );
+ active_challenge = mdl_arritm(&challenge_world->ent_challenge, index);
+ }
+ }
+
+ if( active_challenge ){
+ shader_scene_fxglow_uUvOffset( (v2f){ 8.0f/256.0f, 0.0f } );
+ challenge_list[ challenge_count ++ ] = world_static.focused_entity;
+
+ u32 next = active_challenge->first;
+ while( mdl_entity_id_type(next) == k_ent_objective ){
+ u32 index = mdl_entity_id_id( next );
+ objective_list[ objective_count ++ ] = index;
+
+ ent_objective *objective = mdl_arritm( &world->ent_objective, index );
+ next = objective->id_next;
+ }
+
+ radius = 10000.0f;
+ }
+ else {
+ shader_scene_fxglow_uUvOffset( (v2f){ 0.0f, 0.0f } );
+ bh_iter it;
+ bh_iter_init_range( 0, &it, pos, radius+10.0f );
+ i32 idx;
+ while( bh_next( world->entity_bh, &it, &idx ) ){
+ u32 id = world->entity_list[ idx ],
+ type = mdl_entity_id_type( id ),
+ index = mdl_entity_id_id( id );
+
+ if( type == k_ent_objective ) {
+ if( objective_count < VG_ARRAY_LEN(objective_list) )
+ objective_list[ objective_count ++ ] = index;
+ }
+ else if( type == k_ent_challenge ){
+ if( challenge_count < VG_ARRAY_LEN(challenge_list) )
+ challenge_list[ challenge_count ++ ] = index;
+ }
+ }
+ }
+
+ /* render objectives */
+ glDisable( GL_CULL_FACE );
+ mesh_bind( &world->mesh_no_collide );
+ u32 last_material = 0;
+ for( u32 i=0; i<objective_count; i++ )
+ {
+ u32 index = objective_list[ i ];
+ ent_objective *objective = mdl_arritm( &world->ent_objective, index );
+ if( (objective->flags & k_ent_objective_hidden) &&
+ !active_challenge ) continue;
+
+ f32 scale = 1.0f;
+
+ if( running )
+ {
+ u32 passed = objective->flags & k_ent_objective_passed;
+ f32 target = passed? 0.0f: 1.0f;
+ vg_slewf(&objective->transform.s[0], target, vg.time_frame_delta*4.0f);
+ scale = vg_smoothstepf( objective->transform.s[0] );
+
+ if( (objective == world_static.challenge_target) || passed )
+ shader_scene_fxglow_uUvOffset( (v2f){ 16.0f/256.0f, 0.0f } );
+ else
+ shader_scene_fxglow_uUvOffset( (v2f){ 8.0f/256.0f, 0.0f } );
+ }
+ else
+ {
+ f32 dist = v3_dist( objective->transform.co, pos ) * (1.0f/radius);
+ scale = vg_smoothstepf( vg_clampf( 5.0f-dist*5.0f, 0.0f,1.0f ) );
+ }
+
+ m4x3f mmdl;
+ q_m3x3( objective->transform.q, mmdl );
+ m3x3_scalef( mmdl, scale );
+ v3_copy( objective->transform.co, mmdl[3] );
+ shader_scene_fxglow_uMdl( mmdl );
+
+ for( u32 j=0; j<objective->submesh_count; j++ )
+ {
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ objective->submesh_start + j );
+
+ if( sm->material_id != last_material )
+ {
+ last_material = sm->material_id;
+ pass->fn_bind( world, &world->surfaces[sm->material_id] );
+ }
+ mdl_draw_submesh( sm );
+ }
+ }
+
+ /* render texts */
+ font3d_bind( &gui.font, k_font_shader_world, 0, world, &g_render.cam );
+
+ u32 count = 0;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ )
+ {
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
+ if( challenge->status ) count ++;
+ }
+
+ char buf[32];
+ vg_str str;
+ vg_strnull( &str, buf, sizeof(buf) );
+ vg_strcati32( &str, count );
+ vg_strcatch( &str, '/' );
+ vg_strcati32( &str, mdl_arrcount(&world->ent_challenge) );
+
+ f32 w = font3d_string_width( 1, buf );
+ m4x3f mlocal;
+ m3x3_identity( mlocal );
+ mlocal[3][0] = -w*0.5f;
+ mlocal[3][1] = 0.0f;
+ mlocal[3][2] = 0.0f;
+
+ for( u32 i=0; i<challenge_count; i++ )
+ {
+ u32 index = challenge_list[ i ];
+ ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
+ m4x3f mmdl;
+ mdl_transform_m4x3( &challenge->transform, mmdl );
+ m4x3_mul( mmdl, mlocal, mmdl );
+
+ vg_line_point( challenge->transform.co, 0.25f, VG__RED );
+
+ f32 dist = v3_dist( challenge->transform.co, pos ) * (1.0f/radius),
+ scale = vg_smoothstepf( vg_clampf( 10.0f-dist*10.0f, 0.0f,1.0f ) ),
+ colour = 0.0f;
+
+ if( challenge->status )
+ colour = 1.0f;
+
+ shader_scene_font_uOpacity( scale );
+ shader_scene_font_uColourize( colour );
+ font3d_simple_draw( 1, buf, &g_render.cam, mmdl );
+ }
+}
+
+static void bindpoint_fxglow( world_instance *world,
+ struct world_surface *mat )
+{
+ struct shader_props_standard *props = mat->info.props.compiled;
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
+}
+
+static void render_world_fxglow( world_instance *host_world,
+ world_instance *world, vg_camera *cam,
+ m4x3f world_mmdl,
+ int generic, int challenges, int regions )
+{
+ shader_scene_fxglow_use();
+ shader_scene_fxglow_uUvOffset( (v2f){ 0.0f, 0.0f } );
+ shader_scene_fxglow_uTexMain(1);
+ shader_scene_fxglow_uPv( cam->mtx.pv );
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_fxglow );
+
+ shader_scene_fxglow_uCamera( cam->transform[3] );
+ glDisable(GL_CULL_FACE);
+
+ struct world_pass pass =
+ {
+ .shader = k_shader_fxglow,
+ .cam = cam,
+ .fn_bind = bindpoint_fxglow,
+ .fn_set_mdl = shader_scene_fxglow_uMdl,
+ .fn_set_uPvmPrev = shader_scene_fxglow_uPvmPrev,
+ };
+
+ if( generic )
+ world_render_both_stages( world, &pass );
+
+ if( regions ){
+ mesh_bind( &world->mesh_no_collide );
+
+ u32 last_material = 0;
+ for( u32 i=0; i<mdl_arrcount(&world->ent_region); i ++ ){
+ shader_scene_fxglow_uUvOffset( (v2f){ 0.0f, 0.0f } );
+ ent_region *region = mdl_arritm( &world->ent_region, i );
+
+ f32 offset = 0.0f;
+ if( region->flags & k_ent_route_flag_achieve_gold )
+ offset = 2.0f;
+ else if( region->flags & k_ent_route_flag_achieve_silver )
+ offset = 1.0f;
+
+ shader_scene_fxglow_uUvOffset( (v2f){ (8.0f/256.0f)*offset, 0.0f } );
+
+ m4x3f mmdl;
+ mdl_transform_m4x3( ®ion->transform, mmdl );
+ m4x3_mul( world_mmdl, mmdl, mmdl );
+ shader_scene_fxglow_uMdl( mmdl );
+
+ for( u32 j=0; j<region->submesh_count; j++ )
+ {
+ mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
+ region->submesh_start + j );
+
+ if( sm->material_id != last_material )
+ {
+ last_material = sm->material_id;
+ pass.fn_bind( world, &world->surfaces[sm->material_id] );
+ }
+ mdl_draw_submesh( sm );
+ }
+ }
+ }
+
+ if( challenges )
+ world_render_challenges( world, &pass, cam->pos );
+
+ glEnable(GL_CULL_FACE);
+}
+
+static void bindpoint_terrain( world_instance *world,
+ struct world_surface *mat )
+{
+ struct shader_props_terrain *props = mat->info.props.compiled;
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
+ shader_scene_terrain_uBlendOffset( props->blend_offset );
+ shader_scene_terrain_uSandColour( props->sand_colour );
+}
+
+static void bindpoint_override( world_instance *world,
+ struct world_surface *mat )
+{
+ if( mat->info.flags & k_material_flag_collision )
+ {
+ shader_scene_override_uAlphatest(0);
+ }
+ else
+ {
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_get_texture(world, mat->alpha_tex) );
+ shader_scene_override_uAlphatest(1);
+ }
+}
+
+static void render_terrain( world_instance *world, vg_camera *cam )
+{
+ shader_scene_terrain_use();
+ shader_scene_terrain_uTexGarbage(0);
+ shader_scene_terrain_uTexGradients(1);
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_terrain );
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+
+ shader_scene_terrain_uPv( cam->mtx.pv );
+ shader_scene_terrain_uCamera( cam->transform[3] );
+
+ struct world_pass pass =
+ {
+ .shader = k_shader_terrain_blend,
+ .cam = cam,
+ .fn_bind = bindpoint_terrain,
+ .fn_set_mdl = shader_scene_terrain_uMdl,
+ .fn_set_uPvmPrev = shader_scene_terrain_uPvmPrev,
+ };
+
+ world_render_both_stages( world, &pass );
+}
+
+static void render_sky( world_instance *world, vg_camera *cam )
+{
+ /*
+ * Modify matrix to remove clipping and view translation
+ */
+ m4x4f v,
+ v_prev,
+ pv,
+ pv_prev;
+
+ m4x4_copy( cam->mtx.v, v );
+ m4x4_copy( cam->mtx_prev.v, v_prev );
+
+ for( int i=0; i<3; i++ ){
+ v3_normalize(v[i]);
+ v3_normalize(v_prev[i]);
+ }
+ v3_zero( v[3] );
+ v3_zero( v_prev[3] );
+
+ m4x4_copy( cam->mtx.p, pv );
+ m4x4_copy( cam->mtx_prev.p, pv_prev );
+ m4x4_reset_clipping( pv, 100.0f, 0.1f );
+ m4x4_reset_clipping( pv_prev, 100.0f, 0.1f );
+
+ m4x4_mul( pv, v, pv );
+ m4x4_mul( pv_prev, v_prev, pv_prev );
+
+ m4x3f identity_matrix;
+ m4x3_identity( identity_matrix );
+
+ /*
+ * Draw
+ */
+ if( world->skybox == k_skybox_default ){
+ shader_model_sky_use();
+ shader_model_sky_uMdl( identity_matrix );
+ shader_model_sky_uPv( pv );
+ shader_model_sky_uPvmPrev( pv_prev );
+ shader_model_sky_uTexGarbage(0);
+ world_link_lighting_ub( world, _shader_model_sky.id );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+ }
+ else if( world->skybox == k_skybox_space ){
+ shader_model_sky_space_use();
+
+ shader_model_sky_space_uMdl( identity_matrix );
+ shader_model_sky_space_uPv( pv );
+ shader_model_sky_space_uPvmPrev( pv_prev );
+ shader_model_sky_space_uTexGarbage(0);
+ world_link_lighting_ub( world, _shader_model_sky_space.id );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
+ }
+ else {
+ vg_fatal_error( "Programming error\n" );
+ }
+
+ glDepthMask( GL_FALSE );
+ glDisable( GL_DEPTH_TEST );
+
+ mesh_bind( &world_render.skydome );
+ mesh_draw( &world_render.skydome );
+
+ glEnable( GL_DEPTH_TEST );
+ glDepthMask( GL_TRUE );
+}
+
+void render_world_gates( world_instance *world, vg_camera *cam )
+{
+ float closest = INFINITY;
+ struct ent_gate *gate = NULL;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+ ent_gate *gi = mdl_arritm( &world->ent_gate, i );
+
+ if( !(gi->flags & k_ent_gate_nonlocal) )
+ if( !(gi->flags & k_ent_gate_linked) )
+ continue;
+
+ float dist = v3_dist2( gi->co[0], cam->transform[3] );
+
+ vg_line_point( gi->co[0], 0.25f, VG__BLUE );
+
+ if( dist < closest ){
+ closest = dist;
+ gate = gi;
+ }
+ }
+
+ world->rendering_gate = gate;
+
+ if( gate ){
+ if( gate->flags & k_ent_gate_locked ){
+ world->rendering_gate = NULL;
+ return;
+ }
+
+ if( gate->flags & k_ent_gate_nonlocal ){
+ if( !(gate->flags & k_ent_gate_linked) ||
+ (world_static.load_state != k_world_loader_none) ){
+ world->rendering_gate = NULL;
+ render_gate_unlinked( world, gate, cam );
+ return;
+ }
+
+ world_instance *dest_world = &world_static.instances[ gate->target ];
+ render_gate( world, dest_world, gate, cam );
+ }
+ else
+ render_gate( world, world, gate, cam );
+ }
+}
+
+void world_prerender( world_instance *world )
+{
+ if( mdl_arrcount( &world->ent_light ) ){
+ f32 rate = vg_maxf(0.1f, fabsf(k_day_length)) * vg_signf(k_day_length);
+ world->time += vg.time_frame_delta * (1.0/(rate*60.0));
+ }
+ else{
+ world->time = 0.834;
+ }
+
+ if( world->info.flags & 0x1 ){
+ world->time = world->info.timezone;
+ }
+
+ struct ub_world_lighting *state = &world->ub_lighting;
+
+ state->g_time = world->time;
+ state->g_realtime = vg.time_real;
+ state->g_debug_indices = k_debug_light_indices;
+ state->g_light_preview = k_light_preview;
+ state->g_debug_complexity = k_debug_light_complexity;
+ state->g_time_of_day = vg_fractf( world->time );
+
+ if( vg.quality_profile == k_quality_profile_high )
+ state->g_shadow_samples = 8;
+ else if( vg.quality_profile == k_quality_profile_low )
+ state->g_shadow_samples = 2;
+ else
+ state->g_shadow_samples = 0;
+
+ state->g_day_phase = cosf( state->g_time_of_day * VG_PIf * 2.0f );
+ state->g_sunset_phase= cosf( state->g_time_of_day * VG_PIf * 4.0f + VG_PIf );
+
+ state->g_day_phase = state->g_day_phase * 0.5f + 0.5f;
+ state->g_sunset_phase = powf( state->g_sunset_phase * 0.5f + 0.5f, 6.0f );
+
+ float a = state->g_time_of_day * VG_PIf * 2.0f;
+ state->g_sun_dir[0] = sinf( a );
+ state->g_sun_dir[1] = cosf( a );
+ state->g_sun_dir[2] = 0.2f;
+ v3_normalize( state->g_sun_dir );
+
+ world->probabilities[ k_probability_curve_constant ] = 1.0f;
+ float dp = state->g_day_phase;
+
+ world->probabilities[ k_probability_curve_wildlife_day ] =
+ (dp*dp*0.8f+state->g_sunset_phase)*0.8f;
+ world->probabilities[ k_probability_curve_wildlife_night ] =
+ 1.0f-powf(fabsf((state->g_time_of_day-0.5f)*5.0f),5.0f);
+
+ glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+ glBufferSubData( GL_UNIFORM_BUFFER, 0,
+ sizeof(struct ub_world_lighting), &world->ub_lighting );
+}
+
+static void render_other_entities( world_instance *world, vg_camera *cam )
+{
+ f32 radius = 40.0f;
+ bh_iter it;
+ bh_iter_init_range( 0, &it, cam->pos, radius+10.0f );
+
+ u32 glider_list[4],
+ glider_count = 0,
+ npc_list[4],
+ npc_count = 0;
+
+ i32 idx;
+ while( bh_next( world->entity_bh, &it, &idx ) ){
+ u32 id = world->entity_list[ idx ],
+ type = mdl_entity_id_type( id ),
+ index = mdl_entity_id_id( id );
+
+ if( type == k_ent_glider )
+ {
+ if( glider_count < VG_ARRAY_LEN(glider_list) )
+ glider_list[ glider_count ++ ] = index;
+ }
+ else if( type == k_ent_npc )
+ {
+ if( npc_count < VG_ARRAY_LEN(npc_list) )
+ npc_list[ npc_count ++ ] = index;
+ }
+ }
+
+ shader_model_entity_use();
+ shader_model_entity_uTexMain( 0 );
+ shader_model_entity_uCamera( cam->transform[3] );
+ shader_model_entity_uPv( cam->mtx.pv );
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_entity );
+
+ for( u32 j=0; j<glider_count; j ++ )
+ {
+ ent_glider *glider = mdl_arritm( &world->ent_glider, glider_list[j] );
+
+ if( !(glider->flags & 0x1) )
+ continue;
+
+ m4x3f mdl;
+ mdl_transform_m4x3( &glider->transform, mdl );
+
+ f32 dist = v3_dist( glider->transform.co, cam->pos ) * (1.0f/radius),
+ scale = vg_smoothstepf( vg_clampf( 5.0f-dist*5.0f, 0.0f,1.0f ) );
+ m3x3_scalef( mdl, scale );
+
+ render_glider_model( cam, world, mdl, k_board_shader_entity );
+ }
+
+ for( u32 j=0; j<npc_count; j ++ )
+ {
+ u32 index = npc_list[j];
+ ent_npc *npc = mdl_arritm( &world->ent_npc, npc_list[j] );
+ npc_update( npc );
+ npc_render( npc, world, cam );
+ }
+}
+
+void render_world( world_instance *world, vg_camera *cam,
+ int stenciled, int viewing_from_gate,
+ int with_water, int with_cubemaps )
+{
+ if( stenciled ){
+ glClear( GL_DEPTH_BUFFER_BIT );
+ glStencilFunc( GL_EQUAL, 1, 0xFF );
+ glStencilMask( 0x00 );
+ glEnable( GL_CULL_FACE );
+ glEnable( GL_STENCIL_TEST );
+ }
+ else {
+ glStencilMask( 0xFF );
+ glStencilFunc( GL_ALWAYS, 1, 0xFF );
+ glDisable( GL_STENCIL_TEST );
+ }
+
+ render_sky( world, cam );
+
+ m4x3f identity;
+ m4x3_identity(identity);
+ render_world_routes( world, world, identity, cam, viewing_from_gate, 0 );
+ render_world_standard( world, cam );
+ render_world_cubemapped( world, cam, with_cubemaps );
+
+ render_world_vb( world, cam );
+ render_world_alphatest( world, cam );
+ render_world_foliage( world, cam );
+ render_terrain( world, cam );
+
+ if( !viewing_from_gate ){
+ world_entity_focus_render();
+
+ /* Render SFD's */
+ u32 closest = 0;
+ float min_dist = INFINITY;
+
+ if( mdl_arrcount( &world->ent_route ) ){
+ for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+ float dist = v3_dist2( route->board_transform[3], cam->pos );
+
+ if( dist < min_dist ){
+ min_dist = dist;
+ closest = i;
+ }
+ }
+
+ ent_route *route = mdl_arritm( &world->ent_route, closest );
+ sfd_render( world, cam, route->board_transform );
+ }
+ }
+
+ if( !viewing_from_gate ){
+ f32 greyout = 0.0f;
+ if( mdl_entity_id_type(world_static.focused_entity) == k_ent_challenge )
+ greyout = world_static.focus_strength;
+
+ if( greyout > 0.0f ){
+ glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
+ glEnable(GL_BLEND);
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glBlendEquation(GL_FUNC_ADD);
+
+ shader_blitcolour_use();
+ shader_blitcolour_uColour( (v4f){ 0.5f, 0.5f, 0.5f, greyout*0.56f } );
+ render_fsquad();
+
+ glDisable(GL_BLEND);
+ glEnable(GL_DEPTH_TEST);
+ glDepthMask(GL_TRUE);
+ glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0,
+ GL_COLOR_ATTACHMENT1 } );
+ }
+
+ render_world_fxglow( world, world, cam, NULL, 1, 1, 0 );
+ }
+
+ if( with_water )
+ {
+ render_water_texture( world, cam );
+ vg_framebuffer_bind( g_render.fb_main, k_render_scale );
+ }
+
+ if( stenciled )
+ {
+ glStencilFunc( GL_EQUAL, 1, 0xFF );
+ glStencilMask( 0x00 );
+ glEnable( GL_CULL_FACE );
+ glEnable( GL_STENCIL_TEST );
+ }
+
+ if( with_water )
+ {
+ render_water_surface( world, cam );
+ }
+
+ render_remote_players( world, cam );
+ render_other_entities( world, cam );
+ ent_miniworld_render( world, cam );
+
+ if( stenciled )
+ {
+ glStencilMask( 0xFF );
+ glStencilFunc( GL_ALWAYS, 1, 0xFF );
+ glDisable( GL_STENCIL_TEST );
+ }
+}
+
+
+static void render_world_override_pass( world_instance *world,
+ struct world_pass *pass,
+ m4x3f mmdl, m3x3f mnormal,
+ m4x4f mpvm_prev )
+{
+ for( int i=0; i<world->surface_count; i++ )
+ {
+ struct world_surface *mat = &world->surfaces[i];
+ if( mat->info.flags & k_material_flag_ghosts ) continue;
+
+ mdl_submesh *sm;
+ if( pass->geo_type == k_world_geo_type_solid )
+ sm = &mat->sm_geo;
+ else
+ sm = &mat->sm_no_collide;
+
+ if( !sm->indice_count )
+ continue;
+
+ pass->fn_set_mdl( mmdl );
+ pass->fn_set_uNormalMtx( mnormal );
+ pass->fn_set_uPvmPrev( mpvm_prev );
+ pass->fn_bind( world, mat );
+ mdl_draw_submesh( sm );
+ }
+}
+
+void render_world_override( world_instance *world,
+ world_instance *lighting_source,
+ m4x3f mmdl,
+ vg_camera *cam,
+ ent_spawn *dest_spawn, v4f map_info )
+{
+ struct world_pass pass =
+ {
+ .cam = cam,
+ .fn_bind = bindpoint_override,
+ .fn_set_mdl = shader_scene_override_uMdl,
+ .fn_set_uPvmPrev = shader_scene_override_uPvmPrev,
+ .fn_set_uNormalMtx = shader_scene_override_uNormalMtx,
+ .shader = k_shader_override
+ };
+
+ shader_scene_override_use();
+ shader_scene_override_uTexGarbage(0);
+ shader_scene_override_uTexMain(1);
+ shader_scene_override_uPv( pass.cam->mtx.pv );
+ shader_scene_override_uMapInfo( map_info );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( lighting_source, scene_override );
+ bind_terrain_noise();
+
+ shader_scene_override_uCamera( pass.cam->transform[3] );
+
+ m4x4f mpvm_prev;
+ m4x3_expand( mmdl, mpvm_prev );
+ m4x4_mul( cam->mtx_prev.pv, mpvm_prev, mpvm_prev );
+
+ m3x3f mnormal;
+ m3x3_inv( mmdl, mnormal );
+ m3x3_transpose( mnormal, mnormal );
+ v3_normalize( mnormal[0] );
+ v3_normalize( mnormal[1] );
+ v3_normalize( mnormal[2] );
+
+ v4f uPlayerPos, uSpawnPos;
+ v4_zero( uPlayerPos );
+ v4_zero( uSpawnPos );
+ v3_copy( world->player_co, uPlayerPos );
+
+ if( dest_spawn && (v3_dist2(dest_spawn->transform.co,uPlayerPos) > 0.1f) )
+ v3_copy( dest_spawn->transform.co, uSpawnPos );
+ else
+ v3_add( uPlayerPos, (v3f){0,-1,0}, uSpawnPos );
+
+ uPlayerPos[3] = v3_dist(uPlayerPos,uSpawnPos);
+ uSpawnPos[3] = 1.0f/uPlayerPos[3];
+
+ shader_scene_override_uPlayerPos( uPlayerPos );
+ shader_scene_override_uSpawnPos( uSpawnPos );
+
+
+ glDisable( GL_CULL_FACE );
+ mesh_bind( &world->mesh_geo );
+ pass.geo_type = k_world_geo_type_solid;
+ render_world_override_pass( world, &pass, mmdl, mnormal, mpvm_prev );
+ mesh_bind( &world->mesh_no_collide );
+ pass.geo_type = k_world_geo_type_nonsolid;
+ render_world_override_pass( world, &pass, mmdl, mnormal, mpvm_prev );
+ glEnable( GL_CULL_FACE );
+
+ render_world_fxglow( world, world, cam, mmdl, 0, 0, 1 );
+}
+
+static void render_cubemap_side( world_instance *world, ent_cubemap *cm,
+ u32 side ){
+ vg_camera cam;
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + side, cm->texture_id, 0 );
+ glClear( GL_DEPTH_BUFFER_BIT );
+
+ v3f forward[6] = {
+ { -1.0f, 0.0f, 0.0f },
+ { 1.0f, 0.0f, 0.0f },
+ { 0.0f, -1.0f, 0.0f },
+ { 0.0f, 1.0f, 0.0f },
+ { 0.0f, 0.0f, -1.0f },
+ { 0.0f, 0.0f, 1.0f }
+ };
+ v3f up[6] = {
+ { 0.0f, -1.0f, 0.0f },
+ { 0.0f, -1.0f, 0.0f },
+ { 0.0f, 0.0f, 1.0f },
+ { 0.0f, 0.0f, -1.0f },
+ { 0.0f, -1.0f, 0.0f },
+ { 0.0f, -1.0f, 0.0f }
+ };
+
+ v3_zero( cam.angles );
+ v3_copy( cm->co, cam.pos );
+
+ v3_copy( forward[side], cam.transform[2] );
+ v3_copy( up[side], cam.transform[1] );
+ v3_cross( up[side], forward[side], cam.transform[0] );
+ v3_copy( cm->co, cam.transform[3] );
+ m4x3_invert_affine( cam.transform, cam.transform_inverse );
+
+ vg_camera_update_view( &cam );
+
+ cam.nearz = 0.1f;
+ cam.farz = 1000.0f;
+ cam.fov = 90.0f;
+ m4x4_copy( cam.mtx.p, cam.mtx_prev.p );
+ m4x4_projection( cam.mtx.p, cam.fov, 1.0f, cam.nearz, cam.farz );
+ vg_camera_finalize( &cam );
+ vg_camera_finalize( &cam );
+
+ render_world( world, &cam, 0, 1, 1, 0 );
+}
+
+void render_world_cubemaps( world_instance *world )
+{
+ if( world->cubemap_cooldown )
+ world->cubemap_cooldown --;
+ else{
+ world->cubemap_cooldown = 60;
+
+ glViewport( 0, 0, WORLD_CUBEMAP_RES, WORLD_CUBEMAP_RES );
+ for( u32 i=0; i<mdl_arrcount( &world->ent_cubemap ); i++ ){
+ ent_cubemap *cm = mdl_arritm( &world->ent_cubemap, i );
+ glBindFramebuffer( GL_FRAMEBUFFER, cm->framebuffer_id );
+
+ world->cubemap_side ++;
+ if( world->cubemap_side >= 6 )
+ world->cubemap_side = 0;
+
+ render_cubemap_side( world, cm, world->cubemap_side );
+ }
+ }
+}
+
+/*
+ * Geo shaders
+ * ---------------------------------------------
+ */
+
+void render_world_depth( world_instance *world, vg_camera *cam )
+{
+ m4x3f identity_matrix;
+ m4x3_identity( identity_matrix );
+
+ shader_scene_depth_use();
+ shader_scene_depth_uCamera( cam->transform[3] );
+ shader_scene_depth_uPv( cam->mtx.pv );
+ shader_scene_depth_uPvmPrev( cam->mtx_prev.pv );
+ shader_scene_depth_uMdl( identity_matrix );
+ world_link_lighting_ub( world, _shader_scene_depth.id );
+
+ mesh_bind( &world->mesh_geo );
+ mesh_draw( &world->mesh_geo );
+}
+
+void render_world_position( world_instance *world, vg_camera *cam )
+{
+ m4x3f identity_matrix;
+ m4x3_identity( identity_matrix );
+
+ shader_scene_position_use();
+ shader_scene_position_uCamera( cam->transform[3] );
+ shader_scene_position_uPv( cam->mtx.pv );
+ shader_scene_position_uPvmPrev( cam->mtx_prev.pv );
+ shader_scene_position_uMdl( identity_matrix );
+ world_link_lighting_ub( world, _shader_scene_position.id );
+
+ mesh_bind( &world->mesh_geo );
+ mesh_draw( &world->mesh_geo );
+}
+
+struct ui_enum_opt skybox_setting_options[] = {
+ { 0, "g_daysky_colour" },
+ { 1, "g_nightsky_colour" },
+ { 2, "g_sunset_colour" },
+ { 3, "g_ambient_colour" },
+ { 4, "g_sun_colour" },
+};
+
+static f32 *skybox_prop_location( world_instance *world, i32 index ){
+ switch( index ){
+ case 0: return world->ub_lighting.g_daysky_colour; break;
+ case 1: return world->ub_lighting.g_nightsky_colour; break;
+ case 2: return world->ub_lighting.g_sunset_colour; break;
+ case 3: return world->ub_lighting.g_ambient_colour; break;
+ case 4: return world->ub_lighting.g_sun_colour; break;
+ default: return NULL;
+ }
+}
+
+void imgui_world_light_edit( ui_context *ctx, world_instance *world )
+{
+ ui_rect panel = { vg.window_x-400, 0, 400, vg.window_y };
+ ui_fill( ctx, panel, ui_colour( ctx, k_ui_bg+1 ) );
+ ui_outline( ctx, panel, 1, ui_colour( ctx, k_ui_bg+7 ), 0 );
+ ui_rect_pad( panel, (ui_px[2]){ 8, 8 } );
+ ui_capture_mouse(ctx, 1);
+
+ static i32 option_to_edit = 0;
+ ui_enum( ctx, panel, "option", skybox_setting_options, 5, &option_to_edit );
+ ui_colourpicker( ctx, panel, "colour",
+ skybox_prop_location( world, option_to_edit ) );
+
+ if( ui_button( ctx, panel, "save tweaker file ('/tmp/tweaker.txt')\n" ) == 1 )
+ {
+ FILE *fp = fopen( "/tmp/tweaker.txt", "w" );
+
+ for( i32 i=0; i<5; i ++ ){
+ struct ui_enum_opt *opt = &skybox_setting_options[i];
+ f32 *val = skybox_prop_location( world, i );
+ fprintf( fp, "%s = {%.3ff, %.3ff, %.3ff, %.3ff},\n",
+ opt->alias, val[0], val[1], val[2], val[3] );
+ }
+ fclose( fp );
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+
+#define WORLD_CUBEMAP_RES 32
+
+#include "vg/vg_camera.h"
+#include "world.h"
+#include "shaders/scene_standard.h"
+#include "shaders/scene_standard_alphatest.h"
+#include "shaders/scene_foliage.h"
+#include "shaders/scene_override.h"
+#include "shaders/scene_cubemapped.h"
+#include "shaders/scene_vertex_blend.h"
+#include "shaders/scene_terrain.h"
+#include "shaders/scene_fxglow.h"
+#include "shaders/scene_depth.h"
+#include "shaders/scene_position.h"
+#include "shaders/scene_font.h"
+#include "shaders/model_sky.h"
+#include "shaders/model_sky_space.h"
+
+static const float k_world_light_cube_size = 8.0f;
+
+struct world_render
+{
+ GLuint tex_terrain_noise;
+
+ /* rendering */
+ glmesh skydome;
+
+ double sky_time, sky_rate, sky_target_rate;
+
+ v3f render_gate_pos;
+ struct timer_text{
+ char text[8];
+ m4x3f transform;
+ ent_gate *gate;
+ ent_route *route;
+ }
+ timer_texts[4];
+ u32 timer_text_count;
+
+ struct text_particle{
+ rigidbody rb;
+ m4x3f mlocal;
+ ent_glyph *glyph;
+ v4f colour;
+ m4x3f mdl;
+ f32 radius;
+ }
+ text_particles[6*4];
+ u32 text_particle_count;
+}
+extern world_render;
+
+void world_render_init(void);
+
+void world_prerender( world_instance *world );
+void world_link_lighting_ub( world_instance *world, GLuint shader );
+void world_bind_position_texture( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+void world_bind_light_array( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+void world_bind_light_index( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+void render_world_position( world_instance *world, vg_camera *cam );
+void render_world_depth( world_instance *world, vg_camera *cam );
+void render_world( world_instance *world, vg_camera *cam,
+ int stenciled, int viewing_from_gate,
+ int with_water, int with_cubemaps );
+void render_world_cubemaps( world_instance *world );
+void bind_terrain_noise(void);
+void render_world_override( world_instance *world,
+ world_instance *lighting_source,
+ m4x3f mmdl,
+ vg_camera *cam,
+ ent_spawn *dest_spawn, v4f map_info );
+void render_world_gates( world_instance *world, vg_camera *cam );
+void imgui_world_light_edit( ui_context *ctx, world_instance *world );
+
+#define WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( WORLD, SHADER ) \
+ world_link_lighting_ub( WORLD, _shader_##SHADER.id ); \
+ world_bind_position_texture( WORLD, _shader_##SHADER.id, \
+ _uniform_##SHADER##_g_world_depth, 2 ); \
+ world_bind_light_array( WORLD, _shader_##SHADER.id, \
+ _uniform_##SHADER##_uLightsArray, 3 ); \
+ world_bind_light_index( WORLD, _shader_##SHADER.id, \
+ _uniform_##SHADER##_uLightsIndex, 4 );
+
--- /dev/null
+#pragma once
+
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved
+ *
+ * World routes
+ */
+
+#include <time.h>
+#include "entity.h"
+#include "world_routes.h"
+#include "world_gate.h"
+#include "world_load.h"
+#include "network.h"
+
+#include "font.h"
+#include "gui.h"
+#include "steam.h"
+#include "network_msg.h"
+#include "network_common.h"
+
+#include "shaders/scene_route.h"
+#include "shaders/routeui.h"
+#include "ent_region.h"
+#include "scene_rigidbody.h"
+
+void world_routes_clear( world_instance *world )
+{
+ for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+ route->active_checkpoint = 0xffff;
+ }
+
+ for( u32 i=0; i<mdl_arrcount( &world->ent_gate ); i++ ){
+ ent_gate *rg = mdl_arritm( &world->ent_gate, i );
+ rg->timing_version = 0;
+ rg->timing_time = 0.0;
+ }
+
+ world_static.current_run_version += 4;
+ world_static.last_use = 0.0;
+}
+
+static void world_routes_time_lap( world_instance *world, ent_route *route ){
+ vg_info( "------- time lap %s -------\n",
+ mdl_pstr(&world->meta,route->pstr_name) );
+
+ double start_time = 0.0;
+ u32 last_version=0;
+ f64 last_time = 0.0;
+ ent_checkpoint *last_cp = NULL;
+
+ u32 valid_sections=0;
+ int clean = !localplayer.rewinded_since_last_gate;
+
+ for( u32 i=0; i<route->checkpoints_count; i++ ){
+ u32 cpid = (i+route->active_checkpoint) % route->checkpoints_count;
+ cpid += route->checkpoints_start;
+
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, cpid );
+ ent_gate *rg = mdl_arritm( &world->ent_gate, cp->gate_index );
+ rg = mdl_arritm( &world->ent_gate, rg->target );
+
+ if( i == 1 ){
+ route->timing_base = rg->timing_time;
+ }
+
+ if( i == 0 )
+ start_time = rg->timing_time;
+ else{
+ if( last_version+1 == rg->timing_version ) valid_sections ++;
+ else valid_sections = 0;
+ }
+
+ vg_info( "%u %f [%s]\n", rg->timing_version, rg->timing_time,
+ i? ((rg->flags & k_ent_gate_clean_pass)? "CLEAN": " "):
+ " N/A ");
+
+ if( !(rg->flags & k_ent_gate_clean_pass) )
+ clean = 0;
+
+ last_version = rg->timing_version;
+ last_time = rg->timing_time;
+ last_cp = cp;
+ }
+
+ if( world_static.current_run_version == last_version+1 ){
+ valid_sections ++;
+
+ if( route->checkpoints_count == 1 ){
+ route->timing_base = world_static.time;
+ }
+
+ f32 section = world_static.time - last_time;
+ if( (section < last_cp->best_time) || (last_cp->best_time == 0.0f) ){
+ last_cp->best_time = section;
+ }
+ }
+ else valid_sections = 0;
+
+ vg_info( "%u %f [%s]\n",
+ world_static.current_run_version, world_static.time,
+ !localplayer.rewinded_since_last_gate? "CLEAN": " " );
+
+ if( valid_sections==route->checkpoints_count ){
+ f64 lap_time = world_static.time - start_time;
+
+ if( (route->best_laptime == 0.0) || (lap_time < route->best_laptime) ){
+ route->best_laptime = lap_time;
+ }
+
+ route->flags |= k_ent_route_flag_achieve_silver;
+ if( clean ) route->flags |= k_ent_route_flag_achieve_gold;
+ ent_region_re_eval( world );
+
+ /* for steam achievements. */
+ if( route->anon.official_track_id != 0xffffffff ){
+ struct track_info *ti = &track_infos[ route->anon.official_track_id ];
+ if( ti->achievement_id ){
+ steam_set_achievement( ti->achievement_id );
+ steam_store_achievements();
+ }
+ }
+
+ addon_alias *alias =
+ &world_static.instance_addons[ world_static.active_instance ]->alias;
+
+ char mod_uid[ ADDON_UID_MAX ];
+ addon_alias_uid( alias, mod_uid );
+ network_publish_laptime( mod_uid,
+ mdl_pstr( &world->meta, route->pstr_name ),
+ lap_time );
+ }
+
+ route->valid_checkpoints = valid_sections+1;
+
+ vg_info( "valid sections: %u\n", valid_sections );
+ vg_info( "----------------------------\n" );
+
+ route->ui_residual = 1.0f;
+ route->ui_residual_block_w = route->ui_first_block_width;
+}
+
+/*
+ * When going through a gate this is called for bookkeeping purposes
+ */
+void world_routes_activate_entry_gate( world_instance *world, ent_gate *rg )
+{
+ world_static.last_use = world_static.time;
+ ent_gate *dest = mdl_arritm( &world->ent_gate, rg->target );
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ u32 active_prev = route->active_checkpoint;
+ route->active_checkpoint = 0xffff;
+
+ for( u32 j=0; j<4; j++ ){
+ if( dest->routes[j] == i ){
+ for( u32 k=0; k<route->checkpoints_count; k++ ){
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
+ route->checkpoints_start+k );
+
+ ent_gate *gk = mdl_arritm( &world->ent_gate, cp->gate_index );
+ gk = mdl_arritm( &world->ent_gate, gk->target );
+ if( gk == dest ){
+ route->active_checkpoint = k;
+ world_routes_time_lap( world, route );
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ dest->timing_version = world_static.current_run_version;
+ dest->timing_time = world_static.time;
+
+ if( localplayer.rewinded_since_last_gate ){
+ localplayer.rewinded_since_last_gate = 0;
+ dest->flags &= ~k_ent_gate_clean_pass;
+ }
+ else
+ dest->flags |= k_ent_gate_clean_pass;
+
+ world_static.current_run_version ++;
+}
+
+/* draw lines along the paths */
+static void world_routes_debug( world_instance *world )
+{
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
+ ent_route_node *rn = mdl_arritm(&world->ent_route_node,i);
+ vg_line_point( rn->co, 0.25f, VG__WHITE );
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm(&world->ent_route, i);
+
+ u32 colours[] = { 0xfff58142, 0xff42cbf5, 0xff42f56c, 0xfff542b3,
+ 0xff5442f5 };
+
+ u32 cc = 0xffcccccc;
+ if( route->active_checkpoint != 0xffff ){
+ cc = colours[i%VG_ARRAY_LEN(colours)];
+ }
+
+ for( int i=0; i<route->checkpoints_count; i++ ){
+ int i0 = route->checkpoints_start+i,
+ i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
+
+ ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
+ *c1 = mdl_arritm(&world->ent_checkpoint, i1);
+
+ ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
+ ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index );
+
+ v3f p0, p1;
+ v3_copy( start_gate->co[1], p0 );
+
+ for( int j=0; j<c0->path_count; j ++ ){
+ ent_path_index *index = mdl_arritm( &world->ent_path_index,
+ c0->path_start+j );
+
+ ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+ index->index );
+
+ v3_copy( rn->co, p1 );
+ vg_line( p0, p1, cc );
+ v3_copy( p1, p0 );
+ }
+
+ v3_copy( end_gate->co[0], p1 );
+ vg_line( p0, p1, cc );
+ }
+ }
+}
+
+
+static
+void world_routes_place_curve( world_instance *world, ent_route *route,
+ v4f h[3], v3f n0, v3f n2, scene_context *scene )
+{
+ float t;
+ v3f p, pd;
+ int last_valid=0;
+
+ float total_length = 0.0f,
+ travel_length = 0.0;
+
+ v3f last;
+ v3_copy( h[0], last );
+ for( int it=0; it<128; it ++ ){
+ t = (float)(it+1) * (1.0f/128.0f);
+ eval_bezier3( h[0], h[1], h[2], t, p );
+ total_length += v3_dist( p, last );
+ v3_copy( p, last );
+ }
+
+ float patch_size = 4.0f,
+ patch_count = ceilf( total_length / patch_size );
+
+ t = 0.0f;
+ v3_copy( h[0], last );
+
+ for( int it=0; it<128; it ++ ){
+ float const k_sample_dist = 0.0025f,
+ k_line_width = 1.5f;
+
+ eval_bezier3( h[0], h[1], h[2], t, p );
+ eval_bezier3( h[0], h[1], h[2], t+k_sample_dist, pd );
+
+ travel_length += v3_dist( p, last );
+
+ float mod = k_sample_dist / v3_dist( p, pd );
+
+ v3f v0,up, right;
+
+ v3_muls( n0, -(1.0f-t), up );
+ v3_muladds( up, n2, -t, up );
+ v3_normalize( up );
+
+ v3_sub( pd,p,v0 );
+ v3_cross( up, v0, right );
+ v3_normalize( right );
+
+ float cur_x = (1.0f-t)*h[0][3] + t*h[2][3];
+
+ v3f sc, sa, sb, down;
+ v3_muladds( p, right, cur_x * k_line_width, sc );
+ v3_muladds( sc, up, 1.5f, sc );
+ v3_muladds( sc, right, k_line_width*0.95f, sa );
+ v3_muladds( sc, right, 0.0f, sb );
+ v3_muls( up, -1.0f, down );
+
+ ray_hit ha, hb;
+ ha.dist = 8.0f;
+ hb.dist = 8.0f;
+
+ int resa = ray_world( world, sa, down, &ha, k_material_flag_ghosts ),
+ resb = ray_world( world, sb, down, &hb, k_material_flag_ghosts );
+
+ if( resa && resb ){
+ struct world_surface *surfa = ray_hit_surface( world, &ha ),
+ *surfb = ray_hit_surface( world, &hb );
+
+ if( (surfa->info.flags & k_material_flag_skate_target) &&
+ (surfb->info.flags & k_material_flag_skate_target) )
+ {
+ scene_vert va, vb;
+
+ float gap = vg_fractf(cur_x*0.5f)*0.02f;
+
+ v3_muladds( ha.pos, up, 0.06f+gap, va.co );
+ v3_muladds( hb.pos, up, 0.06f+gap, vb.co );
+
+ scene_vert_pack_norm( &va, up, 0.0f );
+ scene_vert_pack_norm( &vb, up, 0.0f );
+
+ float t1 = (travel_length / total_length) * patch_count;
+ va.uv[0] = t1;
+ va.uv[1] = 0.0f;
+ vb.uv[0] = t1;
+ vb.uv[1] = 1.0f;
+
+ scene_push_vert( scene, &va );
+ scene_push_vert( scene, &vb );
+
+ if( last_valid ){
+ /* Connect them with triangles */
+ scene_push_tri( scene, (u32[3]){
+ last_valid+0-2, last_valid+1-2, last_valid+2-2} );
+ scene_push_tri( scene, (u32[3]){
+ last_valid+1-2, last_valid+3-2, last_valid+2-2} );
+ }
+
+ last_valid = scene->vertex_count;
+ }
+ else
+ last_valid = 0;
+ }
+ else
+ last_valid = 0;
+
+ if( t == 1.0f )
+ return;
+
+ t += 1.0f*mod;
+ if( t > 1.0f )
+ t = 1.0f;
+
+ v3_copy( p, last );
+ }
+}
+
+static void world_routes_gen_meshes( world_instance *world, u32 route_id,
+ scene_context *sc )
+{
+ ent_route *route = mdl_arritm( &world->ent_route, route_id );
+ u8 colour[4];
+ colour[0] = route->colour[0] * 255.0f;
+ colour[1] = route->colour[1] * 255.0f;
+ colour[2] = route->colour[2] * 255.0f;
+ colour[3] = route->colour[3] * 255.0f;
+
+ u32 last_valid = 0;
+
+ for( int i=0; i<route->checkpoints_count; i++ ){
+ int i0 = route->checkpoints_start+i,
+ i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
+
+ ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
+ *c1 = mdl_arritm(&world->ent_checkpoint, i1);
+
+ ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
+ start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
+
+ ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index ),
+ *collector = mdl_arritm( &world->ent_gate, end_gate->target );
+
+ v4f p[3];
+
+ v3_add( (v3f){0.0f,0.1f,0.0f}, start_gate->co[0], p[0] );
+ p[0][3] = start_gate->ref_count;
+ p[0][3] -= (float)start_gate->route_count * 0.5f;
+ start_gate->ref_count ++;
+
+ if( !c0->path_count )
+ continue;
+
+ /* this is so that we get nice flow through the gates */
+ v3f temp_alignments[2];
+ ent_gate *both[] = { start_gate, end_gate };
+
+ for( int j=0; j<2; j++ ){
+ int pi = c0->path_start + ((j==1)? c0->path_count-1: 0);
+
+ ent_path_index *index = mdl_arritm( &world->ent_path_index, pi );
+ ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+ index->index );
+ v3f v0;
+ v3_sub( rn->co, both[j]->co[0], v0 );
+ float d = v3_dot( v0, both[j]->to_world[2] );
+
+ v3_muladds( both[j]->co[0], both[j]->to_world[2], d,
+ temp_alignments[j] );
+ v3_add( (v3f){0.0f,0.1f,0.0f}, temp_alignments[j], temp_alignments[j]);
+ }
+
+
+ for( int j=0; j<c0->path_count; j ++ ){
+ ent_path_index *index = mdl_arritm( &world->ent_path_index,
+ c0->path_start+j );
+ ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+ index->index );
+ if( j==0 || j==c0->path_count-1 )
+ if( j == 0 )
+ v3_copy( temp_alignments[0], p[1] );
+ else
+ v3_copy( temp_alignments[1], p[1] );
+ else
+ v3_copy( rn->co, p[1] );
+
+ p[1][3] = rn->ref_count;
+ p[1][3] -= (float)rn->ref_total * 0.5f;
+ rn->ref_count ++;
+
+ if( j+1 < c0->path_count ){
+ index = mdl_arritm( &world->ent_path_index,
+ c0->path_start+j+1 );
+ rn = mdl_arritm( &world->ent_route_node, index->index );
+
+ if( j+1 == c0->path_count-1 )
+ v3_lerp( p[1], temp_alignments[1], 0.5f, p[2] );
+ else
+ v3_lerp( p[1], rn->co, 0.5f, p[2] );
+
+ p[2][3] = rn->ref_count;
+ p[2][3] -= (float)rn->ref_total * 0.5f;
+ }
+ else{
+ v3_copy( end_gate->co[0], p[2] );
+ v3_add( (v3f){0.0f,0.1f,0.0f}, p[2], p[2] );
+ p[2][3] = collector->ref_count;
+
+ if( i == route->checkpoints_count-1)
+ p[2][3] -= 1.0f;
+
+ p[2][3] -= (float)collector->route_count * 0.5f;
+ //collector->ref_count ++;
+ }
+
+ /* p0,p1,p2 bezier patch is complete
+ * --------------------------------------*/
+ v3f surf0, surf2, n0, n2;
+
+ if( bh_closest_point( world->geo_bh, p[0], surf0, 5.0f ) == -1 )
+ v3_add( (v3f){0.0f,-0.1f,0.0f}, p[0], surf0 );
+
+ if( bh_closest_point( world->geo_bh, p[2], surf2, 5.0f ) == -1 )
+ v3_add( (v3f){0.0f,-0.1f,0.0f}, p[2], surf2 );
+
+ v3_sub( surf0, p[0], n0 );
+ v3_sub( surf2, p[2], n2 );
+ v3_normalize( n0 );
+ v3_normalize( n2 );
+
+ world_routes_place_curve( world, route, p, n0, n2, sc );
+
+ /* --- */
+ v4_copy( p[2], p[0] );
+ }
+ }
+
+ scene_copy_slice( sc, &route->sm );
+}
+
+struct world_surface *world_tri_index_surface( world_instance *world,
+ u32 index );
+
+/*
+ * Create the strips of colour that run through the world along course paths
+ */
+void world_gen_routes_generate( u32 instance_id )
+{
+ world_instance *world = &world_static.instances[ instance_id ];
+ vg_info( "Generating route meshes\n" );
+ vg_async_stall();
+
+ vg_async_item *call_scene = scene_alloc_async( &world->scene_lines,
+ &world->mesh_route_lines,
+ 200000, 300000 );
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+ gate->ref_count = 0;
+ gate->route_count = 0;
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
+ ent_route_node *rn = mdl_arritm( &world->ent_route_node, i );
+ rn->ref_count = 0;
+ rn->ref_total = 0;
+ }
+
+ for( u32 k=0; k<mdl_arrcount(&world->ent_route); k++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, k );
+
+ for( int i=0; i<route->checkpoints_count; i++ ){
+ int i0 = route->checkpoints_start+i,
+ i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
+
+ ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
+ *c1 = mdl_arritm(&world->ent_checkpoint, i1);
+
+ ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
+ start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
+ start_gate->route_count ++;
+
+ if( !c0->path_count )
+ continue;
+
+ for( int j=0; j<c0->path_count; j ++ ){
+ ent_path_index *index = mdl_arritm( &world->ent_path_index,
+ c0->path_start+j );
+ ent_route_node *rn = mdl_arritm( &world->ent_route_node,
+ index->index );
+ rn->ref_total ++;
+ }
+ }
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ world_routes_gen_meshes( world, i, &world->scene_lines );
+ }
+
+ vg_async_dispatch( call_scene, async_scene_upload );
+ world_routes_clear( world );
+}
+
+/* load all routes from model header */
+void world_gen_routes_ent_init( world_instance *world )
+{
+ vg_info( "Initializing routes\n" );
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+ for( u32 j=0; j<4; j++ ){
+ gate->routes[j] = 0xffff;
+ }
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm(&world->ent_route,i);
+ mdl_transform_m4x3( &route->anon.transform, route->board_transform );
+
+ route->flags = 0x00;
+ route->best_laptime = 0.0;
+ route->ui_stopper = 0.0f;
+ route->ui_residual = 0.0f;
+
+ if( mdl_arrcount(&world->ent_region) )
+ route->flags |= k_ent_route_flag_out_of_zone;
+
+ route->anon.official_track_id = 0xffffffff;
+ for( u32 j=0; j<VG_ARRAY_LEN(track_infos); j ++ ){
+ if( !strcmp(track_infos[j].name,
+ mdl_pstr(&world->meta,route->pstr_name))){
+ route->anon.official_track_id = j;
+ }
+ }
+
+ for( u32 j=0; j<route->checkpoints_count; j++ ){
+ u32 id = route->checkpoints_start + j;
+ ent_checkpoint *cp = mdl_arritm(&world->ent_checkpoint,id);
+
+ ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
+
+ for( u32 k=0; k<4; k++ ){
+ if( gate->routes[k] == 0xffff ){
+ gate->routes[k] = i;
+ break;
+ }
+ }
+
+ if( (gate->flags & k_ent_gate_linked) &
+ !(gate->flags & k_ent_gate_nonlocal) ){
+ gate = mdl_arritm(&world->ent_gate, gate->target );
+
+ for( u32 k=0; k<4; k++ ){
+ if( gate->routes[k] == i ){
+ vg_error( "already assigned route to gate\n" );
+ break;
+ }
+ if( gate->routes[k] == 0xffff ){
+ gate->routes[k] = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, i );
+ }
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_checkpoint); i++ ){
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, i );
+ cp->best_time = 0.0;
+ }
+
+ world_routes_clear( world );
+}
+
+void world_routes_recv_scoreboard( world_instance *world,
+ vg_msg *body, u32 route_id,
+ enum request_status status )
+{
+ if( route_id >= mdl_arrcount( &world->ent_route ) ){
+ vg_error( "Scoreboard route_id out of range (%u)\n", route_id );
+ return;
+ }
+
+ struct leaderboard_cache *board = &world->leaderboard_cache[ route_id ];
+ board->status = status;
+
+ if( body == NULL ){
+ board->data_len = 0;
+ return;
+ }
+
+ if( body->max > NETWORK_REQUEST_MAX ){
+ vg_error( "Scoreboard leaderboard too big (%u>%u)\n", body->max,
+ NETWORK_REQUEST_MAX );
+ return;
+ }
+
+ memcpy( board->data, body->buf, body->max );
+ board->data_len = body->max;
+}
+
+/*
+ * -----------------------------------------------------------------------------
+ * Events
+ * -----------------------------------------------------------------------------
+ */
+
+void world_routes_init(void)
+{
+ world_static.current_run_version = 200;
+ world_static.time = 300.0;
+ world_static.last_use = 0.0;
+}
+
+void world_routes_update( world_instance *world )
+{
+ world_static.time += vg.time_delta;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ int target = route->active_checkpoint == 0xffff? 0: 1;
+ route->factive = vg_lerpf( route->factive, target,
+ 0.6f*vg.time_frame_delta );
+ }
+
+ for( u32 i=0; i<world_render.text_particle_count; i++ ){
+ struct text_particle *particle = &world_render.text_particles[i];
+ //rb_object_debug( &particle->obj, VG__RED );
+ }
+}
+
+void world_routes_fixedupdate( world_instance *world )
+{
+ rb_solver_reset();
+
+ rigidbody _null = {0};
+ _null.inv_mass = 0.0f;
+ m3x3_zero( _null.iI );
+
+ for( u32 i=0; i<world_render.text_particle_count; i++ ){
+ struct text_particle *particle = &world_render.text_particles[i];
+
+ if( rb_global_has_space() ){
+ rb_ct *buf = rb_global_buffer();
+
+ int l = rb_sphere__scene( particle->rb.to_world,
+ particle->radius,
+ NULL, world->geo_bh, buf,
+ k_material_flag_ghosts );
+
+ for( int j=0; j<l; j++ ){
+ buf[j].rba = &particle->rb;
+ buf[j].rbb = &_null;
+ }
+
+ rb_contact_count += l;
+ }
+ }
+
+ rb_presolve_contacts( rb_contact_buffer,
+ vg.time_fixed_delta, rb_contact_count );
+
+ for( int i=0; i<rb_contact_count; i++ ){
+ rb_contact_restitution( rb_contact_buffer+i, vg_randf64(&vg.rand) );
+ }
+
+ for( int i=0; i<6; i++ ){
+ rb_solve_contacts( rb_contact_buffer, rb_contact_count );
+ }
+
+ for( u32 i=0; i<world_render.text_particle_count; i++ ){
+ struct text_particle *particle = &world_render.text_particles[i];
+ rb_iter( &particle->rb );
+ }
+
+ for( u32 i=0; i<world_render.text_particle_count; i++ ){
+ struct text_particle *particle = &world_render.text_particles[i];
+ rb_update_matrices( &particle->rb );
+ }
+}
+
+void bind_terrain_noise(void);
+void world_bind_light_array( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+void world_bind_light_index( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+
+void world_routes_update_timer_texts( world_instance *world )
+{
+ world_render.timer_text_count = 0;
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ if( route->active_checkpoint != 0xffff ){
+ u32 next = route->active_checkpoint+1;
+ next = next % route->checkpoints_count;
+ next += route->checkpoints_start;
+
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
+ ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
+ ent_gate *dest = mdl_arritm( &world->ent_gate, gate->target );
+
+ u32 j=0;
+ for( ; j<4; j++ ){
+ if( dest->routes[j] == i ){
+ break;
+ }
+ }
+
+ float h0 = 0.8f,
+ h1 = 1.2f,
+ depth = 0.4f,
+ size = 0.4f;
+
+ struct timer_text *text =
+ &world_render.timer_texts[ world_render.timer_text_count ++ ];
+
+ text->gate = gate;
+ text->route = route;
+
+ vg_str str;
+ vg_strnull( &str, text->text, sizeof(text->text) );
+
+ if( route->valid_checkpoints >= route->checkpoints_count )
+ {
+ double lap_time = world_static.time - route->timing_base,
+ time_centiseconds = lap_time * 100.0;
+
+ if( time_centiseconds > (float)0xfffe ) time_centiseconds = 0.0;
+
+ u16 centiseconds = time_centiseconds,
+ seconds = centiseconds / 100,
+ minutes = seconds / 60;
+
+ centiseconds %= 100;
+ seconds %= 60;
+ minutes %= 60;
+
+ if( minutes > 9 )
+ minutes = 9;
+
+ if( minutes )
+ {
+ vg_strcati32r( &str, minutes, 1, ' ' );
+ vg_strcatch( &str, ':' );
+ }
+
+ if( seconds >= 10 || minutes )
+ {
+ vg_strcati32r( &str, seconds, 2, '0' );
+ }
+ else
+ {
+ vg_strcati32r( &str, seconds, 1, '0' );
+ }
+
+ vg_strcatch( &str, '.' );
+ vg_strcati32r( &str, centiseconds, 1, '0' );
+ }
+ else
+ {
+ vg_strcati32r( &str, route->valid_checkpoints, 1, ' ' );
+ vg_strcatch( &str, '/' );
+ vg_strcati32r( &str, route->checkpoints_count + 1, 1, ' ' );
+ }
+
+ gui_font3d.font = &gui.font;
+ float align_r = font3d_string_width( 0, text->text );
+ align_r *= size;
+
+ v3f positions[] = {
+ { -0.92f, h0, depth },
+ { 0.92f - align_r, h0, depth },
+ { -0.92f, h1, depth },
+ { 0.92f - align_r, h1, depth },
+ };
+
+ if( dest->route_count == 1 ){
+ positions[0][0] = -align_r*0.5f;
+ positions[0][1] = h1;
+ }
+
+ m4x3f mmdl;
+ ent_gate_get_mdl_mtx( gate, mmdl );
+
+ m3x3_copy( mmdl, text->transform );
+ float ratio = v3_length(text->transform[0]) /
+ v3_length(text->transform[1]);
+
+ m3x3_scale( text->transform, (v3f){ size, size*ratio, 0.1f } );
+ m4x3_mulv( mmdl, positions[j], text->transform[3] );
+ }
+ }
+}
+
+void world_routes_fracture( world_instance *world, ent_gate *gate,
+ v3f imp_co, v3f imp_v )
+{
+ world_render.text_particle_count = 0;
+
+ for( u32 i=0; i<world_render.timer_text_count; i++ ){
+ struct timer_text *text = &world_render.timer_texts[i];
+
+ if( text->gate != gate ) continue;
+
+ m4x3f transform;
+ m4x3_mul( gate->transport, text->transform, transform );
+
+ v3f co, s;
+ v4f q;
+ m4x3_decompose( transform, co, q, s );
+
+ v3f offset;
+ v3_zero( offset );
+
+ v4f colour;
+ float brightness = 0.3f + world->ub_lighting.g_day_phase;
+ v3_muls( text->route->colour, brightness, colour );
+ colour[3] = 1.0f-text->route->factive;
+
+ for( u32 j=0;; j++ ){
+ char c = text->text[j];
+ if( !c ) break;
+
+ ent_glyph *glyph = font3d_glyph( &gui.font, 0, c );
+ if( !glyph ) continue;
+
+ if( c >= (u32)'0' && c <= (u32)'9' && glyph->indice_count ){
+ struct text_particle *particle =
+ &world_render.text_particles[world_render.text_particle_count++];
+
+ particle->glyph = glyph;
+ v4_copy( colour, particle->colour );
+
+ v3f origin;
+ v2_muls( glyph->size, 0.5f, origin );
+ origin[2] = -0.5f;
+
+ v3f world_co;
+
+ v3_add( offset, origin, world_co );
+ m4x3_mulv( transform, world_co, world_co );
+
+
+ m3x3_identity( particle->mlocal );
+ m3x3_scale( particle->mlocal, s );
+ origin[2] *= s[2];
+ v3_muls( origin, -1.0f, particle->mlocal[3] );
+
+ v3_copy( world_co, particle->rb.co );
+ v3_muls( imp_v, 1.0f+vg_randf64(&vg.rand), particle->rb.v );
+ particle->rb.v[1] += 2.0f;
+
+ v4_copy( q, particle->rb.q );
+ particle->rb.w[0] = vg_randf64(&vg.rand)*2.0f-1.0f;
+ particle->rb.w[1] = vg_randf64(&vg.rand)*2.0f-1.0f;
+ particle->rb.w[2] = vg_randf64(&vg.rand)*2.0f-1.0f;
+
+ f32 r = vg_maxf( s[0]*glyph->size[0], s[1]*glyph->size[1] )*0.5f;
+ particle->radius = r*0.6f;
+ rb_setbody_sphere( &particle->rb, particle->radius, 1.0f, 1.0f );
+ }
+ offset[0] += glyph->size[0];
+ }
+ }
+}
+
+static void render_gate_markers( m4x3f world_mmdl, int run_id, ent_gate *gate ){
+ for( u32 j=0; j<4; j++ ){
+ if( gate->routes[j] == run_id ){
+ m4x3f mmdl;
+ m4x3_copy( gate->to_world, mmdl );
+ m3x3_scale( mmdl, (v3f){ gate->dimensions[0],
+ gate->dimensions[1], 1.0f } );
+
+ m4x3_mul( world_mmdl, mmdl, mmdl );
+ shader_model_gate_uMdl( mmdl );
+ mdl_draw_submesh( &world_gates.sm_marker[j] );
+ break;
+ }
+ }
+}
+
+void render_world_routes( world_instance *world,
+ world_instance *host_world,
+ m4x3f mmdl, vg_camera *cam,
+ int viewing_from_gate, int viewing_from_hub )
+{
+ shader_scene_route_use();
+ shader_scene_route_uTexGarbage(0);
+ world_link_lighting_ub( host_world, _shader_scene_route.id );
+ world_bind_position_texture( host_world, _shader_scene_route.id,
+ _uniform_scene_route_g_world_depth, 2 );
+ world_bind_light_array( host_world, _shader_scene_route.id,
+ _uniform_scene_route_uLightsArray, 3 );
+ world_bind_light_index( host_world, _shader_scene_route.id,
+ _uniform_scene_route_uLightsIndex, 4 );
+ bind_terrain_noise();
+
+ shader_scene_route_uPv( cam->mtx.pv );
+
+ if( viewing_from_hub ){
+ m4x4f m4mdl, pvm;
+ m4x3_expand( mmdl, m4mdl );
+ m4x4_mul( cam->mtx_prev.pv, m4mdl, pvm );
+ shader_scene_route_uMdl( mmdl );
+ shader_scene_route_uPvmPrev( pvm );
+
+ m3x3f mnormal;
+ m3x3_inv( mmdl, mnormal );
+ m3x3_transpose( mnormal, mnormal );
+ v3_normalize( mnormal[0] );
+ v3_normalize( mnormal[1] );
+ v3_normalize( mnormal[2] );
+ shader_scene_route_uNormalMtx( mnormal );
+ }
+ else{
+ shader_scene_route_uMdl( mmdl );
+ shader_scene_route_uPvmPrev( cam->mtx_prev.pv );
+ m3x3f ident;
+ m3x3_identity( ident );
+ shader_scene_route_uNormalMtx( ident );
+ }
+
+ shader_scene_route_uCamera( cam->transform[3] );
+
+ mesh_bind( &world->mesh_route_lines );
+
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ f32 t = viewing_from_hub? 1.0f: route->factive;
+
+ v4f colour;
+ v3_lerp( (v3f){0.7f,0.7f,0.7f}, route->colour, t, colour );
+ colour[3] = t*0.2f;
+
+ shader_scene_route_uColour( colour );
+ mdl_draw_submesh( &route->sm );
+ }
+
+ /* timers
+ * ---------------------------------------------------- */
+ if( !viewing_from_gate && !viewing_from_hub ){
+ font3d_bind( &gui.font, k_font_shader_default, 0, world, cam );
+
+ for( u32 i=0; i<world_render.timer_text_count; i++ ){
+ struct timer_text *text = &world_render.timer_texts[i];
+
+ v4f colour;
+ float brightness = 0.3f + world->ub_lighting.g_day_phase;
+ v3_muls( text->route->colour, brightness, colour );
+ colour[3] = 1.0f-text->route->factive;
+
+ shader_model_font_uColour( colour );
+ font3d_simple_draw( 0, text->text, cam, text->transform );
+ }
+
+ shader_model_font_uOffset( (v4f){0.0f,0.0f,0.0f,1.0f} );
+
+ for( u32 i=0; i<world_render.text_particle_count; i++ ){
+ struct text_particle *particle = &world_render.text_particles[i];
+
+ m4x4f prev_mtx;
+
+ m4x3_expand( particle->mdl, prev_mtx );
+ m4x4_mul( cam->mtx_prev.pv, prev_mtx, prev_mtx );
+
+ shader_model_font_uPvmPrev( prev_mtx );
+
+ v4f q;
+ m4x3f model;
+ rb_extrapolate( &particle->rb, model[3], q );
+ q_m3x3( q, model );
+
+ m4x3_mul( model, particle->mlocal, particle->mdl );
+ shader_model_font_uMdl( particle->mdl );
+ shader_model_font_uColour( particle->colour );
+
+ mesh_drawn( particle->glyph->indice_start,
+ particle->glyph->indice_count );
+ }
+ }
+
+ /* gate markers
+ * ---------------------------------------------------- */
+
+ shader_model_gate_use();
+ shader_model_gate_uPv( cam->mtx.pv );
+ shader_model_gate_uCam( cam->pos );
+ shader_model_gate_uTime( vg.time*0.25f );
+ shader_model_gate_uInvRes( (v2f){
+ 1.0f / (float)vg.window_x,
+ 1.0f / (float)vg.window_y });
+
+ mesh_bind( &world_gates.mesh );
+
+ /* skip writing into the motion vectors for this */
+ glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
+ glDisable( GL_CULL_FACE );
+
+ if( viewing_from_hub ){
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ v4f colour;
+ v3_muls( route->colour, 1.6666f, colour );
+ colour[3] = 0.0f;
+
+ shader_model_gate_uColour( colour );
+
+ for( u32 j=0; j<mdl_arrcount(&world->ent_gate); j ++ ){
+ ent_gate *gate = mdl_arritm( &world->ent_gate, j );
+ if( !(gate->flags & k_ent_gate_nonlocal) )
+ render_gate_markers( mmdl, i, gate );
+ }
+ }
+ }
+ else{
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+
+ if( route->active_checkpoint != 0xffff ){
+ v4f colour;
+ float brightness = 0.3f + world->ub_lighting.g_day_phase;
+ v3_muls( route->colour, brightness, colour );
+ colour[3] = 1.0f-route->factive;
+
+ shader_model_gate_uColour( colour );
+
+ u32 next = route->active_checkpoint+1+viewing_from_gate;
+ next = next % route->checkpoints_count;
+ next += route->checkpoints_start;
+
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
+ ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
+ render_gate_markers( mmdl, i, gate );
+ }
+ }
+ }
+ glEnable( GL_CULL_FACE );
+ glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 } );
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+#include "vg/vg_camera.h"
+#include "vg/vg_msg.h"
+#include "world.h"
+#include "network_msg.h"
+
+void world_routes_init(void);
+void world_routes_fracture( world_instance *world, ent_gate *gate,
+ v3f imp_co, v3f imp_v );
+void world_routes_activate_entry_gate( world_instance *world,
+ ent_gate *rg );
+void render_world_routes( world_instance *world,
+ world_instance *host_world,
+ m4x3f mmdl, vg_camera *cam,
+ int viewing_from_gate, int viewing_from_hub );
+
+void world_gen_routes_ent_init( world_instance *world );
+void world_gen_routes_generate( u32 instance_id );
+void world_routes_update_timer_texts( world_instance *world );
+void world_routes_update( world_instance *world );
+void world_routes_fixedupdate( world_instance *world );
+void world_routes_clear( world_instance *world );
+void world_routes_recv_scoreboard( world_instance *world,
+ vg_msg *body, u32 route_id,
+ enum request_status status );
--- /dev/null
+#include "skaterift.h"
+#include "world_routes_ui.h"
+#include "world_routes.h"
+#include "player.h"
+
+static u32 v4_rgba( v4f colour ){
+ u32 r = vg_minf(1.0f,colour[0])*255.0f,
+ g = vg_minf(1.0f,colour[1])*255.0f,
+ b = vg_minf(1.0f,colour[2])*255.0f,
+ a = vg_minf(1.0f,colour[3])*255.0f;
+
+ return r | (g<<8) | (b<<16) | (a<<24);
+}
+
+static void ent_route_imgui( ui_context *ctx,
+ world_instance *world, ent_route *route,
+ ui_point inout_cursor ){
+ if( route->flags & k_ent_route_flag_out_of_zone )
+ return;
+
+ u32 last_version=0;
+ f64 last_time = 0.0;
+ ent_checkpoint *last_cp = NULL;
+
+ u32 valid_sections=0;
+
+ struct time_block{
+ f32 length, best;
+ int clean;
+ }
+ blocks[ route->checkpoints_count ];
+
+ for( u32 i=0; i<route->checkpoints_count; i++ ){
+ u32 cpid = i+route->active_checkpoint+1;
+ cpid %= route->checkpoints_count;
+ cpid += route->checkpoints_start;
+
+ ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, cpid );
+ ent_gate *rg = mdl_arritm( &world->ent_gate, cp->gate_index );
+ rg = mdl_arritm( &world->ent_gate, rg->target );
+
+ if( last_version+1 == rg->timing_version ) {
+ struct time_block *block = &blocks[ valid_sections ++ ];
+ block->clean = (rg->flags & k_ent_gate_clean_pass)? 1: 0;
+ block->length = rg->timing_time - last_time;
+ block->best = last_cp? last_cp->best_time: 0.0f;
+ }
+ else valid_sections = 0;
+
+ last_version = rg->timing_version;
+ last_time = rg->timing_time;
+ last_cp = cp;
+ }
+
+ if( last_version+1 == world_static.current_run_version ){
+ struct time_block *block = &blocks[ valid_sections ++ ];
+ block->clean = localplayer.rewinded_since_last_gate? 0: 1;
+ block->length = world_static.time - last_time;
+ block->best = last_cp->best_time;
+ }
+ else
+ valid_sections = 0;
+
+ u32 colour = v4_rgba( route->colour ) | 0xff000000;
+
+ ui_px x = 0,
+ h = route->factive * 16.0f,
+ base = inout_cursor[0];//(f32)vg.window_x*0.5f - route->ui_stopper;
+
+ if( route->ui_residual > 0.0f )
+ {
+ ui_px w = route->ui_residual_block_w,
+ total = w + 4;
+
+ f32 t = vg_smoothstepf(1.0f-route->ui_residual);
+
+ x -= (f32)total * t;
+
+ ui_rect rect = { base+x, inout_cursor[1], w, h };
+
+ v4f fadecolour;
+ v4_copy( route->colour, fadecolour );
+ fadecolour[3] *= route->ui_residual;
+
+ ui_fill( ctx, rect, v4_rgba(fadecolour) );
+
+ x += total;
+ }
+
+ int got_first = 0;
+
+ for( u32 i=0; i<valid_sections; i ++ )
+ {
+ struct time_block *block = &blocks[ i ];
+ ui_px w = 20 + (block->length * 6.0f);
+ ui_rect rect = { base+x, inout_cursor[1], w, h };
+ ui_fill( ctx, rect, colour );
+
+ if( block->clean )
+ ui_outline( ctx, rect, 1, 0xff00ffff, 0 );
+
+ if( block->best != 0.0f )
+ {
+ char buf[32];
+ vg_str str;
+ vg_strnull( &str, buf, 32 );
+
+ f32 diff = block->length - block->best,
+ as = fabsf(diff),
+ s = floorf( as ),
+ ds = floorf( vg_fractf( as ) * 10.0f );
+
+ if( (block->best != 0.0f) && (fabsf(diff) > 0.001f) )
+ {
+ if( diff > 0.0f )
+ vg_strcatch( &str, '+' );
+ else
+ vg_strcatch( &str, '-' );
+
+ vg_strcati32( &str, s );
+ vg_strcatch( &str, '.' );
+ vg_strcati32( &str, ds );
+
+ ui_text( ctx, rect, buf, 1, k_ui_align_middle_center, 0 );
+ }
+ }
+
+ x += w + 4;
+
+ if( !got_first ){
+ route->ui_first_block_width = w;
+ got_first = 1;
+ }
+ }
+
+ for( u32 i=0; i<route->checkpoints_count-valid_sections; i++ )
+ {
+ struct time_block *block = &blocks[ i ];
+
+ ui_px w = 20;
+ ui_rect rect = { base+x, inout_cursor[1], w, h };
+ ui_outline( ctx, rect, -1, colour, 0 );
+ x += w + 4;
+
+ if( !got_first )
+ {
+ route->ui_first_block_width = w;
+ got_first = 1;
+ }
+ }
+
+ inout_cursor[1] += h + 4;
+
+ vg_slewf( &route->ui_residual, 0.0f, vg.time_frame_delta );
+ route->ui_stopper = vg_lerpf( route->ui_stopper, (f32)x*0.5f,
+ vg.time_frame_delta );
+}
+
+void world_routes_imgui( ui_context *ctx, world_instance *world )
+{
+ if( skaterift.activity == k_skaterift_menu ) return;
+
+ ui_point cursor = { 4, 4 };
+ for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ )
+ {
+ ent_route_imgui( ctx, world, mdl_arritm( &world->ent_route, i ), cursor );
+ }
+}
--- /dev/null
+#pragma once
+#include "world_routes.h"
+
+struct route_ui{};
+void world_routes_imgui( ui_context *ctx, world_instance *world );
--- /dev/null
+#ifndef SFD_C
+#define SFD_C
+
+#include "world_sfd.h"
+#include "shaders/scene_scoretext.h"
+#include "shaders/scene_vertex_blend.h"
+#include "network.h"
+#include "entity.h"
+#include "network_common.h"
+#include "world_routes.h"
+
+struct world_sfd world_sfd;
+
+static f32 sfd_encode_glyph( char c ){
+ int value = 0;
+ if( c >= 'a' && c <= 'z' )
+ value = c-'a'+11;
+ else if( c >= '0' && c <= '9' )
+ value = c-'0'+1;
+ else if( c >= 'A' && c <= 'Z' )
+ value = c-'A'+11;
+ else if( c >= '\x01' && c <= '\x01'+10 )
+ value = 63-c;
+ else{
+ int base = 11+26;
+
+ switch( c ){
+ case '!': value=base+0; break;
+ case '?': value=base+1; break;
+ case ',': value=base+2; break;
+ case '.': value=base+3; break;
+ case '#': value=base+4; break;
+ case '$': value=base+5; break;
+ case '%': value=base+6; break;
+ case '*': value=base+7; break;
+ case '+': value=base+8; break;
+ case '-': value=base+9; break;
+ case '/': value=base+10; break;
+ case ':': value=base+11; break;
+ default: value=0; break;
+ }
+ }
+
+ return (float)value;
+}
+
+static void sfd_clear( u32 row ){
+ u32 row_h = world_sfd.h -1 -row;
+ for( int i=0; i<world_sfd.w; i++ ){
+ u32 idx = (world_sfd.w*row_h + i) * 2;
+ world_sfd.buffer[idx] = 0.0f;
+ }
+}
+
+void sfd_encode( v2i co, const char *str, enum world_sfd_align align )
+{
+ i32 row_h = world_sfd.h -1 -co[1];
+ i32 offset_x = 0;
+
+ i32 w = VG_MIN( strlen(str), world_sfd.w );
+ if( align == k_world_sfd_center )
+ offset_x = (world_sfd.w - w) / 2;
+ else if( align == k_world_sfd_right )
+ offset_x = world_sfd.w - w;
+ else
+ offset_x = co[0];
+
+ for( i32 i=0; i<world_sfd.w; i++ ){
+ i32 u = i + offset_x,
+ idx = (world_sfd.w*row_h + u) * 2;
+
+ if( (u >= world_sfd.w) || (u < 0) )
+ continue;
+
+ if( !str[i] )
+ return;
+
+ world_sfd.buffer[idx] = sfd_encode_glyph( str[i] );
+ }
+}
+
+void world_sfd_compile_scores( struct leaderboard_cache *board,
+ const char *title )
+{
+ for( u32 i=0; i<13; i++ )
+ sfd_clear(i);
+
+ sfd_encode( (v2i){0,0}, title, k_world_sfd_left );
+
+ if( !board ){
+ sfd_encode( (v2i){-1,4}, "Error out of range", k_world_sfd_center );
+ return;
+ }
+
+ if( !network_connected() ){
+ sfd_encode( (v2i){-1,0}, "Offline", k_world_sfd_right );
+ return;
+ }
+
+ if( board->status == k_request_status_not_found ){
+ sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center );
+ return;
+ }
+
+ if( board->status != k_request_status_ok ){
+ char buf[32];
+ vg_str s;
+ vg_strnull( &s, buf, 32 );
+ vg_strcat( &s, "Error: " );
+ vg_strcati32( &s, board->status );
+ sfd_encode( (v2i){-1,4}, buf, k_world_sfd_center );
+ return;
+ }
+
+ vg_msg body;
+ vg_msg_init( &body, board->data, board->data_len );
+
+ const char *alias = "rows";
+
+ if( world_sfd.view_weekly ){
+ alias = "rows_weekly";
+ sfd_encode( (v2i){-1,0}, "Weekly", k_world_sfd_right );
+ }
+ else {
+ sfd_encode( (v2i){-1,0}, "All-Time", k_world_sfd_right );
+ }
+
+ u32 l = 1;
+ if( vg_msg_seekframe( &body, alias ) ){
+ while( vg_msg_seekframe( &body, NULL ) ){
+ /* name */
+ const char *username = vg_msg_getkvstr( &body, "username" );
+
+ char buf[100];
+ vg_str str;
+ vg_strnull( &str, buf, 100 );
+ vg_strcati32( &str, l );
+ vg_strcat( &str, " " );
+
+ if( username )
+ vg_strcat( &str, username );
+ else
+ vg_strcat( &str, "??????" );
+
+ sfd_encode( (v2i){0,l}, str.buffer, k_world_sfd_left );
+
+ /* time */
+ vg_strnull( &str, buf, 100 );
+
+ u32 centiseconds;
+ vg_msg_getkvintg( &body, "time", k_vg_msg_i32, ¢iseconds, NULL );
+
+ i32 seconds = centiseconds / 100,
+ minutes = seconds / 60;
+
+ centiseconds %= 100;
+ seconds %= 60;
+ minutes %= 60;
+ if( minutes > 9 ) vg_strcat( &str, "?" );
+ else vg_strcati32( &str, minutes );
+ vg_strcat( &str, ":" );
+ vg_strcati32r( &str, seconds, 2, '0' );
+ vg_strcat( &str, "." );
+ vg_strcati32r( &str, centiseconds, 2, '0' );
+ sfd_encode( (v2i){-1,l}, str.buffer, k_world_sfd_right );
+ l ++;
+
+ vg_msg_skip_frame( &body );
+ }
+ }
+ else {
+ sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center );
+ }
+}
+
+void world_sfd_compile_active_scores(void)
+{
+ world_instance *world = world_current_instance();
+
+ struct leaderboard_cache *board = NULL;
+ const char *name = "Out of range";
+
+ if( world_sfd.active_route_board < mdl_arrcount( &world->ent_route ) ){
+ board = &world->leaderboard_cache[ world_sfd.active_route_board ];
+ ent_route *route = mdl_arritm( &world->ent_route,
+ world_sfd.active_route_board );
+ name = mdl_pstr( &world->meta, route->pstr_name );
+ }
+
+ world_sfd_compile_scores( board, name );
+}
+
+void world_sfd_update( world_instance *world, v3f pos )
+{
+ if( mdl_arrcount( &world->ent_route ) ){
+ u32 closest = 0;
+ float min_dist = INFINITY;
+
+ for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
+ ent_route *route = mdl_arritm( &world->ent_route, i );
+ float dist = v3_dist2( route->board_transform[3], pos );
+
+ if( dist < min_dist ){
+ min_dist = dist;
+ closest = i;
+ }
+ }
+
+ struct leaderboard_cache *board = &world->leaderboard_cache[ closest ];
+
+ /* request new board if cache expires */
+ if( network_connected() ){
+ f64 delta = vg.time_real - board->cache_time;
+ if( (delta > 45.0) || (board->cache_time == 0.0) ){
+ board->cache_time = vg.time_real;
+ ent_route *route = mdl_arritm( &world->ent_route, closest );
+ addon_reg *world_reg =
+ world_static.instance_addons[ world - world_static.instances ];
+
+ char mod_uid[ ADDON_UID_MAX ];
+ addon_alias_uid( &world_reg->alias, mod_uid );
+
+ network_request_scoreboard(
+ mod_uid,
+ mdl_pstr( &world->meta, route->pstr_name ),
+ NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK, closest );
+ }
+ }
+
+ /* compile board text if we changed. */
+ if( world_sfd.active_route_board != closest ){
+ world_sfd_compile_active_scores();
+ }
+
+ world_sfd.active_route_board = closest;
+ }
+
+ for( int i=0; i<world_sfd.w*world_sfd.h; i++ ){
+ float *target = &world_sfd.buffer[i*2+0],
+ *cur = &world_sfd.buffer[i*2+1];
+
+ float const rate = vg.time_delta * 25.2313131414f;
+ float d1 = *target-*cur;
+
+ if( fabsf(d1) > rate ){
+ *cur += rate;
+ if( *cur > 49.0f )
+ *cur -= 49.0f;
+ }
+ else
+ *cur = *target;
+ }
+}
+
+void bind_terrain_noise(void);
+void sfd_render( world_instance *world, vg_camera *cam, m4x3f transform )
+{
+ mesh_bind( &world_sfd.mesh_display );
+ shader_scene_scoretext_use();
+ shader_scene_scoretext_uTexMain(1);
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_scoretext );
+
+ bind_terrain_noise();
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_sfd.tex_scoretex );
+
+ m4x4f pvm_prev;
+ m4x3_expand( transform, pvm_prev );
+ m4x4_mul( cam->mtx_prev.pv, pvm_prev, pvm_prev );
+
+ shader_scene_scoretext_uPv( cam->mtx.pv );
+ shader_scene_scoretext_uPvmPrev( pvm_prev );
+ shader_scene_scoretext_uMdl( transform );
+ shader_scene_scoretext_uCamera( cam->transform[3] );
+
+ for( int y=0;y<world_sfd.h; y++ ){
+ for( int x=0; x<world_sfd.w; x++ ){
+ float value = world_sfd.buffer[(y*world_sfd.w+x)*2+1];
+ shader_scene_scoretext_uInfo( (v3f){ x,y, value } );
+ mesh_draw( &world_sfd.mesh_display );
+ }
+ }
+
+ shader_scene_vertex_blend_use();
+ shader_scene_vertex_blend_uTexGarbage(0);
+ shader_scene_vertex_blend_uTexGradients(1);
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_vertex_blend );
+
+ bind_terrain_noise();
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_sfd.tex_scoretex );
+
+ shader_scene_vertex_blend_uPv( cam->mtx.pv );
+ shader_scene_vertex_blend_uPvmPrev( pvm_prev );
+ shader_scene_vertex_blend_uMdl( transform );
+ shader_scene_vertex_blend_uCamera( cam->transform[3] );
+
+ mesh_bind( &world_sfd.mesh_base );
+ mdl_draw_submesh( &world_sfd.sm_base );
+}
+
+void world_sfd_init(void)
+{
+ vg_info( "world_sfd_init\n" );
+ vg_linear_clear( vg_mem.scratch );
+
+ mdl_context mscoreboard;
+ mdl_open( &mscoreboard, "models/rs_scoretext.mdl", vg_mem.scratch );
+ mdl_load_metadata_block( &mscoreboard, vg_mem.scratch );
+ mdl_async_load_glmesh( &mscoreboard, &world_sfd.mesh_base, NULL );
+
+ mdl_load_mesh_block( &mscoreboard, vg_mem.scratch );
+
+ scene_context *scene = &world_sfd.scene;
+ vg_async_item *call = scene_alloc_async( scene, &world_sfd.mesh_display,
+ 3000, 8000 );
+
+
+ mdl_mesh *m_backer = mdl_find_mesh( &mscoreboard, "backer" ),
+ *m_card = mdl_find_mesh( &mscoreboard, "score_card" );
+
+ mdl_submesh
+ *sm_backer = mdl_arritm( &mscoreboard.submeshs, m_backer->submesh_start ),
+ *sm_card = mdl_arritm( &mscoreboard.submeshs, m_card->submesh_start );
+ world_sfd.sm_base = *sm_backer;
+
+ m4x3f identity;
+ m4x3_identity( identity );
+
+ for( int i=0;i<4;i++ ){
+ u32 vert_start = scene->vertex_count;
+ scene_add_mdl_submesh( scene, &mscoreboard, sm_card, identity );
+
+ for( int j=0; j<sm_card->vertex_count; j++ ){
+ scene_vert *vert = &scene->arrvertices[ vert_start+j ];
+
+ float const k_glyph_uvw = 1.0f/64.0f;
+ vert->uv[0] -= k_glyph_uvw * (float)(i-1);
+ vert->norm[3] = i*42;
+ }
+ }
+
+ vg_async_dispatch( call, async_scene_upload );
+ vg_tex2d_load_qoi_async_file( "textures/scoretext.qoi",
+ VG_TEX2D_CLAMP|VG_TEX2D_NEAREST,
+ &world_sfd.tex_scoretex );
+
+ mdl_close( &mscoreboard );
+
+ int w = 27,
+ h = 13;
+
+ world_sfd.w = w;
+ world_sfd.h = h;
+ world_sfd.buffer = vg_linear_alloc( vg_mem.rtmemory, 2*w*h*sizeof(float) );
+
+ for( int i=0; i<w*h*2; i++ )
+ world_sfd.buffer[i] = 0.0f;
+}
+
+#endif /* WORLD_SFD_C */
--- /dev/null
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+#pragma once
+#include "world.h"
+#include "world_routes.h"
+#include "scene.h"
+
+struct world_sfd{
+ GLuint tex_scoretex;
+
+ glmesh mesh_base, mesh_display;
+ mdl_submesh sm_base;
+
+ u32 active_route_board;
+ scene_context scene;
+
+ u32 view_weekly;
+
+ u32 w, h;
+ float *buffer;
+}
+extern world_sfd;
+void world_sfd_init(void);
+
+enum world_sfd_align {
+ k_world_sfd_left,
+ k_world_sfd_right,
+ k_world_sfd_center
+};
+
+void sfd_encode( v2i co, const char *str, enum world_sfd_align align );
+void world_sfd_update( world_instance *world, v3f pos );
+void sfd_render( world_instance *world, vg_camera *cam, m4x3f transform );
+void world_sfd_compile_scores( struct leaderboard_cache *leaderboard,
+ const char *title );
+void world_sfd_compile_active_scores(void);
--- /dev/null
+#include "world_volumes.h"
+
+void world_volumes_update( world_instance *world, v3f pos )
+{
+ /* filter and check the existing ones */
+ u32 j=0;
+ for( u32 i=0; i<world_static.active_trigger_volume_count; i++ ){
+ i32 idx = world_static.active_trigger_volumes[i];
+ ent_volume *volume = mdl_arritm( &world->ent_volume, idx );
+
+ v3f local;
+ m4x3_mulv( volume->to_local, pos, local );
+ if( (fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f) )
+ {
+ world_static.active_trigger_volumes[ j ++ ] = idx;
+ boxf cube = {{-1.0f,-1.0f,-1.0f},{1.0f,1.0f,1.0f}};
+ vg_line_boxf_transformed( volume->to_world, cube, 0xff00ccff );
+ }
+ else{
+ /*
+ * LEGACY BEHAVIOUR: < v104 does not have leave events
+ */
+ if( world->meta.info.version >= 104 ){
+ ent_call basecall;
+ basecall.function = k_ent_function_trigger_leave;
+ basecall.id = mdl_entity_id( k_ent_volume, idx );
+ basecall.data = NULL;
+
+ entity_call( world, &basecall );
+ }
+ }
+ }
+ world_static.active_trigger_volume_count = j;
+
+ static float random_accum = 0.0f;
+ random_accum += vg.time_delta;
+
+ u32 random_ticks = 0;
+
+ while( random_accum > 0.1f ){
+ random_accum -= 0.1f;
+ random_ticks ++;
+ }
+
+ float radius = 32.0f;
+
+ bh_iter it;
+ bh_iter_init_range( 0, &it, pos, radius );
+ i32 idx;
+
+ while( bh_next( world->entity_bh, &it, &idx ) ){
+ u32 id = world->entity_list[ idx ],
+ type = mdl_entity_id_type( id ),
+ index = mdl_entity_id_id( id );
+
+ if( type != k_ent_volume ) continue;
+
+ ent_volume *volume = mdl_arritm( &world->ent_volume, index );
+ boxf cube = {{-1.0f,-1.0f,-1.0f},{1.0f,1.0f,1.0f}};
+
+ if( volume->flags & k_ent_volume_flag_particles ){
+ vg_line_boxf_transformed( volume->to_world, cube, 0xff00c0ff );
+
+ for( int j=0; j<random_ticks; j++ ){
+ ent_call basecall;
+ basecall.id = id;
+ basecall.data = NULL;
+ basecall.function = 0;
+
+ entity_call( world, &basecall );
+ }
+ }
+ else{
+ for( u32 i=0; i<world_static.active_trigger_volume_count; i++ )
+ if( world_static.active_trigger_volumes[i] == index )
+ goto next_volume;
+
+ if( world_static.active_trigger_volume_count >
+ VG_ARRAY_LEN(world_static.active_trigger_volumes) ) continue;
+
+ v3f local;
+ m4x3_mulv( volume->to_local, pos, local );
+
+ if( (fabsf(local[0]) <= 1.0f) &&
+ (fabsf(local[1]) <= 1.0f) &&
+ (fabsf(local[2]) <= 1.0f) ){
+ ent_call basecall;
+ basecall.function = 0;
+ basecall.id = id;
+ basecall.data = NULL;
+
+ entity_call( world, &basecall );
+ world_static.active_trigger_volumes[
+ world_static.active_trigger_volume_count ++ ] = index;
+ }
+ else
+ vg_line_boxf_transformed( volume->to_world, cube, 0xffcccccc );
+ }
+next_volume:;
+ }
+}
--- /dev/null
+#pragma once
+#include "world.h"
+#include "vg/vg_bvh.h"
+
+void world_volumes_update( world_instance *world, v3f pos );
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#include "world_water.h"
+#include "world_render.h"
+#include "render.h"
+#include "shaders/scene_water.h"
+#include "shaders/scene_water_fast.h"
+#include "scene.h"
+#include "player.h"
+#include "player_walk.h"
+#include "player_dead.h"
+
+struct world_water world_water;
+
+void world_water_init(void)
+{
+ vg_info( "world_water_init\n" );
+
+ vg_tex2d_load_qoi_async_file( "textures/water_surf.qoi",
+ VG_TEX2D_LINEAR|VG_TEX2D_REPEAT,
+ &world_water.tex_water_surf );
+
+ vg_success( "done\n" );
+}
+
+void water_set_surface( world_instance *world, float height )
+{
+ world->water.height = height;
+ v4_copy( (v4f){ 0.0f, 1.0f, 0.0f, height }, world->water.plane );
+}
+
+void world_link_lighting_ub( world_instance *world, GLuint shader );
+void world_bind_position_texture( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+void world_bind_light_array( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+void world_bind_light_index( world_instance *world,
+ GLuint shader, GLuint location,
+ int slot );
+
+/*
+ * Does not write motion vectors
+ */
+void render_water_texture( world_instance *world, vg_camera *cam )
+{
+ if( !world->water.enabled || (vg.quality_profile == k_quality_profile_low) )
+ return;
+
+ /* Draw reflection buffa */
+ vg_framebuffer_bind( g_render.fb_water_reflection, k_render_scale );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+
+ /*
+ * Create flipped view matrix. Don't care about motion vectors
+ */
+ float cam_height = cam->transform[3][1] - world->water.height;
+
+ vg_camera water_cam;
+ water_cam.farz = cam->farz;
+ water_cam.nearz = cam->nearz;
+ v3_copy( cam->transform[3], water_cam.transform[3] );
+ water_cam.transform[3][1] -= 2.0f * cam_height;
+
+ m3x3f flip;
+ m3x3_identity( flip );
+ flip[1][1] = -1.0f;
+ m3x3_mul( flip, cam->transform, water_cam.transform );
+
+ vg_camera_update_view( &water_cam );
+
+ /*
+ * Create clipped projection
+ */
+ v4f clippa = { 0.0f, 1.0f, 0.0f, world->water.height-0.1f };
+ m4x3_mulp( water_cam.transform_inverse, clippa, clippa );
+ clippa[3] *= -1.0f;
+
+ m4x4_copy( cam->mtx.p, water_cam.mtx.p );
+ m4x4_clip_projection( water_cam.mtx.p, clippa );
+
+ vg_camera_finalize( &water_cam );
+
+ /*
+ * Draw world
+ */
+ glEnable( GL_DEPTH_TEST );
+ glDisable( GL_BLEND );
+ glCullFace( GL_FRONT );
+ render_world( world, &water_cam, 0, 1, 0, 1 );
+ glCullFace( GL_BACK );
+
+ /*
+ * Create beneath view matrix
+ */
+ vg_camera beneath_cam;
+ vg_framebuffer_bind( g_render.fb_water_beneath, k_render_scale );
+ glClearColor( 1.0f, 0.0f, 0.0f, 0.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+
+ m4x3_copy( cam->transform, beneath_cam.transform );
+ vg_camera_update_view( &beneath_cam );
+
+ float bias = -(cam->transform[3][1]-world->water.height)*0.1f;
+
+ v4f clippb = { 0.0f, -1.0f, 0.0f, -(world->water.height) + bias };
+ m4x3_mulp( beneath_cam.transform_inverse, clippb, clippb );
+ clippb[3] *= -1.0f;
+
+ m4x4_copy( cam->mtx.p, beneath_cam.mtx.p );
+ m4x4_clip_projection( beneath_cam.mtx.p, clippb );
+ vg_camera_finalize( &beneath_cam );
+
+ glEnable( GL_DEPTH_TEST );
+ glDisable( GL_BLEND );
+ render_world_depth( world, &beneath_cam );
+ //glViewport( 0,0, g_render_x, g_render_y );
+}
+
+void render_water_surface( world_instance *world, vg_camera *cam )
+{
+ if( !world->water.enabled )
+ return;
+
+ if( vg.quality_profile == k_quality_profile_high )
+ {
+ /* Draw surface */
+ shader_scene_water_use();
+
+ vg_framebuffer_bind_texture( g_render.fb_water_reflection, 0, 0 );
+ shader_scene_water_uTexMain( 0 );
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_water.tex_water_surf );
+ shader_scene_water_uTexDudv( 1 );
+
+ shader_scene_water_uInvRes( (v2f){
+ 1.0f / (float)vg.window_x,
+ 1.0f / (float)vg.window_y });
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_water );
+
+ vg_framebuffer_bind_texture( g_render.fb_water_beneath, 0, 5 );
+ shader_scene_water_uTexBack( 5 );
+ shader_scene_water_uTime( world_static.time );
+ shader_scene_water_uCamera( cam->transform[3] );
+ shader_scene_water_uSurfaceY( world->water.height );
+
+ shader_scene_water_uPv( cam->mtx.pv );
+ shader_scene_water_uPvmPrev( cam->mtx_prev.pv );
+
+ m4x3f full;
+ m4x3_identity( full );
+ shader_scene_water_uMdl( full );
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ glBlendEquation(GL_FUNC_ADD);
+
+ mesh_bind( &world->mesh_no_collide );
+
+ for( int i=0; i<world->surface_count; i++ )
+ {
+ struct world_surface *mat = &world->surfaces[i];
+ struct shader_props_water *props = mat->info.props.compiled;
+
+ if( mat->info.shader == k_shader_water )
+ {
+ shader_scene_water_uShoreColour( props->shore_colour );
+ shader_scene_water_uOceanColour( props->deep_colour );
+ shader_scene_water_uFresnel( props->fresnel );
+ shader_scene_water_uWaterScale( props->water_sale );
+ shader_scene_water_uWaveSpeed( props->wave_speed );
+
+ mdl_draw_submesh( &mat->sm_no_collide );
+ }
+ }
+
+ glDisable(GL_BLEND);
+ }
+ else if( (vg.quality_profile == k_quality_profile_low) ||
+ (vg.quality_profile == k_quality_profile_min) )
+ {
+ shader_scene_water_fast_use();
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, world_water.tex_water_surf );
+ shader_scene_water_fast_uTexDudv( 1 );
+
+ shader_scene_water_fast_uTime( world_static.time );
+ shader_scene_water_fast_uCamera( cam->transform[3] );
+ shader_scene_water_fast_uSurfaceY( world->water.height );
+
+ WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_water_fast );
+
+ m4x3f full;
+ m4x3_identity( full );
+ shader_scene_water_fast_uMdl( full );
+ shader_scene_water_fast_uPv( cam->mtx.pv );
+ shader_scene_water_fast_uPvmPrev( cam->mtx_prev.pv );
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ glBlendEquation(GL_FUNC_ADD);
+
+ mesh_bind( &world->mesh_no_collide );
+
+ for( int i=0; i<world->surface_count; i++ )
+ {
+ struct world_surface *mat = &world->surfaces[i];
+ struct shader_props_water *props = mat->info.props.compiled;
+
+ if( mat->info.shader == k_shader_water )
+ {
+ shader_scene_water_fast_uShoreColour( props->shore_colour );
+ shader_scene_water_fast_uOceanColour( props->deep_colour );
+
+ mdl_draw_submesh( &mat->sm_no_collide );
+ }
+ }
+
+ glDisable(GL_BLEND);
+ }
+}
+
+static void world_water_drown(void)
+{
+ if( localplayer.drowned ) return;
+ player__networked_sfx( k_player_subsystem_walk, 32,
+ k_player_walk_soundeffect_splash,
+ localplayer.rb.co, 1.0f );
+ vg_info( "player fell of due to walking into walker\n" );
+ localplayer.drowned = 1;
+ player__dead_transition( k_player_die_type_generic );
+}
+
+bool world_water_player_safe( world_instance *world, f32 allowance )
+{
+ if( !world->water.enabled ) return 1;
+ if( world->info.flags & 0x2 ) return 1;
+
+ if( localplayer.rb.co[1]+allowance < world->water.height )
+ {
+ world_water_drown();
+ return 0;
+ }
+
+ return 1;
+}
+
+entity_call_result ent_water_call( world_instance *world, ent_call *call )
+{
+ if( call->function == 0 )
+ {
+ world_water_drown();
+ return k_entity_call_result_OK;
+ }
+
+ return k_entity_call_result_unhandled;
+}
--- /dev/null
+/*
+ * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
+#pragma once
+#include "world.h"
+
+struct world_water{
+ GLuint tex_water_surf;
+}
+extern world_water;
+void world_water_init(void);
+
+void water_set_surface( world_instance *world, f32 height );
+void render_water_texture( world_instance *world, vg_camera *cam );
+void render_water_surface( world_instance *world, vg_camera *cam );
+entity_call_result ent_water_call( world_instance *world, ent_call *call );
+bool world_water_player_safe( world_instance *world, f32 allowance );
+++ /dev/null
-#include "vg/vg_steam.h"
-#include "vg/vg_steam_utils.h"
-#include "vg/vg_steam_networking.h"
-#include "vg/vg_steam_auth.h"
-#include "vg/vg_steam_http.h"
-#include "vg/vg_steam_friends.h"
-#include "vg/vg_steam_user_stats.h"
-#include "submodules/anyascii/impl/c/anyascii.c"
-#include "skaterift.h"
-#include <string.h>
-
-/*
- * We only want to use steamworks if building for the networked version,
- * theres not much point otherwise. We mainly want steamworks for setting
- * achievements etc.. so that includes our own server too.
- *
- * This file also wraps the functions and interfaces that we want to use to
- * make them a bit easier to read, since they are the flat API they have very
- * long names. in non-networked builds they will return default errors or do
- * nothing.
- */
-
-char steam_username_at_startup[128] = "Unassigned";
-
-static void recv_steam_warning( int severity, const char *msg )
-{
- if( severity == 0 )
- vg_low( "%s\n", msg );
- else
- vg_info( "%s\n", msg );
-}
-
-int steam_ready = 0,
- steam_stats_ready = 0;
-
-void *hSteamNetworkingSockets, *hSteamUser, *hSteamUserStats;
-static HSteamPipe hSteamClientPipe;
-
-static const char *steam_achievement_names[] =
-{
- "ALBERT", "MARC", "JANET", "BERNADETTA",
- "ROUTE_MPY", "ROUTE_MPG", "ROUTE_MPB", "ROUTE_MPR",
- "ROUTE_TO", "ROUTE_TC", "CITY_COMPLETE", "MTZERO_SILVER", "MTZERO_GOLD",
- "80FT"
-};
-
-void steam_store_achievements(void)
-{
- if( steam_ready && steam_stats_ready ){
- SteamAPI_ISteamUserStats_StoreStats( hSteamUserStats );
- }
-}
-
-void update_ach_models(void);
-void steam_set_achievement( const char *name )
-{
- if( skaterift.demo_mode )
- return;
-
- /* hack lol */
- if( !strcmp(name,"MARC") ) skaterift.achievements |= 0x1;
- if( !strcmp(name,"ALBERT") ) skaterift.achievements |= 0x2;
- if( !strcmp(name,"JANET") ) skaterift.achievements |= 0x4;
- if( !strcmp(name,"BERNADETTA") ) skaterift.achievements |= 0x8;
- update_ach_models();
-
- if( steam_ready && steam_stats_ready ){
- if( SteamAPI_ISteamUserStats_SetAchievement( hSteamUserStats, name ) ){
- vg_success( "Achievement set! '%s'\n", name );
-
- }
- else{
- vg_warn( "Failed to set achievement: %s\n", name );
- }
- }
- else{
- vg_warn( "Failed to set achievement (steam not ready): %s\n", name );
- }
-}
-
-void steam_clear_achievement( const char *name )
-{
- if( steam_ready && steam_stats_ready ){
- if( SteamAPI_ISteamUserStats_ClearAchievement( hSteamUserStats, name ) ){
- vg_info( "Achievement cleared: '%s'\n", name );
- }
- else{
- vg_warn( "Failed to clear achievement: %s\n", name );
- }
- }
- else{
- vg_warn( "Failed to clear achievement (steam not ready): %s\n", name );
- }
-}
-
-
-void steam_print_all_achievements(void)
-{
- vg_info( "Achievements: \n" );
-
- if( steam_ready && steam_stats_ready ){
- for( int i=0; i<VG_ARRAY_LEN(steam_achievement_names); i++ ){
- steamapi_bool set = 0;
- const char *name = steam_achievement_names[i];
-
- if( SteamAPI_ISteamUserStats_GetAchievement(
- hSteamUserStats, name, &set ) )
- {
- vg_info( " %s %s\n", (set? "[YES]": "[ ]"), name );
- }
- else{
- vg_warn( " Error while fetching achievement status '%s'\n", name );
- }
- }
- }
- else{
- vg_warn( " Steam is not initialized, no results\n" );
- }
-}
-
-int steam_achievement_ccmd( int argc, char const *argv[] )
-{
- if( !(steam_ready && steam_stats_ready) ) return 1;
-
- if( argc == 1 ){
- if( !strcmp( argv[0], "list" ) ){
- steam_print_all_achievements();
- return 0;
- }
- else if( !strcmp( argv[0], "clearall" )){
- for( int i=0; i<VG_ARRAY_LEN(steam_achievement_names); i++ )
- steam_clear_achievement( steam_achievement_names[i] );
-
- steam_store_achievements();
- }
- }
-
- if( argc == 2 ){
- if( !strcmp( argv[0], "set" ) ){
- steam_set_achievement( argv[1] );
- steam_store_achievements();
- return 0;
- }
- else if( strcmp( argv[0], "clear" ) ){
- steam_clear_achievement( argv[1] );
- steam_store_achievements();
- return 0;
- }
- }
-
- return 1;
-}
-
-static void steam_on_recieve_current_stats( CallbackMsg_t *msg )
-{
- UserStatsReceived_t *rec = (UserStatsReceived_t *)msg->m_pubParam;
-
- if( rec->m_eResult == k_EResultOK ){
- vg_info( "Recieved stats for: %lu (user: %lu)\n", rec->m_nGameID,
- rec->m_steamIDUser );
- steam_stats_ready = 1;
-
- steamapi_bool set = 0;
- if( SteamAPI_ISteamUserStats_GetAchievement(
- hSteamUserStats, "MARC", &set ) ){
- if( set ) skaterift.achievements |= 0x1;
- }
- if( SteamAPI_ISteamUserStats_GetAchievement(
- hSteamUserStats, "ALBERT", &set ) ){
- if( set ) skaterift.achievements |= 0x2;
- }
- if( SteamAPI_ISteamUserStats_GetAchievement(
- hSteamUserStats, "JANET", &set ) ){
- if( set ) skaterift.achievements |= 0x4;
- }
- if( SteamAPI_ISteamUserStats_GetAchievement(
- hSteamUserStats, "BERNADETTA", &set ) ){
- if( set ) skaterift.achievements |= 0x8;
- }
- update_ach_models();
- }
- else{
- vg_error( "Error recieveing stats for user (%u)\n", rec->m_eResult );
- }
-}
-
-static u32 utf8_byte0_byte_count( u8 char0 )
-{
- for( u32 k=2; k<4; k++ ){
- if( !(char0 & (0x80 >> k)) )
- return k;
- }
-
- return 0;
-}
-
-u32 str_utf8_collapse( const char *str, char *buf, u32 length )
-{
- u8 *ustr = (u8 *)str;
- u32 utf32_code = 0x00000000;
- u32 i=0, j=0, utf32_byte_ct=0;
-
- for(;j < length-1;){
- if( ustr[i] == 0x00 )
- break;
-
- if( ustr[i] & 0x80 ){
- if( utf32_byte_ct ){
- utf32_byte_ct --;
- utf32_code |= (ustr[i] & 0x3F) << (utf32_byte_ct*6);
-
- if( !utf32_byte_ct ){
- const char *match;
- size_t chars = anyascii( utf32_code, &match );
-
- for( u32 k=0; k<VG_MIN(chars, length-1-j); k++ ){
- buf[ j++ ] = (u8)match[k];
- }
- }
- }
- else{
- utf32_byte_ct = utf8_byte0_byte_count( ustr[i] )-1;
- utf32_code = ustr[i] & (0x3F >> utf32_byte_ct);
- utf32_code <<= utf32_byte_ct*6;
- }
- }
- else{
- utf32_byte_ct = 0x00;
- buf[j ++] = str[i];
- }
-
- i++;
- }
-
- buf[j] = 0x00;
- return j;
-}
-
-int steam_init(void)
-{
- const char *username = "offline player";
-
- vg_info( "Initializing steamworks\n" );
-
- if( !SteamAPI_Init() ){
- printf("\n");
- vg_error( "Steamworks failed to initialize\n" );
- return 1;
- }
-
- steam_ready = 1;
-
- SteamAPI_ManualDispatch_Init();
-
- /* Connect interfaces */
- hSteamClientPipe = SteamAPI_GetHSteamPipe();
- hSteamNetworkingSockets = SteamAPI_SteamNetworkingSockets_SteamAPI();
- hSteamUser = SteamAPI_SteamUser();
-
- ISteamUtils *utils = SteamAPI_SteamUtils();
- SteamAPI_ISteamUtils_SetWarningMessageHook( utils, recv_steam_warning );
-
- printf("\n");
- vg_success( "\nSteamworks API running\n" );
-
- ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
- username = SteamAPI_ISteamFriends_GetPersonaName( hSteamFriends );
-
- /*
- * Request stats
- * --------------------------------------------------------
- */
- hSteamUserStats = SteamAPI_SteamUserStats();
- steam_register_callback( k_iUserStatsReceived,
- steam_on_recieve_current_stats );
-
- if( !SteamAPI_ISteamUserStats_RequestCurrentStats( hSteamUserStats ) )
- vg_warn( "No Steam Logon: Cannot request stats\n" );
-
-
- vg_console_reg_cmd( "ach", steam_achievement_ccmd, NULL );
-
- /* TODO: On username update callback */
- str_utf8_collapse( username, steam_username_at_startup,
- VG_ARRAY_LEN(steam_username_at_startup) );
-
- return 1;
-}
-
-void steam_update(void)
-{
- if( steam_ready ){
- steamworks_event_loop( hSteamClientPipe );
- }
-}
-
-void steam_end(void)
-{
- if( steam_ready ){
- vg_info( "Shutting down\n..." );
- SteamAPI_Shutdown();
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- * All trademarks are property of their respective owners
- */
-#pragma once
-
-extern int steam_ready, steam_stats_ready;
-extern void *hSteamNetworkingSockets, *hSteamUser, *hSteamUserStats;
-extern char steam_username_at_startup[128];
-
-int steam_init(void);
-void steam_update(void);
-void steam_end(void);
-u32 str_utf8_collapse( const char *str, char *buf, u32 length );
-int steam_achievement_ccmd( int argc, char const *argv[] );
-void steam_print_all_achievements(void);
-void steam_clear_achievement( const char *name );
-void steam_set_achievement( const char *name );
-void steam_store_achievements(void);
+++ /dev/null
-#ifndef TRAFFIC_H
-#define TRAFFIC_H
-
-#include "common.h"
-#include "model.h"
-#include "rigidbody.h"
-#include "world.h"
-
-typedef struct traffic_node traffic_node;
-typedef struct traffic_driver traffic_driver;
-
-struct traffic_node
-{
- v3f co, h;
-
- union
- {
- struct{ traffic_node *next, *next1; };
- struct{ mdl_node *mn_next, *mn_next1; };
- };
-};
-
-struct traffic_driver
-{
- m4x3f transform;
-
- traffic_node *current;
- int option;
- float t, speed;
-};
-
-static float eval_bezier_length( v3f p0, v3f p1, v3f h0, v3f h1, int res )
-{
- float length = 0.0f, m = 1.0f/(float)res;
- v3f l, p;
- v3_copy( p0, l );
-
- for( int i=0; i<res; i++ )
- {
- float t = (float)(i+1)*m;
- eval_bezier_time(p0,p1,h0,h1,t,p);
- length += v3_dist( p,l );
- v3_copy( p, l );
- }
-
- return length;
-}
-
-static void traffic_finalize( traffic_node *system, int count )
-{
- for( int i=0; i<count; i++ )
- {
- traffic_node *tn = &system[i];
-
- if( tn->mn_next )
- tn->next = &system[ tn->mn_next->sub_uid ];
- if( tn->mn_next1 )
- tn->next1 = &system[ tn->mn_next1->sub_uid ];
- }
-}
-
-static void traffic_visualize_link( traffic_node *ta, traffic_node *tb )
-{
- v3f p0, p1, h0, h1, p, l;
-
- if( !tb ) return;
-
- v3_copy( ta->co, p0 );
- v3_muladds( ta->co, ta->h, 1.0f, h0 );
- v3_copy( tb->co, p1 );
- v3_muladds( tb->co, tb->h, -1.0f, h1 );
- v3_copy( p0, l );
-
- vg_line_pt3( h0, 0.2f, 0xff00ff00 );
- vg_line_pt3( h1, 0.2f, 0xffff00ff );
- vg_line( p0, h0, 0xff000000 );
- vg_line( p1, h1, 0xff000000 );
-
- for( int i=0; i<5; i++ )
- {
- float t = (float)(i+1)/5.0f;
- eval_bezier_time( p0, p1, h0, h1, t, p );
-
- vg_line( p, l, 0xffffffff );
- v3_copy( p, l );
- }
-}
-
-static void sample_wheel_floor( v3f pos )
-{
- v3f ground;
- v3_copy( pos, ground );
- ground[1] += 4.0f;
-
- ray_hit hit;
- hit.dist = 8.0f;
-
- if( ray_world( ground, (v3f){0.0f,-1.0f,0.0f}, &hit ))
- {
- v3_copy( hit.pos, pos );
- }
-}
-
-static void traffic_drive( traffic_driver *driver )
-{
- traffic_node *next, *current = driver->current;
-
- if( !current ) return;
- next = driver->option==0? current->next: current->next1;
-
- if( driver->t > 1.0f )
- {
- driver->t = driver->t - floorf( driver->t );
- driver->current = driver->option==0? current->next: current->next1;
- driver->option = 0;
-
- current = driver->current;
- if( !current )
- return;
-
- if( current->next && current->next1 )
- if( vg_randf() > 0.5f )
- driver->option = 1;
- }
-
- traffic_visualize_link( current, next );
-
- /*
- * Calculate the speed of the curve at the current point. On the reference
- * curve the rate should come out to be exactly 1 ktimestep traveled.
- * Dividing this distance by ktimestep gives us the modifier to use.
- */
- v3f p0,p1,h0,h1,pc,pn;
-
- v3_copy( current->co, p0 );
- v3_muladds( current->co, current->h, 1.0f, h0 );
- v3_copy( next->co, p1 );
- v3_muladds( next->co, next->h, -1.0f, h1 );
-
- eval_bezier_time( p0,p1,h0,h1, driver->t, pc );
- eval_bezier_time( p0,p1,h0,h1, driver->t + vg.time_delta, pn );
-
- float mod = vg.time_delta / v3_dist( pc, pn );
- v3f dir,side,up;
- v3_sub( pn, pc, dir );
- v3_normalize(dir);
-
- /*
- * Stick the car on the ground by casting rays where the wheels are
- */
- side[0] = -dir[2];
- side[1] = 0.0f;
- side[2] = dir[0];
- v3_normalize(side);
-
- v3f fl, fr, bc;
- v3_muladds( pc, dir, 2.0f, fr );
- v3_muladds( pc, dir, 2.0f, fl );
- v3_muladds( pc, dir, -2.0f, bc );
- v3_muladds( fr, side, 1.0f, fr );
- v3_muladds( fl, side, -1.0f, fl );
-
- sample_wheel_floor( fl );
- sample_wheel_floor( fr );
- sample_wheel_floor( bc );
-
- vg_line( fl, fr, 0xff00ffff );
- vg_line( fr, bc, 0xff00ffff );
- vg_line( bc, fl, 0xff00ffff );
-
- v3f norm;
- v3f v0, v1;
- v3_sub( fr, fl, v0 );
- v3_sub( bc, fl, v1 );
- v3_cross( v1, v0, norm );
- v3_normalize( norm );
-
- /*
- * Jesus take the wheel
- */
- float steer_penalty = 1.0f-v3_dot( dir, driver->transform[0] );
- steer_penalty /= vg.time_delta;
- steer_penalty *= 30.0f;
-
- float target_speed = vg_maxf( 16.0f * (1.0f-steer_penalty), 0.1f ),
- accel = target_speed - driver->speed;
- driver->speed = stable_force( driver->speed, accel*vg.time_delta*2.0f );
- driver->t += driver->speed*mod*vg.time_delta;
-
- /*
- * Update transform
- */
- v3_cross( dir, norm, side );
- v3_copy( dir, driver->transform[0] );
- v3_copy( norm, driver->transform[1] );
- v3_copy( side, driver->transform[2] );
-
- v3_add( fl, fr, pc );
- v3_add( bc, pc, pc );
- v3_muls( pc, 1.0f/3.0f, pc );
- v3_copy( pc, driver->transform[3] );
-}
-
-static void traffic_visualize( traffic_node *system, int count )
-{
- for( int i=0; i<count; i++ )
- {
- traffic_node *tn = &system[i];
-
- traffic_visualize_link( tn, tn->next );
- traffic_visualize_link( tn, tn->next1 );
- }
-}
-
-static void traffic_visualize_car( traffic_driver *driver )
-{
- vg_line_boxf_transformed( driver->transform,
- (boxf){{-1.0f,0.0f,-0.5f},
- { 1.0f,0.0f, 0.5f}}, 0xff00ff00 );
-}
-
-#endif /* TRAFFIC_H */
+++ /dev/null
-#pragma once
-#include "vg/vg_engine.h"
-#include "vg/vg_platform.h"
-#include "vg/vg_m.h"
-#include "vg/vg_lines.h"
-#include "vg/vg_async.h"
-#include "vg/vg_camera.h"
-#include "trail.h"
-#include "shaders/particle.h"
-#include "shaders/trail.h"
-
-static void trail_increment( trail_system *sys ){
- sys->head ++;
-
- if( sys->head == sys->max )
- sys->head = 0;
-
- /* undesirable effect: will remove active points if out of space! */
- if( sys->count < sys->max )
- sys->count ++;
-}
-
-void trail_system_update( trail_system *sys, f32 dt,
- v3f co, v3f normal, f32 alpha )
-{
- /* update existing points and clip dead ones */
- bool clip_allowed = 1;
- for( i32 i=0; i<sys->count; i ++ ){
- i32 i0 = sys->head - sys->count + i;
- if( i0 < 0 ) i0 += sys->max;
-
- trail_point *p0 = &sys->array[i0];
- p0->alpha -= dt/sys->lifetime;
-
- if( clip_allowed ){
- if( p0->alpha <= 0.0f )
- sys->count --;
- else
- clip_allowed = 0;
- }
- }
-
- i32 icur = sys->head -1,
- iprev = sys->head -2,
- ihead = sys->head;
-
- if( icur < 0 ) icur += sys->max;
- if( iprev < 0 ) iprev += sys->max;
-
- trail_point *pcur = &sys->array[ icur ],
- *pprev = &sys->array[ iprev ],
- *phead = &sys->array[ ihead ],
- *pdest = NULL;
- v3f dir;
-
- f32 k_min = 0.001f;
-
- if( sys->count == 0 ){
- trail_increment( sys );
- v3_copy( (v3f){0,0,-1}, dir );
- pdest = phead;
- }
- else if( sys->count == 1 ){
- if( v3_dist2( pcur->co, co ) < k_min*k_min )
- return;
-
- trail_increment( sys );
- pdest = phead;
- v3_sub( co, pcur->co, dir );
- }
- else {
- if( v3_dist2( pprev->co, co ) < k_min*k_min )
- return;
-
- if( v3_dist2( pprev->co, co ) > sys->min_dist*sys->min_dist ){
- trail_increment( sys );
- pdest = phead;
- }
- else
- pdest = pcur;
-
- v3_sub( co, pprev->co, dir );
- }
-
- v3_cross( dir, normal, pdest->right );
- v3_normalize( pdest->right );
- v3_copy( co, pdest->co );
- v3_copy( normal, pdest->normal );
- pdest->alpha = alpha;
-}
-
-void trail_system_debug( trail_system *sys )
-{
- for( i32 i=0; i<sys->count; i ++ ){
- i32 i0 = sys->head - sys->count + i;
- if( i0 < 0 ) i0 += sys->max;
-
- trail_point *p0 = &sys->array[i0];
- vg_line_point( p0->co, 0.04f, 0xff000000 | (u32)(p0->alpha*255.0f) );
- vg_line_arrow( p0->co, p0->right, 0.3f, VG__GREEN );
-
- if( i == sys->count-1 ) break;
-
- i32 i1 = i0+1;
- if( i1 == sys->max ) i1 = 0;
-
- trail_point *p1 = &sys->array[i1];
- vg_line( p0->co, p1->co, VG__RED );
- }
-}
-
-struct trail_init_args {
- trail_system *sys;
-};
-
-void async_trail_init( void *payload, u32 size )
-{
- struct trail_init_args *args = payload;
- trail_system *sys = args->sys;
-
- glGenVertexArrays( 1, &sys->vao );
- glGenBuffers( 1, &sys->vbo );
- glBindVertexArray( sys->vao );
-
- size_t stride = sizeof(trail_vert);
-
- glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
- glBufferData( GL_ARRAY_BUFFER, sys->max*stride*2, NULL, GL_DYNAMIC_DRAW );
-
- /* 0: coordinates */
- glVertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, stride, (void*)0 );
- glEnableVertexAttribArray( 0 );
-}
-
-void trail_alloc( trail_system *sys, u32 max )
-{
- size_t stride = sizeof(trail_vert);
- sys->max = max;
- sys->array = vg_linear_alloc( vg_mem.rtmemory, max*sizeof(trail_point) );
- sys->vertices = vg_linear_alloc( vg_mem.rtmemory, max*stride*2 );
-
- vg_async_item *call = vg_async_alloc( sizeof(struct trail_init_args) );
-
- struct trail_init_args *init = call->payload;
- init->sys = sys;
- vg_async_dispatch( call, async_trail_init );
-}
-
-void trail_system_prerender( trail_system *sys )
-{
- if( sys->count < 2 ) return;
-
- for( i32 i=0; i<sys->count; i ++ ){
- i32 i0 = sys->head - sys->count + i;
- if( i0 < 0 ) i0 += sys->max;
-
- trail_point *p0 = &sys->array[i0];
- trail_vert *v0 = &sys->vertices[i*2+0],
- *v1 = &sys->vertices[i*2+1];
-
- v3_muladds( p0->co, p0->right, -sys->width, v0->co );
- v3_muladds( p0->co, p0->right, sys->width, v1->co );
- v0->co[3] = p0->alpha;
- v1->co[3] = p0->alpha;
- }
-
- glBindVertexArray( sys->vao );
-
- size_t stride = sizeof(trail_vert);
- glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
- glBufferSubData( GL_ARRAY_BUFFER, 0, sys->count*stride*2, sys->vertices );
-}
-
-void trail_system_render( trail_system *sys, vg_camera *cam )
-{
- if( sys->count < 2 ) return;
- glDisable( GL_CULL_FACE );
- glEnable( GL_DEPTH_TEST );
-
- shader_trail_use();
- shader_trail_uPv( cam->mtx.pv );
- shader_trail_uPvPrev( cam->mtx_prev.pv );
- shader_trail_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
-
- glBindVertexArray( sys->vao );
- glDrawArrays( GL_TRIANGLE_STRIP, 0, sys->count*2 );
-}
+++ /dev/null
-#pragma once
-
-typedef struct trail_system trail_system;
-typedef struct trail_point trail_point;
-typedef struct trail_vert trail_vert;
-
-struct trail_system {
- struct trail_point {
- v3f co, normal, right;
- f32 alpha;
- }
- *array;
-
-#pragma pack(push,1)
- struct trail_vert {
- v4f co; /* xyz: position, w: alpha */
- }
- *vertices;
-#pragma pack(pop)
-
- i32 head, count, max;
- GLuint vao, vbo;
-
- /* render settings */
- f32 width, lifetime, min_dist;
-};
-
-void trail_alloc( trail_system *sys, u32 max );
-void trail_system_update( trail_system *sys, f32 dt, v3f co,
- v3f normal, f32 alpha );
-void trail_system_debug( trail_system *sys );
-void trail_system_prerender( trail_system *sys );
-void trail_system_render( trail_system *sys, vg_camera *cam );
+++ /dev/null
-#include "skaterift.h"
-#include "vehicle.h"
-#include "scene_rigidbody.h"
-
-struct drivable_vehicle gzoomer =
-{
- .rb.co = {-2000,-2000,-2000}
-};
-
-int spawn_car( int argc, const char *argv[] )
-{
- v3f ra, rb, rx;
- v3_copy( g_render.cam.pos, ra );
- v3_muladds( ra, g_render.cam.transform[2], -10.0f, rb );
-
- float t;
- if( spherecast_world( world_current_instance(),
- ra, rb, 1.0f, &t, rx, 0 ) != -1 )
- {
- v3_lerp( ra, rb, t, gzoomer.rb.co );
- gzoomer.rb.co[1] += 4.0f;
- q_axis_angle( gzoomer.rb.q, (v3f){1.0f,0.0f,0.0f}, 0.001f );
- v3_zero( gzoomer.rb.v );
- v3_zero( gzoomer.rb.w );
-
- rb_update_matrices( &gzoomer.rb );
- gzoomer.alive = 1;
-
- vg_success( "Spawned car\n" );
- }
- else{
- vg_error( "Can't spawn here\n" );
- }
-
- return 0;
-}
-
-void vehicle_init(void)
-{
- q_identity( gzoomer.rb.q );
- v3_zero( gzoomer.rb.w );
- v3_zero( gzoomer.rb.v );
- v3_zero( gzoomer.rb.co );
- rb_setbody_sphere( &gzoomer.rb, 1.0f, 8.0f, 1.0f );
-
- VG_VAR_F32( k_car_spring, flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_spring_damp, flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_spring_length, flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_wheel_radius, flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_friction_lat, flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_friction_roll, flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_drive_force, flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_air_resistance,flags=VG_VAR_PERSISTENT );
- VG_VAR_F32( k_car_downforce, flags=VG_VAR_PERSISTENT );
-
- VG_VAR_I32( gzoomer.inside );
-
- vg_console_reg_cmd( "spawn_car", spawn_car, NULL );
-
- v3_copy((v3f){ -1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[0] );
- v3_copy((v3f){ 1.0f, -0.25f, -1.5f }, gzoomer.wheels_local[1] );
- v3_copy((v3f){ -1.0f, -0.25f, 1.5f }, gzoomer.wheels_local[2] );
- v3_copy((v3f){ 1.0f, -0.25f, 1.5f }, gzoomer.wheels_local[3] );
-}
-
-void vehicle_wheel_force( int index )
-{
- v3f pa, pb, n;
- m4x3_mulv( gzoomer.rb.to_world, gzoomer.wheels_local[index], pa );
- v3_muladds( pa, gzoomer.rb.to_world[1], -k_car_spring_length, pb );
-
-
-#if 1
- float t;
- if( spherecast_world( world_current_instance(), pa, pb,
- k_car_wheel_radius, &t, n, 0 ) == -1 )
- { t = 1.0f;
- }
-
-#else
-
- v3f dir;
- v3_muls( gzoomer.rb.up, -1.0f, dir );
-
- ray_hit hit;
- hit.dist = k_car_spring_length;
- ray_world( pa, dir, &hit );
-
- float t = hit.dist / k_car_spring_length;
-
-#endif
-
- v3f pc;
- v3_lerp( pa, pb, t, pc );
-
- m4x3f mtx;
- m3x3_copy( gzoomer.rb.to_world, mtx );
- v3_copy( pc, mtx[3] );
- vg_line_sphere( mtx, k_car_wheel_radius, VG__BLACK );
- vg_line( pa, pc, VG__WHITE );
- v3_copy( pc, gzoomer.wheels[index] );
-
- if( t < 1.0f ){
- /* spring force */
- float Fv = (1.0f-t) * k_car_spring*vg.time_fixed_delta;
-
- v3f delta;
- v3_sub( pa, gzoomer.rb.co, delta );
-
- v3f rv;
- v3_cross( gzoomer.rb.w, delta, rv );
- v3_add( gzoomer.rb.v, rv, rv );
-
- Fv += v3_dot(rv, gzoomer.rb.to_world[1])
- * -k_car_spring_damp*vg.time_fixed_delta;
-
- /* scale by normal incident */
- Fv *= v3_dot( n, gzoomer.rb.to_world[1] );
-
- v3f F;
- v3_muls( gzoomer.rb.to_world[1], Fv, F );
- rb_linear_impulse( &gzoomer.rb, delta, F );
-
- /* friction vectors
- * -------------------------------------------------------------*/
- v3f tx, ty;
-
- if( index <= 1 )
- v3_cross( gzoomer.steerv, n, tx );
- else
- v3_cross( n, gzoomer.rb.to_world[2], tx );
- v3_cross( tx, n, ty );
-
- v3_copy( tx, gzoomer.tangent_vectors[ index ][0] );
- v3_copy( ty, gzoomer.tangent_vectors[ index ][1] );
-
- gzoomer.normal_forces[ index ] = Fv;
- gzoomer.tangent_forces[ index ][0] = 0.0f;
- gzoomer.tangent_forces[ index ][1] = 0.0f;
-
- /* orient inverse inertia tensors */
- v3f raW;
- m3x3_mulv( gzoomer.rb.to_world, gzoomer.wheels_local[index], raW );
-
- v3f raCtx, raCtxI, raCty, raCtyI;
- v3_cross( tx, raW, raCtx );
- v3_cross( ty, raW, raCty );
- m3x3_mulv( gzoomer.rb.iIw, raCtx, raCtxI );
- m3x3_mulv( gzoomer.rb.iIw, raCty, raCtyI );
-
- gzoomer.tangent_mass[index][0] = gzoomer.rb.inv_mass;
- gzoomer.tangent_mass[index][0] += v3_dot( raCtx, raCtxI );
- gzoomer.tangent_mass[index][0] = 1.0f/gzoomer.tangent_mass[index][0];
-
- gzoomer.tangent_mass[index][1] = gzoomer.rb.inv_mass;
- gzoomer.tangent_mass[index][1] += v3_dot( raCty, raCtyI );
- gzoomer.tangent_mass[index][1] = 1.0f/gzoomer.tangent_mass[index][1];
-
- /* apply drive force */
- if( index >= 2 ){
- v3_muls( ty, -gzoomer.drive * k_car_drive_force
- * vg.time_fixed_delta, F );
- rb_linear_impulse( &gzoomer.rb, raW, F );
- }
- }
- else{
- gzoomer.normal_forces[ index ] = 0.0f;
- gzoomer.tangent_forces[ index ][0] = 0.0f;
- gzoomer.tangent_forces[ index ][1] = 0.0f;
- }
-}
-
-void vehicle_solve_friction(void)
-{
- rigidbody *rb = &gzoomer.rb;
- for( int i=0; i<4; i++ ){
- v3f raW;
- m3x3_mulv( rb->to_world, gzoomer.wheels_local[i], raW );
-
- v3f rv;
- v3_cross( rb->w, raW, rv );
- v3_add( rb->v, rv, rv );
-
- float fx = k_car_friction_lat * gzoomer.normal_forces[i],
- fy = k_car_friction_roll * gzoomer.normal_forces[i],
- vtx = v3_dot( rv, gzoomer.tangent_vectors[i][0] ),
- vty = v3_dot( rv, gzoomer.tangent_vectors[i][1] ),
- lambdax = gzoomer.tangent_mass[i][0] * -vtx,
- lambday = gzoomer.tangent_mass[i][1] * -vty;
-
- float tempx = gzoomer.tangent_forces[i][0],
- tempy = gzoomer.tangent_forces[i][1];
- gzoomer.tangent_forces[i][0] = vg_clampf( tempx + lambdax, -fx, fx );
- gzoomer.tangent_forces[i][1] = vg_clampf( tempy + lambday, -fy, fy );
- lambdax = gzoomer.tangent_forces[i][0] - tempx;
- lambday = gzoomer.tangent_forces[i][1] - tempy;
-
- v3f impulsex, impulsey;
- v3_muls( gzoomer.tangent_vectors[i][0], lambdax, impulsex );
- v3_muls( gzoomer.tangent_vectors[i][1], lambday, impulsey );
- rb_linear_impulse( rb, raW, impulsex );
- rb_linear_impulse( rb, raW, impulsey );
- }
-}
-
-void vehicle_update_fixed(void)
-{
- if( !gzoomer.alive )
- return;
-
- rigidbody *rb = &gzoomer.rb;
-
- v3_muls( rb->to_world[2], -cosf(gzoomer.steer), gzoomer.steerv );
- v3_muladds( gzoomer.steerv, rb->to_world[0],
- sinf(gzoomer.steer), gzoomer.steerv );
-
- /* apply air resistance */
- v3f Fair, Fdown;
-
- v3_muls( rb->v, -k_car_air_resistance, Fair );
- v3_muls( rb->to_world[1], -fabsf(v3_dot( rb->v, rb->to_world[2] )) *
- k_car_downforce, Fdown );
-
- v3_muladds( rb->v, Fair, vg.time_fixed_delta, rb->v );
- v3_muladds( rb->v, Fdown, vg.time_fixed_delta, rb->v );
-
- for( int i=0; i<4; i++ )
- vehicle_wheel_force( i );
-
- rigidbody _null = {0};
- _null.inv_mass = 0.0f;
- m3x3_zero( _null.iI );
-
- rb_ct manifold[64];
- int len = rb_sphere__scene( rb->to_world, 1.0f, NULL,
- world_current_instance()->geo_bh,
- manifold, 0 );
- for( int j=0; j<len; j++ ){
- manifold[j].rba = rb;
- manifold[j].rbb = &_null;
- }
- rb_manifold_filter_coplanar( manifold, len, 0.05f );
-
- if( len > 1 ){
- rb_manifold_filter_backface( manifold, len );
- rb_manifold_filter_joint_edges( manifold, len, 0.05f );
- rb_manifold_filter_pairs( manifold, len, 0.05f );
- }
- len = rb_manifold_apply_filtered( manifold, len );
-
- rb_presolve_contacts( manifold, vg.time_fixed_delta, len );
- for( int i=0; i<8; i++ ){
- rb_solve_contacts( manifold, len );
- vehicle_solve_friction();
- }
-
- rb_iter( rb );
- rb_update_matrices( rb );
-}
-
-void vehicle_update_post(void)
-{
- if( !gzoomer.alive )
- return;
-
- vg_line_sphere( gzoomer.rb.to_world, 1.0f, VG__WHITE );
-
- /* draw friction vectors */
- v3f p0, px, py;
-
- for( int i=0; i<4; i++ ){
- v3_copy( gzoomer.wheels[i], p0 );
- v3_muladds( p0, gzoomer.tangent_vectors[i][0], 0.5f, px );
- v3_muladds( p0, gzoomer.tangent_vectors[i][1], 0.5f, py );
-
- vg_line( p0, px, VG__RED );
- vg_line( p0, py, VG__GREEN );
- }
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_rigidbody.h"
-#include "player.h"
-#include "world.h"
-#include "world_physics.h"
-
-static float k_car_spring = 1.0f,
- k_car_spring_damp = 0.001f,
- k_car_spring_length = 0.5f,
- k_car_wheel_radius = 0.2f,
- k_car_friction_lat = 0.6f,
- k_car_friction_roll = 0.01f,
- k_car_drive_force = 1.0f,
- k_car_air_resistance = 0.1f,
- k_car_downforce = 0.5f;
-
-typedef struct drivable_vehicle drivable_vehicle;
-struct drivable_vehicle
-{
- int alive, inside;
- rigidbody rb;
-
- v3f wheels[4];
-
- float tangent_mass[4][2],
- normal_forces[4],
- tangent_forces[4][2];
-
- float steer, drive;
- v3f steerv;
-
- v3f tangent_vectors[4][2];
- v3f wheels_local[4];
-}
-extern gzoomer;
-
-int spawn_car( int argc, const char *argv[] );
-void vehicle_init(void);
-void vehicle_wheel_force( int index );
-void vehicle_solve_friction(void);
-void vehicle_update_fixed(void);
-void vehicle_update_post(void);
+++ /dev/null
-#include "vg/vg_engine.h"
-#include "vg/vg_tex.h"
-#include "vg/vg_image.h"
-#include "vg/vg_msg.h"
-#include "vg/vg_binstr.h"
-#include "vg/vg_loader.h"
-#include "vg/vg_io.h"
-#include "ent_skateshop.h"
-
-#include "vg/vg_steam_auth.h"
-#include "vg/vg_steam_ugc.h"
-#include "vg/vg_steam_friends.h"
-#include "steam.h"
-#include "workshop.h"
-
-struct workshop_form workshop_form;
-
-static struct ui_enum_opt workshop_form_visibility_opts[] = {
- { k_ERemoteStoragePublishedFileVisibilityPublic, "Public" },
- { k_ERemoteStoragePublishedFileVisibilityUnlisted, "Unlisted" },
- { k_ERemoteStoragePublishedFileVisibilityFriendsOnly, "Friends Only" },
- { k_ERemoteStoragePublishedFileVisibilityPrivate, "Private" },
-};
-
-static struct ui_enum_opt workshop_form_type_opts[] = {
- { k_addon_type_none, "None" },
- { k_addon_type_board, "Board" },
- { k_addon_type_world, "World" },
- { k_addon_type_player, "Player" },
-};
-
-/*
- * Close the form and discard UGC query result
- */
-static void workshop_quit_form(void){
- player_board_unload( &workshop_form.board_model );
- workshop_form.file_intent = k_workshop_form_file_intent_none;
-
- if( workshop_form.ugc_query.result == k_EResultOK ){
- workshop_form.ugc_query.result = k_EResultNone;
-
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(
- hSteamUGC, workshop_form.ugc_query.handle );
- }
-
- workshop_form.page = k_workshop_form_hidden;
- workshop_form.op = k_workshop_op_none;
-}
-
-/*
- * Delete all information about the submission
- */
-static void workshop_reset_submission_data(void)
-{
- workshop_form.submission.file_id = 0; /* assuming id of 0 is none/invalid */
- workshop_form.submission.description[0] = '\0';
- workshop_form.submission.title[0] = '\0';
- workshop_form.submission.author[0] = '\0';
- workshop_form.submission.submission_type_selection =
- k_addon_type_none;
- workshop_form.submission.type = k_addon_type_none;
-
- workshop_form.submission.visibility =
- k_ERemoteStoragePublishedFileVisibilityPublic;
-
- workshop_form.addon_folder[0] = '\0';
- player_board_unload( &workshop_form.board_model );
- workshop_form.file_intent = k_workshop_form_file_intent_none;
-}
-
-
-/*
- * Mostly copies of what it sais on the Steam API documentation
- */
-static const char *workshop_EResult_user_string( EResult result )
-{
- switch( result ){
- case k_EResultInsufficientPrivilege:
- return "Your account is currently restricted from uploading content "
- "due to a hub ban, account lock, or community ban. You need to "
- "contact Steam Support to resolve the issue.";
- case k_EResultBanned:
- return "You do not have permission to upload content to this hub "
- "because you have an active VAC or Game ban.";
- case k_EResultTimeout:
- return "The operation took longer than expected, so it was discarded. "
- "Please try again.";
- case k_EResultNotLoggedOn:
- return "You are currently not logged into Steam.";
- case k_EResultServiceUnavailable:
- return "The workshop server is having issues or is unavailable, "
- "please try again.";
- case k_EResultInvalidParam:
- return "One of the submission fields contains something not being "
- "accepted by that field.";
- case k_EResultAccessDenied:
- return "There was a problem trying to save the title and description. "
- "Access was denied.";
- case k_EResultLimitExceeded:
- return "You have exceeded your Steam Cloud quota. If you wish to "
- "upload this file, you must remove some published items.";
- case k_EResultFileNotFound:
- return "The uploaded file could not be found.";
- case k_EResultDuplicateRequest:
- return "The file was already successfully uploaded.";
- case k_EResultDuplicateName:
- return "You already have a Steam Workshop item with that name.";
- case k_EResultServiceReadOnly:
- return "Due to a recent password or email change, you are not allowed "
- "to upload new content. Usually this restriction will expire in"
- " 5 days, but can last up to 30 days if the account has been "
- "inactive recently.";
- default:
- return "Operation failed for an error which has not been accounted for "
- "by the programmer. Try again, sorry :)";
- }
-}
-
-/*
- * op: k_workshop_form_op_publishing_update
- * ----------------------------------------------------------------------------
- */
-
-/*
- * The endpoint of this operation
- */
-static void on_workshop_update_result( void *data, void *user )
-{
- vg_info( "Recieved workshop update result\n" );
- SubmitItemUpdateResult_t *result = data;
-
- /* this seems to be set here, but my account definitely has accepted it */
- if( result->m_bUserNeedsToAcceptWorkshopLegalAgreement ){
- vg_warn( "Workshop agreement currently not accepted\n" );
- }
-
- if( result->m_eResult == k_EResultOK ){
- workshop_form.page = k_workshop_form_closing_good;
- workshop_form.failure_or_success_string = "Uploaded workshop file!";
- vg_success( "file uploaded\n" );
- }
- else{
- workshop_form.page = k_workshop_form_closing_bad;
- workshop_form.failure_or_success_string =
- workshop_EResult_user_string( result->m_eResult );
-
- vg_error( "Error with the submitted file (%d)\n", result->m_eResult );
- }
- workshop_form.op = k_workshop_op_none;
-}
-
-static const char *workshop_filetype_folder(void){
- enum addon_type type = workshop_form.submission.type;
- if ( type == k_addon_type_board ) return "boards/";
- else if( type == k_addon_type_player ) return "playermodels/";
- else if( type == k_addon_type_world ) return "maps/";
-
- return "unknown_addon_type/";
-}
-
-/*
- * reciever on completion of packaging the files, it will then start the item
- * update with Steam API
- */
-static void workshop_form_upload_submission( PublishedFileId_t file_id,
- char *metadata )
-{
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- UGCUpdateHandle_t handle
- = SteamAPI_ISteamUGC_StartItemUpdate( hSteamUGC, SKATERIFT_APPID,
- file_id );
-
- /* TODO: Handle failure cases for these */
-
- SteamAPI_ISteamUGC_SetItemMetadata( hSteamUGC, handle, metadata );
-
- if( workshop_form.submission.submit_title ){
- vg_info( "Setting title\n" );
- SteamAPI_ISteamUGC_SetItemTitle( hSteamUGC, handle,
- workshop_form.submission.title );
- }
-
- if( workshop_form.submission.submit_description ){
- vg_info( "Setting description\n" );
- SteamAPI_ISteamUGC_SetItemDescription( hSteamUGC, handle,
- workshop_form.submission.description);
- }
-
- if( workshop_form.submission.submit_file_and_image ){
- char path_buf[4096];
- vg_str folder;
- vg_strnull( &folder, path_buf, 4096 );
- vg_strcat( &folder, vg.base_path );
-
- vg_strcat( &folder, workshop_filetype_folder() );
- vg_strcat( &folder, workshop_form.addon_folder );
-
- vg_info( "Setting item content\n" );
- SteamAPI_ISteamUGC_SetItemContent( hSteamUGC, handle, folder.buffer );
-
- vg_str preview = folder;
- vg_strcat( &preview, "/preview.jpg" );
-
- vg_info( "Setting preview image\n" );
- SteamAPI_ISteamUGC_SetItemPreview( hSteamUGC, handle, preview.buffer );
- }
-
- vg_info( "Setting visibility\n" );
- SteamAPI_ISteamUGC_SetItemVisibility( hSteamUGC, handle,
- workshop_form.submission.visibility );
-
- vg_info( "Submitting updates\n" );
- vg_steam_async_call *call = vg_alloc_async_steam_api_call();
- call->userdata = NULL;
- call->p_handler = on_workshop_update_result;
- call->id = SteamAPI_ISteamUGC_SubmitItemUpdate( hSteamUGC, handle, "" );
-}
-
-/*
- * Steam API call result for when we've created a new item on their network, or
- * not, if it has failed
- */
-static void on_workshop_createitem( void *data, void *user )
-{
- CreateItemResult_t *result = data;
-
- if( result->m_eResult == k_EResultOK ){
- vg_info( "Created workshop file with id: %lu\n",
- result->m_nPublishedFileId );
-
- if( result->m_bUserNeedsToAcceptWorkshopLegalAgreement ){
- vg_warn( "Workshop agreement currently not accepted\n" );
- }
-
- workshop_form_upload_submission( result->m_nPublishedFileId, user );
- }
- else{
- const char *errstr = workshop_EResult_user_string( result->m_eResult );
-
- if( errstr ){
- vg_error( "ISteamUGC_CreateItem() failed(%d): '%s' \n",
- result->m_eResult, errstr );
- }
-
- workshop_form.page = k_workshop_form_closing_bad;
- workshop_form.failure_or_success_string = errstr;
- }
-}
-
-/*
- * Starts the workshop upload process through Steam API
- */
-static void workshop_form_async_submit_begin( void *payload, u32 size )
-{
-
- /* use existing file */
- if( workshop_form.submission.file_id ){
- workshop_form_upload_submission( workshop_form.submission.file_id,
- payload );
- }
- else{
- vg_steam_async_call *call = vg_alloc_async_steam_api_call();
- call->userdata = payload;
- call->p_handler = on_workshop_createitem;
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- call->id = SteamAPI_ISteamUGC_CreateItem( hSteamUGC, SKATERIFT_APPID,
- k_EWorkshopFileTypeCommunity );
- }
-}
-
-/*
- * Downloads the framebuffer into scratch memory
- */
-static void workshop_form_async_download_image( void *payload, u32 size )
-{
- int w, h;
- vg_framebuffer_get_res( g_render.fb_workshop_preview, &w, &h );
- vg_linear_clear( vg_mem.scratch );
- workshop_form.img_buffer = vg_linear_alloc( vg_mem.scratch, w*h*3 );
-
- vg_info( "read framebuffer: glReadPixels( %dx%d )\n", w,h );
-
- glBindFramebuffer( GL_READ_FRAMEBUFFER, g_render.fb_workshop_preview->id );
- glReadBuffer( GL_COLOR_ATTACHMENT0 );
- glReadPixels( 0,0, w,h, GL_RGB, GL_UNSIGNED_BYTE, workshop_form.img_buffer );
-
- workshop_form.img_w = w;
- workshop_form.img_h = h;
-}
-
-/*
- * Thread which kicks off the upload process
- */
-static void _workshop_form_submit_thread( void *data )
-{
- vg_async_call( workshop_form_async_download_image, NULL, 0 );
- vg_async_stall();
-
- char path_buf[4096];
- vg_str folder;
- vg_strnull( &folder, path_buf, 4096 );
-
- vg_strcat( &folder, workshop_filetype_folder() );
- vg_strcat( &folder, workshop_form.addon_folder );
-
- if( !vg_strgood(&folder) ){
- vg_error( "addon folder path too long\n" );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- /*
- * Create the metadata file
- * -----------------------------------------------------------------------*/
- u8 descriptor_buf[ 512 ];
- vg_msg descriptor;
- vg_msg_init( &descriptor, descriptor_buf, sizeof(descriptor_buf) );
- vg_linear_clear( vg_mem.scratch );
-
- /* short description */
- vg_msg_frame( &descriptor, "workshop" );
- vg_msg_wkvstr( &descriptor, "title", workshop_form.submission.title );
- //vg_msg_wkvstr( &descriptor, "author", "unknown" );
- vg_msg_wkvnum( &descriptor, "type", k_vg_msg_u32, 1,
- &workshop_form.submission.type );
- vg_msg_wkvstr( &descriptor, "folder", workshop_form.addon_folder );
- vg_msg_end_frame( &descriptor );
- //vg_msg_wkvstr( &descriptor, "location", "USA" );
-
- char *short_descriptor_str =
- vg_linear_alloc( vg_mem.scratch, vg_align8(descriptor.cur.co*2+1));
- vg_bin_str( descriptor_buf, short_descriptor_str, descriptor.cur.co );
- short_descriptor_str[descriptor.cur.co*2] = '\0';
- vg_info( "binstr: %s\n", short_descriptor_str );
-
- vg_dir dir;
- if( !vg_dir_open( &dir, folder.buffer ) )
- {
- vg_error( "could not open addon folder '%s'\n", folder.buffer );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- while( vg_dir_next_entry(&dir) )
- {
- if( vg_dir_entry_type(&dir) == k_vg_entry_type_file )
- {
- const char *d_name = vg_dir_entry_name(&dir);
- if( d_name[0] == '.' ) continue;
-
- vg_str file = folder;
- vg_strcat( &file, "/" );
- vg_strcat( &file, d_name );
- if( !vg_strgood( &file ) ) continue;
-
- char *ext = vg_strch( &file, '.' );
- if( !ext ) continue;
- if( strcmp(ext,".mdl") ) continue;
-
- vg_msg_wkvstr( &descriptor, "content", d_name );
- break;
- }
- }
- vg_dir_close(&dir);
-
- vg_str descriptor_file = folder;
- vg_strcat( &descriptor_file, "/addon.inf" );
- if( !vg_strgood(&descriptor_file) ){
- vg_error( "Addon info path too long\n" );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- FILE *fp = fopen( descriptor_file.buffer, "wb" );
- if( !fp ){
- vg_error( "Could not open addon info file '%s'\n",
- descriptor_file.buffer );
- workshop_form.op = k_workshop_op_none;
- return;
- }
- fwrite( descriptor_buf, descriptor.cur.co, 1, fp );
- fclose( fp );
-
- /* Save the preview
- * -----------------------------------------------------------------------*/
- vg_str preview = folder;
- vg_strcat( &preview, "/preview.jpg" );
-
- if( !vg_strgood(&preview) ){
- vg_error( "preview image path too long\n" );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- int w = workshop_form.img_w,
- h = workshop_form.img_h;
-
- vg_info( "writing: %s (%dx%d @90%%)\n", preview.buffer, w,h );
- stbi_flip_vertically_on_write(1);
- stbi_write_jpg( preview.buffer, w,h, 3, workshop_form.img_buffer, 90 );
-
- vg_async_call( workshop_form_async_submit_begin, short_descriptor_str, 0 );
-}
-
-/*
- * Entry point for the publishing submission operation
- */
-static void workshop_op_submit( ui_context *ctx )
-{
- /* TODO: Show these errors to the user */
- if( workshop_form.submission.submit_title )
- {
- if( !workshop_form.submission.title[0] )
- {
- ui_start_modal( ctx, "Cannot submit because a title is required\n",
- UI_MODAL_WARN );
- workshop_form.op = k_workshop_op_none;
- return;
- }
- }
-
- if( workshop_form.submission.submit_description )
- {
- if( !workshop_form.submission.description[0] )
- {
- ui_start_modal( ctx,
- "Cannot submit because a description is required\n",
- UI_MODAL_WARN );
- workshop_form.op = k_workshop_op_none;
- return;
- }
- }
-
- if( workshop_form.submission.submit_file_and_image )
- {
- if( workshop_form.file_intent == k_workshop_form_file_intent_none )
- {
- ui_start_modal( ctx, "Cannot submit because the file is "
- "empty or unspecified\n", UI_MODAL_WARN );
- workshop_form.op = k_workshop_op_none;
- return;
- }
- }
-
- player_board_unload( &workshop_form.board_model );
- workshop_form.file_intent = k_workshop_form_file_intent_none;
- workshop_form.op = k_workshop_op_publishing_update;
-
- vg_loader_start( _workshop_form_submit_thread, NULL );
-}
-
-/*
- * op: k_workshop_form_op_loading_model
- * -----------------------------------------------------------------------------
- */
-
-/*
- * Reciever for completion of the model file load
- */
-static void workshop_form_loadmodel_async_complete( void *payload, u32 size )
-{
- v2_zero( workshop_form.view_angles );
- v3_zero( workshop_form.view_offset );
- workshop_form.view_dist = 1.0f;
- workshop_form.view_changed = 1;
- workshop_form.file_intent = k_workshop_form_file_intent_new;
-
- vg_success( "workshop async load complete\n" );
- workshop_form.op = k_workshop_op_none;
-}
-
-/*
- * Reciever for failure to load
- */
-static void workshop_form_loadmodel_async_error( void *payload, u32 size ){
-}
-
-/*
- * Thread which loads the model from the disk
- */
-static void _workshop_form_load_thread( void *data )
-{
- char path_buf[4096];
- vg_str folder;
- vg_strnull( &folder, path_buf, 4096 );
-
- vg_strcat( &folder, workshop_filetype_folder() );
- vg_strcat( &folder, workshop_form.addon_folder );
-
- if( !vg_strgood(&folder) ){
- vg_error( "workshop async load failed: path too long\n" );
- vg_async_call( workshop_form_loadmodel_async_error, NULL, 0 );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- vg_dir dir;
- if( !vg_dir_open( &dir, folder.buffer ) ){
- vg_error( "workshop async load failed: could not open folder\n" );
- vg_async_call( workshop_form_loadmodel_async_error, NULL, 0 );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- vg_info( "Searching %s for model files\n", folder.buffer );
-
- int found_mdl = 0;
- while( vg_dir_next_entry(&dir) ){
- if( vg_dir_entry_type(&dir) == k_vg_entry_type_file ){
- const char *d_name = vg_dir_entry_name(&dir);
- if( d_name[0] == '.' ) continue;
-
- vg_str file = folder;
- vg_strcat( &file, "/" );
- vg_strcat( &file, d_name );
- if( !vg_strgood( &file ) ) continue;
-
- char *ext = vg_strch( &file, '.' );
- if( !ext ) continue;
- if( strcmp(ext,".mdl") ) continue;
- found_mdl = 1;
- break;
- }
- }
- vg_dir_close(&dir);
-
- if( !found_mdl ){
- vg_error( "workshop async load failed: no model files found\n" );
- vg_async_call( workshop_form_loadmodel_async_error, NULL, 0 );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- if( workshop_form.submission.type == k_addon_type_board )
- player_board_load( &workshop_form.board_model, path_buf );
- else if( workshop_form.submission.type == k_addon_type_player )
- player_model_load( &workshop_form.player_model, path_buf );
-
- vg_async_call( workshop_form_loadmodel_async_complete, NULL, 0 );
-}
-
-/*
- * Entry point for load model operation
- */
-static void workshop_op_load_model( ui_context *ctx )
-{
- world_instance *world = world_current_instance();
- workshop_form.view_world = world;
-
- if( workshop_form.submission.type == k_addon_type_board )
- {
- if( mdl_arrcount( &world->ent_swspreview ) )
- {
- workshop_form.ptr_ent = mdl_arritm( &world->ent_swspreview, 0 );
- }
- else
- {
- ui_start_modal( ctx, "There is no ent_swspreview in the level. \n"
- "Cannot publish here\n", UI_MODAL_BAD );
- workshop_form.op = k_workshop_op_none;
- return;
- }
- }
- else if( workshop_form.submission.type == k_addon_type_player ){}
- else
- {
- ui_start_modal( ctx, "Don't know how to prepare for this item type. \n"
- "Please contact the developers.\n", UI_MODAL_BAD );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- workshop_form.op = k_workshop_op_loading_model;
- vg_loader_start( _workshop_form_load_thread, NULL );
-}
-
-/*
- * op: k_workshop_form_op_downloading_submission
- * -----------------------------------------------------------------------------
- */
-
-/*
- * The image has been decoded and is ready to slap into the framebuffer
- */
-static void workshop_form_async_imageload( void *data, u32 len )
-{
- if( data )
- {
- vg_framebuffer_attachment *a =
- &g_render.fb_workshop_preview->attachments[0];
-
- glBindTexture( GL_TEXTURE_2D, a->id );
- glTexSubImage2D( GL_TEXTURE_2D, 0,0,0,
- WORKSHOP_PREVIEW_WIDTH, WORKSHOP_PREVIEW_HEIGHT,
- a->format, a->type, data );
- stbi_image_free( data );
- vg_success( "Loaded workshop preview image\n" );
- }
- else
- {
- snprintf( workshop_form.error_msg, sizeof(workshop_form.error_msg),
- "Preview image could not be loaded. Reason: %s\n",
- stbi_failure_reason() );
- ui_start_modal( &vg_ui.ctx, workshop_form.error_msg, UI_MODAL_BAD );
- }
- workshop_form.op = k_workshop_op_none;
-}
-
-/*
- * Load the image located at ./workshop_preview.jpg into our framebuffer
- */
-static void _workshop_load_preview_thread( void *data ){
- char path_buf[ 4096 ];
- vg_str path;
- vg_strnull( &path, path_buf, 4096 );
- vg_strcat( &path, workshop_filetype_folder() );
- vg_strcat( &path, workshop_form.addon_folder );
- vg_strcat( &path, "/preview.jpg" );
-
- if( vg_strgood( &path ) )
- {
- stbi_set_flip_vertically_on_load(1);
- int x, y, nc;
- u8 *rgb = stbi_load( path.buffer, &x, &y, &nc, 3 );
-
- if( rgb )
- {
- if( (x == WORKSHOP_PREVIEW_WIDTH) && (y == WORKSHOP_PREVIEW_HEIGHT) )
- {
- vg_async_call( workshop_form_async_imageload, rgb, x*y*3 );
- }
- else
- {
- vg_error( "Resolution does not match framebuffer, so we can't"
- " show it\n" );
- stbi_image_free( rgb );
- vg_async_call( workshop_form_async_imageload, NULL, 0 );
- }
- }
- else
- {
- vg_async_call( workshop_form_async_imageload, NULL, 0 );
- }
- }
- else
- {
- vg_async_call( workshop_form_async_imageload, NULL, 0 );
- }
-}
-
-/*
- * Entry point to view operation
- */
-static void workshop_op_download_and_view_submission( int result_index )
-{
- workshop_form.op = k_workshop_op_downloading_submission;
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- ISteamRemoteStorage *hSteamRemoteStorage = SteamAPI_SteamRemoteStorage();
- SteamUGCDetails_t details;
- if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC,
- workshop_form.ugc_query.handle,
- result_index,
- &details ) )
- {
- workshop_reset_submission_data();
- workshop_form.submission.submit_description = 0;
- workshop_form.submission.submit_file_and_image = 0;
- workshop_form.submission.submit_title = 0;
-
- u8 metadata_buf[512];
- char metadata_str[1024+1];
- int have_meta = SteamAPI_ISteamUGC_GetQueryUGCMetadata( hSteamUGC,
- workshop_form.ugc_query.handle,
- result_index, metadata_str,
- 1024+1 );
-
- vg_strncpy( details.m_rgchDescription,
- workshop_form.submission.description,
- VG_ARRAY_LEN( workshop_form.submission.description ),
- k_strncpy_always_add_null );
-
- vg_strncpy( details.m_rgchTitle,
- workshop_form.submission.title,
- VG_ARRAY_LEN( workshop_form.submission.title ),
- k_strncpy_always_add_null );
-
- snprintf( workshop_form.addon_folder,
- VG_ARRAY_LEN( workshop_form.addon_folder ),
- "Steam Cloud ("PRINTF_U64")", details.m_nPublishedFileId );
-
- workshop_form.submission.file_id = details.m_nPublishedFileId;
- workshop_form.file_intent = k_workshop_form_file_intent_keep_old;
- workshop_form.page = k_workshop_form_edit;
- workshop_form.submission.visibility = details.m_eVisibility;
- workshop_form.submission.type = k_addon_type_none;
- workshop_form.submission.submission_type_selection = k_addon_type_none;
-
- if( have_meta )
- {
- u32 len = strlen(metadata_str);
- vg_info( "Metadata: %s\n", metadata_str );
- vg_str_bin( metadata_str, metadata_buf, len );
- vg_msg msg;
- vg_msg_init( &msg, metadata_buf, len/2 );
-
- if( vg_msg_seekframe( &msg, "workshop" ))
- {
- u32 type;
- vg_msg_getkvintg( &msg, "type", k_vg_msg_u32, &type, NULL );
- workshop_form.submission.type = type;
- workshop_form.submission.submission_type_selection = type;
-
- const char *kv_folder = vg_msg_getkvstr( &msg, "folder" );
- if( kv_folder )
- {
- vg_strncpy( kv_folder, workshop_form.addon_folder,
- sizeof(workshop_form.addon_folder),
- k_strncpy_always_add_null );
- }
- }
- }
- else
- {
- vg_error( "No metadata was returned with this item.\n" );
- }
-
- vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
- glClearColor( 0.2f, 0.0f, 0.0f, 1.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glViewport( 0,0, vg.window_x, vg.window_y );
-
- vg_loader_start( _workshop_load_preview_thread, NULL );
- }
- else
- {
- vg_error( "GetQueryUGCResult: Index out of range\n" );
- workshop_form.op = k_workshop_op_none;
- }
-}
-
-/*
- * Regular stuff
- * -----------------------------------------------------------------------------
- */
-
-/*
- * View a page of results on the sidebar
- */
-static void workshop_view_page( int req )
-{
- if( workshop_form.ugc_query.result != k_EResultOK )
- {
- vg_error( "Tried to change page without complete data\n" );
- workshop_form.op = k_workshop_op_none;
- return;
- }
-
- int page = VG_MAX(VG_MIN(req, workshop_form.view_published_page_count-1),0),
- start = page * WORKSHOP_VIEW_PER_PAGE,
- end = VG_MIN( (page+1) * WORKSHOP_VIEW_PER_PAGE,
- workshop_form.ugc_query.returned_item_count ),
- count = end-start;
-
- vg_info( "View page %d\n", page );
-
- workshop_form.view_published_page_id = page;
- workshop_form.published_files_list_length = count;
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
-
- for( int i=0; i<count; i ++ )
- {
- struct published_file *pfile = &workshop_form.published_files_list[i];
-
- SteamUGCDetails_t details;
- if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC,
- workshop_form.ugc_query.handle,
- start+i,
- &details ) )
- {
- if( details.m_eResult != k_EResultOK )
- {
- snprintf( pfile->title, 80, "Error (%d)", details.m_eResult );
- }
- else
- {
- vg_strncpy( details.m_rgchTitle, pfile->title, 80,
- k_strncpy_always_add_null );
- }
-
- pfile->result = details.m_eResult;
- pfile->result_index = start+i;
- }
- else
- {
- pfile->result = k_EResultValueOutOfRange;
- pfile->result_index = -1;
- snprintf( pfile->title, 80, "Error (invalid index)" );
- }
- }
-}
-
-/*
- * Steam API result for when we recieve submitted UGC information about the user
- */
-static void on_workshop_UGCQueryComplete( void *data, void *userdata )
-{
- SteamUGCQueryCompleted_t *query = data;
- workshop_form.ugc_query.result = query->m_eResult;
-
- if( query->m_eResult == k_EResultOK )
- {
- if( query->m_unTotalMatchingResults > 50 )
- {
- vg_warn( "You have %d items submitted, "
- "we can only view the last 50\n" );
- }
-
- workshop_form.ugc_query.all_item_count = query->m_unTotalMatchingResults;
- workshop_form.ugc_query.returned_item_count =
- query->m_unNumResultsReturned;
-
- workshop_form.ugc_query.handle = query->m_handle;
- workshop_form.view_published_page_count =
- (query->m_unNumResultsReturned+WORKSHOP_VIEW_PER_PAGE-1)/
- WORKSHOP_VIEW_PER_PAGE;
- workshop_form.view_published_page_id = 0;
- workshop_form.published_files_list_length = 0;
-
- workshop_view_page( 0 );
- }
- else
- {
- vg_error( "Steam UGCQuery failed (%d)\n", query->m_eResult );
- workshop_form.view_published_page_count = 0;
- workshop_form.view_published_page_id = 0;
- workshop_form.published_files_list_length = 0;
-
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- SteamAPI_ISteamUGC_ReleaseQueryUGCRequest( hSteamUGC, query->m_handle );
- }
-}
-
-/*
- * Console command to open the workshop publisher
- */
-int workshop_submit_command( int argc, const char *argv[] )
-{
- if( !steam_ready )
- {
- ui_start_modal( &vg_ui.ctx,
- "Steam API is not initialized\n", UI_MODAL_BAD );
- return 0;
- }
-
- workshop_form.page = k_workshop_form_open;
- workshop_form.view_published_page_count = 0;
- workshop_form.view_published_page_id = 0;
- workshop_form.published_files_list_length = 0;
- workshop_form.ugc_query.result = k_EResultNone;
-
- vg_steam_async_call *call = vg_alloc_async_steam_api_call();
-
- ISteamUser *hSteamUser = SteamAPI_SteamUser();
- CSteamID steamid;
- steamid.m_unAll64Bits = SteamAPI_ISteamUser_GetSteamID( hSteamUser );
-
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- call->p_handler = on_workshop_UGCQueryComplete;
- call->userdata = NULL;
- UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryUserUGCRequest
- (
- hSteamUGC,
- steamid.m_comp.m_unAccountID,
- k_EUserUGCList_Published,
- k_EUGCMatchingUGCType_Items,
- k_EUserUGCListSortOrder_CreationOrderDesc,
- SKATERIFT_APPID, SKATERIFT_APPID,
- 1 );
- SteamAPI_ISteamUGC_SetReturnMetadata( hSteamUGC, handle, 1 );
- call->id = SteamAPI_ISteamUGC_SendQueryUGCRequest( hSteamUGC, handle );
- return 0;
-}
-
-void workshop_init(void)
-{
- vg_console_reg_cmd( "workshop_submit", workshop_submit_command, NULL );
-}
-
-static void workshop_render_world_preview(void)
-{
- vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
-
- glClearColor( 0.0f, 0.0f, 0.3f, 1.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
- glEnable( GL_DEPTH_TEST );
- glDisable( GL_BLEND );
-
- render_world( world_current_instance(), &g_render.cam, 0, 0, 1, 1 );
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glViewport( 0,0, vg.window_x, vg.window_y );
-}
-
-/*
- * Redraw the playermodel into the workshop framebuffer
- */
-static void workshop_render_player_preview(void)
-{
- vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
- glClearColor( 0.16f, 0.15f, 0.15f, 1.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
- glEnable( GL_DEPTH_TEST );
- glDisable( GL_BLEND );
-
- struct skeleton *sk = &localplayer.skeleton;
-
- player_pose res;
- res.type = k_player_pose_type_ik;
-
- struct skeleton_anim *anim = skeleton_get_anim( sk, "idle_cycle+y" );
- skeleton_sample_anim( sk, anim, vg.time*0.1f, res.keyframes );
- q_axis_angle( res.root_q, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
- v3_zero( res.root_co );
- res.root_co[1] = 200.0f;
-
- m4x3f transform;
- q_m3x3( res.root_q, transform );
- v3_copy( res.root_co, transform[3] );
-
- /* TODO: Function. */
- skeleton_apply_pose( sk, res.keyframes, k_anim_apply_defer_ik,
- localplayer.final_mtx );
- skeleton_apply_ik_pass( sk, localplayer.final_mtx );
- skeleton_apply_pose( sk, res.keyframes, k_anim_apply_deffered_only,
- localplayer.final_mtx );
- skeleton_apply_inverses( sk, localplayer.final_mtx );
- skeleton_apply_transform( sk, transform, localplayer.final_mtx );
-
- vg_camera cam;
- v3_copy( (v3f){ 0.0f, 201.7f, 1.2f }, cam.pos );
-
- cam.nearz = 0.01f;
- cam.farz = 100.0f;
- cam.fov = 57.0f;
- v3_zero( cam.angles );
-
- vg_camera_update_transform( &cam );
- vg_camera_update_view( &cam );
- vg_camera_update_projection( &cam );
- vg_camera_finalize( &cam );
-
- render_playermodel( &cam, world_current_instance(), 0,
- &workshop_form.player_model, sk, localplayer.final_mtx );
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glViewport( 0,0, vg.window_x, vg.window_y );
-}
-
-/*
- * Redraw the model file into the workshop framebuffer
- */
-static void workshop_render_board_preview(void)
-{
- if( !workshop_form.ptr_ent )
- {
- return;
- }
-
- vg_framebuffer_bind( g_render.fb_workshop_preview, 1.0f );
-
- glClearColor( 0.0f, 0.0f, 0.3f, 1.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
- glEnable( GL_DEPTH_TEST );
- glDisable( GL_BLEND );
-
- ent_swspreview *swsprev = workshop_form.ptr_ent;
- world_instance *world = workshop_form.view_world;
-
- ent_camera *ref = mdl_arritm( &world->ent_camera,
- mdl_entity_id_id(swsprev->id_camera) );
- ent_marker *display = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(swsprev->id_display) ),
- *display1= mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(swsprev->id_display1) );
-
- v3f baseco;
- v3_add( display->transform.co, display1->transform.co, baseco );
- v3_muls( baseco, 0.5f, baseco );
-
- vg_camera cam;
- v3f basevector;
- v3_sub( display->transform.co, ref->transform.co, basevector );
- float dist = v3_length( basevector );
-
- v3f baseangles;
- v3_angles( basevector, baseangles );
-
- v2_add( workshop_form.view_angles, baseangles, cam.angles );
- cam.angles[2] = 0.0f;
-
- float sX = sinf( cam.angles[0] ),
- cX = cosf( cam.angles[0] ),
- sY = sinf( cam.angles[1] ),
- cY = cosf( cam.angles[1] );
-
- v3f offset = { -sX * cY, sY, cX * cY };
-
- v3_muladds( display->transform.co, offset,
- dist*workshop_form.view_dist, cam.pos );
-
- cam.pos[0] += -sX*workshop_form.view_offset[2];
- cam.pos[2] += cX*workshop_form.view_offset[2];
- cam.pos[0] += cX*workshop_form.view_offset[0];
- cam.pos[2] += sX*workshop_form.view_offset[0];
- cam.pos[1] += workshop_form.view_offset[1];
-
- cam.nearz = 0.01f;
- cam.farz = 100.0f;
- cam.fov = ref->fov;
-
- vg_camera_update_transform( &cam );
- vg_camera_update_view( &cam );
- vg_camera_update_projection( &cam );
- vg_camera_finalize( &cam );
-
- m4x3f mmdl, mmdl1;
- mdl_transform_m4x3( &display->transform, mmdl );
- mdl_transform_m4x3( &display1->transform, mmdl1 );
-
- /* force update this for nice shadows. its usually set in the world
- * pre-render step, but that includes timer stuff
- */
- struct player_board *board = &workshop_form.board_model;
- struct ub_world_lighting *ubo = &world->ub_lighting;
- v3f vp0, vp1;
- v3_copy((v3f){0.0f,0.1f, board->truck_positions[0][2]}, vp0 );
- v3_copy((v3f){0.0f,0.1f, board->truck_positions[1][2]}, vp1 );
- m4x3_mulv( mmdl1, vp0, ubo->g_board_0 );
- m4x3_mulv( mmdl1, vp1, ubo->g_board_1 );
- glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
- glBufferSubData( GL_UNIFORM_BUFFER, 0,
- sizeof(struct ub_world_lighting), &world->ub_lighting );
-
- render_world( world, &cam, 0, 0, 0, 0 );
- struct player_board_pose pose = {0};
- render_board( &cam, world, board, mmdl, &pose, k_board_shader_entity );
- render_board( &cam, world, board, mmdl1, &pose, k_board_shader_entity );
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glViewport( 0,0, vg.window_x, vg.window_y );
-}
-
-/*
- * ImGUI section for workshop form
- * -----------------------------------------------------------------------------
- */
-
-static void workshop_changed_model_path( ui_context *ctx, char *buf, u32 len )
-{
- workshop_form.submission.submit_file_and_image = 1;
-}
-
-static void workshop_changed_title( ui_context *ctx, char *buf, u32 len )
-{
- workshop_form.submission.submit_title = 1;
-}
-
-static void workshop_changed_description( ui_context *ctx, char *buf, u32 len )
-{
- workshop_form.submission.submit_description = 1;
-}
-
-static void workshop_form_gui_page_undecided( ui_context *ctx, ui_rect content )
-{
- ui_rect box;
- rect_copy( content, box );
- box[3] = 128;
- box[2] = (box[2]*2)/3;
- ui_rect_center( content, box );
-
- ui_rect row;
- ui_split( box, k_ui_axis_h, 28, 0, row, box );
- ui_text( ctx, row,
- "Select the type of item\n", 1, k_ui_align_middle_center,0);
- ui_split( box, k_ui_axis_h, 28, 0, row, box );
- ui_enum( ctx, row, "Type:", workshop_form_type_opts,
- 4, &workshop_form.submission.submission_type_selection );
- ui_split( box, k_ui_axis_h, 8, 0, row, box );
- ui_split( box, k_ui_axis_h, 28, 0, row, box );
-
- ui_rect button_l, button_r;
- rect_copy( row, button_l );
- button_l[2] = 128*2;
- ui_rect_center( row, button_l );
- ui_split_ratio( button_l, k_ui_axis_v, 0.5f, 2, button_l, button_r );
-
- enum addon_type type = workshop_form.submission.submission_type_selection;
- if( type != k_addon_type_none)
- {
- if( ui_button_text( ctx, button_l, "OK", 1 ) == 1 )
- {
- workshop_form.submission.type = type;
-
- if( type == k_addon_type_world ){
- workshop_form.view_changed = 1;
- workshop_form.file_intent = k_workshop_form_file_intent_new;
- }
- }
- }
- else
- {
- ui_fill( ctx, button_l, ui_colour(ctx,k_ui_bg) );
- ui_text( ctx, button_l, "OK", 1, k_ui_align_middle_center,
- ui_colour(ctx, k_ui_bg+4) );
- }
-
- if( ui_button_text( ctx, button_r, "Cancel", 1 ) == 1 )
- {
- workshop_form.page = k_workshop_form_open;
- workshop_form.file_intent = k_workshop_form_file_intent_none;
- }
-}
-
-static void workshop_form_gui_draw_preview( ui_context *ctx, ui_rect img_box )
-{
- enum addon_type type = workshop_form.submission.type;
- if( workshop_form.file_intent == k_workshop_form_file_intent_keep_old )
- {
- ui_image( ctx,
- img_box, &g_render.fb_workshop_preview->attachments[0].id );
- }
- else if( workshop_form.file_intent == k_workshop_form_file_intent_new )
- {
- ui_image( ctx,
- img_box, &g_render.fb_workshop_preview->attachments[0].id );
-
- if( type == k_addon_type_world )
- {
- return;
- }
-
- int hover = ui_inside_rect( img_box, ctx->mouse ),
- target = ui_inside_rect( img_box, ctx->mouse_click );
-
- if( ui_click_down(ctx,UI_MOUSE_MIDDLE) && target )
- {
- v3_copy( workshop_form.view_offset,
- workshop_form.view_offset_begin );
- }
- else if( ui_click_down(ctx,UI_MOUSE_LEFT) && target )
- {
- v2_copy( workshop_form.view_angles,
- workshop_form.view_angles_begin );
- }
-
- if( ui_clicking(ctx,UI_MOUSE_MIDDLE) && target )
- {
- v2f delta = { ctx->mouse[0]-ctx->mouse_click[0],
- ctx->mouse[1]-ctx->mouse_click[1] };
-
- float *begin = workshop_form.view_offset_begin,
- *offset = workshop_form.view_offset;
- offset[0] = vg_clampf( begin[0]-delta[0]*0.002f, -1.0f, 1.0f );
- offset[2] = vg_clampf( begin[2]-delta[1]*0.002f, -1.0f, 1.0f );
- workshop_form.view_changed = 1;
- }
- else if( ui_clicking(ctx,UI_MOUSE_LEFT) && target )
- {
- v2f delta = { ctx->mouse[0]-ctx->mouse_click[0],
- ctx->mouse[1]-ctx->mouse_click[1] };
-
- v2f angles;
- v2_muladds( workshop_form.view_angles_begin, delta, 0.002f, angles);
-
- float limit = VG_PIf*0.2f;
-
- angles[0] = vg_clampf( angles[0], -limit, limit );
- angles[1] = vg_clampf( angles[1], -limit, limit );
-
- v2_copy( angles, workshop_form.view_angles );
- workshop_form.view_changed = 1;
- }
-
- if( !ui_clicking(ctx,UI_MOUSE_LEFT) && hover )
- {
- float zoom = workshop_form.view_dist;
- zoom += vg.mouse_wheel[1] * -0.07f;
- zoom = vg_clampf( zoom, 0.4f, 2.0f );
-
- if( zoom != workshop_form.view_dist )
- {
- workshop_form.view_changed = 1;
- workshop_form.view_dist = zoom;
- }
- }
- }
- else
- {
- ui_text( ctx, img_box, "No image", 1, k_ui_align_middle_center,
- ui_colour( ctx, k_ui_orange ) );
- }
-}
-
-static void workshop_form_gui_edit_page( ui_context *ctx, ui_rect content )
-{
- enum addon_type type = workshop_form.submission.type;
-
- if( type == k_addon_type_none )
- {
- workshop_form_gui_page_undecided( ctx, content );
- return;
- }
-
- ui_rect image_plane;
- ui_split( content, k_ui_axis_h, 300, 0, image_plane, content );
- ui_fill( ctx, image_plane, ui_colour( ctx, k_ui_bg+0 ) );
-
- ui_rect img_box;
- ui_fit_item( image_plane, (ui_px[2]){ 3, 2 }, img_box );
- workshop_form_gui_draw_preview( ctx, img_box );
-
- /* file path */
- ui_rect file_button, file_label;
-
- char buf[128];
- snprintf( buf, 128,
- "Addon folder: skaterift/%s", workshop_filetype_folder() );
-
- if( type == k_addon_type_world )
- {
- struct ui_textbox_callbacks callbacks =
- {
- .change = workshop_changed_model_path
- };
- ui_textbox( ctx, content, buf, workshop_form.addon_folder,
- VG_ARRAY_LEN(workshop_form.addon_folder), 1, 0, &callbacks );
- }
- else
- {
- ui_rect file_entry;
- ui_standard_widget( ctx, content, file_entry, 1 );
- ui_split( file_entry, k_ui_axis_v, -128, 0, file_entry, file_button );
-
- if( workshop_form.file_intent != k_workshop_form_file_intent_none )
- {
- ui_text( ctx, file_entry, workshop_form.addon_folder, 1,
- k_ui_align_middle_left, ui_colour( ctx, k_ui_fg+4 ) );
-
- if( ui_button_text( ctx, file_button, "Remove", 1 ) == 1 )
- {
- if( type == k_addon_type_board )
- player_board_unload( &workshop_form.board_model );
- else if( type == k_addon_type_player )
- player_model_unload( &workshop_form.player_model );
-
- workshop_form.file_intent = k_workshop_form_file_intent_none;
- workshop_form.addon_folder[0] = '\0';
- }
- }
- else
- {
- struct ui_textbox_callbacks callbacks =
- {
- .change = workshop_changed_model_path
- };
-
- ui_textbox( ctx, file_entry, buf, workshop_form.addon_folder,
- VG_ARRAY_LEN(workshop_form.addon_folder), 1,
- 0, &callbacks );
-
- if( ui_button_text( ctx, file_button, "Load", 1 ) == 1 )
- {
- workshop_op_load_model( ctx );
- }
- }
- }
-
- const char *str_title = "Title:", *str_desc = "Description:";
-
- /* title box */
- {
- struct ui_textbox_callbacks callbacks = {
- .change = workshop_changed_title
- };
- ui_textbox( ctx, content, str_title, workshop_form.submission.title,
- VG_ARRAY_LEN(workshop_form.submission.title), 1,
- 0, &callbacks );
- }
-
- /* visibility option */
- {
- ui_enum( ctx, content, "Visibility:", workshop_form_visibility_opts,
- 4, &workshop_form.submission.visibility );
- }
-
- /* description box */
- {
- struct ui_textbox_callbacks callbacks =
- {
- .change = workshop_changed_description
- };
- ui_textbox( ctx, content, str_desc, workshop_form.submission.description,
- VG_ARRAY_LEN(workshop_form.submission.description), 4,
- UI_TEXTBOX_MULTILINE|UI_TEXTBOX_WRAP, &callbacks );
- }
-
- /* submissionable */
- ui_rect final_row;
- ui_split( content, k_ui_axis_h, content[3]-32-8, 0, content, final_row );
-
- ui_rect submission_center;
- rect_copy( final_row, submission_center );
- submission_center[2] = 256;
- ui_rect_center( final_row, submission_center );
-
- ui_rect btn_left, btn_right;
- ui_split_ratio( submission_center, k_ui_axis_v, 0.5f, 8,
- btn_left, btn_right );
-
- if( ui_button_text( ctx, btn_left, "Publish", 1 ) == 1 )
- {
- workshop_op_submit( ctx );
- }
- if( ui_button_text( ctx, btn_right, "Cancel", 1 ) == 1 )
- {
- workshop_form.page = k_workshop_form_open;
- player_board_unload( &workshop_form.board_model );
- workshop_form.file_intent = k_workshop_form_file_intent_none;
- }
-
- /* disclaimer */
- const char *disclaimer_text =
- "By submitting this item, you agree to the workshop terms of service";
-
- ui_rect disclaimer_row, inner, link;
- ui_split( content, k_ui_axis_h, content[3]-32, 0, content, disclaimer_row );
-
- ui_px btn_width = 32;
-
- rect_copy( disclaimer_row, inner );
- inner[2] = ui_text_line_width( ctx, disclaimer_text ) + btn_width+8;
-
- ui_rect label;
- ui_rect_center( disclaimer_row, inner );
- ui_split( inner, k_ui_axis_v, inner[2]-btn_width, 0, label, btn_right);
- ui_rect_pad( btn_right, (ui_px[2]){2,2} );
-
- if( ui_button_text( ctx, btn_right, "\xb2", 2 ) == 1 )
- {
- ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
- SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends,
- "https://steamcommunity.com/sharedfiles/workshoplegalagreement",
- k_EActivateGameOverlayToWebPageMode_Default );
- }
-
- ui_text( ctx, label, disclaimer_text, 1, k_ui_align_middle_left,
- ui_colour( ctx, k_ui_fg+4 ) );
-}
-
-static void workshop_form_gui_sidebar( ui_context *ctx, ui_rect sidebar )
-{
- ui_fill( ctx, sidebar, ui_colour( ctx, k_ui_bg+2 ) );
-
- ui_rect title;
- ui_split( sidebar, k_ui_axis_h, 28, 0, title, sidebar );
-
- if( workshop_form.page == k_workshop_form_edit )
- {
- ui_text( ctx, title, "Editing", 1, k_ui_align_middle_center, 0 );
- ui_split( sidebar, k_ui_axis_h, 28, 0, title, sidebar );
-
- if( workshop_form.submission.type != k_addon_type_none )
- {
- char buf[512];
- vg_str str;
- vg_strnull( &str, buf, 512 );
-
- if( workshop_form.submission.file_id )
- vg_strcat( &str, "Editing an existing " );
- else
- vg_strcat( &str, "Creating a new " );
-
- if( workshop_form.submission.type == k_addon_type_board )
- vg_strcat( &str, "skateboard." );
- else if( workshop_form.submission.type == k_addon_type_world )
- vg_strcat( &str, "world." );
- else if( workshop_form.submission.type == k_addon_type_player )
- vg_strcat( &str, "playermodel." );
- else
- vg_strcat( &str, "???." );
-
- ui_text( ctx, title, buf, 1, k_ui_align_middle_center,
- ui_colour(ctx, k_ui_fg+4) );
- }
- return;
- }
-
- /*
- * sidebar existing entries panel
- */
- ui_text( ctx, title, "Your submissions", 1, k_ui_align_middle_center, 0 );
-
- ui_rect controls, btn_create_new;
- ui_split( sidebar, k_ui_axis_h, 32, 0, controls, sidebar );
- ui_split( sidebar, k_ui_axis_h, -32, 0, sidebar, btn_create_new );
- ui_fill( ctx, controls, ui_colour( ctx, k_ui_bg+1 ) );
-
- char buf[32];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
- vg_strcat( &str, "page " );
- vg_strcati32( &str, workshop_form.view_published_page_id+1 );
- vg_strcatch( &str, '/' );
- vg_strcati32( &str, workshop_form.view_published_page_count );
-
- ui_rect_pad( controls, (ui_px[2]){0,4} );
- ui_rect info;
- ui_split_ratio( controls, k_ui_axis_v, 0.25f, 0, info, controls );
- ui_text( ctx, info, buf, 1, k_ui_align_middle_center, 0 );
-
- ui_rect btn_left, btn_right;
- ui_split_ratio( controls, k_ui_axis_v, 0.5f, 2, btn_left, btn_right );
-
- if( ui_button_text( ctx, btn_left, "newer", 1 ) == 1 )
- {
- workshop_view_page( workshop_form.view_published_page_id-1 );
- }
-
- if( ui_button_text( ctx, btn_right, "older", 1 ) == 1 )
- {
- workshop_view_page( workshop_form.view_published_page_count+1 );
- }
-
- if( ui_button_text( ctx, btn_create_new, "Create New Item", 1 ) == 1 )
- {
- workshop_reset_submission_data();
- workshop_form.submission.submit_title = 1;
- workshop_form.submission.submit_description = 1;
- workshop_form.submission.submit_file_and_image = 1;
- workshop_form.page = k_workshop_form_edit;
- }
-
- for( int i=0; i<workshop_form.published_files_list_length; i++ )
- {
- ui_rect item;
- ui_split( sidebar, k_ui_axis_h, 28, 0, item, sidebar );
- ui_rect_pad( item, (ui_px[2]){4,4} );
-
- struct published_file *pfile = &workshop_form.published_files_list[i];
- if( ui_button_text( ctx, item, pfile->title, 1 ) == 1 )
- {
- if( pfile->result == k_EResultOK )
- {
- vg_info( "Select index: %d\n", pfile->result_index );
- workshop_op_download_and_view_submission( pfile->result_index );
- }
- else
- {
- vg_warn( "Cannot select that item, result not OK\n" );
- }
- }
- }
-}
-
-void workshop_form_gui( ui_context *ctx )
-{
- enum workshop_form_page stable_page = workshop_form.page;
- if( stable_page == k_workshop_form_hidden ) return;
-
- ui_rect null;
- ui_rect screen = { 0, 0, vg.window_x, vg.window_y };
- ui_rect window = { 0, 0, 1000, 700 };
- ui_rect_center( screen, window );
- ctx->wants_mouse = 1;
-
- ui_fill( ctx, window, ui_colour( ctx, k_ui_bg+1 ) );
- ui_outline( ctx, window, 1, ui_colour( ctx, k_ui_bg+7 ), 0 );
-
- ui_rect title, panel;
- ui_split( window, k_ui_axis_h, 28, 0, title, panel );
- ui_fill( ctx, title, ui_colour( ctx, k_ui_bg+7 ) );
- ui_text( ctx, title, "Workshop tool", 1, k_ui_align_middle_center,
- ui_colourcont( ctx, k_ui_bg+7 ) );
-
- ui_rect quit_button;
- ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button );
-
- if( vg_loader_availible() )
- {
- if( ui_button_text( ctx, quit_button, "X", 1 ) == 1 )
- {
- workshop_quit_form();
- return;
- }
- }
-
- /*
- * temporary operation blinders, we don't yet have a nice way to show the
- * user that we're doing something uninterruptable, so the code just
- * escapes here and we show them a basic string
- */
-
- if( workshop_form.op != k_workshop_op_none )
- {
- const char *op_string = "The programmer has not bothered to describe "
- "the current operation that is running.";
-
- switch( workshop_form.op )
- {
- case k_workshop_op_loading_model:
- op_string = "Operation in progress: Loading model file.";
- break;
- case k_workshop_op_publishing_update:
- op_string = "Operation in progress: publishing submission update "
- "to steam.";
- break;
- case k_workshop_op_downloading_submission:
- op_string = "Operation in progress: downloading existing submission"
- " from Steam services.";
- break;
- default: break;
- }
-
- ui_text( ctx, panel, op_string, 1, k_ui_align_middle_center, 0 );
- return;
- }
-
- /* re draw board preview if need to */
- if( (stable_page == k_workshop_form_edit) &&
- workshop_form.view_changed &&
- workshop_form.file_intent == k_workshop_form_file_intent_new )
- {
- enum addon_type type = workshop_form.submission.type;
- if( type == k_addon_type_board ){
- workshop_render_board_preview();
- }
- else if( type == k_addon_type_world ){
- vg_success( "Renders world preview\n" );
- workshop_render_world_preview();
- }
- else if( type == k_addon_type_player ){
- workshop_render_player_preview();
- }
-
- workshop_form.view_changed = 0;
- }
-
- struct workshop_form *form = &workshop_form;
-
- ui_rect sidebar, content;
- ui_split_ratio( panel, k_ui_axis_v, 0.3f, 1, sidebar, content );
-
- /* content page */
- ui_rect_pad( content, (ui_px[2]){8,8} );
-
- if( stable_page == k_workshop_form_edit )
- {
- workshop_form_gui_edit_page( ctx, content );
- }
- else if( stable_page == k_workshop_form_open )
- {
- ui_text( ctx, content, "Nothing selected.", 1, k_ui_align_middle_center,
- ui_colour( ctx, k_ui_fg+4 ) );
- }
- else if( stable_page >= k_workshop_form_cclosing )
- {
- ui_rect submission_row;
- ui_split( content, k_ui_axis_h, content[3]-32-8, 0, content,
- submission_row );
-
- u32 colour;
-
- if( stable_page == k_workshop_form_closing_bad )
- colour = ui_colour( ctx, k_ui_red+k_ui_brighter );
- else
- colour = ui_colour( ctx, k_ui_green+k_ui_brighter );
-
- ui_text( ctx, content, workshop_form.failure_or_success_string, 1,
- k_ui_align_middle_center, colour );
-
- ui_rect submission_center;
- rect_copy( submission_row, submission_center );
- submission_center[2] = 128;
- ui_rect_center( submission_row, submission_center );
- ui_rect_pad( submission_center, (ui_px[2]){8,8} );
-
- if( ui_button_text( ctx, submission_center, "OK", 1 ) == 1 )
- {
- workshop_form.page = k_workshop_form_open;
- }
- }
-
- workshop_form_gui_sidebar( ctx, sidebar );
-}
-
-/*
- * Some async api stuff
- * -----------------------------------------------------------------------------
- */
-
-void async_workshop_get_filepath( void *data, u32 len )
-{
- struct async_workshop_filepath_info *info = data;
-
- u64 _size;
- u32 _ts;
-
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- if( !SteamAPI_ISteamUGC_GetItemInstallInfo( hSteamUGC, info->id, &_size,
- info->buf, info->len, &_ts ))
- {
- vg_error( "GetItemInstallInfo failed\n" );
- info->buf[0] = '\0';
- }
-}
-
-void async_workshop_get_installed_files( void *data, u32 len )
-{
- struct async_workshop_installed_files_info *info = data;
-
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- u32 count = SteamAPI_ISteamUGC_GetSubscribedItems( hSteamUGC, info->buffer,
- *info->len );
-
- vg_info( "Found %u subscribed items\n", count );
-
- u32 j=0;
- for( u32 i=0; i<count; i++ ){
- u32 state = SteamAPI_ISteamUGC_GetItemState( hSteamUGC, info->buffer[i] );
- if( state & k_EItemStateInstalled ){
- info->buffer[j ++] = info->buffer[i];
- }
- }
-
- *info->len = j;
-}
+++ /dev/null
-#pragma once
-#include "addon_types.h"
-#include "vg/vg_steam_remote_storage.h"
-#include "skaterift.h"
-#include "vg/vg_steam_auth.h"
-#include "vg/vg_steam_ugc.h"
-#include "vg/vg_steam_friends.h"
-#include "steam.h"
-#include "ent_skateshop.h"
-
-struct async_workshop_filepath_info{
- PublishedFileId_t id;
- char *buf;
- u32 len;
-};
-
-struct async_workshop_installed_files_info{
- PublishedFileId_t *buffer;
- u32 *len; /* inout */
-};
-
-struct async_workshop_metadata_info{
- struct workshop_file_info *info;
- const char *path;
-};
-
-
-#define WORKSHOP_VIEW_PER_PAGE 15
-
-struct workshop_form{
- enum workshop_op {
- k_workshop_op_none,
- k_workshop_op_downloading_submission,
- k_workshop_op_publishing_update,
- k_workshop_op_loading_model
- }
- op;
-
- struct {
- char title[80];
- char description[512];
- char author[32];
- i32 submission_type_selection;
- enum addon_type type;
-
- PublishedFileId_t file_id; /* 0 if not published yet */
-
- i32 visibility;
- int submit_title, /* set if the respective controls are touched */
- submit_description,
- submit_file_and_image;
- }
- submission;
-
- enum workshop_form_page{
- k_workshop_form_hidden,
- k_workshop_form_open, /* open but not looking at anything */
- k_workshop_form_edit, /* editing a submission */
- k_workshop_form_cclosing,
- k_workshop_form_closing_good, /* post upload screen */
- k_workshop_form_closing_bad,
- }
- page;
-
- /* model viewer
- * -----------------------------
- */
-
- char addon_folder[128];
- struct player_board board_model;
- struct player_model player_model;
-
- /* what does the user want to do with the image preview? */
- enum workshop_form_file_intent{
- k_workshop_form_file_intent_none, /* loading probably */
- k_workshop_form_file_intent_new, /* board_model is valid */
- k_workshop_form_file_intent_keep_old /* just browsing */
- }
- file_intent;
-
- world_instance *view_world;
- ent_swspreview *ptr_ent;
- v2f view_angles,
- view_angles_begin;
- v3f view_offset,
- view_offset_begin;
-
- float view_dist;
- int view_changed;
-
- /*
- * published UGC request
- * ------------------------------
- */
-
- struct {
- UGCQueryHandle_t handle;
- EResult result;
-
- int all_item_count,
- returned_item_count;
- }
- ugc_query;
-
- /*
- * UI information
- * ------------------------------------------
- */
-
- const char *failure_or_success_string;
- char error_msg[256];
-
- int img_w, img_h;
- u8 *img_buffer;
-
- int view_published_page_count,
- view_published_page_id;
-
- struct published_file{
- EResult result;
- int result_index;
- char title[80];
- }
- published_files_list[WORKSHOP_VIEW_PER_PAGE];
- int published_files_list_length;
-}
-extern workshop_form;
-
-void workshop_init(void);
-int workshop_submit_command( int argc, const char *argv[] );
-void async_workshop_get_filepath( void *data, u32 len );
-void async_workshop_get_installed_files( void *data, u32 len );
-void workshop_load_metadata( const char *path,struct workshop_file_info *info );
-void workshop_form_gui( ui_context *ctx );
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#include "skaterift.h"
-#include "world.h"
-#include "network.h"
-#include "vg/vg_loader.h"
-#include "vg/vg_mem.h"
-#include "save.h"
-#include "player.h"
-#include "ent_traffic.h"
-
-struct world_static world_static;
-
-world_instance *world_current_instance(void)
-{
- return &world_static.instances[ world_static.active_instance ];
-}
-
-static int skaterift_switch_instance_cmd( int argc, const char *argv[] );
-
-void world_init(void)
-{
- vg_loader_step( world_render_init, NULL );
- vg_loader_step( world_sfd_init, NULL );
- vg_loader_step( world_water_init, NULL );
- vg_loader_step( world_gates_init, NULL );
- vg_loader_step( world_routes_init, NULL );
-
- /* Allocate dynamic world memory arena */
- u32 max_size = 76*1024*1024;
- world_static.heap = vg_create_linear_allocator( vg_mem.rtmemory, max_size,
- VG_MEMORY_SYSTEM );
-
- vg_console_reg_cmd( "switch_active_instance",
- skaterift_switch_instance_cmd, NULL );
-}
-
-void world_switch_instance( u32 index )
-{
- localplayer.subsystem = k_player_subsystem_walk;
-
- if( index >= VG_ARRAY_LEN(world_static.instances) ){
- vg_error( "Instance ID out of range (%u)\n", index );
- return;
- }
-
- world_instance *new = &world_static.instances[ index ];
-
- if( new->status != k_world_status_loaded ){
- vg_error( "Instance is not loaded (%u)\n", index );
- return;
- }
-
- if( skaterift.demo_mode ){
- if( world_static.instance_addons[index]->flags & ADDON_REG_PREMIUM ){
- vg_error( "Can't switch to a premium world in the demo version\n" );
- return;
- }
- }
-
- world_instance *current =
- &world_static.instances[ world_static.active_instance ];
-
- if( index != world_static.active_instance ){
- v3_copy( localplayer.rb.co, current->player_co );
- skaterift_autosave(1);
- }
-
- v3_copy( new->player_co, localplayer.rb.co );
-
- world_static.active_instance = index;
- player__reset();
-}
-
-static int skaterift_switch_instance_cmd( int argc, const char *argv[] )
-{
- if( argc )
- world_switch_instance( atoi(argv[0]) );
- else
- vg_info( "switch_active_instance <id>\n" );
- return 0;
-}
-
-void skaterift_world_get_save_path( enum world_purpose which, char buf[128] )
-{
- addon_reg *reg = world_static.instance_addons[ which ];
-
- if( !reg )
- vg_fatal_error( "Looking up addon for world without one\n" );
-
- char id[76];
- addon_alias_uid( ®->alias, id );
- snprintf( buf, 128, "savedata/%s.bkv", id );
-}
-
-void world_update( world_instance *world, v3f pos )
-{
- world_render.sky_time += world_render.sky_rate * vg.time_delta;
- world_render.sky_rate = vg_lerp( world_render.sky_rate,
- world_render.sky_target_rate,
- vg.time_delta * 5.0 );
-
- world_routes_update_timer_texts( world );
- world_routes_update( world );
- ent_traffic_update( world, pos );
- world_sfd_update( world, pos );
- world_volumes_update( world, pos );
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-#include "render.h"
-#include "network_msg.h"
-#include "addon.h"
-#include "scene.h"
-
-/* types
- */
-
-enum world_geo_type{
- k_world_geo_type_solid = 0,
- k_world_geo_type_nonsolid = 1,
- k_world_geo_type_water = 2
-};
-
-enum world_purpose{
- k_world_purpose_invalid = -1,
- k_world_purpose_hub = 0,
- k_world_purpose_client = 1,
- k_world_max
-};
-
-struct leaderboard_cache {
- enum request_status status;
- f64 cache_time;
- u8 *data;
- u32 data_len;
-};
-
-typedef struct world_instance world_instance;
-
-void skaterift_world_get_save_path( enum world_purpose which, char buf[128] );
-
-/* submodule headers */
-#include "world_entity.h"
-#include "world_gate.h"
-#include "world_gen.h"
-#include "world_info.h"
-#include "world_physics.h"
-#include "world_render.h"
-#include "world_sfd.h"
-#include "world_volumes.h"
-#include "world_water.h"
-#include "world_audio.h"
-#include "world_routes.h"
-#include "world_routes_ui.h"
-
-/* console variables */
-
-static f32 k_day_length = 30.0f; /* minutes */
-static i32 k_debug_light_indices = 0,
- k_debug_light_complexity= 0,
- k_light_preview = 0,
- k_light_editor = 0;
-
-#define WORLD_SURFACE_HAS_TRAFFIC 0x1
-#define WORLD_SURFACE_HAS_PROPS 0x2
-
-struct world_instance {
- /* Fixed items
- * -------------------------------------------------------
- */
-
- v4f player_co;
-
- void *heap;
- enum world_status{
- k_world_status_unloaded = 0,
- k_world_status_loading = 1,
- k_world_status_loaded = 2,
- k_world_status_unloading = 3 /* dont spawn sounds and stuff */
- }
- status;
-
- struct{
- boxf depthbounds;
- int depth_computed;
-
- float height;
- int enabled;
- v4f plane;
- }
- water;
-
- f64 time;
- f32 tar_min, tar_max;
-
- /* STD140 */
- struct ub_world_lighting{
- v4f g_cube_min,
- g_cube_inv_range;
-
- v4f g_water_plane,
- g_depth_bounds;
-
- v4f g_daysky_colour;
- v4f g_nightsky_colour;
- v4f g_sunset_colour;
- v4f g_ambient_colour;
- v4f g_sunset_ambient;
- v4f g_sun_colour;
- v4f g_sun_dir;
- v4f g_board_0;
- v4f g_board_1;
-
- float g_water_fog;
- float g_time;
- float g_realtime;
- float g_shadow_length;
- float g_shadow_spread;
-
- float g_time_of_day;
- float g_day_phase;
- float g_sunset_phase;
-
- int g_light_preview;
- int g_shadow_samples;
-
- int g_debug_indices;
- int g_debug_complexity;
- }
- ub_lighting;
- GLuint ubo_lighting;
- int ubo_bind_point;
-
- GLuint tbo_light_entities,
- tex_light_entities,
- tex_light_cubes;
-
- float probabilities[3];
-
- v3i light_cubes;
- vg_framebuffer *heightmap;
-
- /*
- * Dynamically allocated when world_load is called.
- *
- * the following arrays index somewhere into this linear
- * allocator
- * --------------------------------------------------------------------------
- */
-
- /*
- * Main world .mdl
- */
- mdl_context meta;
-
- GLuint *textures;
- u32 texture_count;
-
- struct world_surface{
- mdl_material info;
- mdl_submesh sm_geo,
- sm_no_collide;
- u32 flags;
- u32 alpha_tex;
- }
- * surfaces;
- u32 surface_count;
-
- ent_worldinfo info;
- mdl_array_ptr ent_spawn,
- ent_gate,
- ent_light,
- ent_route_node,
- ent_path_index,
- ent_checkpoint,
- ent_route,
- ent_water,
-
- ent_audio_clip,
- ent_audio,
- ent_volume,
- ent_traffic,
- ent_skateshop,
- ent_marker,
- ent_camera,
- ent_swspreview,
- ent_ccmd,
- ent_objective,
- ent_challenge,
- ent_relay,
- ent_cubemap,
- ent_miniworld,
- ent_prop,
- ent_region,
- ent_glider,
- ent_npc;
-
- enum skybox {
- k_skybox_default,
- k_skybox_space
- } skybox;
-
- ent_gate *rendering_gate;
-
- /* logic
- * ----------------------------------------------------
- */
-
- /* world geometry */
- scene_context scene_geo,
- scene_no_collide,
- scene_lines;
-
- /* spacial mappings */
- bh_tree *geo_bh,
- *entity_bh;
- u32 *entity_list;
-
- /* graphics */
- glmesh mesh_route_lines;
- glmesh mesh_geo,
- mesh_no_collide;
- u32 cubemap_cooldown, cubemap_side;
-
- /* leaderboards */
- struct leaderboard_cache *leaderboard_cache;
-
- /* ui */
- struct route_ui *routes_ui;
-};
-
-struct world_static {
- /*
- * Allocated as system memory
- * --------------------------------------------------------------------------
- */
- void *heap;
-
- u32 current_run_version;
- double time, rewind_from, rewind_to, last_use;
-
- u32 active_trigger_volumes[8];
- u32 active_trigger_volume_count;
-
- addon_reg *instance_addons[ k_world_max ];
- world_instance instances[ k_world_max ];
-
- enum world_purpose active_instance;
- u32 focused_entity; /* like skateshop, challenge.. */
- f32 focus_strength;
- vg_camera focus_cam;
-
- /* challenges */
- ent_objective *challenge_target;
- f32 challenge_timer;
-
- enum world_loader_state{
- k_world_loader_none,
- k_world_loader_preload,
- k_world_loader_load
- }
- load_state;
-
- bool clear_async_op_when_done;
-}
-extern world_static;
-
-struct world_load_args
-{
- enum world_purpose purpose;
- addon_reg *reg;
-};
-
-void world_init(void);
-world_instance *world_current_instance(void);
-void world_switch_instance( u32 index );
-void skaterift_world_load_thread( void *_args );
-void world_update( world_instance *world, v3f pos );
+++ /dev/null
-#include "audio.h"
-#include "world_audio.h"
-
-/* finds any active playing in world and fades them out, we can only do this
- * while unloading */
-void world_fadeout_audio( world_instance *world )
-{
- if( world->status != k_world_status_unloading ){
- vg_fatal_error( "World status must be set to 'unloading', to fadeout"
- " audio.\n" );
- }
-
- u8 world_id = (world - world_static.instances) + 1;
-
- audio_lock();
- for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
- audio_channel *ch = &vg_audio.channels[i];
-
- if( ch->allocated && (ch->world_id == world_id) ){
- ch = audio_channel_fadeout( ch, 1.0f );
- }
- }
- audio_unlock();
-}
-
-/*
- * Trace out a random point, near the player to try and determine water areas
- */
-enum audio_sprite_type world_audio_sample_sprite_random(v3f origin, v3f output)
-{
- v3f chance = { (vg_randf64(&vg.rand)-0.5f) * 30.0f,
- 8,
- (vg_randf64(&vg.rand)-0.5f) * 30.0f };
-
- v3f pos;
- v3_add( chance, origin, pos );
-
- ray_hit contact;
- contact.dist = vg_minf( 16.0f, pos[1] );
-
- world_instance *world = world_current_instance();
-
- if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &contact,
- k_material_flag_ghosts ) ){
- struct world_surface *mat = ray_hit_surface( world, &contact );
-
- if( mat->info.surface_prop == k_surface_prop_grass){
- v3_copy( contact.pos, output );
- return k_audio_sprite_type_grass;
- }
- else{
- return k_audio_sprite_type_none;
- }
- }
-
- output[0] = pos[0];
- output[1] = 0.0f;
- output[2] = pos[2];
-
- float dist = fabsf(output[1] - origin[1]);
-
- if( world->water.enabled && dist<=40.0f && !(world->info.flags&0x2) )
- return k_audio_sprite_type_water;
- else
- return k_audio_sprite_type_none;
-}
-
-void world_audio_sample_distances( v3f co, int *index, float *value )
-{
- float inr3 = 0.57735027,
- inr2 = 0.70710678118;
-
- v3f sample_directions[] = {
- { -1.0f, 0.0f, 0.0f },
- { 1.0f, 0.0f, 0.0f },
- { 0.0f, 0.0f, 1.0f },
- { 0.0f, 0.0f, -1.0f },
- { 0.0f, 1.0f, 0.0f },
- { 0.0f, -1.0f, 0.0f },
- { -inr3, inr3, inr3 },
- { inr3, inr3, inr3 },
- { -inr3, inr3, -inr3 },
- { inr3, inr3, -inr3 },
- { -inr2, 0.0f, inr2 },
- { inr2, 0.0f, inr2 },
- { -inr2, 0.0f, -inr2 },
- { inr2, 0.0f, -inr2 },
- };
-
- static int si = 0;
- static float distances[16];
-
- ray_hit ray;
- ray.dist = 5.0f;
-
- v3f rc, rd, ro;
- v3_copy( sample_directions[ si ], rd );
- v3_add( co, (v3f){0.0f,1.5f,0.0f}, ro );
- v3_copy( ro, rc );
-
- float dist = 200.0f;
-
- for( int i=0; i<10; i++ ){
- if( ray_world( world_current_instance(), rc, rd, &ray,
- k_material_flag_ghosts ) ){
- dist = (float)i*5.0f + ray.dist;
- break;
- }
- else{
- v3_muladds( rc, rd, ray.dist, rc );
- }
- }
-
- distances[si] = dist;
-
- if( vg_audio.debug_ui && vg_lines.enabled ){
- for( int i=0; i<14; i++ ){
- if( distances[i] != 200.0f ){
- u32 colours[] = { VG__RED, VG__BLUE, VG__GREEN,
- VG__CYAN, VG__YELOW, VG__PINK,
- VG__WHITE };
-
- u32 colour = colours[i%7];
-
- v3f p1;
- v3_muladds( ro, sample_directions[i], distances[i], p1 );
- vg_line( ro, p1, colour );
- vg_line_point( p1, 0.1f, colour );
- }
- }
- }
-
- *index = si;
- *value = dist;
-
- si ++;
- if( si >= 14 )
- si = 0;
-}
+++ /dev/null
-#pragma once
-#include "world.h"
-
-void world_fadeout_audio( world_instance *world );
-void world_audio_sample_distances( v3f co, int *index, float *value );
-enum audio_sprite_type
-world_audio_sample_sprite_random( v3f origin, v3f output );
+++ /dev/null
-#include "vg/vg_steam.h"
-#include "vg/vg_steam_user_stats.h"
-#include "model.h"
-#include "entity.h"
-#include "world.h"
-#include "world_load.h"
-#include "save.h"
-#include "vg/vg_msg.h"
-#include "menu.h"
-#include "ent_challenge.h"
-#include "ent_skateshop.h"
-#include "ent_route.h"
-#include "ent_traffic.h"
-#include "ent_glider.h"
-#include "ent_region.h"
-#include "ent_npc.h"
-#include "ent_camera.h"
-#include "input.h"
-#include "player_walk.h"
-
-bh_system bh_system_entity_list =
-{
- .expand_bound = entity_bh_expand_bound,
- .item_centroid = entity_bh_centroid,
- .item_closest = entity_bh_closest,
- .item_swap = entity_bh_swap,
- .item_debug = entity_bh_debug,
- .cast_ray = NULL
-};
-
-void world_entity_set_focus( u32 entity_id )
-{
- if( world_static.focused_entity )
- {
- vg_warn( "Entity %u#%u tried to take focus from %u#%u\n",
- mdl_entity_id_type( entity_id ),
- mdl_entity_id_id( entity_id ),
- mdl_entity_id_type( world_static.focused_entity ),
- mdl_entity_id_id( world_static.focused_entity ) );
- return;
- }
-
- world_static.focused_entity = entity_id;
-}
-
-void world_entity_focus_modal(void)
-{
- localplayer.immobile = 1;
- menu.disable_open = 1;
- srinput.state = k_input_state_resume;
-
- v3_zero( localplayer.rb.v );
- v3_zero( localplayer.rb.w );
- player_walk.move_speed = 0.0f;
- skaterift.activity = k_skaterift_ent_focus;
-}
-
-void world_entity_exit_modal(void)
-{
- if( skaterift.activity != k_skaterift_ent_focus )
- {
- vg_warn( "Entity %u#%u tried to exit modal when we weren't in one\n",
- mdl_entity_id_type( world_static.focused_entity ),
- mdl_entity_id_id( world_static.focused_entity ) );
- return;
- }
-
- localplayer.immobile = 0;
- menu.disable_open = 0;
- srinput.state = k_input_state_resume;
- skaterift.activity = k_skaterift_default;
-}
-
-void world_entity_clear_focus(void)
-{
- if( skaterift.activity == k_skaterift_ent_focus )
- {
- vg_warn( "Entity %u#%u tried to clear focus before exiting modal\n",
- mdl_entity_id_type( world_static.focused_entity ),
- mdl_entity_id_id( world_static.focused_entity ) );
- return;
- }
-
- world_static.focused_entity = 0;
-}
-
-void world_entity_focus_camera( world_instance *world, u32 uid )
-{
- if( mdl_entity_id_type( uid ) == k_ent_camera )
- {
- u32 index = mdl_entity_id_id( uid );
- ent_camera *cam = mdl_arritm( &world->ent_camera, index );
- ent_camera_unpack( cam, &world_static.focus_cam );
- }
- else
- {
- vg_camera_copy( &localplayer.cam, &world_static.focus_cam );
-
- /* TODO ? */
- world_static.focus_cam.nearz = localplayer.cam.nearz;
- world_static.focus_cam.farz = localplayer.cam.farz;
- }
-}
-
-/* logic preupdate */
-void world_entity_focus_preupdate(void)
-{
- f32 rate = vg_minf( 1.0f, vg.time_frame_delta * 2.0f );
- int active = 0;
- if( skaterift.activity == k_skaterift_ent_focus )
- active = 1;
-
- vg_slewf( &world_static.focus_strength, active,
- vg.time_frame_delta * (1.0f/0.5f) );
-
- if( world_static.focused_entity == 0 )
- return;
-
- u32 type = mdl_entity_id_type( world_static.focused_entity ),
- index = mdl_entity_id_id( world_static.focused_entity );
-
- world_instance *world = world_current_instance();
-
- static void (*table[])( ent_focus_context *ctx ) =
- {
- [ k_ent_skateshop ] = ent_skateshop_preupdate,
- [ k_ent_challenge ] = ent_challenge_preupdate,
- [ k_ent_route ] = ent_route_preupdate,
- [ k_ent_npc ] = ent_npc_preupdate,
- };
-
- if( (type > VG_ARRAY_LEN(table)) || (table[type] == NULL) )
- {
- vg_fatal_error( "No pre-update method set for entity (%u#%u)\n",
- type, index );
- }
-
- table[type]( &(ent_focus_context){
- .world = world,
- .index = index,
- .active = active } );
-}
-
-/* additional renderings like text etc.. */
-void world_entity_focus_render(void)
-{
- world_instance *world = world_current_instance();
- if( skaterift.activity != k_skaterift_ent_focus ){
- skateshop_render_nonfocused( world, &g_render.cam );
- return;
- }
-
- u32 type = mdl_entity_id_type( world_static.focused_entity ),
- index = mdl_entity_id_id( world_static.focused_entity );
-
- if( type == k_ent_skateshop ){
- ent_skateshop *skateshop = mdl_arritm( &world->ent_skateshop, index );
- skateshop_render( skateshop );
- }
- else if( type == k_ent_challenge ){}
- else if( type == k_ent_route ){}
- else if( type == k_ent_miniworld ){}
- else if( type == k_ent_npc ){}
- else {
- vg_fatal_error( "Programming error\n" );
- }
-}
-
-void world_gen_entities_init( world_instance *world )
-{
- /* lights */
- for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
- ent_light *light = mdl_arritm( &world->ent_light, j );
-
- m4x3f to_world;
- q_m3x3( light->transform.q, to_world );
- v3_copy( light->transform.co, to_world[3] );
- m4x3_invert_affine( to_world, light->inverse_world );
-
- light->angle_sin_cos[0] = sinf( light->angle * 0.5f );
- light->angle_sin_cos[1] = cosf( light->angle * 0.5f );
- }
-
- vg_async_call( world_link_gates_async, world, 0 );
- vg_async_stall();
-
- /* water */
- for( u32 j=0; j<mdl_arrcount(&world->ent_water); j++ ){
- ent_water *water = mdl_arritm( &world->ent_water, j );
- if( world->water.enabled ){
- vg_warn( "Multiple water surfaces in level!\n" );
- break;
- }
-
- world->water.enabled = 1;
- water_set_surface( world, water->transform.co[1] );
- }
-
- /* volumes */
- for( u32 j=0; j<mdl_arrcount(&world->ent_volume); j++ ){
- ent_volume *volume = mdl_arritm( &world->ent_volume, j );
- mdl_transform_m4x3( &volume->transform, volume->to_world );
- m4x3_invert_full( volume->to_world, volume->to_local );
- }
-
- /* audio packs */
- for( u32 j=0; j<mdl_arrcount(&world->ent_audio); j++ ){
- ent_audio *audio = mdl_arritm( &world->ent_audio, j );
-
- for( u32 k=0; k<audio->clip_count; k++ ){
- ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
- audio->clip_start+k );
-
- if( clip->_.file.pack_size ){
- u32 size = clip->_.file.pack_size,
- offset = clip->_.file.pack_offset;
-
- /* embedded files are fine to clear the scratch buffer, only
- * external audio uses it */
-
- vg_linear_clear( vg_mem.scratch );
- void *data = vg_linear_alloc( vg_mem.scratch,
- clip->_.file.pack_size );
-
- mdl_fread_pack_file( &world->meta, &clip->_.file, data );
-
- clip->_.clip.path = NULL;
- clip->_.clip.flags = audio->flags;
- clip->_.clip.data = data;
- clip->_.clip.size = size;
- }
- else{
- clip->_.clip.path = mdl_pstr(&world->meta,clip->_.file.pstr_path);
- clip->_.clip.flags = audio->flags;
- clip->_.clip.data = NULL;
- clip->_.clip.size = 0;
- }
-
- audio_clip_load( &clip->_.clip, world->heap );
- }
- }
-
- /* create generic entity hierachy for those who need it */
- u32 indexed_count = 0;
- struct {
- u32 type;
- mdl_array_ptr *array;
- }
- indexables[] = {
- { k_ent_gate, &world->ent_gate },
- { k_ent_objective, &world->ent_objective },
- { k_ent_volume, &world->ent_volume },
- { k_ent_challenge, &world->ent_challenge },
- { k_ent_glider, &world->ent_glider },
- { k_ent_npc, &world->ent_npc }
- };
-
- for( u32 i=0; i<VG_ARRAY_LEN(indexables); i++ )
- indexed_count += mdl_arrcount( indexables[i].array );
- vg_info( "indexing %u entities\n", indexed_count );
-
- world->entity_list = vg_linear_alloc( world->heap,
- vg_align8(indexed_count*sizeof(u32)));
-
- u32 index=0;
- for( u32 i=0; i<VG_ARRAY_LEN(indexables); i++ ){
- u32 type = indexables[i].type,
- count = mdl_arrcount( indexables[i].array );
-
- for( u32 j=0; j<count; j ++ )
- world->entity_list[index ++] = mdl_entity_id( type, j );
- }
-
- world->entity_bh = bh_create( world->heap, &bh_system_entity_list, world,
- indexed_count, 2 );
-
- world->tar_min = world->entity_bh->nodes[0].bbx[0][1];
- world->tar_max = world->entity_bh->nodes[0].bbx[1][1] + 20.0f;
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_marker); i++ ){
- ent_marker *marker = mdl_arritm( &world->ent_marker, i );
-
- if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_min" ) )
- world->tar_min = marker->transform.co[1];
-
- if( MDL_CONST_PSTREQ( &world->meta, marker->pstr_alias, "tar_max" ) )
- world->tar_max = marker->transform.co[1];
- }
-}
-
-ent_spawn *world_find_closest_spawn( world_instance *world, v3f position )
-{
- ent_spawn *rp = NULL, *r;
- float min_dist = INFINITY;
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
- r = mdl_arritm( &world->ent_spawn, i );
- float d = v3_dist2( r->transform.co, position );
-
- if( d < min_dist ){
- min_dist = d;
- rp = r;
- }
- }
-
- if( !rp ){
- if( mdl_arrcount(&world->ent_spawn) ){
- vg_warn( "Invalid distances to spawns.. defaulting to first one.\n" );
- return mdl_arritm( &world->ent_spawn, 0 );
- }
- else{
- vg_error( "There are no spawns in the level!\n" );
- }
- }
-
- return rp;
-}
-
-ent_spawn *world_find_spawn_by_name( world_instance *world, const char *name )
-{
- ent_spawn *rp = NULL, *r;
- for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ ){
- r = mdl_arritm( &world->ent_spawn, i );
- if( !strcmp( mdl_pstr(&world->meta, r->pstr_name), name ) ){
- rp = r;
- break;
- }
- }
-
- if( !rp )
- vg_warn( "No spawn named '%s'\n", name );
-
- return rp;
-}
-
-void world_default_spawn_pos( world_instance *world, v3f pos )
-{
- ent_spawn *rp = world_find_spawn_by_name( world, "start" );
- if( !rp ) rp = world_find_closest_spawn( world, (v3f){0,0,0} );
- if( rp )
- v3_copy( rp->transform.co, pos );
- else
- {
- vg_error( "There are no valid spawns in the world\n" );
- v3_zero( pos );
- }
-}
-
-entity_call_result ent_volume_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_volume *volume = mdl_arritm( &world->ent_volume, index );
-
- if( !volume->target )
- return k_entity_call_result_OK;
-
- if( call->function == k_ent_function_trigger )
- {
- call->id = volume->target;
-
- if( volume->flags & k_ent_volume_flag_particles )
- {
- float *co = alloca( sizeof(float)*3 );
- co[0] = vg_randf64(&vg.rand)*2.0f-1.0f;
- co[1] = vg_randf64(&vg.rand)*2.0f-1.0f;
- co[2] = vg_randf64(&vg.rand)*2.0f-1.0f;
- m4x3_mulv( volume->to_world, co, co );
-
- call->function = k_ent_function_particle_spawn;
- call->data = co;
- entity_call( world, call );
- }
- else
- {
- call->function = volume->trigger.event;
- entity_call( world, call );
- }
-
- return k_entity_call_result_OK;
- }
- else if( call->function == k_ent_function_trigger_leave )
- {
- call->id = volume->target;
-
- if( volume->flags & k_ent_volume_flag_particles )
- {
- vg_warn( "Invalid condition; calling leave on particle volume.\n" );
- }
- else
- {
- call->function = volume->trigger.event_leave;
- entity_call( world, call );
- }
-
- return k_entity_call_result_OK;
- }
-
- return k_entity_call_result_unhandled;
-}
-
-entity_call_result ent_audio_call( world_instance *world, ent_call *call )
-{
- if( world->status == k_world_status_unloading )
- {
- vg_warn( "cannot modify audio while unloading world\n" );
- return k_entity_call_result_invalid;
- }
-
- u8 world_id = (world - world_static.instances) + 1;
- u32 index = mdl_entity_id_id( call->id );
- ent_audio *audio = mdl_arritm( &world->ent_audio, index );
-
- v3f sound_co;
-
- if( call->function == k_ent_function_particle_spawn )
- {
- v3_copy( call->data, sound_co );
- }
- else if( call->function == k_ent_function_trigger )
- {
- v3_copy( audio->transform.co, sound_co );
- }
- else
- return k_entity_call_result_unhandled;
-
- float chance = vg_randf64(&vg.rand)*100.0f,
- bar = 0.0f;
-
- for( u32 i=0; i<audio->clip_count; i++ ){
- ent_audio_clip *clip = mdl_arritm( &world->ent_audio_clip,
- audio->clip_start+i );
-
- float mod = world->probabilities[ audio->probability_curve ],
- p = clip->probability * mod;
-
- bar += p;
- if( chance < bar )
- {
- audio_lock();
-
- if( audio->behaviour == k_channel_behaviour_unlimited )
- {
- audio_oneshot_3d( &clip->_.clip, sound_co,
- audio->transform.s[0],
- audio->volume );
- }
- else if( audio->behaviour == k_channel_behaviour_discard_if_full )
- {
- audio_channel *ch =
- audio_get_group_idle_channel( audio->group,
- audio->max_channels );
-
- if( ch )
- {
- audio_channel_init( ch, &clip->_.clip, audio->flags );
- audio_channel_group( ch, audio->group );
- audio_channel_world( ch, world_id );
- audio_channel_set_spacial( ch, sound_co, audio->transform.s[0] );
- audio_channel_edit_volume( ch, audio->volume, 1 );
- ch = audio_relinquish_channel( ch );
- }
- }
- else if( audio->behaviour == k_channel_behaviour_crossfade_if_full)
- {
- audio_channel *ch =
- audio_get_group_idle_channel( audio->group,
- audio->max_channels );
-
- /* group is full */
- if( !ch ){
- audio_channel *existing =
- audio_get_group_first_active_channel( audio->group );
-
- if( existing ){
- if( existing->source == &clip->_.clip ){
- audio_unlock();
- return k_entity_call_result_OK;
- }
-
- existing->group = 0;
- existing = audio_channel_fadeout(existing, audio->crossfade);
- }
-
- ch = audio_get_first_idle_channel();
- }
-
- if( ch )
- {
- audio_channel_init( ch, &clip->_.clip, audio->flags );
- audio_channel_group( ch, audio->group );
- audio_channel_world( ch, world_id );
- audio_channel_fadein( ch, audio->crossfade );
- ch = audio_relinquish_channel( ch );
- }
- }
-
- audio_unlock();
- return k_entity_call_result_OK;
- }
- }
- return k_entity_call_result_OK;
-}
-
-
-entity_call_result ent_ccmd_call( world_instance *world, ent_call *call )
-{
- if( call->function == k_ent_function_trigger )
- {
- u32 index = mdl_entity_id_id( call->id );
- ent_ccmd *ccmd = mdl_arritm( &world->ent_ccmd, index );
- vg_execute_console_input( mdl_pstr(&world->meta, ccmd->pstr_command), 0 );
- return k_entity_call_result_OK;
- }
- else
- return k_entity_call_result_unhandled;
-}
-
-/*
- * BVH implementation
- * ----------------------------------------------------------------------------
- */
-
-void entity_bh_expand_bound( void *user, boxf bound, u32 item_index )
-{
- world_instance *world = user;
-
- u32 id = world->entity_list[ item_index ],
- type = mdl_entity_id_type( id ),
- index = mdl_entity_id_id( id );
-
- if( type == k_ent_gate ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, index );
- boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
- { gate->dimensions[0], gate->dimensions[1], 0.1f }};
-
- m4x3_expand_aabb_aabb( gate->to_world, bound, box );
- }
- else if( type == k_ent_objective ){
- ent_objective *objective = mdl_arritm( &world->ent_objective, index );
-
- /* TODO: This might be more work than necessary. could maybe just get
- * away with representing them as points */
-
- boxf box;
- box_init_inf( box );
-
- for( u32 i=0; i<objective->submesh_count; i++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- objective->submesh_start+i );
- box_concat( box, sm->bbx );
- }
-
- m4x3f transform;
- mdl_transform_m4x3( &objective->transform, transform );
- m4x3_expand_aabb_aabb( transform, bound, box );
- }
- else if( type == k_ent_volume ){
- ent_volume *volume = mdl_arritm( &world->ent_volume, index );
- m4x3_expand_aabb_aabb( volume->to_world, bound,
- (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
- }
- else if( type == k_ent_challenge ){
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
-
- boxf box = {{-1.2f*0.5f,-0.72f*0.5f,-0.01f*0.5f},
- { 1.2f*0.5f, 0.72f*0.5f, 0.01f*0.5f}};
- m4x3f transform;
- mdl_transform_m4x3( &challenge->transform, transform );
- m4x3_expand_aabb_aabb( transform, bound, box );
- }
- else if( type == k_ent_glider ){
- ent_glider *glider = mdl_arritm( &world->ent_glider, index );
- m4x3f transform;
- mdl_transform_m4x3( &glider->transform, transform );
- m4x3_expand_aabb_aabb( transform, bound,
- (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}} );
- }
- else if( type == k_ent_npc )
- {
- ent_npc *npc = mdl_arritm( &world->ent_npc, index );
- box_addpt( bound, npc->transform.co );
- }
- else{
- vg_fatal_error( "Programming error\n" );
- }
-}
-
-float entity_bh_centroid( void *user, u32 item_index, int axis )
-{
- world_instance *world = user;
-
- u32 id = world->entity_list[ item_index ],
- type = mdl_entity_id_type( id ),
- index = mdl_entity_id_id( id );
-
- if( type == k_ent_gate ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, index );
- return gate->to_world[3][axis];
- }
- else if( type == k_ent_objective ){
- ent_objective *objective = mdl_arritm( &world->ent_objective, index );
- return objective->transform.co[axis];
- }
- else if( type == k_ent_volume ){
- ent_volume *volume = mdl_arritm( &world->ent_volume, index );
- return volume->transform.co[axis];
- }
- else if( type == k_ent_challenge )
- {
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
- return challenge->transform.co[axis];
- }
- else if( type == k_ent_glider )
- {
- ent_glider *glider = mdl_arritm( &world->ent_glider, index );
- return glider->transform.co[axis];
- }
- else if( type == k_ent_npc )
- {
- ent_npc *npc = mdl_arritm( &world->ent_npc, index );
- return npc->transform.co[axis];
- }
- else
- {
- vg_fatal_error( "Programming error\n" );
- return INFINITY;
- }
-}
-
-void entity_bh_swap( void *user, u32 ia, u32 ib )
-{
- world_instance *world = user;
-
- u32 a = world->entity_list[ ia ],
- b = world->entity_list[ ib ];
-
- world->entity_list[ ia ] = b;
- world->entity_list[ ib ] = a;
-}
-
-void entity_bh_debug( void *user, u32 item_index ){
- world_instance *world = user;
-
- u32 id = world->entity_list[ item_index ],
- type = mdl_entity_id_type( id ),
- index = mdl_entity_id_id( id );
-
- if( type == k_ent_gate ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, index );
- boxf box = {{ -gate->dimensions[0], -gate->dimensions[1], -0.1f },
- { gate->dimensions[0], gate->dimensions[1], 0.1f }};
- vg_line_boxf_transformed( gate->to_world, box, 0xf000ff00 );
- }
- else if( type == k_ent_objective ){
- ent_objective *objective = mdl_arritm( &world->ent_objective, index );
- boxf box;
- box_init_inf( box );
-
- for( u32 i=0; i<objective->submesh_count; i++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- objective->submesh_start+i );
- box_concat( box, sm->bbx );
- }
-
- m4x3f transform;
- mdl_transform_m4x3( &objective->transform, transform );
- vg_line_boxf_transformed( transform, box, 0xf000ff00 );
- }
- else if( type == k_ent_volume ){
- ent_volume *volume = mdl_arritm( &world->ent_volume, index );
- vg_line_boxf_transformed( volume->to_world,
- (boxf){{-1.0f,-1.0f,-1.0f},{ 1.0f, 1.0f, 1.0f}},
- 0xf000ff00 );
- }
- else if( type == k_ent_challenge ){
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
-
- boxf box = {{-1.2f*0.5f,-0.72f*0.5f,-0.01f*0.5f},
- { 1.2f*0.5f, 0.72f*0.5f, 0.01f*0.5f}};
- m4x3f transform;
- mdl_transform_m4x3( &challenge->transform, transform );
- vg_line_boxf_transformed( transform, box, 0xf0ff0000 );
- }
- else{
- vg_fatal_error( "Programming error\n" );
- }
-}
-
-void update_ach_models(void)
-{
- world_instance *hub = &world_static.instances[k_world_purpose_hub];
- if( hub->status != k_world_status_loaded ) return;
-
- for( u32 i=0; i<mdl_arrcount( &hub->ent_prop ); i ++ ){
- ent_prop *prop = mdl_arritm( &hub->ent_prop, i );
- if( prop->flags & 0x2 ){
- if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "MARC" ) )
- if( skaterift.achievements & 0x1 )
- prop->flags &= ~0x1;
- if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "ALBERT" ) )
- if( skaterift.achievements & 0x2 )
- prop->flags &= ~0x1;
- if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "JANET" ) )
- if( skaterift.achievements & 0x4 )
- prop->flags &= ~0x1;
- if( MDL_CONST_PSTREQ( &hub->meta, prop->pstr_alias, "BERNADETTA" ) )
- if( skaterift.achievements & 0x8 )
- prop->flags &= ~0x1;
- }
- }
-}
-
-void entity_bh_closest( void *user, u32 item_index, v3f point, v3f closest )
-{
- world_instance *world = user;
-
- u32 id = world->entity_list[ item_index ],
- type = mdl_entity_id_type( id ),
- index = mdl_entity_id_id( id );
-
- if( type == k_ent_gate ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, index );
- v3_copy( gate->to_world[3], closest );
- }
- else if( type == k_ent_objective ){
- ent_objective *challenge = mdl_arritm( &world->ent_objective, index );
- v3_copy( challenge->transform.co, closest );
- }
- else if( type == k_ent_volume ){
- ent_volume *volume = mdl_arritm( &world->ent_volume, index );
- v3_copy( volume->to_world[3], closest );
- }
- else if( type == k_ent_challenge ){
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
- v3_copy( challenge->transform.co, closest );
- }
- else{
- vg_fatal_error( "Programming error\n" );
- }
-}
-
-void world_entity_start( world_instance *world, vg_msg *sav )
-{
- vg_info( "Start instance %p\n", world );
-
- world->probabilities[ k_probability_curve_constant ] = 1.0f;
- for( u32 i=0; i<mdl_arrcount(&world->ent_audio); i++ )
- {
- ent_audio *audio = mdl_arritm(&world->ent_audio,i);
- if( audio->flags & AUDIO_FLAG_AUTO_START )
- {
- ent_call call;
- call.data = NULL;
- call.function = k_ent_function_trigger;
- call.id = mdl_entity_id( k_ent_audio, i );
- entity_call( world, &call );
- }
- }
-
- /* read savedata
- * ----------------------------------------------------------------------- */
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
- const char *alias = mdl_pstr( &world->meta, challenge->pstr_alias );
-
- u32 result;
- vg_msg_getkvintg( sav, alias, k_vg_msg_u32, &result, NULL );
-
- if( result ){
- ent_call call;
- call.data = NULL;
- call.function = 0;
- call.id = mdl_entity_id( k_ent_challenge, i );
- entity_call( world, &call );
- }
- }
-
- vg_msg routes_block = *sav;
- if( vg_msg_seekframe( &routes_block, "routes" ) ){
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- vg_msg route_info = routes_block;
- if( vg_msg_seekframe( &route_info,
- mdl_pstr(&world->meta,route->pstr_name) ) ){
-
- u32 flags;
- vg_msg_getkvintg( &route_info, "flags", k_vg_msg_u32,
- &flags, NULL );
- route->flags |= flags;
-
- vg_msg_getkvintg( &route_info, "best_laptime", k_vg_msg_f64,
- &route->best_laptime, NULL );
-
- f32 sections[ route->checkpoints_count ];
- vg_msg_cmd cmd;
- if( vg_msg_getkvcmd( &route_info, "sections", &cmd ) ){
- vg_msg_cast( cmd.value, cmd.code, sections,
- k_vg_msg_f32 |
- vg_msg_count_bits(route->checkpoints_count) );
- }
- else{
- for( u32 j=0; j<route->checkpoints_count; j ++ )
- sections[j] = 0.0f;
- }
-
- for( u32 j=0; j<route->checkpoints_count; j ++ ){
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
- route->checkpoints_start + j );
-
- cp->best_time = sections[j];
- }
-
- /* LEGACY: check if steam achievements can give us a medal */
- if( steam_ready && steam_stats_ready ){
- for( u32 j=0; j<VG_ARRAY_LEN(track_infos); j ++ ){
- struct track_info *inf = &track_infos[j];
- if( !strcmp(inf->name,
- mdl_pstr(&world->meta,route->pstr_name))){
-
- steamapi_bool set = 0;
- if( SteamAPI_ISteamUserStats_GetAchievement(
- hSteamUserStats, inf->achievement_id, &set ) )
- {
- if( set ){
- route->flags |= k_ent_route_flag_achieve_silver;
- }
- }
- }
- }
- }
- }
- }
- }
-
- ent_region_re_eval( world );
-}
-
-void world_entity_serialize( world_instance *world, vg_msg *sav )
-{
- for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ ){
- ent_challenge *challenge = mdl_arritm(&world->ent_challenge,i);
-
- const char *alias = mdl_pstr(&world->meta,challenge->pstr_alias);
- vg_msg_wkvnum( sav, alias, k_vg_msg_u32, 1, &challenge->status );
- }
-
- if( mdl_arrcount(&world->ent_route) ){
- vg_msg_frame( sav, "routes" );
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- vg_msg_frame( sav, mdl_pstr( &world->meta, route->pstr_name ) );
- {
- vg_msg_wkvnum( sav, "flags", k_vg_msg_u32, 1, &route->flags );
- vg_msg_wkvnum( sav, "best_laptime",
- k_vg_msg_f64, 1, &route->best_laptime );
-
- f32 sections[ route->checkpoints_count ];
-
- for( u32 j=0; j<route->checkpoints_count; j ++ ){
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
- route->checkpoints_start + j );
-
- sections[j] = cp->best_time;
- }
-
- vg_msg_wkvnum( sav, "sections", k_vg_msg_f32,
- route->checkpoints_count, sections );
- }
- vg_msg_end_frame( sav );
- }
- vg_msg_end_frame( sav );
- }
-}
+++ /dev/null
-#pragma once
-#include "world.h"
-#include "entity.h"
-#include "vg/vg_bvh.h"
-#include "vg/vg_msg.h"
-
-typedef struct ent_focus_context ent_focus_context;
-struct ent_focus_context
-{
- world_instance *world;
- u32 index; /* Array index of the focused entity */
- bool active;
-};
-
-void world_gen_entities_init( world_instance *world );
-ent_spawn *world_find_spawn_by_name( world_instance *world,
- const char *name );
-ent_spawn *world_find_closest_spawn( world_instance *world,
- v3f position );
-void world_default_spawn_pos( world_instance *world, v3f pos );
-void world_entity_start( world_instance *world, vg_msg *sav );
-void world_entity_serialize( world_instance *world, vg_msg *sav );
-
-entity_call_result ent_volume_call( world_instance *world, ent_call *call );
-entity_call_result ent_audio_call( world_instance *world, ent_call *call );
-entity_call_result ent_ccmd_call( world_instance *world, ent_call *call );
-
-void entity_bh_expand_bound( void *user, boxf bound, u32 item_index );
-float entity_bh_centroid( void *user, u32 item_index, int axis );
-void entity_bh_swap( void *user, u32 ia, u32 ib );
-void entity_bh_debug( void *user, u32 item_index );
-void entity_bh_closest( void *user, u32 item_index, v3f point,
- v3f closest );
-
-void world_entity_set_focus( u32 entity_id );
-void world_entity_focus_modal(void);
-
-void world_entity_exit_modal(void);
-void world_entity_clear_focus(void);
-
-void world_entity_focus_preupdate(void);
-void world_entity_focus_render(void);
-void world_entity_focus_camera( world_instance *world, u32 uid );
-void update_ach_models(void);
-
-extern bh_system bh_system_entity_list;
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#ifndef WORLD_GATE_C
-#define WORLD_GATE_C
-
-#include "world.h"
-#include "world_gate.h"
-
-#include "skaterift.h"
-#include "common.h"
-#include "model.h"
-#include "entity.h"
-#include "render.h"
-
-#include "world_water.h"
-#include "player_remote.h"
-#include "shaders/model_gate_unlinked.h"
-#include <string.h>
-
-struct world_gates world_gates;
-
-/*
- * Update the transform matrices for gate
- */
-void gate_transform_update( ent_gate *gate )
-{
- if( gate->flags & k_ent_gate_flip )
- {
- v4f qflip;
- q_axis_angle( qflip, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
- q_mul( gate->q[1], qflip, gate->q[1] );
- q_normalize( gate->q[1] );
- }
-
- m4x3f to_local, recv_to_world;
-
- q_m3x3( gate->q[0], gate->to_world );
- v3_copy( gate->co[0], gate->to_world[3] );
- m4x3_invert_affine( gate->to_world, to_local );
-
- q_m3x3( gate->q[1], recv_to_world );
- v3_copy( gate->co[1], recv_to_world[3] );
-
- m4x3_mul( recv_to_world, to_local, gate->transport );
-}
-
-void world_gates_init(void)
-{
- vg_info( "world_gates_init\n" );
- vg_linear_clear( vg_mem.scratch );
-
- mdl_context mgate;
- mdl_open( &mgate, "models/rs_gate.mdl", vg_mem.scratch );
- mdl_load_metadata_block( &mgate, vg_mem.scratch );
-
- mdl_mesh *surface = mdl_find_mesh( &mgate, "rs_gate" );
- mdl_submesh *sm = mdl_arritm(&mgate.submeshs,surface->submesh_start);
- world_gates.sm_surface = *sm;
-
- const char *names[] = { "rs_gate_marker", "rs_gate_marker.001",
- "rs_gate_marker.002", "rs_gate_marker.003" };
-
- for( int i=0; i<4; i++ ){
- mdl_mesh *marker = mdl_find_mesh( &mgate, names[i] );
- sm = mdl_arritm( &mgate.submeshs, marker->submesh_start );
- world_gates.sm_marker[i] = *sm;
- }
-
- mdl_async_load_glmesh( &mgate, &world_gates.mesh, NULL );
- mdl_close( &mgate );
-}
-
-void ent_gate_get_mdl_mtx( ent_gate *gate, m4x3f mmdl )
-{
- m4x3_copy( gate->to_world, mmdl );
-
- if( !(gate->flags & k_ent_gate_custom_mesh) ){
- m3x3_scale( mmdl, (v3f){ gate->dimensions[0],
- gate->dimensions[1], 1.0f } );
- }
-}
-
-static void render_gate_mesh( world_instance *world, ent_gate *gate )
-{
- if( gate->flags & k_ent_gate_custom_mesh ){
- mesh_bind( &world->mesh_no_collide );
- for( u32 i=0; i<gate->submesh_count; i++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- gate->submesh_start+i );
- mdl_draw_submesh( sm );
- }
- }
- else {
- mesh_bind( &world_gates.mesh );
- mdl_draw_submesh( &world_gates.sm_surface );
- }
-}
-
-/*
- * Render the view through a gate
- */
-int render_gate( world_instance *world, world_instance *world_inside,
- ent_gate *gate, vg_camera *cam )
-{
- v3f viewdir, gatedir;
- m3x3_mulv( cam->transform, (v3f){0.0f,0.0f,-1.0f}, viewdir );
- q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, gatedir );
-
- v3f v0;
- v3_sub( cam->pos, gate->co[0], v0 );
-
- float dist = v3_dot(v0, gatedir);
-
- /* Hard cutoff */
- if( dist > 3.0f )
- return 0;
-
- if( v3_dist( cam->pos, gate->co[0] ) > 100.0f )
- return 0;
-
- {
- f32 w = gate->dimensions[0],
- h = gate->dimensions[1];
-
- v3f a,b,c,d;
- m4x3_mulv( gate->to_world, (v3f){-w,-h,0.0f}, a );
- m4x3_mulv( gate->to_world, (v3f){ w,-h,0.0f}, b );
- m4x3_mulv( gate->to_world, (v3f){ w, h,0.0f}, c );
- m4x3_mulv( gate->to_world, (v3f){-w, h,0.0f}, d );
-
- vg_line( a,b, 0xffffa000 );
- vg_line( b,c, 0xffffa000 );
- vg_line( c,d, 0xffffa000 );
- vg_line( d,a, 0xffffa000 );
- vg_line( gate->co[0], gate->co[1], 0xff0000ff );
- }
-
- /* update gate camera */
- world_gates.cam.fov = cam->fov;
- world_gates.cam.nearz = 0.1f;
- world_gates.cam.farz = 2000.0f;
-
- m4x3_mul( gate->transport, cam->transform, world_gates.cam.transform );
- vg_camera_update_view( &world_gates.cam );
- vg_camera_update_projection( &world_gates.cam );
-
- /* Add special clipping plane to projection */
- v4f surface;
- q_mulv( gate->q[1], (v3f){0.0f,0.0f,-1.0f}, surface );
- surface[3] = v3_dot( surface, gate->co[1] );
-
- m4x3_mulp( world_gates.cam.transform_inverse, surface, surface );
- surface[3] = -fabsf(surface[3]);
-
- if( dist < -0.5f )
- m4x4_clip_projection( world_gates.cam.mtx.p, surface );
-
- /* Ready to draw with new camrea */
- vg_camera_finalize( &world_gates.cam );
-
- vg_line_point( world_gates.cam.transform[3], 0.3f, 0xff00ff00 );
-
- shader_model_gate_use();
- shader_model_gate_uPv( cam->mtx.pv );
- shader_model_gate_uCam( cam->pos );
- shader_model_gate_uColour( (v4f){0.0f,1.0f,0.0f,0.0f} );
- shader_model_gate_uTime( vg.time*0.25f );
- shader_model_gate_uInvRes( (v2f){
- 1.0f / (float)vg.window_x,
- 1.0f / (float)vg.window_y });
-
- glEnable( GL_STENCIL_TEST );
- glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
- glStencilFunc( GL_ALWAYS, 1, 0xFF );
- glStencilMask( 0xFF );
- glEnable( GL_CULL_FACE );
-
- m4x3f mmdl;
- ent_gate_get_mdl_mtx( gate, mmdl );
- shader_model_gate_uMdl( mmdl );
- render_gate_mesh( world, gate );
-
- render_world( world_inside, &world_gates.cam,
- 1, !localplayer.gate_waiting, 1, 1 );
-
- return 1;
-}
-
-void render_gate_unlinked( world_instance *world,
- ent_gate *gate, vg_camera *cam )
-{
- m4x3f mmdl; m4x4f m4mdl;
- ent_gate_get_mdl_mtx( gate, mmdl );
- m4x3_expand( mmdl, m4mdl );
- m4x4_mul( cam->mtx_prev.pv, m4mdl, m4mdl );
-
- shader_model_gate_unlinked_use();
- shader_model_gate_unlinked_uPv( cam->mtx.pv );
- shader_model_gate_unlinked_uPvmPrev( m4mdl );
- shader_model_gate_unlinked_uCam( cam->pos );
- shader_model_gate_unlinked_uColour( (v4f){0.0f,1.0f,0.0f,0.0f} );
- shader_model_gate_unlinked_uTime( vg.time*0.25f );
- shader_model_gate_unlinked_uMdl( mmdl );
-
- vg_line_point( gate->co[0], 0.1f, 0xffffff00 );
-
- render_gate_mesh( world, gate );
-}
-
-/*
- * Intersect the plane of a gate with a line segment, plane coordinate result
- * stored in 'where'
- */
-static int gate_intersect_plane( ent_gate *gate,
- v3f pos, v3f last, v2f where )
-{
- v4f surface;
- q_mulv( gate->q[0], (v3f){0.0f,0.0f,-1.0f}, surface );
- surface[3] = v3_dot( surface, gate->co[0] );
-
- v3f v0, c, delta, p0;
- v3_sub( pos, last, v0 );
- float l = v3_length( v0 );
-
- if( l == 0.0f )
- return 0;
-
- v3_divs( v0, l, v0 );
-
- v3_muls( surface, surface[3], c );
- v3_sub( c, last, delta );
-
- float d = v3_dot( surface, v0 );
-
- if( d > 0.00001f ){
- float t = v3_dot(delta, surface) / d;
- if( t >= 0.0f && t <= l ){
- v3f local, rel;
- v3_muladds( last, v0, t, local );
- v3_sub( gate->co[0], local, rel );
-
- where[0] = v3_dot( rel, gate->to_world[0] );
- where[1] = v3_dot( rel, gate->to_world[1] );
-
- where[0] /= v3_dot( gate->to_world[0], gate->to_world[0] );
- where[1] /= v3_dot( gate->to_world[1], gate->to_world[1] );
-
- return 1;
- }
- }
-
- return 0;
-}
-
-/*
- * Intersect specific gate
- */
-int gate_intersect( ent_gate *gate, v3f pos, v3f last )
-{
- v2f xy;
-
- if( gate_intersect_plane( gate, pos, last, xy ) ){
- if( (fabsf(xy[0]) <= gate->dimensions[0]) &&
- (fabsf(xy[1]) <= gate->dimensions[1]) ){
- return 1;
- }
- }
-
- return 0;
-}
-
-/*
- * Intersect all gates in the world
- */
-u32 world_intersect_gates( world_instance *world, v3f pos, v3f last )
-{
- for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, i );
-
- if( !(gate->flags & k_ent_gate_linked) ) continue;
- if( gate->flags & k_ent_gate_locked ) continue;
-
- if( gate->flags & k_ent_gate_nonlocal ){
- if( world_static.instances[gate->target].status
- != k_world_status_loaded )
- continue;
- }
-
- if( gate_intersect( gate, pos, last ) )
- return mdl_entity_id( k_ent_gate, i );
- }
-
- return 0;
-}
-
-entity_call_result ent_gate_call( world_instance *world, ent_call *call )
-{
- u32 index = mdl_entity_id_id( call->id );
- ent_gate *gate = mdl_arritm( &world->ent_gate, index );
-
- if( call->function == 0 ) /* unlock() */
- {
- gate->flags &= ~k_ent_gate_locked;
- return k_entity_call_result_OK;
- }
- else
- {
- return k_entity_call_result_unhandled;
- }
-}
-
-
-/*
- * detatches any nonlocal gates
- */
-void world_unlink_nonlocal( world_instance *world )
-{
- for( u32 j=0; j<mdl_arrcount(&world->ent_gate); j ++ )
- {
- ent_gate *gate = mdl_arritm( &world->ent_gate, j );
-
- if( gate->flags & k_ent_gate_nonlocal )
- {
- gate->flags &= ~k_ent_gate_linked;
- }
- }
-}
-
-/*
- * This has to be synchronous because main thread looks at gate data for
- * rendering, and we modify gates that the main thread has ownership of.
- */
-void world_link_gates_async( void *payload, u32 size )
-{
- VG_ASSERT( vg_thread_purpose() == k_thread_purpose_main );
-
- world_instance *world = payload;
- u32 world_id = world - world_static.instances;
-
- for( u32 j=0; j<mdl_arrcount(&world->ent_gate); j ++ )
- {
- ent_gate *gate = mdl_arritm( &world->ent_gate, j );
- gate_transform_update( gate );
-
- if( skaterift.demo_mode )
- if( world_static.instance_addons[world_id]->flags & ADDON_REG_PREMIUM )
- continue;
-
- if( !(gate->flags & k_ent_gate_nonlocal) ) continue;
- if( gate->flags & k_ent_gate_linked ) continue;
-
- const char *key = mdl_pstr( &world->meta, gate->key );
- vg_info( "key: %s\n", key );
-
- for( u32 i=0; i<VG_ARRAY_LEN(world_static.instances); i++ ){
- world_instance *other = &world_static.instances[i];
- if( other == world ) continue;
- if( other->status != k_world_status_loaded ) continue;
- vg_info( "Checking world %u for key matches\n", i );
-
- for( u32 k=0; k<mdl_arrcount( &other->ent_gate ); k++ ){
- ent_gate *gate2 = mdl_arritm( &other->ent_gate, k );
-
- if( !(gate2->flags & k_ent_gate_nonlocal) ) continue;
- if( gate2->flags & k_ent_gate_linked ) continue;
-
- const char *key2 = mdl_pstr( &other->meta, gate2->key );
- vg_info( " key2: %s\n", key2 );
-
- if( strcmp( key, key2 ) ) continue;
-
- vg_success( "Non-local matching pair '%s' found. (%u:%u)\n",
- key, world_id, i );
-
- gate->flags |= k_ent_gate_linked;
- gate2->flags |= k_ent_gate_linked;
- gate->target = i;
- gate2->target = world_id;
-
- v3_copy( gate->co[0], gate2->co[1] );
- v3_copy( gate2->co[0], gate->co[1] );
- v4_copy( gate->q[0], gate2->q[1] );
- v4_copy( gate2->q[0], gate->q[1] );
-
- if( world->meta.info.version < 102 ){
- /* LEGACY BEHAVIOUR: v101
- * this would flip both the client worlds portal's entrance and
- * exit. effectively the clients portal would be the opposite
- * to the hub worlds one. new behaviour is to just flip the
- * destinations so the rules are consistent in each world.
- */
- v4f qflip;
- q_axis_angle( qflip, (v3f){0.0f,1.0f,0.0f}, VG_PIf );
- q_mul( gate->q[0], qflip, gate->q[0] );
- q_mul( gate->q[1], qflip, gate->q[1] );
- q_mul( gate2->q[1], qflip, gate2->q[1] );
- }
-
- gate_transform_update( gate );
- gate_transform_update( gate2 );
-
- goto matched;
- }
- } matched:;
- }
-}
-
-#endif /* WORLD_GATE_C */
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-
-#include "vg/vg_camera.h"
-#include "world.h"
-#include "shaders/model_gate.h"
-#include "entity.h"
-
-struct world_gates
-{
- glmesh mesh;
- mdl_submesh sm_surface, sm_marker[4];
- vg_camera cam;
-
- v3f userportal_co;
-}
-extern world_gates;
-
-void world_gates_init(void);
-void gate_transform_update( ent_gate *gate );
-int render_gate( world_instance *world, world_instance *world_inside,
- ent_gate *gate, vg_camera *cam );
-
-int gate_intersect( ent_gate *gate, v3f pos, v3f last );
-u32 world_intersect_gates( world_instance *world, v3f pos, v3f last );
-
-entity_call_result ent_gate_call( world_instance *world, ent_call *call );
-void ent_gate_get_mdl_mtx( ent_gate *gate, m4x3f mmdl );
-
-void world_link_gates_async( void *payload, u32 size );
-void world_unlink_nonlocal( world_instance *world );
-void render_gate_unlinked( world_instance *world,
- ent_gate *gate, vg_camera *cam );
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- *
- * World generation/population. Different to regular loading, since it needs to
- * create geometry, apply procedural stuff and save that image to files etc.
- */
-#include "world.h"
-#include "world_gen.h"
-#include "world_load.h"
-#include "world_volumes.h"
-#include "world_gate.h"
-#include <string.h>
-
-/*
- * Add all triangles from the model, which match the material ID
- * applies affine transform to the model
- */
-static void world_add_all_if_material( m4x3f transform, scene_context *scene,
- mdl_context *mdl, u32 id )
-{
- for( u32 i=0; i<mdl_arrcount(&mdl->meshs); i++ ){
- mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i );
-
- for( u32 j=0; j<mesh->submesh_count; j++ ){
- mdl_submesh *sm = mdl_arritm( &mdl->submeshs, mesh->submesh_start+j );
- if( sm->material_id == id ){
- m4x3f transform2;
- mdl_transform_m4x3( &mesh->transform, transform2 );
- m4x3_mul( transform, transform2, transform2 );
-
- scene_add_mdl_submesh( scene, mdl, sm, transform2 );
- }
- }
- }
-}
-
-/*
- * Adds a small blob shape to the world at a raycast location. This is for the
- * grass sprites
- *
- * /''''\
- * / \
- * | |
- * |________|
- */
-static void world_gen_add_blob( vg_rand *rand, world_instance *world,
- scene_context *scene, ray_hit *hit )
-{
- m4x3f transform;
- v4f qsurface, qrandom;
- v3f axis;
-
- v3_cross( (v3f){0.0f,1.0f,0.0f}, hit->normal, axis );
-
- float angle = v3_dot(hit->normal,(v3f){0.0f,1.0f,0.0f});
- q_axis_angle( qsurface, axis, angle );
- q_axis_angle( qrandom, (v3f){0.0f,1.0f,0.0f}, vg_randf64(rand)*VG_TAUf );
- q_mul( qsurface, qrandom, qsurface );
- q_m3x3( qsurface, transform );
- v3_copy( hit->pos, transform[3] );
-
- scene_vert verts[] =
- {
- { .co = { -1.00f, 0.0f, 0.0f } },
- { .co = { 1.00f, 0.0f, 0.0f } },
- { .co = { -1.00f, 1.2f, 0.0f } },
- { .co = { 1.00f, 1.2f, 0.0f } },
- { .co = { -0.25f, 2.0f, 0.0f } },
- { .co = { 0.25f, 2.0f, 0.0f } }
- };
-
- const u32 indices[] = { 0,1,3, 0,3,2, 2,3,5, 2,5,4 };
-
- if( scene->vertex_count + VG_ARRAY_LEN(verts) > scene->max_vertices )
- vg_fatal_error( "Scene vertex buffer overflow" );
-
- if( scene->indice_count + VG_ARRAY_LEN(indices) > scene->max_indices )
- vg_fatal_error( "Scene index buffer overflow" );
-
- scene_vert *dst_verts = &scene->arrvertices[ scene->vertex_count ];
- u32 *dst_indices = &scene->arrindices [ scene->indice_count ];
-
- scene_vert *ref = &world->scene_geo.arrvertices[ hit->tri[0] ];
-
- for( u32 i=0; i<VG_ARRAY_LEN(verts); i++ ){
- scene_vert *pvert = &dst_verts[ i ],
- *src = &verts[ i ];
-
- m4x3_mulv( transform, src->co, pvert->co );
- scene_vert_pack_norm( pvert, transform[1], 0.0f );
-
- v2_copy( ref->uv, pvert->uv );
- }
-
- for( u32 i=0; i<VG_ARRAY_LEN(indices); i++ )
- dst_indices[i] = indices[i] + scene->vertex_count;
-
- scene->vertex_count += VG_ARRAY_LEN(verts);
- scene->indice_count += VG_ARRAY_LEN(indices);
-}
-
-/*
- * Sprinkle foliage models over the map on terrain material
- */
-static void world_apply_procedural_foliage( world_instance *world,
- scene_context *scene,
- struct world_surface *mat )
-{
- if( (vg.quality_profile == k_quality_profile_low) ||
- (vg.quality_profile == k_quality_profile_min) )
- return;
-
- vg_info( "Applying foliage (%u)\n", mat->info.pstr_name );
-
- v3f volume;
- v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], volume );
- volume[1] = 1.0f;
-
- int count = 0;
-
- float area = volume[0]*volume[2];
- u32 particles = 0.08f * area;
-
- vg_info( "Map area: %f. Max particles: %u\n", area, particles );
-
- u64 t0 = SDL_GetPerformanceCounter();
-#if 0
- for( u32 i=0; i<particles; i++ ){
- v3f pos;
- v3_mul( volume, (v3f){ vg_randf64(), 1000.0f, vg_randf64() }, pos );
- pos[1] = 1000.0f;
- v3_add( pos, world->scene_geo.bbx[0], pos );
-
- ray_hit hit;
- hit.dist = INFINITY;
-
- if( ray_world( world, pos, (v3f){0.0f,-1.0f,0.0f}, &hit,
- k_material_flag_ghosts )){
- struct world_surface *m1 = ray_hit_surface( world, &hit );
- if((hit.normal[1] > 0.8f) && (m1 == mat) && (hit.pos[1] > 0.0f+10.0f)){
- world_gen_add_blob( world, scene, &hit );
- count ++;
- }
- }
- }
-#else
-
- vg_rand rand;
- vg_rand_seed( &rand, 3030 );
-
- const f32 tile_scale = 16.0f;
- v2i tiles = { volume[0]/tile_scale, volume[2]/tile_scale };
-
- u32 per_tile = particles/(tiles[0]*tiles[1]);
-
- for( i32 x=0; x<tiles[0]; x ++ ){
- for( i32 z=0; z<tiles[1]; z ++ ){
- for( u32 i=0; i<per_tile; i ++ ){
- v3f co = { (f32)x+vg_randf64(&rand), 0, (f32)z+vg_randf64(&rand) };
- v3_muls( co, tile_scale, co );
- co[1] = 1000.0f;
- v3_add( co, world->scene_geo.bbx[0], co );
-
- ray_hit hit;
- hit.dist = INFINITY;
-
- if( ray_world( world, co, (v3f){0.0f,-1.0f,0.0f}, &hit,
- k_material_flag_ghosts )){
- struct world_surface *m1 = ray_hit_surface( world, &hit );
- if((hit.normal[1] > 0.8f) && (m1 == mat) &&
- (hit.pos[1] > 0.0f+10.0f)){
- world_gen_add_blob( &rand, world, scene, &hit );
- count ++;
- }
- }
-
- }
- }
- }
-
-#endif
-
-
-
- u64 t1 = SDL_GetPerformanceCounter(),
- utime_blobs = t1-t0,
- ufreq = SDL_GetPerformanceFrequency();
- f64 ftime_blobs = ((f64)utime_blobs / (f64)ufreq)*1000.0;
-
- vg_info( "%d foliage models added. %f%% (%fms)\n", count,
- 100.0*((f64)count/(f64)particles), ftime_blobs);
-}
-
-static
-void world_unpack_submesh_dynamic( world_instance *world,
- scene_context *scene, mdl_submesh *sm ){
- if( sm->flags & k_submesh_flag_consumed ) return;
-
- m4x3f identity;
- m4x3_identity( identity );
- scene_add_mdl_submesh( scene, &world->meta, sm, identity );
-
- scene_copy_slice( scene, sm );
- sm->flags |= k_submesh_flag_consumed;
-}
-
-/*
- * Create the main meshes for the world
- */
-void world_gen_generate_meshes( world_instance *world )
-{
- /*
- * Compile meshes into the world scenes
- */
- scene_init( &world->scene_geo, 320000, 1200000 );
- u32 buf_size = scene_mem_required( &world->scene_geo );
- u8 *buffer = vg_linear_alloc( world->heap, buf_size );
- scene_supply_buffer( &world->scene_geo, buffer );
-
- m4x3f midentity;
- m4x3_identity( midentity );
-
- /*
- * Generate scene: collidable geometry
- * ----------------------------------------------------------------
- */
-
- vg_info( "Generating collidable geometry\n" );
-
- for( u32 i=0; i<world->surface_count; i++ ){
- struct world_surface *surf = &world->surfaces[ i ];
-
- if( surf->info.flags & k_material_flag_collision )
- world_add_all_if_material( midentity, &world->scene_geo,
- &world->meta, i );
-
- scene_copy_slice( &world->scene_geo, &surf->sm_geo );
- scene_set_vertex_flags( &world->scene_geo,
- surf->sm_geo.vertex_start,
- surf->sm_geo.vertex_count,
- (u16)(surf->info.flags & 0xffff) );
- }
-
- /* compress that bad boy */
- u32 new_vert_max = world->scene_geo.vertex_count,
- new_vert_size = vg_align8(new_vert_max*sizeof(scene_vert)),
- new_indice_len = world->scene_geo.indice_count*sizeof(u32);
-
- u32 *src_indices = world->scene_geo.arrindices,
- *dst_indices = (u32 *)(buffer + new_vert_size);
-
- memmove( dst_indices, src_indices, new_indice_len );
-
- world->scene_geo.max_indices = world->scene_geo.indice_count;
- world->scene_geo.max_vertices = world->scene_geo.vertex_count;
- buf_size = scene_mem_required( &world->scene_geo );
-
- buffer = vg_linear_resize( world->heap, buffer, buf_size );
-
- world->scene_geo.arrvertices = (scene_vert *)(buffer);
- world->scene_geo.arrindices = (u32 *)(buffer + new_vert_size);
-
- scene_upload_async( &world->scene_geo, &world->mesh_geo );
-
- /* need send off the memory to the gpu before we can create the bvh. */
- vg_async_stall();
- vg_info( "creating bvh\n" );
- world->geo_bh = scene_bh_create( world->heap, &world->scene_geo );
-
- /*
- * Generate scene: non-collidable geometry
- * ----------------------------------------------------------------
- */
- vg_info( "Generating non-collidable geometry\n" );
-
- vg_async_item *call = scene_alloc_async( &world->scene_no_collide,
- &world->mesh_no_collide,
- 250000, 500000 );
-
- for( u32 i=0; i<world->surface_count; i++ ){
- struct world_surface *surf = &world->surfaces[ i ];
-
- if( !(surf->info.flags & k_material_flag_collision) ){
- world_add_all_if_material( midentity,
- &world->scene_no_collide, &world->meta, i );
- }
-
- if( surf->info.flags & k_material_flag_grow_grass ){
- world_apply_procedural_foliage( world, &world->scene_no_collide,
- surf );
- }
-
- scene_copy_slice( &world->scene_no_collide, &surf->sm_no_collide );
- }
-
- /* unpack traffic models.. TODO: should we just put all these submeshes in a
- * dynamic models list? and then the actual entitities point to the
- * models. we only have 2 types at the moment which need dynamic models but
- * would make sense to do this when/if we have more.
- */
- for( u32 i=0; i<mdl_arrcount( &world->ent_traffic ); i++ ){
- ent_traffic *vehc = mdl_arritm( &world->ent_traffic, i );
-
- for( u32 j=0; j<vehc->submesh_count; j++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- vehc->submesh_start+j );
- world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
- world->surfaces[ sm->material_id ].flags |= WORLD_SURFACE_HAS_TRAFFIC;
- }
- }
-
- /* unpack challenge models */
- for( u32 i=0; i<mdl_arrcount( &world->ent_objective ); i++ ){
- ent_objective *objective = mdl_arritm( &world->ent_objective, i );
-
- for( u32 j=0; j<objective->submesh_count; j ++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- objective->submesh_start+j );
- world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
- }
- }
-
- /* unpack region models */
- for( u32 i=0; i<mdl_arrcount( &world->ent_region ); i++ ){
- ent_region *region = mdl_arritm( &world->ent_region, i );
-
- for( u32 j=0; j<region->submesh_count; j ++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- region->submesh_start+j );
- world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
- }
- }
-
- /* unpack gate models */
- for( u32 i=0; i<mdl_arrcount( &world->ent_gate ); i++ ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, i );
-
- if( !(gate->flags & k_ent_gate_custom_mesh) ) continue;
-
- for( u32 j=0; j<gate->submesh_count; j ++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- gate->submesh_start+j );
- world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
- }
- }
-
- /* unpack prop models */
- for( u32 i=0; i<mdl_arrcount( &world->ent_prop ); i++ ){
- ent_prop *prop = mdl_arritm( &world->ent_prop, i );
-
- for( u32 j=0; j<prop->submesh_count; j ++ ){
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- prop->submesh_start+j );
- world->surfaces[ sm->material_id ].flags |= WORLD_SURFACE_HAS_PROPS;
- world_unpack_submesh_dynamic( world, &world->scene_no_collide, sm );
- }
- }
-
- vg_async_dispatch( call, async_scene_upload );
-}
-
-/* signed distance function for cone */
-static f32 fsd_cone_infinite( v3f p, v2f c ){
- v2f q = { v2_length( (v2f){ p[0], p[2] } ), -p[1] };
- float s = vg_maxf( 0.0f, v2_dot( q, c ) );
-
- v2f v0;
- v2_muls( c, s, v0 );
- v2_sub( q, v0, v0 );
-
- float d = v2_length( v0 );
- return d * ((q[0]*c[1]-q[1]*c[0]<0.0f)?-1.0f:1.0f);
-}
-
-struct light_indices_upload_info{
- world_instance *world;
- v3i count;
-
- void *data;
-};
-
-/*
- * Async reciever to buffer light index data
- */
-static void async_upload_light_indices( void *payload, u32 size ){
- struct light_indices_upload_info *info = payload;
-
- glGenTextures( 1, &info->world->tex_light_cubes );
- glBindTexture( GL_TEXTURE_3D, info->world->tex_light_cubes );
- glTexImage3D( GL_TEXTURE_3D, 0, GL_RG32UI,
- info->count[0], info->count[1], info->count[2],
- 0, GL_RG_INTEGER, GL_UNSIGNED_INT, info->data );
- glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
- glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
-}
-
-/*
- * Computes light indices for world
- */
-void world_gen_compute_light_indices( world_instance *world )
-{
- /* light cubes */
- v3f cubes_min, cubes_max;
- v3_muls( world->scene_geo.bbx[0], 1.0f/k_world_light_cube_size, cubes_min );
- v3_muls( world->scene_geo.bbx[1], 1.0f/k_world_light_cube_size, cubes_max );
-
- v3_sub( cubes_min, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_min );
- v3_add( cubes_max, (v3f){ 0.5f, 0.5f, 0.5f }, cubes_max );
-
- v3_floor( cubes_min, cubes_min );
- v3_floor( cubes_max, cubes_max );
-
- v3i icubes_min, icubes_max;
-
- for( int i=0; i<3; i++ ){
- icubes_min[i] = cubes_min[i];
- icubes_max[i] = cubes_max[i];
- }
-
- v3f cube_size;
-
- v3i icubes_count;
- v3i_sub( icubes_max, icubes_min, icubes_count );
-
- for( int i=0; i<3; i++ ){
- int clamped_count = VG_MIN( 128, icubes_count[i]+1 );
- float clamped_max = icubes_min[i] + clamped_count,
- max = icubes_min[i] + icubes_count[i]+1;
-
- icubes_count[i] = clamped_count;
- cube_size[i] = (max / clamped_max) * k_world_light_cube_size;
- cubes_max[i] = clamped_max;
- }
-
- v3_mul( cubes_min, cube_size, cubes_min );
- v3_mul( cubes_max, cube_size, cubes_max );
-
- for( int i=0; i<3; i++ ){
- float range = cubes_max[i]-cubes_min[i];
- world->ub_lighting.g_cube_inv_range[i] = 1.0f / range;
- world->ub_lighting.g_cube_inv_range[i] *= (float)icubes_count[i];
-
- vg_info( "cubes[%d]: %d\n", i, icubes_count[i] );
- }
-
- int total_cubes = icubes_count[0]*icubes_count[1]*icubes_count[2];
-
- u32 data_size = vg_align8(total_cubes*sizeof(u32)*2),
- hdr_size = vg_align8(sizeof(struct light_indices_upload_info));
-
- vg_async_item *call = vg_async_alloc( data_size + hdr_size );
- struct light_indices_upload_info *info = call->payload;
- info->data = ((u8*)call->payload) + hdr_size;
- info->world = world;
- u32 *cubes_index = info->data;
-
- for( int i=0; i<3; i++ )
- info->count[i] = icubes_count[i];
-
- vg_info( "Computing light cubes (%d) [%f %f %f] -> [%f %f %f]\n",
- total_cubes, cubes_min[0], -cubes_min[2], cubes_min[1],
- cubes_max[0], -cubes_max[2], cubes_max[1] );
- v3_copy( cubes_min, world->ub_lighting.g_cube_min );
-
- float bound_radius = v3_length( cube_size );
-
- for( int iz = 0; iz<icubes_count[2]; iz ++ ){
- for( int iy = 0; iy<icubes_count[1]; iy++ ){
- for( int ix = 0; ix<icubes_count[0]; ix++ ){
- boxf bbx;
- v3_div( (v3f){ ix, iy, iz }, world->ub_lighting.g_cube_inv_range,
- bbx[0] );
- v3_div( (v3f){ ix+1, iy+1, iz+1 },
- world->ub_lighting.g_cube_inv_range,
- bbx[1] );
-
- v3_add( bbx[0], world->ub_lighting.g_cube_min, bbx[0] );
- v3_add( bbx[1], world->ub_lighting.g_cube_min, bbx[1] );
-
- v3f center;
- v3_add( bbx[0], bbx[1], center );
- v3_muls( center, 0.5f, center );
-
- u32 indices[6] = { 0, 0, 0, 0, 0, 0 };
- u32 count = 0;
-
- float influences[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
- const int N = VG_ARRAY_LEN( influences );
-
- for( u32 j=0; j<mdl_arrcount(&world->ent_light); j ++ ){
- ent_light *light = mdl_arritm( &world->ent_light, j );
- v3f closest;
- closest_point_aabb( light->transform.co, bbx, closest );
-
- f32 dist2 = v3_dist2( closest, light->transform.co );
-
- if( dist2 > light->range*light->range )
- continue;
-
- f32 dist = sqrtf(dist2),
- influence = 1.0f/(dist+1.0f);
-
- if( light->type == k_light_type_spot){
- v3f local;
- m4x3_mulv( light->inverse_world, center, local );
-
- float r = fsd_cone_infinite( local, light->angle_sin_cos );
-
- if( r > bound_radius )
- continue;
- }
-
- int best_pos = N;
- for( int k=best_pos-1; k>=0; k -- )
- if( influence > influences[k] )
- best_pos = k;
-
- if( best_pos < N ){
- for( int k=N-1; k>best_pos; k -- ){
- influences[k] = influences[k-1];
- indices[k] = indices[k-1];
- }
-
- influences[best_pos] = influence;
- indices[best_pos] = j;
- }
- }
-
- for( int j=0; j<N; j++ )
- if( influences[j] > 0.0f )
- count ++;
-
- int base_index = iz * (icubes_count[0]*icubes_count[1]) +
- iy * (icubes_count[0]) +
- ix;
-
- int lower_count = VG_MIN( 3, count );
- u32 packed_index_lower = lower_count;
- packed_index_lower |= indices[0]<<2;
- packed_index_lower |= indices[1]<<12;
- packed_index_lower |= indices[2]<<22;
-
- int upper_count = VG_MAX( 0, count - lower_count );
- u32 packed_index_upper = upper_count;
- packed_index_upper |= indices[3]<<2;
- packed_index_upper |= indices[4]<<12;
- packed_index_upper |= indices[5]<<22;
-
- cubes_index[ base_index*2 + 0 ] = packed_index_lower;
- cubes_index[ base_index*2 + 1 ] = packed_index_upper;
- }
- }
- }
-
- vg_async_dispatch( call, async_upload_light_indices );
-}
-
-/*
- * Rendering pass needed to complete the world
- */
-void async_world_postprocess( void *payload, u32 _size )
-{
- /* create scene lighting buffer */
- world_instance *world = payload;
-
- u32 size = VG_MAX(mdl_arrcount(&world->ent_light),1) * sizeof(float)*12;
- vg_info( "Upload %ubytes (lighting)\n", size );
-
- glGenBuffers( 1, &world->tbo_light_entities );
- glBindBuffer( GL_TEXTURE_BUFFER, world->tbo_light_entities );
- glBufferData( GL_TEXTURE_BUFFER, size, NULL, GL_DYNAMIC_DRAW );
-
- /* buffer layout
- *
- * colour position direction (spots)
- * | . . . . | . . . . | . . . . |
- * | Re Ge Be Night | Xco Yco Zco Range | Dx Dy Dz Da |
- *
- */
-
- v4f *light_dst = glMapBuffer( GL_TEXTURE_BUFFER, GL_WRITE_ONLY );
- for( u32 i=0; i<mdl_arrcount(&world->ent_light); i++ ){
- ent_light *light = mdl_arritm( &world->ent_light, i );
-
- /* colour + night */
- v3_muls( light->colour, light->colour[3] * 2.0f, light_dst[i*3+0] );
- light_dst[i*3+0][3] = 2.0f;
-
- if( !light->daytime ){
- u32 hash = (i * 29986577u) & 0xffu;
- float switch_on = hash;
- switch_on *= (1.0f/255.0f);
-
- light_dst[i*3+0][3] = 0.44f + switch_on * 0.015f;
- }
-
- /* position + 1/range^2 */
- v3_copy( light->transform.co, light_dst[i*3+1] );
- light_dst[i*3+1][3] = 1.0f/(light->range*light->range);
-
- /* direction + angle */
- q_mulv( light->transform.q, (v3f){0.0f,-1.0f,0.0f}, light_dst[i*3+2]);
- light_dst[i*3+2][3] = cosf( light->angle );
- }
-
- glUnmapBuffer( GL_TEXTURE_BUFFER );
-
- glGenTextures( 1, &world->tex_light_entities );
- glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
- glTexBuffer( GL_TEXTURE_BUFFER, GL_RGBA32F, world->tbo_light_entities );
-
- /* Upload lighting uniform buffer */
- if( world->water.enabled )
- v4_copy( world->water.plane, world->ub_lighting.g_water_plane );
-
- v4f info_vec;
- v3f *bounds = world->scene_geo.bbx;
-
- info_vec[0] = bounds[0][0];
- info_vec[1] = bounds[0][2];
- info_vec[2] = 1.0f/ (bounds[1][0]-bounds[0][0]);
- info_vec[3] = 1.0f/ (bounds[1][2]-bounds[0][2]);
- v4_copy( info_vec, world->ub_lighting.g_depth_bounds );
-
- /*
- * Rendering the depth map
- */
- vg_camera ortho;
-
- v3f extent;
- v3_sub( world->scene_geo.bbx[1], world->scene_geo.bbx[0], extent );
-
- float fl = world->scene_geo.bbx[0][0],
- fr = world->scene_geo.bbx[1][0],
- fb = world->scene_geo.bbx[0][2],
- ft = world->scene_geo.bbx[1][2],
- rl = 1.0f / (fr-fl),
- tb = 1.0f / (ft-fb);
-
- m4x4_zero( ortho.mtx.p );
- ortho.mtx.p[0][0] = 2.0f * rl;
- ortho.mtx.p[2][1] = 2.0f * tb;
- ortho.mtx.p[3][0] = (fr + fl) * -rl;
- ortho.mtx.p[3][1] = (ft + fb) * -tb;
- ortho.mtx.p[3][3] = 1.0f;
- m4x3_identity( ortho.transform );
- vg_camera_update_view( &ortho );
- vg_camera_finalize( &ortho );
-
- glDisable(GL_DEPTH_TEST);
- glDisable(GL_BLEND);
- glDisable(GL_CULL_FACE);
- vg_framebuffer_bind( world->heightmap, 1.0f );
- shader_blitcolour_use();
- shader_blitcolour_uColour( (v4f){-9999.0f,-9999.0f,-9999.0f,-9999.0f} );
- render_fsquad();
-
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE);
- glBlendEquation(GL_MAX);
-
- render_world_position( world, &ortho );
- glDisable(GL_BLEND);
- glEnable(GL_DEPTH_TEST);
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
-
- /* upload full buffer */
- glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
- glBufferSubData( GL_UNIFORM_BUFFER, 0,
- sizeof(struct ub_world_lighting), &world->ub_lighting );
-
- /*
- * Allocate cubemaps
- */
- for( u32 i=0; i<mdl_arrcount(&world->ent_cubemap); i++ ){
- ent_cubemap *cm = mdl_arritm(&world->ent_cubemap,i);
-
- glGenTextures( 1, &cm->texture_id );
- glBindTexture( GL_TEXTURE_CUBE_MAP, cm->texture_id );
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
-
- for( u32 j=0; j<6; j ++ ) {
- glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, GL_RGB,
- WORLD_CUBEMAP_RES, WORLD_CUBEMAP_RES,
- 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
- }
-
- glGenFramebuffers( 1, &cm->framebuffer_id );
- glBindFramebuffer( GL_FRAMEBUFFER, cm->framebuffer_id );
- glGenRenderbuffers(1, &cm->renderbuffer_id );
- glBindRenderbuffer( GL_RENDERBUFFER, cm->renderbuffer_id );
- glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
- WORLD_CUBEMAP_RES, WORLD_CUBEMAP_RES );
-
- glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_CUBE_MAP_POSITIVE_X, cm->texture_id, 0 );
- glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
- GL_RENDERBUFFER, cm->renderbuffer_id );
-
- glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_CUBE_MAP_POSITIVE_X, cm->texture_id, 0 );
-
- if( glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE ){
- vg_error( "Cubemap framebuffer incomplete.\n" );
- }
- }
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
-}
-
-/* Loads textures from the pack file */
-void world_gen_load_surfaces( world_instance *world )
-{
- vg_info( "Loading textures\n" );
- world->texture_count = 0;
-
- world->texture_count = world->meta.textures.count+1;
- world->textures = vg_linear_alloc( world->heap,
- vg_align8(sizeof(GLuint)*world->texture_count) );
- world->textures[0] = vg.tex_missing;
-
- for( u32 i=0; i<mdl_arrcount(&world->meta.textures); i++ )
- {
- mdl_texture *tex = mdl_arritm( &world->meta.textures, i );
-
- if( !tex->file.pack_size )
- {
- vg_fatal_error( "World models must have packed textures!" );
- }
-
- vg_linear_clear( vg_mem.scratch );
- void *src_data = vg_linear_alloc( vg_mem.scratch,
- tex->file.pack_size );
- mdl_fread_pack_file( &world->meta, &tex->file, src_data );
-
- vg_tex2d_load_qoi_async( src_data, tex->file.pack_size,
- VG_TEX2D_NEAREST|VG_TEX2D_REPEAT,
- &world->textures[i+1] );
- }
-
- vg_info( "Loading materials\n" );
-
- world->surface_count = world->meta.materials.count+1;
- world->surfaces = vg_linear_alloc( world->heap,
- vg_align8(sizeof(struct world_surface)*world->surface_count) );
-
- /* error material */
- struct world_surface *errmat = &world->surfaces[0];
- memset( errmat, 0, sizeof(struct world_surface) );
-
- for( u32 i=0; i<mdl_arrcount(&world->meta.materials); i++ )
- {
- struct world_surface *surf = &world->surfaces[i+1];
- surf->info = *(mdl_material *)mdl_arritm( &world->meta.materials, i );
- surf->flags = 0;
-
- if( surf->info.shader == k_shader_water )
- {
- struct shader_props_water *props = surf->info.props.compiled;
- world->ub_lighting.g_water_fog = props->fog_scale;
- }
-
- if( surf->info.shader == k_shader_standard_cutout ||
- surf->info.shader == k_shader_foliage )
- {
- struct shader_props_standard *props = surf->info.props.compiled;
- surf->alpha_tex = props->tex_diffuse;
- }
- else
- surf->alpha_tex = 0;
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- *
- * World generation/population. Different to regular loading, since it needs to
- * create geometry, apply procedural stuff and save that image to files etc.
- */
-
-#pragma once
-#include "world.h"
-
-void world_init_blank( world_instance *world );
-void world_gen_load_surfaces( world_instance *world );
-void world_gen_generate_meshes( world_instance *world );
-void world_gen_compute_light_indices( world_instance *world );
-void async_world_postprocess( void *payload, u32 _size );
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#ifndef WORLD_INFO_H
-#define WORLD_INFO_H
-
-/* Purely an information header, shares common strings across client and
- * server programs. */
-
-struct track_info
-{
- const char *name,
- *achievement_id;
-}
-static track_infos[] =
-{
- {
- .name = "Megapark Green",
- .achievement_id = "ROUTE_MPG",
- },
- {
- .name = "Megapark Blue",
- .achievement_id = "ROUTE_MPB",
- },
- {
- .name = "Megapark Yellow",
- .achievement_id = "ROUTE_MPY",
- },
- {
- .name = "Megapark Red",
- .achievement_id = "ROUTE_MPR",
- },
- {
- .name = "Coastal Run",
- .achievement_id = "ROUTE_TC",
- },
- {
- .name = "Docks Jumps",
- .achievement_id = "ROUTE_TO",
- }
-};
-
-#endif
+++ /dev/null
-#include "world_load.h"
-#include "world_routes.h"
-#include "world_gate.h"
-#include "ent_skateshop.h"
-#include "addon.h"
-#include "save.h"
-#include "vg/vg_msg.h"
-#include "network.h"
-#include "player_remote.h"
-#include "vg/vg_loader.h"
-#include "vg/vg_io.h"
-#include <string.h>
-
-/*
- * load the .mdl file located in path as a world instance
- */
-static void world_instance_load_mdl( u32 instance_id, const char *path ){
- world_instance *world = &world_static.instances[ instance_id ];
- world_init_blank( world );
- world->status = k_world_status_loading;
-
- vg_info( "Loading instance[%u]: %s\n", instance_id, path );
-
- void *allocator = NULL;
- if( instance_id == 0 ) allocator = world_static.heap;
- else allocator = world_static.instances[instance_id-1].heap;
-
- u32 heap_availible = vg_linear_remaining( allocator );
- u32 min_overhead = sizeof(vg_linear_allocator);
-
- if( heap_availible < (min_overhead+1024) ){
- vg_fatal_error( "out of memory" );
- }
-
- u32 size = heap_availible - min_overhead;
- void *heap = vg_create_linear_allocator( allocator, size, VG_MEMORY_SYSTEM );
-
- world->heap = heap;
- mdl_context *meta = &world->meta;
-
- mdl_open( meta, path, world->heap );
- mdl_load_metadata_block( meta, world->heap );
- mdl_load_animation_block( meta, world->heap );
- mdl_load_mesh_block( meta, world->heap );
-
- vg_info( "%u\n", sizeof(ent_cubemap) );
-
- MDL_LOAD_ARRAY( meta, &world->ent_gate, ent_gate, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_camera, ent_camera, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_spawn, ent_spawn, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_light, ent_light, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_route_node,ent_route_node, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_path_index,ent_path_index, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_checkpoint,ent_checkpoint, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_route, ent_route, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_water, ent_water, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_audio_clip,ent_audio_clip, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_audio, ent_audio, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_volume, ent_volume, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_traffic, ent_traffic, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_marker, ent_marker, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_skateshop, ent_skateshop, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_swspreview,ent_swspreview, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_ccmd, ent_ccmd, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_objective, ent_objective, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_challenge, ent_challenge, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_relay, ent_relay, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_cubemap, ent_cubemap, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_miniworld, ent_miniworld, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_prop, ent_prop, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_region, ent_region, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_glider, ent_glider, heap );
- MDL_LOAD_ARRAY( meta, &world->ent_npc, ent_npc, heap );
-
- mdl_array_ptr infos;
- MDL_LOAD_ARRAY( meta, &infos, ent_worldinfo, vg_mem.scratch );
-
- world->skybox = k_skybox_default;
- if( mdl_arrcount(&infos) )
- {
- world->info = *((ent_worldinfo *)mdl_arritm(&infos,0));
-
- if( world->meta.info.version >= 104 )
- {
- if( MDL_CONST_PSTREQ( &world->meta, world->info.pstr_skybox,"space"))
- {
- world->skybox = k_skybox_space;
- }
- }
- }
- else
- {
- world->info.pstr_author = 0;
- world->info.pstr_desc = 0;
- world->info.pstr_name = 0;
- world->info.timezone = 0.0f;
- world->info.flags = 0;
- }
-
- time_t seconds = time(NULL) % ((u32)vg_maxf(1.0f,k_day_length)*60);
- world->time = ((f64)(seconds)/(k_day_length*60.0));
- world->time += (world->info.timezone/24.0);
-
- /* process resources from pack */
- u64 t4 = SDL_GetPerformanceCounter();
- world_gen_load_surfaces( world );
- u64 t5 = SDL_GetPerformanceCounter();
- world_gen_routes_ent_init( world );
- world_gen_entities_init( world );
- u64 t6 = SDL_GetPerformanceCounter();
-
- /* main bulk */
- u64 t0 = SDL_GetPerformanceCounter();
- world_gen_generate_meshes( world );
- u64 t1 = SDL_GetPerformanceCounter();
- world_gen_routes_generate( instance_id );
- u64 t2 = SDL_GetPerformanceCounter();
- world_gen_compute_light_indices( world );
- u64 t3 = SDL_GetPerformanceCounter();
- mdl_close( meta );
-
- u64 utime_mesh = t1-t0,
- utime_route = t2-t1,
- utime_indices = t3-t2,
- utime_tex = t5-t4,
- utime_ent = t6-t5,
- ufreq = SDL_GetPerformanceFrequency();
-
- f64 ftime_mesh = ((f64)utime_mesh / (f64)ufreq)*1000.0,
- ftime_route = ((f64)utime_route / (f64)ufreq)*1000.0,
- ftime_ind = ((f64)utime_route / (f64)ufreq)*1000.0,
- ftime_tex = ((f64)utime_tex / (f64)ufreq)*1000.0,
- ftime_ent = ((f64)utime_ent / (f64)ufreq)*1000.0;
-
- vg_info( "wtime:mesh %.2fms route %.2fms ind %.2fms tex %.2fms ent %.2fms\n",
- ftime_mesh, ftime_route, ftime_ind, ftime_tex, ftime_ent );
-
- /* init player position.
- * - this is overriden by the save state when(if) it loads */
- world_default_spawn_pos( world, world->player_co );
-
- /* allocate leaderboard buffers */
- u32 bs = mdl_arrcount(&world->ent_route)*sizeof(struct leaderboard_cache);
- world->leaderboard_cache = vg_linear_alloc( heap, bs );
-
- for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i ++ )
- {
- struct leaderboard_cache *board = &world->leaderboard_cache[i];
- board->data = vg_linear_alloc( heap, NETWORK_REQUEST_MAX );
- board->status = k_request_status_client_error;
- board->cache_time = 0.0;
- board->data_len = 0;
- }
-
- world->routes_ui = vg_linear_alloc( heap,
- sizeof(struct route_ui)*mdl_arrcount(&world->ent_route) );
-
- vg_async_call( async_world_postprocess, world, 0 );
- vg_async_stall();
-}
-
-struct world_load_complete_data{
- savedata_file save;
- enum world_purpose purpose;
-};
-
-static void skaterift_world_load_done( void *payload, u32 size )
-{
- struct world_load_complete_data *data = payload;
- world_instance *world = &world_static.instances[ data->purpose ];
-
- vg_msg sav;
- vg_msg_init( &sav, data->save.buf, data->save.len );
-
- if( data->purpose != k_world_purpose_hub )
- {
- vg_msg player_frame = sav;
- if( vg_msg_seekframe( &player_frame, "player" ) )
- {
- vg_msg_getkvvecf( &player_frame, "position", k_vg_msg_v3f,
- world->player_co, NULL );
- }
- }
-
- world_entity_start( world, &sav );
- world->status = k_world_status_loaded;
- world_static.load_state = k_world_loader_none;
-
- if( world_static.clear_async_op_when_done )
- {
- g_client.loaded = 1;
- world_static.clear_async_op_when_done = 0;
- }
-}
-
-/*
- * Does a complete world switch using the remaining free slots
- */
-void skaterift_world_load_thread( void *_args )
-{
- struct world_load_args args = *((struct world_load_args *)_args);
-
- addon_reg *reg = args.reg;
- world_static.instance_addons[ args.purpose ] = reg;
-
- char uid[ADDON_UID_MAX];
- addon_alias_uid( ®->alias, uid );
- vg_info( "LOAD WORLD %s @%d\n", uid, args.purpose );
-
- char path_buf[4096];
- vg_str path;
- vg_strnull( &path, path_buf, 4096 );
-
- addon_get_content_folder( reg, &path, 1 );
-
- vg_str folder = path;
- if( !vg_strgood( &folder ) ) {
- vg_error( "Load target too long\n" );
- return;
- }
-
- char worlds[k_world_max-1][4096];
- u32 i=0;
-
- vg_dir dir;
- if( !vg_dir_open(&dir, folder.buffer) ){
- vg_error( "opendir('%s') failed\n", folder.buffer );
- return;
- }
-
- while( vg_dir_next_entry(&dir) ){
- if( vg_dir_entry_type(&dir) == k_vg_entry_type_file ){
- const char *d_name = vg_dir_entry_name(&dir);
- if( d_name[0] == '.' ) continue;
-
- vg_str file = folder;
- vg_strcat( &file, "/" );
- vg_strcat( &file, d_name );
- if( !vg_strgood( &file ) ) continue;
-
- char *ext = vg_strch( &file, '.' );
- if( !ext ) continue;
- if( strcmp(ext,".mdl") ) continue;
-
- if( i == k_world_max-1 ){
- vg_warn( "There are too many .mdl files in the map folder!(3)\n" );
- break;
- }
-
- strcpy( worlds[i++], file.buffer );
- }
- }
- vg_dir_close(&dir);
-
- if( i == 0 ){
- vg_warn( "There are no .mdl files in the map folder.\n" );
- }
-
- u32 first_index = 0;
- for( u32 j=0; j<i; j++ ){
- vg_str name = { .buffer = worlds[j], .i=strlen(worlds[j]),
- sizeof(worlds[j]) };
- char *fname = vg_strch( &name, '/' );
- if( fname ){
- if( !strcmp( fname+1, "main.mdl" ) ){
- first_index = j;
- }
- }
- }
-
- world_instance_load_mdl( args.purpose, worlds[first_index] );
-
- vg_async_item *final_call =
- vg_async_alloc( sizeof(struct world_load_complete_data) );
-
- struct world_load_complete_data *data = final_call->payload;
- data->purpose = args.purpose;
-
- skaterift_world_get_save_path( args.purpose, data->save.path );
- savedata_file_read( &data->save );
-
- vg_async_dispatch( final_call, skaterift_world_load_done );
- vg_async_stall();
-}
-
-void skaterift_change_client_world_preupdate(void)
-{
- if( world_static.load_state != k_world_loader_preload )
- return;
-
- /* holding pattern before we can start loading the new world, since we might
- * be waiting for audio to stop */
- for( u32 i=1; i<k_world_max; i++ )
- {
- world_instance *inst = &world_static.instances[i];
-
- if( inst->status == k_world_status_unloading )
- {
- if( world_freeable( inst ) )
- {
- world_free( inst );
- }
- return;
- }
- }
-
- if( vg_loader_availible() )
- {
- vg_info( "worlds cleared, begining load\n" );
- world_static.load_state = k_world_loader_load;
-
- vg_linear_clear( vg_async.buffer );
- struct world_load_args *args =
- vg_linear_alloc( vg_async.buffer, sizeof(struct world_load_args) );
- args->purpose = k_world_purpose_client;
- args->reg = world_static.instance_addons[ k_world_purpose_client ];
-
- /* this is replaces the already correct reg but we have to set it again
- * TOO BAD */
-
- /* finally can start the loader */
- vg_loader_start( skaterift_world_load_thread, args );
- }
-}
-
-/*
- * places all loaded worlds into unloading state, pass NULL to reload the world
- */
-void skaterift_change_world_start( addon_reg *reg )
-{
- if( world_static.instance_addons[ k_world_purpose_client ] == reg )
- {
- vg_warn( "World is already loaded\n" );
- return;
- }
-
- if( !reg )
- {
- if( world_static.instance_addons[ k_world_purpose_client ] )
- {
- reg = world_static.instance_addons[ k_world_purpose_client ];
- world_static.clear_async_op_when_done = 1;
- }
- else
- {
- vg_warn( "No client world loaded\n" );
- return;
- }
- }
-
- world_static.load_state = k_world_loader_preload;
-
- if( world_static.active_instance != 0 )
- g_client.loaded = 0;
-
- char buf[76];
- addon_alias_uid( ®->alias, buf );
- vg_info( "switching to: %s\n", buf );
- skaterift_autosave(1);
-
- vg_linear_clear( vg_mem.scratch ); /* ?? */
- vg_info( "unloading old worlds\n" );
-
- world_instance *client_world =
- &world_static.instances[ k_world_purpose_client ];
-
- if( client_world->status == k_world_status_loaded )
- {
- client_world->status = k_world_status_unloading;
- world_fadeout_audio( client_world );
- }
-
- world_static.instance_addons[ k_world_purpose_client ] = reg;
- network_send_item( k_netmsg_playeritem_world1 );
- relink_all_remote_player_worlds();
- world_unlink_nonlocal( &world_static.instances[k_world_purpose_hub] );
-}
-
-/* console command for the above function */
-int skaterift_load_world_command( int argc, const char *argv[] )
-{
- if( !vg_loader_availible() )
- {
- vg_error( "Loading thread is currently unavailible\n" );
- return 0;
- }
-
- if( argc == 1 )
- {
- if( !strcmp( argv[0], "reload" ) )
- {
- skaterift_change_world_start( NULL );
- return 0;
- }
-
- addon_alias q;
- addon_uid_to_alias( argv[0], &q );
-
- u32 reg_id = addon_match( &q );
- if( reg_id != 0xffffffff )
- {
- addon_reg *reg = get_addon_from_index( k_addon_type_world, reg_id, 0 );
- skaterift_change_world_start( reg );
- }
- else
- {
- vg_error( "Addon '%s' is not installed or not found.\n", argv[0] );
- }
- }
- else
- {
- vg_info( "worlds availible to load:\n" );
-
- for( int i=0; i<addon_count(k_addon_type_world,0); i ++ )
- {
- addon_reg *w = get_addon_from_index( k_addon_type_world, i, 0);
-
- char buf[ADDON_UID_MAX];
- addon_alias_uid( &w->alias, buf );
-
- if( w->flags & ADDON_REG_HIDDEN )
- vg_info( " %s [hidden]\n", buf );
- else
- vg_info( " %s\n", buf );
- }
- }
-
- return 0;
-}
-
-/*
- * checks:
- * 1. to see if all audios owned by the world have been stopped
- * 2. that this is the least significant world
- */
-int world_freeable( world_instance *world )
-{
- if( world->status != k_world_status_unloading ) return 0;
- u8 world_id = (world - world_static.instances) + 1;
-
- for( u32 i=world_id; i<VG_ARRAY_LEN(world_static.instances); i++ ){
- if( world_static.instances[i].status != k_world_status_unloaded ){
- return 0;
- }
- }
-
- int freeable = 1;
- audio_lock();
- for( u32 i=0; i<AUDIO_CHANNELS; i++ ){
- audio_channel *ch = &vg_audio.channels[i];
-
- if( ch->allocated && (ch->world_id == world_id)){
- if( !audio_channel_finished( ch ) ){
- freeable = 0;
- break;
- }
- }
- }
- audio_unlock();
- return freeable;
-}
-
-/*
- * Free all resources for world instance
- */
-void world_free( world_instance *world )
-{
- vg_info( "Free world @%p\n", world );
-
- /* free meshes */
- mesh_free( &world->mesh_route_lines );
- mesh_free( &world->mesh_geo );
- mesh_free( &world->mesh_no_collide );
-
- /* glDeleteBuffers silently ignores 0's and names that do not correspond to
- * existing buffer objects.
- * */
- glDeleteBuffers( 1, &world->tbo_light_entities );
- glDeleteTextures( 1, &world->tex_light_entities );
- glDeleteTextures( 1, &world->tex_light_cubes );
-
- /* delete textures and meshes */
- glDeleteTextures( world->texture_count-1, world->textures+1 );
-
- u32 world_index = world - world_static.instances;
- if( world_index ){
- vg_linear_del( world_static.instances[world_index-1].heap,
- vg_linear_header(world->heap) );
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_cubemap); i++ ){
- ent_cubemap *cm = mdl_arritm(&world->ent_cubemap,i);
- glDeleteTextures( 1, &cm->texture_id );
- glDeleteFramebuffers( 1, &cm->framebuffer_id );
- glDeleteRenderbuffers( 1, &cm->renderbuffer_id );
- }
-
- world->status = k_world_status_unloaded;
-}
-
-/*
- * reset the world structure without deallocating persistent buffers
- * TODO: Make this a memset(0), and have persistent items live in a static loc
- */
-void world_init_blank( world_instance *world )
-{
- memset( &world->meta, 0, sizeof(mdl_context) );
-
- world->textures = NULL;
- world->texture_count = 0;
- world->surfaces = NULL;
- world->surface_count = 0;
-
- world->geo_bh = NULL;
- world->entity_bh = NULL;
- world->entity_list = NULL;
- world->rendering_gate = NULL;
-
- world->water.enabled = 0;
- world->time = 0.0;
-
- /* default lighting conditions
- * -------------------------------------------------------------*/
- struct ub_world_lighting *state = &world->ub_lighting;
-
- state->g_light_preview = 0;
- state->g_shadow_samples = 8;
- state->g_water_fog = 0.04f;
-
- v4_zero( state->g_water_plane );
- v4_zero( state->g_depth_bounds );
-
- state->g_shadow_length = 9.50f;
- state->g_shadow_spread = 0.65f;
-
-#if 0
- /* 2023 style */
- v3_copy( (v3f){0.37f, 0.54f, 0.97f}, state->g_daysky_colour );
- v3_copy( (v3f){0.03f, 0.05f, 0.20f}, state->g_nightsky_colour );
- v3_copy( (v3f){1.00f, 0.32f, 0.01f}, state->g_sunset_colour );
- v3_copy( (v3f){0.13f, 0.17f, 0.35f}, state->g_ambient_colour );
- v3_copy( (v3f){0.25f, 0.17f, 0.51f}, state->g_sunset_ambient );
- v3_copy( (v3f){1.10f, 0.89f, 0.35f}, state->g_sun_colour );
-#else
- /* 2024 style */
- v3_copy( (v3f){0.308f, 0.543f, 0.904f}, state->g_daysky_colour );
- v3_copy( (v3f){0.030f, 0.050f, 0.200f}, state->g_nightsky_colour );
- v3_copy( (v3f){1.000f, 0.320f, 0.010f}, state->g_sunset_colour );
- v3_copy( (v3f){0.130f, 0.170f, 0.350f}, state->g_ambient_colour );
- v3_copy( (v3f){0.25f, 0.17f, 0.51f}, state->g_sunset_ambient );
- v3_copy( (v3f){1.000f, 0.809f, 0.318f}, state->g_sun_colour );
-#endif
-}
+++ /dev/null
-#pragma once
-#include <time.h>
-
-#include "world.h"
-#include "addon.h"
-
-void world_free( world_instance *world );
-int world_freeable( world_instance *world );
-int skaterift_load_world_command( int argc, const char *argv[] );
-void skaterift_change_world_start( addon_reg *reg );
-void skaterift_change_client_world_preupdate(void);
+++ /dev/null
-#include "skaterift.h"
-#include "world_map.h"
-#include "world.h"
-#include "input.h"
-#include "gui.h"
-#include "menu.h"
-#include "scene.h"
-
-struct world_map world_map;
-
-static void world_map_get_dir( v3f dir )
-{
- /* idk */
- dir[0] = -sqrtf(0.5f);
- dir[2] = sqrtf(0.5f);
- dir[1] = 1.0f;
- v3_normalize(dir);
-}
-
-static void world_map_get_plane( v4f plane )
-{
- world_instance *world = &world_static.instances[ world_map.world_id ];
- f32 h = localplayer.rb.co[1];
- if( world_map.world_id != world_static.active_instance )
- h = (world->scene_geo.bbx[0][1] + world->scene_geo.bbx[1][1]) * 0.5f;
-
- v4_copy( (v4f){0.0f,1.0f,0.0f,h}, plane );
-}
-
-static void respawn_world_to_plane_pos( v3f pos, v2f plane_pos )
-{
- v3f dir;
- world_map_get_dir( dir );
- v3_negate(dir,dir);
- v4f plane;
- world_map_get_plane( plane );
-
- v3f co;
- f32 t = ray_plane( plane, pos, dir );
- v3_muladds( pos, dir, t, co );
- plane_pos[0] = co[0];
- plane_pos[1] = co[2];
-}
-
-static void respawn_map_draw_icon( vg_camera *cam,
- enum gui_icon icon, v3f pos, f32 size )
-{
- v4f v;
- v3_copy( pos, v );
- v[3] = 1.0f;
- m4x4_mulv( cam->mtx.pv, v, v );
- v2_divs( v, v[3], v );
-
- gui_draw_icon( icon, (v2f){ v[0]*0.5f+0.5f,v[1]*0.5f+0.5f }, size );
-}
-
-static void world_map_select_close(void)
-{
- world_map.sel_spawn = world_map.close_spawn;
- gui_helper_clear();
-
- vg_str text;
- if( gui_new_helper( input_button_list[k_srbind_maccept], &text ) )
- vg_strcat( &text, "Spawn Here" );
- if( gui_new_helper( input_button_list[k_srbind_mback], &text ) )
- vg_strcat( &text, "Back" );
-}
-
-void world_map_click(void)
-{
- world_map_select_close();
-}
-
-static void world_map_help_normal(void)
-{
- gui_helper_clear();
-
- vg_str text;
- if( gui_new_helper( input_joy_list[k_srjoystick_steer], &text ) )
- vg_strcat( &text, "Move" );
-
- if( gui_new_helper( input_button_list[k_srbind_maccept], &text ) )
- vg_strcat( &text, "Select" );
-
- if( gui_new_helper( input_button_list[k_srbind_mback], &text ) )
- vg_strcat( &text, "Exit" );
-
- if( world_static.instances[1].status == k_world_status_loaded )
- {
- if( gui_new_helper( input_button_list[k_srbind_mhub], &text ) )
- vg_strcat( &text, world_static.active_instance?
- "Go to Hub": "Go to Active World" );
- }
-}
-
-void world_map_pre_update(void)
-{
- if( menu_viewing_map() )
- {
- if( !world_map.view_ready )
- {
- world_map.world_id = world_static.active_instance;
-
- world_instance *world = &world_static.instances[ world_map.world_id ];
- v3f *bbx = world->scene_geo.bbx;
-
- v3_copy( localplayer.rb.co, world->player_co );
- respawn_world_to_plane_pos( localplayer.rb.co, world_map.plane_pos );
- world_map.boom_dist = 400.0f;
- world_map.home_select = 0;
- world_map.view_ready = 1;
- world_map.sel_spawn = NULL;
- world_map.close_spawn = NULL;
-
- world_map_help_normal();
- }
- }
- else
- {
- if( world_map.view_ready )
- {
- gui_helper_clear();
- world_map.view_ready = 0;
- }
-
- return;
- }
-
- world_instance *world = &world_static.instances[ world_map.world_id ];
- v3f *bbx = world->scene_geo.bbx;
- f32 *pos = world_map.plane_pos;
-
- v2f steer;
- joystick_state( k_srjoystick_steer, steer );
- v2_normalize_clamp( steer );
-
- if( !world_map.sel_spawn )
- {
- f32 *pos = world_map.plane_pos;
- m2x2f rm;
- m2x2_create_rotation( rm, -0.25f*VG_PIf );
- m2x2_mulv( rm, steer, steer );
- v2_muladds( pos, steer, vg.time_frame_delta * 200.0f, pos );
- }
-
- f32 bd_target = 400.0f,
- interp = vg.time_frame_delta*2.0f;
-
- if( world_map.sel_spawn )
- {
- v2f pp;
- respawn_world_to_plane_pos( world_map.sel_spawn->transform.co, pp );
- v2_lerp( pos, pp, interp, pos );
-
- bd_target = 200.0f;
- }
- world_map.boom_dist = vg_lerpf( world_map.boom_dist, bd_target, interp );
-
- v2_minv( (v2f){ bbx[1][0], bbx[1][2] }, pos, pos );
- v2_maxv( (v2f){ bbx[0][0], bbx[0][2] }, pos, pos );
-
- /* update camera */
- vg_camera *cam = &world_map.cam;
- v3f dir;
- world_map_get_dir(dir);
-
- v4f plane;
- world_map_get_plane( plane );
-
- v3f co = { pos[0], plane[3]*plane[1], pos[1] };
- v3_muladds( co, dir, world_map.boom_dist, cam->pos );
-
- vg_line_cross( co, VG__RED, 10.0f );
-
- cam->angles[0] = 0.25f * VG_PIf;
- cam->angles[1] = 0.25f * VG_PIf;
- cam->farz = 5000.0f;
- cam->nearz = 10.0f;
- cam->fov = 40.0f;
-
- vg_camera_update_transform( cam );
- vg_camera_update_view( cam );
- vg_camera_update_projection( cam );
- vg_camera_finalize( cam );
-
- /* pick spawn */
- f32 closest2 = INFINITY;
- v2f centroid = { 0, 0 };
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i++ )
- {
- ent_spawn *spawn = mdl_arritm(&world->ent_spawn,i);
-
- v4f v;
- v3_copy( spawn->transform.co, v );
- v[3] = 1.0f;
- m4x4_mulv( cam->mtx.pv, v, v );
- v2_divs( v, v[3], v );
-
- f32 d2 = v2_dist2(v, centroid);
- if( d2 < closest2 )
- {
- world_map.close_spawn = spawn;
- closest2 = d2;
- }
- spawn->transform.s[0] = d2;
- }
-
- if( button_down( k_srbind_maccept ) )
- {
- if( world_map.sel_spawn )
- {
- skaterift.activity = k_skaterift_default;
- world_static.active_instance = world_map.world_id;
- srinput.state = k_input_state_resume;
- player__spawn( world_map.sel_spawn );
- return;
- }
- else
- {
- world_map_select_close();
- }
- }
-
- if( button_down( k_srbind_mback ) )
- {
- if( world_map.sel_spawn )
- {
- world_map.sel_spawn = NULL;
- world_map_help_normal();
- }
- else
- {
- srinput.state = k_input_state_resume;
- skaterift.activity = k_skaterift_default;
- return;
- }
- }
-
- /* icons
- * ---------------------*/
- for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ )
- {
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
-
- enum gui_icon icon = k_gui_icon_exclaim_2d;
- if( challenge->status )
- icon = k_gui_icon_tick_2d;
-
- respawn_map_draw_icon( cam, icon, challenge->transform.co, 1.0f );
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_spawn); i ++ )
- {
- ent_spawn *spawn = mdl_arritm( &world->ent_spawn, i );
-
- if( spawn->transform.s[0] > 0.3f )
- continue;
-
- f32 s = 1.0f-(spawn->transform.s[0] / 0.3f);
- respawn_map_draw_icon( cam,
- spawn==world_map.sel_spawn?
- k_gui_icon_spawn_select: k_gui_icon_spawn,
- spawn->transform.co, s );
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_skateshop); i++ )
- {
- ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, i );
- if( shop->type == k_skateshop_type_boardshop )
- {
- respawn_map_draw_icon( cam, k_gui_icon_board, shop->transform.co, 1 );
- }
- else if( shop->type == k_skateshop_type_worldshop )
- {
- respawn_map_draw_icon( cam, k_gui_icon_world, shop->transform.co, 1 );
- }
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ )
- {
- ent_gate *gate = mdl_arritm( &world->ent_gate, i );
- if( gate->flags & k_ent_gate_nonlocal )
- {
- respawn_map_draw_icon( cam, k_gui_icon_rift, gate->co[0], 1 );
- }
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ )
- {
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- v4f colour;
- v4_copy( route->colour, colour );
- v3_muls( colour, 1.6666f, colour );
- gui_icon_setcolour( colour );
- respawn_map_draw_icon( cam, k_gui_icon_rift_run_2d,
- route->board_transform[3], 1 );
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_glider); i ++ )
- {
- ent_glider *glider = mdl_arritm( &world->ent_glider, i );
-
- v4f colour = { 1,1,1,1 };
-
- if( !(glider->flags & 0x1) )
- v3_muls( colour, 0.5f, colour );
- gui_icon_setcolour( colour );
-
- respawn_map_draw_icon( cam, k_gui_icon_glider, glider->transform.co, 1 );
- }
-}
+++ /dev/null
-#pragma once
-#include "vg/vg_platform.h"
-#include "vg/vg_camera.h"
-#include "world_entity.h"
-
-struct world_map
-{
- v2f plane_pos;
- f32 boom_dist;
- u32 world_id;
- u32 home_select;
-
- ent_spawn *sel_spawn, *close_spawn;
- vg_camera cam;
-
- bool view_ready;
-}
-extern world_map;
-void world_map_pre_update(void);
+++ /dev/null
-#ifndef WORLD_PHYSICS_C
-#define WORLD_PHYSICS_C
-
-#include "world.h"
-#include "world_physics.h"
-
-void ray_world_get_tri( world_instance *world, ray_hit *hit, v3f tri[3] )
-{
- for( int i=0; i<3; i++ )
- v3_copy( world->scene_geo.arrvertices[ hit->tri[i] ].co, tri[i] );
-}
-
-int ray_world( world_instance *world,
- v3f pos, v3f dir, ray_hit *hit, u16 ignore )
-{
- return scene_raycast( &world->scene_geo, world->geo_bh, pos, dir, hit,
- ignore );
-}
-
-/*
- * Cast a sphere from a to b and see what time it hits
- */
-int spherecast_world( world_instance *world,
- v3f pa, v3f pb, float r, float *t, v3f n, u16 ignore )
-{
- boxf region;
- box_init_inf( region );
- box_addpt( region, pa );
- box_addpt( region, pb );
-
- v3_add( (v3f){ r, r, r}, region[1], region[1] );
- v3_add( (v3f){-r,-r,-r}, region[0], region[0] );
-
- v3f dir;
- v3_sub( pb, pa, dir );
-
- v3f dir_inv;
- dir_inv[0] = 1.0f/dir[0];
- dir_inv[1] = 1.0f/dir[1];
- dir_inv[2] = 1.0f/dir[2];
-
- int hit = -1;
- float min_t = 1.0f;
-
- bh_iter it;
- bh_iter_init_box( 0, &it, region );
- i32 idx;
- while( bh_next( world->geo_bh, &it, &idx ) ){
- u32 *ptri = &world->scene_geo.arrindices[ idx*3 ];
- if( world->scene_geo.arrvertices[ptri[0]].flags & ignore ) continue;
-
- v3f tri[3];
- boxf box;
- box_init_inf( box );
- for( int j=0; j<3; j++ ){
- v3_copy( world->scene_geo.arrvertices[ptri[j]].co, tri[j] );
- box_addpt( box, tri[j] );
- }
-
- v3_add( (v3f){ r, r, r}, box[1], box[1] );
- v3_add( (v3f){-r,-r,-r}, box[0], box[0] );
-
- if( !ray_aabb1( box, pa, dir_inv, 1.0f ) )
- continue;
-
- float t;
- v3f n1;
- if( spherecast_triangle( tri, pa, dir, r, &t, n1 ) ){
- if( t < min_t ){
- min_t = t;
- hit = idx;
- v3_copy( n1, n );
- }
- }
- }
-
- *t = min_t;
- return hit;
-}
-
-struct world_surface *world_tri_index_surface( world_instance *world,
- u32 index )
-{
- for( int i=1; i<world->surface_count; i++ ){
- struct world_surface *surf = &world->surfaces[i];
-
- if( (index >= surf->sm_geo.vertex_start) &&
- (index < surf->sm_geo.vertex_start+surf->sm_geo.vertex_count ) )
- {
- return surf;
- }
- }
-
- return &world->surfaces[0];
-}
-
-struct world_surface *world_contact_surface( world_instance *world, rb_ct *ct )
-{
- return world_tri_index_surface( world, ct->element_id );
-}
-
-struct world_surface *ray_hit_surface( world_instance *world, ray_hit *hit )
-{
- return world_tri_index_surface( world, hit->tri[0] );
-}
-
-#endif /* WORLD_PHYSICS_C */
+++ /dev/null
-#pragma once
-#include "world.h"
-#include "vg/vg_rigidbody.h"
-#include "vg/vg_rigidbody_collision.h"
-#include "vg/vg_bvh.h"
-
-void ray_world_get_tri( world_instance *world,
- ray_hit *hit, v3f tri[3] );
-
-int ray_world( world_instance *world,
- v3f pos, v3f dir, ray_hit *hit, u16 ignore );
-
-int spherecast_world( world_instance *world,
- v3f pa, v3f pb, float r, float *t, v3f n,
- u16 ignore );
-
-struct world_surface *world_tri_index_surface( world_instance *world,
- u32 index );
-
-struct world_surface *world_contact_surface( world_instance *world,
- rb_ct *ct );
-
-struct world_surface *ray_hit_surface( world_instance *world,
- ray_hit *hit );
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#include "world.h"
-#include "world_render.h"
-#include "font.h"
-#include "gui.h"
-#include "world_map.h"
-#include "ent_miniworld.h"
-#include "player_remote.h"
-#include "ent_skateshop.h"
-#include "ent_npc.h"
-#include "shaders/model_entity.h"
-
-struct world_render world_render;
-
-static int ccmd_set_time( int argc, const char *argv[] ){
- world_instance *world = world_current_instance();
- if( argc == 1 )
- world->time = atof( argv[0] );
- else
- vg_error( "Usage set_time <0-1.0> (current time: %f)\n", world->time );
- return 0;
-}
-
-static void async_world_render_init( void *payload, u32 size )
-{
- vg_info( "Allocate uniform buffers\n" );
- for( int i=0; i<k_world_max; i++ )
- {
- world_instance *world = &world_static.instances[i];
- world->ubo_bind_point = i;
-
- glGenBuffers( 1, &world->ubo_lighting );
- glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
- glBufferData( GL_UNIFORM_BUFFER, sizeof(struct ub_world_lighting),
- NULL, GL_DYNAMIC_DRAW );
-
- glBindBufferBase( GL_UNIFORM_BUFFER, i, world->ubo_lighting );
- }
-}
-
-void world_render_init(void)
-{
- VG_VAR_F32( k_day_length );
- VG_VAR_I32( k_debug_light_indices );
- VG_VAR_I32( k_debug_light_complexity );
- VG_VAR_I32( k_light_preview );
- VG_VAR_I32( k_light_editor );
- vg_console_reg_cmd( "set_time", ccmd_set_time, NULL );
-
- world_render.sky_rate = 1.0;
- world_render.sky_target_rate = 1.0;
-
- vg_info( "Loading world resources\n" );
- vg_linear_clear( vg_mem.scratch );
-
- mdl_context msky;
- mdl_open( &msky, "models/rs_skydome.mdl", vg_mem.scratch );
- mdl_load_metadata_block( &msky, vg_mem.scratch );
- mdl_async_load_glmesh( &msky, &world_render.skydome, NULL );
- mdl_close( &msky );
-
- vg_info( "Loading default world textures\n" );
- vg_tex2d_load_qoi_async_file( "textures/garbage.qoi",
- VG_TEX2D_NEAREST|VG_TEX2D_REPEAT,
- &world_render.tex_terrain_noise );
-
- vg_info( "Allocate frame buffers\n" );
- for( int i=0; i<k_world_max; i++ )
- {
- world_instance *world = &world_static.instances[i];
- world->heightmap = vg_framebuffer_allocate( vg_mem.rtmemory, 1, 0 );
- world->heightmap->display_name = NULL;
- world->heightmap->fixed_w = 1024;
- world->heightmap->fixed_h = 1024;
- world->heightmap->resolution_div = 0;
- world->heightmap->attachments[0] = (vg_framebuffer_attachment)
- {
- NULL, k_framebuffer_attachment_type_texture,
- .internalformat = GL_RG16F,
- .format = GL_RG,
- .type = GL_FLOAT,
- .attachment = GL_COLOR_ATTACHMENT0
- };
- vg_framebuffer_create( world->heightmap );
- }
-
- vg_async_call( async_world_render_init, NULL, 0 );
-}
-
-/*
- * standard uniform bindings
- * ----------------------------------------------------------------------------
- */
-void world_link_lighting_ub( world_instance *world, GLuint shader )
-{
- GLuint idx = glGetUniformBlockIndex( shader, "ub_world_lighting" );
- glUniformBlockBinding( shader, idx, world->ubo_bind_point );
-}
-
-void world_bind_position_texture( world_instance *world,
- GLuint shader, GLuint location,
- int slot )
-{
- vg_framebuffer_bind_texture( world->heightmap, 0, slot );
- glUniform1i( location, slot );
-}
-
-void world_bind_light_array( world_instance *world,
- GLuint shader, GLuint location,
- int slot )
-{
- glActiveTexture( GL_TEXTURE0 + slot );
- glBindTexture( GL_TEXTURE_BUFFER, world->tex_light_entities );
- glUniform1i( location, slot );
-}
-
-void world_bind_light_index( world_instance *world,
- GLuint shader, GLuint location,
- int slot )
-{
- glActiveTexture( GL_TEXTURE0 + slot );
- glBindTexture( GL_TEXTURE_3D, world->tex_light_cubes );
- glUniform1i( location, slot );
-}
-
-void bind_terrain_noise(void)
-{
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
-}
-
-/*
- * Get OpenGL texture name from texture ID.
- */
-static GLuint world_get_texture( world_instance *world, u32 id ){
- if( id & 0x80000000 ) return skaterift.rt_textures[id & ~0x80000000];
- else return world->textures[ id ];
-}
-
-/*
- * Passes Rendering
- * ----------------------------------------------------------------------------
- */
-
-struct world_pass
-{
- vg_camera *cam;
- enum mdl_shader shader;
- enum world_geo_type geo_type;
-
- void (*fn_bind)( world_instance *world, struct world_surface *mat );
- void (*fn_set_mdl)( m4x3f mdl );
- void (*fn_set_uPvmPrev)( m4x4f pvm );
- void (*fn_set_uNormalMtx)( m3x3f mnorm );
-};
-
-void render_world_depth( world_instance *world, vg_camera *cam );
-
-/*
- * Render a run of submeshes, only of those which match material_id
- */
-static void world_render_submeshes( world_instance *world,
- struct world_pass *pass,
- mdl_transform *transform,
- u32 start, u32 count, u32 material_id )
-{
- for( u32 k=0; k<count; k++ )
- {
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs, start+k );
- if( sm->material_id != material_id )
- continue;
-
- m4x3f mmdl;
- mdl_transform_m4x3( transform, mmdl );
-
- m4x4f m4mdl;
- m4x3_expand( mmdl, m4mdl );
- m4x4_mul( pass->cam->mtx_prev.pv, m4mdl, m4mdl );
-
- pass->fn_set_mdl( mmdl );
- pass->fn_set_uPvmPrev( m4mdl );
-
- mdl_draw_submesh( sm );
- }
-}
-
-/*
- * Render props attached to this material
- */
-static void world_render_props( world_instance *world, u32 material_id,
- struct world_pass *pass )
-{
- struct world_surface *mat = &world->surfaces[ material_id ];
- if( !(mat->flags & WORLD_SURFACE_HAS_PROPS) ) return;
-
- pass->fn_bind( world, mat );
-
- for( u32 j=0; j<mdl_arrcount( &world->ent_prop ); j++ ){
- ent_prop *prop = mdl_arritm( &world->ent_prop, j );
- if( prop->flags & 0x1 ) continue;
-
- world_render_submeshes( world, pass, &prop->transform,
- prop->submesh_start, prop->submesh_count, material_id );
- }
-}
-
-/*
- * Render traffic models attactched to this material
- */
-static void world_render_traffic( world_instance *world, u32 material_id,
- struct world_pass *pass )
-{
- struct world_surface *mat = &world->surfaces[ material_id ];
- if( !(mat->flags & WORLD_SURFACE_HAS_TRAFFIC) ) return;
-
- pass->fn_bind( world, mat );
-
- for( u32 j=0; j<mdl_arrcount( &world->ent_traffic ); j++ ){
- ent_traffic *traffic = mdl_arritm( &world->ent_traffic, j );
-
- world_render_submeshes( world, pass, &traffic->transform,
- traffic->submesh_start, traffic->submesh_count,
- material_id );
- }
-}
-
-/*
- * Iterate and render all materials which match the passes shader and geometry
- * type. Includes props/traffic.
- */
-static void world_render_pass( world_instance *world, struct world_pass *pass )
-{
- for( int i=0; i<world->surface_count; i++ )
- {
- struct world_surface *mat = &world->surfaces[i];
-
- if( mat->info.shader == pass->shader )
- {
- mdl_submesh *sm;
-
- if( pass->geo_type == k_world_geo_type_solid )
- {
- sm = &mat->sm_geo;
- }
- else
- {
- world_render_traffic( world, i, pass );
- world_render_props( world, i, pass );
- sm = &mat->sm_no_collide;
- }
-
- if( !sm->indice_count )
- continue;
-
- m4x3f mmdl;
- m4x3_identity( mmdl );
- pass->fn_set_mdl( mmdl );
- pass->fn_set_uPvmPrev( pass->cam->mtx_prev.pv );
- pass->fn_bind( world, mat );
- mdl_draw_submesh( sm );
- }
- }
-}
-
-/*
- * Specific shader instructions
- * ----------------------------------------------------------------------------
- */
-
-static void world_render_both_stages( world_instance *world,
- struct world_pass *pass )
-{
- mesh_bind( &world->mesh_geo );
- pass->geo_type = k_world_geo_type_solid;
- world_render_pass( world, pass );
-
- glDisable( GL_CULL_FACE );
- mesh_bind( &world->mesh_no_collide );
- pass->geo_type = k_world_geo_type_nonsolid;
- world_render_pass( world, pass );
- glEnable( GL_CULL_FACE );
-}
-
-static void bindpoint_world_vb( world_instance *world,
- struct world_surface *mat )
-{
- struct shader_props_vertex_blend *props = mat->info.props.compiled;
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
-
-#if 0
- shader_scene_vertex_blend_uOffset( props->blend_offset );
-#endif
-}
-
-static void render_world_vb( world_instance *world, vg_camera *cam )
-{
- shader_scene_vertex_blend_use();
- shader_scene_vertex_blend_uTexGarbage(0);
- shader_scene_vertex_blend_uTexGradients(1);
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_vertex_blend );
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
-
- shader_scene_vertex_blend_uPv( cam->mtx.pv );
- shader_scene_vertex_blend_uCamera( cam->transform[3] );
-
- struct world_pass pass =
- {
- .shader = k_shader_standard_vertex_blend,
- .cam = cam,
- .fn_bind = bindpoint_world_vb,
- .fn_set_mdl = shader_scene_vertex_blend_uMdl,
- .fn_set_uPvmPrev = shader_scene_vertex_blend_uPvmPrev,
- };
-
- world_render_both_stages( world, &pass );
-}
-
-static void world_shader_standard_bind( world_instance *world, vg_camera *cam )
-{
- shader_scene_standard_use();
- shader_scene_standard_uTexGarbage(0);
- shader_scene_standard_uTexMain(1);
- shader_scene_standard_uPv( cam->mtx.pv );
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_standard );
-
- bind_terrain_noise();
- shader_scene_standard_uCamera( cam->transform[3] );
-}
-
-static void bindpoint_standard( world_instance *world,
- struct world_surface *mat )
-{
- struct shader_props_standard *props = mat->info.props.compiled;
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
-}
-
-static void render_world_standard( world_instance *world, vg_camera *cam )
-{
- world_shader_standard_bind( world, cam );
- struct world_pass pass =
- {
- .shader = k_shader_standard,
- .cam = cam,
- .fn_bind = bindpoint_standard,
- .fn_set_mdl = shader_scene_standard_uMdl,
- .fn_set_uPvmPrev = shader_scene_standard_uPvmPrev,
- };
-
- world_render_both_stages( world, &pass );
-}
-
-static void bindpoint_world_cubemapped( world_instance *world,
- struct world_surface *mat )
-{
- struct shader_props_cubemapped *props = mat->info.props.compiled;
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D,
- world_get_texture( world,props->tex_diffuse ) );
-
- u32 cubemap_id = props->cubemap_entity,
- cubemap_index = 0;
-
- if( mdl_entity_id_type( cubemap_id ) == k_ent_cubemap )
- {
- cubemap_index = mdl_entity_id_id( cubemap_id );
- }
-
- ent_cubemap *cm = mdl_arritm( &world->ent_cubemap, cubemap_index );
- glActiveTexture( GL_TEXTURE10 );
- glBindTexture( GL_TEXTURE_CUBE_MAP, cm->texture_id );
-
- shader_scene_cubemapped_uColour( props->tint );
-}
-
-static void bindpoint_world_cubemapped_disabled( world_instance *world,
- struct world_surface *mat )
-{
- struct shader_props_cubemapped *props = mat->info.props.compiled;
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D,
- world_get_texture( world, props->tex_diffuse ) );
-}
-
-static void render_world_cubemapped( world_instance *world, vg_camera *cam,
- int enabled )
-{
- if( !mdl_arrcount( &world->ent_cubemap ) )
- return;
-
- if( !enabled )
- {
- world_shader_standard_bind( world, cam );
-
- struct world_pass pass =
- {
- .shader = k_shader_cubemap,
- .cam = cam,
- .fn_bind = bindpoint_world_cubemapped_disabled,
- .fn_set_mdl = shader_scene_standard_uMdl,
- .fn_set_uPvmPrev = shader_scene_standard_uPvmPrev,
- };
-
- world_render_both_stages( world, &pass );
- }
- else
- {
- shader_scene_cubemapped_use();
- shader_scene_cubemapped_uTexGarbage(0);
- shader_scene_cubemapped_uTexMain(1);
- shader_scene_cubemapped_uTexCubemap(10);
- shader_scene_cubemapped_uPv( cam->mtx.pv );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_cubemapped );
-
- bind_terrain_noise();
- shader_scene_cubemapped_uCamera( cam->transform[3] );
-
- struct world_pass pass =
- {
- .shader = k_shader_cubemap,
- .cam = cam,
- .fn_bind = bindpoint_world_cubemapped,
- .fn_set_mdl = shader_scene_cubemapped_uMdl,
- .fn_set_uPvmPrev = shader_scene_cubemapped_uPvmPrev,
- };
-
- world_render_both_stages( world, &pass );
- }
-}
-
-static void render_world_alphatest( world_instance *world, vg_camera *cam )
-{
- shader_scene_standard_alphatest_use();
- shader_scene_standard_alphatest_uTexGarbage(0);
- shader_scene_standard_alphatest_uTexMain(1);
- shader_scene_standard_alphatest_uPv( cam->mtx.pv );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_standard_alphatest );
-
- bind_terrain_noise();
- shader_scene_standard_alphatest_uCamera( cam->transform[3] );
- glDisable(GL_CULL_FACE);
-
- struct world_pass pass =
- {
- .shader = k_shader_standard_cutout,
- .cam = cam,
- .fn_bind = bindpoint_standard,
- .fn_set_mdl = shader_scene_standard_alphatest_uMdl,
- .fn_set_uPvmPrev = shader_scene_standard_alphatest_uPvmPrev,
- };
-
- world_render_both_stages( world, &pass );
- glEnable(GL_CULL_FACE);
-}
-
-static void render_world_foliage( world_instance *world, vg_camera *cam )
-{
- shader_scene_foliage_use();
- shader_scene_foliage_uTexGarbage(0);
- shader_scene_foliage_uTexMain(1);
- shader_scene_foliage_uPv( cam->mtx.pv );
- shader_scene_foliage_uTime( vg.time );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_foliage );
- bind_terrain_noise();
-
- shader_scene_foliage_uCamera( cam->transform[3] );
- glDisable(GL_CULL_FACE);
- struct world_pass pass =
- {
- .shader = k_shader_foliage,
- .cam = cam,
- .fn_bind = bindpoint_standard,
- .fn_set_mdl = shader_scene_foliage_uMdl,
- .fn_set_uPvmPrev = shader_scene_foliage_uPvmPrev,
- };
- world_render_both_stages( world, &pass );
- glEnable(GL_CULL_FACE);
-}
-
-static void world_render_challenges( world_instance *world,
- struct world_pass *pass, v3f pos )
-{
- if( !world ) return;
- if( skaterift.activity == k_skaterift_replay ) return;
- if( world != world_current_instance() ) return;
-
- /* sort lists */
- f32 radius = 40.0f;
-
- u32 objective_list[ 32 ],
- challenge_list[ 16 ];
-
- v2f objective_uv_offsets[ 32 ];
-
- u32 objective_count = 0,
- challenge_count = 0;
-
- ent_challenge *active_challenge = NULL;
- int running = 0;
- if( mdl_entity_id_type( world_static.focused_entity ) == k_ent_challenge ){
- if( (skaterift.activity == k_skaterift_default) &&
- world_static.challenge_target ){
- running = 1;
- }
-
- if( !((skaterift.activity != k_skaterift_ent_focus) &&
- !world_static.challenge_target) ){
- world_instance *challenge_world = world_current_instance();
- u32 index = mdl_entity_id_id( world_static.focused_entity );
- active_challenge = mdl_arritm(&challenge_world->ent_challenge, index);
- }
- }
-
- if( active_challenge ){
- shader_scene_fxglow_uUvOffset( (v2f){ 8.0f/256.0f, 0.0f } );
- challenge_list[ challenge_count ++ ] = world_static.focused_entity;
-
- u32 next = active_challenge->first;
- while( mdl_entity_id_type(next) == k_ent_objective ){
- u32 index = mdl_entity_id_id( next );
- objective_list[ objective_count ++ ] = index;
-
- ent_objective *objective = mdl_arritm( &world->ent_objective, index );
- next = objective->id_next;
- }
-
- radius = 10000.0f;
- }
- else {
- shader_scene_fxglow_uUvOffset( (v2f){ 0.0f, 0.0f } );
- bh_iter it;
- bh_iter_init_range( 0, &it, pos, radius+10.0f );
- i32 idx;
- while( bh_next( world->entity_bh, &it, &idx ) ){
- u32 id = world->entity_list[ idx ],
- type = mdl_entity_id_type( id ),
- index = mdl_entity_id_id( id );
-
- if( type == k_ent_objective ) {
- if( objective_count < VG_ARRAY_LEN(objective_list) )
- objective_list[ objective_count ++ ] = index;
- }
- else if( type == k_ent_challenge ){
- if( challenge_count < VG_ARRAY_LEN(challenge_list) )
- challenge_list[ challenge_count ++ ] = index;
- }
- }
- }
-
- /* render objectives */
- glDisable( GL_CULL_FACE );
- mesh_bind( &world->mesh_no_collide );
- u32 last_material = 0;
- for( u32 i=0; i<objective_count; i++ )
- {
- u32 index = objective_list[ i ];
- ent_objective *objective = mdl_arritm( &world->ent_objective, index );
- if( (objective->flags & k_ent_objective_hidden) &&
- !active_challenge ) continue;
-
- f32 scale = 1.0f;
-
- if( running )
- {
- u32 passed = objective->flags & k_ent_objective_passed;
- f32 target = passed? 0.0f: 1.0f;
- vg_slewf(&objective->transform.s[0], target, vg.time_frame_delta*4.0f);
- scale = vg_smoothstepf( objective->transform.s[0] );
-
- if( (objective == world_static.challenge_target) || passed )
- shader_scene_fxglow_uUvOffset( (v2f){ 16.0f/256.0f, 0.0f } );
- else
- shader_scene_fxglow_uUvOffset( (v2f){ 8.0f/256.0f, 0.0f } );
- }
- else
- {
- f32 dist = v3_dist( objective->transform.co, pos ) * (1.0f/radius);
- scale = vg_smoothstepf( vg_clampf( 5.0f-dist*5.0f, 0.0f,1.0f ) );
- }
-
- m4x3f mmdl;
- q_m3x3( objective->transform.q, mmdl );
- m3x3_scalef( mmdl, scale );
- v3_copy( objective->transform.co, mmdl[3] );
- shader_scene_fxglow_uMdl( mmdl );
-
- for( u32 j=0; j<objective->submesh_count; j++ )
- {
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- objective->submesh_start + j );
-
- if( sm->material_id != last_material )
- {
- last_material = sm->material_id;
- pass->fn_bind( world, &world->surfaces[sm->material_id] );
- }
- mdl_draw_submesh( sm );
- }
- }
-
- /* render texts */
- font3d_bind( &gui.font, k_font_shader_world, 0, world, &g_render.cam );
-
- u32 count = 0;
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_challenge); i++ )
- {
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, i );
- if( challenge->status ) count ++;
- }
-
- char buf[32];
- vg_str str;
- vg_strnull( &str, buf, sizeof(buf) );
- vg_strcati32( &str, count );
- vg_strcatch( &str, '/' );
- vg_strcati32( &str, mdl_arrcount(&world->ent_challenge) );
-
- f32 w = font3d_string_width( 1, buf );
- m4x3f mlocal;
- m3x3_identity( mlocal );
- mlocal[3][0] = -w*0.5f;
- mlocal[3][1] = 0.0f;
- mlocal[3][2] = 0.0f;
-
- for( u32 i=0; i<challenge_count; i++ )
- {
- u32 index = challenge_list[ i ];
- ent_challenge *challenge = mdl_arritm( &world->ent_challenge, index );
- m4x3f mmdl;
- mdl_transform_m4x3( &challenge->transform, mmdl );
- m4x3_mul( mmdl, mlocal, mmdl );
-
- vg_line_point( challenge->transform.co, 0.25f, VG__RED );
-
- f32 dist = v3_dist( challenge->transform.co, pos ) * (1.0f/radius),
- scale = vg_smoothstepf( vg_clampf( 10.0f-dist*10.0f, 0.0f,1.0f ) ),
- colour = 0.0f;
-
- if( challenge->status )
- colour = 1.0f;
-
- shader_scene_font_uOpacity( scale );
- shader_scene_font_uColourize( colour );
- font3d_simple_draw( 1, buf, &g_render.cam, mmdl );
- }
-}
-
-static void bindpoint_fxglow( world_instance *world,
- struct world_surface *mat )
-{
- struct shader_props_standard *props = mat->info.props.compiled;
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
-}
-
-static void render_world_fxglow( world_instance *host_world,
- world_instance *world, vg_camera *cam,
- m4x3f world_mmdl,
- int generic, int challenges, int regions )
-{
- shader_scene_fxglow_use();
- shader_scene_fxglow_uUvOffset( (v2f){ 0.0f, 0.0f } );
- shader_scene_fxglow_uTexMain(1);
- shader_scene_fxglow_uPv( cam->mtx.pv );
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_fxglow );
-
- shader_scene_fxglow_uCamera( cam->transform[3] );
- glDisable(GL_CULL_FACE);
-
- struct world_pass pass =
- {
- .shader = k_shader_fxglow,
- .cam = cam,
- .fn_bind = bindpoint_fxglow,
- .fn_set_mdl = shader_scene_fxglow_uMdl,
- .fn_set_uPvmPrev = shader_scene_fxglow_uPvmPrev,
- };
-
- if( generic )
- world_render_both_stages( world, &pass );
-
- if( regions ){
- mesh_bind( &world->mesh_no_collide );
-
- u32 last_material = 0;
- for( u32 i=0; i<mdl_arrcount(&world->ent_region); i ++ ){
- shader_scene_fxglow_uUvOffset( (v2f){ 0.0f, 0.0f } );
- ent_region *region = mdl_arritm( &world->ent_region, i );
-
- f32 offset = 0.0f;
- if( region->flags & k_ent_route_flag_achieve_gold )
- offset = 2.0f;
- else if( region->flags & k_ent_route_flag_achieve_silver )
- offset = 1.0f;
-
- shader_scene_fxglow_uUvOffset( (v2f){ (8.0f/256.0f)*offset, 0.0f } );
-
- m4x3f mmdl;
- mdl_transform_m4x3( ®ion->transform, mmdl );
- m4x3_mul( world_mmdl, mmdl, mmdl );
- shader_scene_fxglow_uMdl( mmdl );
-
- for( u32 j=0; j<region->submesh_count; j++ )
- {
- mdl_submesh *sm = mdl_arritm( &world->meta.submeshs,
- region->submesh_start + j );
-
- if( sm->material_id != last_material )
- {
- last_material = sm->material_id;
- pass.fn_bind( world, &world->surfaces[sm->material_id] );
- }
- mdl_draw_submesh( sm );
- }
- }
- }
-
- if( challenges )
- world_render_challenges( world, &pass, cam->pos );
-
- glEnable(GL_CULL_FACE);
-}
-
-static void bindpoint_terrain( world_instance *world,
- struct world_surface *mat )
-{
- struct shader_props_terrain *props = mat->info.props.compiled;
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_get_texture(world, props->tex_diffuse) );
- shader_scene_terrain_uBlendOffset( props->blend_offset );
- shader_scene_terrain_uSandColour( props->sand_colour );
-}
-
-static void bindpoint_override( world_instance *world,
- struct world_surface *mat )
-{
- if( mat->info.flags & k_material_flag_collision )
- {
- shader_scene_override_uAlphatest(0);
- }
- else
- {
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_get_texture(world, mat->alpha_tex) );
- shader_scene_override_uAlphatest(1);
- }
-}
-
-static void render_terrain( world_instance *world, vg_camera *cam )
-{
- shader_scene_terrain_use();
- shader_scene_terrain_uTexGarbage(0);
- shader_scene_terrain_uTexGradients(1);
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_terrain );
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
-
- shader_scene_terrain_uPv( cam->mtx.pv );
- shader_scene_terrain_uCamera( cam->transform[3] );
-
- struct world_pass pass =
- {
- .shader = k_shader_terrain_blend,
- .cam = cam,
- .fn_bind = bindpoint_terrain,
- .fn_set_mdl = shader_scene_terrain_uMdl,
- .fn_set_uPvmPrev = shader_scene_terrain_uPvmPrev,
- };
-
- world_render_both_stages( world, &pass );
-}
-
-static void render_sky( world_instance *world, vg_camera *cam )
-{
- /*
- * Modify matrix to remove clipping and view translation
- */
- m4x4f v,
- v_prev,
- pv,
- pv_prev;
-
- m4x4_copy( cam->mtx.v, v );
- m4x4_copy( cam->mtx_prev.v, v_prev );
-
- for( int i=0; i<3; i++ ){
- v3_normalize(v[i]);
- v3_normalize(v_prev[i]);
- }
- v3_zero( v[3] );
- v3_zero( v_prev[3] );
-
- m4x4_copy( cam->mtx.p, pv );
- m4x4_copy( cam->mtx_prev.p, pv_prev );
- m4x4_reset_clipping( pv, 100.0f, 0.1f );
- m4x4_reset_clipping( pv_prev, 100.0f, 0.1f );
-
- m4x4_mul( pv, v, pv );
- m4x4_mul( pv_prev, v_prev, pv_prev );
-
- m4x3f identity_matrix;
- m4x3_identity( identity_matrix );
-
- /*
- * Draw
- */
- if( world->skybox == k_skybox_default ){
- shader_model_sky_use();
- shader_model_sky_uMdl( identity_matrix );
- shader_model_sky_uPv( pv );
- shader_model_sky_uPvmPrev( pv_prev );
- shader_model_sky_uTexGarbage(0);
- world_link_lighting_ub( world, _shader_model_sky.id );
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
- }
- else if( world->skybox == k_skybox_space ){
- shader_model_sky_space_use();
-
- shader_model_sky_space_uMdl( identity_matrix );
- shader_model_sky_space_uPv( pv );
- shader_model_sky_space_uPvmPrev( pv_prev );
- shader_model_sky_space_uTexGarbage(0);
- world_link_lighting_ub( world, _shader_model_sky_space.id );
-
- glActiveTexture( GL_TEXTURE0 );
- glBindTexture( GL_TEXTURE_2D, world_render.tex_terrain_noise );
- }
- else {
- vg_fatal_error( "Programming error\n" );
- }
-
- glDepthMask( GL_FALSE );
- glDisable( GL_DEPTH_TEST );
-
- mesh_bind( &world_render.skydome );
- mesh_draw( &world_render.skydome );
-
- glEnable( GL_DEPTH_TEST );
- glDepthMask( GL_TRUE );
-}
-
-void render_world_gates( world_instance *world, vg_camera *cam )
-{
- float closest = INFINITY;
- struct ent_gate *gate = NULL;
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
- ent_gate *gi = mdl_arritm( &world->ent_gate, i );
-
- if( !(gi->flags & k_ent_gate_nonlocal) )
- if( !(gi->flags & k_ent_gate_linked) )
- continue;
-
- float dist = v3_dist2( gi->co[0], cam->transform[3] );
-
- vg_line_point( gi->co[0], 0.25f, VG__BLUE );
-
- if( dist < closest ){
- closest = dist;
- gate = gi;
- }
- }
-
- world->rendering_gate = gate;
-
- if( gate ){
- if( gate->flags & k_ent_gate_locked ){
- world->rendering_gate = NULL;
- return;
- }
-
- if( gate->flags & k_ent_gate_nonlocal ){
- if( !(gate->flags & k_ent_gate_linked) ||
- (world_static.load_state != k_world_loader_none) ){
- world->rendering_gate = NULL;
- render_gate_unlinked( world, gate, cam );
- return;
- }
-
- world_instance *dest_world = &world_static.instances[ gate->target ];
- render_gate( world, dest_world, gate, cam );
- }
- else
- render_gate( world, world, gate, cam );
- }
-}
-
-void world_prerender( world_instance *world )
-{
- if( mdl_arrcount( &world->ent_light ) ){
- f32 rate = vg_maxf(0.1f, fabsf(k_day_length)) * vg_signf(k_day_length);
- world->time += vg.time_frame_delta * (1.0/(rate*60.0));
- }
- else{
- world->time = 0.834;
- }
-
- if( world->info.flags & 0x1 ){
- world->time = world->info.timezone;
- }
-
- struct ub_world_lighting *state = &world->ub_lighting;
-
- state->g_time = world->time;
- state->g_realtime = vg.time_real;
- state->g_debug_indices = k_debug_light_indices;
- state->g_light_preview = k_light_preview;
- state->g_debug_complexity = k_debug_light_complexity;
- state->g_time_of_day = vg_fractf( world->time );
-
- if( vg.quality_profile == k_quality_profile_high )
- state->g_shadow_samples = 8;
- else if( vg.quality_profile == k_quality_profile_low )
- state->g_shadow_samples = 2;
- else
- state->g_shadow_samples = 0;
-
- state->g_day_phase = cosf( state->g_time_of_day * VG_PIf * 2.0f );
- state->g_sunset_phase= cosf( state->g_time_of_day * VG_PIf * 4.0f + VG_PIf );
-
- state->g_day_phase = state->g_day_phase * 0.5f + 0.5f;
- state->g_sunset_phase = powf( state->g_sunset_phase * 0.5f + 0.5f, 6.0f );
-
- float a = state->g_time_of_day * VG_PIf * 2.0f;
- state->g_sun_dir[0] = sinf( a );
- state->g_sun_dir[1] = cosf( a );
- state->g_sun_dir[2] = 0.2f;
- v3_normalize( state->g_sun_dir );
-
- world->probabilities[ k_probability_curve_constant ] = 1.0f;
- float dp = state->g_day_phase;
-
- world->probabilities[ k_probability_curve_wildlife_day ] =
- (dp*dp*0.8f+state->g_sunset_phase)*0.8f;
- world->probabilities[ k_probability_curve_wildlife_night ] =
- 1.0f-powf(fabsf((state->g_time_of_day-0.5f)*5.0f),5.0f);
-
- glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
- glBufferSubData( GL_UNIFORM_BUFFER, 0,
- sizeof(struct ub_world_lighting), &world->ub_lighting );
-}
-
-static void render_other_entities( world_instance *world, vg_camera *cam )
-{
- f32 radius = 40.0f;
- bh_iter it;
- bh_iter_init_range( 0, &it, cam->pos, radius+10.0f );
-
- u32 glider_list[4],
- glider_count = 0,
- npc_list[4],
- npc_count = 0;
-
- i32 idx;
- while( bh_next( world->entity_bh, &it, &idx ) ){
- u32 id = world->entity_list[ idx ],
- type = mdl_entity_id_type( id ),
- index = mdl_entity_id_id( id );
-
- if( type == k_ent_glider )
- {
- if( glider_count < VG_ARRAY_LEN(glider_list) )
- glider_list[ glider_count ++ ] = index;
- }
- else if( type == k_ent_npc )
- {
- if( npc_count < VG_ARRAY_LEN(npc_list) )
- npc_list[ npc_count ++ ] = index;
- }
- }
-
- shader_model_entity_use();
- shader_model_entity_uTexMain( 0 );
- shader_model_entity_uCamera( cam->transform[3] );
- shader_model_entity_uPv( cam->mtx.pv );
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, model_entity );
-
- for( u32 j=0; j<glider_count; j ++ )
- {
- ent_glider *glider = mdl_arritm( &world->ent_glider, glider_list[j] );
-
- if( !(glider->flags & 0x1) )
- continue;
-
- m4x3f mdl;
- mdl_transform_m4x3( &glider->transform, mdl );
-
- f32 dist = v3_dist( glider->transform.co, cam->pos ) * (1.0f/radius),
- scale = vg_smoothstepf( vg_clampf( 5.0f-dist*5.0f, 0.0f,1.0f ) );
- m3x3_scalef( mdl, scale );
-
- render_glider_model( cam, world, mdl, k_board_shader_entity );
- }
-
- for( u32 j=0; j<npc_count; j ++ )
- {
- u32 index = npc_list[j];
- ent_npc *npc = mdl_arritm( &world->ent_npc, npc_list[j] );
- npc_update( npc );
- npc_render( npc, world, cam );
- }
-}
-
-void render_world( world_instance *world, vg_camera *cam,
- int stenciled, int viewing_from_gate,
- int with_water, int with_cubemaps )
-{
- if( stenciled ){
- glClear( GL_DEPTH_BUFFER_BIT );
- glStencilFunc( GL_EQUAL, 1, 0xFF );
- glStencilMask( 0x00 );
- glEnable( GL_CULL_FACE );
- glEnable( GL_STENCIL_TEST );
- }
- else {
- glStencilMask( 0xFF );
- glStencilFunc( GL_ALWAYS, 1, 0xFF );
- glDisable( GL_STENCIL_TEST );
- }
-
- render_sky( world, cam );
-
- m4x3f identity;
- m4x3_identity(identity);
- render_world_routes( world, world, identity, cam, viewing_from_gate, 0 );
- render_world_standard( world, cam );
- render_world_cubemapped( world, cam, with_cubemaps );
-
- render_world_vb( world, cam );
- render_world_alphatest( world, cam );
- render_world_foliage( world, cam );
- render_terrain( world, cam );
-
- if( !viewing_from_gate ){
- world_entity_focus_render();
-
- /* Render SFD's */
- u32 closest = 0;
- float min_dist = INFINITY;
-
- if( mdl_arrcount( &world->ent_route ) ){
- for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
- float dist = v3_dist2( route->board_transform[3], cam->pos );
-
- if( dist < min_dist ){
- min_dist = dist;
- closest = i;
- }
- }
-
- ent_route *route = mdl_arritm( &world->ent_route, closest );
- sfd_render( world, cam, route->board_transform );
- }
- }
-
- if( !viewing_from_gate ){
- f32 greyout = 0.0f;
- if( mdl_entity_id_type(world_static.focused_entity) == k_ent_challenge )
- greyout = world_static.focus_strength;
-
- if( greyout > 0.0f ){
- glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
- glEnable(GL_BLEND);
- glDisable(GL_DEPTH_TEST);
- glDepthMask(GL_FALSE);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glBlendEquation(GL_FUNC_ADD);
-
- shader_blitcolour_use();
- shader_blitcolour_uColour( (v4f){ 0.5f, 0.5f, 0.5f, greyout*0.56f } );
- render_fsquad();
-
- glDisable(GL_BLEND);
- glEnable(GL_DEPTH_TEST);
- glDepthMask(GL_TRUE);
- glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0,
- GL_COLOR_ATTACHMENT1 } );
- }
-
- render_world_fxglow( world, world, cam, NULL, 1, 1, 0 );
- }
-
- if( with_water )
- {
- render_water_texture( world, cam );
- vg_framebuffer_bind( g_render.fb_main, k_render_scale );
- }
-
- if( stenciled )
- {
- glStencilFunc( GL_EQUAL, 1, 0xFF );
- glStencilMask( 0x00 );
- glEnable( GL_CULL_FACE );
- glEnable( GL_STENCIL_TEST );
- }
-
- if( with_water )
- {
- render_water_surface( world, cam );
- }
-
- render_remote_players( world, cam );
- render_other_entities( world, cam );
- ent_miniworld_render( world, cam );
-
- if( stenciled )
- {
- glStencilMask( 0xFF );
- glStencilFunc( GL_ALWAYS, 1, 0xFF );
- glDisable( GL_STENCIL_TEST );
- }
-}
-
-
-static void render_world_override_pass( world_instance *world,
- struct world_pass *pass,
- m4x3f mmdl, m3x3f mnormal,
- m4x4f mpvm_prev )
-{
- for( int i=0; i<world->surface_count; i++ )
- {
- struct world_surface *mat = &world->surfaces[i];
- if( mat->info.flags & k_material_flag_ghosts ) continue;
-
- mdl_submesh *sm;
- if( pass->geo_type == k_world_geo_type_solid )
- sm = &mat->sm_geo;
- else
- sm = &mat->sm_no_collide;
-
- if( !sm->indice_count )
- continue;
-
- pass->fn_set_mdl( mmdl );
- pass->fn_set_uNormalMtx( mnormal );
- pass->fn_set_uPvmPrev( mpvm_prev );
- pass->fn_bind( world, mat );
- mdl_draw_submesh( sm );
- }
-}
-
-void render_world_override( world_instance *world,
- world_instance *lighting_source,
- m4x3f mmdl,
- vg_camera *cam,
- ent_spawn *dest_spawn, v4f map_info )
-{
- struct world_pass pass =
- {
- .cam = cam,
- .fn_bind = bindpoint_override,
- .fn_set_mdl = shader_scene_override_uMdl,
- .fn_set_uPvmPrev = shader_scene_override_uPvmPrev,
- .fn_set_uNormalMtx = shader_scene_override_uNormalMtx,
- .shader = k_shader_override
- };
-
- shader_scene_override_use();
- shader_scene_override_uTexGarbage(0);
- shader_scene_override_uTexMain(1);
- shader_scene_override_uPv( pass.cam->mtx.pv );
- shader_scene_override_uMapInfo( map_info );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( lighting_source, scene_override );
- bind_terrain_noise();
-
- shader_scene_override_uCamera( pass.cam->transform[3] );
-
- m4x4f mpvm_prev;
- m4x3_expand( mmdl, mpvm_prev );
- m4x4_mul( cam->mtx_prev.pv, mpvm_prev, mpvm_prev );
-
- m3x3f mnormal;
- m3x3_inv( mmdl, mnormal );
- m3x3_transpose( mnormal, mnormal );
- v3_normalize( mnormal[0] );
- v3_normalize( mnormal[1] );
- v3_normalize( mnormal[2] );
-
- v4f uPlayerPos, uSpawnPos;
- v4_zero( uPlayerPos );
- v4_zero( uSpawnPos );
- v3_copy( world->player_co, uPlayerPos );
-
- if( dest_spawn && (v3_dist2(dest_spawn->transform.co,uPlayerPos) > 0.1f) )
- v3_copy( dest_spawn->transform.co, uSpawnPos );
- else
- v3_add( uPlayerPos, (v3f){0,-1,0}, uSpawnPos );
-
- uPlayerPos[3] = v3_dist(uPlayerPos,uSpawnPos);
- uSpawnPos[3] = 1.0f/uPlayerPos[3];
-
- shader_scene_override_uPlayerPos( uPlayerPos );
- shader_scene_override_uSpawnPos( uSpawnPos );
-
-
- glDisable( GL_CULL_FACE );
- mesh_bind( &world->mesh_geo );
- pass.geo_type = k_world_geo_type_solid;
- render_world_override_pass( world, &pass, mmdl, mnormal, mpvm_prev );
- mesh_bind( &world->mesh_no_collide );
- pass.geo_type = k_world_geo_type_nonsolid;
- render_world_override_pass( world, &pass, mmdl, mnormal, mpvm_prev );
- glEnable( GL_CULL_FACE );
-
- render_world_fxglow( world, world, cam, mmdl, 0, 0, 1 );
-}
-
-static void render_cubemap_side( world_instance *world, ent_cubemap *cm,
- u32 side ){
- vg_camera cam;
- glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_CUBE_MAP_POSITIVE_X + side, cm->texture_id, 0 );
- glClear( GL_DEPTH_BUFFER_BIT );
-
- v3f forward[6] = {
- { -1.0f, 0.0f, 0.0f },
- { 1.0f, 0.0f, 0.0f },
- { 0.0f, -1.0f, 0.0f },
- { 0.0f, 1.0f, 0.0f },
- { 0.0f, 0.0f, -1.0f },
- { 0.0f, 0.0f, 1.0f }
- };
- v3f up[6] = {
- { 0.0f, -1.0f, 0.0f },
- { 0.0f, -1.0f, 0.0f },
- { 0.0f, 0.0f, 1.0f },
- { 0.0f, 0.0f, -1.0f },
- { 0.0f, -1.0f, 0.0f },
- { 0.0f, -1.0f, 0.0f }
- };
-
- v3_zero( cam.angles );
- v3_copy( cm->co, cam.pos );
-
- v3_copy( forward[side], cam.transform[2] );
- v3_copy( up[side], cam.transform[1] );
- v3_cross( up[side], forward[side], cam.transform[0] );
- v3_copy( cm->co, cam.transform[3] );
- m4x3_invert_affine( cam.transform, cam.transform_inverse );
-
- vg_camera_update_view( &cam );
-
- cam.nearz = 0.1f;
- cam.farz = 1000.0f;
- cam.fov = 90.0f;
- m4x4_copy( cam.mtx.p, cam.mtx_prev.p );
- m4x4_projection( cam.mtx.p, cam.fov, 1.0f, cam.nearz, cam.farz );
- vg_camera_finalize( &cam );
- vg_camera_finalize( &cam );
-
- render_world( world, &cam, 0, 1, 1, 0 );
-}
-
-void render_world_cubemaps( world_instance *world )
-{
- if( world->cubemap_cooldown )
- world->cubemap_cooldown --;
- else{
- world->cubemap_cooldown = 60;
-
- glViewport( 0, 0, WORLD_CUBEMAP_RES, WORLD_CUBEMAP_RES );
- for( u32 i=0; i<mdl_arrcount( &world->ent_cubemap ); i++ ){
- ent_cubemap *cm = mdl_arritm( &world->ent_cubemap, i );
- glBindFramebuffer( GL_FRAMEBUFFER, cm->framebuffer_id );
-
- world->cubemap_side ++;
- if( world->cubemap_side >= 6 )
- world->cubemap_side = 0;
-
- render_cubemap_side( world, cm, world->cubemap_side );
- }
- }
-}
-
-/*
- * Geo shaders
- * ---------------------------------------------
- */
-
-void render_world_depth( world_instance *world, vg_camera *cam )
-{
- m4x3f identity_matrix;
- m4x3_identity( identity_matrix );
-
- shader_scene_depth_use();
- shader_scene_depth_uCamera( cam->transform[3] );
- shader_scene_depth_uPv( cam->mtx.pv );
- shader_scene_depth_uPvmPrev( cam->mtx_prev.pv );
- shader_scene_depth_uMdl( identity_matrix );
- world_link_lighting_ub( world, _shader_scene_depth.id );
-
- mesh_bind( &world->mesh_geo );
- mesh_draw( &world->mesh_geo );
-}
-
-void render_world_position( world_instance *world, vg_camera *cam )
-{
- m4x3f identity_matrix;
- m4x3_identity( identity_matrix );
-
- shader_scene_position_use();
- shader_scene_position_uCamera( cam->transform[3] );
- shader_scene_position_uPv( cam->mtx.pv );
- shader_scene_position_uPvmPrev( cam->mtx_prev.pv );
- shader_scene_position_uMdl( identity_matrix );
- world_link_lighting_ub( world, _shader_scene_position.id );
-
- mesh_bind( &world->mesh_geo );
- mesh_draw( &world->mesh_geo );
-}
-
-struct ui_enum_opt skybox_setting_options[] = {
- { 0, "g_daysky_colour" },
- { 1, "g_nightsky_colour" },
- { 2, "g_sunset_colour" },
- { 3, "g_ambient_colour" },
- { 4, "g_sun_colour" },
-};
-
-static f32 *skybox_prop_location( world_instance *world, i32 index ){
- switch( index ){
- case 0: return world->ub_lighting.g_daysky_colour; break;
- case 1: return world->ub_lighting.g_nightsky_colour; break;
- case 2: return world->ub_lighting.g_sunset_colour; break;
- case 3: return world->ub_lighting.g_ambient_colour; break;
- case 4: return world->ub_lighting.g_sun_colour; break;
- default: return NULL;
- }
-}
-
-void imgui_world_light_edit( ui_context *ctx, world_instance *world )
-{
- ui_rect panel = { vg.window_x-400, 0, 400, vg.window_y };
- ui_fill( ctx, panel, ui_colour( ctx, k_ui_bg+1 ) );
- ui_outline( ctx, panel, 1, ui_colour( ctx, k_ui_bg+7 ), 0 );
- ui_rect_pad( panel, (ui_px[2]){ 8, 8 } );
- ui_capture_mouse(ctx, 1);
-
- static i32 option_to_edit = 0;
- ui_enum( ctx, panel, "option", skybox_setting_options, 5, &option_to_edit );
- ui_colourpicker( ctx, panel, "colour",
- skybox_prop_location( world, option_to_edit ) );
-
- if( ui_button( ctx, panel, "save tweaker file ('/tmp/tweaker.txt')\n" ) == 1 )
- {
- FILE *fp = fopen( "/tmp/tweaker.txt", "w" );
-
- for( i32 i=0; i<5; i ++ ){
- struct ui_enum_opt *opt = &skybox_setting_options[i];
- f32 *val = skybox_prop_location( world, i );
- fprintf( fp, "%s = {%.3ff, %.3ff, %.3ff, %.3ff},\n",
- opt->alias, val[0], val[1], val[2], val[3] );
- }
- fclose( fp );
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-
-#define WORLD_CUBEMAP_RES 32
-
-#include "vg/vg_camera.h"
-#include "world.h"
-#include "shaders/scene_standard.h"
-#include "shaders/scene_standard_alphatest.h"
-#include "shaders/scene_foliage.h"
-#include "shaders/scene_override.h"
-#include "shaders/scene_cubemapped.h"
-#include "shaders/scene_vertex_blend.h"
-#include "shaders/scene_terrain.h"
-#include "shaders/scene_fxglow.h"
-#include "shaders/scene_depth.h"
-#include "shaders/scene_position.h"
-#include "shaders/scene_font.h"
-#include "shaders/model_sky.h"
-#include "shaders/model_sky_space.h"
-
-static const float k_world_light_cube_size = 8.0f;
-
-struct world_render
-{
- GLuint tex_terrain_noise;
-
- /* rendering */
- glmesh skydome;
-
- double sky_time, sky_rate, sky_target_rate;
-
- v3f render_gate_pos;
- struct timer_text{
- char text[8];
- m4x3f transform;
- ent_gate *gate;
- ent_route *route;
- }
- timer_texts[4];
- u32 timer_text_count;
-
- struct text_particle{
- rigidbody rb;
- m4x3f mlocal;
- ent_glyph *glyph;
- v4f colour;
- m4x3f mdl;
- f32 radius;
- }
- text_particles[6*4];
- u32 text_particle_count;
-}
-extern world_render;
-
-void world_render_init(void);
-
-void world_prerender( world_instance *world );
-void world_link_lighting_ub( world_instance *world, GLuint shader );
-void world_bind_position_texture( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-void world_bind_light_array( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-void world_bind_light_index( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-void render_world_position( world_instance *world, vg_camera *cam );
-void render_world_depth( world_instance *world, vg_camera *cam );
-void render_world( world_instance *world, vg_camera *cam,
- int stenciled, int viewing_from_gate,
- int with_water, int with_cubemaps );
-void render_world_cubemaps( world_instance *world );
-void bind_terrain_noise(void);
-void render_world_override( world_instance *world,
- world_instance *lighting_source,
- m4x3f mmdl,
- vg_camera *cam,
- ent_spawn *dest_spawn, v4f map_info );
-void render_world_gates( world_instance *world, vg_camera *cam );
-void imgui_world_light_edit( ui_context *ctx, world_instance *world );
-
-#define WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( WORLD, SHADER ) \
- world_link_lighting_ub( WORLD, _shader_##SHADER.id ); \
- world_bind_position_texture( WORLD, _shader_##SHADER.id, \
- _uniform_##SHADER##_g_world_depth, 2 ); \
- world_bind_light_array( WORLD, _shader_##SHADER.id, \
- _uniform_##SHADER##_uLightsArray, 3 ); \
- world_bind_light_index( WORLD, _shader_##SHADER.id, \
- _uniform_##SHADER##_uLightsIndex, 4 );
-
+++ /dev/null
-#pragma once
-
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved
- *
- * World routes
- */
-
-#include <time.h>
-#include "entity.h"
-#include "world_routes.h"
-#include "world_gate.h"
-#include "world_load.h"
-#include "network.h"
-
-#include "font.h"
-#include "gui.h"
-#include "steam.h"
-#include "network_msg.h"
-#include "network_common.h"
-
-#include "shaders/scene_route.h"
-#include "shaders/routeui.h"
-#include "ent_region.h"
-#include "scene_rigidbody.h"
-
-void world_routes_clear( world_instance *world )
-{
- for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
- route->active_checkpoint = 0xffff;
- }
-
- for( u32 i=0; i<mdl_arrcount( &world->ent_gate ); i++ ){
- ent_gate *rg = mdl_arritm( &world->ent_gate, i );
- rg->timing_version = 0;
- rg->timing_time = 0.0;
- }
-
- world_static.current_run_version += 4;
- world_static.last_use = 0.0;
-}
-
-static void world_routes_time_lap( world_instance *world, ent_route *route ){
- vg_info( "------- time lap %s -------\n",
- mdl_pstr(&world->meta,route->pstr_name) );
-
- double start_time = 0.0;
- u32 last_version=0;
- f64 last_time = 0.0;
- ent_checkpoint *last_cp = NULL;
-
- u32 valid_sections=0;
- int clean = !localplayer.rewinded_since_last_gate;
-
- for( u32 i=0; i<route->checkpoints_count; i++ ){
- u32 cpid = (i+route->active_checkpoint) % route->checkpoints_count;
- cpid += route->checkpoints_start;
-
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, cpid );
- ent_gate *rg = mdl_arritm( &world->ent_gate, cp->gate_index );
- rg = mdl_arritm( &world->ent_gate, rg->target );
-
- if( i == 1 ){
- route->timing_base = rg->timing_time;
- }
-
- if( i == 0 )
- start_time = rg->timing_time;
- else{
- if( last_version+1 == rg->timing_version ) valid_sections ++;
- else valid_sections = 0;
- }
-
- vg_info( "%u %f [%s]\n", rg->timing_version, rg->timing_time,
- i? ((rg->flags & k_ent_gate_clean_pass)? "CLEAN": " "):
- " N/A ");
-
- if( !(rg->flags & k_ent_gate_clean_pass) )
- clean = 0;
-
- last_version = rg->timing_version;
- last_time = rg->timing_time;
- last_cp = cp;
- }
-
- if( world_static.current_run_version == last_version+1 ){
- valid_sections ++;
-
- if( route->checkpoints_count == 1 ){
- route->timing_base = world_static.time;
- }
-
- f32 section = world_static.time - last_time;
- if( (section < last_cp->best_time) || (last_cp->best_time == 0.0f) ){
- last_cp->best_time = section;
- }
- }
- else valid_sections = 0;
-
- vg_info( "%u %f [%s]\n",
- world_static.current_run_version, world_static.time,
- !localplayer.rewinded_since_last_gate? "CLEAN": " " );
-
- if( valid_sections==route->checkpoints_count ){
- f64 lap_time = world_static.time - start_time;
-
- if( (route->best_laptime == 0.0) || (lap_time < route->best_laptime) ){
- route->best_laptime = lap_time;
- }
-
- route->flags |= k_ent_route_flag_achieve_silver;
- if( clean ) route->flags |= k_ent_route_flag_achieve_gold;
- ent_region_re_eval( world );
-
- /* for steam achievements. */
- if( route->anon.official_track_id != 0xffffffff ){
- struct track_info *ti = &track_infos[ route->anon.official_track_id ];
- if( ti->achievement_id ){
- steam_set_achievement( ti->achievement_id );
- steam_store_achievements();
- }
- }
-
- addon_alias *alias =
- &world_static.instance_addons[ world_static.active_instance ]->alias;
-
- char mod_uid[ ADDON_UID_MAX ];
- addon_alias_uid( alias, mod_uid );
- network_publish_laptime( mod_uid,
- mdl_pstr( &world->meta, route->pstr_name ),
- lap_time );
- }
-
- route->valid_checkpoints = valid_sections+1;
-
- vg_info( "valid sections: %u\n", valid_sections );
- vg_info( "----------------------------\n" );
-
- route->ui_residual = 1.0f;
- route->ui_residual_block_w = route->ui_first_block_width;
-}
-
-/*
- * When going through a gate this is called for bookkeeping purposes
- */
-void world_routes_activate_entry_gate( world_instance *world, ent_gate *rg )
-{
- world_static.last_use = world_static.time;
- ent_gate *dest = mdl_arritm( &world->ent_gate, rg->target );
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- u32 active_prev = route->active_checkpoint;
- route->active_checkpoint = 0xffff;
-
- for( u32 j=0; j<4; j++ ){
- if( dest->routes[j] == i ){
- for( u32 k=0; k<route->checkpoints_count; k++ ){
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint,
- route->checkpoints_start+k );
-
- ent_gate *gk = mdl_arritm( &world->ent_gate, cp->gate_index );
- gk = mdl_arritm( &world->ent_gate, gk->target );
- if( gk == dest ){
- route->active_checkpoint = k;
- world_routes_time_lap( world, route );
- break;
- }
- }
- break;
- }
- }
- }
-
- dest->timing_version = world_static.current_run_version;
- dest->timing_time = world_static.time;
-
- if( localplayer.rewinded_since_last_gate ){
- localplayer.rewinded_since_last_gate = 0;
- dest->flags &= ~k_ent_gate_clean_pass;
- }
- else
- dest->flags |= k_ent_gate_clean_pass;
-
- world_static.current_run_version ++;
-}
-
-/* draw lines along the paths */
-static void world_routes_debug( world_instance *world )
-{
- for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
- ent_route_node *rn = mdl_arritm(&world->ent_route_node,i);
- vg_line_point( rn->co, 0.25f, VG__WHITE );
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm(&world->ent_route, i);
-
- u32 colours[] = { 0xfff58142, 0xff42cbf5, 0xff42f56c, 0xfff542b3,
- 0xff5442f5 };
-
- u32 cc = 0xffcccccc;
- if( route->active_checkpoint != 0xffff ){
- cc = colours[i%VG_ARRAY_LEN(colours)];
- }
-
- for( int i=0; i<route->checkpoints_count; i++ ){
- int i0 = route->checkpoints_start+i,
- i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
-
- ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
- *c1 = mdl_arritm(&world->ent_checkpoint, i1);
-
- ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
- ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index );
-
- v3f p0, p1;
- v3_copy( start_gate->co[1], p0 );
-
- for( int j=0; j<c0->path_count; j ++ ){
- ent_path_index *index = mdl_arritm( &world->ent_path_index,
- c0->path_start+j );
-
- ent_route_node *rn = mdl_arritm( &world->ent_route_node,
- index->index );
-
- v3_copy( rn->co, p1 );
- vg_line( p0, p1, cc );
- v3_copy( p1, p0 );
- }
-
- v3_copy( end_gate->co[0], p1 );
- vg_line( p0, p1, cc );
- }
- }
-}
-
-
-static
-void world_routes_place_curve( world_instance *world, ent_route *route,
- v4f h[3], v3f n0, v3f n2, scene_context *scene )
-{
- float t;
- v3f p, pd;
- int last_valid=0;
-
- float total_length = 0.0f,
- travel_length = 0.0;
-
- v3f last;
- v3_copy( h[0], last );
- for( int it=0; it<128; it ++ ){
- t = (float)(it+1) * (1.0f/128.0f);
- eval_bezier3( h[0], h[1], h[2], t, p );
- total_length += v3_dist( p, last );
- v3_copy( p, last );
- }
-
- float patch_size = 4.0f,
- patch_count = ceilf( total_length / patch_size );
-
- t = 0.0f;
- v3_copy( h[0], last );
-
- for( int it=0; it<128; it ++ ){
- float const k_sample_dist = 0.0025f,
- k_line_width = 1.5f;
-
- eval_bezier3( h[0], h[1], h[2], t, p );
- eval_bezier3( h[0], h[1], h[2], t+k_sample_dist, pd );
-
- travel_length += v3_dist( p, last );
-
- float mod = k_sample_dist / v3_dist( p, pd );
-
- v3f v0,up, right;
-
- v3_muls( n0, -(1.0f-t), up );
- v3_muladds( up, n2, -t, up );
- v3_normalize( up );
-
- v3_sub( pd,p,v0 );
- v3_cross( up, v0, right );
- v3_normalize( right );
-
- float cur_x = (1.0f-t)*h[0][3] + t*h[2][3];
-
- v3f sc, sa, sb, down;
- v3_muladds( p, right, cur_x * k_line_width, sc );
- v3_muladds( sc, up, 1.5f, sc );
- v3_muladds( sc, right, k_line_width*0.95f, sa );
- v3_muladds( sc, right, 0.0f, sb );
- v3_muls( up, -1.0f, down );
-
- ray_hit ha, hb;
- ha.dist = 8.0f;
- hb.dist = 8.0f;
-
- int resa = ray_world( world, sa, down, &ha, k_material_flag_ghosts ),
- resb = ray_world( world, sb, down, &hb, k_material_flag_ghosts );
-
- if( resa && resb ){
- struct world_surface *surfa = ray_hit_surface( world, &ha ),
- *surfb = ray_hit_surface( world, &hb );
-
- if( (surfa->info.flags & k_material_flag_skate_target) &&
- (surfb->info.flags & k_material_flag_skate_target) )
- {
- scene_vert va, vb;
-
- float gap = vg_fractf(cur_x*0.5f)*0.02f;
-
- v3_muladds( ha.pos, up, 0.06f+gap, va.co );
- v3_muladds( hb.pos, up, 0.06f+gap, vb.co );
-
- scene_vert_pack_norm( &va, up, 0.0f );
- scene_vert_pack_norm( &vb, up, 0.0f );
-
- float t1 = (travel_length / total_length) * patch_count;
- va.uv[0] = t1;
- va.uv[1] = 0.0f;
- vb.uv[0] = t1;
- vb.uv[1] = 1.0f;
-
- scene_push_vert( scene, &va );
- scene_push_vert( scene, &vb );
-
- if( last_valid ){
- /* Connect them with triangles */
- scene_push_tri( scene, (u32[3]){
- last_valid+0-2, last_valid+1-2, last_valid+2-2} );
- scene_push_tri( scene, (u32[3]){
- last_valid+1-2, last_valid+3-2, last_valid+2-2} );
- }
-
- last_valid = scene->vertex_count;
- }
- else
- last_valid = 0;
- }
- else
- last_valid = 0;
-
- if( t == 1.0f )
- return;
-
- t += 1.0f*mod;
- if( t > 1.0f )
- t = 1.0f;
-
- v3_copy( p, last );
- }
-}
-
-static void world_routes_gen_meshes( world_instance *world, u32 route_id,
- scene_context *sc )
-{
- ent_route *route = mdl_arritm( &world->ent_route, route_id );
- u8 colour[4];
- colour[0] = route->colour[0] * 255.0f;
- colour[1] = route->colour[1] * 255.0f;
- colour[2] = route->colour[2] * 255.0f;
- colour[3] = route->colour[3] * 255.0f;
-
- u32 last_valid = 0;
-
- for( int i=0; i<route->checkpoints_count; i++ ){
- int i0 = route->checkpoints_start+i,
- i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
-
- ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
- *c1 = mdl_arritm(&world->ent_checkpoint, i1);
-
- ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
- start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
-
- ent_gate *end_gate = mdl_arritm( &world->ent_gate, c1->gate_index ),
- *collector = mdl_arritm( &world->ent_gate, end_gate->target );
-
- v4f p[3];
-
- v3_add( (v3f){0.0f,0.1f,0.0f}, start_gate->co[0], p[0] );
- p[0][3] = start_gate->ref_count;
- p[0][3] -= (float)start_gate->route_count * 0.5f;
- start_gate->ref_count ++;
-
- if( !c0->path_count )
- continue;
-
- /* this is so that we get nice flow through the gates */
- v3f temp_alignments[2];
- ent_gate *both[] = { start_gate, end_gate };
-
- for( int j=0; j<2; j++ ){
- int pi = c0->path_start + ((j==1)? c0->path_count-1: 0);
-
- ent_path_index *index = mdl_arritm( &world->ent_path_index, pi );
- ent_route_node *rn = mdl_arritm( &world->ent_route_node,
- index->index );
- v3f v0;
- v3_sub( rn->co, both[j]->co[0], v0 );
- float d = v3_dot( v0, both[j]->to_world[2] );
-
- v3_muladds( both[j]->co[0], both[j]->to_world[2], d,
- temp_alignments[j] );
- v3_add( (v3f){0.0f,0.1f,0.0f}, temp_alignments[j], temp_alignments[j]);
- }
-
-
- for( int j=0; j<c0->path_count; j ++ ){
- ent_path_index *index = mdl_arritm( &world->ent_path_index,
- c0->path_start+j );
- ent_route_node *rn = mdl_arritm( &world->ent_route_node,
- index->index );
- if( j==0 || j==c0->path_count-1 )
- if( j == 0 )
- v3_copy( temp_alignments[0], p[1] );
- else
- v3_copy( temp_alignments[1], p[1] );
- else
- v3_copy( rn->co, p[1] );
-
- p[1][3] = rn->ref_count;
- p[1][3] -= (float)rn->ref_total * 0.5f;
- rn->ref_count ++;
-
- if( j+1 < c0->path_count ){
- index = mdl_arritm( &world->ent_path_index,
- c0->path_start+j+1 );
- rn = mdl_arritm( &world->ent_route_node, index->index );
-
- if( j+1 == c0->path_count-1 )
- v3_lerp( p[1], temp_alignments[1], 0.5f, p[2] );
- else
- v3_lerp( p[1], rn->co, 0.5f, p[2] );
-
- p[2][3] = rn->ref_count;
- p[2][3] -= (float)rn->ref_total * 0.5f;
- }
- else{
- v3_copy( end_gate->co[0], p[2] );
- v3_add( (v3f){0.0f,0.1f,0.0f}, p[2], p[2] );
- p[2][3] = collector->ref_count;
-
- if( i == route->checkpoints_count-1)
- p[2][3] -= 1.0f;
-
- p[2][3] -= (float)collector->route_count * 0.5f;
- //collector->ref_count ++;
- }
-
- /* p0,p1,p2 bezier patch is complete
- * --------------------------------------*/
- v3f surf0, surf2, n0, n2;
-
- if( bh_closest_point( world->geo_bh, p[0], surf0, 5.0f ) == -1 )
- v3_add( (v3f){0.0f,-0.1f,0.0f}, p[0], surf0 );
-
- if( bh_closest_point( world->geo_bh, p[2], surf2, 5.0f ) == -1 )
- v3_add( (v3f){0.0f,-0.1f,0.0f}, p[2], surf2 );
-
- v3_sub( surf0, p[0], n0 );
- v3_sub( surf2, p[2], n2 );
- v3_normalize( n0 );
- v3_normalize( n2 );
-
- world_routes_place_curve( world, route, p, n0, n2, sc );
-
- /* --- */
- v4_copy( p[2], p[0] );
- }
- }
-
- scene_copy_slice( sc, &route->sm );
-}
-
-struct world_surface *world_tri_index_surface( world_instance *world,
- u32 index );
-
-/*
- * Create the strips of colour that run through the world along course paths
- */
-void world_gen_routes_generate( u32 instance_id )
-{
- world_instance *world = &world_static.instances[ instance_id ];
- vg_info( "Generating route meshes\n" );
- vg_async_stall();
-
- vg_async_item *call_scene = scene_alloc_async( &world->scene_lines,
- &world->mesh_route_lines,
- 200000, 300000 );
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, i );
- gate->ref_count = 0;
- gate->route_count = 0;
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route_node); i++ ){
- ent_route_node *rn = mdl_arritm( &world->ent_route_node, i );
- rn->ref_count = 0;
- rn->ref_total = 0;
- }
-
- for( u32 k=0; k<mdl_arrcount(&world->ent_route); k++ ){
- ent_route *route = mdl_arritm( &world->ent_route, k );
-
- for( int i=0; i<route->checkpoints_count; i++ ){
- int i0 = route->checkpoints_start+i,
- i1 = route->checkpoints_start+((i+1)%route->checkpoints_count);
-
- ent_checkpoint *c0 = mdl_arritm(&world->ent_checkpoint, i0),
- *c1 = mdl_arritm(&world->ent_checkpoint, i1);
-
- ent_gate *start_gate = mdl_arritm( &world->ent_gate, c0->gate_index );
- start_gate = mdl_arritm( &world->ent_gate, start_gate->target );
- start_gate->route_count ++;
-
- if( !c0->path_count )
- continue;
-
- for( int j=0; j<c0->path_count; j ++ ){
- ent_path_index *index = mdl_arritm( &world->ent_path_index,
- c0->path_start+j );
- ent_route_node *rn = mdl_arritm( &world->ent_route_node,
- index->index );
- rn->ref_total ++;
- }
- }
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- world_routes_gen_meshes( world, i, &world->scene_lines );
- }
-
- vg_async_dispatch( call_scene, async_scene_upload );
- world_routes_clear( world );
-}
-
-/* load all routes from model header */
-void world_gen_routes_ent_init( world_instance *world )
-{
- vg_info( "Initializing routes\n" );
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, i );
- for( u32 j=0; j<4; j++ ){
- gate->routes[j] = 0xffff;
- }
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm(&world->ent_route,i);
- mdl_transform_m4x3( &route->anon.transform, route->board_transform );
-
- route->flags = 0x00;
- route->best_laptime = 0.0;
- route->ui_stopper = 0.0f;
- route->ui_residual = 0.0f;
-
- if( mdl_arrcount(&world->ent_region) )
- route->flags |= k_ent_route_flag_out_of_zone;
-
- route->anon.official_track_id = 0xffffffff;
- for( u32 j=0; j<VG_ARRAY_LEN(track_infos); j ++ ){
- if( !strcmp(track_infos[j].name,
- mdl_pstr(&world->meta,route->pstr_name))){
- route->anon.official_track_id = j;
- }
- }
-
- for( u32 j=0; j<route->checkpoints_count; j++ ){
- u32 id = route->checkpoints_start + j;
- ent_checkpoint *cp = mdl_arritm(&world->ent_checkpoint,id);
-
- ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
-
- for( u32 k=0; k<4; k++ ){
- if( gate->routes[k] == 0xffff ){
- gate->routes[k] = i;
- break;
- }
- }
-
- if( (gate->flags & k_ent_gate_linked) &
- !(gate->flags & k_ent_gate_nonlocal) ){
- gate = mdl_arritm(&world->ent_gate, gate->target );
-
- for( u32 k=0; k<4; k++ ){
- if( gate->routes[k] == i ){
- vg_error( "already assigned route to gate\n" );
- break;
- }
- if( gate->routes[k] == 0xffff ){
- gate->routes[k] = i;
- break;
- }
- }
- }
- }
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_gate); i++ ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, i );
- }
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_checkpoint); i++ ){
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, i );
- cp->best_time = 0.0;
- }
-
- world_routes_clear( world );
-}
-
-void world_routes_recv_scoreboard( world_instance *world,
- vg_msg *body, u32 route_id,
- enum request_status status )
-{
- if( route_id >= mdl_arrcount( &world->ent_route ) ){
- vg_error( "Scoreboard route_id out of range (%u)\n", route_id );
- return;
- }
-
- struct leaderboard_cache *board = &world->leaderboard_cache[ route_id ];
- board->status = status;
-
- if( body == NULL ){
- board->data_len = 0;
- return;
- }
-
- if( body->max > NETWORK_REQUEST_MAX ){
- vg_error( "Scoreboard leaderboard too big (%u>%u)\n", body->max,
- NETWORK_REQUEST_MAX );
- return;
- }
-
- memcpy( board->data, body->buf, body->max );
- board->data_len = body->max;
-}
-
-/*
- * -----------------------------------------------------------------------------
- * Events
- * -----------------------------------------------------------------------------
- */
-
-void world_routes_init(void)
-{
- world_static.current_run_version = 200;
- world_static.time = 300.0;
- world_static.last_use = 0.0;
-}
-
-void world_routes_update( world_instance *world )
-{
- world_static.time += vg.time_delta;
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- int target = route->active_checkpoint == 0xffff? 0: 1;
- route->factive = vg_lerpf( route->factive, target,
- 0.6f*vg.time_frame_delta );
- }
-
- for( u32 i=0; i<world_render.text_particle_count; i++ ){
- struct text_particle *particle = &world_render.text_particles[i];
- //rb_object_debug( &particle->obj, VG__RED );
- }
-}
-
-void world_routes_fixedupdate( world_instance *world )
-{
- rb_solver_reset();
-
- rigidbody _null = {0};
- _null.inv_mass = 0.0f;
- m3x3_zero( _null.iI );
-
- for( u32 i=0; i<world_render.text_particle_count; i++ ){
- struct text_particle *particle = &world_render.text_particles[i];
-
- if( rb_global_has_space() ){
- rb_ct *buf = rb_global_buffer();
-
- int l = rb_sphere__scene( particle->rb.to_world,
- particle->radius,
- NULL, world->geo_bh, buf,
- k_material_flag_ghosts );
-
- for( int j=0; j<l; j++ ){
- buf[j].rba = &particle->rb;
- buf[j].rbb = &_null;
- }
-
- rb_contact_count += l;
- }
- }
-
- rb_presolve_contacts( rb_contact_buffer,
- vg.time_fixed_delta, rb_contact_count );
-
- for( int i=0; i<rb_contact_count; i++ ){
- rb_contact_restitution( rb_contact_buffer+i, vg_randf64(&vg.rand) );
- }
-
- for( int i=0; i<6; i++ ){
- rb_solve_contacts( rb_contact_buffer, rb_contact_count );
- }
-
- for( u32 i=0; i<world_render.text_particle_count; i++ ){
- struct text_particle *particle = &world_render.text_particles[i];
- rb_iter( &particle->rb );
- }
-
- for( u32 i=0; i<world_render.text_particle_count; i++ ){
- struct text_particle *particle = &world_render.text_particles[i];
- rb_update_matrices( &particle->rb );
- }
-}
-
-void bind_terrain_noise(void);
-void world_bind_light_array( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-void world_bind_light_index( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-
-void world_routes_update_timer_texts( world_instance *world )
-{
- world_render.timer_text_count = 0;
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- if( route->active_checkpoint != 0xffff ){
- u32 next = route->active_checkpoint+1;
- next = next % route->checkpoints_count;
- next += route->checkpoints_start;
-
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
- ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
- ent_gate *dest = mdl_arritm( &world->ent_gate, gate->target );
-
- u32 j=0;
- for( ; j<4; j++ ){
- if( dest->routes[j] == i ){
- break;
- }
- }
-
- float h0 = 0.8f,
- h1 = 1.2f,
- depth = 0.4f,
- size = 0.4f;
-
- struct timer_text *text =
- &world_render.timer_texts[ world_render.timer_text_count ++ ];
-
- text->gate = gate;
- text->route = route;
-
- vg_str str;
- vg_strnull( &str, text->text, sizeof(text->text) );
-
- if( route->valid_checkpoints >= route->checkpoints_count )
- {
- double lap_time = world_static.time - route->timing_base,
- time_centiseconds = lap_time * 100.0;
-
- if( time_centiseconds > (float)0xfffe ) time_centiseconds = 0.0;
-
- u16 centiseconds = time_centiseconds,
- seconds = centiseconds / 100,
- minutes = seconds / 60;
-
- centiseconds %= 100;
- seconds %= 60;
- minutes %= 60;
-
- if( minutes > 9 )
- minutes = 9;
-
- if( minutes )
- {
- vg_strcati32r( &str, minutes, 1, ' ' );
- vg_strcatch( &str, ':' );
- }
-
- if( seconds >= 10 || minutes )
- {
- vg_strcati32r( &str, seconds, 2, '0' );
- }
- else
- {
- vg_strcati32r( &str, seconds, 1, '0' );
- }
-
- vg_strcatch( &str, '.' );
- vg_strcati32r( &str, centiseconds, 1, '0' );
- }
- else
- {
- vg_strcati32r( &str, route->valid_checkpoints, 1, ' ' );
- vg_strcatch( &str, '/' );
- vg_strcati32r( &str, route->checkpoints_count + 1, 1, ' ' );
- }
-
- gui_font3d.font = &gui.font;
- float align_r = font3d_string_width( 0, text->text );
- align_r *= size;
-
- v3f positions[] = {
- { -0.92f, h0, depth },
- { 0.92f - align_r, h0, depth },
- { -0.92f, h1, depth },
- { 0.92f - align_r, h1, depth },
- };
-
- if( dest->route_count == 1 ){
- positions[0][0] = -align_r*0.5f;
- positions[0][1] = h1;
- }
-
- m4x3f mmdl;
- ent_gate_get_mdl_mtx( gate, mmdl );
-
- m3x3_copy( mmdl, text->transform );
- float ratio = v3_length(text->transform[0]) /
- v3_length(text->transform[1]);
-
- m3x3_scale( text->transform, (v3f){ size, size*ratio, 0.1f } );
- m4x3_mulv( mmdl, positions[j], text->transform[3] );
- }
- }
-}
-
-void world_routes_fracture( world_instance *world, ent_gate *gate,
- v3f imp_co, v3f imp_v )
-{
- world_render.text_particle_count = 0;
-
- for( u32 i=0; i<world_render.timer_text_count; i++ ){
- struct timer_text *text = &world_render.timer_texts[i];
-
- if( text->gate != gate ) continue;
-
- m4x3f transform;
- m4x3_mul( gate->transport, text->transform, transform );
-
- v3f co, s;
- v4f q;
- m4x3_decompose( transform, co, q, s );
-
- v3f offset;
- v3_zero( offset );
-
- v4f colour;
- float brightness = 0.3f + world->ub_lighting.g_day_phase;
- v3_muls( text->route->colour, brightness, colour );
- colour[3] = 1.0f-text->route->factive;
-
- for( u32 j=0;; j++ ){
- char c = text->text[j];
- if( !c ) break;
-
- ent_glyph *glyph = font3d_glyph( &gui.font, 0, c );
- if( !glyph ) continue;
-
- if( c >= (u32)'0' && c <= (u32)'9' && glyph->indice_count ){
- struct text_particle *particle =
- &world_render.text_particles[world_render.text_particle_count++];
-
- particle->glyph = glyph;
- v4_copy( colour, particle->colour );
-
- v3f origin;
- v2_muls( glyph->size, 0.5f, origin );
- origin[2] = -0.5f;
-
- v3f world_co;
-
- v3_add( offset, origin, world_co );
- m4x3_mulv( transform, world_co, world_co );
-
-
- m3x3_identity( particle->mlocal );
- m3x3_scale( particle->mlocal, s );
- origin[2] *= s[2];
- v3_muls( origin, -1.0f, particle->mlocal[3] );
-
- v3_copy( world_co, particle->rb.co );
- v3_muls( imp_v, 1.0f+vg_randf64(&vg.rand), particle->rb.v );
- particle->rb.v[1] += 2.0f;
-
- v4_copy( q, particle->rb.q );
- particle->rb.w[0] = vg_randf64(&vg.rand)*2.0f-1.0f;
- particle->rb.w[1] = vg_randf64(&vg.rand)*2.0f-1.0f;
- particle->rb.w[2] = vg_randf64(&vg.rand)*2.0f-1.0f;
-
- f32 r = vg_maxf( s[0]*glyph->size[0], s[1]*glyph->size[1] )*0.5f;
- particle->radius = r*0.6f;
- rb_setbody_sphere( &particle->rb, particle->radius, 1.0f, 1.0f );
- }
- offset[0] += glyph->size[0];
- }
- }
-}
-
-static void render_gate_markers( m4x3f world_mmdl, int run_id, ent_gate *gate ){
- for( u32 j=0; j<4; j++ ){
- if( gate->routes[j] == run_id ){
- m4x3f mmdl;
- m4x3_copy( gate->to_world, mmdl );
- m3x3_scale( mmdl, (v3f){ gate->dimensions[0],
- gate->dimensions[1], 1.0f } );
-
- m4x3_mul( world_mmdl, mmdl, mmdl );
- shader_model_gate_uMdl( mmdl );
- mdl_draw_submesh( &world_gates.sm_marker[j] );
- break;
- }
- }
-}
-
-void render_world_routes( world_instance *world,
- world_instance *host_world,
- m4x3f mmdl, vg_camera *cam,
- int viewing_from_gate, int viewing_from_hub )
-{
- shader_scene_route_use();
- shader_scene_route_uTexGarbage(0);
- world_link_lighting_ub( host_world, _shader_scene_route.id );
- world_bind_position_texture( host_world, _shader_scene_route.id,
- _uniform_scene_route_g_world_depth, 2 );
- world_bind_light_array( host_world, _shader_scene_route.id,
- _uniform_scene_route_uLightsArray, 3 );
- world_bind_light_index( host_world, _shader_scene_route.id,
- _uniform_scene_route_uLightsIndex, 4 );
- bind_terrain_noise();
-
- shader_scene_route_uPv( cam->mtx.pv );
-
- if( viewing_from_hub ){
- m4x4f m4mdl, pvm;
- m4x3_expand( mmdl, m4mdl );
- m4x4_mul( cam->mtx_prev.pv, m4mdl, pvm );
- shader_scene_route_uMdl( mmdl );
- shader_scene_route_uPvmPrev( pvm );
-
- m3x3f mnormal;
- m3x3_inv( mmdl, mnormal );
- m3x3_transpose( mnormal, mnormal );
- v3_normalize( mnormal[0] );
- v3_normalize( mnormal[1] );
- v3_normalize( mnormal[2] );
- shader_scene_route_uNormalMtx( mnormal );
- }
- else{
- shader_scene_route_uMdl( mmdl );
- shader_scene_route_uPvmPrev( cam->mtx_prev.pv );
- m3x3f ident;
- m3x3_identity( ident );
- shader_scene_route_uNormalMtx( ident );
- }
-
- shader_scene_route_uCamera( cam->transform[3] );
-
- mesh_bind( &world->mesh_route_lines );
-
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- f32 t = viewing_from_hub? 1.0f: route->factive;
-
- v4f colour;
- v3_lerp( (v3f){0.7f,0.7f,0.7f}, route->colour, t, colour );
- colour[3] = t*0.2f;
-
- shader_scene_route_uColour( colour );
- mdl_draw_submesh( &route->sm );
- }
-
- /* timers
- * ---------------------------------------------------- */
- if( !viewing_from_gate && !viewing_from_hub ){
- font3d_bind( &gui.font, k_font_shader_default, 0, world, cam );
-
- for( u32 i=0; i<world_render.timer_text_count; i++ ){
- struct timer_text *text = &world_render.timer_texts[i];
-
- v4f colour;
- float brightness = 0.3f + world->ub_lighting.g_day_phase;
- v3_muls( text->route->colour, brightness, colour );
- colour[3] = 1.0f-text->route->factive;
-
- shader_model_font_uColour( colour );
- font3d_simple_draw( 0, text->text, cam, text->transform );
- }
-
- shader_model_font_uOffset( (v4f){0.0f,0.0f,0.0f,1.0f} );
-
- for( u32 i=0; i<world_render.text_particle_count; i++ ){
- struct text_particle *particle = &world_render.text_particles[i];
-
- m4x4f prev_mtx;
-
- m4x3_expand( particle->mdl, prev_mtx );
- m4x4_mul( cam->mtx_prev.pv, prev_mtx, prev_mtx );
-
- shader_model_font_uPvmPrev( prev_mtx );
-
- v4f q;
- m4x3f model;
- rb_extrapolate( &particle->rb, model[3], q );
- q_m3x3( q, model );
-
- m4x3_mul( model, particle->mlocal, particle->mdl );
- shader_model_font_uMdl( particle->mdl );
- shader_model_font_uColour( particle->colour );
-
- mesh_drawn( particle->glyph->indice_start,
- particle->glyph->indice_count );
- }
- }
-
- /* gate markers
- * ---------------------------------------------------- */
-
- shader_model_gate_use();
- shader_model_gate_uPv( cam->mtx.pv );
- shader_model_gate_uCam( cam->pos );
- shader_model_gate_uTime( vg.time*0.25f );
- shader_model_gate_uInvRes( (v2f){
- 1.0f / (float)vg.window_x,
- 1.0f / (float)vg.window_y });
-
- mesh_bind( &world_gates.mesh );
-
- /* skip writing into the motion vectors for this */
- glDrawBuffers( 1, (GLenum[]){ GL_COLOR_ATTACHMENT0 } );
- glDisable( GL_CULL_FACE );
-
- if( viewing_from_hub ){
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- v4f colour;
- v3_muls( route->colour, 1.6666f, colour );
- colour[3] = 0.0f;
-
- shader_model_gate_uColour( colour );
-
- for( u32 j=0; j<mdl_arrcount(&world->ent_gate); j ++ ){
- ent_gate *gate = mdl_arritm( &world->ent_gate, j );
- if( !(gate->flags & k_ent_gate_nonlocal) )
- render_gate_markers( mmdl, i, gate );
- }
- }
- }
- else{
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
-
- if( route->active_checkpoint != 0xffff ){
- v4f colour;
- float brightness = 0.3f + world->ub_lighting.g_day_phase;
- v3_muls( route->colour, brightness, colour );
- colour[3] = 1.0f-route->factive;
-
- shader_model_gate_uColour( colour );
-
- u32 next = route->active_checkpoint+1+viewing_from_gate;
- next = next % route->checkpoints_count;
- next += route->checkpoints_start;
-
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, next );
- ent_gate *gate = mdl_arritm( &world->ent_gate, cp->gate_index );
- render_gate_markers( mmdl, i, gate );
- }
- }
- }
- glEnable( GL_CULL_FACE );
- glDrawBuffers( 2, (GLenum[]){ GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 } );
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-#include "vg/vg_camera.h"
-#include "vg/vg_msg.h"
-#include "world.h"
-#include "network_msg.h"
-
-void world_routes_init(void);
-void world_routes_fracture( world_instance *world, ent_gate *gate,
- v3f imp_co, v3f imp_v );
-void world_routes_activate_entry_gate( world_instance *world,
- ent_gate *rg );
-void render_world_routes( world_instance *world,
- world_instance *host_world,
- m4x3f mmdl, vg_camera *cam,
- int viewing_from_gate, int viewing_from_hub );
-
-void world_gen_routes_ent_init( world_instance *world );
-void world_gen_routes_generate( u32 instance_id );
-void world_routes_update_timer_texts( world_instance *world );
-void world_routes_update( world_instance *world );
-void world_routes_fixedupdate( world_instance *world );
-void world_routes_clear( world_instance *world );
-void world_routes_recv_scoreboard( world_instance *world,
- vg_msg *body, u32 route_id,
- enum request_status status );
+++ /dev/null
-#include "skaterift.h"
-#include "world_routes_ui.h"
-#include "world_routes.h"
-#include "player.h"
-
-static u32 v4_rgba( v4f colour ){
- u32 r = vg_minf(1.0f,colour[0])*255.0f,
- g = vg_minf(1.0f,colour[1])*255.0f,
- b = vg_minf(1.0f,colour[2])*255.0f,
- a = vg_minf(1.0f,colour[3])*255.0f;
-
- return r | (g<<8) | (b<<16) | (a<<24);
-}
-
-static void ent_route_imgui( ui_context *ctx,
- world_instance *world, ent_route *route,
- ui_point inout_cursor ){
- if( route->flags & k_ent_route_flag_out_of_zone )
- return;
-
- u32 last_version=0;
- f64 last_time = 0.0;
- ent_checkpoint *last_cp = NULL;
-
- u32 valid_sections=0;
-
- struct time_block{
- f32 length, best;
- int clean;
- }
- blocks[ route->checkpoints_count ];
-
- for( u32 i=0; i<route->checkpoints_count; i++ ){
- u32 cpid = i+route->active_checkpoint+1;
- cpid %= route->checkpoints_count;
- cpid += route->checkpoints_start;
-
- ent_checkpoint *cp = mdl_arritm( &world->ent_checkpoint, cpid );
- ent_gate *rg = mdl_arritm( &world->ent_gate, cp->gate_index );
- rg = mdl_arritm( &world->ent_gate, rg->target );
-
- if( last_version+1 == rg->timing_version ) {
- struct time_block *block = &blocks[ valid_sections ++ ];
- block->clean = (rg->flags & k_ent_gate_clean_pass)? 1: 0;
- block->length = rg->timing_time - last_time;
- block->best = last_cp? last_cp->best_time: 0.0f;
- }
- else valid_sections = 0;
-
- last_version = rg->timing_version;
- last_time = rg->timing_time;
- last_cp = cp;
- }
-
- if( last_version+1 == world_static.current_run_version ){
- struct time_block *block = &blocks[ valid_sections ++ ];
- block->clean = localplayer.rewinded_since_last_gate? 0: 1;
- block->length = world_static.time - last_time;
- block->best = last_cp->best_time;
- }
- else
- valid_sections = 0;
-
- u32 colour = v4_rgba( route->colour ) | 0xff000000;
-
- ui_px x = 0,
- h = route->factive * 16.0f,
- base = inout_cursor[0];//(f32)vg.window_x*0.5f - route->ui_stopper;
-
- if( route->ui_residual > 0.0f )
- {
- ui_px w = route->ui_residual_block_w,
- total = w + 4;
-
- f32 t = vg_smoothstepf(1.0f-route->ui_residual);
-
- x -= (f32)total * t;
-
- ui_rect rect = { base+x, inout_cursor[1], w, h };
-
- v4f fadecolour;
- v4_copy( route->colour, fadecolour );
- fadecolour[3] *= route->ui_residual;
-
- ui_fill( ctx, rect, v4_rgba(fadecolour) );
-
- x += total;
- }
-
- int got_first = 0;
-
- for( u32 i=0; i<valid_sections; i ++ )
- {
- struct time_block *block = &blocks[ i ];
- ui_px w = 20 + (block->length * 6.0f);
- ui_rect rect = { base+x, inout_cursor[1], w, h };
- ui_fill( ctx, rect, colour );
-
- if( block->clean )
- ui_outline( ctx, rect, 1, 0xff00ffff, 0 );
-
- if( block->best != 0.0f )
- {
- char buf[32];
- vg_str str;
- vg_strnull( &str, buf, 32 );
-
- f32 diff = block->length - block->best,
- as = fabsf(diff),
- s = floorf( as ),
- ds = floorf( vg_fractf( as ) * 10.0f );
-
- if( (block->best != 0.0f) && (fabsf(diff) > 0.001f) )
- {
- if( diff > 0.0f )
- vg_strcatch( &str, '+' );
- else
- vg_strcatch( &str, '-' );
-
- vg_strcati32( &str, s );
- vg_strcatch( &str, '.' );
- vg_strcati32( &str, ds );
-
- ui_text( ctx, rect, buf, 1, k_ui_align_middle_center, 0 );
- }
- }
-
- x += w + 4;
-
- if( !got_first ){
- route->ui_first_block_width = w;
- got_first = 1;
- }
- }
-
- for( u32 i=0; i<route->checkpoints_count-valid_sections; i++ )
- {
- struct time_block *block = &blocks[ i ];
-
- ui_px w = 20;
- ui_rect rect = { base+x, inout_cursor[1], w, h };
- ui_outline( ctx, rect, -1, colour, 0 );
- x += w + 4;
-
- if( !got_first )
- {
- route->ui_first_block_width = w;
- got_first = 1;
- }
- }
-
- inout_cursor[1] += h + 4;
-
- vg_slewf( &route->ui_residual, 0.0f, vg.time_frame_delta );
- route->ui_stopper = vg_lerpf( route->ui_stopper, (f32)x*0.5f,
- vg.time_frame_delta );
-}
-
-void world_routes_imgui( ui_context *ctx, world_instance *world )
-{
- if( skaterift.activity == k_skaterift_menu ) return;
-
- ui_point cursor = { 4, 4 };
- for( u32 i=0; i<mdl_arrcount(&world->ent_route); i++ )
- {
- ent_route_imgui( ctx, world, mdl_arritm( &world->ent_route, i ), cursor );
- }
-}
+++ /dev/null
-#pragma once
-#include "world_routes.h"
-
-struct route_ui{};
-void world_routes_imgui( ui_context *ctx, world_instance *world );
+++ /dev/null
-#ifndef SFD_C
-#define SFD_C
-
-#include "world_sfd.h"
-#include "shaders/scene_scoretext.h"
-#include "shaders/scene_vertex_blend.h"
-#include "network.h"
-#include "entity.h"
-#include "network_common.h"
-#include "world_routes.h"
-
-struct world_sfd world_sfd;
-
-static f32 sfd_encode_glyph( char c ){
- int value = 0;
- if( c >= 'a' && c <= 'z' )
- value = c-'a'+11;
- else if( c >= '0' && c <= '9' )
- value = c-'0'+1;
- else if( c >= 'A' && c <= 'Z' )
- value = c-'A'+11;
- else if( c >= '\x01' && c <= '\x01'+10 )
- value = 63-c;
- else{
- int base = 11+26;
-
- switch( c ){
- case '!': value=base+0; break;
- case '?': value=base+1; break;
- case ',': value=base+2; break;
- case '.': value=base+3; break;
- case '#': value=base+4; break;
- case '$': value=base+5; break;
- case '%': value=base+6; break;
- case '*': value=base+7; break;
- case '+': value=base+8; break;
- case '-': value=base+9; break;
- case '/': value=base+10; break;
- case ':': value=base+11; break;
- default: value=0; break;
- }
- }
-
- return (float)value;
-}
-
-static void sfd_clear( u32 row ){
- u32 row_h = world_sfd.h -1 -row;
- for( int i=0; i<world_sfd.w; i++ ){
- u32 idx = (world_sfd.w*row_h + i) * 2;
- world_sfd.buffer[idx] = 0.0f;
- }
-}
-
-void sfd_encode( v2i co, const char *str, enum world_sfd_align align )
-{
- i32 row_h = world_sfd.h -1 -co[1];
- i32 offset_x = 0;
-
- i32 w = VG_MIN( strlen(str), world_sfd.w );
- if( align == k_world_sfd_center )
- offset_x = (world_sfd.w - w) / 2;
- else if( align == k_world_sfd_right )
- offset_x = world_sfd.w - w;
- else
- offset_x = co[0];
-
- for( i32 i=0; i<world_sfd.w; i++ ){
- i32 u = i + offset_x,
- idx = (world_sfd.w*row_h + u) * 2;
-
- if( (u >= world_sfd.w) || (u < 0) )
- continue;
-
- if( !str[i] )
- return;
-
- world_sfd.buffer[idx] = sfd_encode_glyph( str[i] );
- }
-}
-
-void world_sfd_compile_scores( struct leaderboard_cache *board,
- const char *title )
-{
- for( u32 i=0; i<13; i++ )
- sfd_clear(i);
-
- sfd_encode( (v2i){0,0}, title, k_world_sfd_left );
-
- if( !board ){
- sfd_encode( (v2i){-1,4}, "Error out of range", k_world_sfd_center );
- return;
- }
-
- if( !network_connected() ){
- sfd_encode( (v2i){-1,0}, "Offline", k_world_sfd_right );
- return;
- }
-
- if( board->status == k_request_status_not_found ){
- sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center );
- return;
- }
-
- if( board->status != k_request_status_ok ){
- char buf[32];
- vg_str s;
- vg_strnull( &s, buf, 32 );
- vg_strcat( &s, "Error: " );
- vg_strcati32( &s, board->status );
- sfd_encode( (v2i){-1,4}, buf, k_world_sfd_center );
- return;
- }
-
- vg_msg body;
- vg_msg_init( &body, board->data, board->data_len );
-
- const char *alias = "rows";
-
- if( world_sfd.view_weekly ){
- alias = "rows_weekly";
- sfd_encode( (v2i){-1,0}, "Weekly", k_world_sfd_right );
- }
- else {
- sfd_encode( (v2i){-1,0}, "All-Time", k_world_sfd_right );
- }
-
- u32 l = 1;
- if( vg_msg_seekframe( &body, alias ) ){
- while( vg_msg_seekframe( &body, NULL ) ){
- /* name */
- const char *username = vg_msg_getkvstr( &body, "username" );
-
- char buf[100];
- vg_str str;
- vg_strnull( &str, buf, 100 );
- vg_strcati32( &str, l );
- vg_strcat( &str, " " );
-
- if( username )
- vg_strcat( &str, username );
- else
- vg_strcat( &str, "??????" );
-
- sfd_encode( (v2i){0,l}, str.buffer, k_world_sfd_left );
-
- /* time */
- vg_strnull( &str, buf, 100 );
-
- u32 centiseconds;
- vg_msg_getkvintg( &body, "time", k_vg_msg_i32, ¢iseconds, NULL );
-
- i32 seconds = centiseconds / 100,
- minutes = seconds / 60;
-
- centiseconds %= 100;
- seconds %= 60;
- minutes %= 60;
- if( minutes > 9 ) vg_strcat( &str, "?" );
- else vg_strcati32( &str, minutes );
- vg_strcat( &str, ":" );
- vg_strcati32r( &str, seconds, 2, '0' );
- vg_strcat( &str, "." );
- vg_strcati32r( &str, centiseconds, 2, '0' );
- sfd_encode( (v2i){-1,l}, str.buffer, k_world_sfd_right );
- l ++;
-
- vg_msg_skip_frame( &body );
- }
- }
- else {
- sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center );
- }
-}
-
-void world_sfd_compile_active_scores(void)
-{
- world_instance *world = world_current_instance();
-
- struct leaderboard_cache *board = NULL;
- const char *name = "Out of range";
-
- if( world_sfd.active_route_board < mdl_arrcount( &world->ent_route ) ){
- board = &world->leaderboard_cache[ world_sfd.active_route_board ];
- ent_route *route = mdl_arritm( &world->ent_route,
- world_sfd.active_route_board );
- name = mdl_pstr( &world->meta, route->pstr_name );
- }
-
- world_sfd_compile_scores( board, name );
-}
-
-void world_sfd_update( world_instance *world, v3f pos )
-{
- if( mdl_arrcount( &world->ent_route ) ){
- u32 closest = 0;
- float min_dist = INFINITY;
-
- for( u32 i=0; i<mdl_arrcount( &world->ent_route ); i++ ){
- ent_route *route = mdl_arritm( &world->ent_route, i );
- float dist = v3_dist2( route->board_transform[3], pos );
-
- if( dist < min_dist ){
- min_dist = dist;
- closest = i;
- }
- }
-
- struct leaderboard_cache *board = &world->leaderboard_cache[ closest ];
-
- /* request new board if cache expires */
- if( network_connected() ){
- f64 delta = vg.time_real - board->cache_time;
- if( (delta > 45.0) || (board->cache_time == 0.0) ){
- board->cache_time = vg.time_real;
- ent_route *route = mdl_arritm( &world->ent_route, closest );
- addon_reg *world_reg =
- world_static.instance_addons[ world - world_static.instances ];
-
- char mod_uid[ ADDON_UID_MAX ];
- addon_alias_uid( &world_reg->alias, mod_uid );
-
- network_request_scoreboard(
- mod_uid,
- mdl_pstr( &world->meta, route->pstr_name ),
- NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK, closest );
- }
- }
-
- /* compile board text if we changed. */
- if( world_sfd.active_route_board != closest ){
- world_sfd_compile_active_scores();
- }
-
- world_sfd.active_route_board = closest;
- }
-
- for( int i=0; i<world_sfd.w*world_sfd.h; i++ ){
- float *target = &world_sfd.buffer[i*2+0],
- *cur = &world_sfd.buffer[i*2+1];
-
- float const rate = vg.time_delta * 25.2313131414f;
- float d1 = *target-*cur;
-
- if( fabsf(d1) > rate ){
- *cur += rate;
- if( *cur > 49.0f )
- *cur -= 49.0f;
- }
- else
- *cur = *target;
- }
-}
-
-void bind_terrain_noise(void);
-void sfd_render( world_instance *world, vg_camera *cam, m4x3f transform )
-{
- mesh_bind( &world_sfd.mesh_display );
- shader_scene_scoretext_use();
- shader_scene_scoretext_uTexMain(1);
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_scoretext );
-
- bind_terrain_noise();
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_sfd.tex_scoretex );
-
- m4x4f pvm_prev;
- m4x3_expand( transform, pvm_prev );
- m4x4_mul( cam->mtx_prev.pv, pvm_prev, pvm_prev );
-
- shader_scene_scoretext_uPv( cam->mtx.pv );
- shader_scene_scoretext_uPvmPrev( pvm_prev );
- shader_scene_scoretext_uMdl( transform );
- shader_scene_scoretext_uCamera( cam->transform[3] );
-
- for( int y=0;y<world_sfd.h; y++ ){
- for( int x=0; x<world_sfd.w; x++ ){
- float value = world_sfd.buffer[(y*world_sfd.w+x)*2+1];
- shader_scene_scoretext_uInfo( (v3f){ x,y, value } );
- mesh_draw( &world_sfd.mesh_display );
- }
- }
-
- shader_scene_vertex_blend_use();
- shader_scene_vertex_blend_uTexGarbage(0);
- shader_scene_vertex_blend_uTexGradients(1);
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_vertex_blend );
-
- bind_terrain_noise();
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_sfd.tex_scoretex );
-
- shader_scene_vertex_blend_uPv( cam->mtx.pv );
- shader_scene_vertex_blend_uPvmPrev( pvm_prev );
- shader_scene_vertex_blend_uMdl( transform );
- shader_scene_vertex_blend_uCamera( cam->transform[3] );
-
- mesh_bind( &world_sfd.mesh_base );
- mdl_draw_submesh( &world_sfd.sm_base );
-}
-
-void world_sfd_init(void)
-{
- vg_info( "world_sfd_init\n" );
- vg_linear_clear( vg_mem.scratch );
-
- mdl_context mscoreboard;
- mdl_open( &mscoreboard, "models/rs_scoretext.mdl", vg_mem.scratch );
- mdl_load_metadata_block( &mscoreboard, vg_mem.scratch );
- mdl_async_load_glmesh( &mscoreboard, &world_sfd.mesh_base, NULL );
-
- mdl_load_mesh_block( &mscoreboard, vg_mem.scratch );
-
- scene_context *scene = &world_sfd.scene;
- vg_async_item *call = scene_alloc_async( scene, &world_sfd.mesh_display,
- 3000, 8000 );
-
-
- mdl_mesh *m_backer = mdl_find_mesh( &mscoreboard, "backer" ),
- *m_card = mdl_find_mesh( &mscoreboard, "score_card" );
-
- mdl_submesh
- *sm_backer = mdl_arritm( &mscoreboard.submeshs, m_backer->submesh_start ),
- *sm_card = mdl_arritm( &mscoreboard.submeshs, m_card->submesh_start );
- world_sfd.sm_base = *sm_backer;
-
- m4x3f identity;
- m4x3_identity( identity );
-
- for( int i=0;i<4;i++ ){
- u32 vert_start = scene->vertex_count;
- scene_add_mdl_submesh( scene, &mscoreboard, sm_card, identity );
-
- for( int j=0; j<sm_card->vertex_count; j++ ){
- scene_vert *vert = &scene->arrvertices[ vert_start+j ];
-
- float const k_glyph_uvw = 1.0f/64.0f;
- vert->uv[0] -= k_glyph_uvw * (float)(i-1);
- vert->norm[3] = i*42;
- }
- }
-
- vg_async_dispatch( call, async_scene_upload );
- vg_tex2d_load_qoi_async_file( "textures/scoretext.qoi",
- VG_TEX2D_CLAMP|VG_TEX2D_NEAREST,
- &world_sfd.tex_scoretex );
-
- mdl_close( &mscoreboard );
-
- int w = 27,
- h = 13;
-
- world_sfd.w = w;
- world_sfd.h = h;
- world_sfd.buffer = vg_linear_alloc( vg_mem.rtmemory, 2*w*h*sizeof(float) );
-
- for( int i=0; i<w*h*2; i++ )
- world_sfd.buffer[i] = 0.0f;
-}
-
-#endif /* WORLD_SFD_C */
+++ /dev/null
-/*
- * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-#pragma once
-#include "world.h"
-#include "world_routes.h"
-#include "scene.h"
-
-struct world_sfd{
- GLuint tex_scoretex;
-
- glmesh mesh_base, mesh_display;
- mdl_submesh sm_base;
-
- u32 active_route_board;
- scene_context scene;
-
- u32 view_weekly;
-
- u32 w, h;
- float *buffer;
-}
-extern world_sfd;
-void world_sfd_init(void);
-
-enum world_sfd_align {
- k_world_sfd_left,
- k_world_sfd_right,
- k_world_sfd_center
-};
-
-void sfd_encode( v2i co, const char *str, enum world_sfd_align align );
-void world_sfd_update( world_instance *world, v3f pos );
-void sfd_render( world_instance *world, vg_camera *cam, m4x3f transform );
-void world_sfd_compile_scores( struct leaderboard_cache *leaderboard,
- const char *title );
-void world_sfd_compile_active_scores(void);
+++ /dev/null
-#include "world_volumes.h"
-
-void world_volumes_update( world_instance *world, v3f pos )
-{
- /* filter and check the existing ones */
- u32 j=0;
- for( u32 i=0; i<world_static.active_trigger_volume_count; i++ ){
- i32 idx = world_static.active_trigger_volumes[i];
- ent_volume *volume = mdl_arritm( &world->ent_volume, idx );
-
- v3f local;
- m4x3_mulv( volume->to_local, pos, local );
- if( (fabsf(local[0]) <= 1.0f) &&
- (fabsf(local[1]) <= 1.0f) &&
- (fabsf(local[2]) <= 1.0f) )
- {
- world_static.active_trigger_volumes[ j ++ ] = idx;
- boxf cube = {{-1.0f,-1.0f,-1.0f},{1.0f,1.0f,1.0f}};
- vg_line_boxf_transformed( volume->to_world, cube, 0xff00ccff );
- }
- else{
- /*
- * LEGACY BEHAVIOUR: < v104 does not have leave events
- */
- if( world->meta.info.version >= 104 ){
- ent_call basecall;
- basecall.function = k_ent_function_trigger_leave;
- basecall.id = mdl_entity_id( k_ent_volume, idx );
- basecall.data = NULL;
-
- entity_call( world, &basecall );
- }
- }
- }
- world_static.active_trigger_volume_count = j;
-
- static float random_accum = 0.0f;
- random_accum += vg.time_delta;
-
- u32 random_ticks = 0;
-
- while( random_accum > 0.1f ){
- random_accum -= 0.1f;
- random_ticks ++;
- }
-
- float radius = 32.0f;
-
- bh_iter it;
- bh_iter_init_range( 0, &it, pos, radius );
- i32 idx;
-
- while( bh_next( world->entity_bh, &it, &idx ) ){
- u32 id = world->entity_list[ idx ],
- type = mdl_entity_id_type( id ),
- index = mdl_entity_id_id( id );
-
- if( type != k_ent_volume ) continue;
-
- ent_volume *volume = mdl_arritm( &world->ent_volume, index );
- boxf cube = {{-1.0f,-1.0f,-1.0f},{1.0f,1.0f,1.0f}};
-
- if( volume->flags & k_ent_volume_flag_particles ){
- vg_line_boxf_transformed( volume->to_world, cube, 0xff00c0ff );
-
- for( int j=0; j<random_ticks; j++ ){
- ent_call basecall;
- basecall.id = id;
- basecall.data = NULL;
- basecall.function = 0;
-
- entity_call( world, &basecall );
- }
- }
- else{
- for( u32 i=0; i<world_static.active_trigger_volume_count; i++ )
- if( world_static.active_trigger_volumes[i] == index )
- goto next_volume;
-
- if( world_static.active_trigger_volume_count >
- VG_ARRAY_LEN(world_static.active_trigger_volumes) ) continue;
-
- v3f local;
- m4x3_mulv( volume->to_local, pos, local );
-
- if( (fabsf(local[0]) <= 1.0f) &&
- (fabsf(local[1]) <= 1.0f) &&
- (fabsf(local[2]) <= 1.0f) ){
- ent_call basecall;
- basecall.function = 0;
- basecall.id = id;
- basecall.data = NULL;
-
- entity_call( world, &basecall );
- world_static.active_trigger_volumes[
- world_static.active_trigger_volume_count ++ ] = index;
- }
- else
- vg_line_boxf_transformed( volume->to_world, cube, 0xffcccccc );
- }
-next_volume:;
- }
-}
+++ /dev/null
-#pragma once
-#include "world.h"
-#include "vg/vg_bvh.h"
-
-void world_volumes_update( world_instance *world, v3f pos );
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#include "world_water.h"
-#include "world_render.h"
-#include "render.h"
-#include "shaders/scene_water.h"
-#include "shaders/scene_water_fast.h"
-#include "scene.h"
-#include "player.h"
-#include "player_walk.h"
-#include "player_dead.h"
-
-struct world_water world_water;
-
-void world_water_init(void)
-{
- vg_info( "world_water_init\n" );
-
- vg_tex2d_load_qoi_async_file( "textures/water_surf.qoi",
- VG_TEX2D_LINEAR|VG_TEX2D_REPEAT,
- &world_water.tex_water_surf );
-
- vg_success( "done\n" );
-}
-
-void water_set_surface( world_instance *world, float height )
-{
- world->water.height = height;
- v4_copy( (v4f){ 0.0f, 1.0f, 0.0f, height }, world->water.plane );
-}
-
-void world_link_lighting_ub( world_instance *world, GLuint shader );
-void world_bind_position_texture( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-void world_bind_light_array( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-void world_bind_light_index( world_instance *world,
- GLuint shader, GLuint location,
- int slot );
-
-/*
- * Does not write motion vectors
- */
-void render_water_texture( world_instance *world, vg_camera *cam )
-{
- if( !world->water.enabled || (vg.quality_profile == k_quality_profile_low) )
- return;
-
- /* Draw reflection buffa */
- vg_framebuffer_bind( g_render.fb_water_reflection, k_render_scale );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
-
- /*
- * Create flipped view matrix. Don't care about motion vectors
- */
- float cam_height = cam->transform[3][1] - world->water.height;
-
- vg_camera water_cam;
- water_cam.farz = cam->farz;
- water_cam.nearz = cam->nearz;
- v3_copy( cam->transform[3], water_cam.transform[3] );
- water_cam.transform[3][1] -= 2.0f * cam_height;
-
- m3x3f flip;
- m3x3_identity( flip );
- flip[1][1] = -1.0f;
- m3x3_mul( flip, cam->transform, water_cam.transform );
-
- vg_camera_update_view( &water_cam );
-
- /*
- * Create clipped projection
- */
- v4f clippa = { 0.0f, 1.0f, 0.0f, world->water.height-0.1f };
- m4x3_mulp( water_cam.transform_inverse, clippa, clippa );
- clippa[3] *= -1.0f;
-
- m4x4_copy( cam->mtx.p, water_cam.mtx.p );
- m4x4_clip_projection( water_cam.mtx.p, clippa );
-
- vg_camera_finalize( &water_cam );
-
- /*
- * Draw world
- */
- glEnable( GL_DEPTH_TEST );
- glDisable( GL_BLEND );
- glCullFace( GL_FRONT );
- render_world( world, &water_cam, 0, 1, 0, 1 );
- glCullFace( GL_BACK );
-
- /*
- * Create beneath view matrix
- */
- vg_camera beneath_cam;
- vg_framebuffer_bind( g_render.fb_water_beneath, k_render_scale );
- glClearColor( 1.0f, 0.0f, 0.0f, 0.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
-
- m4x3_copy( cam->transform, beneath_cam.transform );
- vg_camera_update_view( &beneath_cam );
-
- float bias = -(cam->transform[3][1]-world->water.height)*0.1f;
-
- v4f clippb = { 0.0f, -1.0f, 0.0f, -(world->water.height) + bias };
- m4x3_mulp( beneath_cam.transform_inverse, clippb, clippb );
- clippb[3] *= -1.0f;
-
- m4x4_copy( cam->mtx.p, beneath_cam.mtx.p );
- m4x4_clip_projection( beneath_cam.mtx.p, clippb );
- vg_camera_finalize( &beneath_cam );
-
- glEnable( GL_DEPTH_TEST );
- glDisable( GL_BLEND );
- render_world_depth( world, &beneath_cam );
- //glViewport( 0,0, g_render_x, g_render_y );
-}
-
-void render_water_surface( world_instance *world, vg_camera *cam )
-{
- if( !world->water.enabled )
- return;
-
- if( vg.quality_profile == k_quality_profile_high )
- {
- /* Draw surface */
- shader_scene_water_use();
-
- vg_framebuffer_bind_texture( g_render.fb_water_reflection, 0, 0 );
- shader_scene_water_uTexMain( 0 );
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_water.tex_water_surf );
- shader_scene_water_uTexDudv( 1 );
-
- shader_scene_water_uInvRes( (v2f){
- 1.0f / (float)vg.window_x,
- 1.0f / (float)vg.window_y });
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_water );
-
- vg_framebuffer_bind_texture( g_render.fb_water_beneath, 0, 5 );
- shader_scene_water_uTexBack( 5 );
- shader_scene_water_uTime( world_static.time );
- shader_scene_water_uCamera( cam->transform[3] );
- shader_scene_water_uSurfaceY( world->water.height );
-
- shader_scene_water_uPv( cam->mtx.pv );
- shader_scene_water_uPvmPrev( cam->mtx_prev.pv );
-
- m4x3f full;
- m4x3_identity( full );
- shader_scene_water_uMdl( full );
-
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
- glBlendEquation(GL_FUNC_ADD);
-
- mesh_bind( &world->mesh_no_collide );
-
- for( int i=0; i<world->surface_count; i++ )
- {
- struct world_surface *mat = &world->surfaces[i];
- struct shader_props_water *props = mat->info.props.compiled;
-
- if( mat->info.shader == k_shader_water )
- {
- shader_scene_water_uShoreColour( props->shore_colour );
- shader_scene_water_uOceanColour( props->deep_colour );
- shader_scene_water_uFresnel( props->fresnel );
- shader_scene_water_uWaterScale( props->water_sale );
- shader_scene_water_uWaveSpeed( props->wave_speed );
-
- mdl_draw_submesh( &mat->sm_no_collide );
- }
- }
-
- glDisable(GL_BLEND);
- }
- else if( (vg.quality_profile == k_quality_profile_low) ||
- (vg.quality_profile == k_quality_profile_min) )
- {
- shader_scene_water_fast_use();
-
- glActiveTexture( GL_TEXTURE1 );
- glBindTexture( GL_TEXTURE_2D, world_water.tex_water_surf );
- shader_scene_water_fast_uTexDudv( 1 );
-
- shader_scene_water_fast_uTime( world_static.time );
- shader_scene_water_fast_uCamera( cam->transform[3] );
- shader_scene_water_fast_uSurfaceY( world->water.height );
-
- WORLD_BIND_LIGHT_BUFFERS_UB0_TEX234( world, scene_water_fast );
-
- m4x3f full;
- m4x3_identity( full );
- shader_scene_water_fast_uMdl( full );
- shader_scene_water_fast_uPv( cam->mtx.pv );
- shader_scene_water_fast_uPvmPrev( cam->mtx_prev.pv );
-
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
- glBlendEquation(GL_FUNC_ADD);
-
- mesh_bind( &world->mesh_no_collide );
-
- for( int i=0; i<world->surface_count; i++ )
- {
- struct world_surface *mat = &world->surfaces[i];
- struct shader_props_water *props = mat->info.props.compiled;
-
- if( mat->info.shader == k_shader_water )
- {
- shader_scene_water_fast_uShoreColour( props->shore_colour );
- shader_scene_water_fast_uOceanColour( props->deep_colour );
-
- mdl_draw_submesh( &mat->sm_no_collide );
- }
- }
-
- glDisable(GL_BLEND);
- }
-}
-
-static void world_water_drown(void)
-{
- if( localplayer.drowned ) return;
- player__networked_sfx( k_player_subsystem_walk, 32,
- k_player_walk_soundeffect_splash,
- localplayer.rb.co, 1.0f );
- vg_info( "player fell of due to walking into walker\n" );
- localplayer.drowned = 1;
- player__dead_transition( k_player_die_type_generic );
-}
-
-bool world_water_player_safe( world_instance *world, f32 allowance )
-{
- if( !world->water.enabled ) return 1;
- if( world->info.flags & 0x2 ) return 1;
-
- if( localplayer.rb.co[1]+allowance < world->water.height )
- {
- world_water_drown();
- return 0;
- }
-
- return 1;
-}
-
-entity_call_result ent_water_call( world_instance *world, ent_call *call )
-{
- if( call->function == 0 )
- {
- world_water_drown();
- return k_entity_call_result_OK;
- }
-
- return k_entity_call_result_unhandled;
-}
+++ /dev/null
-/*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
- */
-
-#pragma once
-#include "world.h"
-
-struct world_water{
- GLuint tex_water_surf;
-}
-extern world_water;
-void world_water_init(void);
-
-void water_set_surface( world_instance *world, f32 height );
-void render_water_texture( world_instance *world, vg_camera *cam );
-void render_water_surface( world_instance *world, vg_camera *cam );
-entity_call_result ent_water_call( world_instance *world, ent_call *call );
-bool world_water_player_safe( world_instance *world, f32 allowance );