353 lines
11 KiB
C
353 lines
11 KiB
C
////////////////////////////////////////////////////////////
|
|
//~ World
|
|
|
|
String P_PackWorld(Arena *arena, P_World *src_world)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
TempArena scratch = BeginScratch(arena);
|
|
P_Frame *src_frame = src_world->last_frame;
|
|
|
|
result.len += StringF(arena, "version: %F\n", FmtUint(P_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, "time: %F\n", FmtSint(src_frame->time_ns)).len;
|
|
result.len += StringF(arena, "tick: %F\n", FmtSint(src_frame->tick)).len;
|
|
|
|
// Pack entities
|
|
// FIXME: Precision
|
|
result.len += PushString(arena, Lit("\nentities:\n")).len;
|
|
result.len += PushString(arena, Lit("{\n")).len;
|
|
for (P_Ent *ent = P_FirstEnt(src_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
|
|
{
|
|
// TODO: Pack bullets
|
|
b32 should_pack = 1;
|
|
if (ent->is_bullet)
|
|
{
|
|
should_pack = 0;
|
|
}
|
|
if (should_pack)
|
|
{
|
|
result.len += StringF(arena, " 0x%F:\n", FmtHex(ent->key.v)).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_guy)
|
|
{
|
|
result.len += PushString(arena, Lit(" guy\n")).len;
|
|
}
|
|
if (ent->is_bot)
|
|
{
|
|
result.len += PushString(arena, Lit(" bot\n")).len;
|
|
}
|
|
if (ent->is_weapon)
|
|
{
|
|
result.len += PushString(arena, Lit(" weapon\n")).len;
|
|
}
|
|
if (ent->is_bullet)
|
|
{
|
|
result.len += PushString(arena, Lit(" bullet\n")).len;
|
|
}
|
|
if (ent->is_guy_spawn)
|
|
{
|
|
result.len += PushString(arena, Lit(" guy_spawn\n")).len;
|
|
}
|
|
}
|
|
result.len += StringF(arena, " }\n").len;
|
|
result.len += StringF(arena, " created_at_ns: \"%F\"\n", FmtSint(ent->created_at_ns)).len;
|
|
result.len += StringF(arena, " created_at_tick: \"%F\"\n", FmtSint(ent->created_at_tick)).len;
|
|
result.len += StringF(arena, " pos: \"%F\"\n", FmtFloat2(ent->xf.t)).len;
|
|
result.len += StringF(arena, " angle: \"%F\"\n", FmtFloat(AngleFromVec2(ent->xf.r))).len;
|
|
result.len += StringF(arena, " look: \"%F\"\n", FmtFloat2(ent->control.look)).len;
|
|
result.len += StringF(arena, " exists: \"%F\"\n", FmtFloat(ent->exists)).len;
|
|
result.len += StringF(arena, " look: \"%F\"\n", FmtFloat2(ent->control.look)).len;
|
|
result.len += StringF(arena, " health: \"%F\"\n", FmtFloat(ent->health)).len;
|
|
result.len += StringF(arena, " guy: \"0x%F\"\n", FmtHex(ent->guy.v)).len;
|
|
result.len += StringF(arena, " source: \"0x%F\"\n", FmtHex(ent->source.v)).len;
|
|
result.len += StringF(arena, " kills: \"%F\"\n", FmtFloat(ent->kills)).len;
|
|
result.len += StringF(arena, " deaths: \"%F\"\n", FmtFloat(ent->deaths)).len;
|
|
result.len += StringF(arena, " text: \"%F\"\n", FmtString(CR_SanitizeString(scratch.arena, STRING(ent->string_len, ent->string_text)))).len;
|
|
}
|
|
result.len += PushString(arena, Lit(" }\n")).len;
|
|
}
|
|
}
|
|
result.len += PushString(arena, Lit("}\n")).len;
|
|
|
|
// Pack tiles
|
|
// TODO: Compress tile data
|
|
result.len += PushString(arena, Lit("\ntiles:\n")).len;
|
|
result.len += PushString(arena, Lit("{\n")).len;
|
|
{
|
|
String tiles_str = Base64FromString(scratch.arena, STRING(P_TilesCount, src_world->tiles));
|
|
u64 tile_chars_per_line = 128;
|
|
u64 tile_char_pos = 0;
|
|
while (tile_char_pos < tiles_str.len)
|
|
{
|
|
result.len += PushString(arena, Lit(" ")).len;
|
|
String src = STRING(tile_chars_per_line, tiles_str.text + tile_char_pos);
|
|
src.len = MinU64(tile_chars_per_line, tiles_str.len - tile_char_pos);
|
|
result.len += PushString(arena, src).len;
|
|
result.len += PushString(arena, Lit("\n")).len;
|
|
tile_char_pos += tile_chars_per_line;
|
|
}
|
|
|
|
}
|
|
result.len += PushString(arena, Lit("}\n")).len;
|
|
|
|
EndScratch(scratch);
|
|
return result;
|
|
}
|
|
|
|
P_UnpackedWorld P_UnpackWorld(Arena *arena, String packed)
|
|
{
|
|
P_UnpackedWorld result = Zi;
|
|
TempArena scratch = BeginScratch(arena);
|
|
|
|
CR_Item *root = CR_ItemFromString(scratch.arena, packed);
|
|
|
|
// Unpack version
|
|
P_Tv version = 0;
|
|
for (CR_Item *root_item = root->first; root_item; root_item = root_item->next)
|
|
{
|
|
if (MatchString(root_item->name, Lit("version")))
|
|
{
|
|
version = CR_IntFromString(root_item->value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (CR_Item *top_item = root->first; top_item; top_item = top_item->next)
|
|
{
|
|
// Unpack metadata
|
|
if (MatchString(top_item->name, Lit("seed")))
|
|
{
|
|
result.seed = CR_IntFromString(top_item->value);
|
|
}
|
|
if (MatchString(top_item->name, Lit("time")))
|
|
{
|
|
result.time_ns = CR_IntFromString(top_item->value);
|
|
}
|
|
if (MatchString(top_item->name, Lit("tick")))
|
|
{
|
|
result.tick = CR_IntFromString(top_item->value);
|
|
}
|
|
|
|
// Unpack entities
|
|
if (MatchString(top_item->name, Lit("entities")))
|
|
{
|
|
for (CR_Item *ent_item = top_item->first; ent_item; ent_item = ent_item->next)
|
|
{
|
|
P_Ent *ent = P_PushTempEnt(arena, &result.ents);
|
|
ent->key = (P_EntKey) { .v = CR_IntFromString(ent_item->name) };
|
|
for (CR_Item *attr = ent_item->first; attr; attr = attr->next)
|
|
{
|
|
if (MatchString(attr->name, Lit("props")))
|
|
{
|
|
for (CR_Item *prop = attr->first; prop; prop = prop->next)
|
|
{
|
|
if (MatchString(prop->value, Lit("player")))
|
|
{
|
|
ent->is_player = 1;
|
|
}
|
|
if (MatchString(prop->value, Lit("guy")))
|
|
{
|
|
ent->is_guy = 1;
|
|
}
|
|
if (MatchString(prop->value, Lit("bot")))
|
|
{
|
|
ent->is_bot = 1;
|
|
}
|
|
if (MatchString(prop->value, Lit("weapon")))
|
|
{
|
|
ent->is_weapon = 1;
|
|
}
|
|
if (MatchString(prop->value, Lit("bullet")))
|
|
{
|
|
ent->is_bullet = 1;
|
|
}
|
|
if (MatchString(prop->value, Lit("guy_spawn")))
|
|
{
|
|
ent->is_guy_spawn = 1;
|
|
}
|
|
}
|
|
}
|
|
if (MatchString(attr->name, Lit("created_at_ns")))
|
|
{
|
|
ent->created_at_ns = CR_IntFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("created_at_tick")))
|
|
{
|
|
ent->created_at_tick = CR_IntFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("pos")))
|
|
{
|
|
ent->xf.t = CR_Vec2FromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("angle")))
|
|
{
|
|
ent->xf.r = Vec2FromAngle(CR_FloatFromString(attr->value));
|
|
}
|
|
if (MatchString(attr->name, Lit("look")))
|
|
{
|
|
ent->control.look = CR_Vec2FromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("exists")))
|
|
{
|
|
ent->exists = CR_FloatFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("look")))
|
|
{
|
|
ent->control.look = CR_Vec2FromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("health")))
|
|
{
|
|
ent->health = CR_FloatFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("guy")))
|
|
{
|
|
ent->guy.v = CR_IntFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("source")))
|
|
{
|
|
ent->source.v = CR_IntFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("kills")))
|
|
{
|
|
ent->kills = CR_FloatFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("deaths")))
|
|
{
|
|
ent->deaths = CR_FloatFromString(attr->value);
|
|
}
|
|
if (MatchString(attr->name, Lit("text")))
|
|
{
|
|
P_SetEntString(ent, attr->value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse tiles
|
|
String tiles_base64 = Zi;
|
|
tiles_base64.text = ArenaNext(scratch.arena, u8);
|
|
for (CR_Item *tile_item = top_item->first; tile_item; tile_item = tile_item->next)
|
|
{
|
|
if (tile_item->name.len == 0)
|
|
{
|
|
tiles_base64.len += PushString(scratch.arena, tile_item->value).len;
|
|
}
|
|
}
|
|
if (StringLenFromBase64Len(tiles_base64.len) == P_TilesCount)
|
|
{
|
|
result.tiles = StringFromBase64(arena, tiles_base64).text;
|
|
}
|
|
}
|
|
|
|
if (!result.tiles)
|
|
{
|
|
result.tiles = PushStructs(arena, u8, P_TilesCount);
|
|
}
|
|
|
|
EndScratch(scratch);
|
|
return result;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Message
|
|
|
|
String P_PackMessages(BB_Writer *bbw, P_MsgList msgs)
|
|
{
|
|
u64 pos_start = BB_GetNumBytesWritten(bbw);
|
|
{
|
|
for (P_MsgNode *msg_node = msgs.first; msg_node; msg_node = msg_node->next)
|
|
{
|
|
// TODO: Compress
|
|
P_Msg *msg = &msg_node->msg;
|
|
BB_WriteBit(bbw, 0); // Not raw
|
|
BB_WriteBytes(bbw, StringFromStruct(msg));
|
|
BB_WriteString(bbw, msg->data);
|
|
}
|
|
}
|
|
u64 pos_end = BB_GetNumBytesWritten(bbw);
|
|
String result = Zi;
|
|
result.text = BB_GetWrittenRaw(bbw) + pos_start;
|
|
result.len = pos_end - pos_start;
|
|
return result;
|
|
}
|
|
|
|
P_MsgList P_UnpackMessages(Arena *arena, String packed, NET_Key sender)
|
|
{
|
|
P_MsgList result = Zi;
|
|
BB_Buff bb = BB_BuffFromString(packed);
|
|
BB_Reader bbr = BB_ReaderFromBuff(&bb);
|
|
b32 done = 0;
|
|
while (!done)
|
|
{
|
|
//- Read
|
|
P_Msg msg = Zi;
|
|
{
|
|
b32 is_raw = BB_ReadBit(&bbr);
|
|
if (is_raw)
|
|
{
|
|
msg.kind = P_MsgKind_Raw;
|
|
msg.data = packed;
|
|
done = 1;
|
|
}
|
|
else
|
|
{
|
|
u8 *raw_src_msg = BB_ReadBytesRaw(&bbr, sizeof(P_Msg));
|
|
if (raw_src_msg != 0)
|
|
{
|
|
CopyStruct(&msg, raw_src_msg);
|
|
String data_str = BB_ReadStringRaw(&bbr);
|
|
msg.data = data_str;
|
|
}
|
|
else
|
|
{
|
|
done = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//- Validate
|
|
b32 skip = 0;
|
|
{
|
|
if (msg.kind < P_MsgKind_None || msg.kind > P_MsgKind_COUNT)
|
|
{
|
|
skip = 1;
|
|
}
|
|
if (msg.prefab < P_PrefabKind_None || msg.prefab > P_PrefabKind_COUNT)
|
|
{
|
|
skip = 1;
|
|
}
|
|
}
|
|
|
|
if (!skip)
|
|
{
|
|
//- Sanitize
|
|
{
|
|
msg.src = sender;
|
|
msg.dst = NET_NilKey;
|
|
msg.xf = NormXform(msg.xf);
|
|
msg.data = PushString(arena, msg.data);
|
|
}
|
|
|
|
//- Collect
|
|
{
|
|
P_MsgNode *msg_node = PushStruct(arena, P_MsgNode);
|
|
DllQueuePush(result.first, result.last, msg_node);
|
|
result.count += 1;
|
|
msg_node->msg = msg;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|