create application directory in appdata

This commit is contained in:
jacob 2026-01-09 11:10:41 -06:00
parent a00fcf3fba
commit 2bfc0d2ab2
16 changed files with 328 additions and 374 deletions

View File

@ -33,6 +33,10 @@
#error Missing compile time definition for 'IsHotSwappingEnabled' #error Missing compile time definition for 'IsHotSwappingEnabled'
#endif #endif
#ifndef DefaultAppName
#error Default application name not defined
#endif
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Machine context //~ Machine context
@ -784,6 +788,7 @@ u64 MixU64s(u64 seed_a, u64 seed_b)
#if IsLanguageC #if IsLanguageC
StringList GetRawCommandline(void); StringList GetRawCommandline(void);
String GetAppDirectory(void);
void Echo(String msg); void Echo(String msg);
b32 Panic(String msg); b32 Panic(String msg);
Callstack CaptureCallstack(u64 skip_frames); Callstack CaptureCallstack(u64 skip_frames);

View File

@ -177,24 +177,31 @@ Vec4 CR_Vec4FromString(String str)
b32 ok = 1; b32 ok = 1;
if (StringBeginsWith(str, Lit("("))) // if (StringBeginsWith(str, Lit("(")))
{ // {
str.text += 1; // str.text += 1;
str.len -= 1; // str.len -= 1;
} // }
if (StringEndsWith(str, Lit(")"))) // if (StringEndsWith(str, Lit(")")))
{ // {
str.len -= 1; // str.len -= 1;
} // }
u64 pos = 0; u64 pos = 0;
u64 parts_found = 0; u64 parts_found = 0;
String part = Zi; String part = Zi;
part.text = str.text; part.text = str.text;
if (StringBeginsWith(str, Lit("(")))
{
part.text += 1;
pos += 1;
}
while (pos < str.len && parts_found < 4) while (pos < str.len && parts_found < 4)
{ {
u8 c = str.text[pos]; u8 c = str.text[pos];
if (c == ',' || c == ')' || pos + 1 >= str.len) u8 next_c = 0;
if (c == ',' || c == ')')
{ {
part.len = (str.text + pos) - part.text; part.len = (str.text + pos) - part.text;
f64 part_float = CR_FloatFromString(part); f64 part_float = CR_FloatFromString(part);
@ -347,16 +354,7 @@ CR_Item *CR_ItemFromString(Arena *arena, String str)
if (text_end != -1) if (text_end != -1)
{ {
if (!cur_item)
{
cur_item = PushStruct(arena, CR_Item);
cur_item->parent = top_item->item;
DllQueuePush(cur_item->parent->first, cur_item->parent->last, cur_item);
cur_item->parent->count += 1;
}
is_escaped = 0; is_escaped = 0;
text_is_string = 0;
String src_text = Zi; String src_text = Zi;
if (text_end > text_start) if (text_end > text_start)
@ -365,8 +363,12 @@ CR_Item *CR_ItemFromString(Arena *arena, String str)
src_text.text = str.text + text_start; src_text.text = str.text + text_start;
} }
if (is_name) if (!cur_item)
{ {
cur_item = PushStruct(arena, CR_Item);
cur_item->parent = top_item->item;
DllQueuePush(cur_item->parent->first, cur_item->parent->last, cur_item);
cur_item->parent->count += 1;
} }
// FIXME: Arrays always have an item even if empty // FIXME: Arrays always have an item even if empty
@ -406,9 +408,8 @@ CR_Item *CR_ItemFromString(Arena *arena, String str)
} }
} }
mode = Mode_None; mode = Mode_None;
text_is_string = 0;
} }
pos += 1; pos += 1;

View File

