From: hgn Date: Sun, 8 Sep 2024 15:36:01 +0000 (+0100) Subject: move files to src folder X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;h=66bf80cafdff3b73b3481ec8d182a75eaf66f3e1;p=carveJwlIkooP6JGAAIwe30JlM.git move files to src folder --- diff --git a/addon.c b/addon.c deleted file mode 100644 index 6769b5a..0000000 --- a/addon.c +++ /dev/null @@ -1,881 +0,0 @@ -#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 - -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; typecountalias.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; typecountalias.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; countalias.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; counttype]; 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; typecache_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; jcache_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; ialias.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; ialias.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; jalias.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; typepool.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; typepool.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 ); -} diff --git a/addon.h b/addon.h deleted file mode 100644 index c43277c..0000000 --- a/addon.h +++ /dev/null @@ -1,108 +0,0 @@ -#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] ); diff --git a/addon_types.c b/addon_types.c deleted file mode 100644 index b10a23c..0000000 --- a/addon_types.c +++ /dev/null @@ -1,20 +0,0 @@ -#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/" - } -}; diff --git a/addon_types.h b/addon_types.h deleted file mode 100644 index a244fc0..0000000 --- a/addon_types.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 diff --git a/audio.c b/audio.c deleted file mode 100644 index dda8a2d..0000000 --- a/audio.c +++ /dev/null @@ -1,259 +0,0 @@ -#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; ilpf, 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] ); - } - } - } -} diff --git a/audio.h b/audio.h deleted file mode 100644 index 95894f1..0000000 --- a/audio.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 -}; diff --git a/build.c b/build.c index f772fd2..692e941 100644 --- a/build.c +++ b/build.c @@ -5,11 +5,11 @@ #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 @@ -149,7 +149,7 @@ void build_game_content( struct vg_project *proj ) 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 ) { @@ -171,9 +171,6 @@ 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, @@ -184,7 +181,7 @@ void build_game_bin( struct vg_project *proj, struct vg_compiler_env *env ) .custom_game_settings = 0, .custom_shaders = 1 }, - env, &conf, "client.c", "skaterift" ); + env, &conf, "src/client.c", "skaterift" ); vg_add_controller_database( proj ); } @@ -216,9 +213,10 @@ void compile_server( struct vg_project *proj, struct vg_compiler_env *env ) 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", "" ); diff --git a/build.sh b/build.sh index f5175ea..4ad323c 100755 --- a/build.sh +++ b/build.sh @@ -1 +1 @@ -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 $@ diff --git a/build_control_overlay.c b/build_control_overlay.c deleted file mode 100644 index 1b67b37..0000000 --- a/build_control_overlay.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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; ipstr_name ), mesh->submesh_start ); - } - - fclose( hdr ); -} diff --git a/client.c b/client.c deleted file mode 100644 index e06c253..0000000 --- a/client.c +++ /dev/null @@ -1,88 +0,0 @@ -#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" diff --git a/client.h b/client.h deleted file mode 100644 index e3435b9..0000000 --- a/client.h +++ /dev/null @@ -1,18 +0,0 @@ -#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 ); diff --git a/common.h b/common.h deleted file mode 100644 index 6f0c2f9..0000000 --- a/common.h +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved - */ - -#ifndef COMMON_H -#define COMMON_H - -#endif /* COMMON_H */ diff --git a/control_overlay.c b/control_overlay.c deleted file mode 100644 index a39d77b..0000000 --- a/control_overlay.c +++ /dev/null @@ -1,793 +0,0 @@ -#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 ); - } - } -} diff --git a/control_overlay.h b/control_overlay.h deleted file mode 100644 index 089136d..0000000 --- a/control_overlay.h +++ /dev/null @@ -1,17 +0,0 @@ -#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); diff --git a/depth_compare.h b/depth_compare.h deleted file mode 100644 index 1db5665..0000000 --- a/depth_compare.h +++ /dev/null @@ -1,23 +0,0 @@ -#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 ); -} diff --git a/ent_camera.c b/ent_camera.c deleted file mode 100644 index d23d8d8..0000000 --- a/ent_camera.c +++ /dev/null @@ -1,10 +0,0 @@ -#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; -} diff --git a/ent_camera.h b/ent_camera.h deleted file mode 100644 index bf4ce14..0000000 --- a/ent_camera.h +++ /dev/null @@ -1,3 +0,0 @@ -#include "entity.h" - -void ent_camera_unpack( ent_camera *ent, vg_camera *cam ); diff --git a/ent_challenge.c b/ent_challenge.c deleted file mode 100644 index d30a5dc..0000000 --- a/ent_challenge.c +++ /dev/null @@ -1,127 +0,0 @@ -#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 ){ - -} diff --git a/ent_challenge.h b/ent_challenge.h deleted file mode 100644 index f53c956..0000000 --- a/ent_challenge.h +++ /dev/null @@ -1,5 +0,0 @@ -#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 ); diff --git a/ent_glider.c b/ent_glider.c deleted file mode 100644 index 12b0575..0000000 --- a/ent_glider.c +++ /dev/null @@ -1,25 +0,0 @@ -#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; -} diff --git a/ent_glider.h b/ent_glider.h deleted file mode 100644 index 5815dda..0000000 --- a/ent_glider.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include "entity.h" - -entity_call_result ent_glider_call( world_instance *world, ent_call *call ); diff --git a/ent_miniworld.c b/ent_miniworld.c deleted file mode 100644 index 19c88d0..0000000 --- a/ent_miniworld.c +++ /dev/null @@ -1,211 +0,0 @@ -#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; ient_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; ient_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; ient_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); -} diff --git a/ent_miniworld.h b/ent_miniworld.h deleted file mode 100644 index 278cf75..0000000 --- a/ent_miniworld.h +++ /dev/null @@ -1,18 +0,0 @@ -#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); diff --git a/ent_npc.c b/ent_npc.c deleted file mode 100644 index 7a4b484..0000000 --- a/ent_npc.c +++ /dev/null @@ -1,304 +0,0 @@ -#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 ); -} diff --git a/ent_npc.h b/ent_npc.h deleted file mode 100644 index c20cc97..0000000 --- a/ent_npc.h +++ /dev/null @@ -1,28 +0,0 @@ -#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); diff --git a/ent_objective.c b/ent_objective.c deleted file mode 100644 index 6a8bbe5..0000000 --- a/ent_objective.c +++ /dev/null @@ -1,139 +0,0 @@ -#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; -} diff --git a/ent_objective.h b/ent_objective.h deleted file mode 100644 index 9d94772..0000000 --- a/ent_objective.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include "entity.h" -#include "world.h" -entity_call_result ent_objective_call( world_instance *world, ent_call *call ); diff --git a/ent_region.c b/ent_region.c deleted file mode 100644 index 5e0ec03..0000000 --- a/ent_region.c +++ /dev/null @@ -1,165 +0,0 @@ -#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; ient_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; ient_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; ient_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; jent_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; jent_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(); - } -} diff --git a/ent_region.h b/ent_region.h deleted file mode 100644 index 6a86a32..0000000 --- a/ent_region.h +++ /dev/null @@ -1,14 +0,0 @@ -#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 ); diff --git a/ent_relay.c b/ent_relay.c deleted file mode 100644 index b22b0b5..0000000 --- a/ent_relay.c +++ /dev/null @@ -1,25 +0,0 @@ -#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; itargets); 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; -} diff --git a/ent_relay.h b/ent_relay.h deleted file mode 100644 index 054570c..0000000 --- a/ent_relay.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -#include "entity.h" -entity_call_result ent_relay_call( world_instance *world, ent_call *call ); diff --git a/ent_route.c b/ent_route.c deleted file mode 100644 index 8564eed..0000000 --- a/ent_route.c +++ /dev/null @@ -1,75 +0,0 @@ -#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; - } -} diff --git a/ent_route.h b/ent_route.h deleted file mode 100644 index cbf61b2..0000000 --- a/ent_route.h +++ /dev/null @@ -1,11 +0,0 @@ -#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 ); diff --git a/ent_skateshop.c b/ent_skateshop.c deleted file mode 100644 index ded707c..0000000 --- a/ent_skateshop.c +++ /dev/null @@ -1,850 +0,0 @@ -#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; icache_id ); - } - - for( u32 i=0; icache_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; ient_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; icache_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; jent_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; itransform, &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; -} diff --git a/ent_skateshop.h b/ent_skateshop.h deleted file mode 100644 index 2f8e3a6..0000000 --- a/ent_skateshop.h +++ /dev/null @@ -1,54 +0,0 @@ -#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); diff --git a/ent_tornado.c b/ent_tornado.c deleted file mode 100644 index f9fca03..0000000 --- a/ent_tornado.c +++ /dev/null @@ -1,87 +0,0 @@ -#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; ient_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; ient_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; ient_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 ); - } - } -} diff --git a/ent_tornado.h b/ent_tornado.h deleted file mode 100644 index f89c070..0000000 --- a/ent_tornado.h +++ /dev/null @@ -1,6 +0,0 @@ -#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); diff --git a/ent_traffic.c b/ent_traffic.c deleted file mode 100644 index 8bb19b9..0000000 --- a/ent_traffic.c +++ /dev/null @@ -1,63 +0,0 @@ -#include "world.h" - -void ent_traffic_update( world_instance *world, v3f pos ) -{ - for( u32 i=0; ient_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 ); - } -} diff --git a/ent_traffic.h b/ent_traffic.h deleted file mode 100644 index 18d8d1e..0000000 --- a/ent_traffic.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -#include "world.h" -void ent_traffic_update( world_instance *world, v3f pos ); diff --git a/entity.c b/entity.c deleted file mode 100644 index 797e29d..0000000 --- a/entity.c +++ /dev/null @@ -1,77 +0,0 @@ -#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 - -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; ipstr_alias ), alias ) ) - { - return marker; - } - } - - return NULL; -} - diff --git a/entity.h b/entity.h deleted file mode 100644 index 6d503f9..0000000 --- a/entity.h +++ /dev/null @@ -1,582 +0,0 @@ -#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 ); diff --git a/font.h b/font.h deleted file mode 100644 index fa144c4..0000000 --- a/font.h +++ /dev/null @@ -1,324 +0,0 @@ -#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 - -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; ifont_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; -} diff --git a/freecam.c b/freecam.c deleted file mode 100644 index d961ed6..0000000 --- a/freecam.c +++ /dev/null @@ -1,44 +0,0 @@ -#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 ); -} diff --git a/freecam.h b/freecam.h deleted file mode 100644 index 1f9f5e2..0000000 --- a/freecam.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -void freecam_preupdate(void); -int freecam_cmd( int argc, const char *argv[] ); diff --git a/gameserver.c b/gameserver.c deleted file mode 100644 index e315fd0..0000000 --- a/gameserver.c +++ /dev/null @@ -1,1136 +0,0 @@ -/* - * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved - */ - -#define _DEFAULT_SOURCE -#include -#include -#include -#include - -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; iconnection ) - 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; isteamid ) - 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; jitems[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; iactive = 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; isteamid ) - 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; iinstance == 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<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; im_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; -} diff --git a/gameserver.h b/gameserver.h deleted file mode 100644 index a03b1a0..0000000 --- a/gameserver.h +++ /dev/null @@ -1,52 +0,0 @@ -#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 - -#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; diff --git a/gameserver_db.h b/gameserver_db.h deleted file mode 100644 index fe39931..0000000 --- a/gameserver_db.h +++ /dev/null @@ -1,398 +0,0 @@ -#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 -#include - -#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 */ diff --git a/gui.h b/gui.h deleted file mode 100644 index 46e2e1e..0000000 --- a/gui.h +++ /dev/null @@ -1,360 +0,0 @@ -#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; icolour_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; ibinding, 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; iicon = 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 ); -} diff --git a/input.h b/input.h deleted file mode 100644 index 3377824..0000000 --- a/input.h +++ /dev/null @@ -1,311 +0,0 @@ -#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 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; - } -} diff --git a/menu.c b/menu.c deleted file mode 100644 index 1f2b9c8..0000000 --- a/menu.c +++ /dev/null @@ -1,1095 +0,0 @@ -#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; ient_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; ifont = &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; ient_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; jent_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; jent_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; -} diff --git a/menu.h b/menu.h deleted file mode 100644 index 5a6dbc4..0000000 --- a/menu.h +++ /dev/null @@ -1,56 +0,0 @@ -#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); diff --git a/model.c b/model.c deleted file mode 100644 index f3f7ab2..0000000 --- a/model.c +++ /dev/null @@ -1,648 +0,0 @@ -/* - * 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 -#include -#include -#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; iitem_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; iindex); 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; imaterials); 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; imeshs ); 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; ivertex_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; isubmeshs ); i++ ) - { - mdl_submesh *sm = mdl_arritm( &mdl->submeshs, i ); - u32 *indices = job->indices + sm->indice_start; - - for( u32 j=0; jindice_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; itextures ); 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 diff --git a/model.h b/model.h deleted file mode 100644 index aade46a..0000000 --- a/model.h +++ /dev/null @@ -1,345 +0,0 @@ -/* - * 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 ); - diff --git a/network.c b/network.c deleted file mode 100644 index 0869612..0000000 --- a/network.c +++ /dev/null @@ -1,789 +0,0 @@ -#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 - #include -#else - #include - #include - #include -#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; icur_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; iticket[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; isteamid == 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; im_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; ibuffer = 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 ); - } -} diff --git a/network.h b/network.h deleted file mode 100644 index 6af51af..0000000 --- a/network.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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; -} diff --git a/network_common.h b/network_common.h deleted file mode 100644 index ca46e47..0000000 --- a/network_common.h +++ /dev/null @@ -1,42 +0,0 @@ -#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) && (_abytes+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 */ diff --git a/network_msg.h b/network_msg.h deleted file mode 100644 index 4dfeb5e..0000000 --- a/network_msg.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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 */ diff --git a/particle.c b/particle.c deleted file mode 100644 index ec2960b..0000000 --- a/particle.c +++ /dev/null @@ -1,187 +0,0 @@ -#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; ialive; 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; iindices[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; ialive; 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 ); -} diff --git a/particle.h b/particle.h deleted file mode 100644 index 6858890..0000000 --- a/particle.h +++ /dev/null @@ -1,42 +0,0 @@ -#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 ); diff --git a/physics_test.h b/physics_test.h deleted file mode 100644 index 243de36..0000000 --- a/physics_test.h +++ /dev/null @@ -1,290 +0,0 @@ -/* - * 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; inode_count; i++ ) - { - mdl_node *pnode = mdl_node_from_id( mdl, i ); - - for( int j=0; jsubmesh_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 - -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; isystem_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; ibind ) 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; ialias, 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; istatus == 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; ipriority < 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 ); -} diff --git a/player.h b/player.h deleted file mode 100644 index ff8c2e1..0000000 --- a/player.h +++ /dev/null @@ -1,197 +0,0 @@ -#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 ); diff --git a/player_api.h b/player_api.h deleted file mode 100644 index f7f4785..0000000 --- a/player_api.h +++ /dev/null @@ -1,34 +0,0 @@ -#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 -}; diff --git a/player_basic_info.c b/player_basic_info.c deleted file mode 100644 index ffc7ae0..0000000 --- a/player_basic_info.c +++ /dev/null @@ -1,34 +0,0 @@ -#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; ikeyframes[i].co); - q_identity(pose->keyframes[i].q); - v3_fill(pose->keyframes[i].s,1.0f); - } -} diff --git a/player_basic_info.h b/player_basic_info.h deleted file mode 100644 index 815be67..0000000 --- a/player_basic_info.h +++ /dev/null @@ -1,18 +0,0 @@ -#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 ); - diff --git a/player_common.c b/player_common.c deleted file mode 100644 index 1ecbae9..0000000 --- a/player_common.c +++ /dev/null @@ -1,290 +0,0 @@ -#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 ); -} diff --git a/player_common.h b/player_common.h deleted file mode 100644 index b32faee..0000000 --- a/player_common.h +++ /dev/null @@ -1,16 +0,0 @@ -#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); diff --git a/player_dead.c b/player_dead.c deleted file mode 100644 index 43b6211..0000000 --- a/player_dead.c +++ /dev/null @@ -1,205 +0,0 @@ -#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; ipart_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; ibone_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; ibone_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; ibone_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; itransforms[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" ); -} diff --git a/player_dead.h b/player_dead.h deleted file mode 100644 index 93b0cc3..0000000 --- a/player_dead.h +++ /dev/null @@ -1,32 +0,0 @@ -#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 ); - diff --git a/player_drive.c b/player_drive.c deleted file mode 100644 index 0462037..0000000 --- a/player_drive.c +++ /dev/null @@ -1,87 +0,0 @@ -#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" ); -} diff --git a/player_drive.h b/player_drive.h deleted file mode 100644 index 9a5649d..0000000 --- a/player_drive.h +++ /dev/null @@ -1,21 +0,0 @@ -#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); diff --git a/player_effects.c b/player_effects.c deleted file mode 100644 index 981c232..0000000 --- a/player_effects.c +++ /dev/null @@ -1,37 +0,0 @@ -#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; -} diff --git a/player_effects.h b/player_effects.h deleted file mode 100644 index f148dbc..0000000 --- a/player_effects.h +++ /dev/null @@ -1,26 +0,0 @@ -#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; -}; diff --git a/player_glide.c b/player_glide.c deleted file mode 100644 index d9977ce..0000000 --- a/player_glide.c +++ /dev/null @@ -1,706 +0,0 @@ -#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; janimator; - 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; itransform.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; istatus == 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; imeshs); 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; jsubmesh_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; icollider_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; jpart_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; ibone_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; ipart_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; iparent && !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; ipart_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; ipart_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; jparts[i].rb; - buf[j].rbb = &_null; - } - - rb_contact_count += l; - } - } - - /* - * self-collision - */ - for( int i=0; ipart_count-1; i ++ ){ - for( int j=i+1; jpart_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; kparts[i].rb; - buf[k].rbb = &rd->parts[j].rb; - } - - rb_contact_count += l; - } - } - } - - if( localplayer.drowned ) - { - for( int j=0; jpart_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; ico, 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; ipart_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; ipart_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; ipart_count; i++ ) - rb_update_matrices( &rd->parts[i].rb ); - } - - rb_ct *stress = NULL; - float max_stress = 1.0f; - - for( u32 i=0; ico, 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; icone_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(); - } -} diff --git a/player_ragdoll.h b/player_ragdoll.h deleted file mode 100644 index 08ab5e7..0000000 --- a/player_ragdoll.h +++ /dev/null @@ -1,71 +0,0 @@ -#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 ); diff --git a/player_remote.c b/player_remote.c deleted file mode 100644 index 6fdf04f..0000000 --- a/player_remote.c +++ /dev/null @@ -1,1159 +0,0 @@ -#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; iworld_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; iactive ) - 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; iframes); 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; iframes); 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; isound_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; jsystem == 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; iflags |= 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; iactive ){ - 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; iactive ) - { - 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; iframes); 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; iactive ) 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; iactive || 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; jbone_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; jrender_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; iactive = (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; iusername)-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; isystem != 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; iactive || 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; iactive ) - { - 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; - } - } - } -} diff --git a/player_remote.h b/player_remote.h deleted file mode 100644 index 3e4b67e..0000000 --- a/player_remote.h +++ /dev/null @@ -1,107 +0,0 @@ -#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 ); diff --git a/player_render.c b/player_render.c deleted file mode 100644 index 15dec10..0000000 --- a/player_render.c +++ /dev/null @@ -1,629 +0,0 @@ -#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; ibone_count; i ++ ){ - localplayer.skeleton_mirror[i] = 0; - } - - for( i32 i=1; ibone_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; jbone_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; ientity_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; ibone_count+1; i ++ ) - fixup_table[i] = 0; - - for( u32 i=1; iname ); - - for( u32 j=1; jbone_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; ibone_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; ibone_count-1; i ++ ){ - mirrored[i] = temp[i]; - } -} diff --git a/player_render.h b/player_render.h deleted file mode 100644 index cfc48e7..0000000 --- a/player_render.h +++ /dev/null @@ -1,82 +0,0 @@ -#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); diff --git a/player_replay.c b/player_replay.c deleted file mode 100644 index 950ad78..0000000 --- a/player_replay.c +++ /dev/null @@ -1,1207 +0,0 @@ -#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 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; idata_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; ihead ) 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; icursor) && (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; ico, 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; irb; - - 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; istatehead->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; itime-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; jmouse[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; jmouse ) ) - { - 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 - */ -} diff --git a/player_replay.h b/player_replay.h deleted file mode 100644 index 313b4d4..0000000 --- a/player_replay.h +++ /dev/null @@ -1,125 +0,0 @@ -#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 ); diff --git a/player_skate.c b/player_skate.c deleted file mode 100644 index aea7027..0000000 --- a/player_skate.c +++ /dev/null @@ -1,3664 +0,0 @@ -#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 - -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; igeo_bh, man, - k_material_flag_walking ); - - for( int i=0; i 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; ico, 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; jlog[jump->log_length-1], grind.co ); - float descale = 1.0f-vg_minf(1.0f,dist); - jump->score += descale*3.0f; - } - - for( int i=0; itype = 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; iscore < score_min ) - best = jump; - - score_min = vg_minf( score_min, jump->score ); - score_max = vg_maxf( score_max, jump->score ); - } - - for( int i=0; iscore; - - 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; ilog_length == 0 ){ - vg_fatal_error( "assert: jump->log_length == 0\n" ); - } - - for( int j=0; jlog_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; iactivity = 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 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; islap, 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; iactivity >= k_skate_activity_grind_any ){ - for( int i=0; ira, 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; ico, 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; iinfo.surface_prop > player_skate.surface ) - player_skate.surface = surf->info.surface_prop; - } - - for( int i=0; icog, 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; ikeyframes[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; ikeyframes[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; ikeyframes[ 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(); -} diff --git a/player_skate.h b/player_skate.h deleted file mode 100644 index 8adb024..0000000 --- a/player_skate.h +++ /dev/null @@ -1,326 +0,0 @@ -#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); diff --git a/player_walk.c b/player_walk.c deleted file mode 100644 index 1e15afc..0000000 --- a/player_walk.c +++ /dev/null @@ -1,1210 +0,0 @@ -#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; inormal, 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; itype == k_contact_type_disabled || - ci->type == k_contact_type_edge ) - continue; - - - float d1 = v3_dot( ci->co, ci->n ); - - for( int j=0; jtype == 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; in ) ){ - 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; in ); - - 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; ibone_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(); -} diff --git a/player_walk.h b/player_walk.h deleted file mode 100644 index 5da0350..0000000 --- a/player_walk.h +++ /dev/null @@ -1,113 +0,0 @@ -#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 ); diff --git a/render.c b/render.c deleted file mode 100644 index fada4a5..0000000 --- a/render.c +++ /dev/null @@ -1,258 +0,0 @@ -#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(); -} diff --git a/render.h b/render.h deleted file mode 100644 index 765f41b..0000000 --- a/render.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 ); diff --git a/save.c b/save.c deleted file mode 100644 index 73eaa49..0000000 --- a/save.c +++ /dev/null @@ -1,195 +0,0 @@ -#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; ifile_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; - } - } -} diff --git a/save.h b/save.h deleted file mode 100644 index acda301..0000000 --- a/save.h +++ /dev/null @@ -1,29 +0,0 @@ -#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 ); diff --git a/scene.c b/scene.c deleted file mode 100644 index a94fbca..0000000 --- a/scene.c +++ /dev/null @@ -1,402 +0,0 @@ -#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; ivertex_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; iindice_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; iarrvertices[ 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 ); -} diff --git a/scene.h b/scene.h deleted file mode 100644 index 8d6f57a..0000000 --- a/scene.h +++ /dev/null @@ -1,61 +0,0 @@ -#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 ); diff --git a/scene_rigidbody.h b/scene_rigidbody.h deleted file mode 100644 index 57ff1ff..0000000 --- a/scene_rigidbody.h +++ /dev/null @@ -1,247 +0,0 @@ -#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; -} - diff --git a/shader_props.h b/shader_props.h deleted file mode 100644 index 29e79f5..0000000 --- a/shader_props.h +++ /dev/null @@ -1,37 +0,0 @@ -#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; -}; diff --git a/skaterift.c b/skaterift.c deleted file mode 100644 index 36e087f..0000000 --- a/skaterift.c +++ /dev/null @@ -1,679 +0,0 @@ -/* - * ============================================================================= - * - * Copyright . . . -----, ,----- ,---. .---. - * 2021-2024 |\ /| | / | | | | /| - * | \ / | +-- / +----- +---' | / | - * | \ / | | / | | \ | / | - * | \/ | | / | | \ | / | - * ' ' '--' [] '----- '----- ' ' '---' SOFTWARE - * - * ============================================================================= - */ - -#define SR_ALLOW_REWIND_HUB - -#ifdef _WIN32 - #include -#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; iub_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" diff --git a/skaterift.h b/skaterift.h deleted file mode 100644 index e480d2e..0000000 --- a/skaterift.h +++ /dev/null @@ -1,33 +0,0 @@ -#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; diff --git a/skaterift_lib.c b/skaterift_lib.c deleted file mode 100644 index 95b8141..0000000 --- a/skaterift_lib.c +++ /dev/null @@ -1,31 +0,0 @@ -#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; ibone_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; ico, 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; ibone_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; ibone_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; ibone_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; ibone_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; iik_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; ibone_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; ibone_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; iik_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; ianim_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; ibone_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; ibone_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; ianim_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; ibone_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 ); - } -} diff --git a/src/addon.c b/src/addon.c new file mode 100644 index 0000000..6769b5a --- /dev/null +++ b/src/addon.c @@ -0,0 +1,881 @@ +#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 + +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; typecountalias.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; typecountalias.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; countalias.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; counttype]; 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; typecache_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; jcache_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; ialias.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; ialias.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; jalias.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; typepool.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; typepool.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 ); +} diff --git a/src/addon.h b/src/addon.h new file mode 100644 index 0000000..c43277c --- /dev/null +++ b/src/addon.h @@ -0,0 +1,108 @@ +#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] ); diff --git a/src/addon_types.c b/src/addon_types.c new file mode 100644 index 0000000..b10a23c --- /dev/null +++ b/src/addon_types.c @@ -0,0 +1,20 @@ +#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/" + } +}; diff --git a/src/addon_types.h b/src/addon_types.h new file mode 100644 index 0000000..a244fc0 --- /dev/null +++ b/src/addon_types.h @@ -0,0 +1,24 @@ +#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 diff --git a/src/audio.c b/src/audio.c new file mode 100644 index 0000000..dda8a2d --- /dev/null +++ b/src/audio.c @@ -0,0 +1,259 @@ +#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; ilpf, 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] ); + } + } + } +} diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 0000000..95894f1 --- /dev/null +++ b/src/audio.h @@ -0,0 +1,57 @@ +/* + * 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 +}; diff --git a/src/build_control_overlay.c b/src/build_control_overlay.c new file mode 100644 index 0000000..3205955 --- /dev/null +++ b/src/build_control_overlay.c @@ -0,0 +1,21 @@ +/* + * 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; ipstr_name ), mesh->submesh_start ); + } + + fclose( hdr ); +} diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..e06c253 --- /dev/null +++ b/src/client.c @@ -0,0 +1,88 @@ +#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" diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..e3435b9 --- /dev/null +++ b/src/client.h @@ -0,0 +1,18 @@ +#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 ); diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..6f0c2f9 --- /dev/null +++ b/src/common.h @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved + */ + +#ifndef COMMON_H +#define COMMON_H + +#endif /* COMMON_H */ diff --git a/src/control_overlay.c b/src/control_overlay.c new file mode 100644 index 0000000..a39d77b --- /dev/null +++ b/src/control_overlay.c @@ -0,0 +1,793 @@ +#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 ); + } + } +} diff --git a/src/control_overlay.h b/src/control_overlay.h new file mode 100644 index 0000000..089136d --- /dev/null +++ b/src/control_overlay.h @@ -0,0 +1,17 @@ +#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); diff --git a/src/depth_compare.h b/src/depth_compare.h new file mode 100644 index 0000000..1db5665 --- /dev/null +++ b/src/depth_compare.h @@ -0,0 +1,23 @@ +#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 ); +} diff --git a/src/ent_camera.c b/src/ent_camera.c new file mode 100644 index 0000000..d23d8d8 --- /dev/null +++ b/src/ent_camera.c @@ -0,0 +1,10 @@ +#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; +} diff --git a/src/ent_camera.h b/src/ent_camera.h new file mode 100644 index 0000000..bf4ce14 --- /dev/null +++ b/src/ent_camera.h @@ -0,0 +1,3 @@ +#include "entity.h" + +void ent_camera_unpack( ent_camera *ent, vg_camera *cam ); diff --git a/src/ent_challenge.c b/src/ent_challenge.c new file mode 100644 index 0000000..d30a5dc --- /dev/null +++ b/src/ent_challenge.c @@ -0,0 +1,127 @@ +#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 ){ + +} diff --git a/src/ent_challenge.h b/src/ent_challenge.h new file mode 100644 index 0000000..f53c956 --- /dev/null +++ b/src/ent_challenge.h @@ -0,0 +1,5 @@ +#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 ); diff --git a/src/ent_glider.c b/src/ent_glider.c new file mode 100644 index 0000000..12b0575 --- /dev/null +++ b/src/ent_glider.c @@ -0,0 +1,25 @@ +#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; +} diff --git a/src/ent_glider.h b/src/ent_glider.h new file mode 100644 index 0000000..5815dda --- /dev/null +++ b/src/ent_glider.h @@ -0,0 +1,4 @@ +#pragma once +#include "entity.h" + +entity_call_result ent_glider_call( world_instance *world, ent_call *call ); diff --git a/src/ent_miniworld.c b/src/ent_miniworld.c new file mode 100644 index 0000000..19c88d0 --- /dev/null +++ b/src/ent_miniworld.c @@ -0,0 +1,211 @@ +#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; ient_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; ient_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; ient_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); +} diff --git a/src/ent_miniworld.h b/src/ent_miniworld.h new file mode 100644 index 0000000..278cf75 --- /dev/null +++ b/src/ent_miniworld.h @@ -0,0 +1,18 @@ +#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); diff --git a/src/ent_npc.c b/src/ent_npc.c new file mode 100644 index 0000000..7a4b484 --- /dev/null +++ b/src/ent_npc.c @@ -0,0 +1,304 @@ +#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 ); +} diff --git a/src/ent_npc.h b/src/ent_npc.h new file mode 100644 index 0000000..c20cc97 --- /dev/null +++ b/src/ent_npc.h @@ -0,0 +1,28 @@ +#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); diff --git a/src/ent_objective.c b/src/ent_objective.c new file mode 100644 index 0000000..6a8bbe5 --- /dev/null +++ b/src/ent_objective.c @@ -0,0 +1,139 @@ +#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; +} diff --git a/src/ent_objective.h b/src/ent_objective.h new file mode 100644 index 0000000..9d94772 --- /dev/null +++ b/src/ent_objective.h @@ -0,0 +1,4 @@ +#pragma once +#include "entity.h" +#include "world.h" +entity_call_result ent_objective_call( world_instance *world, ent_call *call ); diff --git a/src/ent_region.c b/src/ent_region.c new file mode 100644 index 0000000..5e0ec03 --- /dev/null +++ b/src/ent_region.c @@ -0,0 +1,165 @@ +#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; ient_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; ient_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; ient_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; jent_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; jent_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(); + } +} diff --git a/src/ent_region.h b/src/ent_region.h new file mode 100644 index 0000000..6a86a32 --- /dev/null +++ b/src/ent_region.h @@ -0,0 +1,14 @@ +#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 ); diff --git a/src/ent_relay.c b/src/ent_relay.c new file mode 100644 index 0000000..b22b0b5 --- /dev/null +++ b/src/ent_relay.c @@ -0,0 +1,25 @@ +#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; itargets); 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; +} diff --git a/src/ent_relay.h b/src/ent_relay.h new file mode 100644 index 0000000..054570c --- /dev/null +++ b/src/ent_relay.h @@ -0,0 +1,3 @@ +#pragma once +#include "entity.h" +entity_call_result ent_relay_call( world_instance *world, ent_call *call ); diff --git a/src/ent_route.c b/src/ent_route.c new file mode 100644 index 0000000..8564eed --- /dev/null +++ b/src/ent_route.c @@ -0,0 +1,75 @@ +#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; + } +} diff --git a/src/ent_route.h b/src/ent_route.h new file mode 100644 index 0000000..cbf61b2 --- /dev/null +++ b/src/ent_route.h @@ -0,0 +1,11 @@ +#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 ); diff --git a/src/ent_skateshop.c b/src/ent_skateshop.c new file mode 100644 index 0000000..ded707c --- /dev/null +++ b/src/ent_skateshop.c @@ -0,0 +1,850 @@ +#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; icache_id ); + } + + for( u32 i=0; icache_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; ient_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; icache_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; jent_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; itransform, &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; +} diff --git a/src/ent_skateshop.h b/src/ent_skateshop.h new file mode 100644 index 0000000..2f8e3a6 --- /dev/null +++ b/src/ent_skateshop.h @@ -0,0 +1,54 @@ +#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); diff --git a/src/ent_tornado.c b/src/ent_tornado.c new file mode 100644 index 0000000..f9fca03 --- /dev/null +++ b/src/ent_tornado.c @@ -0,0 +1,87 @@ +#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; ient_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; ient_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; ient_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 ); + } + } +} diff --git a/src/ent_tornado.h b/src/ent_tornado.h new file mode 100644 index 0000000..f89c070 --- /dev/null +++ b/src/ent_tornado.h @@ -0,0 +1,6 @@ +#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); diff --git a/src/ent_traffic.c b/src/ent_traffic.c new file mode 100644 index 0000000..8bb19b9 --- /dev/null +++ b/src/ent_traffic.c @@ -0,0 +1,63 @@ +#include "world.h" + +void ent_traffic_update( world_instance *world, v3f pos ) +{ + for( u32 i=0; ient_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 ); + } +} diff --git a/src/ent_traffic.h b/src/ent_traffic.h new file mode 100644 index 0000000..18d8d1e --- /dev/null +++ b/src/ent_traffic.h @@ -0,0 +1,3 @@ +#pragma once +#include "world.h" +void ent_traffic_update( world_instance *world, v3f pos ); diff --git a/src/entity.c b/src/entity.c new file mode 100644 index 0000000..797e29d --- /dev/null +++ b/src/entity.c @@ -0,0 +1,77 @@ +#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 + +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; ipstr_alias ), alias ) ) + { + return marker; + } + } + + return NULL; +} + diff --git a/src/entity.h b/src/entity.h new file mode 100644 index 0000000..6d503f9 --- /dev/null +++ b/src/entity.h @@ -0,0 +1,582 @@ +#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 ); diff --git a/src/font.h b/src/font.h new file mode 100644 index 0000000..fa144c4 --- /dev/null +++ b/src/font.h @@ -0,0 +1,324 @@ +#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 + +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; ifont_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; +} diff --git a/src/freecam.c b/src/freecam.c new file mode 100644 index 0000000..d961ed6 --- /dev/null +++ b/src/freecam.c @@ -0,0 +1,44 @@ +#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 ); +} diff --git a/src/freecam.h b/src/freecam.h new file mode 100644 index 0000000..1f9f5e2 --- /dev/null +++ b/src/freecam.h @@ -0,0 +1,3 @@ +#pragma once +void freecam_preupdate(void); +int freecam_cmd( int argc, const char *argv[] ); diff --git a/src/gameserver.c b/src/gameserver.c new file mode 100644 index 0000000..e315fd0 --- /dev/null +++ b/src/gameserver.c @@ -0,0 +1,1136 @@ +/* + * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved + */ + +#define _DEFAULT_SOURCE +#include +#include +#include +#include + +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; iconnection ) + 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; isteamid ) + 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; jitems[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; iactive = 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; isteamid ) + 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; iinstance == 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<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; im_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; +} diff --git a/src/gameserver.h b/src/gameserver.h new file mode 100644 index 0000000..a03b1a0 --- /dev/null +++ b/src/gameserver.h @@ -0,0 +1,52 @@ +#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 + +#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; diff --git a/src/gameserver_db.h b/src/gameserver_db.h new file mode 100644 index 0000000..fe39931 --- /dev/null +++ b/src/gameserver_db.h @@ -0,0 +1,398 @@ +#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 +#include + +#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 */ diff --git a/src/gui.h b/src/gui.h new file mode 100644 index 0000000..46e2e1e --- /dev/null +++ b/src/gui.h @@ -0,0 +1,360 @@ +#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; icolour_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; ibinding, 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; iicon = 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 ); +} diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..3377824 --- /dev/null +++ b/src/input.h @@ -0,0 +1,311 @@ +#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 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; + } +} diff --git a/src/menu.c b/src/menu.c new file mode 100644 index 0000000..1f2b9c8 --- /dev/null +++ b/src/menu.c @@ -0,0 +1,1095 @@ +#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; ient_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; ifont = &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; ient_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; jent_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; jent_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; +} diff --git a/src/menu.h b/src/menu.h new file mode 100644 index 0000000..5a6dbc4 --- /dev/null +++ b/src/menu.h @@ -0,0 +1,56 @@ +#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); diff --git a/src/model.c b/src/model.c new file mode 100644 index 0000000..f3f7ab2 --- /dev/null +++ b/src/model.c @@ -0,0 +1,648 @@ +/* + * 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 +#include +#include +#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; iitem_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; iindex); 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; imaterials); 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; imeshs ); 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; ivertex_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; isubmeshs ); i++ ) + { + mdl_submesh *sm = mdl_arritm( &mdl->submeshs, i ); + u32 *indices = job->indices + sm->indice_start; + + for( u32 j=0; jindice_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; itextures ); 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 diff --git a/src/model.h b/src/model.h new file mode 100644 index 0000000..aade46a --- /dev/null +++ b/src/model.h @@ -0,0 +1,345 @@ +/* + * 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 ); + diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..0869612 --- /dev/null +++ b/src/network.c @@ -0,0 +1,789 @@ +#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 + #include +#else + #include + #include + #include +#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; icur_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; iticket[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; isteamid == 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; im_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; ibuffer = 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 ); + } +} diff --git a/src/network.h b/src/network.h new file mode 100644 index 0000000..6af51af --- /dev/null +++ b/src/network.h @@ -0,0 +1,98 @@ +/* + * 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; +} diff --git a/src/network_common.h b/src/network_common.h new file mode 100644 index 0000000..ca46e47 --- /dev/null +++ b/src/network_common.h @@ -0,0 +1,42 @@ +#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) && (_abytes+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 */ diff --git a/src/network_msg.h b/src/network_msg.h new file mode 100644 index 0000000..4dfeb5e --- /dev/null +++ b/src/network_msg.h @@ -0,0 +1,144 @@ +/* + * 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 */ diff --git a/src/particle.c b/src/particle.c new file mode 100644 index 0000000..ec2960b --- /dev/null +++ b/src/particle.c @@ -0,0 +1,187 @@ +#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; ialive; 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; iindices[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; ialive; 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 ); +} diff --git a/src/particle.h b/src/particle.h new file mode 100644 index 0000000..6858890 --- /dev/null +++ b/src/particle.h @@ -0,0 +1,42 @@ +#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 ); diff --git a/src/physics_test.h b/src/physics_test.h new file mode 100644 index 0000000..243de36 --- /dev/null +++ b/src/physics_test.h @@ -0,0 +1,290 @@ +/* + * 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; inode_count; i++ ) + { + mdl_node *pnode = mdl_node_from_id( mdl, i ); + + for( int j=0; jsubmesh_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 + +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; isystem_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; ibind ) 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; ialias, 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; istatus == 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; ipriority < 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 ); +} diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..ff8c2e1 --- /dev/null +++ b/src/player.h @@ -0,0 +1,197 @@ +#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 ); diff --git a/src/player_api.h b/src/player_api.h new file mode 100644 index 0000000..f7f4785 --- /dev/null +++ b/src/player_api.h @@ -0,0 +1,34 @@ +#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 +}; diff --git a/src/player_basic_info.c b/src/player_basic_info.c new file mode 100644 index 0000000..ffc7ae0 --- /dev/null +++ b/src/player_basic_info.c @@ -0,0 +1,34 @@ +#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; ikeyframes[i].co); + q_identity(pose->keyframes[i].q); + v3_fill(pose->keyframes[i].s,1.0f); + } +} diff --git a/src/player_basic_info.h b/src/player_basic_info.h new file mode 100644 index 0000000..815be67 --- /dev/null +++ b/src/player_basic_info.h @@ -0,0 +1,18 @@ +#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 ); + diff --git a/src/player_common.c b/src/player_common.c new file mode 100644 index 0000000..1ecbae9 --- /dev/null +++ b/src/player_common.c @@ -0,0 +1,290 @@ +#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 ); +} diff --git a/src/player_common.h b/src/player_common.h new file mode 100644 index 0000000..b32faee --- /dev/null +++ b/src/player_common.h @@ -0,0 +1,16 @@ +#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); diff --git a/src/player_dead.c b/src/player_dead.c new file mode 100644 index 0000000..43b6211 --- /dev/null +++ b/src/player_dead.c @@ -0,0 +1,205 @@ +#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; ipart_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; ibone_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; ibone_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; ibone_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; itransforms[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" ); +} diff --git a/src/player_dead.h b/src/player_dead.h new file mode 100644 index 0000000..93b0cc3 --- /dev/null +++ b/src/player_dead.h @@ -0,0 +1,32 @@ +#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 ); + diff --git a/src/player_drive.c b/src/player_drive.c new file mode 100644 index 0000000..0462037 --- /dev/null +++ b/src/player_drive.c @@ -0,0 +1,87 @@ +#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" ); +} diff --git a/src/player_drive.h b/src/player_drive.h new file mode 100644 index 0000000..9a5649d --- /dev/null +++ b/src/player_drive.h @@ -0,0 +1,21 @@ +#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); diff --git a/src/player_effects.c b/src/player_effects.c new file mode 100644 index 0000000..981c232 --- /dev/null +++ b/src/player_effects.c @@ -0,0 +1,37 @@ +#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; +} diff --git a/src/player_effects.h b/src/player_effects.h new file mode 100644 index 0000000..f148dbc --- /dev/null +++ b/src/player_effects.h @@ -0,0 +1,26 @@ +#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; +}; diff --git a/src/player_glide.c b/src/player_glide.c new file mode 100644 index 0000000..d9977ce --- /dev/null +++ b/src/player_glide.c @@ -0,0 +1,706 @@ +#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; janimator; + 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; itransform.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; istatus == 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; imeshs); 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; jsubmesh_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; icollider_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; jpart_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; ibone_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; ipart_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; iparent && !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; ipart_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; ipart_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; jparts[i].rb; + buf[j].rbb = &_null; + } + + rb_contact_count += l; + } + } + + /* + * self-collision + */ + for( int i=0; ipart_count-1; i ++ ){ + for( int j=i+1; jpart_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; kparts[i].rb; + buf[k].rbb = &rd->parts[j].rb; + } + + rb_contact_count += l; + } + } + } + + if( localplayer.drowned ) + { + for( int j=0; jpart_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; ico, 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; ipart_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; ipart_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; ipart_count; i++ ) + rb_update_matrices( &rd->parts[i].rb ); + } + + rb_ct *stress = NULL; + float max_stress = 1.0f; + + for( u32 i=0; ico, 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; icone_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(); + } +} diff --git a/src/player_ragdoll.h b/src/player_ragdoll.h new file mode 100644 index 0000000..08ab5e7 --- /dev/null +++ b/src/player_ragdoll.h @@ -0,0 +1,71 @@ +#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 ); diff --git a/src/player_remote.c b/src/player_remote.c new file mode 100644 index 0000000..6fdf04f --- /dev/null +++ b/src/player_remote.c @@ -0,0 +1,1159 @@ +#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; iworld_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; iactive ) + 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; iframes); 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; iframes); 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; isound_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; jsystem == 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; iflags |= 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; iactive ){ + 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; iactive ) + { + 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; iframes); 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; iactive ) 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; iactive || 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; jbone_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; jrender_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; iactive = (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; iusername)-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; isystem != 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; iactive || 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; iactive ) + { + 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; + } + } + } +} diff --git a/src/player_remote.h b/src/player_remote.h new file mode 100644 index 0000000..3e4b67e --- /dev/null +++ b/src/player_remote.h @@ -0,0 +1,107 @@ +#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 ); diff --git a/src/player_render.c b/src/player_render.c new file mode 100644 index 0000000..15dec10 --- /dev/null +++ b/src/player_render.c @@ -0,0 +1,629 @@ +#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; ibone_count; i ++ ){ + localplayer.skeleton_mirror[i] = 0; + } + + for( i32 i=1; ibone_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; jbone_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; ientity_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; ibone_count+1; i ++ ) + fixup_table[i] = 0; + + for( u32 i=1; iname ); + + for( u32 j=1; jbone_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; ibone_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; ibone_count-1; i ++ ){ + mirrored[i] = temp[i]; + } +} diff --git a/src/player_render.h b/src/player_render.h new file mode 100644 index 0000000..cfc48e7 --- /dev/null +++ b/src/player_render.h @@ -0,0 +1,82 @@ +#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); diff --git a/src/player_replay.c b/src/player_replay.c new file mode 100644 index 0000000..950ad78 --- /dev/null +++ b/src/player_replay.c @@ -0,0 +1,1207 @@ +#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 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; idata_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; ihead ) 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; icursor) && (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; ico, 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; irb; + + 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; istatehead->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; itime-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; jmouse[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; jmouse ) ) + { + 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 + */ +} diff --git a/src/player_replay.h b/src/player_replay.h new file mode 100644 index 0000000..313b4d4 --- /dev/null +++ b/src/player_replay.h @@ -0,0 +1,125 @@ +#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 ); diff --git a/src/player_skate.c b/src/player_skate.c new file mode 100644 index 0000000..aea7027 --- /dev/null +++ b/src/player_skate.c @@ -0,0 +1,3664 @@ +#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 + +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; igeo_bh, man, + k_material_flag_walking ); + + for( int i=0; i 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; ico, 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; jlog[jump->log_length-1], grind.co ); + float descale = 1.0f-vg_minf(1.0f,dist); + jump->score += descale*3.0f; + } + + for( int i=0; itype = 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; iscore < score_min ) + best = jump; + + score_min = vg_minf( score_min, jump->score ); + score_max = vg_maxf( score_max, jump->score ); + } + + for( int i=0; iscore; + + 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; ilog_length == 0 ){ + vg_fatal_error( "assert: jump->log_length == 0\n" ); + } + + for( int j=0; jlog_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; iactivity = 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 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; islap, 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; iactivity >= k_skate_activity_grind_any ){ + for( int i=0; ira, 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; ico, 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; iinfo.surface_prop > player_skate.surface ) + player_skate.surface = surf->info.surface_prop; + } + + for( int i=0; icog, 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; ikeyframes[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; ikeyframes[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; ikeyframes[ 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(); +} diff --git a/src/player_skate.h b/src/player_skate.h new file mode 100644 index 0000000..8adb024 --- /dev/null +++ b/src/player_skate.h @@ -0,0 +1,326 @@ +#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); diff --git a/src/player_walk.c b/src/player_walk.c new file mode 100644 index 0000000..1e15afc --- /dev/null +++ b/src/player_walk.c @@ -0,0 +1,1210 @@ +#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; inormal, 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; itype == k_contact_type_disabled || + ci->type == k_contact_type_edge ) + continue; + + + float d1 = v3_dot( ci->co, ci->n ); + + for( int j=0; jtype == 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; in ) ){ + 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; in ); + + 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; ibone_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(); +} diff --git a/src/player_walk.h b/src/player_walk.h new file mode 100644 index 0000000..5da0350 --- /dev/null +++ b/src/player_walk.h @@ -0,0 +1,113 @@ +#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 ); diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..fada4a5 --- /dev/null +++ b/src/render.c @@ -0,0 +1,258 @@ +#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(); +} diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..765f41b --- /dev/null +++ b/src/render.h @@ -0,0 +1,48 @@ +/* + * 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 ); diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..73eaa49 --- /dev/null +++ b/src/save.c @@ -0,0 +1,195 @@ +#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; ifile_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; + } + } +} diff --git a/src/save.h b/src/save.h new file mode 100644 index 0000000..acda301 --- /dev/null +++ b/src/save.h @@ -0,0 +1,29 @@ +#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 ); diff --git a/src/scene.c b/src/scene.c new file mode 100644 index 0000000..a94fbca --- /dev/null +++ b/src/scene.c @@ -0,0 +1,402 @@ +#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; ivertex_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; iindice_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; iarrvertices[ 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 ); +} diff --git a/src/scene.h b/src/scene.h new file mode 100644 index 0000000..8d6f57a --- /dev/null +++ b/src/scene.h @@ -0,0 +1,61 @@ +#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 ); diff --git a/src/scene_rigidbody.h b/src/scene_rigidbody.h new file mode 100644 index 0000000..57ff1ff --- /dev/null +++ b/src/scene_rigidbody.h @@ -0,0 +1,247 @@ +#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; +} + diff --git a/src/shader_props.h b/src/shader_props.h new file mode 100644 index 0000000..29e79f5 --- /dev/null +++ b/src/shader_props.h @@ -0,0 +1,37 @@ +#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; +}; diff --git a/src/skaterift.c b/src/skaterift.c new file mode 100644 index 0000000..36e087f --- /dev/null +++ b/src/skaterift.c @@ -0,0 +1,679 @@ +/* + * ============================================================================= + * + * Copyright . . . -----, ,----- ,---. .---. + * 2021-2024 |\ /| | / | | | | /| + * | \ / | +-- / +----- +---' | / | + * | \ / | | / | | \ | / | + * | \/ | | / | | \ | / | + * ' ' '--' [] '----- '----- ' ' '---' SOFTWARE + * + * ============================================================================= + */ + +#define SR_ALLOW_REWIND_HUB + +#ifdef _WIN32 + #include +#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; iub_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" diff --git a/src/skaterift.h b/src/skaterift.h new file mode 100644 index 0000000..e480d2e --- /dev/null +++ b/src/skaterift.h @@ -0,0 +1,33 @@ +#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; diff --git a/src/skaterift_lib.c b/src/skaterift_lib.c new file mode 100644 index 0000000..95b8141 --- /dev/null +++ b/src/skaterift_lib.c @@ -0,0 +1,31 @@ +#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; ibone_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; ico, 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; ibone_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; ibone_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; ibone_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; ibone_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; iik_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; ibone_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; ibone_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; iik_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; ianim_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; ibone_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; ibone_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; ianim_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; ibone_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 ); + } +} diff --git a/src/steam.c b/src/steam.c new file mode 100644 index 0000000..f28b8e7 --- /dev/null +++ b/src/steam.c @@ -0,0 +1,303 @@ +#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 + +/* + * 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; im_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> 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(); + } +} diff --git a/src/steam.h b/src/steam.h new file mode 100644 index 0000000..e2ed982 --- /dev/null +++ b/src/steam.h @@ -0,0 +1,19 @@ +/* + * 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); diff --git a/src/traffic.h b/src/traffic.h new file mode 100644 index 0000000..004c624 --- /dev/null +++ b/src/traffic.h @@ -0,0 +1,222 @@ +#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; imn_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; inext ); + 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 */ diff --git a/src/trail.c b/src/trail.c new file mode 100644 index 0000000..376a009 --- /dev/null +++ b/src/trail.c @@ -0,0 +1,187 @@ +#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; icount; 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; icount; 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; icount; 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 ); +} diff --git a/src/trail.h b/src/trail.h new file mode 100644 index 0000000..82c7d60 --- /dev/null +++ b/src/trail.h @@ -0,0 +1,33 @@ +#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 ); diff --git a/src/vehicle.c b/src/vehicle.c new file mode 100644 index 0000000..c0a3376 --- /dev/null +++ b/src/vehicle.c @@ -0,0 +1,279 @@ +#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 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 ); + } +} diff --git a/src/vehicle.h b/src/vehicle.h new file mode 100644 index 0000000..3e60bb7 --- /dev/null +++ b/src/vehicle.h @@ -0,0 +1,42 @@ +#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); diff --git a/src/workshop.c b/src/workshop.c new file mode 100644 index 0000000..7814b09 --- /dev/null +++ b/src/workshop.c @@ -0,0 +1,1638 @@ +#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; ititle, 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; ititle, 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; ibuffer[i] ); + if( state & k_EItemStateInstalled ){ + info->buffer[j ++] = info->buffer[i]; + } + } + + *info->len = j; +} diff --git a/src/workshop.h b/src/workshop.h new file mode 100644 index 0000000..08776df --- /dev/null +++ b/src/workshop.h @@ -0,0 +1,134 @@ +#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 ); diff --git a/src/world.c b/src/world.c new file mode 100644 index 0000000..e6d6c31 --- /dev/null +++ b/src/world.c @@ -0,0 +1,110 @@ +/* + * 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 \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 ); +} diff --git a/src/world.h b/src/world.h new file mode 100644 index 0000000..3a067db --- /dev/null +++ b/src/world.h @@ -0,0 +1,274 @@ +/* + * 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 ); diff --git a/src/world_audio.c b/src/world_audio.c new file mode 100644 index 0000000..b33d62f --- /dev/null +++ b/src/world_audio.c @@ -0,0 +1,139 @@ +#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; iallocated && (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; +} diff --git a/src/world_audio.h b/src/world_audio.h new file mode 100644 index 0000000..07d66d1 --- /dev/null +++ b/src/world_audio.h @@ -0,0 +1,7 @@ +#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 ); diff --git a/src/world_entity.c b/src/world_entity.c new file mode 100644 index 0000000..b85e6fa --- /dev/null +++ b/src/world_entity.c @@ -0,0 +1,876 @@ +#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; jent_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; jent_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; jent_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; jent_audio); j++ ){ + ent_audio *audio = mdl_arritm( &world->ent_audio, j ); + + for( u32 k=0; kclip_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; ientity_list = vg_linear_alloc( world->heap, + vg_align8(indexed_count*sizeof(u32))); + + u32 index=0; + for( u32 i=0; ientity_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; ient_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; ient_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; ient_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; iclip_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; isubmesh_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; isubmesh_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; ient_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; ient_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; ient_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; ient_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; jcheckpoints_count; j ++ ) + sections[j] = 0.0f; + } + + for( u32 j=0; jcheckpoints_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; jname, + 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; ient_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; ient_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; jcheckpoints_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 ); + } +} diff --git a/src/world_entity.h b/src/world_entity.h new file mode 100644 index 0000000..c954052 --- /dev/null +++ b/src/world_entity.h @@ -0,0 +1,46 @@ +#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; diff --git a/src/world_gate.c b/src/world_gate.c new file mode 100644 index 0000000..3e6fdbb --- /dev/null +++ b/src/world_gate.c @@ -0,0 +1,410 @@ +/* + * 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 + +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; isubmesh_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; ient_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; jent_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; jent_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; istatus != k_world_status_loaded ) continue; + vg_info( "Checking world %u for key matches\n", i ); + + for( u32 k=0; kent_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 */ diff --git a/src/world_gate.h b/src/world_gate.h new file mode 100644 index 0000000..a071e4b --- /dev/null +++ b/src/world_gate.h @@ -0,0 +1,36 @@ +/* + * 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 ); diff --git a/src/world_gen.c b/src/world_gen.c new file mode 100644 index 0000000..e51836d --- /dev/null +++ b/src/world_gen.c @@ -0,0 +1,776 @@ +/* + * 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 + +/* + * 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; imeshs); i++ ){ + mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i ); + + for( u32 j=0; jsubmesh_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; ico, pvert->co ); + scene_vert_pack_norm( pvert, transform[1], 0.0f ); + + v2_copy( ref->uv, pvert->uv ); + } + + for( u32 i=0; ivertex_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; iscene_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; xscene_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; isurface_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; isurface_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; ient_traffic ); i++ ){ + ent_traffic *vehc = mdl_arritm( &world->ent_traffic, i ); + + for( u32 j=0; jsubmesh_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; ient_objective ); i++ ){ + ent_objective *objective = mdl_arritm( &world->ent_objective, i ); + + for( u32 j=0; jsubmesh_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; ient_region ); i++ ){ + ent_region *region = mdl_arritm( &world->ent_region, i ); + + for( u32 j=0; jsubmesh_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; ient_gate ); i++ ){ + ent_gate *gate = mdl_arritm( &world->ent_gate, i ); + + if( !(gate->flags & k_ent_gate_custom_mesh) ) continue; + + for( u32 j=0; jsubmesh_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; ient_prop ); i++ ){ + ent_prop *prop = mdl_arritm( &world->ent_prop, i ); + + for( u32 j=0; jsubmesh_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; izub_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; jent_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 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; ient_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; ient_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; imeta.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; imeta.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; + } +} diff --git a/src/world_gen.h b/src/world_gen.h new file mode 100644 index 0000000..c6ffb92 --- /dev/null +++ b/src/world_gen.h @@ -0,0 +1,15 @@ +/* + * 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 ); diff --git a/src/world_info.h b/src/world_info.h new file mode 100644 index 0000000..3b29b1f --- /dev/null +++ b/src/world_info.h @@ -0,0 +1,44 @@ +/* + * 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 diff --git a/src/world_load.c b/src/world_load.c new file mode 100644 index 0000000..30c2b2c --- /dev/null +++ b/src/world_load.c @@ -0,0 +1,553 @@ +#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 + +/* + * 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; ient_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; jpayload; + 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; istatus == 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; ialias, 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; iallocated && (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; ient_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 +} diff --git a/src/world_load.h b/src/world_load.h new file mode 100644 index 0000000..038233d --- /dev/null +++ b/src/world_load.h @@ -0,0 +1,11 @@ +#pragma once +#include + +#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); diff --git a/src/world_map.c b/src/world_map.c new file mode 100644 index 0000000..2799428 --- /dev/null +++ b/src/world_map.c @@ -0,0 +1,313 @@ +#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; ient_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; ient_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; ient_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; ient_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; ient_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; ient_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; ient_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 ); + } +} diff --git a/src/world_map.h b/src/world_map.h new file mode 100644 index 0000000..3899363 --- /dev/null +++ b/src/world_map.h @@ -0,0 +1,19 @@ +#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); diff --git a/src/world_physics.c b/src/world_physics.c new file mode 100644 index 0000000..03be1fc --- /dev/null +++ b/src/world_physics.c @@ -0,0 +1,107 @@ +#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; isurface_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 */ diff --git a/src/world_physics.h b/src/world_physics.h new file mode 100644 index 0000000..06143b9 --- /dev/null +++ b/src/world_physics.h @@ -0,0 +1,24 @@ +#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 ); diff --git a/src/world_render.c b/src/world_render.c new file mode 100644 index 0000000..3eb07a1 --- /dev/null +++ b/src/world_render.c @@ -0,0 +1,1377 @@ +/* + * 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; iubo_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; iheightmap = 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; kmeta.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; jent_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; jent_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; isurface_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; ient_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; jsubmesh_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; ient_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; ient_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; ient_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; jsubmesh_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; ient_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; jent_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; jent_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; ient_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; isurface_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; ient_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 ); + } +} diff --git a/src/world_render.h b/src/world_render.h new file mode 100644 index 0000000..1eb5ab1 --- /dev/null +++ b/src/world_render.h @@ -0,0 +1,95 @@ +/* + * 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 ); + diff --git a/src/world_routes.c b/src/world_routes.c new file mode 100644 index 0000000..e4fd80e --- /dev/null +++ b/src/world_routes.c @@ -0,0 +1,1089 @@ +#pragma once + +/* + * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved + * + * World routes + */ + +#include +#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; ient_route ); i++ ){ + ent_route *route = mdl_arritm( &world->ent_route, i ); + route->active_checkpoint = 0xffff; + } + + for( u32 i=0; ient_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; icheckpoints_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; ient_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; kcheckpoints_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; ient_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; ient_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; icheckpoints_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; jpath_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; icheckpoints_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; jpath_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; ient_gate); i++ ){ + ent_gate *gate = mdl_arritm( &world->ent_gate, i ); + gate->ref_count = 0; + gate->route_count = 0; + } + + for( u32 i=0; ient_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; kent_route); k++ ){ + ent_route *route = mdl_arritm( &world->ent_route, k ); + + for( int i=0; icheckpoints_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; jpath_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; ient_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; ient_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; ient_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; jmeta,route->pstr_name))){ + route->anon.official_track_id = j; + } + } + + for( u32 j=0; jcheckpoints_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; ient_gate); i++ ){ + ent_gate *gate = mdl_arritm( &world->ent_gate, i ); + } + + for( u32 i=0; ient_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; ient_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; iobj, 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; irb.to_world, + particle->radius, + NULL, world->geo_bh, buf, + k_material_flag_ghosts ); + + for( int j=0; jrb; + 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; irb ); + } + + for( u32 i=0; irb ); + } +} + +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; ient_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; igate != 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; ient_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; iub_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; imdl, 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; ient_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; jent_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; ient_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 } ); +} diff --git a/src/world_routes.h b/src/world_routes.h new file mode 100644 index 0000000..621c28a --- /dev/null +++ b/src/world_routes.h @@ -0,0 +1,29 @@ +/* + * 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 ); diff --git a/src/world_routes_ui.c b/src/world_routes_ui.c new file mode 100644 index 0000000..0afbeae --- /dev/null +++ b/src/world_routes_ui.c @@ -0,0 +1,168 @@ +#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; icheckpoints_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; ilength * 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; icheckpoints_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; ient_route); i++ ) + { + ent_route_imgui( ctx, world, mdl_arritm( &world->ent_route, i ), cursor ); + } +} diff --git a/src/world_routes_ui.h b/src/world_routes_ui.h new file mode 100644 index 0000000..70c0fcd --- /dev/null +++ b/src/world_routes_ui.h @@ -0,0 +1,5 @@ +#pragma once +#include "world_routes.h" + +struct route_ui{}; +void world_routes_imgui( ui_context *ctx, world_instance *world ); diff --git a/src/world_sfd.c b/src/world_sfd.c new file mode 100644 index 0000000..6473d63 --- /dev/null +++ b/src/world_sfd.c @@ -0,0 +1,362 @@ +#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) || (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; ient_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 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;ymtx.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; jvertex_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; ient_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 + 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:; + } +} diff --git a/src/world_volumes.h b/src/world_volumes.h new file mode 100644 index 0000000..2d84e9e --- /dev/null +++ b/src/world_volumes.h @@ -0,0 +1,5 @@ +#pragma once +#include "world.h" +#include "vg/vg_bvh.h" + +void world_volumes_update( world_instance *world, v3f pos ); diff --git a/src/world_water.c b/src/world_water.c new file mode 100644 index 0000000..3d95f09 --- /dev/null +++ b/src/world_water.c @@ -0,0 +1,263 @@ +/* + * 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; isurface_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; isurface_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; +} diff --git a/src/world_water.h b/src/world_water.h new file mode 100644 index 0000000..7ff9af5 --- /dev/null +++ b/src/world_water.h @@ -0,0 +1,18 @@ +/* + * 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 ); diff --git a/steam.c b/steam.c deleted file mode 100644 index f28b8e7..0000000 --- a/steam.c +++ /dev/null @@ -1,303 +0,0 @@ -#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 - -/* - * 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; im_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> 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(); - } -} diff --git a/steam.h b/steam.h deleted file mode 100644 index e2ed982..0000000 --- a/steam.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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); diff --git a/traffic.h b/traffic.h deleted file mode 100644 index 004c624..0000000 --- a/traffic.h +++ /dev/null @@ -1,222 +0,0 @@ -#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; imn_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; inext ); - 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 */ diff --git a/trail.c b/trail.c deleted file mode 100644 index 376a009..0000000 --- a/trail.c +++ /dev/null @@ -1,187 +0,0 @@ -#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; icount; 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; icount; 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; icount; 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 ); -} diff --git a/trail.h b/trail.h deleted file mode 100644 index 82c7d60..0000000 --- a/trail.h +++ /dev/null @@ -1,33 +0,0 @@ -#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 ); diff --git a/vehicle.c b/vehicle.c deleted file mode 100644 index c0a3376..0000000 --- a/vehicle.c +++ /dev/null @@ -1,279 +0,0 @@ -#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 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 ); - } -} diff --git a/vehicle.h b/vehicle.h deleted file mode 100644 index 3e60bb7..0000000 --- a/vehicle.h +++ /dev/null @@ -1,42 +0,0 @@ -#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); diff --git a/workshop.c b/workshop.c deleted file mode 100644 index 7814b09..0000000 --- a/workshop.c +++ /dev/null @@ -1,1638 +0,0 @@ -#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; ititle, 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; ititle, 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; ibuffer[i] ); - if( state & k_EItemStateInstalled ){ - info->buffer[j ++] = info->buffer[i]; - } - } - - *info->len = j; -} diff --git a/workshop.h b/workshop.h deleted file mode 100644 index 08776df..0000000 --- a/workshop.h +++ /dev/null @@ -1,134 +0,0 @@ -#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 ); diff --git a/world.c b/world.c deleted file mode 100644 index e6d6c31..0000000 --- a/world.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 \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 ); -} diff --git a/world.h b/world.h deleted file mode 100644 index 3a067db..0000000 --- a/world.h +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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 ); diff --git a/world_audio.c b/world_audio.c deleted file mode 100644 index b33d62f..0000000 --- a/world_audio.c +++ /dev/null @@ -1,139 +0,0 @@ -#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; iallocated && (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; -} diff --git a/world_audio.h b/world_audio.h deleted file mode 100644 index 07d66d1..0000000 --- a/world_audio.h +++ /dev/null @@ -1,7 +0,0 @@ -#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 ); diff --git a/world_entity.c b/world_entity.c deleted file mode 100644 index b85e6fa..0000000 --- a/world_entity.c +++ /dev/null @@ -1,876 +0,0 @@ -#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; jent_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; jent_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; jent_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; jent_audio); j++ ){ - ent_audio *audio = mdl_arritm( &world->ent_audio, j ); - - for( u32 k=0; kclip_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; ientity_list = vg_linear_alloc( world->heap, - vg_align8(indexed_count*sizeof(u32))); - - u32 index=0; - for( u32 i=0; ientity_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; ient_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; ient_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; ient_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; iclip_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; isubmesh_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; isubmesh_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; ient_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; ient_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; ient_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; ient_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; jcheckpoints_count; j ++ ) - sections[j] = 0.0f; - } - - for( u32 j=0; jcheckpoints_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; jname, - 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; ient_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; ient_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; jcheckpoints_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 ); - } -} diff --git a/world_entity.h b/world_entity.h deleted file mode 100644 index c954052..0000000 --- a/world_entity.h +++ /dev/null @@ -1,46 +0,0 @@ -#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; diff --git a/world_gate.c b/world_gate.c deleted file mode 100644 index 3e6fdbb..0000000 --- a/world_gate.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * 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 - -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; isubmesh_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; ient_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; jent_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; jent_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; istatus != k_world_status_loaded ) continue; - vg_info( "Checking world %u for key matches\n", i ); - - for( u32 k=0; kent_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 */ diff --git a/world_gate.h b/world_gate.h deleted file mode 100644 index a071e4b..0000000 --- a/world_gate.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 ); diff --git a/world_gen.c b/world_gen.c deleted file mode 100644 index e51836d..0000000 --- a/world_gen.c +++ /dev/null @@ -1,776 +0,0 @@ -/* - * 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 - -/* - * 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; imeshs); i++ ){ - mdl_mesh *mesh = mdl_arritm( &mdl->meshs, i ); - - for( u32 j=0; jsubmesh_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; ico, pvert->co ); - scene_vert_pack_norm( pvert, transform[1], 0.0f ); - - v2_copy( ref->uv, pvert->uv ); - } - - for( u32 i=0; ivertex_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; iscene_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; xscene_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; isurface_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; isurface_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; ient_traffic ); i++ ){ - ent_traffic *vehc = mdl_arritm( &world->ent_traffic, i ); - - for( u32 j=0; jsubmesh_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; ient_objective ); i++ ){ - ent_objective *objective = mdl_arritm( &world->ent_objective, i ); - - for( u32 j=0; jsubmesh_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; ient_region ); i++ ){ - ent_region *region = mdl_arritm( &world->ent_region, i ); - - for( u32 j=0; jsubmesh_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; ient_gate ); i++ ){ - ent_gate *gate = mdl_arritm( &world->ent_gate, i ); - - if( !(gate->flags & k_ent_gate_custom_mesh) ) continue; - - for( u32 j=0; jsubmesh_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; ient_prop ); i++ ){ - ent_prop *prop = mdl_arritm( &world->ent_prop, i ); - - for( u32 j=0; jsubmesh_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; izub_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; jent_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 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; ient_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; ient_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; imeta.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; imeta.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; - } -} diff --git a/world_gen.h b/world_gen.h deleted file mode 100644 index c6ffb92..0000000 --- a/world_gen.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 ); diff --git a/world_info.h b/world_info.h deleted file mode 100644 index 3b29b1f..0000000 --- a/world_info.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 diff --git a/world_load.c b/world_load.c deleted file mode 100644 index 30c2b2c..0000000 --- a/world_load.c +++ /dev/null @@ -1,553 +0,0 @@ -#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 - -/* - * 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; ient_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; jpayload; - 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; istatus == 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; ialias, 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; iallocated && (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; ient_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 -} diff --git a/world_load.h b/world_load.h deleted file mode 100644 index 038233d..0000000 --- a/world_load.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include - -#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); diff --git a/world_map.c b/world_map.c deleted file mode 100644 index 2799428..0000000 --- a/world_map.c +++ /dev/null @@ -1,313 +0,0 @@ -#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; ient_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; ient_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; ient_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; ient_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; ient_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; ient_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; ient_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 ); - } -} diff --git a/world_map.h b/world_map.h deleted file mode 100644 index 3899363..0000000 --- a/world_map.h +++ /dev/null @@ -1,19 +0,0 @@ -#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); diff --git a/world_physics.c b/world_physics.c deleted file mode 100644 index 03be1fc..0000000 --- a/world_physics.c +++ /dev/null @@ -1,107 +0,0 @@ -#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; isurface_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 */ diff --git a/world_physics.h b/world_physics.h deleted file mode 100644 index 06143b9..0000000 --- a/world_physics.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 ); diff --git a/world_render.c b/world_render.c deleted file mode 100644 index 3eb07a1..0000000 --- a/world_render.c +++ /dev/null @@ -1,1377 +0,0 @@ -/* - * 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; iubo_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; iheightmap = 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; kmeta.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; jent_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; jent_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; isurface_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; ient_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; jsubmesh_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; ient_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; ient_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; ient_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; jsubmesh_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; ient_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; jent_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; jent_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; ient_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; isurface_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; ient_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 ); - } -} diff --git a/world_render.h b/world_render.h deleted file mode 100644 index 1eb5ab1..0000000 --- a/world_render.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 ); - diff --git a/world_routes.c b/world_routes.c deleted file mode 100644 index e4fd80e..0000000 --- a/world_routes.c +++ /dev/null @@ -1,1089 +0,0 @@ -#pragma once - -/* - * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved - * - * World routes - */ - -#include -#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; ient_route ); i++ ){ - ent_route *route = mdl_arritm( &world->ent_route, i ); - route->active_checkpoint = 0xffff; - } - - for( u32 i=0; ient_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; icheckpoints_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; ient_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; kcheckpoints_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; ient_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; ient_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; icheckpoints_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; jpath_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; icheckpoints_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; jpath_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; ient_gate); i++ ){ - ent_gate *gate = mdl_arritm( &world->ent_gate, i ); - gate->ref_count = 0; - gate->route_count = 0; - } - - for( u32 i=0; ient_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; kent_route); k++ ){ - ent_route *route = mdl_arritm( &world->ent_route, k ); - - for( int i=0; icheckpoints_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; jpath_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; ient_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; ient_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; ient_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; jmeta,route->pstr_name))){ - route->anon.official_track_id = j; - } - } - - for( u32 j=0; jcheckpoints_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; ient_gate); i++ ){ - ent_gate *gate = mdl_arritm( &world->ent_gate, i ); - } - - for( u32 i=0; ient_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; ient_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; iobj, 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; irb.to_world, - particle->radius, - NULL, world->geo_bh, buf, - k_material_flag_ghosts ); - - for( int j=0; jrb; - 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; irb ); - } - - for( u32 i=0; irb ); - } -} - -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; ient_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; igate != 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; ient_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; iub_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; imdl, 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; ient_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; jent_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; ient_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 } ); -} diff --git a/world_routes.h b/world_routes.h deleted file mode 100644 index 621c28a..0000000 --- a/world_routes.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 ); diff --git a/world_routes_ui.c b/world_routes_ui.c deleted file mode 100644 index 0afbeae..0000000 --- a/world_routes_ui.c +++ /dev/null @@ -1,168 +0,0 @@ -#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; icheckpoints_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; ilength * 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; icheckpoints_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; ient_route); i++ ) - { - ent_route_imgui( ctx, world, mdl_arritm( &world->ent_route, i ), cursor ); - } -} diff --git a/world_routes_ui.h b/world_routes_ui.h deleted file mode 100644 index 70c0fcd..0000000 --- a/world_routes_ui.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "world_routes.h" - -struct route_ui{}; -void world_routes_imgui( ui_context *ctx, world_instance *world ); diff --git a/world_sfd.c b/world_sfd.c deleted file mode 100644 index 6473d63..0000000 --- a/world_sfd.c +++ /dev/null @@ -1,362 +0,0 @@ -#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) || (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; ient_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 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;ymtx.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; jvertex_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; ient_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 - 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:; - } -} diff --git a/world_volumes.h b/world_volumes.h deleted file mode 100644 index 2d84e9e..0000000 --- a/world_volumes.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "world.h" -#include "vg/vg_bvh.h" - -void world_volumes_update( world_instance *world, v3f pos ); diff --git a/world_water.c b/world_water.c deleted file mode 100644 index 3d95f09..0000000 --- a/world_water.c +++ /dev/null @@ -1,263 +0,0 @@ -/* - * 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; isurface_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; isurface_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; -} diff --git a/world_water.h b/world_water.h deleted file mode 100644 index 7ff9af5..0000000 --- a/world_water.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 );