@ -482,6 +482,21 @@ String StringFromArray(Arena *arena, StringArray a)
return result; return result;
} }
String PathFromString(Arena *arena, String str, u8 path_delimiter)
{
String result = Zi;
result = PushString(arena, str);
for (u64 char_idx = 0; char_idx < result.len; ++char_idx)
{
u8 c = result.text[char_idx];
if ((c == '\\' || c == '/') && c != path_delimiter)
{
result.text[char_idx] = path_delimiter;
}
}
return result;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ String list helpers //~ String list helpers

View File

@ -1,7 +1,7 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Formatting types //~ Formatting types
#define DefaultFmtPrecision 3 #define DefaultFmtPrecision 6
#define Base16Chars ("0123456789abcdef") #define Base16Chars ("0123456789abcdef")
Enum(FmtArgKind) Enum(FmtArgKind)
@ -100,6 +100,7 @@ b32 StringContains(String str, String substring);
b32 StringBeginsWith(String str, String substring); b32 StringBeginsWith(String str, String substring);
b32 StringEndsWith(String str, String substring); b32 StringEndsWith(String str, String substring);
String StringFromArray(Arena *arena, StringArray a); String StringFromArray(Arena *arena, StringArray a);
String PathFromString(Arena *arena, String str, u8 path_delimiter);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Trimming helpers //~ Trimming helpers

View File

@ -189,6 +189,11 @@ void SleepSeconds(f64 seconds)
} }
} }
String GetAppDirectory(void)
{
return W32.appdir_path;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ @hookimpl Swap //~ @hookimpl Swap
@ -206,7 +211,7 @@ String SwappedStateFromName(Arena *arena, String name)
{ {
TempArena scratch = BeginScratch(arena); TempArena scratch = BeginScratch(arena);
String result = Zi; String result = Zi;
String path = StringF(scratch.arena, "ppswap/%F.swp", FmtString(name)); String path = StringF(scratch.arena, "%F/swap/%F.swp", FmtString(GetAppDirectory()), FmtString(name));
wchar_t *path_wstr = WstrFromString(scratch.arena, path); wchar_t *path_wstr = WstrFromString(scratch.arena, path);
HANDLE handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); HANDLE handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (handle != INVALID_HANDLE_VALUE) if (handle != INVALID_HANDLE_VALUE)
@ -234,11 +239,11 @@ String SwappedStateFromName(Arena *arena, String name)
void WriteSwappedState(String name, String data) void WriteSwappedState(String name, String data)
{ {
TempArena scratch = BeginScratchNoConflict(); TempArena scratch = BeginScratchNoConflict();
// TODO: Use directory non-relative to executable String dir_path = PathFromString(scratch.arena, StringF(scratch.arena, "%F/swap", FmtString(GetAppDirectory())), '\\');
CreateDirectoryW(L"ppswap", 0); String path = PathFromString(scratch.arena, StringF(scratch.arena, "%F/%F.swp", FmtString(dir_path), FmtString(name)), '\\');
String result = Zi; wchar_t *dir_path_wstr = WstrFromString(scratch.arena, dir_path);
String path = StringF(scratch.arena, "ppswap/%F.swp", FmtString(name));
wchar_t *path_wstr = WstrFromString(scratch.arena, path); wchar_t *path_wstr = WstrFromString(scratch.arena, path);
SHCreateDirectoryExW(0, dir_path_wstr, 0);
HANDLE handle = CreateFileW(path_wstr, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); HANDLE handle = CreateFileW(path_wstr, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (handle != INVALID_HANDLE_VALUE) if (handle != INVALID_HANDLE_VALUE)
{ {
@ -277,16 +282,14 @@ void ExitNow(i32 code)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Log //~ Log
void W32_BootstrapLogs(String logfile_path) void W32_BootstrapLogs(void)
{ {
Arena *perm = PermArena();
W32.logs_arena = AcquireArena(Gibi(64)); W32.logs_arena = AcquireArena(Gibi(64));
W32.log_msgs_arena = AcquireArena(Gibi(64)); W32.log_msgs_arena = AcquireArena(Gibi(64));
W32.readable_log_events = ArenaNext(W32.logs_arena, LogEvent); W32.readable_log_events = ArenaNext(W32.logs_arena, LogEvent);
if (logfile_path.len > 0) String logfile_path = StringF(perm, "%F/log.log", FmtString(GetAppDirectory()));
{ wchar_t *path_wstr = WstrFromString(perm, logfile_path);
TempArena scratch = BeginScratchNoConflict();
{
wchar_t *path_wstr = WstrFromString(scratch.arena, logfile_path);
W32.logfile = CreateFileW( W32.logfile = CreateFileW(
path_wstr, path_wstr,
FILE_APPEND_DATA, FILE_APPEND_DATA,
@ -296,10 +299,8 @@ void W32_BootstrapLogs(String logfile_path)
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
0 0
); );
}
EndScratch(scratch);
}
Atomic32Set(&W32.logs_initialized, 1); Atomic32Set(&W32.logs_initialized, 1);
LogInfoF("Log file path: %F", FmtString(logfile_path));
} }
void W32_Log(i32 level, String msg) void W32_Log(i32 level, String msg)
@ -443,8 +444,8 @@ i32 W32_Main(void)
W32_InitCurrentThread(Lit("Main")); W32_InitCurrentThread(Lit("Main"));
// Get raw args from command line // Get raw args from command line
{
Arena *perm = PermArena(); Arena *perm = PermArena();
{
StringList args_list = Zi; StringList args_list = Zi;
{ {
LPCWSTR cmdline_wstr = GetCommandLineW(); LPCWSTR cmdline_wstr = GetCommandLineW();
@ -469,10 +470,78 @@ i32 W32_Main(void)
// Bootstrap command line // Bootstrap command line
BootstrapCmdline(); BootstrapCmdline();
// Init app directory
String appdir_error = Zi;
{
String appdir_path = Zi;
CommandlineArg appdir_arg = CommandlineArgFromName(Lit("appdir"));
CommandlineArg appname_arg = CommandlineArgFromName(Lit("appname"));
if (appdir_arg.exists && appdir_arg.value.len > 0)
{
appdir_path = appdir_arg.value;
}
else
{
String appname = Lit(Stringize(DefaultAppName));
if (appname_arg.exists && appname_arg.value.len > 0)
{
appname = appname_arg.value;
}
wchar_t *path_wstr = 0;
HRESULT hr = SHGetKnownFolderPath(&FOLDERID_LocalAppData, 0, 0, &path_wstr);
if (!SUCCEEDED(hr))
{
Panic(Lit("Failed to locate AppData directory"));
}
appdir_path = StringFromWstrNoLimit(perm, path_wstr);
CoTaskMemFree(path_wstr);
appdir_path = PathFromString(perm, StringF(perm, "%F\\Cabin\\%F\\", FmtString(appdir_path), FmtString(appname)), '/');
}
// Create app dir
{
String path = PathFromString(perm, appdir_path, '\\');
wchar_t *path_wstr = WstrFromString(perm, appdir_path);
i32 err_code = SHCreateDirectoryExW(0, path_wstr, 0);
String err = StringF(perm, "Error code %F", FmtSint(err_code));
switch (err_code)
{
default: break;
case ERROR_BAD_PATHNAME:
{
err = Lit("Bad path name");
} break;
case ERROR_FILENAME_EXCED_RANGE:
{
err = Lit("Path name too long");
} break;
case ERROR_CANCELLED:
{
err = Lit("User canceled the operation");
} break;
}
if (err_code != ERROR_SUCCESS && err_code != ERROR_ALREADY_EXISTS && err_code != ERROR_FILE_EXISTS)
{
appdir_error = StringF(
perm,
"Failed to initialize app directory at \"%F\": %F",
FmtString(path),
FmtString(err)
);
wchar_t *msg_wstr = WstrFromString(perm, appdir_error);
MessageBoxExW(0, msg_wstr, L"Warning", MB_ICONWARNING | MB_SETFOREGROUND | MB_TOPMOST, 0);
}
}
W32.appdir_path = appdir_path;
}
// Bootstrap log system // Bootstrap log system
// FIXME: Remove hardcoded log path W32_BootstrapLogs();
W32_BootstrapLogs(Lit("log.log"));
LogInfoF("Main thread ID: %F", FmtUint(ThreadId())); LogInfoF("Main thread ID: %F", FmtUint(ThreadId()));
if (appdir_error.len > 0)
{
LogError(appdir_error);
}
// Bootstrap resource system // Bootstrap resource system
{ {

View File

@ -68,6 +68,7 @@ Struct(W32_Ctx)
i64 ns_per_qpc; i64 ns_per_qpc;
StringList raw_command_line; StringList raw_command_line;
String appdir_path;
//- Application control flow //- Application control flow
@ -107,7 +108,7 @@ BOOL W32_FindEmbeddedRcData(HMODULE module, LPCWSTR type, LPWSTR wstr_entry_name
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Log //~ Log
void W32_BootstrapLogs(String logfile_path); void W32_BootstrapLogs();
void W32_Log(i32 level, String msg); void W32_Log(i32 level, String msg);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -349,6 +349,7 @@ void BuildEntryPoint(WaveLaneCtx *lane)
PushStringToList(perm, &cp.defs, Lit("-DIsUnoptimized=1")); PushStringToList(perm, &cp.defs, Lit("-DIsUnoptimized=1"));
PushStringToList(perm, &cp.defs, Lit("-DIsTestingEnabled=0")); PushStringToList(perm, &cp.defs, Lit("-DIsTestingEnabled=0"));
PushStringToList(perm, &cp.defs, Lit("-DIsHotSwappingEnabled=1")); PushStringToList(perm, &cp.defs, Lit("-DIsHotSwappingEnabled=1"));
PushStringToList(perm, &cp.defs, StringF(perm, "-DDefaultAppName=%F", FmtString(cmdline.leaf_layer_name)));
} }
//- Msvc //- Msvc
@ -1015,7 +1016,7 @@ void BuildEntryPoint(WaveLaneCtx *lane)
if (lane->idx == 0) if (lane->idx == 0)
{ {
EchoLine(StringF(perm, "Runtime: %Fs", FmtFloat(SecondsFromNs(TimeNs())))); EchoLine(StringF(perm, "Runtime: %Fs", FmtFloat(SecondsFromNs(TimeNs()), .p = 3)));
ExitNow(GetBuildStatus()); ExitNow(GetBuildStatus());
} }
} }

View File

@ -39,6 +39,10 @@
#define IsHotSwappingEnabled 0 #define IsHotSwappingEnabled 0
#endif #endif
#ifndef DefaultAppName
#define DefaultAppName meta
#endif
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Includes //~ Includes

View File

@ -77,10 +77,8 @@ void PLT_Bootstrap(void);
// NOTE: File paths use forward slash '/' as delimiter // NOTE: File paths use forward slash '/' as delimiter
//- File system helpers //- File system helpers
String PLT_GetWritePath(Arena *arena);
b32 PLT_IsFile(String path); b32 PLT_IsFile(String path);
b32 PLT_IsDir(String path); b32 PLT_IsDir(String path);
void PLT_MkDir(String path);
//- File creation //- File creation
PLT_File PLT_OpenFileRead(String path); PLT_File PLT_OpenFileRead(String path);

View File

@ -212,25 +212,6 @@ void PLT_W32_SyncTimerForever(WaveLaneCtx *lane)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ @hookimpl File system //~ @hookimpl File system
String PLT_GetWritePath(Arena *arena)
{
u16 *p = 0;
// TODO: cache this?
HRESULT result = SHGetKnownFolderPath(
&FOLDERID_LocalAppData,
0,
0,
&p
);
String path = Zi;
if (result == S_OK)
{
path = PLT_W32_StringFromWin32Path(arena, p);
}
CoTaskMemFree(p);
return path;
}
b32 PLT_IsFile(String path) b32 PLT_IsFile(String path)
{ {
TempArena scratch = BeginScratchNoConflict(); TempArena scratch = BeginScratchNoConflict();
@ -249,47 +230,16 @@ b32 PLT_IsDir(String path)
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY); return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY);
} }
void PLT_MkDir(String path) b32 PLT_MkDir(String path)
{ {
TempArena scratch = BeginScratchNoConflict(); TempArena scratch = BeginScratchNoConflict();
i32 err = 0;
{
wchar_t *path_wstr = WstrFromString(scratch.arena, path); wchar_t *path_wstr = WstrFromString(scratch.arena, path);
int err_code = SHCreateDirectory(0, path_wstr); err = SHCreateDirectory(0, path_wstr);
String err = Zi;
switch (err_code)
{
default: break;
case ERROR_BAD_PATHNAME:
{
err = Lit("Bad path name");
} break;
case ERROR_FILENAME_EXCED_RANGE:
{
err = Lit("Path name too long");
} break;
case ERROR_FILE_EXISTS:
{
err = Lit("A file already exists at this location");
} break;
case ERROR_CANCELLED:
{
err = Lit("User canceled the operation");
} break;
}
if (err.len > 0)
{
String msg = StringF(
scratch.arena,
"Failed to create directory \"%F\": %F",
FmtString(path),
FmtString(err)
);
Panic(msg);
} }
EndScratch(scratch); EndScratch(scratch);
return err == ERROR_SUCCESS || err == ERROR_ALREADY_EXISTS || err == ERROR_FILE_EXISTS;
} }
PLT_File PLT_OpenFileRead(String path) PLT_File PLT_OpenFileRead(String path)

View File

@ -2,7 +2,6 @@ S_Ctx S = Zi;
ThreadLocal S_ThreadLocalCtx S_tl = Zi; ThreadLocal S_ThreadLocalCtx S_tl = Zi;
Readonly S_Ent S_NilEnt = { Readonly S_Ent S_NilEnt = {
.last_xf = CompXformIdentity,
.xf = CompXformIdentity, .xf = CompXformIdentity,
.look = { 0, -1 }, .look = { 0, -1 },
}; };
@ -220,6 +219,30 @@ Rng2 S_BoundingBoxFromShape(S_Shape shape)
return result; return result;
} }
S_Shape S_LocalShapeFromEnt(S_Ent *ent)
{
S_Shape result = Zi;
// TODO: This is a temporary hack. Use prefab-lookup table.
if (ent->is_player)
{
result = S_ShapeFromDesc(
.mass = 10,
.count = 1,
.radius = 0.3,
);
}
return result;
}
S_Shape S_WorldShapeFromEnt(S_Ent *ent)
{
S_Shape local = S_LocalShapeFromEnt(ent);
S_Shape world = S_MulXformShape(ent->xf, local);
return world;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Collision //~ Collision
@ -1254,7 +1277,7 @@ void S_TickForever(WaveLaneCtx *lane)
String packed = Zi; String packed = Zi;
if (swapin) if (swapin)
{ {
packed = SwappedStateFromName(frame_arena, Lit("pp_sim.swp")); packed = SwappedStateFromName(frame_arena, Lit("pp_sim"));
} }
S_UnpackedWorld unpacked = S_UnpackWorld(frame_arena, packed); S_UnpackedWorld unpacked = S_UnpackWorld(frame_arena, packed);
@ -1263,6 +1286,10 @@ void S_TickForever(WaveLaneCtx *lane)
world->seed = unpacked.seed; world->seed = unpacked.seed;
world->tick = unpacked.tick; world->tick = unpacked.tick;
world->time_ns = unpacked.time_ns; world->time_ns = unpacked.time_ns;
if (world->seed == 0)
{
TrueRand(StringFromStruct(&world->seed));
}
world->ent_bins_count = Kibi(16); world->ent_bins_count = Kibi(16);
world->ent_bins = PushStructs(world_arena, S_EntBin, world->ent_bins_count); world->ent_bins = PushStructs(world_arena, S_EntBin, world->ent_bins_count);
@ -1279,7 +1306,7 @@ void S_TickForever(WaveLaneCtx *lane)
if (swapout) if (swapout)
{ {
String packed = S_PackWorld(frame_arena, world); String packed = S_PackWorld(frame_arena, world);
WriteSwappedState(Lit("pp_sim.swp"), packed); WriteSwappedState(Lit("pp_sim"), packed);
} }
} }
@ -1309,9 +1336,30 @@ void S_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Update double-buffered entity data //- Update double-buffered entity data
for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) // for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
// {
// ent->last_xf = ent->xf;
// }
//////////////////////////////
//- Process save commands
// FIXME: Only accept save command from local user
b32 should_save = 0;
for (S_CmdNode *cmd_node = input->first_cmd_node; cmd_node; cmd_node = cmd_node->next)
{ {
ent->last_xf = ent->xf; S_Cmd *cmd = &cmd_node->cmd;
if (cmd->kind == S_CmdKind_Save)
{
should_save = 1;
break;
}
}
if (should_save)
{
String path = Lit("HAH");
LogInfoF("Saving world to %F", FmtString(path));
} }
////////////////////////////// //////////////////////////////
@ -1410,7 +1458,8 @@ void S_TickForever(WaveLaneCtx *lane)
{ {
desired_xf = XformWithWorldRotation(xf, AngleFromVec2(ent->look)); desired_xf = XformWithWorldRotation(xf, AngleFromVec2(ent->look));
} }
desired_xf.og = AddVec2(xf.og, MulVec2(ent->move, ent->move_speed)); f32 move_speed = TweakFloat("Player move speed", 6.5, 0, 20);
desired_xf.og = AddVec2(xf.og, MulVec2(ent->move, move_speed * sim_dt));
Vec2 pos_diff = SubVec2(desired_xf.og, xf.og); Vec2 pos_diff = SubVec2(desired_xf.og, xf.og);
f32 angle_diff = UnwindAngleF32(RotationFromXform(desired_xf) - RotationFromXform(xf)); f32 angle_diff = UnwindAngleF32(RotationFromXform(desired_xf) - RotationFromXform(xf));
@ -1436,55 +1485,45 @@ void S_TickForever(WaveLaneCtx *lane)
// for (i32 tile_y = bb_tiles.p0.y; tile_y < bb_tiles.p1.y; ++tile_y)
// for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
// { // {
// for (i32 tile_x = bb_tiles.p0.x; tile_x < bb_tiles.p1.x; ++tile_x) // if (ent->is_player)
// { // {
// Vec2I32 tile_pos = VEC2I32(tile_x, tile_y); // Xform last_xf = ent->last_xf;
// S_TileKind tile = S_TileFromPos(tile_pos); // S_Shape last_world_shape = S_MulXformShape(last_xf, ent->local_shape);
// Xform xf = ent->xf;
// S_Shape world_shape = S_MulXformShape(xf, ent->local_shape);
// Rng2 bb0 = S_BoundingBoxFromShape(last_world_shape);
// Rng2 bb1 = S_BoundingBoxFromShape(world_shape);
// if (constraints_count < max_constraints)
// {
// S_Constraint *constraint = &constraints[constraints_count];
// // TODO: Real constraint data
// constraint->ent0 = ent->key;
// constraint->shape0 = world_shape;
// Rng2 test_rect = Zi;
// test_rect.p0 = VEC2(-1, -1);
// test_rect.p1 = VEC2(1, 1);
// constraint->shape1 = S_ShapeFromDesc(
// .radius = 0.5,
// .count = 4,
// .points[0] = VEC2(test_rect.p0.x, test_rect.p0.y),
// .points[1] = VEC2(test_rect.p1.x, test_rect.p0.y),
// .points[2] = VEC2(test_rect.p1.x, test_rect.p1.y),
// .points[3] = VEC2(test_rect.p0.x, test_rect.p1.y),
// );
// constraints_count += 1;
// }
// } // }
// } // }
for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
{
if (ent->is_player)
{
Xform last_xf = ent->last_xf;
S_Shape last_world_shape = S_MulXformShape(last_xf, ent->local_shape);
Xform xf = ent->xf;
S_Shape world_shape = S_MulXformShape(xf, ent->local_shape);
Rng2 bb0 = S_BoundingBoxFromShape(last_world_shape);
Rng2 bb1 = S_BoundingBoxFromShape(world_shape);
if (constraints_count < max_constraints)
{
S_Constraint *constraint = &constraints[constraints_count];
// TODO: Real constraint data
constraint->ent0 = ent->key;
constraint->shape0 = world_shape;
Rng2 test_rect = Zi;
test_rect.p0 = VEC2(-1, -1);
test_rect.p1 = VEC2(1, 1);
constraint->shape1 = S_ShapeFromDesc(
.radius = 0.5,
.count = 4,
.points[0] = VEC2(test_rect.p0.x, test_rect.p0.y),
.points[1] = VEC2(test_rect.p1.x, test_rect.p0.y),
.points[2] = VEC2(test_rect.p1.x, test_rect.p1.y),
.points[3] = VEC2(test_rect.p0.x, test_rect.p1.y),
);
constraints_count += 1;
}
}
}
////////////////////////////// //////////////////////////////
//- Solve constraints //- Solve constraints
@ -1643,8 +1682,7 @@ void S_TickForever(WaveLaneCtx *lane)
i64 tick_bullets_count = bullets_per_fire; i64 tick_bullets_count = bullets_per_fire;
if (tick_bullets_count > 0) if (tick_bullets_count > 0)
{ {
Xform firer_xf = firer->xf; S_Shape firer_world_shape = S_WorldShapeFromEnt(firer);
S_Shape firer_world_shape = S_MulXformShape(firer_xf, firer->local_shape);
Vec2 pos = S_EdgePointFromShape(firer_world_shape, firer->look); Vec2 pos = S_EdgePointFromShape(firer_world_shape, firer->look);
@ -1706,8 +1744,7 @@ void S_TickForever(WaveLaneCtx *lane)
{ {
if (victim->is_player && !S_MatchKey(victim->key, bullet->bullet_firer)) if (victim->is_player && !S_MatchKey(victim->key, bullet->bullet_firer))
{ {
Xform victim_xf = victim->xf; S_Shape victim_world_shape = S_WorldShapeFromEnt(victim);
S_Shape victim_world_shape = S_MulXformShape(victim_xf, victim->local_shape);
S_RaycastResult entrance_raycast = S_RaycastShape(victim_world_shape, ray_start, ray_dir); S_RaycastResult entrance_raycast = S_RaycastShape(victim_world_shape, ray_start, ray_dir);
Vec2 entrance = entrance_raycast.p; Vec2 entrance = entrance_raycast.p;
@ -1885,8 +1922,7 @@ void S_TickForever(WaveLaneCtx *lane)
{ {
for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
{ {
Xform xf = ent->xf; S_Shape world_shape = S_WorldShapeFromEnt(ent);
S_Shape world_shape = S_MulXformShape(xf, ent->local_shape);
// Draw aabb // Draw aabb
{ {

View File

@ -62,12 +62,8 @@ Struct(S_Ent)
b32 is_player; b32 is_player;
f32 health; f32 health;
Xform last_xf;
Xform xf; Xform xf;
S_Shape local_shape;
f32 move_speed;
Vec2 move; Vec2 move;
Vec2 look; Vec2 look;
f32 fire_held; f32 fire_held;
@ -245,6 +241,7 @@ Struct(S_SnapshotNode)
Enum(S_CmdKind) Enum(S_CmdKind)
{ {
S_CmdKind_Nop, S_CmdKind_Nop,
S_CmdKind_Save,
S_CmdKind_Delta, S_CmdKind_Delta,
S_CmdKind_Control, S_CmdKind_Control,
}; };
@ -394,6 +391,9 @@ S_Shape S_ShapeFromDescEx(S_ShapeDesc desc);
S_Shape S_MulXformShape(Xform xf, S_Shape shape); S_Shape S_MulXformShape(Xform xf, S_Shape shape);
Rng2 S_BoundingBoxFromShape(S_Shape shape); Rng2 S_BoundingBoxFromShape(S_Shape shape);
S_Shape S_LocalShapeFromEnt(S_Ent *ent);
S_Shape S_WorldShapeFromEnt(S_Ent *ent);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Collision //~ Collision

View File

@ -1,7 +1,7 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Tile types //~ Tile types
#define S_WorldPitch 80.0 #define S_WorldPitch 60.0
#define S_TilesPitch (S_WorldPitch * 2) #define S_TilesPitch (S_WorldPitch * 2)
#define S_TilesCount (S_TilesPitch * S_TilesPitch) #define S_TilesCount (S_TilesPitch * S_TilesPitch)

View File

@ -15,6 +15,11 @@ String S_PackWorld(Arena *arena, S_World *src_world)
result.len += StringF(arena, "version: %F\n", FmtUint(S_Tv_Latest)).len; result.len += StringF(arena, "version: %F\n", FmtUint(S_Tv_Latest)).len;
result.len += StringF(arena, "\n").len;
result.len += StringF(arena, "seed: 0x%F\n", FmtHex(src_world->seed)).len;
result.len += StringF(arena, "tick: %F\n", FmtSint(src_world->tick)).len;
result.len += StringF(arena, "time: %F\n", FmtSint(src_world->time_ns)).len;
// Pack entities // Pack entities
// FIXME: Precision // FIXME: Precision
result.len += PushString(arena, Lit("\nentities:\n")).len; result.len += PushString(arena, Lit("\nentities:\n")).len;
@ -24,14 +29,23 @@ String S_PackWorld(Arena *arena, S_World *src_world)
result.len += StringF(arena, " 0x%F:\n", FmtHex(ent->key.v)).len; result.len += StringF(arena, " 0x%F:\n", FmtHex(ent->key.v)).len;
result.len += PushString(arena, Lit(" {\n")).len; result.len += PushString(arena, Lit(" {\n")).len;
{ {
result.len += StringF(arena, " props: \n").len;
result.len += StringF(arena, " {\n").len;
{
if (ent->is_player)
{
result.len += PushString(arena, Lit(" player\n")).len;
}
if (ent->is_bullet)
{
result.len += PushString(arena, Lit(" bullet\n")).len;
}
}
result.len += StringF(arena, " }\n").len;
result.len += StringF(arena, " pos: \"%F\"\n", FmtFloat2(ent->xf.og)).len; result.len += StringF(arena, " pos: \"%F\"\n", FmtFloat2(ent->xf.og)).len;
result.len += StringF(arena, " rot: \"%F\"\n", FmtFloat2(RightFromXform(ent->xf))).len; result.len += StringF(arena, " rot: \"%F\"\n", FmtFloat2(RightFromXform(ent->xf))).len;
result.len += StringF(arena, " exists: \"%F\"\n", FmtFloat(ent->exists)).len; result.len += StringF(arena, " exists: \"%F\"\n", FmtFloat(ent->exists)).len;
result.len += StringF(arena, " look: \"%F\"\n", FmtFloat2(ent->look)).len; result.len += StringF(arena, " look: \"%F\"\n", FmtFloat2(ent->look)).len;
if (ent->is_player)
{
result.len += PushString(arena, Lit(" player: true\n")).len;
}
} }
result.len += PushString(arena, Lit(" }\n")).len; result.len += PushString(arena, Lit(" }\n")).len;
} }
@ -69,7 +83,7 @@ S_UnpackedWorld S_UnpackWorld(Arena *arena, String packed)
CR_Item *root = CR_ItemFromString(scratch.arena, packed); CR_Item *root = CR_ItemFromString(scratch.arena, packed);
// Get version // Unpack version
S_Tv version = 0; S_Tv version = 0;
for (CR_Item *root_item = root->first; root_item; root_item = root_item->next) for (CR_Item *root_item = root->first; root_item; root_item = root_item->next)
{ {
@ -82,47 +96,67 @@ S_UnpackedWorld S_UnpackWorld(Arena *arena, String packed)
for (CR_Item *top_item = root->first; top_item; top_item = top_item->next) for (CR_Item *top_item = root->first; top_item; top_item = top_item->next)
{ {
// Parse entities // Unpack metadata
if (MatchString(top_item->name, Lit("seed")))
{
result.seed = CR_IntFromString(top_item->value);
}
if (MatchString(top_item->name, Lit("tick")))
{
result.tick = CR_IntFromString(top_item->value);
}
if (MatchString(top_item->name, Lit("time")))
{
result.time_ns = CR_IntFromString(top_item->value);
}
// Unpack entities
if (MatchString(top_item->name, Lit("entities"))) if (MatchString(top_item->name, Lit("entities")))
{ {
for (CR_Item *ent_item = top_item->first; ent_item; ent_item = ent_item->next) for (CR_Item *ent_item = top_item->first; ent_item; ent_item = ent_item->next)
{ {
S_Ent *ent = S_PushTempEnt(arena, &result.ents); S_Ent *ent = S_PushTempEnt(arena, &result.ents);
ent->key = (S_Key) { .v = CR_IntFromString(ent_item->name) }; ent->key = (S_Key) { .v = CR_IntFromString(ent_item->name) };
for (CR_Item *prop = ent_item->first; prop; prop = prop->next) for (CR_Item *attr = ent_item->first; attr; attr = attr->next)
{ {
if (MatchString(prop->name, Lit("pos"))) if (MatchString(attr->name, Lit("props")))
{ {
Vec2 pos = CR_Vec2FromString(prop->value); for (CR_Item *prop = attr->first; prop; prop = prop->next)
ent->xf.og = pos;
}
if (MatchString(prop->name, Lit("rot")))
{ {
Vec2 rot = CR_Vec2FromString(prop->value); if (MatchString(prop->value, Lit("player")))
ent->xf = XformWithWorldRotation(ent->xf, AngleFromVec2(rot));
}
if (MatchString(prop->name, Lit("exists")))
{
ent->exists = CR_FloatFromString(prop->value);
}
if (MatchString(prop->name, Lit("health")))
{
ent->health = CR_FloatFromString(prop->value);
}
if (MatchString(prop->name, Lit("look")))
{
Vec2 look = CR_Vec2FromString(prop->value);
ent->look = look;
}
if (MatchString(prop->name, Lit("player")) && CR_BoolFromString(prop->value))
{ {
ent->is_player = 1; ent->is_player = 1;
ent->has_weapon = 1; ent->has_weapon = 1;
} }
// if (MatchString(prop->name, Lit("bullet")) && CR_BoolFromString(prop->value)) if (MatchString(prop->value, Lit("bullet")))
// { {
// ent->is_bullet = 1; ent->is_bullet = 1;
// } }
}
}
if (MatchString(attr->name, Lit("pos")))
{
Vec2 pos = CR_Vec2FromString(attr->value);
ent->xf.og = pos;
}
if (MatchString(attr->name, Lit("rot")))
{
Vec2 rot = CR_Vec2FromString(attr->value);
ent->xf = XformWithWorldRotation(ent->xf, AngleFromVec2(rot));
}
if (MatchString(attr->name, Lit("exists")))
{
ent->exists = CR_FloatFromString(attr->value);
}
if (MatchString(attr->name, Lit("health")))
{
ent->health = CR_FloatFromString(attr->value);
}
if (MatchString(attr->name, Lit("look")))
{
Vec2 look = CR_Vec2FromString(attr->value);
ent->look = look;
}
} }
} }
} }
@ -148,160 +182,3 @@ S_UnpackedWorld S_UnpackWorld(Arena *arena, String packed)
EndScratch(scratch); EndScratch(scratch);
return result; return result;
}; };
// ////////////////////////////////////////////////////////////
// //~ Transcode
// S_TranscodeResult S_TranscodeWorld(Arena *arena, S_World *src_world, String src_packed, b32 pack)
// {
// S_TranscodeResult result = Zi;
// result.ok = 1;
// result.version = S_Tv_Latest;
// //////////////////////////////
// //- Init bitbuff
// u32 level_magic = 0xa2bf209c;
// BB_Buff bb = Zi;
// BB_Writer bw = Zi;
// BB_Reader br = Zi;
// if (pack)
// {
// bb = BB_AcquireDynamicBuff(Gibi(4));
// bw = BB_WriterFromBuff(&bb);
// BB_WriteUBits(&bw, level_magic, 32);
// BB_WriteUBits(&bw, result.version, 32);
// }
// else
// {
// bb = BB_BuffFromString(src_packed);
// br = BB_ReaderFromBuff(&bb);
// result.ok = BB_ReadUBits(&br, 32) == level_magic;
// result.version = (S_Tv)BB_ReadUBits(&br, 32);
// result.unpacked = PushStruct(arena, S_World);
// }
// //////////////////////////////
// //- Transcode world metadata
// if (pack)
// {
// BB_WriteUBits(&bw, src_world->seed, 64);
// BB_WriteIBits(&bw, src_world->tick, 64);
// BB_WriteIBits(&bw, src_world->time_ns, 64);
// }
// else
// {
// result.unpacked->seed = BB_ReadUBits(&br, 64);
// result.unpacked->tick = BB_ReadIBits(&br, 64);
// result.unpacked->time_ns = BB_ReadIBits(&br, 64);
// }
// //////////////////////////////
// //- Transcode tiles
// // TODO: Compress tile data
// if (pack)
// {
// String tiles_str = Zi;
// tiles_str.len = S_TilesCount;
// tiles_str.text = src_world->tiles;
// BB_WriteUBits(&bw, tiles_str.len, 64);
// BB_WriteBytes(&bw, tiles_str);
// }
// else
// {
// u64 raw_tiles_count = BB_ReadUBits(&br, 64);
// u8 *raw_tiles = BB_ReadBytesRaw(&br, raw_tiles_count);
// if (raw_tiles && raw_tiles_count == S_TilesCount)
// {
// result.unpacked->tiles = PushStructsNoZero(arena, u8, raw_tiles_count);
// CopyBytes(result.unpacked->tiles, raw_tiles, raw_tiles_count);
// }
// else
// {
// result.unpacked->tiles = PushStructs(arena, u8, S_TilesCount);
// result.ok = 0;
// }
// }
// //////////////////////////////
// //- Transcode entities
// // TODO: Compress entity data
// if (result.ok)
// {
// if (pack)
// {
// u32 ent_size = sizeof(S_Ent);
// u32 ent_align = alignof(S_Ent);
// i64 ents_count = src_world->ents_count;
// BB_WriteUBits(&bw, ent_size, 32);
// BB_WriteUBits(&bw, ent_align, 32);
// BB_WriteUBits(&bw, ents_count, 64);
// for (S_Ent *ent = S_FirstEnt(src_world); ent->valid; ent = S_NextEnt(ent))
// {
// BB_WriteBytes(&bw, StringFromStruct(ent));
// }
// }
// else
// {
// i64 ent_size = BB_ReadUBits(&br, 32);
// i64 ent_align = BB_ReadUBits(&br, 32);
// i64 ents_count = BB_ReadUBits(&br, 64);
// if (ent_size == sizeof(S_Ent) && ent_align == alignof(S_Ent))
// {
// for (i64 i = 0; i < ents_count; ++i)
// {
// S_Ent *raw_ent = (S_Ent *)BB_ReadBytesRaw(&br, sizeof(S_Ent));
// if (raw_ent)
// {
// S_Ent *ent = PushStructNoZero(arena, S_Ent);
// {
// CopyBytes(ent, raw_ent, ent_size);
// ent->valid = 1;
// }
// DllQueuePush(result.unpacked->first_ent, result.unpacked->last_ent, ent);
// result.unpacked->ents_count += 1;
// }
// else
// {
// result.ok = 0;
// break;
// }
// }
// }
// else
// {
// result.ok = 0;
// }
// }
// }
// //////////////////////////////
// //- Finalize
// if (result.ok)
// {
// if (pack)
// {
// result.packed = BB_GetWritten(arena, &bw);
// BB_ReleaseDynamicBuff(&bb);
// }
// else
// {
// if (!result.unpacked->seed)
// {
// TrueRand(StringFromStruct(&result.unpacked->seed));
// }
// }
// }
// return result;
// }

View File

@ -274,7 +274,7 @@ V_WidgetTheme V_GetWidgetTheme(void)
theme.icon_font = UI_BuiltinIconFont(); theme.icon_font = UI_BuiltinIconFont();
// theme.font_size = 14; // theme.font_size = 14;
theme.font_size = TweakFloat("Font size", 14, 6, 50, .precision = 0); theme.font_size = TweakFloat("Font size", 14, 8, 24, .precision = 0);
theme.h1 = 2.00; theme.h1 = 2.00;
theme.h2 = 1.50; theme.h2 = 1.50;
theme.h3 = 1.25; theme.h3 = 1.25;
@ -558,7 +558,7 @@ void V_TickForever(WaveLaneCtx *lane)
} }
else else
{ {
String swap_encoded = SwappedStateFromName(frame->arena, Lit("pp_vis.swp")); String swap_encoded = SwappedStateFromName(frame->arena, Lit("pp_vis"));
bb = BB_BuffFromString(swap_encoded); bb = BB_BuffFromString(swap_encoded);
br = BB_ReaderFromBuff(&bb); br = BB_ReaderFromBuff(&bb);
} }
@ -586,7 +586,7 @@ void V_TickForever(WaveLaneCtx *lane)
// Write swapout // Write swapout
if (swapout) if (swapout)
{ {
WriteSwappedState(Lit("pp_vis.swp"), STRING(BB_GetNumBytesWritten(&bw), BB_GetWrittenRaw(&bw))); WriteSwappedState(Lit("pp_vis"), STRING(BB_GetNumBytesWritten(&bw), BB_GetWrittenRaw(&bw)));
} }
} }
} }
@ -861,7 +861,7 @@ void V_TickForever(WaveLaneCtx *lane)
look_ratio.y = 0.25; look_ratio.y = 0.25;
look_ratio.x = look_ratio.y / (16.0 / 9.0); look_ratio.x = look_ratio.y / (16.0 / 9.0);
S_Ent *player = S_EntFromKey(world, V.player_key); S_Ent *player = S_EntFromKey(world, V.player_key);
target_camera_pos = MulXformV2(player->xf, player->local_shape.centroid); target_camera_pos = S_WorldShapeFromEnt(player).centroid;
target_camera_pos = AddVec2(target_camera_pos, MulVec2Vec2(player->look, look_ratio)); target_camera_pos = AddVec2(target_camera_pos, MulVec2Vec2(player->look, look_ratio));
target_camera_zoom = 1; target_camera_zoom = 1;
} }
@ -1010,7 +1010,7 @@ void V_TickForever(WaveLaneCtx *lane)
S_Shape cursor_shape = S_ShapeFromDesc(.count = 1, .points = { frame->world_cursor }); S_Shape cursor_shape = S_ShapeFromDesc(.count = 1, .points = { frame->world_cursor });
for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
{ {
S_Shape ent_shape = S_MulXformShape(ent->xf, ent->local_shape); S_Shape ent_shape = S_WorldShapeFromEnt(ent);
b32 is_hovered = S_CollisionResultFromShapes(ent_shape, cursor_shape).collision_points_count > 0; b32 is_hovered = S_CollisionResultFromShapes(ent_shape, cursor_shape).collision_points_count > 0;
if (is_hovered) if (is_hovered)
{ {
@ -2379,9 +2379,8 @@ void V_TickForever(WaveLaneCtx *lane)
UI_Push(BackgroundColor, color); UI_Push(BackgroundColor, color);
UI_Push(Width, UI_GROW(1, 0)); UI_Push(Width, UI_GROW(1, 0));
UI_Push(Height, UI_FNT(1.5, 1)); UI_Push(Height, UI_FNT(1.5, 1));
UI_Push(BorderColor, Rgb(0.25, 0.25, 0.25));
UI_Push(Rounding, UI_RPIX(0)); UI_Push(Rounding, UI_RPIX(0));
UI_Push(BorderSize, 1); UI_Push(BorderSize, 0);
UI_Push(ChildAlignment, UI_Region_Left); UI_Push(ChildAlignment, UI_Region_Left);
UI_PushCP(UI_BuildRow()); UI_PushCP(UI_BuildRow());
{ {
@ -2517,13 +2516,7 @@ void V_TickForever(WaveLaneCtx *lane)
*ent = S_NilEnt; *ent = S_NilEnt;
ent->key = V.player_key; ent->key = V.player_key;
ent->xf = XformFromPos(player_pos); ent->xf = XformFromPos(player_pos);
ent->move_speed = 0.075;
ent->is_player = 1; ent->is_player = 1;
ent->local_shape = S_ShapeFromDesc(
.mass = 10,
.count = 1,
.radius = 0.3,
);
ent->has_weapon = 1; ent->has_weapon = 1;
ent->exists = 1; ent->exists = 1;
} }
@ -2537,13 +2530,7 @@ void V_TickForever(WaveLaneCtx *lane)
*ent = S_NilEnt; *ent = S_NilEnt;
ent->key = S_RandKey(); ent->key = S_RandKey();
ent->xf = XformFromPos(frame->world_cursor); ent->xf = XformFromPos(frame->world_cursor);
ent->move_speed = 0.075;
ent->is_player = 1; ent->is_player = 1;
ent->local_shape = S_ShapeFromDesc(
.mass = 10,
.count = 1,
.radius = 0.3,
);
ent->has_weapon = 1; ent->has_weapon = 1;
ent->exists = 1; ent->exists = 1;
} break; } break;
@ -2560,6 +2547,14 @@ void V_TickForever(WaveLaneCtx *lane)
} }
} break; } break;
case V_CmdKind_save_level:
{
if (frame->is_editing)
{
S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Save);
}
} break;
case V_CmdKind_clear_particles: case V_CmdKind_clear_particles:
{ {
should_clear_particles = 1; should_clear_particles = 1;
@ -2582,7 +2577,7 @@ void V_TickForever(WaveLaneCtx *lane)
f32 fire_presses = fire_held && !last_frame->held_buttons[Button_M1]; f32 fire_presses = fire_held && !last_frame->held_buttons[Button_M1];
Vec2 look = Zi; Vec2 look = Zi;
{ {
Vec2 center = MulXformV2(player->xf, player->local_shape.centroid); Vec2 center = S_WorldShapeFromEnt(player).centroid;
look = SubVec2(frame->world_cursor, center); look = SubVec2(frame->world_cursor, center);
} }
if (frame->is_editing) if (frame->is_editing)

View File

@ -8,6 +8,7 @@
X(zoom_in, Zoom In, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelUp ), ) \ X(zoom_in, Zoom In, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelUp ), ) \
X(zoom_out, Zoom Out, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelDown ), ) \ X(zoom_out, Zoom Out, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelDown ), ) \
X(toggle_editor, Toggle Editor, V_CmdDescFlag_None, V_HOTKEY( Button_F1 ), ) \ X(toggle_editor, Toggle Editor, V_CmdDescFlag_None, V_HOTKEY( Button_F1 ), ) \
X(save_level, Save level, V_CmdDescFlag_None, V_HOTKEY( Button_S, .ctrl = 1 ), ) \
X(toggle_ui_debug, Toggle UI Debug, V_CmdDescFlag_None, V_HOTKEY( Button_F5 ), ) \ X(toggle_ui_debug, Toggle UI Debug, V_CmdDescFlag_None, V_HOTKEY( Button_F5 ), ) \
X(toggle_console, Toggle Developer Console, V_CmdDescFlag_None, V_HOTKEY( Button_GraveAccent ), ) \ X(toggle_console, Toggle Developer Console, V_CmdDescFlag_None, V_HOTKEY( Button_GraveAccent ), ) \
X(toggle_fullscreen, Toggle Fullscreen Mode, V_CmdDescFlag_None, V_HOTKEY( Button_Enter, .alt = 1 ) ) \ X(toggle_fullscreen, Toggle Fullscreen Mode, V_CmdDescFlag_None, V_HOTKEY( Button_Enter, .alt = 1 ) ) \
@ -15,7 +16,7 @@
X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_Q ), ) \ X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_Q ), ) \
X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \ X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \
X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \ X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \
X(reset_world, Reset world, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \ X(reset_world, Reset world, V_CmdDescFlag_None, V_HOTKEY( Button_R, .ctrl = 1, .alt = 1 ), ) \
X(clear_particles, Clear particles, V_CmdDescFlag_None, V_HOTKEY( Button_C ), ) \ X(clear_particles, Clear particles, V_CmdDescFlag_None, V_HOTKEY( Button_C ), ) \
/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */